From 9856c27f8cc48189ed9d1bbf2f0db92f5d690721 Mon Sep 17 00:00:00 2001 From: Romain Bignon Date: Mon, 25 Mar 2013 16:29:14 +0100 Subject: [PATCH] support professionnal accounts --- modules/creditdunord/browser.py | 46 +++++------ modules/creditdunord/pages.py | 131 +++++++++++++++++++++++++++++--- 2 files changed, 138 insertions(+), 39 deletions(-) diff --git a/modules/creditdunord/browser.py b/modules/creditdunord/browser.py index 3a0da917..de464787 100644 --- a/modules/creditdunord/browser.py +++ b/modules/creditdunord/browser.py @@ -23,7 +23,7 @@ import urllib from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword -from .pages import LoginPage, AccountsPage, TransactionsPage +from .pages import LoginPage, AccountsPage, ProAccountsPage, TransactionsPage, ProTransactionsPage __all__ = ['CreditDuNordBrowser'] @@ -35,8 +35,11 @@ class CreditDuNordBrowser(BaseBrowser): PAGES = {'https://[^/]+/?': LoginPage, 'https://[^/]+/.*\?.*_pageLabel=page_erreur_connexion': LoginPage, 'https://[^/]+/vos-comptes/particuliers(\?.*)?': AccountsPage, - 'https://[^/]+/vos-comptes/.*/transac/.*': TransactionsPage, + 'https://[^/]+/vos-comptes/.*/transac/particuliers.*': TransactionsPage, + 'https://[^/]+/vos-comptes/professionnels.*': ProAccountsPage, + 'https://[^/]+/vos-comptes/.*/transac/professionnels.*': ProTransactionsPage, } + account_type = 'particuliers' def __init__(self, website, *args, **kwargs): self.DOMAIN = website @@ -47,11 +50,9 @@ class CreditDuNordBrowser(BaseBrowser): def home(self): if self.is_logged(): - self.location(self.buildurl('/vos-comptes/particuliers')) + self.location(self.buildurl('/vos-comptes/%s' % self.account_type)) else: self.login() - return - return self.location(self.buildurl('/vos-comptes/particuliers')) def login(self): assert isinstance(self.username, basestring) @@ -79,9 +80,13 @@ class CreditDuNordBrowser(BaseBrowser): if not self.is_logged(): raise BrowserIncorrectPassword() + m = re.match('https://[^/]+/vos-comptes/(\w+).*', self.page.url) + if m: + self.account_type = m.group(1) + def get_accounts_list(self): if not self.is_on_page(AccountsPage): - self.location(self.buildurl('/vos-comptes/particuliers')) + self.location(self.buildurl('/vos-comptes/%s' % self.account_type)) return self.page.get_list() def get_account(self, id): @@ -94,20 +99,12 @@ class CreditDuNordBrowser(BaseBrowser): return None - def iter_transactions(self, link, link_id, execution, is_coming=None): - if link_id is None: + def iter_transactions(self, link, args, is_coming=None): + if args is None: return - event = 'clicDetailCompte' - while 1: - data = {'_eventId': event, - '_ipc_eventValue': '', - '_ipc_fireEvent': '', - 'deviseAffichee': 'DEVISE', - 'execution': execution, - 'idCompteClique': link_id, - } - self.location(link, urllib.urlencode(data)) + while args is not None: + self.location(link, urllib.urlencode(args)) assert self.is_on_page(TransactionsPage) @@ -116,22 +113,17 @@ class CreditDuNordBrowser(BaseBrowser): 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 + args = self.page.get_next_args(args) def get_history(self, account): - for tr in self.iter_transactions(account._link, account._link_id, account._execution): + for tr in self.iter_transactions(account._link, account._args): 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): + for link_args in account._card_ids: + for tr in self.iter_transactions(account._link, link_args, True): yield tr diff --git a/modules/creditdunord/pages.py b/modules/creditdunord/pages.py index 17712f9a..afe33cb1 100644 --- a/modules/creditdunord/pages.py +++ b/modules/creditdunord/pages.py @@ -18,6 +18,7 @@ # along with weboob. If not, see . +from urllib import quote from decimal import Decimal import re from cStringIO import StringIO @@ -97,14 +98,19 @@ class AccountsPage(CDNBasePage): a.label = self.parser.tocleanstring(self.parser.parse(fp, self.browser.ENCODING).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() if line[self.COL_HISTORY] == 'true': - a._link_id = line[self.COL_ID] + a._args = {'_eventId': 'clicDetailCompte', + '_ipc_eventValue': '', + '_ipc_fireEvent': '', + 'deviseAffichee': 'DEVISE', + 'execution': self.get_execution(), + 'idCompteClique': line[self.COL_ID], + } else: - a._link_id = None + a._args = None if a.id.find('_CarteVisa') >= 0: - accounts[0]._card_ids.append(a._link_id) + accounts[0]._card_ids.append(a._args) if not accounts[0].coming: accounts[0].coming = Decimal('0.0') accounts[0].coming += a.balance @@ -116,6 +122,45 @@ class AccountsPage(CDNBasePage): return iter(accounts) +class ProAccountsPage(AccountsPage): + COL_ID = 0 + COL_BALANCE = 1 + + ARGS = ['Banque', 'Agence', 'classement', 'Serie', 'SSCompte', 'Devise', 'CodeDeviseCCB', 'LibelleCompte', 'IntituleCompte', 'Indiceclassement', 'IndiceCompte', 'NomClassement'] + def params_from_js(self, text): + l = [] + for sub in re.findall("'([^']*)'", text): + l.append(sub) + + url = '/vos-comptes/IPT/appmanager/transac/professionnels?_nfpb=true&_windowLabel=portletInstance_18&_pageLabel=page_synthese_v1' + '&_cdnCltUrl=' + "/transacClippe/" + quote(l.pop(0)) + args = {} + + for i, key in enumerate(self.ARGS): + args[key] = l[self.ARGS.index(key)] + + return url, args + + + def get_list(self): + for tr in self.document.xpath('//table[@class="datas"]//tr'): + if tr.attrib.get('class', '') == 'entete': + continue + + cols = tr.findall('td') + + a = Account() + a.id = cols[self.COL_ID].xpath('.//span[@class="right-underline"]')[0].text.strip() + a.label = unicode(cols[self.COL_ID].xpath('.//span[@class="left-underline"]')[0].text.strip()) + balance = self.parser.tocleanstring(cols[self.COL_BALANCE]) + a.balance = Decimal(FrenchTransaction.clean_amount(balance)) + a.currency = a.get_currency(balance) + a._link, a._args = self.params_from_js(cols[self.COL_ID].find('a').attrib['href']) + + a._card_ids = [] + + yield a + + class Transaction(FrenchTransaction): PATTERNS = [(re.compile(r'^(?PRET DAB \w+ .*?) LE (?P
\d{2})(?P\d{2})$'), FrenchTransaction.TYPE_WITHDRAWAL), @@ -143,6 +188,15 @@ class TransactionsPage(CDNBasePage): is_coming = None + def get_next_args(self, args): + if self.is_last(): + return None + + args['_eventId'] = 'clicChangerPageSuivant' + args['execution'] = self.get_execution() + args.pop('idCompteClique', None) + return args + def is_last(self): for script in self.document.xpath('//script'): txt = script.text @@ -154,11 +208,28 @@ class TransactionsPage(CDNBasePage): return True + def set_coming(self, t): + if self.is_coming is not None and t.raw.startswith('TOTAL DES') and t.amount > 0: + # ignore card credit and next transactions are already debited + self.is_coming = False + return True + if self.is_coming is None and t.raw.startswith('ACHATS CARTE'): + # Ignore card debit + return True + + t._is_coming = bool(self.is_coming) + return False + 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') + no_trans = self.get_from_js('js_noMvts = new Ext.Panel(', ')') + if no_trans is not None: + # there is no transactions for this account, this is normal. + return + else: + raise BrokenPageError('Unable to find transactions list in scripts') data = json.loads('[%s]' % txt.replace('"', '\\"').replace("'", '"')) @@ -175,13 +246,49 @@ class TransactionsPage(CDNBasePage): t.parse(date, raw) t.set_amount(line[self.COL_VALUE]) - if self.is_coming is not None 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 + if self.set_coming(t): + continue + + yield t + +class ProTransactionsPage(TransactionsPage): + def get_next_args(self, args): + txt = self.get_from_js('myPage.setPiedPage(oNavSuivantPrec_1(', ')') + + if txt is None: + return None + + l = txt.split(',') + if int(l[4]) <= 40: + return None + + args['PageDemandee'] = int(args.get('PageDemandee', 1)) + 1 + return args + + def parse_transactions(self): + transactions = {} + for script in self.document.xpath('//script'): + txt = script.text + if txt is None: + continue + + for i, key, value in re.findall('listeopecv\[(\d+)\]\[\'(\w+)\'\]="(.*)";', txt): + i = int(i) + if not i in transactions: + transactions[i] = {} + transactions[i][key] = value + + return transactions.iteritems() + + def get_history(self): + for i, tr in self.parse_transactions(): + t = Transaction(i) + date = tr['date'] + raw = self.parser.strip('

%s

' % (' '.join([tr['typeope'], tr['LibComp']]))) + t.parse(date, raw) + t.set_amount(tr['mont']) + + if self.set_coming(t): continue - t._is_coming = bool(self.is_coming) yield t