From 5c3df70c5033369168411eb6008b86a93d7e76b8 Mon Sep 17 00:00:00 2001 From: Gabriel Kerneis Date: Sun, 27 Nov 2011 23:24:50 +0100 Subject: [PATCH] First draft of a Boursorama backend --- weboob/backends/boursorama/__init__.py | 24 ++++++ weboob/backends/boursorama/backend.py | 73 ++++++++++++++++ weboob/backends/boursorama/browser.py | 86 +++++++++++++++++++ weboob/backends/boursorama/pages/__init__.py | 31 +++++++ .../boursorama/pages/account_history.py | 72 ++++++++++++++++ .../boursorama/pages/accounts_list.py | 69 +++++++++++++++ weboob/backends/boursorama/pages/login.py | 51 +++++++++++ weboob/backends/boursorama/test.py | 32 +++++++ 8 files changed, 438 insertions(+) create mode 100644 weboob/backends/boursorama/__init__.py create mode 100644 weboob/backends/boursorama/backend.py create mode 100644 weboob/backends/boursorama/browser.py create mode 100644 weboob/backends/boursorama/pages/__init__.py create mode 100644 weboob/backends/boursorama/pages/account_history.py create mode 100644 weboob/backends/boursorama/pages/accounts_list.py create mode 100644 weboob/backends/boursorama/pages/login.py create mode 100644 weboob/backends/boursorama/test.py diff --git a/weboob/backends/boursorama/__init__.py b/weboob/backends/boursorama/__init__.py new file mode 100644 index 00000000..7766c0c4 --- /dev/null +++ b/weboob/backends/boursorama/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2011 Gabriel Kerneis +# Copyright(C) 2010-2011 Jocelyn Jaubert +# +# 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 BoursoramaBackend + +__all__ = ['BoursoramaBackend'] diff --git a/weboob/backends/boursorama/backend.py b/weboob/backends/boursorama/backend.py new file mode 100644 index 00000000..82643055 --- /dev/null +++ b/weboob/backends/boursorama/backend.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2011 Gabriel Kerneis +# Copyright(C) 2010-2011 Jocelyn Jaubert +# +# 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 . + + +# python2.5 compatibility +from __future__ import with_statement + +from weboob.capabilities.bank import ICapBank, AccountNotFound +from weboob.tools.backend import BaseBackend, BackendConfig +from weboob.tools.value import ValueBackendPassword + +from .browser import Boursorama + + +__all__ = ['BoursoramaBackend'] + + +class BoursoramaBackend(BaseBackend, ICapBank): + NAME = 'boursorama' + MAINTAINER = 'Gabriel Kerneis' + EMAIL = 'gabriel@kerneis.info' + VERSION = '0.a' + LICENSE = 'AGPLv3+' + DESCRIPTION = u'Boursorama french bank\'s website' + CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False), + ValueBackendPassword('password', label='Password')) + BROWSER = Boursorama + + def create_default_browser(self): + return self.create_browser(self.config['login'].get(), + self.config['password'].get()) + + def iter_accounts(self): + for account in self.browser.get_accounts_list(): + yield account + + def get_account(self, _id): + if not _id.isdigit(): + raise AccountNotFound() + 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 history in self.browser.get_history(account): + yield history + + def iter_operations(self, account): + with self.browser: + for coming in self.browser.get_coming_operations(account): + yield coming + diff --git a/weboob/backends/boursorama/browser.py b/weboob/backends/boursorama/browser.py new file mode 100644 index 00000000..5457b27b --- /dev/null +++ b/weboob/backends/boursorama/browser.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2011 Gabriel Kerneis +# Copyright(C) 2010-2011 Jocelyn Jaubert +# +# 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 weboob.backends.boursorama import pages + + +__all__ = ['boursorama'] + + +class Boursorama(BaseBrowser): + DOMAIN = 'www.boursorama.com' + PROTOCOL = 'https' + ENCODING = None # refer to the HTML encoding + PAGES = { + '.*connexion.phtml.*': pages.LoginPage, + '.*/comptes/synthese.phtml': pages.AccountsList, + '.*/comptes/banque/detail/mouvements.phtml.*': pages.AccountHistory, + } + + def __init__(self, *args, **kwargs): + BaseBrowser.__init__(self, *args, **kwargs) + + def home(self): + self.location('https://' + self.DOMAIN + '/connexion.phtml') + + def is_logged(self): + return not self.is_on_page(pages.LoginPage) + + def login(self): + assert isinstance(self.username, basestring) + assert isinstance(self.password, basestring) + assert self.password.isdigit() + + if not self.is_on_page(pages.LoginPage): + self.location('https://' + self.DOMAIN + '/connexion.phtml') + + self.page.login(self.username, self.password) + + if self.is_on_page(pages.LoginPage): + raise BrowserIncorrectPassword() + + def get_accounts_list(self): + if not self.is_on_page(pages.AccountsList): + self.location('/comptes/synthese.phtml') + + return self.page.get_list() + + def get_account(self, id): + assert isinstance(id, basestring) + + if not self.is_on_page(pages.AccountsList): + self.location('/comptes/synthese.phtml') + + l = self.page.get_list() + for a in l: + if a.id == id: + return a + + return None + + def get_history(self, account): + if not self.is_on_page(pages.AccountHistory) or self.page.account.id != account.id: + self.location(account.link_id) + return self.page.get_operations() + + def transfer(self, from_id, to_id, amount, reason=None): + raise NotImplementedError() diff --git a/weboob/backends/boursorama/pages/__init__.py b/weboob/backends/boursorama/pages/__init__.py new file mode 100644 index 00000000..43bbcfc0 --- /dev/null +++ b/weboob/backends/boursorama/pages/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2011 Gabriel Kerneis +# Copyright(C) 2010-2011 Jocelyn Jaubert +# +# 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 .account_history import AccountHistory +from .accounts_list import AccountsList +from .login import LoginPage + +class AccountPrelevement(AccountsList): pass + +__all__ = ['LoginPage', + 'AccountsList', + 'AccountHistory', + ] diff --git a/weboob/backends/boursorama/pages/account_history.py b/weboob/backends/boursorama/pages/account_history.py new file mode 100644 index 00000000..e3760ea1 --- /dev/null +++ b/weboob/backends/boursorama/pages/account_history.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2011 Gabriel Kerneis +# Copyright(C) 2009-2011 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 re +from datetime import date + +from weboob.tools.misc import to_unicode + +from weboob.tools.browser import BasePage +from weboob.capabilities.bank import Operation +from weboob.capabilities.base import NotAvailable + + +__all__ = ['AccountHistory'] + + +class AccountHistory(BasePage): + + def on_loaded(self): + self.operations = [] + + for form in self.document.getiterator('form'): + if form.attrib.get('name', '') == 'marques': + for tr in form.getiterator('tr'): + tds = tr.findall('td') + if len(tds) != 6: + continue + # tds[0]: operation + # tds[1]: valeur + d = date(*reversed([int(x) for x in tds[1].text.split('/')])) + labeldiv = tds[2].find('div') + label = u'' + label += labeldiv.text + label = label.strip(u' \n\t') + + category = labeldiv.attrib.get('title', '') + useless, sep, category = [part.strip() for part in category.partition(':')] + + amount = tds[3].text + if amount == None: + amount = tds[4].text + amount = amount.strip(u' \n\t\x80').replace(' ', '').replace(',', '.') + + # if we don't have exactly one '.', this is not a floatm try the next + operation = Operation(len(self.operations)) + operation.amount = float(amount) + + operation.date = d + operation.label = label + operation.category = category + self.operations.append(operation) + + def get_operations(self): + return self.operations diff --git a/weboob/backends/boursorama/pages/accounts_list.py b/weboob/backends/boursorama/pages/accounts_list.py new file mode 100644 index 00000000..796378bc --- /dev/null +++ b/weboob/backends/boursorama/pages/accounts_list.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2011 Gabriel Kerneis +# Copyright(C) 2010-2011 Jocelyn Jaubert +# +# 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 re + +from weboob.capabilities.bank import Account +from weboob.tools.browser import BasePage + +class AccountsList(BasePage): + + def on_loaded(self): + pass + + def get_list(self): + l = [] + for div in self.document.getiterator('div'): + if div.attrib.get('id', '') == 'synthese-list': + for tr in div.getiterator('tr'): + account = Account() + for td in tr.getiterator('td'): + if td.attrib.get('class', '') == 'account-cb': + break + + elif td.attrib.get('class', '') == 'account-name': + a = td.find('a') + account.label = a.text + account.link_id = a.get('href', '') + + elif td.attrib.get('class', '') == 'account-number': + id = td.text + id = id.strip(u' \n\t') + account.id = id + + elif td.attrib.get('class', '') == 'account-total': + span = td.find('span') + if span == None: + balance = td.text + else: + balance = span.text + balance = balance.strip(u' \n\t€+').replace(',','.').replace(' ','') + if balance != "": + account.balance = float(balance) + else: + account.balance = 0.0 + + else: + # because of some weird useless + if account.id != 0: + l.append(account) + + return l diff --git a/weboob/backends/boursorama/pages/login.py b/weboob/backends/boursorama/pages/login.py new file mode 100644 index 00000000..447266b7 --- /dev/null +++ b/weboob/backends/boursorama/pages/login.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010-2011 Jocelyn Jaubert +# +# 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 logging import error + +from weboob.tools.browser import BasePage, BrowserUnavailable +from lxml import etree + + +__all__ = ['LoginPage'] + + +class LoginPage(BasePage): + def on_loaded(self): + pass +# for td in self.document.getroot().cssselect('td.LibelleErreur'): +# if td.text is None: +# continue +# msg = td.text.strip() +# if 'indisponible' in msg: +# raise BrowserUnavailable(msg) + + def login(self, login, password): + DOMAIN = self.browser.DOMAIN + + url_login = 'https://' + DOMAIN + '/connexion.phtml' + + self.browser.openurl(url_login) + self.browser.select_form('identification') + self.browser.set_all_readonly(False) + + self.browser['login'] = login + self.browser['password'] = password + self.browser.submit() diff --git a/weboob/backends/boursorama/test.py b/weboob/backends/boursorama/test.py new file mode 100644 index 00000000..96266b71 --- /dev/null +++ b/weboob/backends/boursorama/test.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2011 Gabriel Kerneis +# Copyright(C) 2010-2011 Jocelyn Jaubert +# +# 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 BoursoramaTest(BackendTest): + BACKEND = 'boursorama' + + def test_boursorama(self): + l = list(self.backend.iter_accounts()) + if len(l) > 0: + a = l[0] + list(self.backend.iter_operations(a)) + list(self.backend.iter_history(a))