From d5fabd9c758770e789aff5390a4f7bf0cab57db7 Mon Sep 17 00:00:00 2001 From: Romain Bignon Date: Fri, 21 Dec 2012 12:29:31 +0100 Subject: [PATCH] new module ganassurances (ICapBank) --- modules/ganassurances/__init__.py | 23 ++++++ modules/ganassurances/backend.py | 65 +++++++++++++++++ modules/ganassurances/browser.py | 96 +++++++++++++++++++++++++ modules/ganassurances/favicon.png | Bin 0 -> 3328 bytes modules/ganassurances/pages.py | 116 ++++++++++++++++++++++++++++++ modules/ganassurances/test.py | 30 ++++++++ 6 files changed, 330 insertions(+) create mode 100644 modules/ganassurances/__init__.py create mode 100644 modules/ganassurances/backend.py create mode 100644 modules/ganassurances/browser.py create mode 100644 modules/ganassurances/favicon.png create mode 100644 modules/ganassurances/pages.py create mode 100644 modules/ganassurances/test.py diff --git a/modules/ganassurances/__init__.py b/modules/ganassurances/__init__.py new file mode 100644 index 00000000..44dacc94 --- /dev/null +++ b/modules/ganassurances/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012 Romain Bignon +# +# 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 . + + +from .backend import GanAssurancesBackend + +__all__ = ['GanAssurancesBackend'] diff --git a/modules/ganassurances/backend.py b/modules/ganassurances/backend.py new file mode 100644 index 00000000..6f098768 --- /dev/null +++ b/modules/ganassurances/backend.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012 Romain Bignon +# +# 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 . + + +from weboob.capabilities.bank import ICapBank, AccountNotFound +from weboob.tools.backend import BaseBackend, BackendConfig +from weboob.tools.value import ValueBackendPassword + +from .browser import GanAssurances + + +__all__ = ['GanAssurancesBackend'] + + +class GanAssurancesBackend(BaseBackend, ICapBank): + NAME = 'ganassurances' + MAINTAINER = u'Romain Bignon' + EMAIL = 'romain@weboob.org' + VERSION = '0.e' + DESCRIPTION = u'Groupama Assurances French bank website' + LICENSE = 'AGPLv3+' + CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False), + ValueBackendPassword('password', label='Password', regexp='\d+')) + BROWSER = GanAssurances + + def create_default_browser(self): + 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() + + def get_account(self, _id): + with self.browser: + account = self.browser.get_account(_id) + + if account: + return account + else: + raise AccountNotFound() + + def iter_history(self, account): + with self.browser: + return self.browser.get_history(account) + + def iter_coming(self, account): + with self.browser: + return self.browser.get_coming(account) diff --git a/modules/ganassurances/browser.py b/modules/ganassurances/browser.py new file mode 100644 index 00000000..c6875897 --- /dev/null +++ b/modules/ganassurances/browser.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012 Romain Bignon +# +# 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 . + + +from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword + +from .pages import LoginPage, AccountsPage, TransactionsPage + + +__all__ = ['GanAssurances'] + + +class GanAssurances(BaseBrowser): + PROTOCOL = 'https' + DOMAIN = 'espaceclient.ganassurances.fr' + PAGES = {'https://espaceclient.ganassurances.fr/wps/portal/login.*': LoginPage, + 'https://espaceclient.ganassurances.fr/wps/myportal/TableauDeBord': AccountsPage, + 'https://espaceclient.ganassurances.fr/wps/myportal/!ut.*': TransactionsPage, + } + + def is_logged(self): + return self.page is not None and not self.is_on_page(LoginPage) + + def home(self): + self.location('https://espaceclient.ganassurances.fr/wps/myportal/TableauDeBord') + + def login(self): + """ + Attempt to log in. + Note: this method does nothing if we are already logged in. + """ + assert isinstance(self.username, basestring) + assert isinstance(self.password, basestring) + + if self.is_logged(): + return + + if not self.is_on_page(LoginPage): + self.home() + + self.page.login(self.username, self.password) + + if not self.is_logged(): + raise BrowserIncorrectPassword() + + def get_accounts_list(self): + if not self.is_on_page(AccountsPage): + self.location('/wps/myportal/TableauDeBord') + 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 account._link is None: + return iter([]) + + self.location(account._link) + assert self.is_on_page(TransactionsPage) + + return self.page.get_history() + + def get_coming(self, account): + if account._link is None: + return iter([]) + + self.location(account._link) + assert self.is_on_page(TransactionsPage) + + self.location(self.page.get_coming_link()) + assert self.is_on_page(TransactionsPage) + + return self.page.get_history() diff --git a/modules/ganassurances/favicon.png b/modules/ganassurances/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1f716f2726d891a8e735a3bae5c4b460581fc6ea GIT binary patch literal 3328 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 KMNUMnLSTYqiF>gC literal 0 HcmV?d00001 diff --git a/modules/ganassurances/pages.py b/modules/ganassurances/pages.py new file mode 100644 index 00000000..b427448d --- /dev/null +++ b/modules/ganassurances/pages.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012 Romain Bignon +# +# 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 . + + +from decimal import Decimal +import re + +from weboob.tools.browser import BasePage +from weboob.capabilities.bank import Account +from weboob.tools.capabilities.bank.transactions import FrenchTransaction + + +__all__ = ['LoginPage', 'AccountsPage', 'TransactionsPage'] + + +class LoginPage(BasePage): + def login(self, login, passwd): + self.browser.select_form(name='loginForm') + self.browser.set_all_readonly(False) + self.browser['LoginPortletFormID'] = login.encode(self.browser.ENCODING) + self.browser['LoginPortletFormPassword1'] = passwd.encode(self.browser.ENCODING) + self.browser.submit(nologin=True) + +class AccountsPage(BasePage): + ACCOUNT_TYPES = {u'Solde des comptes bancaires - Groupama Banque': Account.TYPE_CHECKING, + u'Epargne bancaire constituée - Groupama Banque': Account.TYPE_SAVINGS, + } + + def get_list(self): + account_type = Account.TYPE_UNKNOWN + accounts = [] + + for tr in self.document.xpath('//table[@class="ecli"]/tr'): + if tr.attrib.get('class', '') == 'entete': + account_type = self.ACCOUNT_TYPES.get(tr.find('th').text.strip(), Account.TYPE_UNKNOWN) + continue + + tds = tr.findall('td') + + balance = tds[-1].text.strip() + if balance == '': + continue + + account = Account() + account.label = u' '.join([txt.strip() for txt in tds[0].itertext()]) + account.label = re.sub(u'[ \xa0\u2022\r\n\t]+', u' ', account.label).strip() + account.id = re.findall('(\d+)', account.label)[0] + account.balance = Decimal(FrenchTransaction.clean_amount(balance)) + account.currency = account.get_currency(balance) + account.type = account_type + m = re.search(r"javascript:submitForm\(([\w_]+),'([^']+)'\);", tds[0].find('a').attrib['onclick']) + if not m: + self.logger.warning('Unable to find link for %r' % account.label) + account._link = None + else: + account._link = m.group(2) + + accounts.append(account) + + return accounts + + +class Transaction(FrenchTransaction): + PATTERNS = [(re.compile('^Facture (?P
\d{2})/(?P\d{2})-(?P.*) carte .*'), + FrenchTransaction.TYPE_CARD), + (re.compile(u'^(Prlv( de)?|Ech(éance|\.)) (?P.*)'), + FrenchTransaction.TYPE_ORDER), + (re.compile('^(Vir|VIR)( de)? (?P.*)'), + FrenchTransaction.TYPE_TRANSFER), + (re.compile(u'^CHEQUE.*? (N° \w+)?$'), FrenchTransaction.TYPE_CHECK), + (re.compile('^Cotis(ation)? (?P.*)'), + FrenchTransaction.TYPE_BANK), + (re.compile('(?PInt .*)'), FrenchTransaction.TYPE_BANK), + ] + +class TransactionsPage(BasePage): + def get_history(self): + count = 0 + for tr in self.document.xpath('//table[@id="releve_operation"]/tr'): + tds = tr.findall('td') + + if len(tds) < 4: + continue + + t = Transaction(count) + + date = u''.join([txt.strip() for txt in tds[0].itertext()]) + raw = u' '.join([txt.strip() for txt in tds[1].itertext()]) + debit = u''.join([txt.strip() for txt in tds[-2].itertext()]) + credit = u''.join([txt.strip() for txt in tds[-1].itertext()]) + t.parse(date, re.sub(r'[ ]+', ' ', raw)) + t.set_amount(credit, debit) + + yield t + + count += 1 + + def get_coming_link(self): + a = self.document.getroot().cssselect('div#sous_nav ul li a.bt_sans_off')[0] + return re.sub('[ \t\r\n]+', '', a.attrib['href']) diff --git a/modules/ganassurances/test.py b/modules/ganassurances/test.py new file mode 100644 index 00000000..327cbe03 --- /dev/null +++ b/modules/ganassurances/test.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012 Romain Bignon +# +# 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 . + + +from weboob.tools.test import BackendTest + +class GanAssurancesTest(BackendTest): + BACKEND = 'ganassurances' + + def test_banquepop(self): + l = list(self.backend.iter_accounts()) + if len(l) > 0: + a = l[0] + list(self.backend.iter_history(a))