diff --git a/modules/lcl/backend.py b/modules/lcl/backend.py index 9ade87ea..eb9727ba 100644 --- a/modules/lcl/backend.py +++ b/modules/lcl/backend.py @@ -60,10 +60,13 @@ class LCLBackend(BaseBackend, ICapBank): raise AccountNotFound() def iter_coming(self, account): - """ TODO Not supported yet """ - return iter([]) + with self.browser: + transactions = list(self.browser.get_cb_operations(account)) + transactions.sort(key=lambda tr: tr.rdate, reverse=True) + return transactions def iter_history(self, account): with self.browser: - for history in self.browser.get_history(account): - yield history + transactions = list(self.browser.get_history(account)) + transactions.sort(key=lambda tr: tr.rdate, reverse=True) + return transactions diff --git a/modules/lcl/browser.py b/modules/lcl/browser.py index 011f87df..b30ffcbb 100644 --- a/modules/lcl/browser.py +++ b/modules/lcl/browser.py @@ -18,9 +18,12 @@ # along with weboob. If not, see . +from urlparse import urlsplit, parse_qsl + from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword -from .pages import SkipPage, LoginPage, AccountsPage, AccountHistoryPage +from .pages import SkipPage, LoginPage, AccountsPage, AccountHistoryPage, \ + CBListPage, CBHistoryPage __all__ = ['LCLBrowser'] @@ -34,8 +37,11 @@ class LCLBrowser(BaseBrowser): USER_AGENT = BaseBrowser.USER_AGENTS['wget'] PAGES = { 'https://particuliers.secure.lcl.fr/outil/UAUT/Authentication/authenticate': LoginPage, + 'https://particuliers.secure.lcl.fr/outil/UAUT\?from=.*': LoginPage, 'https://particuliers.secure.lcl.fr/outil/UWSP/Synthese': AccountsPage, 'https://particuliers.secure.lcl.fr/outil/UWLM/ListeMouvements.*/accesListeMouvements.*': AccountHistoryPage, + 'https://particuliers.secure.lcl.fr/outil/UWCB/UWCBEncours.*/listeCBCompte.*': CBListPage, + 'https://particuliers.secure.lcl.fr/outil/UWCB/UWCBEncours.*/listeOperations.*': CBHistoryPage, 'https://particuliers.secure.lcl.fr/outil/UAUT/Contrat/selectionnerContrat.*': SkipPage, 'https://particuliers.secure.lcl.fr/index.html': SkipPage } @@ -69,7 +75,7 @@ class LCLBrowser(BaseBrowser): def get_accounts_list(self): if not self.is_on_page(AccountsPage): - self.login() + self.location('https://particuliers.secure.lcl.fr/outil/UWSP/Synthese') return self.page.get_list() def get_account(self, id): @@ -82,11 +88,32 @@ class LCLBrowser(BaseBrowser): return None - def get_history(self,account): - self.location('%s://%s%s' % (self.PROTOCOL, self.DOMAIN, account._link_id)) - return self.page.get_operations(account) + def get_history(self, account): + self.location(account._link_id) + for tr in self.page.get_operations(): + yield tr - #def get_coming_operations(self, account): - # if not self.is_on_page(AccountComing) or self.page.account.id != account.id: - # self.location('/NS_AVEEC?ch4=%s' % account._link_id) - # return self.page.get_operations() + for tr in self.get_cb_operations(account, 1): + yield tr + + def get_cb_operations(self, account, month=0): + """ + Get CB operations. + + * month=0 : current operations (non debited) + * month=1 : previous month operations (debited) + """ + for link in account._coming_links: + v = urlsplit(self.absurl(link)) + args = dict(parse_qsl(v.query)) + args['MOIS'] = month + + self.location(self.buildurl(v.path, **args)) + + for tr in self.page.get_operations(): + yield tr + + for card_link in self.page.get_cards(): + self.location(card_link) + for tr in self.page.get_operations(): + yield tr diff --git a/modules/lcl/pages.py b/modules/lcl/pages.py index d6d4e2b5..b2d86d46 100644 --- a/modules/lcl/pages.py +++ b/modules/lcl/pages.py @@ -135,9 +135,12 @@ class AccountsPage(BasePage): l = [] for a in self.document.getiterator('a'): link=a.attrib.get('href') - if link is not None and link.startswith("/outil/UWLM/ListeMouvements"): + if link is None: + continue + if link.startswith("/outil/UWLM/ListeMouvements"): account = Account() account._link_id=link+"&mode=45" + account._coming_links = [] parameters=link.split("?").pop().split("&") for parameter in parameters: list=parameter.split("=") @@ -161,6 +164,21 @@ class AccountsPage(BasePage): account.balance=Decimal(balance) self.logger.debug('%s Type: %s' % (account.label, account._type)) l.append(account) + if link.startswith('/outil/UWCB/UWCBEncours'): + if len(l) == 0: + self.logger.warning('There is a card account but not any check account') + continue + + account = l[-1] + + coming = a.text.replace(u"\u00A0",'').replace(' ','').replace('.','').replace('+','').replace(',','.').strip() + if '-' in coming: + coming = '-'+coming.replace('-', '') + if not account.coming: + account.coming = Decimal('0') + account.coming += Decimal(coming) + account._coming_links.append(link) + return l @@ -185,61 +203,107 @@ class Transaction(FrenchTransaction): ] class AccountHistoryPage(BasePage): - def get_operations(self,account): - operations = [] + def get_table(self): tables=self.document.findall("//table[@class='tagTab pyjama']") - table=None - for i in range(len(tables)): + for table in tables: # Look for the relevant table in the Pro version - header=tables[i].getprevious() + header=table.getprevious() while str(header.tag)=="": header=header.getprevious() header=header.find("div") if header is not None: header=header.find("span") + if header is not None and \ header.text.strip().startswith("Opérations effectuées".decode('utf-8')): - table=tables[i] - break; + return table + # Look for the relevant table in the Particulier version - header=tables[i].find("thead").find("tr").find("th[@class='titleTab titleTableft']") + header=table.find("thead").find("tr").find("th[@class='titleTab titleTableft']") if header is not None and\ header.text.strip().startswith("Solde au"): - table=tables[i] - break; + return table + + def strip_label(self, s): + return s + + def get_operations(self): + table = self.get_table() + operations = [] + + if table is None: + return operations for tr in table.iter('tr'): # skip headers and empty rows if len(tr.findall("th"))!=0 or\ len(tr.findall("td"))<=1: continue - mntColumn=0 + mntColumn = 0 date = None raw = None credit = '' debit = '' for td in tr.iter('td'): - value=td.attrib.get('id') + value = td.attrib.get('id') if value is None: - value=td.attrib.get('class'); - if value.startswith("date"): + # if tag has no id nor class, assume it's a label + value = td.attrib.get('class', 'opLib') + + if value.startswith("date") or value.endswith('center'): # some transaction are included in a tag - date=u''.join([txt.strip() for txt in td.itertext()]) + date = u''.join([txt.strip() for txt in td.itertext()]) elif value.startswith("lib") or value.startswith("opLib"): # misclosed A tag requires to grab text from td - raw=u''.join([txt.strip() for txt in td.itertext()]) - elif value.startswith("solde") or value.startswith("mnt"): - mntColumn+=1 - amount=u''.join([txt.strip() for txt in td.itertext()]) + raw = self.strip_label(u''.join([txt.strip() for txt in td.itertext()])) + elif value.startswith("solde") or value.startswith("mnt") or \ + value.startswith('debit') or value.startswith('credit'): + mntColumn += 1 + amount = u''.join([txt.strip() for txt in td.itertext()]) if amount != "": - if value.startswith("soldeDeb") or mntColumn==1: + if value.startswith("soldeDeb") or value.startswith('debit') or mntColumn==1: debit = amount else: credit = amount - operation=Transaction(len(operations)) + if date is None: + # skip non-transaction + continue + + operation = Transaction(len(operations)) operation.parse(date, raw) operation.set_amount(credit, debit) + + if operation.category == 'RELEVE CB': + # strip that transaction which is detailled in CBListPage. + continue + operations.append(operation) return operations + +class CBHistoryPage(AccountHistoryPage): + def get_table(self): + # there is only one table on the page + try: + return self.document.findall("//table[@class='tagTab pyjama']")[0] + except IndexError: + return None + + def strip_label(self, label): + # prevent to be considered as a category if there are two spaces. + return re.sub(r'[ ]+', ' ', label).strip() + + def get_operations(self): + for tr in AccountHistoryPage.get_operations(self): + tr.type = tr.TYPE_CARD + yield tr + +class CBListPage(CBHistoryPage): + def get_cards(self): + cards = [] + for a in self.document.getiterator('a'): + link = a.attrib.get('href', '') + if link.startswith('/outil/UWCB/UWCBEncours') and 'listeOperations' in link: + cards.append(link) + return cards