diff --git a/modules/amazon/module.py b/modules/amazon/module.py index 481a19d6ee354c3ffe360de4d179f2caa43c4659..274bdcd4a766920f3f45d9fef6c8c29247c1eb14 100644 --- a/modules/amazon/module.py +++ b/modules/amazon/module.py @@ -23,7 +23,9 @@ from collections import OrderedDict from weboob.capabilities.bill import DocumentTypes, CapDocument, Subscription, Document, SubscriptionNotFound, DocumentNotFound from weboob.capabilities.base import find_object, NotAvailable from weboob.tools.backend import Module, BackendConfig +from weboob.tools.compat import urljoin from weboob.tools.value import ValueBackendPassword, Value +from weboob.tools.pdf import html_to_pdf from .browser import AmazonBrowser from .en.browser import AmazonEnBrowser @@ -93,3 +95,14 @@ class AmazonModule(Module, CapDocument): return return self.browser.open(document.url).content + + def download_document_pdf(self, document): + if not isinstance(document, Document): + document = self.get_document(document) + if document.url is NotAvailable: + return + if document.format == 'pdf': + return self.browser.open(document.url).content + + url = urljoin(self.browser.BASEURL, document.url) + return html_to_pdf(self.browser, url=url) diff --git a/modules/amazon/pages.py b/modules/amazon/pages.py index 7288d28250db8f96842d83b19ddae513de1f93df..68d191eaeeb0e167834554e302e8947201f8bee1 100644 --- a/modules/amazon/pages.py +++ b/modules/amazon/pages.py @@ -138,7 +138,7 @@ class DocumentsPage(LoggedPage, HTMLPage): klass = Bill load_details = Field('_pre_url') & AsyncLoad - obj__simple_id = CleanText('.//div[has-class("actions")]//span[has-class("value")]') + obj__simple_id = CleanText('.//span[contains(text(), "N° de commande")]/following-sibling::span') obj_id = Format('%s_%s', Env('subid'), Field('_simple_id')) obj__pre_url = Format('/gp/shared-cs/ajax/invoice/invoice.html?orderId=%s&relatedRequestId=%s&isADriveSubscription=&isHFC=', Field('_simple_id'), Env('request_id')) @@ -165,7 +165,7 @@ class DocumentsPage(LoggedPage, HTMLPage): async_page = Async('details').loaded_page(self) url = Link('//a[contains(@href, "download")]|//a[contains(@href, "generated_invoices")]', default=NotAvailable)(async_page.doc) if not url: - url = Link('//a[contains(text(), "Imprimer un récapitulatif de commande")]')(self) + url = Link('//a[contains(text(), "Imprimer un récapitulatif de commande")]')(async_page.doc) return url def obj_format(self): diff --git a/modules/banquepopulaire/pages.py b/modules/banquepopulaire/pages.py index 4232785fce3aca3cc0ccbaa55d1d3f6417ab2af4..2fe5c673402ccd58042a25d9efc700fb9bd4da04 100644 --- a/modules/banquepopulaire/pages.py +++ b/modules/banquepopulaire/pages.py @@ -319,7 +319,7 @@ class Login2Page(LoginPage): r = self.browser.open(self.request_url) doc = r.json() - self.form_id, = [(k, v[0]['id']) for k, v in doc['step']['validationUnits'][0].items() if v[0]['type'] == 'PASSWORD_LOOKUP'] + self.form_id, = [(k, v[0]['id'], v[0]['type']) for k, v in doc['step']['validationUnits'][0].items() if v[0]['type'] in ('PASSWORD_LOOKUP', 'IDENTIFIER')] def login(self, login, password): payload = { @@ -332,10 +332,22 @@ class Login2Page(LoginPage): } ] } } - url = self.request_url + '/step' - headers = {'Content-Type': 'application/json'} - r = self.browser.open(url, data=json.dumps(payload), headers=headers) + if self.form_id[2] == 'IDENTIFIER': + del payload['validate'][self.form_id[0]][0]['password'] + payload['validate'][self.form_id[0]][0]['type'] = 'IDENTIFIER' + doc = self.browser.open(url, json=payload).json() + form_id, = [(k, v[0]['id'], v[0]['type']) for k, v in doc['validationUnits'][0].items() if v[0]['type'] in ('PASSWORD',)] + payload = { + 'validate': { + form_id[0]: [{ + 'id': self.form_id[1], + 'password': password, + 'type': 'PASSWORD', + }] + } + } + r = self.browser.open(url, json=payload) doc = r.json() self.logger.debug('doc = %s', doc) @@ -349,8 +361,7 @@ class Login2Page(LoginPage): payload = {'validate': doc['validationUnits'][0]} url = self.request_url + '/step' - headers = {'Content-Type': 'application/json'} - r = self.browser.open(url, data=json.dumps(payload), headers=headers) + r = self.browser.open(url, json=payload) doc = r.json() self.logger.debug('doc = %s', doc) diff --git a/modules/bred/bred/browser.py b/modules/bred/bred/browser.py index 22652f040d5c7c989b625c5761aef49060d99de7..baaa402eb0b7c188887b9f5e6cbfad1957293306 100644 --- a/modules/bred/bred/browser.py +++ b/modules/bred/bred/browser.py @@ -34,6 +34,7 @@ from .pages import ( TokenPage, MoveUniversePage, SwitchPage, LoansPage, AccountsPage, IbanPage, LifeInsurancesPage, SearchPage, ProfilePage, EmailsPage, ErrorPage, + ErrorCodePage, ) __all__ = ['BredBrowser'] @@ -59,6 +60,7 @@ class BredBrowser(LoginBrowser): search = URL('/transactionnel/services/applications/operations/getSearch/', SearchPage) profile = URL('/transactionnel/services/rest/User/user', ProfilePage) emails = URL('/transactionnel/services/applications/gestionEmail/getAdressesMails', EmailsPage) + error_code = URL('/.*\?errorCode=.*', ErrorCodePage) def __init__(self, accnum, login, password, *args, **kwargs): kwargs['username'] = login diff --git a/modules/bred/bred/pages.py b/modules/bred/bred/pages.py index b71e72214fb064b65a45d06523474f92b56f756d..e2a5b719dccfbfd5d399b3fa239c3ce12e9fb7cb 100644 --- a/modules/bred/bred/pages.py +++ b/modules/bred/bred/pages.py @@ -325,3 +325,15 @@ class ErrorPage(LoggedPage, HTMLPage): if '/pages-gestion-des-erreurs/message-tiers-oppose' in self.url: # need a case to retrieve the error message raise ActionNeeded("Impossible de se connecter au compte car l'identification en 2 étapes a été activée") + + +class ErrorCodePage(HTMLPage): + def on_load(self): + code = re.search(r'\/\?errorCode=(\d+)', self.url).group(1) + page = self.browser.open('/particuliers/compte-bancaire/comptes-en-ligne/bredconnect-compte-ligne?errorCode=%s' % code).page + # invalid login/password + if code == '20100': + msg = CleanText('//label[contains(@class, "error")]')(page.doc) + raise BrowserIncorrectPassword(msg) + + assert False, 'The % error is not handled.' % code diff --git a/modules/caissedepargne/browser.py b/modules/caissedepargne/browser.py index 9250f80e3bd7553ad522f08402c3b3ea6b0c851c..c9297302e5d8825bf346a38607c36dd7ab13ca13 100644 --- a/modules/caissedepargne/browser.py +++ b/modules/caissedepargne/browser.py @@ -481,6 +481,10 @@ class CaisseEpargne(LoginBrowser, StatesMixin): self.page.go_history(info) + # ensure we are on the correct history page + if 'netpro' in self.page.url and not self.page.is_history_of(info['id']): + self.page.go_history_netpro(info) + info['link'] = [info['link']] for i in range(self.HISTORY_MAX_PAGE): @@ -745,6 +749,9 @@ class CaisseEpargne(LoginBrowser, StatesMixin): ) ) + if 'netpro' in self.url: + return self.page.create_transfer(account, recipient, transfer) + self.page.continue_transfer(account.label, recipient.label, transfer.label) return self.page.update_transfer(transfer, account, recipient) diff --git a/modules/caissedepargne/cenet/pages.py b/modules/caissedepargne/cenet/pages.py index 80cf4b89bc4ef1a20caab6a8252a4c1b88bc152e..15f44efc2a7d627f149d2cb5189fe649055bf050 100644 --- a/modules/caissedepargne/cenet/pages.py +++ b/modules/caissedepargne/cenet/pages.py @@ -129,12 +129,17 @@ class CenetLoanPage(LoggedPage, CenetJsonPage): obj_label = CleanText(Dict('Libelle')) obj_total_amount = CleanDecimal(Dict('MontantInitial/Valeur')) obj_currency = Currency(Dict('MontantInitial/Devise')) - obj_balance = CleanDecimal(Dict('CapitalRestantDu/Valeur')) obj_type = Account.TYPE_LOAN obj_duration = CleanDecimal(Dict('Duree')) obj_rate = CleanDecimal.French(Dict('Taux')) obj_next_payment_amount = CleanDecimal(Dict('MontantProchaineEcheance/Valeur')) + def obj_balance(self): + balance = CleanDecimal(Dict('CapitalRestantDu/Valeur'))(self) + if balance > 0: + balance *= -1 + return balance + def obj_subscription_date(self): sub_date = Dict('DateDebutEffet')(self) if sub_date: diff --git a/modules/caissedepargne/pages.py b/modules/caissedepargne/pages.py index 8400c8a50be6178c2353752d1da8d39cfdad8861..b1c11113f85bc07a3ac88739df29bae27b35447e 100644 --- a/modules/caissedepargne/pages.py +++ b/modules/caissedepargne/pages.py @@ -589,6 +589,12 @@ class IndexPage(LoggedPage, HTMLPage): form.submit() + def is_history_of(self, account_id): + """ + Check whether the displayed history is for the correct account + """ + return bool(self.doc.xpath('//option[@value="%s" and @selected]' % account_id)) + def go_history(self, info, is_cbtab=False): form = self.get_form(id='main') @@ -601,6 +607,20 @@ class IndexPage(LoggedPage, HTMLPage): fix_form(form) return form.submit() + def go_history_netpro(self, info, ): + """ + On the netpro website the go_history() does not work. + Even from a web browser the site does not work, and display the history of the first account + We use a different post to go through and display the history we need + """ + form = self.get_form(id='main') + form['m_ScriptManager'] = 'MM$m_UpdatePanel|MM$HISTORIQUE_COMPTE$m_ExDropDownList' + form['MM$HISTORIQUE_COMPTE$m_ExDropDownList'] = info['id'] + form['__EVENTTARGET'] = 'MM$HISTORIQUE_COMPTE$m_ExDropDownList' + + fix_form(form) + return form.submit() + def get_form_to_detail(self, transaction): m = re.match('.*\("(.*)", "(DETAIL_OP&[\d]+).*\)\)', transaction._link) # go to detailcard page diff --git a/modules/cragr/api/browser.py b/modules/cragr/api/browser.py index 1d5ab4cbbd7176ed2181012cdc6f89ff05b9711f..ae6f7118dd47ff6d16a43a52f5b0d80fa783246e 100644 --- a/modules/cragr/api/browser.py +++ b/modules/cragr/api/browser.py @@ -208,6 +208,26 @@ class CragrAPI(LoginBrowser): form = self.page.get_login_form(self.username, keypad_password, keypad_id) return form + def get_account_iban(self, account_index, account_category, weboob_account_id): + """ + Fetch an IBAN for a given account + It may fail from time to time (error 500 or 403) + """ + params = { + 'compteIdx': int(account_index), + 'grandeFamilleCode': int(account_category), + } + try: + self.account_iban.go(params=params) + except (ClientError, ServerError): + self.logger.warning('Request to IBAN failed for account id "%s"', weboob_account_id) + return NotAvailable + + iban = self.page.get_iban() + if is_iban_valid(iban): + return iban + return NotAvailable + @need_login def check_space_connection(self, contract): # Going to a specific space often returns a 500 error @@ -278,14 +298,8 @@ class CragrAPI(LoginBrowser): loan_ids.update(self.page.get_loan_ids()) if main_account.type == Account.TYPE_CHECKING: - params = { - 'compteIdx': int(main_account._index), - 'grandeFamilleCode': 1, - } - self.account_iban.go(params=params) - iban = self.page.get_iban() - if is_iban_valid(iban): - main_account.iban = iban + main_account.iban = self.get_account_iban(main_account._index, 1, main_account.id) + if main_account.id not in all_accounts: all_accounts[main_account.id] = main_account yield main_account @@ -294,14 +308,7 @@ class CragrAPI(LoginBrowser): if empty(account.balance): account.balance = account_balances.get(account._id_element_contrat, NotAvailable) if account.type == Account.TYPE_CHECKING: - params = { - 'compteIdx': int(account._index), - 'grandeFamilleCode': int(account._category), - } - self.account_iban.go(params=params) - iban = self.page.get_iban() - if is_iban_valid(iban): - account.iban = iban + account.iban = self.get_account_iban(account._index, account._category, account.id) # Loans have a specific ID that we need to fetch # so the backend can match loans properly. @@ -319,12 +326,12 @@ class CragrAPI(LoginBrowser): # Once again, this request tends to crash often. try: self.cards.go() - except ClientError: + except (ServerError, ClientError): self.logger.warning('Request to cards failed, we try again') try: self.check_space_connection(contract) self.cards.go() - except ClientError: + except (ServerError, ClientError): self.logger.warning('Request to cards failed twice, cards of this space will be skipped.') if self.cards.is_here(): diff --git a/modules/cragr/api/pages.py b/modules/cragr/api/pages.py index aa9910681605d74544fb5be027371dd2f1581148..27cb8fd4ecd73a929f9371b2a6dd3b2e1219edfe 100644 --- a/modules/cragr/api/pages.py +++ b/modules/cragr/api/pages.py @@ -119,6 +119,7 @@ ACCOUNT_TYPES = { 'PRET PERSO': Account.TYPE_LOAN, 'P. ENTREPR': Account.TYPE_LOAN, 'P. HABITAT': Account.TYPE_LOAN, + 'P. CONV.': Account.TYPE_LOAN, 'PRET 0%': Account.TYPE_LOAN, 'INV PRO': Account.TYPE_LOAN, 'TRES. PRO': Account.TYPE_LOAN, diff --git a/modules/creditdunord/browser.py b/modules/creditdunord/browser.py index 092327b3a3682d4b00e8c9e3e2d6483079d29e2c..73e2f520fb2301c03a6a71f602da96ebc17a44cb 100644 --- a/modules/creditdunord/browser.py +++ b/modules/creditdunord/browser.py @@ -81,6 +81,9 @@ class CreditDuNordBrowser(LoginBrowser): if not self.logged: raise BrowserIncorrectPassword() + if self.page.doc.xpath('//head[title="Authentification"]/script[contains(text(), "_pageLabel=reinitialisation_mot_de_passe")]'): + raise BrowserPasswordExpired() + def _iter_accounts(self): self.loans.go(account_type=self.account_type, loans_page_label=self.loans_page_label) for a in self.page.get_list(): diff --git a/modules/creditmutuel/browser.py b/modules/creditmutuel/browser.py index f6ef29c3c207cd54d3766a2f9a0dcb03110557bf..80710eec78918cd936c823b3378f381d33d3aa1d 100644 --- a/modules/creditmutuel/browser.py +++ b/modules/creditmutuel/browser.py @@ -32,7 +32,7 @@ from weboob.browser.profiles import Wget from weboob.browser.url import URL from weboob.browser.pages import FormNotFound from weboob.browser.exceptions import ClientError, ServerError -from weboob.exceptions import BrowserIncorrectPassword, AuthMethodNotImplemented, BrowserUnavailable +from weboob.exceptions import BrowserIncorrectPassword, AuthMethodNotImplemented, BrowserUnavailable, NoAccountsException from weboob.capabilities.bank import Account, AddRecipientStep, Recipient from weboob.tools.capabilities.bank.investments import create_french_liquidity from weboob.capabilities import NotAvailable @@ -242,12 +242,14 @@ class CreditMutuelBrowser(LoginBrowser, StatesMixin): # Populate accounts from old website if not self.is_new_website: self.accounts.stay_or_go(subbank=self.currentSubBank) + has_no_account = self.page.has_no_account() self.accounts_list.extend(self.page.iter_accounts()) self.iban.go(subbank=self.currentSubBank).fill_iban(self.accounts_list) self.por.go(subbank=self.currentSubBank).add_por_accounts(self.accounts_list) # Populate accounts from new website else: self.new_accounts.stay_or_go(subbank=self.currentSubBank) + has_no_account = self.page.has_no_account() self.accounts_list.extend(self.page.iter_accounts()) self.iban.go(subbank=self.currentSubBank).fill_iban(self.accounts_list) self.por.go(subbank=self.currentSubBank).add_por_accounts(self.accounts_list) @@ -261,6 +263,8 @@ class CreditMutuelBrowser(LoginBrowser, StatesMixin): excluded_label = ['etalis', 'valorisation totale'] self.accounts_list = [acc for acc in self.accounts_list if not any(w in acc.label.lower() for w in excluded_label)] + if has_no_account and not self.accounts_list: + raise NoAccountsException(has_no_account) return self.accounts_list diff --git a/modules/creditmutuel/pages.py b/modules/creditmutuel/pages.py index b1966916ac2e8ffb87d29c53713c091e6caef973..f311e236e44367d5425b03cb57e2ed1059d20a27 100644 --- a/modules/creditmutuel/pages.py +++ b/modules/creditmutuel/pages.py @@ -35,7 +35,7 @@ from weboob.browser.filters.standard import ( ) from weboob.browser.filters.html import Link, Attr, TableCell, ColumnNotFound from weboob.exceptions import ( - BrowserIncorrectPassword, ParseError, NoAccountsException, ActionNeeded, BrowserUnavailable, + BrowserIncorrectPassword, ParseError, ActionNeeded, BrowserUnavailable, AuthMethodNotImplemented, ) from weboob.capabilities import NotAvailable @@ -356,12 +356,8 @@ class item_account_generic(ItemElement): class AccountsPage(LoggedPage, HTMLPage): - def on_load(self): - super(AccountsPage, self).on_load() - - no_account_message = CleanText('//td[contains(text(), "Votre contrat de banque à distance ne vous donne accès à aucun compte.")]')(self.doc) - if no_account_message: - raise NoAccountsException(no_account_message) + def has_no_account(self): + return CleanText('//td[contains(text(), "Votre contrat de banque à distance ne vous donne accès à aucun compte.")]')(self.doc) @method class iter_accounts(ListElement): @@ -1397,7 +1393,9 @@ class InternalTransferPage(LoggedPage, HTMLPage): 'Montant maximum autorisé au crédit pour ce compte', 'Débit interdit sur ce compte', 'Virement interdit sur compte clos', - "L'intitulé du virement ne peut contenir le ou les caractères suivants",] + "L'intitulé du virement ne peut contenir le ou les caractères suivants", + 'La date ne peut être inférieure à la date du jour. Veuillez la corriger', + ] for message in messages: if message in content: diff --git a/modules/erehsbc/module.py b/modules/erehsbc/module.py index 65c12316e19063219ab91932d8cd9fe6eced5ff6..c86c5345db4b98947dfe244edb87fa4b365e321c 100644 --- a/modules/erehsbc/module.py +++ b/modules/erehsbc/module.py @@ -38,7 +38,6 @@ class ErehsbcModule(AbstractModule, CapBank): CONFIG = BackendConfig( ValueBackendPassword('login', label='Identifiant', masked=False), ValueBackendPassword('password', label='Code secret', regexp='^(\d{6})$'), - ValueBackendPassword('secret', label=u'Réponse secrète'), Value('otp', label=u'Code de sécurité', default='', regexp='^(\d{6})$')) BROWSER = ErehsbcBrowser diff --git a/modules/ing/api/__init__.py b/modules/ing/api/__init__.py index ff74abf0bea532e1e697c0df904217c1e4105ec6..6673d4880c8eff57a0f1482f6a8f44d922984314 100644 --- a/modules/ing/api/__init__.py +++ b/modules/ing/api/__init__.py @@ -19,11 +19,11 @@ from .login import LoginPage from .accounts_page import AccountsPage, HistoryPage, ComingPage -from .transfer_page import DebitAccountsPage, CreditAccountsPage +from .transfer_page import DebitAccountsPage, CreditAccountsPage, TransferPage from .profile_page import ProfilePage __all__ = ['LoginPage', 'AccountsPage', 'HistoryPage', 'ComingPage', - 'DebitAccountsPage', 'CreditAccountsPage', + 'DebitAccountsPage', 'CreditAccountsPage', 'TransferPage', 'ProfilePage'] diff --git a/modules/ing/api/login.py b/modules/ing/api/login.py index ac99f42a91d0131269b9ceb95d81c08c2e91ce29..100a79328d703dced0b401c49124df3e99624757 100644 --- a/modules/ing/api/login.py +++ b/modules/ing/api/login.py @@ -27,10 +27,21 @@ from weboob.browser.filters.json import Dict class INGVirtKeyboard(SimpleVirtualKeyboard): + # from parent tile_margin = 3 margin = (0, 4, 0, 0) convert = 'RGB' + # for children + safe_tile_margin = 10 + small_img_size = (100, 40) + alter_img_params = { + 'radius': 2, + 'percent': 135, + 'threshold': 3, + 'limit_pixel': 160 + } + symbols = { '0': ('117b18365105224c7207d3ec0ce7516f',), '1': ('112a72c31ebdf0cdafb84e67c6e1f8f2',), @@ -50,10 +61,14 @@ class INGVirtKeyboard(SimpleVirtualKeyboard): self.original_image = self.image # create miniature of image to get more reliable hash - self.image = self.image.resize((100, 40), resample=Image.BILINEAR) + self.image = self.image.resize(self.small_img_size, resample=Image.BILINEAR) # See ImageFilter.UnsharpMask from Pillow - self.image = self.image.filter(ImageFilter.UnsharpMask(radius=2, percent=135, threshold=3)) - self.image = Image.eval(self.image, lambda px: 0 if px <= 160 else 255) + self.image = self.image.filter(ImageFilter.UnsharpMask( + radius=self.alter_img_params['radius'], + percent=self.alter_img_params['percent'], + threshold=self.alter_img_params['threshold']) + ) + self.image = Image.eval(self.image, lambda px: 0 if px <= self.alter_img_params['limit_pixel'] else 255) def password_tiles_coord(self, password): # get image original size to get password coord @@ -73,7 +88,7 @@ class INGVirtKeyboard(SimpleVirtualKeyboard): % (digit, self.path)) formatted_password = [] - safe_margin = 10 + safe_margin = self.safe_tile_margin for tile in password_tiles: # default matching_symbol is str(range(cols*rows)) x0 = (int(tile.matching_symbol) % self.cols) * tile_width @@ -101,7 +116,7 @@ class LoginPage(JsonPage): pin_position = Dict('pinPositions')(self.doc) image = BytesIO(img) - vk = INGVirtKeyboard(image, 5, 2, browser=self.browser) - password_radom_coords = vk.password_tiles_coord(password) + vk = INGVirtKeyboard(image, cols=5, rows=2, browser=self.browser) + password_random_coords = vk.password_tiles_coord(password) # pin positions (website side) start at 1, our positions start at 0 - return [password_radom_coords[index-1] for index in pin_position] + return [password_random_coords[index-1] for index in pin_position] diff --git a/modules/ing/api/transfer_page.py b/modules/ing/api/transfer_page.py index 47ee658daf5fa12d12c90a761f254bf2b290fddb..c9349bca105419f74cda2a6ae8abbd0150987223 100644 --- a/modules/ing/api/transfer_page.py +++ b/modules/ing/api/transfer_page.py @@ -20,15 +20,46 @@ from __future__ import unicode_literals from datetime import datetime +from io import BytesIO from weboob.browser.pages import LoggedPage, JsonPage from weboob.browser.elements import method, DictElement, ItemElement from weboob.browser.filters.json import Dict -from weboob.browser.filters.standard import ( - Env, Field, -) +from weboob.browser.filters.standard import Env, Field, Date from weboob.capabilities.bank import Recipient +from .login import INGVirtKeyboard + + +class TransferINGVirtKeyboard(INGVirtKeyboard): + # from grand parent + tile_margin = 5 + margin = None + convert = 'RGB' + + # from parent + safe_tile_margin = 50 + small_img_size = (125, 50) # original image size is (2420, 950) + alter_img_params = { + 'radius': 2, + 'percent': 150, + 'threshold': 3, + 'limit_pixel': 125 + } + + symbols = { + '0': 'e3e62175aa1a5ef8dc67639194caa880', + '1': '80245727e4e5f123fd64bbb1fa80dde0', + '2': '62cfc40429652190c996db741ac90830', + '3': 'bb2f87d32f688679745fe95ac31b80fd', + '4': 'a4b5e16c64817deb12ca6311cb98e59a', + '5': '56a8f3b4f068f9e2f93c4daa3a53dc17', + '6': 'b50f7e4a375153b9f6b029dc9b0a7e64', + '7': 'd52320c62c6157d0cadbb7a186153628', + '8': 'dd3fb25fc7f0765610b0ffe47da85330', + '9': 'ca55399a5b36da3fedcd1dbb73d72a2f' + } + class DebitAccountsPage(LoggedPage, JsonPage): def get_debit_accounts_uid(self): @@ -59,3 +90,26 @@ class CreditAccountsPage(LoggedPage, JsonPage): if Field('_is_internal_recipient')(self): return 'Interne' return 'Externe' + + +class TransferPage(LoggedPage, JsonPage): + @property + def suggested_date(self): + return Date(Dict('pinValidateResponse/executionSuggestedDate'), dayfirst=True)(self.doc) + + def get_password_coord(self, password): + assert Dict('pinValidateResponse', default=None)(self.doc), "Transfer virtualkeyboard position has failed" + + pin_position = Dict('pinValidateResponse/pinPositions')(self.doc) + + image_url = '/secure/api-v1%s' % Dict('pinValidateResponse/keyPadUrl')(self.doc) + image = BytesIO(self.browser.open(image_url, headers={'Referer': self.browser.absurl('/secure/transfers/new')}).content) + + vk = TransferINGVirtKeyboard(image, cols=5, rows=2, browser=self.browser) + password_random_coords = vk.password_tiles_coord(password) + # pin positions (website side) start at 1, our positions start at 0 + return [password_random_coords[index-1] for index in pin_position] + + @property + def transfer_is_validated(self): + return Dict('acknowledged')(self.doc) diff --git a/modules/ing/api_browser.py b/modules/ing/api_browser.py index 942c3b0a063e61bae9170102ca7bd877defce3c8..2954794091ac4b3301be537f42ecdec3c63136c8 100644 --- a/modules/ing/api_browser.py +++ b/modules/ing/api_browser.py @@ -26,10 +26,11 @@ from functools import wraps from weboob.browser import LoginBrowser, URL from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded from weboob.browser.exceptions import ClientError +from weboob.capabilities.bank import TransferBankError, TransferInvalidAmount from .api import ( LoginPage, AccountsPage, HistoryPage, ComingPage, - DebitAccountsPage, CreditAccountsPage, + DebitAccountsPage, CreditAccountsPage, TransferPage, ProfilePage, ) from .web import StopPage, ActionNeededPage @@ -104,6 +105,8 @@ class IngAPIBrowser(LoginBrowser): # transfer credit_accounts = URL(r'/secure/api-v1/transfers/debitAccounts/(?P.*)/creditAccounts', CreditAccountsPage) debit_accounts = URL(r'/secure/api-v1/transfers/debitAccounts', DebitAccountsPage) + init_transfer_page = URL(r'/secure/api-v1/transfers/v2/new/validate', TransferPage) + exec_transfer_page = URL(r'/secure/api-v1/transfers/v2/new/execute/pin', TransferPage) # profile informations = URL(r'/secure/api-v1/customer/info', ProfilePage) @@ -113,13 +116,14 @@ class IngAPIBrowser(LoginBrowser): super(IngAPIBrowser, self).__init__(*args, **kwargs) self.old_browser = IngBrowser(*args, **kwargs) + self.transfer_data = None def handle_login_error(self, r): error_page = r.response.json() assert 'error' in error_page, "Something went wrong in login" error = error_page['error'] - if error['code'] == 'AUTHENTICATION.INVALID_PIN_CODE': + if error['code'] in ('AUTHENTICATION.INVALID_PIN_CODE', 'AUTHENTICATION.INVALID_CIF_AND_BIRTHDATE_COMBINATION'): raise BrowserIncorrectPassword(error['message']) elif error['code'] in ('AUTHENTICATION.ACCOUNT_INACTIVE', 'AUTHENTICATION.ACCOUNT_LOCKED', 'AUTHENTICATION.NO_COMPLETE_ACCOUNT_FOUND'): @@ -314,13 +318,55 @@ class IngAPIBrowser(LoginBrowser): for recipient in self.page.iter_recipients(acc_uid=account._uid): yield recipient + def handle_transfer_errors(self, r): + error_page = r.response.json() + assert 'error' in error_page, "Something went wrong, transfer is not created" + + error = error_page['error'] + error_msg = error['message'] + + if error['code'] == 'TRANSFER.INVALID_AMOUNT_MINIMUM': + raise TransferInvalidAmount(message=error_msg) + elif error['code'] == 'INPUT_INVALID' and len(error['values']): + for value in error['values']: + error_msg = '%s %s %s.' % (error_msg, value, error['values'][value]) + + raise TransferBankError(message=error_msg) + + @need_to_be_on_website('api') @need_login def init_transfer(self, account, recipient, transfer): - raise NotImplementedError() + data = { + 'amount': transfer.amount, + 'executionDate': transfer.exec_date.strftime('%Y-%m-%d'), + 'keyPadSize': {'width': 3800, 'height': 1520}, + 'label': transfer.label, + 'fromAccount': account._uid, + 'toAccount': recipient.id + } + try: + self.init_transfer_page.go(json=data, headers={'Referer': self.absurl('/secure/transfers/new')}) + except ClientError as e: + self.handle_transfer_errors(e) + + assert self.page.suggested_date == transfer.exec_date, "Transfer date is not valid" + self.transfer_data = data + self.transfer_data.pop('keyPadSize') + self.transfer_data['clickPositions'] = self.page.get_password_coord(self.password) + return transfer + + @need_to_be_on_website('api') @need_login def execute_transfer(self, transfer): - raise NotImplementedError() + headers = { + 'Referer': self.absurl('/secure/transfers/new'), + 'Accept': 'application/json, text/plain, */*' + } + self.exec_transfer_page.go(json=self.transfer_data, headers=headers) + + assert self.page.transfer_is_validated, "Transfer is not validated" + return transfer ############# CapDocument ############# @need_login diff --git a/modules/ing/browser.py b/modules/ing/browser.py index 55ef197d5455c2a39b3e114c8c09aab25caea091..7da9dcee400e08fddb778ce6072dc0aec2bf44bc 100644 --- a/modules/ing/browser.py +++ b/modules/ing/browser.py @@ -23,6 +23,7 @@ import hashlib import time import json +from decimal import Decimal from requests.exceptions import SSLError from weboob.browser import LoginBrowser, URL, need_login @@ -179,6 +180,11 @@ class IngBrowser(LoginBrowser): self.accountspage.go() self.where = 'start' + if account.balance == Decimal('0'): + # some market accounts link with null balance redirect to logout page + # avoid it because it can crash iter accounts + return + self.change_space(account._space) data = self.get_investments_data(account) diff --git a/modules/ing/module.py b/modules/ing/module.py index 7595d0075cf4d9f52a3a802d6a108030f03cb1a8..38a21e153c0f464dd4221c01b4852838bc8dc042 100644 --- a/modules/ing/module.py +++ b/modules/ing/module.py @@ -19,13 +19,15 @@ from __future__ import unicode_literals -from weboob.capabilities.bank import CapBankWealth, CapBankTransfer, Account, AccountNotFound +from decimal import Decimal + +from weboob.capabilities.bank import CapBankWealth, CapBankTransfer, Account, AccountNotFound, RecipientNotFound from weboob.capabilities.bill import ( CapDocument, Bill, Subscription, SubscriptionNotFound, DocumentNotFound, DocumentTypes, ) from weboob.capabilities.profile import CapProfile -from weboob.capabilities.base import find_object +from weboob.capabilities.base import find_object, strict_find_object from weboob.tools.backend import Module, BackendConfig from weboob.tools.value import ValueBackendPassword, ValueDate @@ -99,10 +101,18 @@ class INGModule(Module, CapBankWealth, CapBankTransfer, CapDocument, CapProfile) return self.browser.iter_recipients(account) def init_transfer(self, transfer, **params): - raise NotImplementedError() + self.logger.info('Going to do a new transfer') + + account = strict_find_object(self.iter_accounts(), id=transfer.account_id, error=AccountNotFound) + + recipient = strict_find_object(self.iter_transfer_recipients(account), id=transfer.recipient_id, error=RecipientNotFound) + + transfer.amount = Decimal(transfer.amount).quantize(Decimal('.01')) + + return self.browser.init_transfer(account, recipient, transfer) def execute_transfer(self, transfer, **params): - raise NotImplementedError() + return self.browser.execute_transfer(transfer) ############# CapDocument ############# def iter_subscription(self): diff --git a/modules/paypal/__init__.py b/modules/paypal/__init__.py index 02de401d60cf8e24ca4c93623c0c95fd815f199f..d6ce232b1dbe1a35a3fd3adc4ab62a948ee4e78e 100644 --- a/modules/paypal/__init__.py +++ b/modules/paypal/__init__.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . diff --git a/modules/paypal/browser.py b/modules/paypal/browser.py index 1a6b0aa65dfc523552fdcf1562aa40a19b61023f..da59fbe01fa8c2a5366204459b34523ec8205edb 100644 --- a/modules/paypal/browser.py +++ b/modules/paypal/browser.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . diff --git a/modules/paypal/module.py b/modules/paypal/module.py index bfed6e7f01441e2834543e9baaa56c5bd7b51c07..df3a4e2fcc615621b9d477326bf14d6c53bd64d7 100644 --- a/modules/paypal/module.py +++ b/modules/paypal/module.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . @@ -33,7 +33,7 @@ class PaypalModule(Module, CapBank): MAINTAINER = u'Laurent Bachelier' EMAIL = 'laurent@bachelier.name' VERSION = '1.6' - LICENSE = 'AGPLv3+' + LICENSE = 'LGPLv3+' DESCRIPTION = u'PayPal' CONFIG = BackendConfig(ValueBackendPassword('login', label='E-mail', masked=False), ValueBackendPassword('password', label='Password')) diff --git a/modules/paypal/pages.py b/modules/paypal/pages.py index e79bc7517e8e0334de6b6e7c735bf49a34f5bbce..00bb95426c01cb9aeca405025e11ef087d12e078 100644 --- a/modules/paypal/pages.py +++ b/modules/paypal/pages.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from ast import literal_eval diff --git a/modules/paypal/test.py b/modules/paypal/test.py index d407fb26838c7fd2444af5ef3784693ce500d223..5bea3fdb80b892db7a5c8f70bcabd31a6fe69656 100644 --- a/modules/paypal/test.py +++ b/modules/paypal/test.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from weboob.tools.test import BackendTest diff --git a/modules/s2e/browser.py b/modules/s2e/browser.py index 46bf64f007a70fdf547d8ee50062febc94bf98bc..4ab71dcc0bd3a9941206cb4df3bc8e40a14cd5fb 100644 --- a/modules/s2e/browser.py +++ b/modules/s2e/browser.py @@ -161,13 +161,6 @@ class ErehsbcBrowser(S2eBrowser): SLUG = 'hsbc' LANG = 'fr' # ['fr', 'en'] - def __init__(self, *args, **kwargs): - super(ErehsbcBrowser, self).__init__(*args, **kwargs) - # don't remove "secret" from config, it might still be useful to keep it in config. - # as of writing (2018/01/30), it seems the web doesn't ask for it. - # even worse: when passing it, we're asked for otp everytime, so don't pass it - self.secret = None - class BnppereBrowser(S2eBrowser): BASEURL = 'https://personeo.epargne-retraite-entreprises.bnpparibas.com' diff --git a/modules/societegenerale/__init__.py b/modules/societegenerale/__init__.py index ca5b81b80f58144b1e7b210e925d0a249e730583..a8ffbf6af4ae2b4f2a84b83d8d25302a77240619 100644 --- a/modules/societegenerale/__init__.py +++ b/modules/societegenerale/__init__.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . diff --git a/modules/societegenerale/browser.py b/modules/societegenerale/browser.py index e6b600a15c6d02273c2716ebc91c2fbeeb12157a..67ffaf2afb4b4dc3f495d0f7c78a169661fe9ad1 100644 --- a/modules/societegenerale/browser.py +++ b/modules/societegenerale/browser.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from __future__ import unicode_literals @@ -34,7 +34,7 @@ from weboob.tools.value import Value, ValueBool from .pages.accounts_list import ( AccountsMainPage, AccountDetailsPage, AccountsPage, LoansPage, HistoryPage, CardHistoryPage, PeaLiquidityPage, AccountsSynthesesPage, - AdvisorPage, HTMLProfilePage, CreditPage, CreditHistoryPage, + AdvisorPage, HTMLProfilePage, CreditPage, CreditHistoryPage, OldHistoryPage, MarketPage, LifeInsurance, LifeInsuranceHistory, LifeInsuranceInvest, LifeInsuranceInvest2, UnavailableServicePage, ) @@ -57,10 +57,15 @@ class SocieteGenerale(LoginBrowser, StatesMixin): accounts = URL(r'/icd/cbo/data/liste-prestations-navigation-authsec.json', AccountsPage) accounts_syntheses = URL(r'/icd/cbo/data/liste-prestations-authsec.json\?n10_avecMontant=1', AccountsSynthesesPage) history = URL(r'/icd/cbo/data/liste-operations-authsec.json', HistoryPage) - card_history = URL(r'/restitution/cns_listeReleveCarteDd.xml', CardHistoryPage) loans = URL(r'/abm/restit/listeRestitutionPretsNET.json\?a100_isPretConso=(?P\w+)', LoansPage) + + card_history = URL(r'/restitution/cns_listeReleveCarteDd.xml', CardHistoryPage) credit = URL(r'/restitution/cns_detailAVPAT.html', CreditPage) credit_history = URL(r'/restitution/cns_listeEcrCav.xml', CreditHistoryPage) + old_hist_page = URL(r'/restitution/cns_detailPep.html', + r'/restitution/cns_listeEcrPep.html', + r'/restitution/cns_detailAlterna.html', + r'/restitution/cns_listeEncoursAlterna.html', OldHistoryPage) # Recipient add_recipient = URL(r'/personnalisation/per_cptBen_ajouterFrBic.html', @@ -223,28 +228,22 @@ class SocieteGenerale(LoginBrowser, StatesMixin): if not account._internal_id: raise BrowserUnavailable() - if account.type in (account.TYPE_LIFE_INSURANCE, account.TYPE_PERP, ): - # request to get json is not available yet, old request to get html response + # get history for account on old website + # request to get json is not available yet, old request to get html response + if any(( + account.type in (account.TYPE_LIFE_INSURANCE, account.TYPE_PERP), + account.type == account.TYPE_REVOLVING_CREDIT and account._loan_type != 'PR_CONSO', + account.type in (account.TYPE_REVOLVING_CREDIT, account.TYPE_SAVINGS) and not account._is_json_histo + )): self.account_details_page.go(params={'idprest': account._prestation_id}) - link = self.page.get_history_link() - if link: - self.location(self.absurl(link)) - for tr in self.page.iter_li_history(): - yield tr - return + history_url = self.page.get_history_url() + assert history_url + self.location(self.absurl(history_url)) - if account.type == account.TYPE_REVOLVING_CREDIT and account._loan_type != 'PR_CONSO': - # request to get json is not available yet, old request to get html response - self.account_details_page.go(params={'idprest': account._prestation_id}) - self.page.go_history_page() - for tr in self.page.iter_credit_history(): + for tr in self.page.iter_history(): yield tr return - if account.type == account.TYPE_REVOLVING_CREDIT and not account._is_json_histo: - # Waiting for account with transactions - return - if account.type == account.TYPE_CARD: self.history.go(params={'b64e200_prestationIdTechnique': account.parent._internal_id}) for summary_card_tr in self.page.iter_card_transactions(card_number=account.number): @@ -269,6 +268,10 @@ class SocieteGenerale(LoginBrowser, StatesMixin): if not account._internal_id: raise BrowserUnavailable() + if account.type == account.TYPE_SAVINGS and not account._is_json_histo: + # Waiting for account with transactions + return + internal_id = account._internal_id if account.type == account.TYPE_CARD: internal_id = account.parent._internal_id diff --git a/modules/societegenerale/captcha.py b/modules/societegenerale/captcha.py index e4e6a95e875c6a1675d21a4d1947c78dc4424438..8289b9a438aef656ed96078765cf40de419b4eec 100644 --- a/modules/societegenerale/captcha.py +++ b/modules/societegenerale/captcha.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . import hashlib diff --git a/modules/societegenerale/module.py b/modules/societegenerale/module.py index d041b1539427ba76b526109006c0905a7ff1ddb2..13397eef2878d8cf37a71a6ca46e30ef8dcd3183 100644 --- a/modules/societegenerale/module.py +++ b/modules/societegenerale/module.py @@ -6,16 +6,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . import re @@ -49,7 +49,7 @@ class SocieteGeneraleModule(Module, CapBankWealth, CapBankTransferAddRecipient, MAINTAINER = u'Jocelyn Jaubert' EMAIL = 'jocelyn.jaubert@gmail.com' VERSION = '1.6' - LICENSE = 'AGPLv3+' + LICENSE = 'LGPLv3+' DESCRIPTION = u'Société Générale' CONFIG = BackendConfig( ValueBackendPassword('login', label='Code client', masked=False), diff --git a/modules/societegenerale/pages/accounts_list.py b/modules/societegenerale/pages/accounts_list.py index 2b788a741461f8f2cc3006a17715de10e48149b8..0028a6fd5b1d6eaa025659bb588a1d6414cd1cfb 100644 --- a/modules/societegenerale/pages/accounts_list.py +++ b/modules/societegenerale/pages/accounts_list.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from __future__ import unicode_literals @@ -120,6 +120,9 @@ class AccountsPage(JsonBasePage): item_xpath = 'donnees' class item(ItemElement): + def condition(self): + return Dict('etatPrestation')(self) != 'INDISPONIBLE' + klass = Account # There are more account type to find @@ -129,11 +132,13 @@ class AccountsPage(JsonBasePage): 'CEL': Account.TYPE_SAVINGS, 'LDD': Account.TYPE_SAVINGS, 'LIVRETA': Account.TYPE_SAVINGS, + 'SOGEMONDE': Account.TYPE_SAVINGS, 'LIVRET_JEUNE': Account.TYPE_SAVINGS, 'LIVRET_EUROKID': Account.TYPE_SAVINGS, 'COMPTE_SUR_LIVRET': Account.TYPE_SAVINGS, 'LIVRET_EPARGNE_PLUS': Account.TYPE_SAVINGS, 'PLAN_EPARGNE_BANCAIRE': Account.TYPE_SAVINGS, + 'PLAN_EPARGNE_POPULAIRE': Account.TYPE_SAVINGS, 'LIVRET_EPARGNE_POPULAIRE': Account.TYPE_SAVINGS, 'BANQUE_FRANCAISE_MUTUALISEE': Account.TYPE_SAVINGS, 'PRET_GENERAL': Account.TYPE_LOAN, @@ -185,6 +190,10 @@ class AccountsPage(JsonBasePage): if Field('type')(self) == Account.TYPE_REVOLVING_CREDIT and \ not Dict('produit')(self) in ('COMPTE_ALTERNA', 'AVANCE_PATRIMOINE'): return True + # PLAN_EPARGNE_POPULAIRE account type history is not in json yet + if Field('type')(self) == Account.TYPE_SAVINGS and \ + not Dict('produit')(self) in ('PLAN_EPARGNE_POPULAIRE', ): + return True class AccountsSynthesesPage(JsonBasePage): def is_new_website_available(self): @@ -497,11 +506,11 @@ class CardHistoryPage(LoggedPage, HTMLPage): class CreditPage(LoggedPage, HTMLPage): - def go_history_page(self): + def get_history_url(self): redirection_script = CleanText('//script[contains(text(), "setPrestationURL")]')(self.doc) history_link = re.search(r'setPrestationURL\("(.*)"\)', redirection_script) if history_link: - self.browser.location(self.browser.absurl(history_link.group(1))) + return history_link.group(1) class CreditHistoryPage(LoggedPage, HTMLPage): @@ -512,7 +521,7 @@ class CreditHistoryPage(LoggedPage, HTMLPage): return super(CreditHistoryPage, self).build_doc(content) @method - class iter_credit_history(ListElement): + class iter_history(ListElement): item_xpath = '//tr' class item(ItemElement): @@ -528,6 +537,24 @@ class CreditHistoryPage(LoggedPage, HTMLPage): return MyDecimal(CleanText('./td[contains(@headers, "Debit")]', replace=[(' ', '')]))(self) +class OldHistoryPage(LoggedPage, HTMLPage): + def get_history_url(self): + redirection = CleanText('//body/@onload')(self.doc) + history_link = re.search(r",'(/.*)',", redirection) + if history_link: + return history_link.group(1) + + def iter_history(self): + is_no_transaction_msg = any(( + self.doc.xpath(u'//div[contains(text(), "Aucune opération trouvée sur la période de restitution possible")]'), + self.doc.xpath(u'//div[contains(text(), "Aucune opération n\'a été réalisée depuis le dernier relevé")]'), + )) + assert is_no_transaction_msg, 'There are transactions, retrieve them !' + + # waiting for account with history + return [] + + class LifeInsurance(LoggedPage, HTMLPage): def on_load(self): errors_msg = ( @@ -545,8 +572,9 @@ class LifeInsurance(LoggedPage, HTMLPage): def has_link(self): return Link('//a[@href="asvcns20a.html"]', default=NotAvailable)(self.doc) - def get_history_link(self): - return Link('//a[img[@alt="Suivi des opérations"]]', default=NotAvailable)(self.doc) + def get_history_url(self): + history_url = Link('//a[img[@alt="Suivi des opérations"]]', default=NotAvailable)(self.doc) + return history_url def get_pages(self): pages = CleanText('//div[@class="net2g_asv_tableau_pager"]')(self.doc) @@ -621,7 +649,7 @@ class LifeInsuranceInvest2(LifeInsuranceInvest): class LifeInsuranceHistory(LifeInsurance): @pagination @method - class iter_li_history(TableElement): + class iter_history(TableElement): def next_page(self): return self.page.li_pagination() diff --git a/modules/societegenerale/pages/base.py b/modules/societegenerale/pages/base.py index 3f24cda83d180def8268a33bb99125b977f08915..2fcaa3aef13420a3728aaed17ea6b8978dfc86c7 100644 --- a/modules/societegenerale/pages/base.py +++ b/modules/societegenerale/pages/base.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . diff --git a/modules/societegenerale/pages/login.py b/modules/societegenerale/pages/login.py index 141b46c88ac82fdc8874839865718cbbcc913370..b137d295a5f34c389d6b220c03c49ea65d211f6b 100644 --- a/modules/societegenerale/pages/login.py +++ b/modules/societegenerale/pages/login.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . diff --git a/modules/societegenerale/pages/subscription.py b/modules/societegenerale/pages/subscription.py index 352df1d276fa6e2c7e751bd390542387a178ffed..c8844dd3f76d453b937fc3d4706a0057609350f1 100644 --- a/modules/societegenerale/pages/subscription.py +++ b/modules/societegenerale/pages/subscription.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from __future__ import unicode_literals diff --git a/modules/societegenerale/pages/transfer.py b/modules/societegenerale/pages/transfer.py index 04c479e8cdd88cbac186711c8354120c220c26cc..c724c5a3993ec7dec7645b6aa541a2f1addc11a0 100644 --- a/modules/societegenerale/pages/transfer.py +++ b/modules/societegenerale/pages/transfer.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from datetime import datetime diff --git a/modules/societegenerale/sgpe/browser.py b/modules/societegenerale/sgpe/browser.py index 7bcc955a5e0bdf26e4c856e2e440c3c55e307ef5..9ab2a40b954fd3f13d5d5fc22b373e72c9ce1c0e 100644 --- a/modules/societegenerale/sgpe/browser.py +++ b/modules/societegenerale/sgpe/browser.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from __future__ import unicode_literals diff --git a/modules/societegenerale/sgpe/json_pages.py b/modules/societegenerale/sgpe/json_pages.py index d441c5fcfb4800a45ed68600f0fd158487fa53b1..ce9d67f7dfefe14fe653d1d7299b3d59c59721c2 100644 --- a/modules/societegenerale/sgpe/json_pages.py +++ b/modules/societegenerale/sgpe/json_pages.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . import requests diff --git a/modules/societegenerale/sgpe/pages.py b/modules/societegenerale/sgpe/pages.py index 38cfc1c7629226deea49e5c4d62aadeea552c3fe..df869fbc00be17fde1994f735ca9510060f4fc5e 100644 --- a/modules/societegenerale/sgpe/pages.py +++ b/modules/societegenerale/sgpe/pages.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from __future__ import unicode_literals diff --git a/modules/societegenerale/sgpe/transfer_pages.py b/modules/societegenerale/sgpe/transfer_pages.py index 10e4cb295beb1f7f6bacdbe80d69e301980b51ed..ac4052a2c110e41116c5167446bb856bc6b7d11b 100644 --- a/modules/societegenerale/sgpe/transfer_pages.py +++ b/modules/societegenerale/sgpe/transfer_pages.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . from __future__ import unicode_literals diff --git a/modules/societegenerale/test.py b/modules/societegenerale/test.py index b642899b8a7279ef111e341244c8481569ffa366..c83ea4ce3de8951e4aa230e0cdcc0b2827a98235 100644 --- a/modules/societegenerale/test.py +++ b/modules/societegenerale/test.py @@ -5,16 +5,16 @@ # This file is part of a weboob module. # # This weboob module is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This weboob module is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Affero General Public License +# You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see .