Skip to content
module.py 8.81 KiB
Newer Older
Johann Broudin's avatar
Johann Broudin committed
# -*- coding: utf-8 -*-

# Copyright(C) 2012 Johann Broudin
Johann Broudin's avatar
Johann Broudin committed
#
# 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 CapBank, AccountNotFound
from weboob.capabilities.bank import Account, Transaction
from weboob.tools.backend import Module, BackendConfig
Johann Broudin's avatar
Johann Broudin committed
from weboob.tools.value import ValueBackendPassword
Johann Broudin's avatar
Johann Broudin committed
from weboob.capabilities.base import NotAvailable
from weboob.exceptions import BrowserIncorrectPassword, BrowserHTTPError, ParseError
from weboob.browser import Browser
Johann Broudin's avatar
Johann Broudin committed

from re import match, compile, sub
from decimal import Decimal
Johann Broudin's avatar
Johann Broudin committed
from lxml import etree
from datetime import date
from StringIO import StringIO

Florent's avatar
Florent committed
__all__ = ['CmbModule']
Johann Broudin's avatar
Johann Broudin committed

class CmbModule(Module, CapBank):
Johann Broudin's avatar
Johann Broudin committed
    NAME = 'cmb'
    MAINTAINER = u'Johann Broudin'
Johann Broudin's avatar
Johann Broudin committed
    EMAIL = 'Johann.Broudin@6-8.fr'
Florent's avatar
Florent committed
    VERSION = '1.2'
Johann Broudin's avatar
Johann Broudin committed
    LICENSE = 'AGPLv3+'
    DESCRIPTION = u'Crédit Mutuel de Bretagne'
Johann Broudin's avatar
Johann Broudin committed
    CONFIG = BackendConfig(
            ValueBackendPassword('login', label='Identifiant', masked=False),
            ValueBackendPassword('password', label='Mot de passe', masked=True))
    LABEL_PATTERNS = [
            (   # card
                compile('^CARTE (?P<text>.*)'),
                Transaction.TYPE_CARD,
                '%(text)s'
            ),
            (   # order
                compile('^PRLV (?P<text>.*)'),
                Transaction.TYPE_ORDER,
                '%(text)s'
            ),
            (   # withdrawal
                compile('^RET DAB (?P<text>.*)'),
                Transaction.TYPE_WITHDRAWAL,
                '%(text)s'
            ),
            (   # loan payment
                compile('^ECH (?P<text>.*)'),
                Transaction.TYPE_LOAN_PAYMENT,
                '%(text)s'
            ),
            (   # transfer
                compile('^VIR (?P<text>.*)'),
                Transaction.TYPE_TRANSFER,
                '%(text)s'
            ),
            (   # payback
                compile('^ANN (?P<text>.*)'),
                Transaction.TYPE_PAYBACK,
                '%(text)s'
            ),
            (   # bank
                compile('^F (?P<text>.*)'),
                Transaction.TYPE_BANK,
                '%(text)s'
            ),
            (   # deposit
                compile('^VRST (?P<text>.*)'),
                Transaction.TYPE_DEPOSIT,
                '%(text)s'
Florent's avatar
Florent committed
    BROWSER = Browser
    islogged = False
Johann Broudin's avatar
Johann Broudin committed

    def login(self):
Johann Broudin's avatar
Johann Broudin committed
            'codeEspace': 'NO',
            'codeEFS': '01',
            'codeSi': '001',
            'noPersonne': self.config['login'].get(),
            'motDePasse': self.config['password'].get()
            self.browser.open("https://www.cmb.fr/domiweb/servlet/Identification", data=data)
            self.browser.open("https://www.cmb.fr/domiweb/prive/espacesegment/selectionnerAbonnement/0-selectionnerAbonnement.act")
        except BrowserHTTPError:
Johann Broudin's avatar
Johann Broudin committed
            raise BrowserIncorrectPassword()
        else:
            self.islogged=True
Johann Broudin's avatar
Johann Broudin committed

            # Go on pro space when there is one.
            self.browser.open("https://www.cmb.fr/domiweb/prive/espacesegment/selectionnerAbonnement/1-selectionnerAbonnement.act?espace=PR&indice=0")

Johann Broudin's avatar
Johann Broudin committed
    def iter_accounts(self):
        if not self.islogged:
Johann Broudin's avatar
Johann Broudin committed
            self.login()

        data = self.browser.open("https://www.cmb.fr/domiweb/prive/particulier/releve/0-releve.act").content
Johann Broudin's avatar
Johann Broudin committed
        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 = self.browser.open("https://www.cmb.fr/domiweb/prive/particulier/releve/0-releve.act").content
Johann Broudin's avatar
Johann Broudin committed

                parser = etree.HTMLParser()
                tree = etree.parse(StringIO(data), parser)
                table = tree.xpath('/html/body/table')
                if len(table) == 0:
Johann Broudin's avatar
Johann Broudin committed
            else:
Johann Broudin's avatar
Johann Broudin committed

        for tr in tree.xpath('/html/body//table[contains(@class, "Tb")]/tr'):
            if tr.get('class', None) not in ('LnTit', 'LnTot', 'LnMnTiers', None):
Johann Broudin's avatar
Johann Broudin committed
                account = Account()
                td = tr.xpath('td')
                a = td[1].xpath('a')
Johann Broudin's avatar
Johann Broudin committed
                account.label = unicode(a[0].text).strip()
                href = a[0].get('href')
                m = match(r"javascript:releve\((.*),'(.*)','(.*)'\)",
                             href)
                if not m:
                    continue
Johann Broudin's avatar
Johann Broudin committed
                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)
Johann Broudin's avatar
Johann Broudin committed

Romain Bignon's avatar
Romain Bignon committed
                balance = u''.join([txt.strip() for txt in td[2].itertext()])
                balance = balance.replace(',', '.').replace(u"\xa0", '')
                account.balance = Decimal(balance)
Johann Broudin's avatar
Johann Broudin committed

                span = td[4].xpath('a/span')
Johann Broudin's avatar
Johann Broudin committed
                if len(span):
                    coming = span[0].text.replace(' ', '').replace(',', '.')
                    coming = coming.replace(u"\xa0", '')
                    account.coming = Decimal(coming)
Johann Broudin's avatar
Johann Broudin committed
                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):
        if not self.islogged:
            self.login()

        page = "https://www.cmb.fr/domiweb/prive/particulier/releve/"
        if account._cmbtype == 'D':
Johann Broudin's avatar
Johann Broudin committed
            page += "10-releve.act"
        else:
            page += "2-releve.act"
        page +="?noPageReleve=1&indiceCompte="
        page += account._cmbvaleur
Johann Broudin's avatar
Johann Broudin committed
        page += "&typeCompte="
        page += account._cmbvaleur2
Johann Broudin's avatar
Johann Broudin committed
        page += "&deviseOrigineEcran=EUR"

        data = self.browser.open(page).content
Johann Broudin's avatar
Johann Broudin committed
        parser = etree.HTMLParser()
        tree = etree.parse(StringIO(data), parser)
Johann Broudin's avatar
Johann Broudin committed
        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 = self.browser.open(page).content
Johann Broudin's avatar
Johann Broudin committed

                parser = etree.HTMLParser()
                tree = etree.parse(StringIO(data), parser)
                tables = tree.xpath('/html/body/table')
                if len(tables) == 0:
Johann Broudin's avatar
Johann Broudin committed
            else:
Johann Broudin's avatar
Johann Broudin committed

        i = 0

        for table in tables:
            if table.get('id') != "tableMouvements":
                continue
            for tr in table.getiterator('tr'):
                if (tr.get('class') != 'LnTit' and
Laurent Bachelier's avatar
Laurent Bachelier committed
                        tr.get('class') != 'LnTot'):
                    operation = Transaction()
Johann Broudin's avatar
Johann Broudin committed
                    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.raw = unicode(' '.join(label.split()))
                    for pattern, _type, _label in self.LABEL_PATTERNS:
                        mm = pattern.match(operation.raw)
                        if mm:
                            operation.type = _type
                            operation.label = sub('[ ]+', ' ',
                                    _label % mm.groupdict()).strip()
Johann Broudin's avatar
Johann Broudin committed
                    amount = td[3].text
                    if amount.count(',') != 1:
                        amount = td[4].text
                        amount = amount.replace(',', '.').replace(u'\xa0', '')
                        operation.amount = Decimal(amount)
Johann Broudin's avatar
Johann Broudin committed
                    else:
                        amount = amount.replace(',', '.').replace(u'\xa0', '')
                        operation.amount = - Decimal(amount)
Johann Broudin's avatar
Johann Broudin committed

                    i += 1
                    yield operation