diff --git a/weboob/backends/bnporc/backend.py b/weboob/backends/bnporc/backend.py index d13afe35..0750f711 100644 --- a/weboob/backends/bnporc/backend.py +++ b/weboob/backends/bnporc/backend.py @@ -76,12 +76,12 @@ class BNPorcBackend(BaseBackend, ICapBank): def iter_history(self, account): with self.browser: - for history in self.browser.get_history(account): + for history in self.browser.get_history(account.id): yield history def iter_operations(self, account): with self.browser: - for coming in self.browser.get_coming_operations(account): + for coming in self.browser.get_coming_operations(account.id): yield coming def iter_transfer_recipients(self, ignored): diff --git a/weboob/backends/bnporc/browser.py b/weboob/backends/bnporc/browser.py index 29b628b4..9c8fadf6 100644 --- a/weboob/backends/bnporc/browser.py +++ b/weboob/backends/bnporc/browser.py @@ -34,18 +34,18 @@ class BNPorc(BaseBrowser): DOMAIN = 'www.secure.bnpparibas.net' PROTOCOL = 'https' ENCODING = None # refer to the HTML encoding - PAGES = {'.*identifiant=DOSSIER_Releves_D_Operation.*': pages.AccountsList, - '.*SAF_ROP.*': pages.AccountHistory, + PAGES = {'.*pageId=unedescomptes.*': pages.AccountsList, + '.*pageId=releveoperations.*': pages.AccountHistory, '.*Action=SAF_CHM.*': pages.ChangePasswordPage, - '.*NS_AVEDT.*': pages.AccountComing, + '.*pageId=mouvementsavenir.*': pages.AccountComing, '.*NS_AVEDP.*': pages.AccountPrelevement, '.*NS_VIRDF.*': pages.TransferPage, '.*NS_VIRDC.*': pages.TransferConfirmPage, '.*/NS_VIRDA\?stp=(?P\d+).*': pages.TransferCompletePage, - '.*Action=DSP_VGLOBALE.*': pages.LoginPage, '.*type=homeconnex.*': pages.LoginPage, '.*layout=HomeConnexion.*': pages.ConfirmPage, '.*SAF_CHM_VALID.*': pages.ConfirmPage, + '.*Action=DSP_MSG.*': pages.MessagePage, } def __init__(self, *args, **kwargs): @@ -81,8 +81,8 @@ class BNPorc(BaseBrowser): self.page.change_password(self.password, new_password) - if not self.is_on_page(pages.ConfirmPage): - self.logger.error('Oops, unable to change password') + if not self.is_on_page(pages.ConfirmPage) or self.page.get_error() is not None: + self.logger.error('Oops, unable to change password (%s)' % (self.page.get_error() if self.is_on_page(pages.ConfirmPage) else 'unknown')) return self.password, self.rotating_password = (new_password, self.password) @@ -123,16 +123,18 @@ class BNPorc(BaseBrowser): return None - def get_history(self, account): - if not self.is_on_page(pages.AccountHistory) or self.page.account.id != account.id: - self.location('/SAF_ROP?ch4=%s' % account.link_id) + def get_history(self, id): + self.location('/banque/portail/particulier/FicheA?contractId=%d&pageId=releveoperations&_eventId=changeOperationsPerPage&operationsPerPage=200' % int(id)) return self.page.get_operations() - def get_coming_operations(self, account): - if not self.is_on_page(pages.AccountComing) or self.page.account.id != account.id: - self.location('/NS_AVEDT?ch4=%s' % account.link_id) + def get_coming_operations(self, id): + if not self.is_on_page(pages.AccountsList): + self.location('/NSFR?Action=DSP_VGLOBALE') + execution = self.page.get_execution_id() + self.location('/banque/portail/particulier/FicheA?externalIAId=IAStatements&contractId=%d&pastOrPendingOperations=2&pageId=mouvementsavenir&execution=%s' % (int(id), execution)) return self.page.get_operations() + @check_expired_password def get_transfer_accounts(self): if not self.is_on_page(pages.TransferPage): self.location('/NS_VIRDF') @@ -140,6 +142,7 @@ class BNPorc(BaseBrowser): assert self.is_on_page(pages.TransferPage) return self.page.get_accounts() + @check_expired_password def transfer(self, from_id, to_id, amount, reason=None): if not self.is_on_page(pages.TransferPage): self.location('/NS_VIRDF') diff --git a/weboob/backends/bnporc/pages/__init__.py b/weboob/backends/bnporc/pages/__init__.py index 97d3bd8b..1e5cecaf 100644 --- a/weboob/backends/bnporc/pages/__init__.py +++ b/weboob/backends/bnporc/pages/__init__.py @@ -22,10 +22,10 @@ from .accounts_list import AccountsList from .account_coming import AccountComing from .account_history import AccountHistory from .transfer import TransferPage, TransferConfirmPage, TransferCompletePage -from .login import LoginPage, ConfirmPage, ChangePasswordPage +from .login import LoginPage, ConfirmPage, ChangePasswordPage, MessagePage class AccountPrelevement(AccountsList): pass __all__ = ['AccountsList', 'AccountComing', 'AccountHistory', 'LoginPage', - 'ConfirmPage', 'AccountPrelevement', 'ChangePasswordPage', + 'ConfirmPage', 'MessagePage', 'AccountPrelevement', 'ChangePasswordPage', 'TransferPage', 'TransferConfirmPage', 'TransferCompletePage'] diff --git a/weboob/backends/bnporc/pages/account_coming.py b/weboob/backends/bnporc/pages/account_coming.py index 39d22ca3..233351c4 100644 --- a/weboob/backends/bnporc/pages/account_coming.py +++ b/weboob/backends/bnporc/pages/account_coming.py @@ -29,7 +29,7 @@ __all__ = ['AccountComing'] class AccountComing(BasePage): - LABEL_PATTERNS = [('^FACTURECARTEDU(?P
\d{2})(?P\d{2})(?P\d{2})(?P.*)', + LABEL_PATTERNS = [('^FACTURE CARTE DU (?P
\d{2})(?P\d{2})(?P\d{2}) (?P.*)', u'CB %(yy)s-%(mm)s-%(dd)s: %(text)s'), ('^PRELEVEMENT(?P.*)', 'Order: %(text)s'), ('^ECHEANCEPRET(?P.*)', u'Loan payment n°%(text)s'), @@ -38,20 +38,18 @@ class AccountComing(BasePage): def on_loaded(self): self.operations = [] - for tr in self.document.getiterator('tr'): - if tr.attrib.get('class', '') == 'hdoc1' or tr.attrib.get('class', '') == 'hdotc1': + for tr in self.document.xpath('//table[@id="tableauOperations"]//tr'): + if 'typeop' in tr.attrib: tds = tr.findall('td') if len(tds) != 3: continue - d = tds[0].getchildren()[0].attrib.get('name', '') - d = date(int(d[0:4]), int(d[4:6]), int(d[6:8])) - label = u'' - label += tds[1].text or u'' + d = tr.attrib['dateop'] + d = date(int(d[4:8]), int(d[2:4]), int(d[0:2])) + label = tds[1].text or u'' label = label.replace(u'\xa0', u'') for child in tds[1].getchildren(): if child.text: label += child.text if child.tail: label += child.tail - if tds[1].tail: label += tds[1].tail label = label.strip() for pattern, text in self.LABEL_PATTERNS: @@ -59,7 +57,7 @@ class AccountComing(BasePage): if m: label = text % m.groupdict() - amount = tds[2].text.replace('.', '').replace(',', '.') + amount = tds[2].text.replace('.','').replace(',','.').strip(u' \t\u20ac\xa0€\n') operation = Operation(len(self.operations)) operation.date = d diff --git a/weboob/backends/bnporc/pages/account_history.py b/weboob/backends/bnporc/pages/account_history.py index 4774fb97..f258e792 100644 --- a/weboob/backends/bnporc/pages/account_history.py +++ b/weboob/backends/bnporc/pages/account_history.py @@ -35,46 +35,34 @@ class AccountHistory(BasePage): def on_loaded(self): self.operations = [] - for tr in self.document.getiterator('tr'): - if tr.attrib.get('class', '') == 'hdoc1' or tr.attrib.get('class', '') == 'hdotc1': - tds = tr.findall('td') - if len(tds) != 4: - continue - d = date(*reversed([int(x) for x in tds[0].text.split('/')])) - label = u'' - label += tds[1].text - label = label.replace(u'\xa0', u'') - for child in tds[1].getchildren(): - if child.text: label += child.text - if child.tail: label += child.tail - if tds[1].tail: label += tds[1].tail + for tr in self.document.xpath('//table[@id="tableCompte"]//tr'): + if len(tr.xpath('td[@class="debit"]')) == 0: + continue - label = label.strip() - category = NotAvailable - for pattern, _cat, _lab in self.LABEL_PATTERNS: - m = re.match(pattern, label) - if m: - category = _cat % m.groupdict() - label = _lab % m.groupdict() - break - else: - if ' ' in label: - category, useless, label = [part.strip() for part in label.partition(' ')] + id = tr.find('td').find('input').attrib['value'] + op = Operation(id) + op.label = tr.findall('td')[2].text.replace(u'\xa0', u'').strip() + op.date = date(*reversed([int(x) for x in tr.findall('td')[1].text.split('/')])) - amount = tds[2].text.replace('.', '').replace(',', '.') + op.category = NotAvailable + for pattern, _cat, _lab in self.LABEL_PATTERNS: + m = re.match(pattern, op.label) + if m: + op.category = _cat % m.groupdict() + op.label = _lab % m.groupdict() + break + else: + if ' ' in op.label: + op.category, useless, op.label = [part.strip() for part in op.label.partition(' ')] - # if we don't have exactly one '.', this is not a floatm try the next - operation = Operation(len(self.operations)) - if amount.count('.') != 1: - amount = tds[3].text.replace('.', '').replace(',', '.') - operation.amount = float(amount) - else: - operation.amount = - float(amount) + debit = tr.xpath('.//td[@class="debit"]')[0].text.replace('.','').replace(',','.').strip(u' \t\u20ac\xa0€\n') + credit = tr.xpath('.//td[@class="credit"]')[0].text.replace('.','').replace(',','.').strip(u' \t\u20ac\xa0€\n') + if len(debit) > 0: + op.amount = - float(debit) + else: + op.amount = float(credit) - operation.date = d - operation.label = label - operation.category = category - self.operations.append(operation) + self.operations.append(op) def get_operations(self): return self.operations diff --git a/weboob/backends/bnporc/pages/accounts_list.py b/weboob/backends/bnporc/pages/accounts_list.py index 748a7cc5..34d05543 100644 --- a/weboob/backends/bnporc/pages/accounts_list.py +++ b/weboob/backends/bnporc/pages/accounts_list.py @@ -39,34 +39,12 @@ class AccountsList(BasePage): def get_list(self): l = [] for tr in self.document.getiterator('tr'): - if tr.attrib.get('class', '') == 'comptes': + if not 'class' in tr.attrib and tr.find('td') is not None and tr.find('td').attrib.get('class', '') == 'typeTitulaire': account = Account() - for td in tr.getiterator('td'): - if td.attrib.get('headers', '').startswith('Numero_'): - id = td.text - account.id = ''.join(id.split(' ')).strip() - elif td.attrib.get('headers', '').startswith('Libelle_'): - a = td.findall('a') - label = unicode(a[0].text) - account.label = label.strip() - m = self.LINKID_REGEXP.match(a[0].attrib.get('href', '')) - if m: - account.link_id = m.group(1) - elif td.attrib.get('headers', '').startswith('Solde'): - a = td.findall('a') - balance = a[0].text - balance = balance.replace('.','').replace(',','.') - account.balance = float(balance) - elif td.attrib.get('headers', '').startswith('Avenir'): - a = td.findall('a') - # Some accounts don't have a "coming" - if len(a): - coming = a[0].text - coming = coming.replace('.','').replace(',','.') - account.coming = float(coming) - else: - account.coming = NotAvailable - + account.id = tr.xpath('.//td[@class="libelleCompte"]/input')[0].attrib['id'][len('libelleCompte'):] + account.label = tr.xpath('.//td[@class="libelleCompte"]/a')[0].text.strip() + account.balance = float(tr.findall('td')[3].find('a').text.replace('.','').replace(',','.').strip(u' \t\u20ac\xa0€\n')) + account.coming = float(tr.findall('td')[4].find('a').text.replace('.','').replace(',','.').strip(u' \t\u20ac\xa0€\n')) l.append(account) if len(l) == 0: @@ -76,3 +54,6 @@ class AccountsList(BasePage): if div.text.strip() == 'Vous avez atteint la date de fin de vie de votre code secret.': raise PasswordExpired(div.text.strip()) return l + + def get_execution_id(self): + return self.document.xpath('//input[@name="execution"]')[0].attrib['value'] diff --git a/weboob/backends/bnporc/pages/login.py b/weboob/backends/bnporc/pages/login.py index 66332b56..078dce22 100644 --- a/weboob/backends/bnporc/pages/login.py +++ b/weboob/backends/bnporc/pages/login.py @@ -18,6 +18,7 @@ # along with weboob. If not, see . +import re from weboob.tools.mech import ClientForm import urllib from logging import error @@ -59,8 +60,21 @@ class LoginPage(BasePage): class ConfirmPage(BasePage): - pass + def get_error(self): + for td in self.document.xpath('//td[@class="hdvon1"]'): + if td.text: + return td.text.strip() + return None + def get_relocate_url(self): + script = self.document.xpath('//script')[0] + m = re.match('document.location.replace\("(.*)"\)', script.text[script.text.find('document.location.replace'):]) + if m: + return m.group(1) + +class MessagePage(BasePage): + def on_loaded(self): + pass class ChangePasswordPage(BasePage): def change_password(self, current, new): @@ -78,7 +92,12 @@ class ChangePasswordPage(BasePage): data = {'ch1': code_current, 'ch2': code_new, - 'ch3': code_new + 'ch3': code_new, + 'radiobutton3': 'radiobutton', + 'x': 12, + 'y': 9, } - self.browser.location('/SAF_CHM_VALID', urllib.urlencode(data)) + headers = {'Referer': self.url} + request = self.browser.request_class('https://www.secure.bnpparibas.net/SAF_CHM_VALID', urllib.urlencode(data), headers) + self.browser.location(request) diff --git a/weboob/backends/bnporc/pages/transfer.py b/weboob/backends/bnporc/pages/transfer.py index 2c443aa5..5391a108 100644 --- a/weboob/backends/bnporc/pages/transfer.py +++ b/weboob/backends/bnporc/pages/transfer.py @@ -23,6 +23,7 @@ import re from weboob.tools.browser import BasePage from weboob.tools.ordereddict import OrderedDict from weboob.capabilities.bank import TransferError +from ..errors import PasswordExpired __all__ = ['TransferPage', 'TransferConfirmPage', 'TransferCompletePage'] @@ -36,6 +37,11 @@ class Account(object): self.receive_checkbox = receive_checkbox class TransferPage(BasePage): + def on_loaded(self): + for td in self.document.xpath('//td[@class="hdvon1"]'): + if td.text and 'Vous avez atteint le seuil de' in td.text: + raise PasswordExpired(td.text.strip()) + def get_accounts(self): accounts = OrderedDict() for table in self.document.getiterator('table'):