diff --git a/modules/cmso/mobile/__init__.py b/modules/cmso/mobile/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/cmso/browser.py b/modules/cmso/mobile/browser.py similarity index 91% rename from modules/cmso/browser.py rename to modules/cmso/mobile/browser.py index bec6c2d9..f7553496 100644 --- a/modules/cmso/browser.py +++ b/modules/cmso/mobile/browser.py @@ -23,10 +23,10 @@ from weboob.deprecated.browser import Browser, BrowserIncorrectPassword from .pages import LoginPage, AccountsPage, TransactionsPage -__all__ = ['Cmso'] +__all__ = ['CmsoMobileBrowser'] -class Cmso(Browser): +class CmsoMobileBrowser(Browser): PROTOCOL = 'https' DOMAIN = 'www.cmso.com' ENCODING = 'iso-8859-1' @@ -69,16 +69,6 @@ class Cmso(Browser): self.location('https://www.cmso.com/domimobile/m.jsp?a=sommaire') return self.page.get_list() - def get_account(self, id): - assert isinstance(id, basestring) - - l = self.get_accounts_list() - for a in l: - if a.id == id: - return a - - return None - def get_history(self, account): if not self.is_on_page(AccountsPage): self.location('https://www.cmso.com/domimobile/m.jsp?a=sommaire') diff --git a/modules/cmso/pages.py b/modules/cmso/mobile/pages.py similarity index 100% rename from modules/cmso/pages.py rename to modules/cmso/mobile/pages.py diff --git a/modules/cmso/module.py b/modules/cmso/module.py index 3575de9f..5319e963 100644 --- a/modules/cmso/module.py +++ b/modules/cmso/module.py @@ -19,10 +19,12 @@ from weboob.capabilities.bank import CapBank, AccountNotFound +from weboob.capabilities.base import find_object from weboob.tools.backend import Module, BackendConfig -from weboob.tools.value import ValueBackendPassword +from weboob.tools.value import Value, ValueBackendPassword -from .browser import Cmso +from .mobile.browser import CmsoMobileBrowser +from .web.browser import CmsoProBrowser __all__ = ['CmsoModule'] @@ -36,26 +38,22 @@ class CmsoModule(Module, CapBank): DESCRIPTION = u'Crédit Mutuel Sud-Ouest' LICENSE = 'AGPLv3+' CONFIG = BackendConfig(ValueBackendPassword('login', label='Identifiant', masked=False), - ValueBackendPassword('password', label='Mot de passe')) - BROWSER = Cmso + ValueBackendPassword('password', label='Mot de passe'), + Value('website', label='Type de compte', default='par', + choices={'par': 'Particuliers', 'pro': 'Professionnels'})) + BROWSER = CmsoMobileBrowser def create_default_browser(self): + b = {'par': CmsoMobileBrowser, 'pro': CmsoProBrowser} + self.BROWSER = b[self.config['website'].get()] return self.create_browser(self.config['login'].get(), self.config['password'].get()) def iter_accounts(self): - with self.browser: - return self.browser.get_accounts_list() + return self.browser.get_accounts_list() def get_account(self, _id): - with self.browser: - account = self.browser.get_account(_id) - - if account: - return account - else: - raise AccountNotFound() + return find_object(self.browser.get_accounts_list(), id=_id, error=AccountNotFound) def iter_history(self, account): - with self.browser: - return self.browser.get_history(account) + return self.browser.get_history(account) diff --git a/modules/cmso/web/__init__.py b/modules/cmso/web/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/cmso/web/browser.py b/modules/cmso/web/browser.py new file mode 100644 index 00000000..dbd9de3b --- /dev/null +++ b/modules/cmso/web/browser.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2014 smurail +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +import datetime +from dateutil.relativedelta import relativedelta +from itertools import chain + +from weboob.browser import LoginBrowser, URL, need_login + +from .pages import LoginPage, AccountsPage, HistoryPage + + +class CmsoProBrowser(LoginBrowser): + BASEURL = 'https://www.cmso.com/' + + login = URL('/creditmutuel/index_espace.jsp\\?fede=cmso&espace=pro', LoginPage) + accounts = URL('/domiweb/prive/professionnel/situationGlobaleProfessionnel/0-situationGlobaleProfessionnel.act', AccountsPage) + history = URL('/domiweb/prive/professionnel/situationGlobaleProfessionnel/1-situationGlobaleProfessionnel.act', HistoryPage) + + def do_login(self): + self.login.stay_or_go() + self.page.login(self.username, self.password) + + @need_login + def get_accounts_list(self): + self.accounts.go() + return self.page.iter_accounts() + + @need_login + def get_history(self, account): + # Query history for 6 last months + def format_date(d): + return datetime.date.strftime(d, '%d/%m/%Y') + today = datetime.date.today() + period = (today - relativedelta(months=6), today) + query = {'date': ''.join(map(format_date, period))} + + # Let's go + self.location(account._history_url) + first_page = self.page + rest_page = self.history.go(data=query) + + return chain(first_page.iter_history(), rest_page.iter_history()) diff --git a/modules/cmso/web/pages.py b/modules/cmso/web/pages.py new file mode 100644 index 00000000..162c44e8 --- /dev/null +++ b/modules/cmso/web/pages.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2014 smurail +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +import datetime + +from weboob.browser.pages import HTMLPage, LoggedPage +from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.elements import ListElement, ItemElement, method +from weboob.browser.filters.standard import CleanText, CleanDecimal, Regexp, DateGuesser +from weboob.browser.filters.html import Link +from weboob.capabilities.bank import Account +from weboob.tools.capabilities.bank.transactions import FrenchTransaction +from weboob.tools.date import LinearDateGuesser + +__all__ = ['LoginPage'] + + +class LoginPage(HTMLPage): + def on_load(self): + # Yes, I know... In the Wild Wild Web, nobody respects nothing + if self.response.status_code == 500: + raise BrowserIncorrectPassword + + def login(self, username, password): + form = self.get_form(name='formIdentification') + + form['noPersonne'] = username + form['motDePasse'] = password + + form.submit() + + +class CmsoListElement(ListElement): + item_xpath = '//table[@class="Tb" and tr[1][@class="LnTit"]]/tr[@class="LnA" or @class="LnB"]' + + +class AccountsPage(LoggedPage, HTMLPage): + @method + class iter_accounts(CmsoListElement): + class item(ItemElement): + klass = Account + + obj__history_url = Link('./td[1]/a') + obj_id = obj__history_url & Regexp(pattern="indCptSelectionne=(\d+)") + obj_label = CleanText('./td[1]') + obj_balance = CleanDecimal('./td[2]') + + +class Transaction(FrenchTransaction): + pass + + +class CmsoTransactionElement(ItemElement): + klass = Transaction + + def condition(self): + return len(self.el) >= 5 and not self.el.get('id', '').startswith('libelleLong') + + +class HistoryPage(LoggedPage, HTMLPage): + def iter_history(self): + if self.doc.xpath('//a[@href="1-situationGlobaleProfessionnel.act"]'): + return self.iter_history_rest_page() + return self.iter_history_first_page() + + @method + class iter_history_first_page(CmsoListElement): + class item(CmsoTransactionElement): + def validate(self, obj): + return obj.date >= datetime.date.today().replace(day=1) + + def date(selector): + return DateGuesser(CleanText(selector), LinearDateGuesser()) | Transaction.Date(selector) + + obj_date = date('./td[1]') + obj_vdate = date('./td[2]') + # Each row is followed by a "long labelled" version + obj_raw = Transaction.Raw('./following-sibling::tr[1][starts-with(@id, "libelleLong")]/td[3]') + obj_amount = Transaction.Amount('./td[5]', './td[4]') + + @method + class iter_history_rest_page(CmsoListElement): + def find_elements(self): + return reversed(list(super(type(self), self).find_elements())) + + class item(CmsoTransactionElement): + obj_date = Transaction.Date('./td[2]') + obj_vdate = Transaction.Date('./td[1]') + obj_raw = Transaction.Raw('./td[3]') + obj_amount = Transaction.Amount('./td[5]', './td[4]', replace_dots=False)