Commit a3f9137d authored by ntome's avatar ntome

backport master modules fixes

parent e2ac44f3
Pipeline #2683 failed with stages
in 6 minutes and 19 seconds
......@@ -20,28 +20,29 @@
from __future__ import unicode_literals
from random import randint
from weboob.browser import URL, LoginBrowser, need_login
from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable
from weboob.browser.browsers import URL, LoginBrowser, need_login
from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable, BrowserPasswordExpired
from weboob.tools.compat import basestring
from .pages import LoginPage, IndexPage, BadLogin, AccountDetailPage, AccountHistoryPage
from .pages import (
LoginPage, IndexPage, WrongPasswordPage, WrongWebsitePage,
AccountDetailPage, AccountHistoryPage, MigrationPage,
)
class AferBrowser(LoginBrowser):
BASEURL = 'https://adherent.gie-afer.fr'
login = URL('/web/ega.nsf/listeAdhesions\?OpenForm', LoginPage)
bad_login = URL('/names.nsf\?Login', BadLogin)
login = URL(r'/espaceadherent/MonCompte/Connexion$', LoginPage)
wrong_password = URL(r'/espaceadherent/MonCompte/Connexion\?err=6001', WrongPasswordPage)
wrong_website = URL(r'/espaceadherent/MonCompte/Connexion\?err=6008', WrongWebsitePage)
migration = URL(r'/espaceadherent/MonCompte/Migration', MigrationPage)
index = URL('/web/ega.nsf/listeAdhesions\?OpenForm', IndexPage)
account_detail = URL('/web/ega.nsf/soldeEpargne\?openForm', AccountDetailPage)
account_history = URL('/web/ega.nsf/generationSearchModule\?OpenAgent', AccountHistoryPage)
history_detail = URL('/web/ega.nsf/WOpendetailOperation\?OpenAgent', AccountHistoryPage)
def do_login(self):
"""
Attempt to log in.
Note: this method does nothing if we are already logged in.
"""
assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring)
self.login.go()
......@@ -51,13 +52,14 @@ class AferBrowser(LoginBrowser):
except BrowserUnavailable:
raise BrowserIncorrectPassword()
if self.bad_login.is_here():
if self.migration.is_here():
raise BrowserPasswordExpired(self.page.get_error())
if self.wrong_password.is_here():
error = self.page.get_error()
if "La saisie de l’identifiant ou du code confidentiel est incorrecte" in error or \
"Veuillez-vous identifier" in error:
if error:
raise BrowserIncorrectPassword(error)
else:
assert False, "Message d'erreur inconnu: %s" % error
assert False, 'We landed on WrongPasswordPage but no error message was fetched.'
@need_login
......
......@@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError):
code = 'invalidOTP'
class TransferInvalidOTP(TransferError):
code = 'invalidOTP'
class AccountOwnership(object):
"""
Relationship between the credentials owner (PSU) and the account
......@@ -43,6 +47,6 @@ AccountOwnerType.ASSOCIATION = u'ASSO'
try:
__all__ += ['AccountOwnership', 'RecipientInvalidOTP']
__all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP']
except NameError:
pass
import weboob.exceptions as OLD
# can't import *, __all__ is incomplete...
for attr in dir(OLD):
globals()[attr] = getattr(OLD, attr)
try:
__all__ = OLD.__all__
except AttributeError:
pass
class BrowserInteraction(Exception):
pass
class BrowserQuestion(BrowserInteraction):
"""
When raised by a browser,
"""
def __init__(self, *fields):
self.fields = fields
class DecoupledValidation(BrowserInteraction):
def __init__(self, message='', resource=None, *values):
super(DecoupledValidation, self).__init__(*values)
self.message = message
self.resource = resource
def __str__(self):
return self.message
class AppValidation(DecoupledValidation):
pass
......@@ -39,8 +39,11 @@ class AferModule(Module, CapBankWealth):
VERSION = '1.5'
BROWSER = AferBrowser
CONFIG = BackendConfig(ValueBackendPassword('login', label='Username', regexp='[A-z]\d+', masked=False),
ValueBackendPassword('password', label=u"mdp", regexp='\d{1,8}'))
CONFIG = BackendConfig(
ValueBackendPassword('login', label='Identifiant', regexp=r'.+', masked=False),
ValueBackendPassword('password', label="Mot de passe", regexp=r'\d{1,8}|[a-zA-Z0-9]{7,30}')
# TODO lose previous regex (and in backend) once users credentials migration is complete
)
def create_default_browser(self):
return self.create_browser(self.config['login'].get(),
......
......@@ -28,23 +28,36 @@ from .compat.weboob_browser_filters_standard import CleanText, Regexp, CleanDeci
from weboob.browser.pages import HTMLPage, LoggedPage, pagination
from .compat.weboob_capabilities_bank import Account, Investment, Transaction
from weboob.capabilities.base import NotAvailable
from weboob.exceptions import BrowserUnavailable, ActionNeeded
from .compat.weboob_exceptions import BrowserUnavailable, ActionNeeded
class LoginPage(HTMLPage):
def login(self, login, passwd):
form = self.get_form(name='_DominoForm')
form['Username'] = login
form = self.get_form(id='loginForm')
form['username'] = login
form['password'] = passwd
form.submit()
def is_here(self):
return bool(self.doc.xpath('//form[@name="_DominoForm"]'))
class WrongPasswordPage(HTMLPage):
def get_error(self):
return CleanText('//p[contains(text(), "Votre saisie est erronée")]')(self.doc)
class WrongWebsitePage(HTMLPage):
# We land on this page when the website indicates that
# an account is already created on the 'Aviva et moi' space,
# So we check the message and raise ActionNeeded with it
def on_load(self):
message = CleanText('//p[contains(text(), "Vous êtes déjà inscrit")]')(self.doc)
if message:
raise ActionNeeded(message)
assert False, 'We landed on WrongWebsitePage but no message was fetched.'
class BadLogin(HTMLPage):
class MigrationPage(HTMLPage):
def get_error(self):
return CleanText('//div[@id="idDivErrorLogin"]')(self.doc)
return CleanText('//h1[contains(text(), "Votre nouvel identifiant et mot de passe")]')(self.doc)
class IndexPage(LoggedPage, HTMLPage):
......
......@@ -20,8 +20,8 @@
from __future__ import unicode_literals
from datetime import date
from weboob.browser import LoginBrowser, URL, need_login, StatesMixin
from weboob.exceptions import (
from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin
from .compat.weboob_exceptions import (
BrowserIncorrectPassword, BrowserUnavailable, ImageCaptchaQuestion, BrowserQuestion, ActionNeeded,
WrongCaptchaResponse
)
......
import weboob.exceptions as OLD
# can't import *, __all__ is incomplete...
for attr in dir(OLD):
globals()[attr] = getattr(OLD, attr)
try:
__all__ = OLD.__all__
except AttributeError:
pass
class BrowserInteraction(Exception):
pass
class BrowserQuestion(BrowserInteraction):
"""
When raised by a browser,
"""
def __init__(self, *fields):
self.fields = fields
class DecoupledValidation(BrowserInteraction):
def __init__(self, message='', resource=None, *values):
super(DecoupledValidation, self).__init__(*values)
self.message = message
self.resource = resource
def __str__(self):
return self.message
class AppValidation(DecoupledValidation):
pass
......@@ -25,7 +25,7 @@ from tempfile import mkstemp
from weboob.browser.browsers import URL, LoginBrowser, need_login
from .compat.weboob_capabilities_bank import AccountNotFound
from weboob.exceptions import BrowserIncorrectPassword
from .compat.weboob_exceptions import BrowserIncorrectPassword
from weboob.tools.compat import unquote
from .pages import ActivityPage, SomePage, StatementPage, StatementsPage, SummaryPage
......
......@@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError):
code = 'invalidOTP'
class TransferInvalidOTP(TransferError):
code = 'invalidOTP'
class AccountOwnership(object):
"""
Relationship between the credentials owner (PSU) and the account
......@@ -43,6 +47,6 @@ AccountOwnerType.ASSOCIATION = u'ASSO'
try:
__all__ += ['AccountOwnership', 'RecipientInvalidOTP']
__all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP']
except NameError:
pass
import weboob.exceptions as OLD
# can't import *, __all__ is incomplete...
for attr in dir(OLD):
globals()[attr] = getattr(OLD, attr)
try:
__all__ = OLD.__all__
except AttributeError:
pass
class BrowserInteraction(Exception):
pass
class BrowserQuestion(BrowserInteraction):
"""
When raised by a browser,
"""
def __init__(self, *fields):
self.fields = fields
class DecoupledValidation(BrowserInteraction):
def __init__(self, message='', resource=None, *values):
super(DecoupledValidation, self).__init__(*values)
self.message = message
self.resource = resource
def __str__(self):
return self.message
class AppValidation(DecoupledValidation):
pass
......@@ -42,7 +42,7 @@ class AmeliBrowser(LoginBrowser):
@need_login
def iter_subscription(self):
self.subscription_page.go()
return self.page.iter_subscriptions()
yield self.page.get_subscription()
@need_login
def iter_documents(self, subscription):
......@@ -75,6 +75,6 @@ class AmeliBrowser(LoginBrowser):
# then we set Rechercher to actionEvt to filter for this subscription, within last 6 months
# without first request we would have filter for this subscription but within last 2 months
params['actionEvt'] = 'Rechercher'
params['Beneficiaire'] = subscription._param
params['Beneficiaire'] = 'tout_selectionner'
self.documents_page.go(params=params)
return self.page.iter_documents(subid=subscription.id)
import weboob.exceptions as OLD
# can't import *, __all__ is incomplete...
for attr in dir(OLD):
globals()[attr] = getattr(OLD, attr)
try:
__all__ = OLD.__all__
except AttributeError:
pass
class BrowserInteraction(Exception):
pass
class BrowserQuestion(BrowserInteraction):
"""
When raised by a browser,
"""
def __init__(self, *fields):
self.fields = fields
class DecoupledValidation(BrowserInteraction):
def __init__(self, message='', resource=None, *values):
super(DecoupledValidation, self).__init__(*values)
self.message = message
self.resource = resource
def __str__(self):
return self.message
class AppValidation(DecoupledValidation):
pass
......@@ -19,15 +19,15 @@
from __future__ import unicode_literals
import re
from weboob.browser.elements import method, ListElement, ItemElement
from weboob.browser.filters.html import Attr, Link
from weboob.browser.filters.html import Link
from .compat.weboob_browser_filters_standard import CleanText, Regexp, CleanDecimal, Currency, Field, Format, Env
from weboob.browser.pages import LoggedPage, HTMLPage, PartialHTMLPage
from weboob.capabilities.bill import Subscription, Bill
from weboob.exceptions import BrowserUnavailable
from .compat.weboob_exceptions import BrowserUnavailable
from weboob.tools.date import parse_french_date
from weboob.tools.json import json
class LoginPage(HTMLPage):
......@@ -45,32 +45,22 @@ class ErrorPage(HTMLPage):
class SubscriptionPage(LoggedPage, HTMLPage):
@method
class iter_subscriptions(ListElement):
item_xpath = '//div[@id="corps-de-la-page"]//div[@class="tableau"]/div'
class item(ItemElement):
klass = Subscription
obj__labelid = Attr('.', 'aria-labelledby')
def get_subscription(self):
sub = Subscription()
# DON'T TAKE social security number for id because it's a very confidential data, take birth date instead
sub.id = CleanText('//button[@id="idAssure"]//td[@class="dateNaissance"]')(self.doc).replace('/', '')
sub.label = sub.subscriber = CleanText('//div[@id="pageAssure"]//span[@class="NomEtPrenomLabel"]')(self.doc)
def obj__birthdate(self):
return CleanText('//button[@id="%s"]//td[@class="dateNaissance"]' % Field('_labelid')(self))(self)
return sub
def obj_id(self):
# DON'T TAKE social security number for id because it's a very confidential data, take birth date instead
return ''.join(re.findall(r'\d+', Field('_birthdate')(self)))
def obj__param(self):
reversed_date = ''.join(reversed(re.findall(r'\d+', Field('_birthdate')(self))))
name = CleanText('//button[@id="%s"]//td[@class="nom"]' % Field('_labelid')(self))(self)
return '%s!-!%s!-!1' % (reversed_date, name)
obj_subscriber = CleanText('.//span[@class="NomEtPrenomLabel"]')
obj_label = obj_subscriber
class DocumentsPage(LoggedPage, PartialHTMLPage):
ENCODING = 'utf-8'
def build_doc(self, content):
res = json.loads(content)
return super(DocumentsPage, self).build_doc(res['tableauPaiement'].encode('utf-8'))
class DocumentsPage(LoggedPage, PartialHTMLPage):
@method
class iter_documents(ListElement):
item_xpath = '//ul[@id="unordered_list"]//li[has-class("rowitem")]'
......@@ -82,7 +72,7 @@ class DocumentsPage(LoggedPage, PartialHTMLPage):
obj_label = CleanText('.//div[has-class("col-label")]')
obj_price = CleanDecimal.French('.//div[has-class("col-montant")]/span')
obj_currency = Currency('.//div[has-class("col-montant")]/span')
obj_url = Link('.//a[@class="downdetail"]')
obj_url = Link('.//div[@class="col-download"]/a')
obj_format = 'pdf'
def obj_date(self):
......
......@@ -17,8 +17,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from weboob.browser import LoginBrowser, URL, need_login
from weboob.exceptions import BrowserIncorrectPassword
from weboob.browser.browsers import LoginBrowser, URL, need_login
from .compat.weboob_exceptions import BrowserIncorrectPassword
from weboob.capabilities.bill import Detail
from weboob.tools.compat import urlencode
from decimal import Decimal
......
import weboob.exceptions as OLD
# can't import *, __all__ is incomplete...
for attr in dir(OLD):
globals()[attr] = getattr(OLD, attr)
try:
__all__ = OLD.__all__
except AttributeError:
pass
class BrowserInteraction(Exception):
pass
class BrowserQuestion(BrowserInteraction):
"""
When raised by a browser,
"""
def __init__(self, *fields):
self.fields = fields
class DecoupledValidation(BrowserInteraction):
def __init__(self, message='', resource=None, *values):
super(DecoupledValidation, self).__init__(*values)
self.message = message
self.resource = resource
def __str__(self):
return self.message
class AppValidation(DecoupledValidation):
pass
......@@ -17,17 +17,20 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import datetime
from weboob.exceptions import BrowserIncorrectPassword
from .compat.weboob_exceptions import BrowserIncorrectPassword
from weboob.browser.browsers import LoginBrowser, need_login
from weboob.browser.exceptions import HTTPNotFound, ServerError
from .compat.weboob_browser_url import URL
from dateutil.parser import parse as parse_date
from .pages import (
AccountsPage, JsonBalances, JsonPeriods, JsonHistory,
JsonBalances2, CurrencyPage, LoginPage, WrongLoginPage, AccountSuspendedPage,
NoCardPage, NotFoundPage
NoCardPage, NotFoundPage,
)
......@@ -49,16 +52,16 @@ class AmericanExpressBrowser(LoginBrowser):
js_posted = URL(r'/account-data/v1/financials/transactions\?limit=1000&offset=(?P<offset>\d+)&statement_end_date=(?P<end>[0-9-]+)&status=posted',
JsonHistory)
js_periods = URL(r'/account-data/v1/financials/statement_periods', JsonPeriods)
currency_page = URL(r'https://www.aexp-static.com/cdaas/axp-app/modules/axp-offers/1.11.1/fr-fr/axp-offers.json', CurrencyPage)
currency_page = URL(r'https://www.aexp-static.com/cdaas/axp-app/modules/axp-balance-summary/4.7.0/(?P<locale>\w\w-\w\w)/axp-balance-summary.json', CurrencyPage)
no_card = URL('https://www.americanexpress.com/us/content/no-card/',
'https://www.americanexpress.com/us/no-card/', NoCardPage)
no_card = URL(r'https://www.americanexpress.com/us/content/no-card/',
r'https://www.americanexpress.com/us/no-card/', NoCardPage)
not_found = URL(r'/accounts/error', NotFoundPage)
SUMMARY_CARD_LABEL = [
u'PAYMENT RECEIVED - THANK YOU',
u'PRELEVEMENT AUTOMATIQUE ENREGISTRE-MERCI'
'PAYMENT RECEIVED - THANK YOU',
'PRELEVEMENT AUTOMATIQUE ENREGISTRE-MERCI',
]
def __init__(self, *args, **kwargs):
......@@ -73,7 +76,6 @@ class AmericanExpressBrowser(LoginBrowser):
if self.wrong_login.is_here() or self.login.is_here() or self.account_suspended.is_here():
raise BrowserIncorrectPassword()
@need_login