diff --git a/modules/hsbc/__init__.py b/modules/hsbc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..55f11a53b33ff86539b8f5973f27aaf5cca5c566 --- /dev/null +++ b/modules/hsbc/__init__.py @@ -0,0 +1,3 @@ +from .backend import HSBCBackend + +__all__ = ['HSBCBackend'] diff --git a/modules/hsbc/backend.py b/modules/hsbc/backend.py new file mode 100644 index 0000000000000000000000000000000000000000..ccf75d251d84cd36b13c6250190e74712619af81 --- /dev/null +++ b/modules/hsbc/backend.py @@ -0,0 +1,61 @@ +# -*- 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 HSBC + + +__all__ = ['HSBCBackend'] + + +class HSBCBackend(BaseBackend, ICapBank): + NAME = 'hsbc' + MAINTAINER = 'Romain Bignon' + EMAIL = 'romain@weboob.org' + VERSION = '0.a' + LICENSE = 'AGPLv3+' + DESCRIPTION = 'HSBC bank\' website' + CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False), + ValueBackendPassword('password', label='Password', regexp='^(\d+|)$')) + BROWSER = HSBC + + 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): + 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.link_id): + yield history diff --git a/modules/hsbc/browser.py b/modules/hsbc/browser.py new file mode 100644 index 0000000000000000000000000000000000000000..9f11c5405f0d17218d3cf6cb3ba3c1688e762b2f --- /dev/null +++ b/modules/hsbc/browser.py @@ -0,0 +1,95 @@ +# -*- 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 . + + +import urllib +import re + +from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword, BasePage, BrokenPageError +from .pages.accounts import AccountsListPage, HistoryPage + + +__all__ = ['HSBC'] + + +class NotLoggedPage(BasePage): + pass + +class HSBC(BaseBrowser): + DOMAIN = 'client.hsbc.fr' + PROTOCOL = 'https' + ENCODING = None # refer to the HTML encoding + PAGES = {'https://client.hsbc.fr/session_absente.html': NotLoggedPage, + 'https://client.hsbc.fr/cgi-bin/emcgi\?.*debr=COMPTES_PAN': AccountsListPage, + 'https://client.hsbc.fr/cgi-bin/emcgi\?.*CPT_IdPrestation=.*': HistoryPage + } + + _session = None + + def home(self): + self.login() + + def is_logged(self): + return self._session is not None and not self.is_on_page(NotLoggedPage) + + def login(self): + assert isinstance(self.username, basestring) + assert isinstance(self.password, basestring) + assert self.password.isdigit() + + data = {'Ident': self.username} + r = self.readurl('https://client.hsbc.fr/cgi-bin/emcgi?Appl=WEBACC', urllib.urlencode(data)) + m = re.search('sessionid=([^ "]+)', r, flags=re.MULTILINE) + if not m: + raise BrowserIncorrectPassword() + + self._session = m.group(1) + + data = {'Secret': self.password} + r = self.readurl('https://client.hsbc.fr/cgi-bin/emcgi?sessionid=%s' % self._session, urllib.urlencode(data)) + if r.find('Erreur Identification') >= 0: + raise BrowserIncorrectPassword() + + m = re.search('url = "/cgi-bin/emcgi\?sessionid=([^& "]+)&debr="', r, flags=re.MULTILINE) + if not m: + raise BrokenPageError('Unable to find session token') + self._session = m.group(1) + + def get_accounts_list(self): + self.location(self.buildurl('/cgi-bin/emcgi', sessionid=self._session, debr='COMPTES_PAN')) + + return self.page.get_list() + + def get_account(self, id): + assert isinstance(id, basestring) + + if not self.is_on_page(AccountsListPage): + l = self.get_accounts_list() + else: + l = self.page.get_list() + + for a in l: + if a.id == id: + return a + + return None + + def get_history(self, link): + self.location(link) + return self.page.get_operations() diff --git a/modules/hsbc/pages/__init__.py b/modules/hsbc/pages/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/hsbc/pages/accounts.py b/modules/hsbc/pages/accounts.py new file mode 100644 index 0000000000000000000000000000000000000000..6f4c887f6e5ae4862972afddc88eea50496e6834 --- /dev/null +++ b/modules/hsbc/pages/accounts.py @@ -0,0 +1,66 @@ +# -*- 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 . + + +import re +from datetime import date + +from weboob.tools.browser import BasePage +from weboob.capabilities.bank import Account, Operation +from weboob.capabilities.base import NotAvailable + + +__all__ = ['AccountsListPage'] + + +class AccountsListPage(BasePage): + def get_list(self): + for tr in self.document.getiterator('tr'): + tds = tr.findall('td') + if len(tds) != 3 or tds[0].attrib.get('class', '') != 'txt' or tds[0].attrib.get('valign', '') == 'center': + continue + + account = Account() + account.id = tds[1].text.strip() + + a = tds[0].findall('a')[-1] + account.label = a.text.strip() + account.link_id = a.attrib['href'] + + tag = tds[2].find('font') + if tag is None: + tag = tds[2] + account.balance = float(tag.text.replace('.','').replace(',','.').replace(' ', '').strip(u' \t\u20ac\xa0€\n\r')) + account.coming = NotAvailable + + yield account + +class HistoryPage(BasePage): + def get_operations(self): + for script in self.document.getiterator('script'): + if script.text is None or script.text.find('\nCL(0') < 0: + continue + + for m in re.finditer(r"CL\((\d+),'(.+)','(.+)','(.+)','([\d -\.,]+)','([\d -\.,]+)','\d+','\d+','[\w\s]+'\);", script.text, flags=re.MULTILINE): + op = Operation(m.group(1)) + op.label = m.group(4) + op.amount = float(m.group(5).replace('.','').replace(',','.').replace(' ', '').strip(u' \t\u20ac\xa0€\n\r')) + op.date = date(*reversed([int(x) for x in m.group(3).split('/')])) + op.category = NotAvailable + yield op