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))