From 93cd71e193f8d97a7b5038adee2462fd7be03eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9=20Rubinstein?= Date: Fri, 19 Jul 2013 18:28:36 +0200 Subject: [PATCH] Bank: Add Delubac --- modules/delubac/__init__.py | 24 ++++++++ modules/delubac/backend.py | 60 ++++++++++++++++++++ modules/delubac/browser.py | 84 +++++++++++++++++++++++++++ modules/delubac/pages.py | 109 ++++++++++++++++++++++++++++++++++++ modules/delubac/test.py | 31 ++++++++++ 5 files changed, 308 insertions(+) create mode 100644 modules/delubac/__init__.py create mode 100644 modules/delubac/backend.py create mode 100644 modules/delubac/browser.py create mode 100644 modules/delubac/pages.py create mode 100644 modules/delubac/test.py diff --git a/modules/delubac/__init__.py b/modules/delubac/__init__.py new file mode 100644 index 00000000..456fc62c --- /dev/null +++ b/modules/delubac/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Noe Rubinstein +# +# 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 DelubacBackend + + +__all__ = ['DelubacBackend'] diff --git a/modules/delubac/backend.py b/modules/delubac/backend.py new file mode 100644 index 00000000..91cf7dfe --- /dev/null +++ b/modules/delubac/backend.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Noe Rubinstein +# +# 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 itertools import ifilter + +from weboob.capabilities.bank import ICapBank +from weboob.tools.backend import BaseBackend, BackendConfig +from weboob.tools.value import ValueBackendPassword + +from .browser import DelubacBrowser + + +__all__ = ['DelubacBackend'] + + +class DelubacBackend(BaseBackend, ICapBank): + NAME = 'delubac' + DESCRIPTION = u'Banque Delubac & Cie' + MAINTAINER = u'Noe Rubinstein' + EMAIL = 'nru@budget-insight.com' + VERSION = '0.g' + + BROWSER = DelubacBrowser + + CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False), + ValueBackendPassword('password', label='Password of account')) + + def create_default_browser(self): + return self.create_browser(self.config['login'].get(), + self.config['password'].get()) + + def iter_accounts(self): + return self.browser.iter_accounts() + + def get_account(self, _id): + return self.browser.get_account(_id) + + def iter_history(self, account, coming=False): + with self.browser: + return ifilter(lambda tr: coming == tr._is_coming, + self.browser.iter_history(account)) + + #def iter_coming(self, account): + # return self.iter_history(account, coming=True) diff --git a/modules/delubac/browser.py b/modules/delubac/browser.py new file mode 100644 index 00000000..e2c6db73 --- /dev/null +++ b/modules/delubac/browser.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Noe Rubinstein +# +# 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, DashboardPage, OperationsPage + + +__all__ = ['DelubacBrowser'] + + +class DelubacBrowser(BaseBrowser): + PROTOCOL = 'https' + DOMAIN = 'vbankonline.delubac.com' + ENCODING = None + + @classmethod + def page_url(cls, name): + return '%s://%s/%s.do' % (cls.PROTOCOL, cls.DOMAIN, name) + + PAGES = { + '%s://%s/(simpleIndex|index).do(\;.*)?' % (PROTOCOL, DOMAIN): LoginPage, + '%s://%s/tbord.do(\?.*)?' % (PROTOCOL, DOMAIN): DashboardPage, + '%s://%s/releve.do' % (PROTOCOL, DOMAIN): OperationsPage, + } + + PAGES_REV = { + LoginPage: '%s://%s/index.do' % (PROTOCOL, DOMAIN), + DashboardPage: '%s://%s/tbord.do' % (PROTOCOL, DOMAIN), + OperationsPage: '%s://%s/releve.do' % (PROTOCOL, DOMAIN), + } + + def stay_or_go(self, page, **kwargs): + if not self.is_on_page(page): + self.location(self.PAGES_REV[page], **kwargs) + + assert self.is_on_page(page) + + def login(self): + assert isinstance(self.username, basestring) + assert isinstance(self.password, basestring) + + self.stay_or_go(LoginPage, no_login=True) + + self.page.login(self.username, self.password) + + self.location(self.page_url('loginSecurite')+'?_top') + + if not self.is_logged(): + raise BrowserIncorrectPassword() + + def is_logged(self): + return not self.is_on_page(LoginPage) + + def iter_accounts(self): + self.stay_or_go(DashboardPage) + return self.page.iter_accounts() + + def get_account(self, _id): + self.stay_or_go(DashboardPage) + return self.page.get_account(_id) + + def iter_history(self, account): + self.location(account._url) + assert self.is_on_page(OperationsPage) + + return self.page.iter_history() diff --git a/modules/delubac/pages.py b/modules/delubac/pages.py new file mode 100644 index 00000000..b365136b --- /dev/null +++ b/modules/delubac/pages.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Noe Rubinstein +# +# 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.capabilities.bank import Account +from weboob.tools.browser import BasePage +from weboob.tools.capabilities.bank.transactions import FrenchTransaction + + +__all__ = ['LoginPage', 'DashboardPage', 'OperationsPage'] + + +class LoginPage(BasePage): + + def login(self, username, password): + self.browser.select_form(name="frmLogin") + self.browser['username'] = username + self.browser['password'] = password + self.browser.find_control('lang').readonly = False + self.browser['lang'] = 'fr' + self.browser.submit(nologin=True) + + +class DashboardPage(BasePage): + def iter_accounts(self): + for line in self._accounts(): + yield self._get_account(line) + + def get_account(self, _id): + xpath = './/a[@href="tbord.do?id=%s"]' % _id + account = next(a for a in self._accounts() if a.xpath(xpath)) + return self._get_account(account) + + def _accounts(self): + return self.document.getroot().cssselect('.tbord_account') + + _FIELD_XPATH = './/span[@class="%s"]//text()' + _URL_XPATH = './/span[@class="accountLabel"]//a/@href' + + def _get_account(self, line): + def get_field(field): + return unicode(line.xpath(self._FIELD_XPATH % field)[0]).strip() + + account = Account() + account._url = unicode(line.xpath(self._URL_XPATH)[0]) + account.id = account._url.replace("tbord.do?id=", "") + account.balance = FrenchTransaction.clean_amount( + get_field('accountTotal')) + account.label = get_field('accountLabel2') + account.currency = Account.TXT2CUR.get(get_field('accountDev'), + Account.CUR_UNKNOWN) + + return account + + +class OperationsPage(BasePage): + _LINE_XPATH = '//tr[starts-with(@class,"PL_LIGLST_")]' + + def iter_history(self): + i = 0 + for line in self.document.xpath(self._LINE_XPATH): + i += 1 + operation = Transaction(i) + + date = line.xpath('.//td[@class="nlb d"]')[0].text_content().strip() + raw = line.xpath('.//td[@class="t"]')[0].text_content().strip() + + amounts = line.xpath('.//td[@class="n"]') + [debit, credit] = [amount.text_content().strip() + for amount in line.xpath('.//td[@class="n"]')] + + operation.parse(date=date, raw=raw) + operation.set_amount(credit, debit) + + yield operation + + +class Transaction(FrenchTransaction): + PATTERNS = [(re.compile('^(?:Vir(?:ement)?|VRT) (?P.*)', re.I), + FrenchTransaction.TYPE_TRANSFER), + (re.compile('^CARTE(?: ETR.)? ' + + '(?P
\d{2})/(?P\d{2})/(?P\d{4}) ' + + '(?P.*)'), + FrenchTransaction.TYPE_CARD), + (re.compile('^CHQ (?P.*)$'), + FrenchTransaction.TYPE_CHECK), + (re.compile('^RCH (?P.*)'), + FrenchTransaction.TYPE_DEPOSIT)] + + _is_coming = False diff --git a/modules/delubac/test.py b/modules/delubac/test.py new file mode 100644 index 00000000..4caa804d --- /dev/null +++ b/modules/delubac/test.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Noe Rubinstein +# +# 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 DelubacTest(BackendTest): + BACKEND = 'delubac' + + def test_delubac(self): + l = list(self.backend.iter_accounts()) + if len(l) > 0: + a = l[0] + list(self.backend.iter_history(a))