From 8e82bfc439f15defcf99dc99ebc67ed4ecf05330 Mon Sep 17 00:00:00 2001 From: Sylvie Ye Date: Wed, 6 Mar 2019 17:30:48 +0100 Subject: [PATCH] [ing] handle API website create separate browser and page for API website API website is not working for now (can only retrieve checking and card accounts information), Redirect to old browser after new website login Also fix multispace redirect --- modules/ing/api/__init__.py | 23 +++ modules/ing/api/login.py | 111 +++++++++++++ modules/ing/api_browser.py | 164 ++++++++++++++++++++ modules/ing/browser.py | 27 +--- modules/ing/module.py | 4 +- modules/ing/pages/login.py | 144 ----------------- modules/ing/{pages => web}/__init__.py | 4 +- modules/ing/{pages => web}/accounts_list.py | 7 +- modules/ing/{pages => web}/bills.py | 0 modules/ing/web/login.py | 47 ++++++ modules/ing/{pages => web}/titre.py | 0 modules/ing/{pages => web}/transfer.py | 2 +- 12 files changed, 363 insertions(+), 170 deletions(-) create mode 100644 modules/ing/api/__init__.py create mode 100644 modules/ing/api/login.py create mode 100644 modules/ing/api_browser.py delete mode 100644 modules/ing/pages/login.py rename modules/ing/{pages => web}/__init__.py (91%) rename modules/ing/{pages => web}/accounts_list.py (98%) rename modules/ing/{pages => web}/bills.py (100%) create mode 100644 modules/ing/web/login.py rename modules/ing/{pages => web}/titre.py (100%) rename modules/ing/{pages => web}/transfer.py (99%) diff --git a/modules/ing/api/__init__.py b/modules/ing/api/__init__.py new file mode 100644 index 0000000000..64d0d9c9de --- /dev/null +++ b/modules/ing/api/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2019 Sylvie Ye +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + +from .login import LoginPage + + +__all__ = ['LoginPage', ] diff --git a/modules/ing/api/login.py b/modules/ing/api/login.py new file mode 100644 index 0000000000..0064828b08 --- /dev/null +++ b/modules/ing/api/login.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2019 Sylvie Ye +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + +from io import BytesIO +from PIL import Image, ImageFilter +import random + +from weboob.tools.captcha.virtkeyboard import SimpleVirtualKeyboard +from weboob.browser.pages import JsonPage +from weboob.browser.filters.json import Dict + + +class INGVirtKeyboard(SimpleVirtualKeyboard): + tile_margin = 3 + margin = (0, 4, 0, 0) + convert = 'RGB' + + symbols = { + '0': ('117b18365105224c7207d3ec0ce7516f',), + '1': ('112a72c31ebdf0cdafb84e67c6e1f8f2',), + '2': ('df8534cb28a19e600976d39af2c4f6fe',), + '3': ('911dbe595604da336fbdd360f89bada1',), + '4': ('8a22058801980e4afb25c414e388bfa8',), + '5': ('c7d430083b55fbe2834c912c7cded124',), + '6': ('64f8b9f3a93bc534443646f0b54e26ad',), + '7': ('6c14303e9bffdcd1880ce415b6f0efb2',), + '8': ('a62e9e25b047160090de1634c8d3b0f6',), + '9': ('2b9bc97ce4ccc67d4ae0c3ca54957b33', 'afc9d2840290b7da08bf1d0b27b6c302'), + } + + # Clean image + def alter_image(self): + # original image size is (484, 190), save the original image + self.original_image = self.image + + # create miniature of image to get more reliable hash + self.image = self.image.resize((100, 40), 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) + + def password_tiles_coord(self, password): + # get image original size to get password coord + image_width, image_height = self.original_image.size + tile_width, tile_height = image_width // self.cols, image_height // self.rows + + password_tiles = [] + for digit in password: + for tile in self.tiles: + if tile.md5 in self.symbols[digit]: + password_tiles.append(tile) + break + else: + # Dump file only when the symbol is not found + self.dump_tiles(self.path) + raise Exception("Symbol '%s' not found; all symbol hashes are available in %s" + % (digit, self.path)) + + formatted_password = [] + safe_margin = 10 + for tile in password_tiles: + # default matching_symbol is str(range(cols*rows)) + x0 = (int(tile.matching_symbol) % self.cols) * tile_width + y0 = (int(tile.matching_symbol) // self.cols) * tile_height + tile_original_coords = ( + x0 + safe_margin, y0 + safe_margin, + x0 + tile_width - safe_margin, y0 + tile_height - safe_margin, + ) + formatted_password.append([ + random.uniform(tile_original_coords[0], tile_original_coords[2]), + random.uniform(tile_original_coords[1], tile_original_coords[3]), + ]) + return formatted_password + + +class LoginPage(JsonPage): + @property + def is_logged(self): + return 'firstName' in self.doc + + def get_password_coord(self, img, password): + assert 'pinPositions' in self.doc, 'Virtualkeyboard position has failed' + assert 'keyPadUrl' in self.doc, 'Virtualkeyboard image url is missing' + + pin_position = Dict('pinPositions')(self.doc) + image = BytesIO(img) + + vk = INGVirtKeyboard(image, 5, 2) + password_radom_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] + + def get_error(self): + if 'error' in self.doc: + return (Dict('error/code')(self.doc), Dict('error/message')(self.doc)) diff --git a/modules/ing/api_browser.py b/modules/ing/api_browser.py new file mode 100644 index 0000000000..91a2416a15 --- /dev/null +++ b/modules/ing/api_browser.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2019 Sylvie Ye +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . +from __future__ import unicode_literals + + +from collections import OrderedDict + +from weboob.browser import LoginBrowser, URL, need_login +from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from weboob.browser.exceptions import ClientError + +from .api import LoginPage +from .web import StopPage, ActionNeededPage + +from .browser import IngBrowser + + +class IngAPIBrowser(LoginBrowser): + BASEURL = 'https://m.ing.fr' + + # Login + context = URL(r'/secure/api-v1/session/context') + login = URL(r'/secure/api-v1/login/cif', LoginPage) + keypad = URL(r'/secure/api-v1/login/keypad', LoginPage) + pin_page = URL(r'/secure/api-v1/login/pin', LoginPage) + + # Error on old website + errorpage = URL(r'https://secure.ing.fr/.*displayCoordonneesCommand.*', StopPage) + actioneeded = URL(r'https://secure.ing.fr/general\?command=displayTRAlertMessage', + r'https://secure.ing.fr/protected/pages/common/eco1/moveMoneyForbidden.jsf', ActionNeededPage) + + def __init__(self, *args, **kwargs): + self.birthday = kwargs.pop('birthday') + super(IngAPIBrowser, self).__init__(*args, **kwargs) + + self.old_browser = IngBrowser(*args, **kwargs) + + def do_login(self): + assert self.password.isdigit() + assert self.birthday.isdigit() + + # login on new website + # update cookies + self.context.go() + + data = OrderedDict([ + ('birthDate', self.birthday), + ('cif', self.username), + ]) + self.login.go(json=data) + + data = '{"keyPadSize":{"width":3800,"height":1520},"mode":""}' + self.keypad.go(data=data, headers={'Content-Type': 'application/json'}) + + img = self.open('/secure/api-v1/keypad/newkeypad.png').content + data = { + 'clickPositions': self.page.get_password_coord(img, self.password) + } + + try: + self.pin_page.go(json=data, headers={'Referer': 'https://m.ing.fr/secure/login/pin'}) + except ClientError: + # handle error later + pass + + error = self.page.get_error() + if not self.page.is_logged: + assert error + if error[0] == 'AUTHENTICATION.INVALID_PIN_CODE': + raise BrowserIncorrectPassword(error[1]) + assert error[0] != 'INPUT_INVALID', '%s' % error[1] + raise BrowserUnavailable(error[1]) + + self.auth_token = self.page.response.headers['Ingdf-Auth-Token'] + self.session.headers['Ingdf-Auth-Token'] = self.auth_token + self.session.cookies['ingdfAuthToken'] = self.auth_token + + # Go on old website because new website is not stable + self.redirect_to_old_browser() + + def redirect_to_old_browser(self): + token = self.location( + 'https://m.ing.fr/secure/api-v1/sso/exit?context={"originatingApplication":"SECUREUI"}&targetSystem=INTERNET', + method='POST' + ).content + data = { + 'token': token, + 'next': 'protected/pages/index.jsf', + 'redirectUrl': 'protected/pages/index.jsf', + 'targetApplication': 'INTERNET', + 'accountNumber': 'undefined' + } + self.session.cookies['produitsoffres'] = 'comptes' + self.location('https://secure.ing.fr', data=data, headers={'Referer': 'https://secure.ing.fr'}) + self.old_browser.session.cookies.update(self.session.cookies) + + def deinit(self): + super(IngAPIBrowser, self).deinit() + self.old_browser.deinit() + + @need_login + def get_accounts_list(self): + return self.old_browser.get_accounts_list() + + @need_login + def get_account(self, _id): + raise BrowserUnavailable() + + @need_login + def get_coming(self, account): + raise BrowserUnavailable() + + @need_login + def get_history(self, account): + raise BrowserUnavailable() + + @need_login + def iter_recipients(self, account): + raise BrowserUnavailable() + + @need_login + def init_transfer(self, account, recipient, transfer): + raise BrowserUnavailable() + + @need_login + def execute_transfer(self, transfer): + raise BrowserUnavailable() + + @need_login + def get_investments(self, account): + raise BrowserUnavailable() + + ############# CapDocument ############# + @need_login + def get_subscriptions(self): + raise BrowserUnavailable() + + @need_login + def get_documents(self, subscription): + raise BrowserUnavailable() + + def download_document(self, bill): + raise BrowserUnavailable() + + ############# CapProfile ############# + @need_login + def get_profile(self): + raise BrowserUnavailable() diff --git a/modules/ing/browser.py b/modules/ing/browser.py index f2a06e8ec1..ef94f81291 100644 --- a/modules/ing/browser.py +++ b/modules/ing/browser.py @@ -26,14 +26,14 @@ from requests.exceptions import SSLError from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from weboob.exceptions import BrowserUnavailable from weboob.browser.exceptions import ServerError from weboob.capabilities.bank import Account, AccountNotFound from weboob.capabilities.base import find_object, NotAvailable from weboob.tools.capabilities.bank.transactions import FrenchTransaction -from .pages import ( - AccountsList, LoginPage, NetissimaPage, TitrePage, +from .web import ( + AccountsList, NetissimaPage, TitrePage, TitreHistory, TransferPage, BillsPage, StopPage, TitreDetails, TitreValuePage, ASVHistory, ASVInvest, DetailFondsPage, IbanPage, ActionNeededPage, ReturnPage, ProfilePage, LoanTokenPage, LoanDetailPage, @@ -75,7 +75,6 @@ class IngBrowser(LoginBrowser): lifeback = URL(r'https://ingdirectvie.ing.fr/b2b2c/entreesite/EntAccExit', ReturnPage) # Login and error - loginpage = URL(r'/public/displayLogin.jsf.*', LoginPage) errorpage = URL(r'.*displayCoordonneesCommand.*', StopPage) actioneeded = URL(r'/general\?command=displayTRAlertMessage', r'/protected/pages/common/eco1/moveMoneyForbidden.jsf', ActionNeededPage) @@ -108,7 +107,6 @@ class IngBrowser(LoginBrowser): __states__ = ['where'] def __init__(self, *args, **kwargs): - self.birthday = kwargs.pop('birthday') self.where = None LoginBrowser.__init__(self, *args, **kwargs) self.cache = {} @@ -126,24 +124,14 @@ def __init__(self, *args, **kwargs): self.current_subscription = None def do_login(self): - assert self.password.isdigit() - assert self.birthday.isdigit() - - self.do_logout() - self.loginpage.go() - - self.page.prelogin(self.username, self.birthday) - self.page.login(self.password) - if self.page.error(): - raise BrowserIncorrectPassword() - if self.errorpage.is_here(): - raise BrowserIncorrectPassword('Please login on website to fill the form and retry') - self.page.check_for_action_needed() + pass @need_login def set_multispace(self): self.where = 'start' - self.page.load_space_page() + + if not self.page.is_multispace_page(): + self.page.load_space_page() self.multispace = self.page.get_multispace() @@ -156,6 +144,7 @@ def set_multispace(self): @need_login def change_space(self, space): if self.multispace and not self.is_same_space(space, self.current_space): + self.logger.info('Change spaces') self.accountspage.go() self.where = 'start' self.page.load_space_page() diff --git a/modules/ing/module.py b/modules/ing/module.py index 63e129d58e..b6d7f24f2f 100644 --- a/modules/ing/module.py +++ b/modules/ing/module.py @@ -33,7 +33,7 @@ from weboob.tools.backend import Module, BackendConfig from weboob.tools.value import ValueBackendPassword, ValueDate -from .browser import IngBrowser +from .api_browser import IngAPIBrowser __all__ = ['INGModule'] @@ -55,7 +55,7 @@ class INGModule(Module, CapBankWealth, CapBankTransfer, CapDocument, CapProfile) label='Date de naissance', formats=('%d%m%Y', '%d/%m/%Y', '%d-%m-%Y')) ) - BROWSER = IngBrowser + BROWSER = IngAPIBrowser accepted_document_types = (DocumentTypes.STATEMENT,) diff --git a/modules/ing/pages/login.py b/modules/ing/pages/login.py deleted file mode 100644 index 3de72deff4..0000000000 --- a/modules/ing/pages/login.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright(C) 2009-2014 Florent Fourcot, Romain Bignon -# -# 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 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this weboob module. If not, see . - -from io import BytesIO - -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded -from weboob.tools.captcha.virtkeyboard import VirtKeyboard -from weboob.browser.pages import HTMLPage, LoggedPage -from weboob.browser.filters.html import Attr -from weboob.browser.filters.standard import CleanText - - -class INGVirtKeyboard(VirtKeyboard): - symbols = {'0': '327208d491507341908cf6920f26b586', - '1': '615ff37b15645da106cebc4605b399de', - '2': 'fb04e648c93620f8b187981f9742b57e', - '3': 'b786d471a70de83657d57bdedb6a2f38', - '4': '41b5501219e8d8f6d3b0baef3352ce88', - '5': 'c72b372fb035160f2ff8dae59cd7e174', - '6': '392fa79e9a1749f5c8c0170f6a8ec68b', - '7': 'fb495b5cf7f46201af0b4977899b56d4', - '8': 'e8fea1e1aa86f8fca7f771db9a1dca4d', - '9': '82e63914f2e52ec04c11cfc6fecf7e08' - } - color = 64 - coords = {"11": (5, 5, 33, 33), - "21": (45, 5, 73, 33), - "31": (85, 5, 113, 33), - "41": (125, 5, 153, 33), - "51": (165, 5, 193, 33), - "12": (5, 45, 33, 73), - "22": (45, 45, 73, 73), - "32": (85, 45, 113, 73), - "42": (125, 45, 153, 73), - "52": (165, 45, 193, 73) - } - - def __init__(self, page): - self.page = page - img = page.doc.xpath("//div[has-class('clavier')]/img") - if len(img) == 0: - raise BrowserIncorrectPassword() - - url = Attr('.', "src")(img[1]) - - VirtKeyboard.__init__(self, BytesIO(self.page.browser.open(url).content), - self.coords, self.color) - - self.check_symbols(self.symbols, self.page.browser.responses_dirname) - - def get_string_code(self, string): - code = '' - first = True - for c in string: - if not first: - code += "," - else: - first = False - codesymbol = self.get_symbol_code(self.symbols[c]) - x = (self.coords[codesymbol][0] + self.coords[codesymbol][2]) / 2 - y = (self.coords[codesymbol][1] + self.coords[codesymbol][3]) / 2 - code += "%d,%d" % (x, y) - return code - - def get_coordinates(self, password): - temppasswd = "" - elems = self.page.doc.xpath('//div[@class="digitpad"]/span/font') - for i, font in enumerate(elems): - if Attr('.', 'class')(font) == "vide": - temppasswd += password[i] - coordinates = self.get_string_code(temppasswd) - self.page.browser.logger.debug("Coordonates: " + coordinates) - return coordinates - - -class LoginPage(HTMLPage): - def prelogin(self, login, birthday): - # First step : login and birthday - form = self.get_form(name='zone1Form') - form['zone1Form:numClient'] = login - form['zone1Form:dateDay'] = birthday[0:2] - form['zone1Form:dateMonth'] = birthday[2:4] - form['zone1Form:dateYear'] = birthday[4:9] - form['zone1Form:idRememberMyCifCheck'] = False - form.submit() - - def error(self): - err = self.doc.find('//span[@class="error"]') - return err is not None - - def login(self, password): - # 2) And now, the virtual Keyboard - vk = INGVirtKeyboard(self) - - form = self.get_form(name='mrc') - form['mrc:mrg'] = 'mrc:mrg' - form['AJAXREQUEST'] = '_viewRoot' - form['mrc:mrldisplayLogin'] = vk.get_coordinates(password) - form.submit() - - def check_for_action_needed(self): - link = Attr('//meta[@content="/general?command=displayTRAlertMessage"]', 'content', default=None)(self.doc) - if link: - self.browser.location(link) - - -class ActionNeededPage(HTMLPage): - def on_load(self): - if self.doc.xpath(u'//form//h1[1][contains(text(), "Accusé de reception du chéquier")]'): - form = self.get_form(name='Alert') - form['command'] = 'validateAlertMessage' - form['radioValide_1_2_40003039944'] = 'Non' - form.submit() - elif self.doc.xpath(u'//p[@class="cddErrorMessage"]'): - error_message = CleanText(u'//p[@class="cddErrorMessage"]')(self.doc) - # TODO python2 handles unicode exceptions badly, fix when passing to python3 - raise ActionNeeded(error_message.encode('ascii', 'replace')) - else: - raise ActionNeeded(CleanText(u'//form//h1[1]')(self.doc)) - - -class StopPage(HTMLPage): - pass - - -class ReturnPage(LoggedPage, HTMLPage): - def on_load(self): - self.get_form(name='retoursso').submit() diff --git a/modules/ing/pages/__init__.py b/modules/ing/web/__init__.py similarity index 91% rename from modules/ing/pages/__init__.py rename to modules/ing/web/__init__.py index f20dfad06e..2bf74acc92 100644 --- a/modules/ing/pages/__init__.py +++ b/modules/ing/web/__init__.py @@ -22,7 +22,7 @@ AccountsList, TitreDetails, ASVInvest, DetailFondsPage, IbanPage, ProfilePage, LoanTokenPage, LoanDetailPage, ) -from .login import LoginPage, StopPage, ActionNeededPage, ReturnPage +from .login import StopPage, ActionNeededPage, ReturnPage from .transfer import TransferPage from .bills import BillsPage from .titre import NetissimaPage, TitrePage, TitreHistory, TitreValuePage, ASVHistory @@ -32,7 +32,7 @@ class AccountPrelevement(AccountsList): pass -__all__ = ['AccountsList', 'LoginPage', 'NetissimaPage','TitreDetails', +__all__ = ['AccountsList', 'NetissimaPage','TitreDetails', 'AccountPrelevement', 'TransferPage', 'BillsPage', 'StopPage', 'TitrePage', 'TitreHistory', 'IbanPage', 'TitreValuePage', 'ASVHistory', 'ASVInvest','DetailFondsPage', diff --git a/modules/ing/pages/accounts_list.py b/modules/ing/web/accounts_list.py similarity index 98% rename from modules/ing/pages/accounts_list.py rename to modules/ing/web/accounts_list.py index 1003ae5d90..94028edf5a 100644 --- a/modules/ing/pages/accounts_list.py +++ b/modules/ing/web/accounts_list.py @@ -318,11 +318,14 @@ def change_space(self, space): def load_space_page(self): # The accounts page exists in two forms: with the spaces list and without # When having the spaceless page, a form must be submit to access the space page - form = self.get_form(id='user-menu') - on_click = self.doc.xpath('//a[contains(@class, "comptes")]/@onclick')[1] + form = self.get_form(id='header-menu') + on_click = Attr('//a[@class="home"]', 'onclick')(self.doc) self.fillup_form(form, r"\),\{(.*)\},'", on_click) form.submit() + def is_multispace_page(self): + return self.doc.xpath('//a[contains(@name, "mainMenu")]') + class IbanPage(LoggedPage, HTMLPage): def get_iban(self): diff --git a/modules/ing/pages/bills.py b/modules/ing/web/bills.py similarity index 100% rename from modules/ing/pages/bills.py rename to modules/ing/web/bills.py diff --git a/modules/ing/web/login.py b/modules/ing/web/login.py new file mode 100644 index 0000000000..a0ef10a747 --- /dev/null +++ b/modules/ing/web/login.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2009-2014 Florent Fourcot, Romain Bignon +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.exceptions import ActionNeeded +from weboob.browser.pages import HTMLPage, LoggedPage +from weboob.browser.filters.standard import CleanText + + +class ActionNeededPage(HTMLPage): + def on_load(self): + if self.doc.xpath(u'//form//h1[1][contains(text(), "Accusé de reception du chéquier")]'): + form = self.get_form(name='Alert') + form['command'] = 'validateAlertMessage' + form['radioValide_1_2_40003039944'] = 'Non' + form.submit() + elif self.doc.xpath(u'//p[@class="cddErrorMessage"]'): + error_message = CleanText(u'//p[@class="cddErrorMessage"]')(self.doc) + # TODO python2 handles unicode exceptions badly, fix when passing to python3 + raise ActionNeeded(error_message.encode('ascii', 'replace')) + else: + raise ActionNeeded(CleanText(u'//form//h1[1]')(self.doc)) + + +class StopPage(HTMLPage): + pass + + +class ReturnPage(LoggedPage, HTMLPage): + def on_load(self): + self.get_form(name='retoursso').submit() diff --git a/modules/ing/pages/titre.py b/modules/ing/web/titre.py similarity index 100% rename from modules/ing/pages/titre.py rename to modules/ing/web/titre.py diff --git a/modules/ing/pages/transfer.py b/modules/ing/web/transfer.py similarity index 99% rename from modules/ing/pages/transfer.py rename to modules/ing/web/transfer.py index eaa802d9bf..33b52c0c26 100644 --- a/modules/ing/pages/transfer.py +++ b/modules/ing/web/transfer.py @@ -29,7 +29,7 @@ from weboob.tools.capabilities.bank.iban import is_iban_valid from weboob.tools.date import parse_french_date -from .login import INGVirtKeyboard +from ..api.login import INGVirtKeyboard class MyRecipient(ItemElement): -- GitLab