From 80e8025ee346950732427078d463f507091b45ee Mon Sep 17 00:00:00 2001 From: Vincent Ardisson Date: Wed, 12 Aug 2020 12:08:01 +0200 Subject: [PATCH] [bforbank] improve code style --- modules/bforbank/browser.py | 84 +++++++++++++++------- modules/bforbank/module.py | 21 +++--- modules/bforbank/pages.py | 135 ++++++++++++++++++++++-------------- 3 files changed, 154 insertions(+), 86 deletions(-) diff --git a/modules/bforbank/browser.py b/modules/bforbank/browser.py index 77fd801ac5..82dcca18be 100644 --- a/modules/bforbank/browser.py +++ b/modules/bforbank/browser.py @@ -16,7 +16,11 @@ # # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . + +# flake8: compatible + import datetime + from dateutil.relativedelta import relativedelta from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded from weboob.browser import LoginBrowser, URL, need_login @@ -38,10 +42,12 @@ class BforbankBrowser(LoginBrowser): BASEURL = 'https://client.bforbank.com' - login = URL(r'/connexion-client/service/login\?urlBack=%2Fespace-client', - r'/connexion-client/service/login\?urlBack=', - r'https://secure.bforbank.com/connexion-client/service/login\?urlBack=', - LoginPage) + login = URL( + r'/connexion-client/service/login\?urlBack=%2Fespace-client', + r'/connexion-client/service/login\?urlBack=', + r'https://secure.bforbank.com/connexion-client/service/login\?urlBack=', + LoginPage + ) error = URL('/connexion-client/service/auth', ErrorPage) user_validation = URL( r'/profil-client/', @@ -49,8 +55,11 @@ class BforbankBrowser(LoginBrowser): UserValidationPage ) home = URL('/espace-client/$', AccountsPage) - rib = URL('/espace-client/rib', - '/espace-client/rib/(?P\d+)', RibPage) + rib = URL( + '/espace-client/rib', + r'/espace-client/rib/(?P\d+)', + RibPage + ) loan_history = URL('/espace-client/livret/consultation.*', LoanHistoryPage) history = URL('/espace-client/consultation/operations/.*', HistoryPage) coming = URL(r'/espace-client/consultation/operationsAVenir/(?P\d+)$', HistoryPage) @@ -58,20 +67,33 @@ class BforbankBrowser(LoginBrowser): card_page = URL(r'/espace-client/carte/(?P\d+)$', CardPage) lifeinsurance_list = URL(r'/client/accounts/lifeInsurance/lifeInsuranceSummary.action', LifeInsuranceList) - lifeinsurance_iframe = URL(r'https://(?:www|client).bforbank.com/client/accounts/lifeInsurance/consultationDetailSpirica.action', LifeInsuranceIframe) + lifeinsurance_iframe = URL( + r'https://(?:www|client).bforbank.com/client/accounts/lifeInsurance/consultationDetailSpirica.action', + LifeInsuranceIframe + ) lifeinsurance_redir = URL(r'https://assurance-vie.bforbank.com/sylvea/welcomeSSO.xhtml', LifeInsuranceRedir) - lifeinsurance_error = URL(r'/client/accounts/lifeInsurance/lifeInsuranceError.action\?errorCode=.*&errorMsg=.*', - r'https://client.bforbank.com/client/accounts/lifeInsurance/lifeInsuranceError.action\?errorCode=.*&errorMsg=.*', - ErrorPage) + lifeinsurance_error = URL( + r'/client/accounts/lifeInsurance/lifeInsuranceError.action\?errorCode=.*&errorMsg=.*', + r'https://client.bforbank.com/client/accounts/lifeInsurance/lifeInsuranceError.action\?errorCode=.*&errorMsg=.*', + ErrorPage + ) bourse_login = URL(r'/espace-client/titres/debranchementCaTitre/(?P\d+)') bourse_action_needed = URL('https://bourse.bforbank.com/netfinca-titres/*', BourseActionNeeded) - bourse = URL('https://bourse.bforbank.com/netfinca-titres/servlet/com.netfinca.frontcr.synthesis.HomeSynthesis', - 'https://bourse.bforbank.com/netfinca-titres/servlet/com.netfinca.frontcr.account.*', - BoursePage) - bourse_titre = URL(r'https://bourse.bforbank.com/netfinca-titres/servlet/com.netfinca.frontcr.navigation.Titre', BoursePage) # to get logout link - - bourse_disco = URL(r'https://bourse.bforbank.com/netfinca-titres/servlet/com.netfinca.frontcr.login.Logout', BourseDisconnectPage) + bourse = URL( + 'https://bourse.bforbank.com/netfinca-titres/servlet/com.netfinca.frontcr.synthesis.HomeSynthesis', + 'https://bourse.bforbank.com/netfinca-titres/servlet/com.netfinca.frontcr.account.*', + BoursePage + ) + # to get logout link + bourse_titre = URL( + r'https://bourse.bforbank.com/netfinca-titres/servlet/com.netfinca.frontcr.navigation.Titre', + BoursePage + ) + bourse_disco = URL( + r'https://bourse.bforbank.com/netfinca-titres/servlet/com.netfinca.frontcr.login.Logout', + BourseDisconnectPage + ) profile = URL(r'/espace-client/profil/informations', ProfilePage) def __init__(self, birthdate, username, password, *args, **kwargs): @@ -80,8 +102,10 @@ def __init__(self, birthdate, username, password, *args, **kwargs): self.accounts = None self.weboob = kwargs['weboob'] - self.spirica = SpiricaBrowser('https://assurance-vie.bforbank.com/', - None, None, *args, **kwargs) + self.spirica = SpiricaBrowser( + 'https://assurance-vie.bforbank.com/', + *args, username=None, password=None, **kwargs + ) def deinit(self): super(BforbankBrowser, self).deinit() @@ -103,7 +127,7 @@ def do_login(self): elif error == 'error.authentification': raise BrowserIncorrectPassword() elif error is not None: - assert False, 'Unexpected error at login: "%s"' % error + raise AssertionError('Unexpected error at login: "%s"' % error) # We must go home after login otherwise do_login will be done twice. self.home.go() @@ -143,9 +167,15 @@ def iter_accounts(self): card._checking_account = account card._index = indexes[card.number] - self.location(account.url.replace('operations', 'encoursCarte') + '/%s' % card._index) - if self.page.get_debit_date().month == (datetime.date.today() + relativedelta(months=1)).month: - self.location(account.url.replace('operations', 'encoursCarte') + '/%s?month=1' % card._index) + card_url = account.url.replace('operations', 'encoursCarte') + card_url += '/%s' % card._index + + self.location(card_url) + next_month = datetime.date.today() + relativedelta(months=1) + if self.page.get_debit_date().month == next_month.month: + card_url += '?month=1' + self.location(card_url) + card.balance = 0 card.coming = self.page.get_balance() assert not empty(card.coming) @@ -164,9 +194,12 @@ def get_history(self, account): if not bourse_account: return iter([]) - self.location(bourse_account._link_id, params={ - 'nump': bourse_account._market_id, - }) + self.location( + bourse_account._link_id, + params={ + 'nump': bourse_account._market_id, + } + ) assert self.bourse.is_here() history = list(self.page.iter_history()) self.leave_espace_bourse() @@ -218,7 +251,6 @@ def get_coming(self, account): else: raise NotImplementedError() - def goto_lifeinsurance(self, account): self.location('https://client.bforbank.com/espace-client/assuranceVie') self.lifeinsurance_list.go() diff --git a/modules/bforbank/module.py b/modules/bforbank/module.py index 9758ad1c59..07231683d6 100644 --- a/modules/bforbank/module.py +++ b/modules/bforbank/module.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . +# flake8: compatible from weboob.tools.backend import Module, BackendConfig from weboob.capabilities.bank import AccountNotFound @@ -24,6 +25,7 @@ from weboob.capabilities.base import find_object from weboob.capabilities.profile import CapProfile from weboob.tools.value import ValueBackendPassword, ValueDate + from .browser import BforbankBrowser @@ -37,18 +39,21 @@ class BforbankModule(Module, CapBankWealth, CapProfile): EMAIL = 'b.delpey@hotmail.fr' LICENSE = 'LGPLv3+' VERSION = '2.1' - CONFIG = BackendConfig(ValueBackendPassword('login', label='Identifiant', masked=False), - ValueBackendPassword('password', label='Code personnel', regexp=r'\d+$'), - ValueDate('birthdate', label='Date de naissance', formats=('%d/%m/%Y',)) - ) + CONFIG = BackendConfig( + ValueBackendPassword('login', label='Identifiant', masked=False), + ValueBackendPassword('password', label='Code personnel', regexp=r'\d+$'), + ValueDate('birthdate', label='Date de naissance', formats=('%d/%m/%Y',)) + ) BROWSER = BforbankBrowser def create_default_browser(self): - return self.create_browser(self.config['birthdate'].get(), - self.config['login'].get(), - self.config['password'].get(), - weboob=self.weboob) + return self.create_browser( + self.config['birthdate'].get(), + self.config['login'].get(), + self.config['password'].get(), + weboob=self.weboob + ) def get_account(self, _id): return find_object(self.browser.iter_accounts(), id=_id, error=AccountNotFound) diff --git a/modules/bforbank/pages.py b/modules/bforbank/pages.py index a85c5fc242..01c455f887 100644 --- a/modules/bforbank/pages.py +++ b/modules/bforbank/pages.py @@ -17,15 +17,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . +# flake8: compatible + from __future__ import unicode_literals +from base64 import b64decode from collections import OrderedDict -import re +import datetime from io import BytesIO -from base64 import b64decode +import re from PIL import Image - from weboob.exceptions import ActionNeeded from weboob.browser.pages import LoggedPage, HTMLPage, pagination, AbstractPage, JsonPage from weboob.browser.elements import method, ListElement, ItemElement, TableElement @@ -37,21 +39,21 @@ ) from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.compat import urlencode, urlparse, urlunparse, parse_qsl, urljoin -import datetime class BfBKeyboard(object): - symbols = {'0': '00111111001111111111111111111111000000111000000001111111111111111111110011111100', - '1': '00000000000011000000011100000001100000001111111111111111111100000000000000000000', - '2': '00100000111110000111111000011111000011111000011101111111100111111100010111000001', - '3': '00100001001110000111111000011111001000011101100001111111011111111111110000011110', - '4': '00000011000000111100000111010001111001001111111111111111111111111111110000000100', - '5': '00000001001111100111111110011110010000011001000001100110011110011111110000111110', - '6': '00011111000111111110111111111111001100011000100001110011001111001111110100011110', - '7': '10000000001000000000100000111110011111111011111100111110000011100000001100000000', - '8': '00000011001111111111111111111110001000011000100001111111111111111111110010011110', - '9': '00111000001111110011111111001110000100011000010011111111111111111111110011111100', - } + symbols = { + '0': '00111111001111111111111111111111000000111000000001111111111111111111110011111100', + '1': '00000000000011000000011100000001100000001111111111111111111100000000000000000000', + '2': '00100000111110000111111000011111000011111000011101111111100111111100010111000001', + '3': '00100001001110000111111000011111001000011101100001111111011111111111110000011110', + '4': '00000011000000111100000111010001111001001111111111111111111111111111110000000100', + '5': '00000001001111100111111110011110010000011001000001100110011110011111110000111110', + '6': '00011111000111111110111111111111001100011000100001110011001111001111110100011110', + '7': '10000000001000000000100000111110011111111011111100111110000011100000001100000000', + '8': '00000011001111111111111111111110001000011000100001111111111111111111110010011110', + '9': '00111000001111110011111111001110000100011000010011111111111111111111110011111100', + } def __init__(self, basepage): self.basepage = basepage @@ -123,12 +125,16 @@ def populate_rib(self, accounts): if 'selected' in option.attrib: self.get_iban(accounts) else: - self.browser.rib.go(id=re.sub('[^\d]', '', Attr('.', 'value')(option))).get_iban(accounts) + page = self.browser.rib.go(id=re.sub(r'[^\d]', '', Attr('.', 'value')(option))) + page.get_iban(accounts) def get_iban(self, accounts): for account in accounts: if self.doc.xpath('//option[@selected and contains(@value, $id)]', id=account.id): - account.iban = CleanText('//td[contains(text(), "IBAN")]/following-sibling::td[1]', replace=[(' ', '')])(self.doc) + account.iban = CleanText( + '//td[contains(text(), "IBAN")]/following-sibling::td[1]', + replace=[(' ', '')] + )(self.doc) class AccountsPage(LoggedPage, HTMLPage): @@ -145,16 +151,20 @@ class iter_accounts(ListElement): class item(ItemElement): klass = Account - TYPE = {'Livret': Account.TYPE_SAVINGS, - 'Compte': Account.TYPE_CHECKING, - 'PEA': Account.TYPE_PEA, - 'PEA-PME': Account.TYPE_PEA, - 'Compte-titres': Account.TYPE_MARKET, - 'Assurance-vie': Account.TYPE_LIFE_INSURANCE, - 'Crédit': Account.TYPE_LOAN, - } - - obj_id = CleanText('./td//div[contains(@class, "-synthese-title") or contains(@class, "-synthese-text")]') & Regexp(pattern=r'(\d+)') + TYPE = { + 'Livret': Account.TYPE_SAVINGS, + 'Compte': Account.TYPE_CHECKING, + 'PEA': Account.TYPE_PEA, + 'PEA-PME': Account.TYPE_PEA, + 'Compte-titres': Account.TYPE_MARKET, + 'Assurance-vie': Account.TYPE_LIFE_INSURANCE, + 'Crédit': Account.TYPE_LOAN, + } + + obj_id = Regexp( + CleanText('./td//div[contains(@class, "-synthese-title") or contains(@class, "-synthese-text")]'), + r'(\d+)' + ) obj_number = obj_id obj_label = CleanText('./td//div[contains(@class, "-synthese-title")]') obj_balance = MyDecimal('./td//div[contains(@class, "-synthese-num")]', replace_dots=True) @@ -169,11 +179,19 @@ def obj_url(self): def condition(self): return not len(self.el.xpath('./td[@class="chart"]')) + owner_re = re.compile( + r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', + re.IGNORECASE + ) + def obj_ownership(self): - owner = CleanText('./td//div[contains(@class, "-synthese-text") and not(starts-with(., "N°"))]', default=None)(self) + owner = CleanText( + './td//div[contains(@class, "-synthese-text") and not(starts-with(., "N°"))]', + default=None + )(self) if owner: - if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', owner, re.IGNORECASE): + if self.owner_re.search(owner): return AccountOwnership.CO_OWNER elif all(n in owner.upper() for n in self.env['name'].split()): return AccountOwnership.OWNER @@ -181,11 +199,12 @@ def obj_ownership(self): class Transaction(FrenchTransaction): - PATTERNS = [(re.compile('^(?PVIREMENT)'), FrenchTransaction.TYPE_TRANSFER), - (re.compile('^(?PINTERETS)'), FrenchTransaction.TYPE_BANK), - (re.compile('^RETRAIT AU DISTRIBUTEUR'), FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile(u'^Règlement cartes à débit différé du'), FrenchTransaction.TYPE_CARD_SUMMARY), - ] + PATTERNS = [ + (re.compile('^(?PVIREMENT)'), FrenchTransaction.TYPE_TRANSFER), + (re.compile('^(?PINTERETS)'), FrenchTransaction.TYPE_BANK), + (re.compile('^RETRAIT AU DISTRIBUTEUR'), FrenchTransaction.TYPE_WITHDRAWAL), + (re.compile('^Règlement cartes à débit différé du'), FrenchTransaction.TYPE_CARD_SUMMARY), + ] class LoanHistoryPage(LoggedPage, HTMLPage): @@ -201,6 +220,7 @@ class item(ItemElement): obj_vdate = Transaction.Date('./td[3]') obj_raw = Transaction.Raw(Format('%s %s', CleanText('./td[1]'), CleanText('./following-sibling::tr[contains(@class, "tr-more")]/td/p[1]/span'))) + class HistoryPage(LoggedPage, HTMLPage): @pagination @method @@ -220,19 +240,23 @@ def condition(self): return False def obj_date(self): - return Transaction.Date(Regexp(CleanText('./preceding::tr[has-class("tr-section")][1]/th'), r'(\d+/\d+/\d+)'))(self) + return Transaction.Date( + Regexp( + CleanText('./preceding::tr[has-class("tr-section")][1]/th'), + r'(\d+/\d+/\d+)' + ) + )(self) obj_raw = Transaction.Raw(Format('%s %s', CleanText('./td[1]'), CleanText('./following-sibling::tr[contains(@class, "tr-more")]/td/p[1]/span'))) obj_amount = MyDecimal('./td[2]', replace_dots=True) - @method class get_today_operations(TableElement): item_xpath = '//table[has-class("style-virements")]/tbody/tr[@class="tr-trigger"]' head_xpath = '//table[has-class("style-virements")]/thead/tr/th' col_amount = 'Montant' - col_raw = u'Libellé' + col_raw = 'Libellé' class item(ItemElement): klass = Transaction @@ -266,9 +290,14 @@ def get_balance(self): return MyDecimal('./span', replace_dots=True)(d) def get_debit_date(self): - return Date(Regexp(CleanText('//div[@class="m-tabs-tab-meta"]'), - r'Ces opérations (?:seront|ont été) débitées sur votre compte le (\d{2}/\d{2}/\d{4})'), - dayfirst=True)(self.doc) + return ( + Date( + Regexp( + CleanText('//div[@class="m-tabs-tab-meta"]'), + r'Ces opérations (?:seront|ont été) débitées sur votre compte le (\d{2}/\d{2}/\d{4})'), + dayfirst=True + )(self.doc) + ) def create_summary(self): tr = Transaction() @@ -288,15 +317,15 @@ def next_page(self): page = Attr('//a[@id="next-page"]', 'data')(self) return add_qs(self.page.url, page=page) - col_raw = u'Libellé' - col_vdate = u'Date opération' + col_raw = 'Libellé' + col_vdate = 'Date opération' col_amount = 'Montant' class item(ItemElement): klass = Transaction def condition(self): - return CleanText('.')(self) != u'Aucune opération effectuée' + return CleanText('.')(self) != 'Aucune opération effectuée' obj_type = Transaction.TYPE_DEFERRED_CARD obj_raw = CleanText(TableCell('raw')) @@ -311,18 +340,20 @@ class CardPage(LoggedPage, HTMLPage): def has_no_card(self): # Persistent message for cardless accounts return ( - CleanText('//div[@id="alert"]/p[contains(text(), "Aucune donnée n\'a été retournée par le service")]')(self.doc) + CleanText( + '''//div[@id="alert"]/p[contains(text(), "Aucune donnée n'a été retournée par le service")]''' + )(self.doc) or not self.doc.xpath('//div[@class="content-boxed"]') ) def get_cards(self, account_id): divs = self.doc.xpath('//div[@class="content-boxed"]') msgs = re.compile( - 'Vous avez fait opposition sur cette carte bancaire.' + - '|Votre carte bancaire a été envoyée.' + - '|Carte bancaire commandée.' + - '|BforBank a fait opposition sur votre carte' + - '|Pour des raisons de sécurité, la demande de réception du code confidentiel de votre carte par SMS est indisponible' + 'Vous avez fait opposition sur cette carte bancaire.' + + '|Votre carte bancaire a été envoyée.' + + '|Carte bancaire commandée.' + + '|BforBank a fait opposition sur votre carte' + + '|Pour des raisons de sécurité, la demande de réception du code confidentiel de votre carte par SMS est indisponible' ) divs = [d for d in divs if not msgs.search(CleanText('.//div[has-class("alert")]', default='')(d))] divs = [d.xpath('.//div[@class="m-card-infos"]')[0] for d in divs] @@ -336,9 +367,9 @@ def get_cards(self, account_id): for div in divs: label = CleanText('.//div[@class="m-card-infos-body-title"]')(div) number = CleanText('.//div[@class="m-card-infos-body-num"]', default='')(div) - number = re.sub('[^\d*]', '', number).replace('*', 'x') - debit = CleanText(u'.//div[@class="m-card-infos-body-text"][contains(text(),"Débit")]')(div) - assert debit == u'Débit différé', 'unrecognized card type %s: %s' % (number, debit) + number = re.sub(r'[^\d*]', '', number).replace('*', 'x') + debit = CleanText('.//div[@class="m-card-infos-body-text"][contains(text(),"Débit")]')(div) + assert debit == 'Débit différé', 'unrecognized card type %s: %s' % (number, debit) card = Account() card.id = '%s.%s' % (account_id, number) -- GitLab