From 5c561875b72e18a62b030769c261c9c8c38f8539 Mon Sep 17 00:00:00 2001 From: Etienne Lachere Date: Tue, 1 Oct 2019 11:26:08 -0400 Subject: [PATCH] [creditdunord] add account ownership --- modules/creditdunord/browser.py | 9 +- modules/creditdunord/pages.py | 239 +++++++++++++++++--------------- 2 files changed, 135 insertions(+), 113 deletions(-) diff --git a/modules/creditdunord/browser.py b/modules/creditdunord/browser.py index fcbc692701..e88e63efe3 100644 --- a/modules/creditdunord/browser.py +++ b/modules/creditdunord/browser.py @@ -90,6 +90,7 @@ def do_login(self): raise BrowserIncorrectPassword() def _iter_accounts(self): + owner_name = self.get_profile().name.upper() self.loans.go(account_type=self.account_type, loans_page_label=self.loans_page_label) for a in self.page.get_list(): yield a @@ -104,8 +105,12 @@ def _iter_accounts(self): self.page.fill_diff_currency(a) yield a self.accounts.go(account_type=self.account_type, accounts_page_label=self.accounts_page_label) - for a in self.page.get_list(): - yield a + if self.accounts.is_here(): + for a in self.page.get_list(name=owner_name): + yield a + else: + for a in self.page.get_list(): + yield a @need_login def get_pages_labels(self): diff --git a/modules/creditdunord/pages.py b/modules/creditdunord/pages.py index db660a61b6..a8ae602025 100755 --- a/modules/creditdunord/pages.py +++ b/modules/creditdunord/pages.py @@ -21,6 +21,7 @@ import ast +from collections import OrderedDict from decimal import Decimal from io import BytesIO from datetime import date as da @@ -33,7 +34,7 @@ from weboob.browser.filters.json import Dict from weboob.browser.filters.html import Attr, TableCell from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable, BrowserPasswordExpired -from weboob.capabilities.bank import Account, Investment +from weboob.capabilities.bank import Account, Investment, AccountOwnership from weboob.capabilities.profile import Profile from weboob.capabilities.base import Currency, find_object from weboob.capabilities import NotAvailable @@ -230,68 +231,130 @@ def iban_go(self): return '%s%s' % ('/vos-comptes/IPT/cdnProxyResource', self.get_from_js('C_PROXY.StaticResourceClientTranslation( "', '"')) -class AccountsPage(LoggedPage, CDNBasePage): +class ProIbanPage(CDNBasePage): + pass + + +class AVPage(LoggedPage, CDNBasePage): + COL_LABEL = 0 + COL_BALANCE = 3 + + ARGS = ['IndiceClassement', 'IndiceCompte', 'Banque', 'Agence', 'Classement', 'Serie', 'SScompte', 'Categorie', 'IndiceSupport', 'NumPolice', 'LinkHypertext'] + + def get_params(self, text): + url = self.get_from_js('document.detail.action="', '";') + args = {} + l = [] + for sub in re.findall("'([^']*)'", text): + l.append(sub) + for i, key in enumerate(self.ARGS): + args[key] = l[self.ARGS.index(key)] + return url, args + + def get_av_accounts(self): + for table in self.doc.xpath('//table[@class="datas"]'): + head_cols = table.xpath('./tr[@class="entete"]/td') + for tr in table.xpath('./tr[not(@class)]'): + cols = tr.findall('td') + if len(cols) != 4: + continue + + a = Account() + + # get acc_nb like on accounts page + a._acc_nb = Regexp( + CleanText('//div[@id="v1-cadre"]//b[contains(text(), "Compte N")]', replace=[(' ', '')]), + r'(\d+)' + )(self.doc)[5:] + + a.label = CleanText('.')(cols[self.COL_LABEL]) + a.type = Account.TYPE_LIFE_INSURANCE + a.balance = MyDecimal('.')(cols[self.COL_BALANCE]) + a.currency = a.get_currency(CleanText('.')(head_cols[self.COL_BALANCE])) + a._link, a._args = self.get_params(cols[self.COL_LABEL].find('span/a').attrib['href']) + a.id = '%s%s%s' % (a._acc_nb, a._args['IndiceSupport'], a._args['NumPolice']) + a._inv = True + yield a + + +class PartAVPage(AVPage): + pass + + +class AccountsPageMixin(LoggedPage, CDNBasePage): COL_HISTORY = 2 COL_FIRE_EVENT = 3 - COL_ID = 4 COL_LABEL = 5 - COL_BALANCE = -1 TYPES = { - u'CARTE': Account.TYPE_CARD, - u'COMPTE COURANT': Account.TYPE_CHECKING, - u'CPTE EXPLOITATION IMMOB': Account.TYPE_CHECKING, - u'CPT COURANT': Account.TYPE_CHECKING, - u'CONSEILLE RESIDENT': Account.TYPE_CHECKING, - u'PEA': Account.TYPE_PEA, - u'P.E.A': Account.TYPE_PEA, - u'COMPTE ÉPARGNE': Account.TYPE_SAVINGS, - u'COMPTE EPARGNE': Account.TYPE_SAVINGS, - u'COMPTE SUR LIVRET': Account.TYPE_SAVINGS, - u'LDDS': Account.TYPE_SAVINGS, - u'LIVRET': Account.TYPE_SAVINGS, - u"PLAN D'EPARGNE": Account.TYPE_SAVINGS, - u'PLAN ÉPARGNE': Account.TYPE_SAVINGS, - u'ASS.VIE': Account.TYPE_LIFE_INSURANCE, - u'BONS CAPI': Account.TYPE_CAPITALISATION, - u'ÉTOILE AVANCE': Account.TYPE_LOAN, - u'ETOILE AVANCE': Account.TYPE_LOAN, - u'PRÊT': Account.TYPE_LOAN, - u'CREDIT': Account.TYPE_LOAN, - u'FACILINVEST': Account.TYPE_LOAN, - u'TITRES': Account.TYPE_MARKET, - u'COMPTE TIT': Account.TYPE_MARKET, - u'PRDTS BLOQ. TIT': Account.TYPE_MARKET, - u'PRODUIT BLOQUE TIT': Account.TYPE_MARKET, - u'COMPTE A TERME': Account.TYPE_DEPOSIT, - } + 'CARTE': Account.TYPE_CARD, + 'COMPTE COURANT': Account.TYPE_CHECKING, + 'CPTE EXPLOITATION IMMOB': Account.TYPE_CHECKING, + 'CPT COURANT': Account.TYPE_CHECKING, + 'CONSEILLE RESIDENT': Account.TYPE_CHECKING, + 'PEA': Account.TYPE_PEA, + 'P.E.A': Account.TYPE_PEA, + 'COMPTE ÉPARGNE': Account.TYPE_SAVINGS, + 'COMPTE EPARGNE': Account.TYPE_SAVINGS, + 'COMPTE SUR LIVRET': Account.TYPE_SAVINGS, + 'LDDS': Account.TYPE_SAVINGS, + 'LIVRET': Account.TYPE_SAVINGS, + "PLAN D'EPARGNE": Account.TYPE_SAVINGS, + 'PLAN ÉPARGNE': Account.TYPE_SAVINGS, + 'ASS.VIE': Account.TYPE_LIFE_INSURANCE, + 'BONS CAPI': Account.TYPE_CAPITALISATION, + 'ÉTOILE AVANCE': Account.TYPE_LOAN, + 'ETOILE AVANCE': Account.TYPE_LOAN, + 'PRÊT': Account.TYPE_LOAN, + 'CREDIT': Account.TYPE_LOAN, + 'FACILINVEST': Account.TYPE_LOAN, + 'TITRES': Account.TYPE_MARKET, + 'COMPTE TIT': Account.TYPE_MARKET, + 'PRDTS BLOQ. TIT': Account.TYPE_MARKET, + 'PRODUIT BLOQUE TIT': Account.TYPE_MARKET, + 'COMPTE A TERME': Account.TYPE_DEPOSIT, + } + + def get_account_type(self, label): + for pattern, actype in sorted(self.TYPES.items()): + if label.startswith(pattern) or label.endswith(pattern): + return actype + return Account.TYPE_UNKNOWN + + +class AccountsPage(AccountsPageMixin): + COL_ID = 4 + COL_BALANCE = -1 def make__args_dict(self, line): - return {'_eventId': 'clicDetailCompte', - '_ipc_eventValue': '', - '_ipc_fireEvent': '', - 'execution': self.get_execution(), - 'idCompteClique': line[self.COL_ID], - } + return { + '_eventId': 'clicDetailCompte', + '_ipc_eventValue': '', + '_ipc_fireEvent': '', + 'execution': self.get_execution(), + 'idCompteClique': line[self.COL_ID], + } def get_password_expired(self): error = CleanText('//div[@class="x-attentionErreur"]/b')(self.doc) if "vous devez modifier votre code confidentiel à la première connexion" in error: return error - def get_account_type(self, label): - for pattern, actype in sorted(self.TYPES.items()): - if label.startswith(pattern) or label.endswith(pattern): - return actype - return Account.TYPE_UNKNOWN - def get_history_link(self): return CleanText().filter(self.get_from_js(",url: Ext.util.Format.htmlDecode('", "'")).replace('&', '&') - def get_av_link(self): - return self.doc.xpath('//a[contains(text(), "Consultation")]')[0].attrib['href'] - - def get_list(self): + def get_account_ownership(self, owner_pos, acc_id, name): + acc_id_pos = self.text.find(acc_id) + reg = re.compile(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bet (m|mr|me|mme|mlle|mle|ml)\b(.*)', re.IGNORECASE) + for pos, owner in owner_pos.items(): + if acc_id_pos < pos: + if reg.search(owner): + return AccountOwnership.CO_OWNER + elif all(n in owner.upper() for n in name.split()): + return AccountOwnership.OWNER + return AccountOwnership.ATTORNEY + + def get_list(self, name): accounts = [] previous_account = None @@ -305,6 +368,10 @@ def get_list(self): if txt is None: raise BrowserUnavailable('Unable to find accounts list in scripts') + owner_pos = OrderedDict() + for m in re.finditer(r'(M\. .*|Mme .*|Mlle .*)(?=\')', self.text): + owner_pos[m.start()] = m.group(1) + data = json.loads('[%s]' % txt.replace("'", '"')) for line in data: @@ -320,6 +387,7 @@ def get_list(self): if a.label == 'ASSURANCE VIE-BON CAPI-SCPI-DIVERS *': continue + a.ownership = self.get_account_ownership(owner_pos, line[self.COL_ID], name) a.balance = Decimal(FrenchTransaction.clean_amount(line[self.COL_BALANCE])) a.currency = a.get_currency(line[self.COL_BALANCE]) a.type = self.get_account_type(a.label) @@ -358,71 +426,19 @@ def iban_page(self): form['_ipc_eventValue'] = 'bouchon=bouchon' form.submit() - @method - class get_profile(ItemElement): - klass = Profile - - obj_name = CleanText('//p[@class="nom"]') - def get_strid(self): return re.search(r'(\d{4,})', Attr('//form[@name="changePageForm"]', 'action')(self.doc)).group(0) -class ProIbanPage(CDNBasePage): - pass - - -class AVPage(LoggedPage, CDNBasePage): - COL_LABEL = 0 - COL_BALANCE = 3 - - ARGS = ['IndiceClassement', 'IndiceCompte', 'Banque', 'Agence', 'Classement', 'Serie', 'SScompte', 'Categorie', 'IndiceSupport', 'NumPolice', 'LinkHypertext'] - - def get_params(self, text): - url = self.get_from_js('document.detail.action="', '";') - args = {} - l = [] - for sub in re.findall("'([^']*)'", text): - l.append(sub) - for i, key in enumerate(self.ARGS): - args[key] = l[self.ARGS.index(key)] - return url, args - - def get_av_accounts(self): - for table in self.doc.xpath('//table[@class="datas"]'): - head_cols = table.xpath('./tr[@class="entete"]/td') - for tr in table.xpath('./tr[not(@class)]'): - cols = tr.findall('td') - if len(cols) != 4: - continue - - a = Account() - - # get acc_nb like on accounts page - a._acc_nb = Regexp( - CleanText('//div[@id="v1-cadre"]//b[contains(text(), "Compte N")]', replace=[(' ', '')]), - r'(\d+)' - )(self.doc)[5:] - - a.label = CleanText('.')(cols[self.COL_LABEL]) - a.type = Account.TYPE_LIFE_INSURANCE - a.balance = MyDecimal('.')(cols[self.COL_BALANCE]) - a.currency = a.get_currency(CleanText('.')(head_cols[self.COL_BALANCE])) - a._link, a._args = self.get_params(cols[self.COL_LABEL].find('span/a').attrib['href']) - a.id = '%s%s%s' % (a._acc_nb, a._args['IndiceSupport'], a._args['NumPolice']) - a._inv = True - yield a - - -class PartAVPage(AVPage): - pass - - -class ProAccountsPage(AccountsPage): +class ProAccountsPage(AccountsPageMixin): COL_ID = 0 COL_BALANCE = 1 - ARGS = ['Banque', 'Agence', 'Classement', 'Serie', 'SSCompte', 'Devise', 'CodeDeviseCCB', 'LibelleCompte', 'IntituleCompte', 'Indiceclassement', 'IndiceCompte', 'NomClassement'] + ARGS = [ + 'Banque', 'Agence', 'Classement', 'Serie', 'SSCompte', 'Devise', + 'CodeDeviseCCB', 'LibelleCompte', 'IntituleCompte', 'Indiceclassement', + 'IndiceCompte', 'NomClassement', + ] def on_load(self): if self.doc.xpath('//h1[contains(text(), "Erreur")]'): @@ -465,6 +481,7 @@ def get_list(self): deposit_count = 1 for tr in self.doc.xpath('//table[has-class("datas")]//tr'): if tr.attrib.get('class', '') == 'entete': + owner = CleanText('.')(tr.findall('td')[0]) continue cols = tr.findall('td') @@ -472,6 +489,7 @@ def get_list(self): a = Account() a.label = unicode(cols[self.COL_ID].xpath('.//span[@class="left-underline"] | .//span[@class="left"]/a')[0].text.strip()) a.type = self.get_account_type(a.label) + a.ownership = self.get_account_ownership(owner) balance = CleanText('.')(cols[self.COL_BALANCE]) if balance == '': continue @@ -533,18 +551,17 @@ def get_list(self): yield a + def get_account_ownership(self, owner): + if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (m|mr|me|mme|mlle|mle|ml)\b', owner, re.IGNORECASE): + return AccountOwnership.CO_OWNER + return AccountOwnership.OWNER + def iban_page(self): self.browser.location(self.doc.xpath('.//a[contains(text(), "Impression IBAN")]')[0].attrib['href']) def has_iban(self): return not bool(CleanText('//*[contains(., "pas de compte vous permettant l\'impression de RIB")]')(self.doc)) - @method - class get_profile(ItemElement): - klass = Profile - - obj_name = CleanText('//p[@class="nom"]') - class IbanPage(LoggedPage, HTMLPage): def get_iban(self): -- GitLab