diff --git a/modules/cic/backend.py b/modules/cic/backend.py index d6b0393b..cc565df1 100644 --- a/modules/cic/backend.py +++ b/modules/cic/backend.py @@ -56,9 +56,17 @@ class CICBackend(BaseBackend, ICapBank): else: raise AccountNotFound() + def iter_coming(self, account): + with self.browser: + for tr in self.browser.get_history(account): + if tr._is_coming: + yield tr + def iter_history(self, account): - for history in self.browser.get_history(account): - yield history + with self.browser: + for tr in self.browser.get_history(account): + if not tr._is_coming: + yield tr def iter_transfer_recipients(self, ignored): for account in self.browser.get_accounts_list().itervalues(): diff --git a/modules/cic/browser.py b/modules/cic/browser.py index 2e6c0074..c909b155 100644 --- a/modules/cic/browser.py +++ b/modules/cic/browser.py @@ -18,14 +18,15 @@ # along with weboob. If not, see . -from urlparse import urlparse +from urlparse import urlsplit, parse_qsl, urlparse +from datetime import datetime, timedelta from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword from weboob.capabilities.bank import Transfer, TransferError -from datetime import datetime -from .pages import LoginPage, LoginErrorPage, AccountsPage, UserSpacePage, \ - OperationsPage, NoOperationsPage, InfoPage, TransfertPage +from .pages import LoginPage, LoginErrorPage, AccountsPage, UserSpacePage, EmptyPage, \ + OperationsPage, CardPage, NoOperationsPage, InfoPage, TransfertPage + __all__ = ['CICBrowser'] @@ -42,10 +43,11 @@ class CICBrowser(BaseBrowser): 'https://www.cic.fr/.*/fr/banque/espace_personnel.aspx': UserSpacePage, 'https://www.cic.fr/.*/fr/banque/mouvements.cgi.*': OperationsPage, 'https://www.cic.fr/.*/fr/banque/nr/nr_devbooster.aspx.*': OperationsPage, - 'https://www.cic.fr/.*/fr/banque/operations_carte\.cgi.*': OperationsPage, + 'https://www.cic.fr/.*/fr/banque/operations_carte\.cgi.*': CardPage, 'https://www.cic.fr/.*/fr/banque/CR/arrivee\.asp.*': NoOperationsPage, 'https://www.cic.fr/.*/fr/banque/BAD.*': InfoPage, - 'https://www.cic.fr/.*/fr/banque/.*Vir.*': TransfertPage + 'https://www.cic.fr/.*/fr/banque/.*Vir.*': TransfertPage, + 'https://www.cic.fr/.*/fr/': EmptyPage, } currentSubBank = None @@ -90,11 +92,9 @@ class CICBrowser(BaseBrowser): url = urlparse(self.geturl()) self.currentSubBank = url.path.lstrip('/').split('/')[0] - def get_history(self, account): - page_url = account._link_id - #operations_count = 0 + def list_operations(self, page_url): l_ret = [] - while (page_url): + while page_url: if page_url.startswith('/'): self.location(page_url) else: @@ -109,6 +109,32 @@ class CICBrowser(BaseBrowser): return l_ret + def get_history(self, account): + transactions = [] + last_debit = None + for tr in self.list_operations(account._link_id): + if tr.raw == 'RELEVE CARTE' and last_debit is None: + last_debit = (tr.date - timedelta(days=10)).month + else: + transactions.append(tr) + + month = 0 + for card_link in account._card_links: + v = urlsplit(card_link) + args = dict(parse_qsl(v.query)) + # useful with 12 -> 1 + if int(args['mois']) < month: + month = month + 1 + month = int(args['mois']) + + for tr in self.list_operations(card_link): + if month > last_debit: + tr._is_coming = True + transactions.append(tr) + + transactions.sort(key=lambda tr: tr.rdate, reverse=True) + return transactions + def transfer(self, account, to, amount, reason=None): # access the transfer page transfert_url = 'WI_VPLV_VirUniSaiCpt.asp?RAZ=ALL&Cat=6&PERM=N&CHX=A' diff --git a/modules/cic/pages.py b/modules/cic/pages.py index 9e441ea2..fff5a84c 100644 --- a/modules/cic/pages.py +++ b/modules/cic/pages.py @@ -23,6 +23,7 @@ from decimal import Decimal import re from weboob.tools.browser import BasePage +from weboob.tools.ordereddict import OrderedDict from weboob.capabilities.bank import Account from weboob.tools.capabilities.bank.transactions import FrenchTransaction @@ -39,6 +40,9 @@ class LoginErrorPage(BasePage): class InfoPage(BasePage): pass +class EmptyPage(BasePage): + pass + class TransfertPage(BasePage): pass @@ -47,45 +51,49 @@ class UserSpacePage(BasePage): class AccountsPage(BasePage): def get_list(self): - ids = set() + accounts = OrderedDict() for tr in self.document.getiterator('tr'): first_td = tr.getchildren()[0] if (first_td.attrib.get('class', '') == 'i g' or first_td.attrib.get('class', '') == 'p g') \ and first_td.find('a') is not None: - account = Account() - account.label = u"%s"%first_td.find('a').text.strip().lstrip(' 0123456789').title() - account._link_id = first_td.find('a').get('href', '') - if account._link_id.startswith('POR_SyntheseLst'): + + a = first_td.find('a') + link = a.get('href', '') + if link.startswith('POR_SyntheseLst'): continue - url = urlparse(account._link_id) + url = urlparse(link) p = parse_qs(url.query) if not 'rib' in p: continue - account.id = p['rib'][0] + for i in (2,1): + balance = FrenchTransaction.clean_amount(tr.getchildren()[i].text.strip(' EUR')) + if len(balance) > 0: + break + balance = Decimal(balance) - if account.id in ids: + id = p['rib'][0] + if id in accounts: + account = accounts[id] + if not account.coming: + account.coming = Decimal('0.0') + account.coming += balance + account._card_links.append(link) continue - ids.add(account.id) + account = Account() + account.id = id + account.label = unicode(a.text).strip().lstrip(' 0123456789').title() + account._link_id = link + account._card_links = [] - s = tr.getchildren()[2].text - if s.strip() == "": - s = tr.getchildren()[1].text - balance = u'' - for c in s: - if c.isdigit() or c == '-': - balance += c - if c == ',': - balance += '.' - account.balance = Decimal(balance) - yield account + account.balance = balance - def next_page_url(self): - """ TODO pouvoir passer à la page des comptes suivante """ - return 0 + accounts[account.id] = account + + return accounts.itervalues() class Transaction(FrenchTransaction): PATTERNS = [(re.compile('^VIR(EMENT)? (?P.*)'), FrenchTransaction.TYPE_TRANSFER), @@ -99,6 +107,7 @@ class Transaction(FrenchTransaction): (re.compile('^REMISE (?P.*)'), FrenchTransaction.TYPE_DEPOSIT), ] + _is_coming = False class OperationsPage(BasePage): def get_history(self): @@ -120,14 +129,7 @@ class OperationsPage(BasePage): operation = Transaction(index) index += 1 - # Find different parts of label - parts = [] - if len(tds[-3].findall('a')) > 0: - parts = [a.text.strip() for a in tds[-3].findall('a')] - else: - parts.append(tds[-3].text.strip()) - if tds[-3].find('br') is not None: - parts.append(tds[-3].find('br').tail.strip()) + parts = [txt.strip() for txt in tds[-3].itertext() if len(txt.strip()) > 0] # To simplify categorization of CB, reverse order of parts to separate # location and institution. @@ -139,23 +141,36 @@ class OperationsPage(BasePage): if tds[-1].text is not None and len(tds[-1].text) > 2: s = tds[-1].text.strip() - elif tds[-1].text is not None and len(tds[-2].text) > 2: + elif tds[-2].text is not None and len(tds[-2].text) > 2: s = tds[-2].text.strip() else: s = "0" - balance = u'' - for c in s: - if c.isdigit() or c == "-": - balance += c - if c == ',': - balance += '.' - operation.amount = Decimal(balance) + operation.set_amount(s.rstrip('EUR')) yield operation def next_page_url(self): """ TODO pouvoir passer à la page des opérations suivantes """ return 0 +class CardPage(OperationsPage): + def get_history(self): + index = 0 + for tr in self.document.xpath('//table[@class="liste"]/tbody/tr'): + tds = tr.findall('td') + if len(tds) < 4: + continue + + tr = Transaction(index) + + parts = [txt.strip() for txt in list(tds[-3].itertext()) + list(tds[-2].itertext()) if len(txt.strip()) > 0] + + tr.parse(date=tds[0].text.strip(' \xa0'), + raw=u' '.join(parts)) + tr.type = tr.TYPE_CARD + + tr.set_amount(tds[-1].text.rstrip('EUR')) + yield tr + class NoOperationsPage(OperationsPage): def get_history(self): return iter([]) diff --git a/modules/creditmutuel/backend.py b/modules/creditmutuel/backend.py index 317056e4..1bacb982 100644 --- a/modules/creditmutuel/backend.py +++ b/modules/creditmutuel/backend.py @@ -57,12 +57,16 @@ class CreditMutuelBackend(BaseBackend, ICapBank): raise AccountNotFound() def iter_coming(self, account): - """ TODO Not supported yet """ - return iter([]) + with self.browser: + for tr in self.browser.get_history(account): + if tr._is_coming: + yield tr def iter_history(self, account): - for history in self.browser.get_history(account): - yield history + with self.browser: + for tr in self.browser.get_history(account): + if not tr._is_coming: + yield tr def iter_transfer_recipients(self, ignored): for account in self.browser.get_accounts_list().itervalues(): diff --git a/modules/creditmutuel/browser.py b/modules/creditmutuel/browser.py index 730418bc..eb677f84 100644 --- a/modules/creditmutuel/browser.py +++ b/modules/creditmutuel/browser.py @@ -18,12 +18,15 @@ # along with weboob. If not, see . +from urlparse import urlsplit, parse_qsl, urlparse +from datetime import datetime, timedelta + from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword from weboob.capabilities.bank import Transfer, TransferError -from datetime import datetime -from .pages import LoginPage, LoginErrorPage, AccountsPage, UserSpacePage, \ - OperationsPage, NoOperationsPage, InfoPage, TransfertPage +from .pages import LoginPage, LoginErrorPage, AccountsPage, UserSpacePage, EmptyPage, \ + OperationsPage, CardPage, NoOperationsPage, InfoPage, TransfertPage + __all__ = ['CreditMutuelBrowser'] @@ -36,20 +39,18 @@ class CreditMutuelBrowser(BaseBrowser): USER_AGENT = BaseBrowser.USER_AGENTS['wget'] PAGES = {'https://www.creditmutuel.fr/groupe/fr/index.html': LoginPage, 'https://www.creditmutuel.fr/.*/fr/identification/default.cgi': LoginErrorPage, - 'https://www.creditmutuel.fr/.*/fr/banque/situation_financiere.cgi': AccountsPage, - 'https://www.creditmutuel.fr/.*/fr/banque/espace_personnel.aspx': UserSpacePage, - 'https://www.creditmutuel.fr/.*/fr/banque/mouvements.cgi.*': OperationsPage, - 'https://www.creditmutuel.fr/.*/fr/banque/nr/nr_devbooster.aspx.*': OperationsPage, - 'https://www.creditmutuel.fr/.*/fr/banque/operations_carte\.cgi.*': OperationsPage, - 'https://www.creditmutuel.fr/.*/fr/banque/CR/arrivee\.asp.*': NoOperationsPage, - 'https://www.creditmutuel.fr/.*/fr/banque/BAD.*': InfoPage, - 'https://www.creditmutuel.fr/.*/fr/banque/.*Vir.*': TransfertPage + 'https://www.creditmutuel.fr/.*/fr/banque/situation_financiere.cgi': AccountsPage, + 'https://www.creditmutuel.fr/.*/fr/banque/espace_personnel.aspx': UserSpacePage, + 'https://www.creditmutuel.fr/.*/fr/banque/mouvements.cgi.*': OperationsPage, + 'https://www.creditmutuel.fr/.*/fr/banque/nr/nr_devbooster.aspx.*': OperationsPage, + 'https://www.creditmutuel.fr/.*/fr/banque/operations_carte\.cgi.*': CardPage, + 'https://www.creditmutuel.fr/.*/fr/banque/CR/arrivee\.asp.*': NoOperationsPage, + 'https://www.creditmutuel.fr/.*/fr/banque/BAD.*': InfoPage, + 'https://www.creditmutuel.fr/.*/fr/banque/.*Vir.*': TransfertPage, + 'https://www.creditmutuel.fr/.*/fr/': EmptyPage, } - def __init__(self, *args, **kwargs): - BaseBrowser.__init__(self, *args, **kwargs) - #self.SUB_BANKS = ['cmdv','cmcee','cmse', 'cmidf', 'cmsmb', 'cmma', 'cmmabn', 'cmc', 'cmlaco', 'cmnormandie', 'cmm'] - #self.currentSubBank = None + currentSubBank = None def is_logged(self): return self.page and not self.is_on_page(LoginPage) and not self.is_on_page(LoginErrorPage) @@ -69,7 +70,6 @@ class CreditMutuelBrowser(BaseBrowser): if not self.is_logged() or self.is_on_page(LoginErrorPage): raise BrowserIncorrectPassword() - self.SUB_BANKS = ['cmdv', 'cmcee', 'cmse', 'cmidf', 'cmsmb', 'cmma', 'cmmabn', 'cmc', 'cmlaco', 'cmnormandie', 'cmm'] self.getCurrentSubBank() def get_accounts_list(self): @@ -89,17 +89,12 @@ class CreditMutuelBrowser(BaseBrowser): def getCurrentSubBank(self): # the account list and history urls depend on the sub bank of the user - current_url = self.geturl() - current_url_parts = current_url.split('/') - for subbank in self.SUB_BANKS: - if subbank in current_url_parts: - self.currentSubBank = subbank + url = urlparse(self.geturl()) + self.currentSubBank = url.path.lstrip('/').split('/')[0] - def get_history(self, account): - page_url = account._link_id - #operations_count = 0 + def list_operations(self, page_url): l_ret = [] - while (page_url): + while page_url: if page_url.startswith('/'): self.location(page_url) else: @@ -114,6 +109,32 @@ class CreditMutuelBrowser(BaseBrowser): return l_ret + def get_history(self, account): + transactions = [] + last_debit = None + for tr in self.list_operations(account._link_id): + if tr.raw == 'RELEVE CARTE' and last_debit is None: + last_debit = (tr.date - timedelta(days=10)).month + else: + transactions.append(tr) + + month = 0 + for card_link in account._card_links: + v = urlsplit(card_link) + args = dict(parse_qsl(v.query)) + # useful with 12 -> 1 + if int(args['mois']) < month: + month = month + 1 + month = int(args['mois']) + + for tr in self.list_operations(card_link): + if month > last_debit: + tr._is_coming = True + transactions.append(tr) + + transactions.sort(key=lambda tr: tr.rdate, reverse=True) + return transactions + def transfer(self, account, to, amount, reason=None): # access the transfer page transfert_url = 'WI_VPLV_VirUniSaiCpt.asp?RAZ=ALL&Cat=6&PERM=N&CHX=A' @@ -163,8 +184,3 @@ class CreditMutuelBrowser(BaseBrowser): transfer.recipient = to transfer.date = submit_date return transfer - - #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() diff --git a/modules/creditmutuel/pages.py b/modules/creditmutuel/pages.py index 253816d3..16a342a0 100644 --- a/modules/creditmutuel/pages.py +++ b/modules/creditmutuel/pages.py @@ -23,6 +23,7 @@ from decimal import Decimal import re from weboob.tools.browser import BasePage +from weboob.tools.ordereddict import OrderedDict from weboob.capabilities.bank import Account from weboob.tools.capabilities.bank.transactions import FrenchTransaction @@ -39,6 +40,9 @@ class LoginErrorPage(BasePage): class InfoPage(BasePage): pass +class EmptyPage(BasePage): + pass + class TransfertPage(BasePage): pass @@ -47,45 +51,49 @@ class UserSpacePage(BasePage): class AccountsPage(BasePage): def get_list(self): - ids = set() + accounts = OrderedDict() for tr in self.document.getiterator('tr'): first_td = tr.getchildren()[0] if (first_td.attrib.get('class', '') == 'i g' or first_td.attrib.get('class', '') == 'p g') \ and first_td.find('a') is not None: - account = Account() - account.label = u"%s"%first_td.find('a').text.strip().lstrip(' 0123456789').title() - account._link_id = first_td.find('a').get('href', '') - if account._link_id.startswith('POR_SyntheseLst'): + + a = first_td.find('a') + link = a.get('href', '') + if link.startswith('POR_SyntheseLst'): continue - url = urlparse(account._link_id) + url = urlparse(link) p = parse_qs(url.query) if not 'rib' in p: continue - account.id = p['rib'][0] + for i in (2,1): + balance = FrenchTransaction.clean_amount(tr.getchildren()[i].text.strip(' EUR')) + if len(balance) > 0: + break + balance = Decimal(balance) - if account.id in ids: + id = p['rib'][0] + if id in accounts: + account = accounts[id] + if not account.coming: + account.coming = Decimal('0.0') + account.coming += balance + account._card_links.append(link) continue - ids.add(account.id) + account = Account() + account.id = id + account.label = unicode(a.text).strip().lstrip(' 0123456789').title() + account._link_id = link + account._card_links = [] - s = tr.getchildren()[2].text - if s.strip() == "": - s = tr.getchildren()[1].text - balance = u'' - for c in s: - if c.isdigit() or c == '-': - balance += c - if c == ',': - balance += '.' - account.balance = Decimal(balance) - yield account + account.balance = balance - def next_page_url(self): - """ TODO pouvoir passer à la page des comptes suivante """ - return 0 + accounts[account.id] = account + + return accounts.itervalues() class Transaction(FrenchTransaction): PATTERNS = [(re.compile('^VIR(EMENT)? (?P.*)'), FrenchTransaction.TYPE_TRANSFER), @@ -99,6 +107,7 @@ class Transaction(FrenchTransaction): (re.compile('^REMISE (?P.*)'), FrenchTransaction.TYPE_DEPOSIT), ] + _is_coming = False class OperationsPage(BasePage): def get_history(self): @@ -120,14 +129,7 @@ class OperationsPage(BasePage): operation = Transaction(index) index += 1 - # Find different parts of label - parts = [] - if len(tds[-3].findall('a')) > 0: - parts = [a.text.strip() for a in tds[-3].findall('a')] - else: - parts.append(tds[-3].text.strip()) - if tds[-3].find('br') is not None: - parts.append(tds[-3].find('br').tail.strip()) + parts = [txt.strip() for txt in tds[-3].itertext() if len(txt.strip()) > 0] # To simplify categorization of CB, reverse order of parts to separate # location and institution. @@ -139,23 +141,36 @@ class OperationsPage(BasePage): if tds[-1].text is not None and len(tds[-1].text) > 2: s = tds[-1].text.strip() - elif tds[-1].text is not None and len(tds[-2].text) > 2: + elif tds[-2].text is not None and len(tds[-2].text) > 2: s = tds[-2].text.strip() else: s = "0" - balance = u'' - for c in s: - if c.isdigit() or c == "-": - balance += c - if c == ',': - balance += '.' - operation.amount = Decimal(balance) + operation.set_amount(s.rstrip('EUR')) yield operation def next_page_url(self): """ TODO pouvoir passer à la page des opérations suivantes """ return 0 +class CardPage(OperationsPage): + def get_history(self): + index = 0 + for tr in self.document.xpath('//table[@class="liste"]/tbody/tr'): + tds = tr.findall('td') + if len(tds) < 4: + continue + + tr = Transaction(index) + + parts = [txt.strip() for txt in list(tds[-3].itertext()) + list(tds[-2].itertext()) if len(txt.strip()) > 0] + + tr.parse(date=tds[0].text.strip(' \xa0'), + raw=u' '.join(parts)) + tr.type = tr.TYPE_CARD + + tr.set_amount(tds[-1].text.rstrip('EUR')) + yield tr + class NoOperationsPage(OperationsPage): def get_history(self): return iter([])