diff --git a/modules/ticketscesu/__init__.py b/modules/ticketscesu/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed7b80ad3df62f750d4677a0a9a85a6a7c398361
--- /dev/null
+++ b/modules/ticketscesu/__init__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2019 Antoine BOSSY
+#
+# This file is part of a weboob module.
+#
+# This weboob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This weboob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this weboob module. If not, see .
+
+from __future__ import unicode_literals
+
+
+from .module import TicketsCesuModule
+
+
+__all__ = ['TicketsCesuModule']
diff --git a/modules/ticketscesu/browser.py b/modules/ticketscesu/browser.py
new file mode 100644
index 0000000000000000000000000000000000000000..646b4629cae33fdd211e5a5ddfcbde757d83d87e
--- /dev/null
+++ b/modules/ticketscesu/browser.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2019 Antoine BOSSY
+#
+# This file is part of a weboob module.
+#
+# This weboob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This weboob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this weboob module. If not, see .
+
+from __future__ import unicode_literals
+
+
+from weboob.browser import LoginBrowser, URL, need_login
+from weboob.exceptions import ActionNeeded
+
+from .pages import AccountsPage, LoginPage, ProfilePage
+
+
+class TicketCesuBrowser(LoginBrowser):
+ BASEURL = 'https://ebeneficiaire.cesu-as.fr'
+
+ login_page = URL('/login.aspx', LoginPage)
+ profile_page = URL('/customerManagement/ProfileManagement.aspx', ProfilePage)
+ accounts_page = URL('/PaymentManagement/PaymentAccountInfoFullDemat.aspx', AccountsPage)
+
+
+ def do_login(self):
+ self.login_page.go().login(login=self.username, password=self.password)
+
+ if self.profile_page.is_here():
+ raise ActionNeeded('Please agree CGU on the CESU website.')
+
+ @need_login
+ def get_accounts(self):
+ return self.accounts_page.go().get_accounts()
+
+ @need_login
+ def get_history(self, id):
+ accounts = self.get_accounts()
+
+ account = None
+ for acc in accounts:
+ if acc.id == id:
+ account = acc
+
+ if account and self.accounts_page.is_here():
+ self.page.go_to_transaction_page(account._page)
+ return self.page.get_transactions()
+
+ return []
diff --git a/modules/ticketscesu/module.py b/modules/ticketscesu/module.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c5d9b1fd307fddae7fb618757a0640363148afb
--- /dev/null
+++ b/modules/ticketscesu/module.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2019 Antoine BOSSY
+#
+# This file is part of a weboob module.
+#
+# This weboob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This weboob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this weboob module. If not, see .
+
+from __future__ import unicode_literals
+
+from weboob.capabilities.base import find_object
+from weboob.capabilities.bank import CapBank, Account, AccountNotFound
+
+from weboob.tools.backend import Module, BackendConfig
+from weboob.tools.value import ValueBackendPassword, Value
+
+from .browser import TicketCesuBrowser
+
+
+__all__ = ['TicketsCesuModule']
+
+
+class TicketsCesuModule(Module, CapBank):
+ NAME = 'ticketscesu'
+ DESCRIPTION = 'Tickets CESU Endered'
+ MAINTAINER = 'Antoine BOSSY'
+ EMAIL = 'mail+github@abossy.fr'
+ LICENSE = 'LGPLv3+'
+ VERSION = '1.6'
+
+ BROWSER = TicketCesuBrowser
+
+ CONFIG = BackendConfig(
+ Value('login', label='Identifiant', masked=False),
+ ValueBackendPassword('password', label='Code secret', required=True)
+ )
+
+ def create_default_browser(self):
+ return self.create_browser(self.config['login'].get(), self.config['password'].get())
+
+ def get_account(self, id):
+ """
+ Get an account from its ID.
+
+ :param id: ID of the account
+ :type id: :class:`str`
+ :rtype: :class:`Account`
+ :raises: :class:`AccountNotFound`
+ """
+ return find_object(self.iter_accounts(), id=id, error=AccountNotFound)
+
+ def iter_accounts(self):
+ """
+ Iter accounts.
+
+ :rtype: iter[:class:`Account`]
+ """
+ return self.browser.get_accounts()
+
+ def iter_coming(self, account):
+ """
+ Iter coming transactions on a specific account.
+
+ :param account: account to get coming transactions
+ :type account: :class:`Account`
+ :rtype: iter[:class:`Transaction`]
+ :raises: :class:`AccountNotFound`
+ """
+ raise NotImplementedError()
+
+ def iter_history(self, account):
+ """
+ Iter history of transactions on a specific account.
+
+ :param account: account to get history
+ :type account: :class:`Account`
+ :rtype: iter[:class:`Transaction`]
+ :raises: :class:`AccountNotFound`
+ """
+ return self.browser.get_history(account.id)
+
+ def iter_resources(self, objs, split_path):
+ """
+ Iter resources.
+
+ Default implementation of this method is to return on top-level
+ all accounts (by calling :func:`iter_accounts`).
+
+ :param objs: type of objects to get
+ :type objs: tuple[:class:`BaseObject`]
+ :param split_path: path to discover
+ :type split_path: :class:`list`
+ :rtype: iter[:class:`BaseObject`]
+ """
+ if Account in objs:
+ self._restrict_level(split_path)
+ return self.iter_accounts()
+
+ return []
diff --git a/modules/ticketscesu/pages.py b/modules/ticketscesu/pages.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c99899b8a9da2b14a1590c5de22e374b285eb2c
--- /dev/null
+++ b/modules/ticketscesu/pages.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2019 Antoine BOSSY
+#
+# This file is part of a weboob module.
+#
+# This weboob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This weboob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this weboob module. If not, see .
+
+from __future__ import unicode_literals
+
+
+from weboob.browser.elements import method, ItemElement, ListElement, SkipItem
+from weboob.browser.filters.standard import CleanDecimal, CleanText, Field, Format, Date
+from weboob.browser.filters.html import Attr
+from weboob.browser.pages import HTMLPage, LoggedPage
+from weboob.capabilities.bank import Account, Transaction
+from weboob.capabilities.base import NotAvailable
+
+
+class LoginPage(HTMLPage):
+ def login(self, login, password):
+ form = self.get_form('//form[@id="frmMain"]')
+ form['UserLogin'] = login
+ form['UserPass'] = password
+ form.submit()
+
+
+class ProfilePage(HTMLPage):
+ pass
+
+
+class AccountsPage(LoggedPage, HTMLPage):
+ def go_to_transaction_page(self, page):
+ form = self.get_form('//form[@id="frmMain"]')
+ form['%s.x' % page] = 1
+ form['%s.y' % page] = 1
+ form.submit()
+
+ @method
+ class get_accounts(ListElement):
+ item_xpath = '//tr[has-class("ItemH23")]'
+
+ class item(ItemElement):
+ klass = Account
+
+ obj_id = CleanText('./td[position()=2]')
+ obj_balance = CleanDecimal('./td[position()=6]', replace_dots=True)
+ obj_label = Format('Millésime %s', Field('id'))
+ obj_number = Field('id')
+ obj_currency = 'EUR'
+
+ obj__page = Attr('./td//input', 'name')
+
+ @method
+ class get_transactions(ListElement):
+ item_xpath = '//tr[has-class("ItemH23")]'
+
+ class item(ItemElement):
+ klass = Transaction
+
+ def obj_date(self):
+ maybe_date = CleanText('./td[position()=2]')(self)
+ if maybe_date == '-':
+ raise SkipItem()
+
+ return Date(CleanText('./td[position()=2]'), dayfirst=True)(self)
+
+ obj_id = CleanText('./td[position()=3]')
+
+ def obj_amount(self):
+ amount = CleanDecimal('./td[position()=4]', replace_dots=True,
+ default=NotAvailable)(self)
+
+ if amount is NotAvailable:
+ return CleanDecimal('./td[position()=5]', replace_dots=True)(self)
+
+ return amount * -1
+
+ obj_currency = 'EUR'
+
+ def obj_label(self):
+ label = CleanText('./td[position()=6]')(self)
+ if label == '-':
+ return CleanText('./td[position()=7]')(self)
+
+ return label
+
+ obj_raw = Field('label')