support both pro and perso websites

This commit is contained in:
Romain Bignon 2013-01-02 16:54:10 +01:00
commit b0fd66ae67
7 changed files with 292 additions and 8 deletions

View file

@ -20,9 +20,10 @@
from weboob.capabilities.bank import ICapBank, AccountNotFound
from weboob.tools.backend import BaseBackend, BackendConfig
from weboob.tools.value import ValueBackendPassword
from weboob.tools.value import ValueBackendPassword, Value
from .browser import CreditCooperatif
from .perso.browser import CreditCooperatif as CreditCooperatifPerso
from .pro.browser import CreditCooperatif as CreditCooperatifPro
__all__ = ['CreditCooperatifBackend']
@ -35,14 +36,23 @@ class CreditCooperatifBackend(BaseBackend, ICapBank):
VERSION = '0.e'
DESCRIPTION = u'Credit Cooperatif French bank website'
LICENSE = 'AGPLv3+'
CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False),
auth_type = {'particular': "Interface Particuliers",
'weak' : "Code confidentiel (pro)",
'strong': "Sesame (pro)"}
CONFIG = BackendConfig(Value('auth_type', label='Authentication type', choices=auth_type, default="particular"),
ValueBackendPassword('login', label='Account ID', masked=False),
ValueBackendPassword('password', label='Password or one time pin'))
BROWSER = CreditCooperatif
def create_default_browser(self):
return self.create_browser(self.config['login'].get(),
self.config['password'].get())
if self.config['auth_type'].get() == 'particular':
self.BROWSER = CreditCooperatifPerso
return self.create_browser(self.config['login'].get(),
self.config['password'].get())
else:
self.BROWSER = CreditCooperatifPro
return self.create_browser(self.config['login'].get(),
self.config['password'].get(),
strong_auth=self.config['auth_type'].get() == "strong")
def iter_accounts(self):
with self.browser:

View file

@ -98,7 +98,7 @@ class TransactionsJSONPage(BasePage):
ROW_DEBIT = -2
def get_transactions(self):
for tr in self.document['aaData']:
for tr in self.document['exportData'][1:]:
t = Transaction(0)
t.parse(tr[self.ROW_DATE], tr[self.ROW_TEXT])
t.set_amount(tr[self.ROW_CREDIT], tr[self.ROW_DEBIT])

View file

View file

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# Copyright(C) 2012 Kevin Pouget
#
# 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.browser import BaseBrowser, BrowserIncorrectPassword
from .pages import LoginPage, AccountsPage, TransactionsPage, ComingTransactionsPage
__all__ = ['CreditCooperatif']
class CreditCooperatif(BaseBrowser):
PROTOCOL = 'https'
ENCODING = 'iso-8859-15'
DOMAIN = "www.coopanet.com"
PAGES = {'https://www.coopanet.com/banque/sso/.*': LoginPage,
'https://www.coopanet.com/banque/cpt/incoopanetj2ee.do.*': AccountsPage,
'https://www.coopanet.com/banque/cpt/cpt/situationcomptes.do\?lnkReleveAction=X&numeroExterne=.*': TransactionsPage,
'https://www.coopanet.com/banque/cpt/cpt/relevecompte.do\?tri_page=.*': TransactionsPage,
'https://www.coopanet.com/banque/cpt/cpt/situationcomptes.do\?lnkOpCB=X&numeroExterne=.*': ComingTransactionsPage
}
def __init__(self, *args, **kwargs):
self.strong_auth = kwargs.pop('strong_auth', False)
BaseBrowser.__init__(self, *args, **kwargs)
def home(self):
self.location("/banque/sso/")
assert self.is_on_page(LoginPage)
def is_logged(self):
return not self.is_on_page(LoginPage)
def login(self):
"""
Attempt to log in.
Note: this method does nothing if we are already logged in.
"""
assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring)
assert isinstance(self.strong_auth, bool)
if self.is_logged():
return
if not self.is_on_page(LoginPage):
self.home()
self.page.login(self.username, self.password, self.strong_auth)
if not self.is_logged():
raise BrowserIncorrectPassword()
def get_accounts_list(self):
self.location(self.buildurl('/banque/cpt/incoopanetj2ee.do?ssomode=ok'))
return self.page.get_list()
def get_account(self, id):
assert isinstance(id, basestring)
for a in self.get_accounts_list():
if a.id == id:
return a
return None
def get_history(self, account):
self.location('/banque/cpt/cpt/situationcomptes.do?lnkReleveAction=X&numeroExterne='+ account.id)
while 1:
assert self.is_on_page(TransactionsPage)
for tr in self.page.get_history():
yield tr
next_url = self.page.get_next_url()
if next_url is None:
return
self.location(next_url)
def get_coming(self, account):
self.location('/banque/cpt/cpt/situationcomptes.do?lnkOpCB=X&numeroExterne='+ account.id)
assert self.is_on_page(ComingTransactionsPage)
for ctr in self.page.get_history():
yield ctr

View file

@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
# Copyright(C) 2012 Kevin Pouget
#
# 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
import time
from weboob.tools.browser import BasePage
from weboob.capabilities.bank import Account
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
__all__ = ['LoginPage', 'AccountsPage', 'TransactionsPage', 'ComingTransactionsPage']
class LoginPage(BasePage):
def login(self, login, pin, strong_auth):
form_nb = 1 if strong_auth else 0
indentType = "RENFORCE" if strong_auth else "MDP"
self.browser.select_form(name='loginCoForm', nr=form_nb)
self.browser['codeUtil'] = login
self.browser['motPasse'] = pin
assert self.browser['identType'] == indentType
self.browser.submit(nologin=True)
class AccountsPage(BasePage):
ACCOUNT_TYPES = {u'COMPTE NEF': Account.TYPE_CHECKING}
CPT_ROW_ID = 0
CPT_ROW_NAME = 1
CPT_ROW_NATURE = 2
CPT_ROW_BALANCE = 3
CPT_ROW_ENCOURS = 4
def is_error(self):
for par in self.document.xpath('//p[@class=acctxtnoirlien]'):
if par.text is not None and u"La page demandée ne peut pas être affichée." in par.text:
return True
return False
def get_list(self):
for trCompte in self.document.xpath('//table[@id="compte"]/tbody/tr'):
tds = trCompte.findall('td')
account = Account()
account.id = tds[self.CPT_ROW_ID].text.strip()
account.label = tds[self.CPT_ROW_NAME].text.strip()
account_type_str = "".join([td.text for td in tds[self.CPT_ROW_NATURE].xpath('.//td[@class="txt"]')]).strip()
account.type = self.ACCOUNT_TYPES.get(account_type_str, Account.TYPE_UNKNOWN)
account.balance = Decimal(FrenchTransaction.clean_amount(tds[self.CPT_ROW_BALANCE].find("a").text))
account.coming = Decimal(FrenchTransaction.clean_amount( tds[self.CPT_ROW_ENCOURS].find("a").text))
account.currency = account.get_currency(tds[self.CPT_ROW_BALANCE].find("a").text)
yield account
return
class Transaction(FrenchTransaction):
PATTERNS = [(re.compile('^RETRAIT DAB (?P<text>.*?).*'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile('^(?P<text>.*) RETRAIT DU (?P<dd>\d{2})(?P<mm>\d{2})(?P<yy>\d{2}) .*'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile('^CARTE \d+ .*'), FrenchTransaction.TYPE_CARD),
(re.compile('^VIR(EMENT)? (?P<text>.*)'), FrenchTransaction.TYPE_TRANSFER),
(re.compile('^PRLV (?P<text>.*)'), FrenchTransaction.TYPE_ORDER),
(re.compile('^CHEQUE.*'), FrenchTransaction.TYPE_CHECK),
(re.compile('^(AGIOS /|FRAIS) (?P<text>.*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^ABONNEMENT (?P<text>.*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^REMISE (?P<text>.*)'), FrenchTransaction.TYPE_DEPOSIT),
(re.compile('^(?P<text>.*)( \d+)? QUITTANCE .*'),
FrenchTransaction.TYPE_ORDER),
(re.compile('^.* LE (?P<dd>\d{2})/(?P<mm>\d{2})/(?P<yy>\d{2})$'),
FrenchTransaction.TYPE_UNKNOWN),
]
class TransactionsPage(BasePage):
def get_next_url(self):
# can be 'Suivant' or ' Suivant'
next = self.document.xpath("//a[normalize-space(text()) = 'Suivant']")
if not next:
return None
return next[0].attrib["href"]
TR_DATE = 0
TR_TEXT = 2
TR_DEBIT = 3
TR_CREDIT = 4
def get_history(self):
for tr in self.document.xpath('//table[@id="operation"]/tbody/tr'):
tds = tr.findall('td')
def get_content(td):
ret = "".join([ttd.text if ttd.text else "" for ttd in td.xpath(".//td")])
return ret.replace(u"\xa0", " ").strip()
date = get_content(tds[self.TR_DATE])
raw = get_content(tds[self.TR_TEXT])
debit = get_content(tds[self.TR_DEBIT])
credit = get_content(tds[self.TR_CREDIT])
t = Transaction(date+""+raw)
t.parse(date, re.sub(r'[ ]+', ' ', raw))
t.set_amount(credit, debit)
yield t
class ComingTransactionsPage(BasePage):
COM_TR_COMMENT = 0
COM_TR_DATE = 1
COM_TR_TEXT = 2
COM_TR_VALUE = 3
def get_history(self):
comment = None
for tr in self.document.xpath('//table[@id="operation"]/tbody/tr'):
tds = tr.findall('td')
def get_content(td):
ret = td.text
return ret.replace(u"\xa0", " ").strip()
raw = get_content(tds[self.COM_TR_TEXT])
if comment is None:
comment = get_content(tds[self.COM_TR_COMMENT])
raw = "%s (%s) " % (raw, comment)
debit = get_content(tds[self.COM_TR_VALUE])
date = get_content(tds[self.COM_TR_DATE])
if comment is not None:
#date is 'JJ/MM'. add '/YYYY'
date += comment[comment.rindex("/"):]
else:
date += "/%d" % time.localtime().tm_year
t = Transaction(date+""+raw)
t.parse(date, re.sub(r'[ ]+', ' ', raw))
t.set_amount("", debit)
yield t