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