diff --git a/modules/ing/api/__init__.py b/modules/ing/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..64d0d9c9de305f1a7a5e6711b05d15ce90850a56
--- /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 0000000000000000000000000000000000000000..0064828b08848e14144c6e05d3c5011cc21a790c
--- /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 0000000000000000000000000000000000000000..91a2416a15b7b80474e65e2dcf98d18b816bec78
--- /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 f2a06e8ec1d21c06af1b2fd38c378d1c252b0f47..ef94f812918eec9839a1121c415c027ccc9084ad 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 63e129d58e66dd93572036a85111abf8e6565601..b6d7f24f2f7bc7f01853e03662b34c3803bb3236 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 3de72deff4613164b6c8e6a91373709e710fe2ac..0000000000000000000000000000000000000000
--- 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 f20dfad06eb3ebd69e4acd2b8cbd99540bd886ba..2bf74acc928c84b823ab5eb2b8f77ce309ac1b8c 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 1003ae5d907f6bd649928364b2e0d514a78665d0..94028edf5a8380f089a32292fd3cb8dac72ac287 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 0000000000000000000000000000000000000000..a0ef10a7475351f8888a7ee0cdeb2c7167e031d8
--- /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 eaa802d9bf1ae80792440b8e774edd13b93284c1..33b52c0c26186dbca562491b886c6e72b5001c0c 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):