diff --git a/modules/banquepopulaire/backend.py b/modules/banquepopulaire/backend.py index c7bdd14b..6a0e804f 100644 --- a/modules/banquepopulaire/backend.py +++ b/modules/banquepopulaire/backend.py @@ -83,3 +83,7 @@ class BanquePopulaireBackend(BaseBackend, ICapBank): def iter_history(self, account): with self.browser: return self.browser.get_history(account) + + def iter_coming(self, account): + with self.browser: + return self.browser.get_history(account, coming=True) diff --git a/modules/banquepopulaire/browser.py b/modules/banquepopulaire/browser.py index 047481ed..10209254 100644 --- a/modules/banquepopulaire/browser.py +++ b/modules/banquepopulaire/browser.py @@ -22,7 +22,8 @@ import urllib from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword, BrokenPageError -from .pages import LoginPage, IndexPage, AccountsPage, TransactionsPage, UnavailablePage, RedirectPage, HomePage +from .pages import LoginPage, IndexPage, AccountsPage, CardsPage, TransactionsPage, \ + UnavailablePage, RedirectPage, HomePage __all__ = ['BanquePopulaire'] @@ -37,6 +38,8 @@ class BanquePopulaire(BaseBrowser): 'https://[^/]+/cyber/internet/StartTask.do\?taskInfoOID=maSyntheseGratuite.*': AccountsPage, 'https://[^/]+/cyber/internet/StartTask.do\?taskInfoOID=accueilSynthese.*': AccountsPage, 'https://[^/]+/cyber/internet/ContinueTask.do\?.*dialogActionPerformed=EQUIPEMENT_COMPLET.*': AccountsPage, + 'https://[^/]+/cyber/internet/ContinueTask.do\?.*dialogActionPerformed=ENCOURS_COMPTE.*': CardsPage, + 'https://[^/]+/cyber/internet/ContinueTask.do\?.*dialogActionPerformed=SELECTION_ENCOURS_CARTE.*': TransactionsPage, 'https://[^/]+/cyber/internet/ContinueTask.do\?.*dialogActionPerformed=SOLDE.*': TransactionsPage, 'https://[^/]+/cyber/internet/Page.do\?.*': TransactionsPage, 'https://[^/]+/cyber/internet/Sort.do\?.*': TransactionsPage, @@ -78,15 +81,14 @@ class BanquePopulaire(BaseBrowser): self.token = self.page.get_token() - def get_accounts_list(self): - self.location(self.buildurl('/cyber/internet/StartTask.do', taskInfoOID='mesComptes', token=self.token)) - if self.page.is_error(): - self.location(self.buildurl('/cyber/internet/StartTask.do', taskInfoOID='mesComptesPRO', token=self.token)) - if self.page.is_error(): - self.location(self.buildurl('/cyber/internet/StartTask.do', taskInfoOID='maSyntheseGratuite', token=self.token)) - if self.page.is_error(): - self.location(self.buildurl('/cyber/internet/StartTask.do', taskInfoOID='accueilSynthese', token=self.token)) - if self.page.is_error(): + ACCOUNT_URLS = ['mesComptes', 'mesComptesPRO', 'maSyntheseGratuite', 'accueilSynthese'] + def go_on_accounts_list(self): + for taskInfoOID in self.ACCOUNT_URLS: + self.location(self.buildurl('/cyber/internet/StartTask.do', taskInfoOID=taskInfoOID, token=self.token)) + if not self.page.is_error(): + self.ACCOUNT_URLS = [taskInfoOID] + break + else: raise BrokenPageError('Unable to go on the accounts list page') if self.page.is_short_list(): @@ -95,28 +97,46 @@ class BanquePopulaire(BaseBrowser): self['dialogActionPerformed'] = 'EQUIPEMENT_COMPLET' self.submit() + def get_accounts_list(self): + self.go_on_accounts_list() self.token = self.page.get_token() - return self.page.get_list() + next_pages = [] + + for a in self.page.iter_accounts(next_pages): + yield a + + for next_page in next_pages: + if not self.is_on_page(AccountsPage): + self.go_on_accounts_list() + + self.location('/cyber/internet/ContinueTask.do', urllib.urlencode(next_page)) + + for a in self.page.iter_accounts(): + yield a def get_account(self, id): assert isinstance(id, basestring) - l = self.get_accounts_list() - for a in l: + for a in self.get_accounts_list(): if a.id == id: return a return None - def get_history(self, account): + def get_history(self, account, coming=False): if not self.is_on_page(AccountsPage): account = self.get_account(account.id) - if account._params is None: + if coming: + params = account._coming_params + else: + params = account._params + + if params is None: return - self.location('/cyber/internet/ContinueTask.do', urllib.urlencode(account._params)) + self.location('/cyber/internet/ContinueTask.do', urllib.urlencode(params)) self.token = self.page.get_token() if self.page.no_operations(): @@ -132,7 +152,7 @@ class BanquePopulaire(BaseBrowser): assert self.is_on_page(TransactionsPage) self.token = self.page.get_token() - for tr in self.page.get_history(): + for tr in self.page.get_history(account, coming): yield tr next_params = self.page.get_next_params() diff --git a/modules/banquepopulaire/pages.py b/modules/banquepopulaire/pages.py index 21b03f7a..184bd3a5 100644 --- a/modules/banquepopulaire/pages.py +++ b/modules/banquepopulaire/pages.py @@ -18,6 +18,7 @@ # along with weboob. If not, see . +import datetime from urlparse import urlsplit, parse_qsl from decimal import Decimal import re @@ -28,7 +29,7 @@ from weboob.capabilities.bank import Account from weboob.tools.capabilities.bank.transactions import FrenchTransaction -__all__ = ['LoginPage', 'IndexPage', 'AccountsPage', 'TransactionsPage', 'UnavailablePage', 'RedirectPage'] +__all__ = ['LoginPage', 'IndexPage', 'AccountsPage', 'CardsPage', 'TransactionsPage', 'UnavailablePage', 'RedirectPage'] class WikipediaARC4(object): @@ -236,7 +237,13 @@ class AccountsPage(BasePage): def is_short_list(self): return len(self.document.xpath('//script[contains(text(), "EQUIPEMENT_COMPLET")]')) > 0 - def get_list(self): + COL_NUMBER = 0 + COL_TYPE = 1 + COL_LABEL = 2 + COL_BALANCE = 3 + COL_COMING = 4 + + def iter_accounts(self, next_pages): account_type = Account.TYPE_UNKNOWN params = {} @@ -273,16 +280,81 @@ class AccountsPage(BasePage): if account.type == account.TYPE_LOAN: account.balance = - abs(account.balance) - if balance == u'': - # There is no detail - account._params = None - else: + account._prev_debit = None + account._next_debit = None + account._params = None + account._coming_params = None + if balance != u'': account._params = params.copy() account._params['dialogActionPerformed'] = 'SOLDE' account._params['attribute($SEL_$%s)' % tr.attrib['id'].split('_')[0]] = tr.attrib['id'].split('_', 1)[1] + + if len(tds) >= 5: + _params = account._params.copy() + _params['dialogActionPerformed'] = 'ENCOURS_COMPTE' + next_pages.append(_params) yield account - return + +class CardsPage(BasePage): + COL_ID = 0 + COL_TYPE = 1 + COL_LABEL = 2 + COL_DATE = 3 + COL_AMOUNT = 4 + + def iter_accounts(self): + params = {} + for field in self.document.xpath('//input'): + params[field.attrib['name']] = field.attrib.get('value', '') + + account = None + for tr in self.document.xpath('//table[@id="TabCtes"]/tbody/tr'): + cols = tr.xpath('./td') + + id = self.parser.tocleanstring(cols[self.COL_ID]) + if len(id) > 0: + if account is not None: + yield account + account = Account() + account.id = id + account.balance = account.coming = Decimal('0') + account._next_debit = datetime.date.today() + account._prev_debit = datetime.date(2000,1,1) + account.label = u' '.join([self.parser.tocleanstring(cols[self.COL_TYPE]), + self.parser.tocleanstring(cols[self.COL_LABEL])]) + account._coming_params = params.copy() + account._coming_params['dialogActionPerformed'] = 'SELECTION_ENCOURS_CARTE' + account._coming_params['attribute($SEL_$%s)' % tr.attrib['id'].split('_')[0]] = tr.attrib['id'].split('_', 1)[1] + elif account is None: + raise BrokenPageError('Unable to find accounts on cards page') + else: + account._params = params.copy() + account._params['dialogActionPerformed'] = 'SELECTION_ENCOURS_CARTE' + account._params['attribute($SEL_$%s)' % tr.attrib['id'].split('_')[0]] = tr.attrib['id'].split('_', 1)[1] + + date_col = self.parser.tocleanstring(cols[self.COL_DATE]) + m = re.search('(\d+)/(\d+)/(\d+)', date_col) + if not m: + self.logger.warning('Unable to parse date %r' % date_col) + continue + + date = datetime.date(*reversed(map(int, m.groups()))) + if date.year < 100: + date = date.replace(year=date.year+2000) + + amount = Decimal(FrenchTransaction.clean_amount(self.parser.tocleanstring(cols[self.COL_AMOUNT]))) + + if not date_col.endswith('(1)'): + # debited + account.coming += - abs(amount) + account._next_debit = date + elif date > account._prev_debit: + account._prev_balance = - abs(amount) + account._prev_debit = date + + if account is not None: + yield account class Transaction(FrenchTransaction): @@ -314,7 +386,7 @@ class Transaction(FrenchTransaction): class TransactionsPage(BasePage): def get_next_params(self): - nxt = self.document.xpath('//li[@id="tbl1_nxt"]') + nxt = self.document.xpath('//li[contains(@id, "_nxt")]') if len(nxt) == 0 or nxt[0].attrib.get('class', '') == 'nxt-dis': return None @@ -324,10 +396,18 @@ class TransactionsPage(BasePage): params['validationStrategy'] = 'NV' params['pagingDirection'] = 'NEXT' - params['pagerName'] = 'tbl1' + params['pagerName'] = nxt[0].attrib['id'].split('_', 1)[0] return params + def get_history(self, account, coming): + if len(self.document.xpath('//table[@id="tbl1"]')) > 0: + return self.get_account_history() + if len(self.document.xpath('//table[@id="TabFact"]')) > 0: + return self.get_card_history(account, coming) + + raise BrokenPageError('Unable to find what kind of history it is.') + COL_COMPTA_DATE = 0 COL_LABEL = 1 COL_REF = 2 # optional @@ -336,7 +416,7 @@ class TransactionsPage(BasePage): COL_DEBIT = -2 COL_CREDIT = -1 - def get_history(self): + def get_account_history(self): for tr in self.document.xpath('//table[@id="tbl1"]/tbody/tr'): tds = tr.findall('td') @@ -359,8 +439,40 @@ class TransactionsPage(BasePage): t.set_amount(credit, debit) yield t + COL_CARD_DATE = 0 + COL_CARD_LABEL = 1 + COL_CARD_AMOUNT = 2 + + def get_card_history(self, account, coming): + if coming: + debit_date = account._next_debit + else: + debit_date = account._prev_debit + if 'ContinueTask.do' in self.url: + t = Transaction(0) + t.parse(debit_date, 'RELEVE CARTE') + t.amount = -account._prev_balance + yield t + + for i, tr in enumerate(self.document.xpath('//table[@id="TabFact"]/tbody/tr')): + tds = tr.findall('td') + + if len(tds) < 3: + continue + + t = Transaction(i) + + date = self.parser.tocleanstring(tds[self.COL_CARD_DATE]) + label = self.parser.tocleanstring(tds[self.COL_CARD_LABEL]) + amount = '-' + self.parser.tocleanstring(tds[self.COL_CARD_AMOUNT]) + + t.parse(debit_date, re.sub(r'[ ]+', ' ', label)) + t.set_amount(amount) + t.rdate = t.parse_date(date) + yield t + def no_operations(self): - if len(self.document.xpath('//table[@id="tbl1"]//td[@colspan]')) > 0: + if len(self.document.xpath('//table[@id="tbl1" or @id="TabFact"]//td[@colspan]')) > 0: return True if len(self.document.xpath(u'//div[contains(text(), "Accès à LineBourse")]')) > 0: return True