From e7f0e96e4eb3a38c7199b23b729ae92af1c67960 Mon Sep 17 00:00:00 2001 From: Romain Bignon Date: Mon, 7 Jan 2013 16:55:28 +0100 Subject: [PATCH] add bank module for carrefourbanque --- modules/carrefourbanque/__init__.py | 23 ++++++ modules/carrefourbanque/backend.py | 69 +++++++++++++++++ modules/carrefourbanque/browser.py | 95 +++++++++++++++++++++++ modules/carrefourbanque/favicon.png | Bin 0 -> 1449 bytes modules/carrefourbanque/pages.py | 116 ++++++++++++++++++++++++++++ modules/carrefourbanque/test.py | 30 +++++++ 6 files changed, 333 insertions(+) create mode 100644 modules/carrefourbanque/__init__.py create mode 100644 modules/carrefourbanque/backend.py create mode 100644 modules/carrefourbanque/browser.py create mode 100644 modules/carrefourbanque/favicon.png create mode 100644 modules/carrefourbanque/pages.py create mode 100644 modules/carrefourbanque/test.py diff --git a/modules/carrefourbanque/__init__.py b/modules/carrefourbanque/__init__.py new file mode 100644 index 00000000..c00a085f --- /dev/null +++ b/modules/carrefourbanque/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 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 CarrefourBanqueBackend + +__all__ = ['CarrefourBanqueBackend'] diff --git a/modules/carrefourbanque/backend.py b/modules/carrefourbanque/backend.py new file mode 100644 index 00000000..6b50ada4 --- /dev/null +++ b/modules/carrefourbanque/backend.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 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 CarrefourBanque + + +__all__ = ['CarrefourBanqueBackend'] + + +class CarrefourBanqueBackend(BaseBackend, ICapBank): + NAME = 'carrefourbanque' + MAINTAINER = u'Romain Bignon' + EMAIL = 'romain@weboob.org' + VERSION = '0.e' + DESCRIPTION = u'Carrefour Banque French bank website' + LICENSE = 'AGPLv3+' + CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', regexp='\d+', masked=False), + ValueBackendPassword('password', label='Password', regexp='\d+')) + BROWSER = CarrefourBanque + + 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: + for tr in self.browser.iter_history(account): + if not tr._coming: + yield tr + + def iter_coming(self, account): + with self.browser: + for tr in self.browser.iter_history(account): + if tr._coming: + yield tr diff --git a/modules/carrefourbanque/browser.py b/modules/carrefourbanque/browser.py new file mode 100644 index 00000000..8968aefe --- /dev/null +++ b/modules/carrefourbanque/browser.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 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 . + + +import urllib + +from weboob.tools.browser import BaseBrowser + +from .pages import LoginPage, HomePage, AccountsPage, TransactionsPage + + +__all__ = ['CarrefourBanque'] + + +class CarrefourBanque(BaseBrowser): + PROTOCOL = 'https' + DOMAIN = 'services.carrefour-banque.fr' + ENCODING = 'iso-8859-15' + PAGES = {'https?://services.carrefour-banque.fr/s2pnet/publ/identification.do': LoginPage, + 'https?://services.carrefour-banque.fr/stscripts/run.stn/s2p/SommairePS2P': HomePage, + 'https?://services.carrefour-banque.fr/s2pnet/priv/disponible.do': AccountsPage, + 'https?://services.carrefour-banque.fr/s2pnet/priv/consult.do\?btnTelechargement': (TransactionsPage, 'raw'), + } + + def is_logged(self): + return self.page is not None and not self.is_on_page(LoginPage) + + def home(self): + if self.is_logged(): + self.location('/s2pnet/priv/disponible.do') + else: + self.login() + + 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 + + args = {'login': '', + 'loginCBPASS': self.username.encode(self.ENCODING), + 'motPasse': self.password.encode(self.ENCODING), + 'testJS': 'true', + 'x': 8, + 'y': 4, + } + + self.location('https://services.carrefour-banque.fr/s2pnet/publ/identification.do', urllib.urlencode(args), no_login=True) + + assert self.is_on_page(LoginPage) + + # raises BrowserIncorrectPassword if no redirect form is found + self.page.redirect() + + def get_accounts_list(self): + if not self.is_on_page(AccountsPage): + self.location('/s2pnet/priv/disponible.do') + 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 iter_history(self, account): + self.location('/s2pnet/priv/consult.do?btnTelechargement') + + assert self.is_on_page(TransactionsPage) + return self.page.get_history(account) diff --git a/modules/carrefourbanque/favicon.png b/modules/carrefourbanque/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..bcfc3b10fb9828b87f3c5619e1f616643aec5822 GIT binary patch literal 1449 zcmV;a1y=frP)QEN0JS?h)NG}znvaHiSQ?Kx*>&Y82jh5Zr& z?Ah6w@0)M_^Pm45XrO@x8n}d!saelL=7Fn$%Yom3-*KJcOG5x5cLJM$#XuJL6W9mr z!FBpCUIByzcp2CM+ z^IT>vc6=+6jEDU+WNV77&^HUkSM_3Jue<7xx$Q2+Z5*Ll~z0A@2WYRRpO zri`In1R-As9`MDtB0?U*bvo=V0U=E+Jp>F`a_pTzn~Ez_Cql?BU~5@$&j9V}nWa2H z$VY(Pn%~Z=gU)Rh1Q=Cf_5;5FU#Wlp2y6znRcL>2`EqD7B%TM30Z$vW>I2SNj9FKy zKP!N>>hp2U$=t1u;4zkfkk6=GeqoG(f0OcwBdaOE0)*UR?f@ar#w1>kcm;Sv-vL5i zut5I%Iuc+8LVltrfRIDL6&5-@4*XL`BD5pq`fve+Y(~gIV5X%G`v6%-0%U+K!2)>a z?*bq!clspoX&s5sf{?jv8Tr+~x4?`FjpxSfv_s_t<8>gwdB7)tNB+3xnxCm*u`p}{ z?gGB7u>faEOTe@JK8=|#Mbc;#D?IXDKR*V6?Exa30M_9;CxQwLQ{g%f zA>=HTFK0qiNi*DMIryyt-VgY^SBcQ!`>ZSMjxoF*;5u7XfpIV1JzY>B%vNy9T;MNd z|BaR-|B{Gjy=sY+oE;HRh!Ejz)zNza+1)aY1JjM_@3q*u8}Q>ljbvvaio9MP;KhY| zfEUU{SPCq!(E4wmU2R^(7V+LHi5hqKy@8Pcr5}78c+-D y8c#<_{sDI`8;BFP)< zsThdR3GDZ6h1XYTF;N9NrUq>b{p#4Bh79Ro+Y7|VOPI3>cj4v_yWJ_T3axiVt1+>T5`gW)^-3obo0>l&H-;fM53~%9G zfLbGOSAcjT6oB~}-y2d{WuHy*$zwqBZKwr!5%2bRG{PxP8szOeAif*&DwwWNC%JiQ zYdr<*$949mHOzEhm5u*GQ{LyQzBGqe&3A>PkOPKoWOnr@KwKwOfqeA@FcqOn. + + +import datetime +from decimal import Decimal +import re +from mechanize import FormNotFoundError + +from weboob.tools.browser import BasePage, BrowserIncorrectPassword +from weboob.capabilities.bank import Account +from weboob.tools.capabilities.bank.transactions import FrenchTransaction + + +__all__ = ['LoginPage', 'AccountsPage', 'TransactionsPage'] + + +class LoginPage(BasePage): + def redirect(self): + try: + self.browser.select_form(name='redirectEpargne') + self.browser.submit(nologin=True) + except FormNotFoundError: + raise BrowserIncorrectPassword() + +class HomePage(BasePage): + pass + +class AccountsPage(BasePage): + def get_list(self): + div = self.document.xpath('//div[@id="descriptifdroite"]')[0] + + account = Account() + + account.id = re.search(u'(\d+)', div.xpath('.//div[@class="credithauttexte"]')[0].text).group(1) + account.label = u'Carte PASS' + account.balance = Decimal('0') + + for tr in div.xpath('.//table/tr'): + tds = tr.findall('td') + + if len(tds) < 3: + continue + + label = u''.join([txt.strip() for txt in tds[1].itertext()]) + value = u''.join([txt.strip() for txt in tds[2].itertext()]) + + if 'encours depuis le dernier' in label.lower(): + coming = u'-' + value + account.coming = Decimal(FrenchTransaction.clean_amount(coming)) + account.currency = account.get_currency(coming) + elif u'arrĂȘtĂ© de compte' in label.lower(): + m = re.search(u'(\d+)/(\d+)/(\d+)', label) + if m: + account._outstanding_date = datetime.date(*reversed(map(int, m.groups()))) + break + + yield account + +class TransactionsPage(BasePage): + COL_DATE = 0 + COL_TEXT = 1 + COL_AMOUNT = 2 + + def get_history(self, account): + transactions = [] + last_order = None + + for tr in self.document.split('\n')[1:]: + cols = tr.split(';') + + if len(cols) < 4: + continue + + t = FrenchTransaction(0) + date = cols[self.COL_DATE] + raw = cols[self.COL_TEXT] + amount = cols[self.COL_AMOUNT] + + t.parse(date, re.sub(r'[ ]+', ' ', raw)) + + if t.raw.startswith('PRELEVEMENT ACHATS DIFFERES'): + t._coming = False + if last_order is None: + last_order = t.date + else: + t._coming = True + + t.set_amount(amount) + transactions.append(t) + + # go to the previous stop date before the last order + while last_order is not None and last_order.day != account._outstanding_date.day: + last_order = last_order - datetime.timedelta(days=1) + + for t in transactions: + if t.date <= last_order: + t._coming = False + + return iter(transactions) diff --git a/modules/carrefourbanque/test.py b/modules/carrefourbanque/test.py new file mode 100644 index 00000000..2fd36ba6 --- /dev/null +++ b/modules/carrefourbanque/test.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 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 CarrefourBanqueTest(BackendTest): + BACKEND = 'carrefourbanque' + + def test_carrefourbanque(self): + l = list(self.backend.iter_accounts()) + if len(l) > 0: + a = l[0] + list(self.backend.iter_history(a))