add module creditdunord

This commit is contained in:
Romain Bignon 2013-02-09 18:05:22 +01:00 committed by Romain Bignon
commit fd9d069442
6 changed files with 410 additions and 0 deletions

View file

@ -0,0 +1,23 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from .backend import CreditDuNordBackend
__all__ = ['CreditDuNordBackend']

View file

@ -0,0 +1,70 @@
# -*- 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 <http://www.gnu.org/licenses/>.
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 CreditDuNordBrowser
__all__ = ['CreditDuNordBackend']
class CreditDuNordBackend(BaseBackend, ICapBank):
NAME = 'creditdunord'
MAINTAINER = u'Romain Bignon'
EMAIL = 'romain@weboob.org'
VERSION = '0.f'
DESCRIPTION = u'Crédit du Nord French bank website'
LICENSE = 'AGPLv3+'
CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False),
ValueBackendPassword('password', label='Password of account'))
BROWSER = CreditDuNordBrowser
def create_default_browser(self):
return self.create_browser(self.config['login'].get(), self.config['password'].get())
def iter_accounts(self):
with self.browser:
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:
transactions = list(self.browser.get_history(account))
transactions.sort(key=lambda tr: tr.rdate, reverse=True)
return [tr for tr in transactions if not tr._is_coming]
def iter_coming(self, account):
with self.browser:
transactions = list(self.browser.get_card_operations(account))
transactions.sort(key=lambda tr: tr.rdate, reverse=True)
return [tr for tr in transactions if tr._is_coming]

View file

@ -0,0 +1,123 @@
# -*- 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 <http://www.gnu.org/licenses/>.
import urllib
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
from .pages import LoginPage, AccountsPage, TransactionsPage
__all__ = ['CreditDuNordBrowser']
class CreditDuNordBrowser(BaseBrowser):
PROTOCOL = 'https'
DOMAIN = 'www.credit-du-nord.fr'
#CERTHASH = 'b2f8a8a7a03c54d7bb918f10eb4e141c3fb51bebf0eb8371aefb33a997efc600'
ENCODING = 'UTF-8'
PAGES = {'https://www.credit-du-nord.fr/?': LoginPage,
'https://www.credit-du-nord.fr/vos-comptes/particuliers(\?.*)?': AccountsPage,
'https://www.credit-du-nord.fr/vos-comptes/.*/transac/.*': TransactionsPage,
}
def is_logged(self):
return self.page is not None and not self.is_on_page(LoginPage)
def home(self):
if self.is_logged():
self.location('https://www.credit-du-nord.fr/vos-comptes/particuliers')
else:
self.login()
return
return self.location('https://www.credit-du-nord.fr/vos-comptes/particuliers')
def login(self):
assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring)
# not necessary (and very slow)
#self.location('https://www.credit-du-nord.fr/', no_login=True)
data = {'bank': 'credit-du-nord',
'pagecible': 'vos-comptes',
'password': self.password.encode(self.ENCODING),
'pwAuth': 'Authentification+mot+de+passe',
'username': self.username.encode(self.ENCODING),
}
self.location('https://www.credit-du-nord.fr/saga/authentification', urllib.urlencode(data), no_login=True)
if not self.is_logged():
raise BrowserIncorrectPassword()
def get_accounts_list(self):
if not self.is_on_page(AccountsPage):
self.location('https://www.credit-du-nord.fr/vos-comptes/particuliers')
return self.page.get_list()
def get_account(self, id):
assert isinstance(id, basestring)
l = self.get_accounts_list()
for a in l:
if a.id == id:
return a
return None
def iter_transactions(self, link, link_id, execution, is_coming=None):
event = 'clicDetailCompte'
while 1:
data = {'_eventId': event,
'_ipc_eventValue': '',
'_ipc_fireEvent': '',
'deviseAffichee': 'DEVISE',
'execution': execution,
'idCompteClique': link_id,
}
self.location(link, urllib.urlencode(data))
assert self.is_on_page(TransactionsPage)
self.page.is_coming = is_coming
for tr in self.page.get_history():
yield tr
is_last = self.page.is_last()
if is_last:
return
event = 'clicChangerPageSuivant'
execution = self.page.get_execution()
is_coming = self.page.is_coming
def get_history(self, account):
for tr in self.iter_transactions(account._link, account._link_id, account._execution):
yield tr
for tr in self.get_card_operations(account):
yield tr
def get_card_operations(self, account):
for link_id in account._card_ids:
for tr in self.iter_transactions(account._link, link_id, account._execution, True):
yield tr

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,163 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from decimal import Decimal
import re
from cStringIO import StringIO
from weboob.tools.browser import BasePage, BrokenPageError
from weboob.tools.json import json
from weboob.capabilities.bank import Account
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
__all__ = ['LoginPage', 'AccountsPage', 'TransactionsPage']
class LoginPage(BasePage):
pass
class CDNBasePage(BasePage):
def get_from_js(self, pattern, end):
"""
find a pattern in any javascript text
"""
for script in self.document.xpath('//script'):
txt = script.text
if txt is None:
continue
start = txt.find(pattern)
if start < 0:
continue
txt = txt[start+len(pattern):start+txt[start+len(pattern):].find(end)+len(pattern)]
return txt
def get_execution(self):
return self.get_from_js("name: 'execution', value: '", "'")
class AccountsPage(CDNBasePage):
COL_ID = 4
COL_LABEL = 5
COL_BALANCE = -1
def get_history_link(self):
return self.parser.strip(self.get_from_js(",url: Ext.util.Format.htmlDecode('", "'"))
def get_list(self):
accounts = []
txt = self.get_from_js('_data = new Array(', ');')
if txt is None:
raise BrokenPageError('Unable to find accounts list in scripts')
data = json.loads('[%s]' % txt.replace("'", '"'))
for line in data:
a = Account()
a.id = line[self.COL_ID].replace(' ','')
a.label = self.parser.tocleanstring(self.parser.parse(StringIO(line[self.COL_LABEL])).xpath('//div[@class="libelleCompteTDB"]')[0])
a.balance = Decimal(FrenchTransaction.clean_amount(line[self.COL_BALANCE]))
a._link = self.get_history_link()
a._execution = self.get_execution()
a._link_id = line[self.COL_ID]
if a.id.endswith('_CarteVisaPremier'):
accounts[0]._card_ids.append(a._link_id)
if not accounts[0].coming:
accounts[0].coming = Decimal('0.0')
accounts[0].coming += a.balance
continue
a._card_ids = []
accounts.append(a)
return iter(accounts)
class Transaction(FrenchTransaction):
PATTERNS = [(re.compile(r'^(?P<text>RET DAB \w+ .*?) LE (?P<dd>\d{2})(?P<mm>\d{2})$'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^VIR(EMENT)?\.?(DE)? (?P<text>.*)'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile(r'^PRLV (DE )?(?P<text>.*)'), FrenchTransaction.TYPE_ORDER),
(re.compile(r'^CB (?P<text>.*) LE (?P<dd>\d{2})\.?(?P<mm>\d{2})$'),
FrenchTransaction.TYPE_CARD),
(re.compile(r'^CHEQUE.*'), FrenchTransaction.TYPE_CHECK),
(re.compile(r'^(CONVENTION \d+ )?COTISATION (?P<text>.*)'),
FrenchTransaction.TYPE_BANK),
(re.compile(r'^REM(ISE)?\.?( CHQ\.)? .*'), FrenchTransaction.TYPE_DEPOSIT),
(re.compile(r'^(?P<text>.*?)( \d{2}.*)? LE (?P<dd>\d{2})\.?(?P<mm>\d{2})$'),
FrenchTransaction.TYPE_CARD),
]
class TransactionsPage(CDNBasePage):
COL_ID = 0
COL_DATE = 1
COL_DEBIT_DATE = 2
COL_LABEL = 3
COL_VALUE = -1
is_coming = None
def is_last(self):
for script in self.document.xpath('//script'):
txt = script.text
if txt is None:
continue
if txt.find('clicChangerPageSuivant') >= 0:
return False
return True
def get_history(self):
txt = self.get_from_js('ListeMvts_data = new Array(', ');')
if txt is None:
raise BrokenPageError('Unable to find transactions list in scripts')
data = json.loads('[%s]' % txt.replace('"', '\\"').replace("'", '"'))
for line in data:
t = Transaction(line[self.COL_ID])
if self.is_coming is not None:
t.type = t.TYPE_CARD
date = self.parser.strip(line[self.COL_DEBIT_DATE])
else:
date = self.parser.strip(line[self.COL_DATE])
raw = self.parser.strip(line[self.COL_LABEL])
t.parse(date, raw)
t.set_amount(line[self.COL_VALUE])
if self.is_coming is True and raw.startswith('TOTAL DES') and t.amount > 0:
# ignore card credit and next transactions are already debited
self.is_coming = False
continue
if self.is_coming is None and raw.startswith('ACHATS CARTE'):
# Ignore card debit
continue
t._is_coming = bool(self.is_coming)
yield t

View file

@ -0,0 +1,31 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from weboob.tools.test import BackendTest
class CreditDuNordTest(BackendTest):
BACKEND = 'creditdunord'
def test_creditdunord(self):
l = list(self.backend.iter_accounts())
a = l[0]
list(self.backend.iter_history(a))
list(self.backend.iter_coming(a))