use www.credit-cooperatif.coop instead of coopanet.com

This commit is contained in:
Romain Bignon 2013-01-02 13:58:24 +01:00
commit 1b09042b67
3 changed files with 110 additions and 144 deletions

View file

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

View file

@ -17,9 +17,11 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>. # along with weboob. If not, see <http://www.gnu.org/licenses/>.
import urllib
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
from .pages import LoginPage, AccountsPage, TransactionsPage, ComingTransactionsPage from .pages import LoginPage, LoggedPage, AccountsPage, TransactionsPage, TransactionsJSONPage, ComingTransactionsPage
__all__ = ['CreditCooperatif'] __all__ = ['CreditCooperatif']
@ -28,22 +30,17 @@ __all__ = ['CreditCooperatif']
class CreditCooperatif(BaseBrowser): class CreditCooperatif(BaseBrowser):
PROTOCOL = 'https' PROTOCOL = 'https'
ENCODING = 'iso-8859-15' ENCODING = 'iso-8859-15'
DOMAIN = "www.coopanet.com" DOMAIN = "www.credit-cooperatif.coop"
PAGES = {'https://www.coopanet.com/banque/sso/.*': LoginPage, PAGES = {'https://www.credit-cooperatif.coop/portail/particuliers/login.do': LoginPage,
'https://www.coopanet.com/banque/cpt/incoopanetj2ee.do.*': AccountsPage, 'https://www.credit-cooperatif.coop/portail/particuliers/authentification.do': LoggedPage,
'https://www.coopanet.com/banque/cpt/cpt/situationcomptes.do\?lnkReleveAction=X&numeroExterne=.*': TransactionsPage, 'https://www.credit-cooperatif.coop/portail/particuliers/mescomptes/synthese.do': AccountsPage,
'https://www.coopanet.com/banque/cpt/cpt/relevecompte.do\?tri_page=.*': TransactionsPage, 'https://www.credit-cooperatif.coop/portail/particuliers/mescomptes/relevedesoperations.do': TransactionsPage,
'https://www.coopanet.com/banque/cpt/cpt/situationcomptes.do\?lnkOpCB=X&numeroExterne=.*': ComingTransactionsPage 'https://www.credit-cooperatif.coop/portail/particuliers/mescomptes/relevedesoperationsjson.do': (TransactionsJSONPage, 'json'),
'https://www.credit-cooperatif.coop/portail/particuliers/mescomptes/synthese/operationsencourslien.do': ComingTransactionsPage,
} }
def __init__(self, *args, **kwargs):
self.strong_auth = kwargs.pop('strong_auth', False)
BaseBrowser.__init__(self, *args, **kwargs)
def home(self): def home(self):
self.location("/banque/sso/") self.location("/portail/particuliers/mescomptes/synthese.do")
assert self.is_on_page(LoginPage)
def is_logged(self): def is_logged(self):
return not self.is_on_page(LoginPage) return not self.is_on_page(LoginPage)
@ -56,7 +53,6 @@ class CreditCooperatif(BaseBrowser):
assert isinstance(self.username, basestring) assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring) assert isinstance(self.password, basestring)
assert isinstance(self.strong_auth, bool)
if self.is_logged(): if self.is_logged():
return return
@ -64,13 +60,19 @@ class CreditCooperatif(BaseBrowser):
if not self.is_on_page(LoginPage): if not self.is_on_page(LoginPage):
self.home() self.home()
self.page.login(self.username, self.password, self.strong_auth) self.page.login(self.username, self.password)
if self.is_on_page(LoggedPage):
error = self.page.get_error()
if error is not None:
raise BrowserIncorrectPassword(error)
if not self.is_logged(): if not self.is_logged():
raise BrowserIncorrectPassword() raise BrowserIncorrectPassword()
def get_accounts_list(self): def get_accounts_list(self):
self.location(self.buildurl('/banque/cpt/incoopanetj2ee.do?ssomode=ok')) if not self.is_on_page(AccountsPage):
self.location('/portail/particuliers/mescomptes/synthese.do')
return self.page.get_list() return self.page.get_list()
@ -84,24 +86,25 @@ class CreditCooperatif(BaseBrowser):
return None return None
def get_history(self, account): def get_history(self, account):
self.location('/banque/cpt/cpt/situationcomptes.do?lnkReleveAction=X&numeroExterne='+ account.id) data = {'accountExternalNumber': account.id}
self.location('/portail/particuliers/mescomptes/relevedesoperations.do', urllib.urlencode(data))
while 1: data = {'iDisplayLength': 400,
assert self.is_on_page(TransactionsPage) 'iDisplayStart': 0,
'iSortCol_0': 0,
'iSortingCols': 1,
'sColumns': '',
'sEcho': 1,
'sSortDir_0': 'asc',
}
self.location('/portail/particuliers/mescomptes/relevedesoperationsjson.do', urllib.urlencode(data))
for tr in self.page.get_history(): return self.page.get_transactions()
yield tr
next_url = self.page.get_next_url()
if next_url is None:
return
self.location(next_url)
def get_coming(self, account): def get_coming(self, account):
self.location('/banque/cpt/cpt/situationcomptes.do?lnkOpCB=X&numeroExterne='+ account.id) data = {'accountExternalNumber': account.id}
self.location('/portail/particuliers/mescomptes/synthese/operationsencourslien.do', urllib.urlencode(data))
assert self.is_on_page(ComingTransactionsPage) assert self.is_on_page(ComingTransactionsPage)
for ctr in self.page.get_history(): return self.page.get_transactions()
yield ctr

View file

@ -20,8 +20,8 @@
from decimal import Decimal from decimal import Decimal
import re import re
import time
from weboob.tools.json import json
from weboob.tools.browser import BasePage from weboob.tools.browser import BasePage
from weboob.capabilities.bank import Account from weboob.capabilities.bank import Account
from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.capabilities.bank.transactions import FrenchTransaction
@ -29,64 +29,57 @@ from weboob.tools.capabilities.bank.transactions import FrenchTransaction
__all__ = ['LoginPage', 'AccountsPage', 'TransactionsPage', 'ComingTransactionsPage'] __all__ = ['LoginPage', 'AccountsPage', 'TransactionsPage', 'ComingTransactionsPage']
class LoginPage(BasePage): class LoginPage(BasePage):
def login(self, login, pin, strong_auth): def login(self, login, password):
form_nb = 1 if strong_auth else 0 self.browser.select_form(predicate=lambda form: form.attrs.get('id', '') == 'AuthForm')
indentType = "RENFORCE" if strong_auth else "MDP" self.browser['j_username'] = login.encode('iso-8859-15')
self.browser['j_password'] = password.encode('iso-8859-15')
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) self.browser.submit(nologin=True)
class LoggedPage(BasePage):
def get_error(self):
div = self.document.xpath('//div[@class="errorForm-msg"]')
if len(div) == 0:
return None
msg = u', '.join([li.text.strip() for li in div[0].xpath('.//li')])
return re.sub('[\r\n\t\xa0]+', ' ', msg)
class AccountsPage(BasePage): class AccountsPage(BasePage):
ACCOUNT_TYPES = {u'COMPTE NEF': Account.TYPE_CHECKING} 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): def get_list(self):
for trCompte in self.document.xpath('//table[@id="compte"]/tbody/tr'): for table in self.document.getroot().cssselect('table.table-synthese'):
tds = trCompte.findall('td')
account = Account() account = Account()
labels = table.xpath('.//ul[@class="nClient"]/li')
account_type_str = table.xpath('.//h2[@class="tt_compte"]')[0].text.strip()
account.id = tds[self.CPT_ROW_ID].text.strip() account.id = re.sub(u'[^0-9]', '', labels[-1].text)
account.label = tds[self.CPT_ROW_NAME].text.strip() account.label = u' '.join([account_type_str, labels[0].text.strip()])
account.type = self.ACCOUNT_TYPES.get(account_type_str, Account.TYPE_UNKNOWN)
account_type_str = "".join([td.text for td in tds[self.CPT_ROW_NATURE].xpath('.//td[@class="txt"]')]).strip() balance = table.xpath('.//td[@class="sum_solde"]//span')[-1].text
account.balance = Decimal(FrenchTransaction.clean_amount(balance))
account.currency = account.get_currency(balance)
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 yield account
return
class Transaction(FrenchTransaction): class Transaction(FrenchTransaction):
PATTERNS = [(re.compile('^RETRAIT DAB (?P<text>.*?).*'), PATTERNS = [(re.compile('^(?P<text>RETRAIT DAB) (?P<dd>\d{2})-(?P<mm>\d{2})-([\d\-]+)'),
FrenchTransaction.TYPE_WITHDRAWAL), FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile('^(?P<text>.*) RETRAIT DU (?P<dd>\d{2})(?P<mm>\d{2})(?P<yy>\d{2}) .*'), (re.compile('^RETRAIT DAB (?P<dd>\d{2})-(?P<mm>\d{2})-([\d\-]+) (?P<text>.*)'),
FrenchTransaction.TYPE_WITHDRAWAL), FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile('^CARTE \d+ .*'), FrenchTransaction.TYPE_CARD), (re.compile('^CARTE (?P<dd>\d{2})(?P<mm>\d{2}) \d+ (?P<text>.*)'),
(re.compile('^VIR(EMENT)? (?P<text>.*)'), FrenchTransaction.TYPE_TRANSFER), FrenchTransaction.TYPE_CARD),
(re.compile('^PRLV (?P<text>.*)'), FrenchTransaction.TYPE_ORDER), (re.compile('^VIR COOPA (?P<dd>\d{2})/(?P<mm>\d{2}) (?P<text>.*)'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile('^VIR(EMENT|EMT)? (?P<text>.*?)(- .*)?$'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile('^(PRLV|PRELEVEMENT) (?P<text>.*?)(- .*)?$'),
FrenchTransaction.TYPE_ORDER),
(re.compile('^CHEQUE.*'), FrenchTransaction.TYPE_CHECK), (re.compile('^CHEQUE.*'), FrenchTransaction.TYPE_CHECK),
(re.compile('^(AGIOS /|FRAIS) (?P<text>.*)'), FrenchTransaction.TYPE_BANK), (re.compile('^(AGIOS /|FRAIS) (?P<text>.*)'),FrenchTransaction.TYPE_BANK),
(re.compile('^ABONNEMENT (?P<text>.*)'), FrenchTransaction.TYPE_BANK), (re.compile('^ABONNEMENT (?P<text>.*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^REMISE (?P<text>.*)'), FrenchTransaction.TYPE_DEPOSIT), (re.compile('^REMISE (?P<text>.*)'), FrenchTransaction.TYPE_DEPOSIT),
(re.compile('^(?P<text>.*)( \d+)? QUITTANCE .*'), (re.compile('^(?P<text>.*)( \d+)? QUITTANCE .*'),
@ -96,72 +89,46 @@ class Transaction(FrenchTransaction):
] ]
class TransactionsPage(BasePage): class TransactionsPage(BasePage):
def get_next_url(self): pass
# can be 'Suivant' or ' Suivant'
next = self.document.xpath("//a[normalize-space(text()) = 'Suivant']")
if not next: class TransactionsJSONPage(BasePage):
return None ROW_DATE = 0
ROW_TEXT = 2
return next[0].attrib["href"] ROW_CREDIT = -1
ROW_DEBIT = -2
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)
def get_transactions(self):
for tr in self.document['aaData']:
t = Transaction(0)
t.parse(tr[self.ROW_DATE], tr[self.ROW_TEXT])
t.set_amount(tr[self.ROW_CREDIT], tr[self.ROW_DEBIT])
yield t yield t
class ComingTransactionsPage(BasePage): class ComingTransactionsPage(BasePage):
COM_TR_COMMENT = 0 ROW_REF = 0
COM_TR_DATE = 1 ROW_TEXT = 1
COM_TR_TEXT = 2 ROW_DATE = 2
COM_TR_VALUE = 3 ROW_CREDIT = -1
ROW_DEBIT = -2
def get_history(self): def get_transactions(self):
comment = None data = []
for tr in self.document.xpath('//table[@id="operation"]/tbody/tr'): for script in self.document.xpath('//script'):
tds = tr.findall('td') txt = script.text
if txt is None:
continue
def get_content(td): pattern = 'var jsonData ='
ret = td.text start = txt.find(pattern)
return ret.replace(u"\xa0", " ").strip() if start < 0:
continue
raw = get_content(tds[self.COM_TR_TEXT]) txt = txt[start+len(pattern):start+txt[start:].find(';')]
data = json.loads(txt)
if comment is None: break
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)
for tr in data:
t = Transaction(0)
t.parse(tr[self.ROW_DATE], tr[self.ROW_TEXT])
t.set_amount(tr[self.ROW_CREDIT], tr[self.ROW_DEBIT])
yield t yield t