[weboob] [PATCH 1/1] Add CMB Backend

Johann Broudin johann.broudin at 6-8.fr
Sat Jan 14 21:26:41 CET 2012


From: Johann Broudin <Johann.Broudin at 6-8.fr>


Signed-off-by: Johann Broudin <Johann.Broudin at 6-8.fr>
---
 weboob/backends/cmb/__init__.py |   23 ++++
 weboob/backends/cmb/backend.py  |  230 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 253 insertions(+), 0 deletions(-)
 create mode 100644 weboob/backends/cmb/__init__.py
 create mode 100644 weboob/backends/cmb/backend.py

diff --git a/weboob/backends/cmb/__init__.py b/weboob/backends/cmb/__init__.py
new file mode 100644
index 0000000..e83e319
--- /dev/null
+++ b/weboob/backends/cmb/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2011 Johann Broudin
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+
+from .backend import CmbBackend
+
+__all__ = ['CmbBackend']
diff --git a/weboob/backends/cmb/backend.py b/weboob/backends/cmb/backend.py
new file mode 100644
index 0000000..8cb432e
--- /dev/null
+++ b/weboob/backends/cmb/backend.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2011 Johann Broudin
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+
+from weboob.capabilities.bank import ICapBank, AccountNotFound
+from weboob.capabilities.bank import Account, Recipient, Operation
+from weboob.tools.backend import BaseBackend, BackendConfig
+from weboob.tools.value import ValueBackendPassword
+from weboob.capabilities.base import NotAvailable
+from weboob.tools.browser import BrowserIncorrectPassword, BrokenPageError
+
+from re import match
+from httplib import HTTPSConnection
+from urllib import urlencode
+
+from lxml import etree
+from datetime import date
+from StringIO import StringIO
+
+__all__ = ['CmbBackend']
+
+class WrongLoginOrPassword(Exception):
+     def __init__(self):
+        return
+
+class CmbBackend(BaseBackend, ICapBank):
+    NAME = 'cmb'
+    MAINTAINER = 'Johann Broudin'
+    EMAIL = 'Johann.Broudin at 6-8.fr'
+    VERSION = '0.2'
+    LICENSE = 'AGPLv3+'
+    DESCRIPTION = 'Credit Mutuel de Bretagne'
+    CONFIG = BackendConfig(
+            ValueBackendPassword('login', label='Account ID', masked=False),
+            ValueBackendPassword('password', label='Password', masked=True))
+    connected = False
+    headers = {
+            'User-Agent':
+                'Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OSX; en-us) ' +
+                'AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405'
+            }
+
+    def login(self):
+        params = urlencode({
+            'codeEspace': 'NO',
+            'codeEFS': '01',
+            'codeSi': '001',
+            'noPersonne': self.config['login'].get(),
+            'motDePasse': self.config['password'].get()
+            })
+        conn = HTTPSConnection("www.cmb.fr")
+        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+        conn.request("POST",
+                     "/domiweb/servlet/Identification",
+                     params,
+                     headers)
+        response = conn.getresponse()
+        if response.status == 302:
+            self.cookie = response.getheader('Set-Cookie').split(';')[0]
+            self.cookie += ';'
+            self.connected = True
+            return True
+        else:
+            raise BrowserIncorrectPassword()
+        return false
+
+    def iter_accounts(self):
+
+        if not self.connected:
+            self.login()
+
+        def do_http():
+            conn = HTTPSConnection("www.cmb.fr")
+            headers = self.headers
+            headers['Cookie'] = self.cookie
+            conn.request("GET",
+                         '/domiweb/prive/particulier/releve/0-releve.act',
+                         {},
+                         headers)
+            response = conn.getresponse()
+            data = response.read()
+            conn.close
+            return data
+        
+        data = do_http()
+        parser = etree.HTMLParser()
+        tree = etree.parse(StringIO(data), parser)
+
+        table = tree.xpath('/html/body/table')
+        if len(table) == 0:
+            title = tree.xpath('/html/head/title')[0].text
+            if title == u"Utilisateur non identifié":
+                self.login()
+                data = do_http()
+
+                parser = etree.HTMLParser()
+                tree = etree.parse(StringIO(data), parser)
+                table = tree.xpath('/html/body/table')
+                if len(table) == 0:
+                    raise BrokenPageError
+            else:
+                raise BrokenPageError
+
+        for tr in table[1].getiterator('tr'):
+            if tr.get('class') != 'LnTit' and tr.get('class') != 'LnTot':
+                account = Account()
+                td = tr.xpath('td')
+                
+                a = td[0].xpath('a')
+                account.label = unicode(a[0].text).strip()
+                href = a[0].get('href')
+                m = match(r"javascript:releve\((.*),'(.*)','(.*)'\)",
+                             href)
+                account.id = unicode(m.group(1) + m.group(2) + m.group(3))
+                account.cmbvaleur = m.group(1)
+                account.cmbvaleur2 = m.group(2)
+                account.cmbtype = m.group(3)
+
+
+                balance = td[1].text
+                balance = balance.replace(',','.').replace(u"\xa0",'')
+                account.balance = float(balance)
+
+                span = td[3].xpath('a/span')
+                if len(span):
+                    coming = span[0].text.replace(' ','').replace(',','.')
+                    coming = coming.replace(u"\xa0",'')
+                    account.coming = float(coming)
+                else:
+                    account.coming = NotAvailable
+
+                yield account
+
+    def get_account(self, _id):
+        for account in self.iter_accounts():
+            if account.id == _id:
+                return account
+
+        raise AccountNotFound()
+
+    def iter_history(self, account):
+        page = "/domiweb/prive/particulier/releve/"
+        if account.cmbtype == 'D':
+            page += "10-releve.act"
+        else:
+            page += "2-releve.act"
+        page +="?noPageReleve=1&indiceCompte="
+        page += account.cmbvaleur
+        page += "&typeCompte="
+        page += account.cmbvaleur2
+        page += "&deviseOrigineEcran=EUR"
+
+        def do_http():
+            conn = HTTPSConnection("www.cmb.fr")
+            headers = self.headers
+            headers['Cookie'] = self.cookie
+            conn.request("GET", page, {}, headers)
+            response = conn.getresponse()
+            data = response.read()
+            conn.close
+            return data
+
+        data = do_http()
+        parser = etree.HTMLParser()
+        tree = etree.parse(StringIO(data), parser)
+        
+        
+        tables = tree.xpath('/html/body/table')
+        if len(tables) == 0:
+            title = tree.xpath('/html/head/title')[0].text
+            if title == u"Utilisateur non identifié":
+                self.login()
+                data = do_http()
+
+                parser = etree.HTMLParser()
+                tree = etree.parse(StringIO(data), parser)
+                tables = tree.xpath('/html/body/table')
+                if len(tables) == 0:
+                    raise BrokenPageError
+            else:
+                raise BrokenPageError
+
+        i = 0
+
+        for table in tables:
+            if table.get('id') != "tableMouvements":
+                continue
+            for tr in table.getiterator('tr'):
+                if (tr.get('class') != 'LnTit' and
+                    tr.get('class') != 'LnTot'):
+                    operation = Operation(i)
+                    td = tr.xpath('td')
+
+                    div = td[1].xpath('div')
+                    d = div[0].text.split('/')
+                    operation.date = date(*reversed([int(x) for x in d]))
+
+                    div = td[2].xpath('div')
+                    label = div[0].xpath('a')[0].text.replace('\n','')
+                    operation.label = unicode(' '.join(label.split()))
+                    
+                    amount = td[3].text
+                    if amount.count(',') != 1:
+                        amount = td[4].text
+                        amount = amount.replace(',','.').replace(u'\xa0','')
+                        print repr(amount)
+                        operation.amount = float(amount)
+                    else:
+                        amount = amount.replace(',','.').replace(u'\xa0','')
+                        operation.amount = - float(amount)
+
+                    i += 1
+                    yield operation
+
-- 
1.7.2.5




More information about the weboob mailing list