diff --git a/modules/amazon/browser.py b/modules/amazon/browser.py
index 5756206d07970d9456fc307f764556b7a2104e9f..187898b7ddfbc7a1989a8ec52c66c2c57572e543 100644
--- a/modules/amazon/browser.py
+++ b/modules/amazon/browser.py
@@ -22,7 +22,8 @@ from datetime import date
from weboob.browser import LoginBrowser, URL, need_login, StatesMixin
from weboob.exceptions import (
- BrowserIncorrectPassword, BrowserUnavailable, ImageCaptchaQuestion, BrowserQuestion, ActionNeeded
+ BrowserIncorrectPassword, BrowserUnavailable, ImageCaptchaQuestion, BrowserQuestion, ActionNeeded,
+ WrongCaptchaResponse
)
from weboob.tools.value import Value
from weboob.browser.browsers import ClientError
@@ -42,6 +43,13 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
L_LOGIN = 'Connexion'
L_SUBSCRIBER = 'Nom : (.*) Modifier E-mail'
+ WRONGPASS_MESSAGES = [
+ "Votre mot de passe est incorrect",
+ "Saisissez une adresse e-mail ou un numéro de téléphone portable valable",
+ "Impossible de trouver un compte correspondant à cette adresse e-mail"
+ ]
+ WRONG_CAPTCHA_RESPONSE = "Saisissez les caractères tels qu'ils apparaissent sur l'image."
+
login = URL(r'/ap/signin(.*)', LoginPage)
home = URL(r'/$', r'/\?language=\w+$', HomePage)
panel = URL('/gp/css/homepage.html/ref=nav_youraccount_ya', PanelPage)
@@ -73,7 +81,9 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
super(AmazonBrowser, self).__init__(*args, **kwargs)
def locate_browser(self, state):
- self.location(state['url'])
+ if '/ap/cvf/verify' not in state['url']:
+ # don't perform a GET to this url, it's the otp url, which will be reached by otp_form
+ self.location(state['url'])
def push_security_otp(self, pin_code):
res_form = self.otp_form
@@ -130,7 +140,14 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
self.handle_security()
if self.login.is_here():
- raise BrowserIncorrectPassword()
+ msg = self.page.get_error_message()
+
+ if any(wrongpass_message in msg for wrongpass_message in self.WRONGPASS_MESSAGES):
+ raise BrowserIncorrectPassword(msg)
+ elif self.WRONG_CAPTCHA_RESPONSE in msg:
+ raise WrongCaptchaResponse(msg)
+ else:
+ assert False, msg
else:
return
@@ -157,7 +174,9 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
if captcha and not self.config['captcha_response'].get():
self.handle_captcha(captcha)
else:
- raise BrowserIncorrectPassword()
+ msg = self.page.get_error_message()
+ assert self.WRONGPASS_MESSAGE in msg, msg
+ raise BrowserIncorrectPassword(msg)
def is_login(self):
if self.login.is_here():
diff --git a/modules/amazon/de/browser.py b/modules/amazon/de/browser.py
index 32ac03959db02049aa6d07526f3c42b2730ad373..ed4b29bc74ab6728b83468e9dad188c2363a1637 100644
--- a/modules/amazon/de/browser.py
+++ b/modules/amazon/de/browser.py
@@ -26,3 +26,7 @@ class AmazonDeBrowser(AmazonEnBrowser):
BASEURL = 'https://www.amazon.de'
CURRENCY = 'EUR'
LANGUAGE = 'en-GB'
+
+ # it's in english even in for this browser
+ WRONGPASS_MESSAGES = ['Your password is incorrect', 'We cannot find an account with that e-mail address']
+ WRONG_CAPTCHA_RESPONSE = "Enter the characters as they are shown in the image."
diff --git a/modules/amazon/en/browser.py b/modules/amazon/en/browser.py
index 94f3e131d98853180321105a7bcbb4f8fc832068..89e9a48f8fb36168296700b2961582fb44804b33 100644
--- a/modules/amazon/en/browser.py
+++ b/modules/amazon/en/browser.py
@@ -30,3 +30,6 @@ class AmazonEnBrowser(AmazonBrowser):
L_SIGNIN = 'Sign in'
L_LOGIN = 'Login'
L_SUBSCRIBER = 'Name: (.*) Edit E'
+
+ WRONGPASS_MESSAGES = ['Your password is incorrect', 'We cannot find an account with that email address']
+ WRONG_CAPTCHA_RESPONSE = "Enter the characters as they are given in the challenge."
diff --git a/modules/amazon/pages.py b/modules/amazon/pages.py
index da584341c3541261a116f92516efeb6c3409c98b..7288d28250db8f96842d83b19ddae513de1f93df 100644
--- a/modules/amazon/pages.py
+++ b/modules/amazon/pages.py
@@ -108,6 +108,9 @@ class LoginPage(HTMLPage):
form = self.get_form(nr=0)
return form
+ def get_error_message(self):
+ return CleanText('//div[@id="auth-error-message-box"]')(self.doc)
+
class SubscriptionsPage(LoggedPage, HTMLPage):
@method
@@ -139,8 +142,6 @@ class DocumentsPage(LoggedPage, HTMLPage):
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'))
- obj_url = Async('details') & Link('//a[contains(@href, "download")]|//a[contains(@href, "generated_invoices")]')
- obj_format = 'pdf'
obj_label = Format('Facture %s', Field('_simple_id'))
obj_type = DocumentTypes.BILL
@@ -160,6 +161,18 @@ class DocumentsPage(LoggedPage, HTMLPage):
currency = Env('currency')(self)
return Currency('.//div[has-class("a-col-left")]//span[has-class("value") and contains(., "%s")]' % currency)(self)
+ def obj_url(self):
+ 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)
+ return url
+
+ def obj_format(self):
+ if 'summary' in Field('url')(self):
+ return 'html'
+ return 'pdf'
+
class DownloadDocumentPage(LoggedPage, PartialHTMLPage):
pass
diff --git a/modules/amazon/uk/browser.py b/modules/amazon/uk/browser.py
index d1cce2068349a0c42d0dd5725c067eac0500bc40..e78a8dd5fe6bf21d82bd7e36f0209524ef70cdb6 100644
--- a/modules/amazon/uk/browser.py
+++ b/modules/amazon/uk/browser.py
@@ -25,4 +25,7 @@ from ..en.browser import AmazonEnBrowser
class AmazonUkBrowser(AmazonEnBrowser):
BASEURL = 'https://www.amazon.co.uk'
CURRENCY = u'£'
- LANGUAGE = u'en-GB'
\ No newline at end of file
+ LANGUAGE = u'en-GB'
+
+ WRONGPASS_MESSAGES = ['Your password is incorrect', 'We cannot find an account with that e-mail address']
+ WRONG_CAPTCHA_RESPONSE = "Enter the characters as they are shown in the image."
diff --git a/modules/bnporc/module.py b/modules/bnporc/module.py
index 9d13c9264acc416ecd2bbd195fbf206e736881b9..92d9c3a1f42497fd34ab779638cd23d964a10614 100644
--- a/modules/bnporc/module.py
+++ b/modules/bnporc/module.py
@@ -53,6 +53,7 @@ class BNPorcModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapMessag
ValueBackendPassword('login', label=u'Numéro client', masked=False),
ValueBackendPassword('password', label=u'Code secret', regexp='^(\d{6})$'),
ValueBool('rotating_password', label=u'Automatically renew password every 100 connections', default=False),
+ ValueBool('digital_key', label=u'User with digital key have to add recipient with digital key', default=False),
Value('website', label='Type de compte', default='pp',
choices={'pp': 'Particuliers/Professionnels',
'hbank': 'HelloBank',
diff --git a/modules/bnporc/pp/browser.py b/modules/bnporc/pp/browser.py
index 98bfd7b9b19df63a8d9f568cc3a9176384141cd4..33324a82ceee07d88c359350c7eaac5be7a69bf5 100644
--- a/modules/bnporc/pp/browser.py
+++ b/modules/bnporc/pp/browser.py
@@ -26,7 +26,10 @@ from requests.exceptions import ConnectionError
from weboob.browser.browsers import LoginBrowser, URL, need_login
from weboob.capabilities.base import find_object
-from weboob.capabilities.bank import AccountNotFound, Account, TransferError, AddRecipientStep
+from weboob.capabilities.bank import (
+ AccountNotFound, Account, AddRecipientStep, AddRecipientTimeout,
+ TransferInvalidRecipient,
+)
from weboob.capabilities.profile import ProfileMissing
from weboob.tools.decorators import retry
from weboob.tools.capabilities.bank.transactions import sorted_transactions
@@ -34,7 +37,7 @@ from weboob.tools.json import json
from weboob.browser.exceptions import ServerError
from weboob.browser.elements import DataError
from weboob.exceptions import BrowserIncorrectPassword
-from weboob.tools.value import Value
+from weboob.tools.value import Value, ValueBool
from .pages import (
LoginPage, AccountsPage, AccountsIBANPage, HistoryPage, TransferInitPage,
@@ -43,6 +46,7 @@ from .pages import (
MarketListPage, MarketPage, MarketHistoryPage, MarketSynPage, BNPKeyboard,
RecipientsPage, ValidateTransferPage, RegisterTransferPage, AdvisorPage,
AddRecipPage, ActivateRecipPage, ProfilePage, ListDetailCardPage, ListErrorPage,
+ UselessPage, TransferAssertionError,
)
@@ -77,6 +81,9 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
LoginPage)
list_error_page = URL('https://mabanque.bnpparibas/rsc/contrib/document/properties/identification-fr-part-V1.json', ListErrorPage)
+
+ useless_page = URL('/fr/connexion/comptes-et-contrats', UselessPage)
+
con_threshold = URL('/fr/connexion/100-connexions',
'/fr/connexion/mot-de-passe-expire',
'/fr/espace-prive/100-connexions.*',
@@ -105,7 +112,8 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
recipients = URL('/virement-wspl/rest/listerBeneficiaire', RecipientsPage)
add_recip = URL('/virement-wspl/rest/ajouterBeneficiaire', AddRecipPage)
- activate_recip = URL('/virement-wspl/rest/activerBeneficiaire', ActivateRecipPage)
+ activate_recip_sms = URL('/virement-wspl/rest/activerBeneficiaire', ActivateRecipPage)
+ activate_recip_digital_key = URL('/virement-wspl/rest/verifierAuthentForte', ActivateRecipPage)
validate_transfer = URL('/virement-wspl/rest/validationVirement', ValidateTransferPage)
register_transfer = URL('/virement-wspl/rest/enregistrerVirement', RegisterTransferPage)
@@ -119,6 +127,7 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
self.accounts_list = None
self.card_to_transaction_type = {}
self.rotating_password = config['rotating_password'].get()
+ self.digital_key = config['digital_key'].get()
@retry(ConnectionError, tries=3)
def open(self, *args, **kwargs):
@@ -167,7 +176,7 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
# This page might be unavailable.
try:
ibans.update(self.transfer_init.go(data=JSON({'modeBeneficiaire': '0'})).get_ibans_dict('Crediteur'))
- except (TransferError, AttributeError):
+ except (TransferAssertionError, AttributeError):
pass
accounts = list(self.accounts.go().iter_accounts(ibans))
@@ -324,7 +333,7 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
try:
if not origin_account_id in self.transfer_init.go(data=JSON({'modeBeneficiaire': '0'})).get_ibans_dict('Debiteur'):
raise NotImplementedError()
- except TransferError:
+ except TransferAssertionError:
return
# avoid recipient with same iban
@@ -343,19 +352,95 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
@need_login
def new_recipient(self, recipient, **params):
if 'code' in params:
+ # for sms authentication
return self.send_code(recipient, **params)
- # needed to get the phone number, enabling the possibility to send sms.
- self.recipients.go(data=JSON({'type': 'TOUS'}))
- # post recipient data sending sms with same request
+
+ # prepare commun data for all authentication method
data = {}
data['adresseBeneficiaire'] = ''
data['iban'] = recipient.iban
data['libelleBeneficiaire'] = recipient.label
data['notification'] = True
data['typeBeneficiaire'] = ''
- data['typeEnvoi'] = 'SMS'
- recipient = self.add_recip.go(data=json.dumps(data), headers={'Content-Type': 'application/json'}).get_recipient(recipient)
- raise AddRecipientStep(recipient, Value('code', label='Saisissez le code.'))
+
+ # provisional
+ if self.digital_key:
+ if 'digital_key' in params:
+ return self.new_recipient_digital_key(recipient, data)
+
+ # need to be on recipient page send sms or mobile notification
+ # needed to get the phone number, enabling the possibility to send sms.
+ # all users with validated phone number can receive sms code
+ self.recipients.go(data=JSON({'type': 'TOUS'}))
+
+ # check type of recipient activation
+ type_activation = 'sms'
+
+ # provisional
+ if self.digital_key:
+ if self.page.has_digital_key():
+ # force users with digital key activated to use digital key authentication
+ type_activation = 'digital_key'
+
+ if type_activation == 'sms':
+ # post recipient data sending sms with same request
+ data['typeEnvoi'] = 'SMS'
+ recipient = self.add_recip.go(
+ data=json.dumps(data),
+ headers={'Content-Type': 'application/json'}
+ ).get_recipient(recipient)
+ raise AddRecipientStep(recipient, Value('code', label='Saisissez le code reçu par SMS.'))
+ elif type_activation == 'digital_key':
+ # recipient validated with digital key are immediatly available
+ recipient.enabled_date = datetime.today()
+ raise AddRecipientStep(
+ recipient,
+ ValueBool('digital_key', label='Validez pour recevoir une demande sur votre application bancaire. La validation de votre bénéficiaire peut prendre plusieurs minutes.')
+ )
+
+ @need_login
+ def send_code(self, recipient, **params):
+ """
+ add recipient with sms otp authentication
+ """
+ data = {}
+ data['idBeneficiaire'] = recipient._transfer_id
+ data['typeActivation'] = 1
+ data['codeActivation'] = params['code']
+ return self.activate_recip_sms.go(data=json.dumps(data), headers={'Content-Type': 'application/json'}).get_recipient(recipient)
+
+ @need_login
+ def new_recipient_digital_key(self, recipient, data):
+ """
+ add recipient with 'clé digitale' authentication
+ """
+ # post recipient data, sending app notification with same request
+ data['typeEnvoi'] = 'AF'
+ self.add_recip.go(data=json.dumps(data), headers={'Content-Type': 'application/json'})
+ recipient = self.page.get_recipient(recipient)
+
+ # prepare data for polling
+ assert recipient._id_transaction
+ polling_data = {}
+ polling_data['idBeneficiaire'] = recipient._transfer_id
+ polling_data['idTransaction'] = recipient._id_transaction
+ polling_data['typeActivation'] = 2
+
+ timeout = time.time() + 300.00 # float(second), like bnp website
+
+ # polling
+ while time.time() < timeout:
+ time.sleep(5) # like website
+ self.activate_recip_digital_key.go(
+ data = json.dumps(polling_data),
+ headers = {'Content-Type': 'application/json'}
+ )
+ if self.page.is_recipient_validated():
+ break
+ else:
+ raise AddRecipientTimeout()
+
+ return recipient
@need_login
def prepare_transfer(self, account, recipient, amount, reason, exec_date):
@@ -367,24 +452,19 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
data['montant'] = str(amount)
data['typeVirement'] = 'SEPA'
if recipient.category == u'Externe':
- data['idBeneficiaire'] = recipient.id
+ data['idBeneficiaire'] = recipient._transfer_id
else:
data['compteCrediteur'] = recipient.id
return data
@need_login
def init_transfer(self, account, recipient, amount, reason, exec_date):
+ if recipient._web_state == 'En attente':
+ raise TransferInvalidRecipient(message="Le bénéficiaire sélectionné n'est pas activé")
+
data = self.prepare_transfer(account, recipient, amount, reason, exec_date)
return self.validate_transfer.go(data=JSON(data)).handle_response(account, recipient, amount, reason)
- @need_login
- def send_code(self, recipient, **params):
- data = {}
- data['idBeneficiaire'] = recipient.id
- data['typeActivation'] = 1
- data['codeActivation'] = params['code']
- return self.activate_recip.go(data=json.dumps(data), headers={'Content-Type': 'application/json'}).get_recipient(recipient)
-
@need_login
def execute_transfer(self, transfer):
self.register_transfer.go(data=JSON({'referenceVirement': transfer.id}))
diff --git a/modules/bnporc/pp/pages.py b/modules/bnporc/pp/pages.py
index 9c2db1b68e2f8ff0f39371a0171e95002169d3a8..1d9b28fddb0c5bb413baa07671a570dc6e2a3712 100644
--- a/modules/bnporc/pp/pages.py
+++ b/modules/bnporc/pp/pages.py
@@ -34,8 +34,8 @@ from weboob.browser.filters.html import TableCell
from weboob.browser.pages import JsonPage, LoggedPage, HTMLPage
from weboob.capabilities import NotAvailable
from weboob.capabilities.bank import (
- Account, Investment, Recipient, Transfer, TransferError, TransferBankError,
- AddRecipientBankError,
+ Account, Investment, Recipient, Transfer, TransferBankError,
+ AddRecipientBankError, AddRecipientTimeout,
)
from weboob.capabilities.contact import Advisor
from weboob.capabilities.profile import Person, ProfileMissing
@@ -49,6 +49,10 @@ from weboob.tools.compat import unquote_plus
from weboob.tools.html import html2text
+class TransferAssertionError(Exception):
+ pass
+
+
class ConnectionThresholdPage(HTMLPage):
NOT_REUSABLE_PASSWORDS_COUNT = 3
"""BNP disallows to reuse one of the three last used passwords."""
@@ -362,7 +366,7 @@ class TransferInitPage(BNPPage):
def on_load(self):
message_code = BNPPage.on_load(self)
if message_code is not None:
- raise TransferError('%s, code=%s' % (message_code[0], message_code[1]))
+ raise TransferAssertionError('%s, code=%s' % (message_code[0], message_code[1]))
def get_ibans_dict(self, account_type):
return dict([(a['ibanCrypte'], a['iban']) for a in self.path('data.infoVirement.listeComptes%s.*' % account_type)])
@@ -381,6 +385,7 @@ class TransferInitPage(BNPPage):
obj_label = Dict('libelleCompte')
obj_iban = Dict('iban')
obj_category = u'Interne'
+ obj__web_state = None
def obj_bank_name(self):
return u'BNP PARIBAS'
@@ -398,10 +403,11 @@ class RecipientsPage(BNPPage):
# For the moment, only yield ready to transfer on recipients.
condition = lambda self: Dict('libelleStatut')(self.el) in [u'Activé', u'Temporisé', u'En attente']
- obj_id = Dict('idBeneficiaire')
+ obj_id = obj_iban = Dict('ibanNumCompte')
+ obj__transfer_id = Dict('idBeneficiaire')
obj_label = Dict('nomBeneficiaire')
- obj_iban = Dict('ibanNumCompte')
obj_category = u'Externe'
+ obj__web_state = Dict('libelleStatut')
def obj_bank_name(self):
return Dict('nomBanque')(self) or NotAvailable
@@ -409,6 +415,9 @@ class RecipientsPage(BNPPage):
def obj_enabled_at(self):
return datetime.now().replace(microsecond=0) if Dict('libelleStatut')(self) == u'Activé' else (datetime.now() + timedelta(days=5)).replace(microsecond=0)
+ def has_digital_key(self):
+ return Dict('data/infoBeneficiaire/authentForte')(self.doc) and Dict('data/infoBeneficiaire/nomDeviceAF', default=False)(self.doc)
+
class ValidateTransferPage(BNPPage):
def check_errors(self):
@@ -417,12 +426,12 @@ class ValidateTransferPage(BNPPage):
def abort_if_unknown(self, transfer_data):
try:
- assert transfer_data['typeOperation'] in ['1', '2']
- assert transfer_data['repartitionFrais'] == '0'
- assert transfer_data['devise'] == 'EUR'
- assert not transfer_data['montantDeviseEtrangere']
+ assert transfer_data['typeOperation'] in ['1', '2'], 'Transfer operation type is %s' % transfer_data['typeOperation']
+ assert transfer_data['repartitionFrais'] == '0', 'Transfer fees is not 0'
+ assert transfer_data['devise'] == 'EUR', "Transfer currency is not EUR, it's %s" % transfer_data['devise']
+ assert not transfer_data['montantDeviseEtrangere'], 'Transfer currency is foreign currency'
except AssertionError as e:
- raise TransferError(e)
+ raise TransferAssertionError(e)
def handle_response(self, account, recipient, amount, reason):
self.check_errors()
@@ -431,7 +440,7 @@ class ValidateTransferPage(BNPPage):
self.abort_if_unknown(transfer_data)
if 'idBeneficiaire' in transfer_data and transfer_data['idBeneficiaire'] is not None:
- assert transfer_data['idBeneficiaire'] == recipient.id
+ assert transfer_data['idBeneficiaire'] == recipient._transfer_id
elif transfer_data.get('ibanCompteCrediteur'):
assert transfer_data['ibanCompteCrediteur'] == recipient.iban
@@ -830,9 +839,20 @@ class AddRecipPage(BNPPage):
r.enabled_at = datetime.now().replace(microsecond=0) + timedelta(days=5)
r.currency = u'EUR'
r.bank_name = NotAvailable
+ r._id_transaction = self.get('data.gestionBeneficiaire.idTransactionAF') or NotAvailable
return r
+
class ActivateRecipPage(AddRecipPage):
+ def is_recipient_validated(self):
+ authent_state = self.doc['data']['verifAuthentForte']['authentForteDone']
+ assert authent_state in (0, 1, 2, 3), 'State of authent is %s' % authent_state
+ if authent_state == 2:
+ raise ActionNeeded("La demande d'ajout de bénéficiaire a été annulée.")
+ elif authent_state == 3:
+ raise AddRecipientTimeout()
+ return authent_state
+
def get_recipient(self, recipient):
r = Recipient()
r.iban = recipient.iban
@@ -843,3 +863,7 @@ class ActivateRecipPage(AddRecipPage):
r.currency = u'EUR'
r.bank_name = self.get('data.activationBeneficiaire.nomBanque')
return r
+
+
+class UselessPage(LoggedPage, HTMLPage):
+ pass
diff --git a/modules/bnppere/browser.py b/modules/bnppere/browser.py
index cc08f390c67bc7b8733d1ed040707701bdba5bfc..c2a1b2610dc9c458541f8566d9a700514f3d6ec8 100644
--- a/modules/bnppere/browser.py
+++ b/modules/bnppere/browser.py
@@ -28,6 +28,9 @@ class BnppereBrowser(AbstractBrowser):
PARENT = 's2e'
PARENT_ATTR = 'package.browser.BnppereBrowser'
+ def get_profile(self):
+ raise NotImplementedError()
+
class VisiogoBrowser(LoginBrowser):
BASEURL = 'https://visiogo.bnpparibas.com/'
diff --git a/modules/boursorama/__init__.py b/modules/boursorama/__init__.py
index e8c2ac5ab55eea0a7e2882a908df070f9756cef5..9a2a77e7e318881ebdcd316b0dd4841b3bb7123b 100644
--- a/modules/boursorama/__init__.py
+++ b/modules/boursorama/__init__.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 .
diff --git a/modules/boursorama/browser.py b/modules/boursorama/browser.py
index 190bc288c7e0b8dc02755fe42aa9f12ff077cdc7..45ff7b2a66f1bbd880a2d98e7fe20bb890ad4baa 100644
--- a/modules/boursorama/browser.py
+++ b/modules/boursorama/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 .
@@ -31,7 +31,7 @@ from weboob.browser.exceptions import LoggedOut, ClientError
from weboob.capabilities.bank import (
Account, AccountNotFound, TransferError, TransferInvalidAmount,
TransferInvalidEmitter, TransferInvalidLabel, TransferInvalidRecipient,
- AddRecipientStep, Recipient, Rate
+ AddRecipientStep, Recipient, Rate, TransferBankError,
)
from weboob.capabilities.contact import Advisor
from weboob.tools.captcha.virtkeyboard import VirtKeyboardError
@@ -465,6 +465,10 @@ class BoursoramaBrowser(RetryLoginBrowser, StatesMixin):
self.page.submit()
assert self.transfer_sent.is_here()
+ transfer_error = self.page.get_transfer_error()
+ if transfer_error:
+ raise TransferBankError(transfer_error)
+
# the last page contains no info, return the last transfer object from init_transfer
return transfer
diff --git a/modules/boursorama/module.py b/modules/boursorama/module.py
index 47e10b9f272458649eb9926e4e909cd96aff14f7..014704d1aef00c8ed432d31a01a7bff7265f4df5 100644
--- a/modules/boursorama/module.py
+++ b/modules/boursorama/module.py
@@ -7,16 +7,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 .
@@ -41,7 +41,7 @@ class BoursoramaModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapPr
MAINTAINER = u'Gabriel Kerneis'
EMAIL = 'gabriel@kerneis.info'
VERSION = '1.6'
- LICENSE = 'AGPLv3+'
+ LICENSE = 'LGPLv3+'
DESCRIPTION = u'Boursorama'
CONFIG = BackendConfig(ValueBackendPassword('login', label='Identifiant', masked=False),
ValueBackendPassword('password', label='Mot de passe'),
diff --git a/modules/boursorama/pages.py b/modules/boursorama/pages.py
index 00e8850ba832b349ea5b57964b91faef1b633bf7..775b3882f358d5bd768bc399403ae743ee7724b2 100644
--- a/modules/boursorama/pages.py
+++ b/modules/boursorama/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
@@ -126,6 +126,7 @@ class Transaction(FrenchTransaction):
(re.compile(r'^(?P[A-Z][\sa-z]* )?AVOIR (?P\d{2})(?P\d{2})(?P\d{4}) (?P.*)'), FrenchTransaction.TYPE_PAYBACK),
(re.compile('^REM CHQ (?P.*)'), FrenchTransaction.TYPE_DEPOSIT),
(re.compile(u'^([*]{3} solde des operations cb [*]{3} )?Relevé différé Carte (.*)'), FrenchTransaction.TYPE_CARD_SUMMARY),
+ (re.compile(u'^[*]{3} solde des operations cb [*]{3}(.*)'), FrenchTransaction.TYPE_CARD),
(re.compile(r'^Ech pret'), FrenchTransaction.TYPE_LOAN_PAYMENT),
]
@@ -282,7 +283,8 @@ class AccountsPage(LoggedPage, HTMLPage):
raise SkipItem()
return self.obj__idparts()[1]
- id = Async('details', Regexp(CleanText('//h3[has-class("account-number")]'), r'(\d+)', default=NotAvailable))(self)
+ # sometimes it's sometimes it's
+ id = Async('details', Regexp(CleanText('//*[has-class("account-number")]'), r'Référence du compte : (\d+)', default=NotAvailable))(self)
if not id:
raise SkipItem()
return id
@@ -433,7 +435,7 @@ class HistoryPage(LoggedPage, HTMLPage):
if self.obj.type == Transaction.TYPE_CARD_SUMMARY:
return self.obj.type
deferred_card_labels = [card.label for card in self.page.browser.cards_list]
- if 'cartes débit différé' in Field('category')(self) or Field('_account_name')(self).upper() in deferred_card_labels:
+ if Field('_account_name')(self).upper() in deferred_card_labels:
return Transaction.TYPE_DEFERRED_CARD
if not Env('is_card', default=False)(self):
if Env('coming', default=False)(self) and Field('raw')(self).startswith('CARTE '):
@@ -838,23 +840,23 @@ class TransferAccounts(LoggedPage, HTMLPage):
class TransferRecipients(LoggedPage, HTMLPage):
@method
class iter_recipients(ListElement):
- item_xpath = '//a[has-class("transfer__account-wrapper")]'
+ item_xpath = '//div[contains(@class, "deploy__wrapper")]//label[@class="account-choice__label"]'
class item(ItemElement):
klass = Recipient
- obj_id = CleanText('.//div[@class="transfer__account-number"]')
+ obj_id = CleanText('.//div[@class="c-card-ghost__sub-label"]')
obj_bank_name = Regexp(CleanText('.//div[@class="transfer__account-name"]'), pattern=r'- ([^-]*)$', default=NotAvailable)
def obj_label(self):
- label = Regexp(CleanText('.//div[@class="transfer__account-name"]'), pattern=r'^(.*?)(?: -[^-]*)?$')(self)
+ label = Regexp(CleanText('.//div[@class="c-card-ghost__top-label"]'), pattern=r'^(.*?)(?: -[^-]*)?$')(self)
return label.rstrip('-').rstrip()
def obj_category(self):
text = CleanText('./ancestor::div[has-class("deploy--item")]//a[has-class("deploy__title")]')(self)
if 'Mes comptes Boursorama Banque' in text:
return 'Interne'
- elif 'Comptes externes' in text or 'Comptes de tiers' in text:
+ elif any(exp in text for exp in ('Comptes externes', 'Comptes de tiers', 'Mes bénéficiaires')):
return 'Externe'
def obj_iban(self):
@@ -864,7 +866,7 @@ class TransferRecipients(LoggedPage, HTMLPage):
def obj_enabled_at(self):
return datetime.datetime.now().replace(microsecond=0)
- obj__tempid = Attr('.', 'data-value')
+ obj__tempid = Attr('./div[@class="c-card-ghost "]', 'data-value')
def condition(self):
iban = Field('iban')(self)
@@ -943,7 +945,8 @@ class TransferConfirm(LoggedPage, HTMLPage):
class TransferSent(LoggedPage, HTMLPage):
- pass
+ def get_transfer_error(self):
+ return CleanText('//form[@name="Confirm"]/div[@class="form-errors"]//li')(self.doc)
class AddRecipientPage(LoggedPage, HTMLPage):
diff --git a/modules/boursorama/test.py b/modules/boursorama/test.py
index 41403efe611061f89ad3cc04e12339c68085e9e0..0807d2b504f859c98c6636d539f4bd0d3c10dc04 100644
--- a/modules/boursorama/test.py
+++ b/modules/boursorama/test.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 .
diff --git a/modules/bouygues/browser.py b/modules/bouygues/browser.py
index f9315c67aac671a0aa2f9e221f97009397cb5865..513b2680b27e5eff40a6c701dccf517606b444b9 100644
--- a/modules/bouygues/browser.py
+++ b/modules/bouygues/browser.py
@@ -43,7 +43,7 @@ class BouyguesBrowser(LoginBrowser):
subscriptions = URL(r'/personnes/(?P\d+)/comptes-facturation', SubscriptionPage)
subscriptions_details = URL(r'/comptes-facturation/(?P\d+)/contrats-payes', SubscriptionDetailPage)
- document_file = URL(r'/comptes-facturation/(?P\d+)/factures/\d+/documents', DocumentFilePage)
+ document_file = URL(r'/comptes-facturation/(?P\d+)/factures/.*/documents', DocumentFilePage)
documents = URL(r'/comptes-facturation/(?P\d+)/factures', DocumentsPage)
sms_page = URL(r'https://www.secure.bbox.bouyguestelecom.fr/services/SMSIHD/sendSMS.phtml',
@@ -139,7 +139,7 @@ class BouyguesBrowser(LoginBrowser):
self.location(subscription.url, headers=self.headers)
return self.page.iter_documents(subid=subscription.id)
except HTTPNotFound as error:
- if error.response.json()['error'] == 'facture_introuvable':
+ if error.response.json()['error'] in ('facture_introuvable', 'compte_jamais_facture'):
return []
raise
diff --git a/modules/bp/browser.py b/modules/bp/browser.py
index 8b72f97ccf432e8d3b50ba987496d90c42846e7b..a9b296dedb4bf9f53c71f12cae76080cfa8537a7 100644
--- a/modules/bp/browser.py
+++ b/modules/bp/browser.py
@@ -42,7 +42,7 @@ from .pages.pro import RedirectPage, ProAccountsList, ProAccountHistory, Downloa
from .pages.mandate import MandateAccountsList, PreMandate, PreMandateBis, MandateLife, MandateMarket
from .linebourse_browser import LinebourseBrowser
-from weboob.capabilities.bank import TransferError, Account, Recipient, AddRecipientStep
+from weboob.capabilities.bank import Account, Recipient, AddRecipientStep
from weboob.tools.value import Value
__all__ = ['BPBrowser', 'BProBrowser']
@@ -457,8 +457,7 @@ class BPBrowser(LoginBrowser, StatesMixin):
@need_login
def execute_transfer(self, transfer, code=None):
- if not self.transfer_confirm.is_here():
- raise TransferError('Case not handled.')
+ assert self.transfer_confirm.is_here(), 'Case not handled.'
self.page.confirm()
# Should only happen if double auth.
if self.transfer_confirm.is_here():
diff --git a/modules/bp/module.py b/modules/bp/module.py
index d40f642ef18f0bc5f17321971a9d34e65f02d4ba..23657ae278a337a3ce2ffff2ee01d5fb578009f8 100644
--- a/modules/bp/module.py
+++ b/modules/bp/module.py
@@ -19,7 +19,7 @@
from decimal import Decimal
-from weboob.capabilities.bank import CapBankWealth, CapBankTransferAddRecipient, Account, AccountNotFound, RecipientNotFound, TransferError
+from weboob.capabilities.bank import CapBankWealth, CapBankTransferAddRecipient, Account, AccountNotFound, RecipientNotFound
from weboob.capabilities.contact import CapContact
from weboob.capabilities.base import find_object, strict_find_object, NotAvailable
from weboob.capabilities.profile import CapProfile
@@ -94,11 +94,7 @@ class BPModule(
if not recipient:
recipient = strict_find_object(self.iter_transfer_recipients(account.id), id=transfer.recipient_id, error=RecipientNotFound)
- try:
- # quantize to show 2 decimals.
- amount = Decimal(transfer.amount).quantize(Decimal(10) ** -2)
- except (AssertionError, ValueError):
- raise TransferError('something went wrong')
+ amount = Decimal(transfer.amount).quantize(Decimal(10) ** -2)
# format label like label sent by firefox or chromium browser
transfer.label = transfer.label.encode('latin-1', errors="xmlcharrefreplace").decode('latin-1')
diff --git a/modules/bp/pages/transfer.py b/modules/bp/pages/transfer.py
index 4831e3c4b7f3fe2b21fb22ff634cee90d656eeab..8d71ee1924bb5181d2405d034e1207c3401c555d 100644
--- a/modules/bp/pages/transfer.py
+++ b/modules/bp/pages/transfer.py
@@ -21,7 +21,7 @@
from datetime import datetime
from weboob.capabilities.bank import (
- TransferError, TransferBankError, Transfer, TransferStep, NotAvailable, Recipient,
+ TransferBankError, Transfer, TransferStep, NotAvailable, Recipient,
AccountNotFound, AddRecipientBankError
)
from weboob.capabilities.base import find_object
@@ -149,11 +149,10 @@ class TransferConfirm(LoggedPage, CheckTransferError):
def handle_response(self, account, recipient, amount, reason):
account_txt = CleanText('//form//dl/dt[span[contains(text(), "biter")]]/following::dd[1]', replace=[(' ', '')])(self.doc)
recipient_txt = CleanText('//form//dl/dt[span[contains(text(), "diter")]]/following::dd[1]', replace=[(' ', '')])(self.doc)
- try:
- assert account.id in account_txt or ''.join(account.label.split()) == account_txt
- assert recipient.id in recipient_txt or ''.join(recipient.label.split()) == recipient_txt
- except AssertionError:
- raise TransferError('Something went wrong')
+
+ assert account.id in account_txt or ''.join(account.label.split()) == account_txt, 'Something went wrong'
+ assert recipient.id in recipient_txt or ''.join(recipient.label.split()) == recipient_txt, 'Something went wrong'
+
r_amount = CleanDecimal('//form//dl/dt[span[contains(text(), "Montant")]]/following::dd[1]', replace_dots=True)(self.doc)
exec_date = Date(CleanText('//form//dl/dt[span[contains(text(), "Date")]]/following::dd[1]'), dayfirst=True)(self.doc)
currency = FrenchTransaction.Currency('//form//dl/dt[span[contains(text(), "Montant")]]/following::dd[1]')(self.doc)
diff --git a/modules/caissedepargne/browser.py b/modules/caissedepargne/browser.py
index 299b73dd09514027f1a88b7d4c4799978792cdfd..9250f80e3bd7553ad522f08402c3b3ea6b0c851c 100644
--- a/modules/caissedepargne/browser.py
+++ b/modules/caissedepargne/browser.py
@@ -29,7 +29,7 @@ from dateutil import parser
from weboob.browser import LoginBrowser, need_login, StatesMixin
from weboob.browser.switch import SiteSwitch
from weboob.browser.url import URL
-from weboob.capabilities.bank import Account, AddRecipientStep, Recipient, TransferBankError, Transaction
+from weboob.capabilities.bank import Account, AddRecipientStep, Recipient, TransferBankError, Transaction, TransferStep
from weboob.capabilities.base import NotAvailable
from weboob.capabilities.profile import Profile
from weboob.browser.exceptions import BrowserHTTPNotFound, ClientError
@@ -731,12 +731,32 @@ class CaisseEpargne(LoginBrowser, StatesMixin):
@need_login
def init_transfer(self, account, recipient, transfer):
+ self.is_send_sms = False
self.pre_transfer(account)
self.page.init_transfer(account, recipient, transfer)
+
if self.sms_option.is_here():
- raise NotImplementedError()
- self.page.continue_transfer(account.label, recipient, transfer.label)
- return self.page.create_transfer(account, recipient, transfer)
+ self.is_send_sms = True
+ raise TransferStep(
+ transfer,
+ Value(
+ 'otp_sms',
+ label='Veuillez renseigner le mot de passe unique qui vous a été envoyé par SMS dans le champ réponse.'
+ )
+ )
+
+ self.page.continue_transfer(account.label, recipient.label, transfer.label)
+ return self.page.update_transfer(transfer, account, recipient)
+
+ @need_login
+ def otp_sms_continue_transfer(self, transfer, **params):
+ self.is_send_sms = False
+ assert 'otp_sms' in params, 'OTP SMS is missing'
+
+ self.otp_sms_validation(params['otp_sms'])
+ if self.transfer.is_here():
+ self.page.continue_transfer(transfer.account_label, transfer.recipient_label, transfer.label)
+ return self.page.update_transfer(transfer)
@need_login
def execute_transfer(self, transfer):
@@ -754,6 +774,32 @@ class CaisseEpargne(LoginBrowser, StatesMixin):
r.bank_name = NotAvailable
return r
+ def otp_sms_validation(self, otp_sms):
+ tr_id = re.search(r'transactionID=(.*)', self.page.url)
+ if tr_id:
+ transaction_id = tr_id.group(1)
+ else:
+ assert False, 'Transfer transaction id was not found in url'
+
+ self.request_sms.go(param=transaction_id)
+
+ key = self.page.validate_key()
+ data = {
+ 'validate': {
+ key: [{
+ 'id': self.page.validation_id(key),
+ 'otp_sms': otp_sms,
+ 'type': 'SMS'
+ }]
+ }
+ }
+ headers = {'Content-Type': 'application/json'}
+ self.location(self.url + '/step', json=data, headers=headers)
+
+ saml = self.page.get_saml()
+ action = self.page.get_action()
+ self.location(action, data={'SAMLResponse': saml})
+
def post_sms_password(self, otp, otp_field_xpath):
data = {}
for k, v in self.recipient_form.items():
@@ -785,22 +831,8 @@ class CaisseEpargne(LoginBrowser, StatesMixin):
return self.end_sms_recipient(recipient, **params)
if 'otp_sms' in params:
- transactionid = re.search(r'transactionID=(.*)', self.page.url).group(1)
- self.request_sms.go(param=transactionid)
- validation = {}
- validation['validate'] = {}
- key = self.page.validate_key()
- validation['validate'][key] = []
- inner_param = {}
- inner_param['id'] = self.page.validation_id(key)
- inner_param['type'] = 'SMS'
- inner_param['otp_sms'] = params['otp_sms']
- validation['validate'][key].append(inner_param)
- headers = {'Content-Type': 'application/json', 'Accept': 'application/json, text/plain, */*'}
- self.location(self.url + '/step', data=json.dumps(validation), headers=headers)
- saml = self.page.get_saml()
- action = self.page.get_action()
- self.location(action, data={'SAMLResponse': saml})
+ self.otp_sms_validation(params['otp_sms'])
+
if self.authent.is_here():
self.page.go_on()
return self.facto_post_recip(recipient)
diff --git a/modules/caissedepargne/cenet/pages.py b/modules/caissedepargne/cenet/pages.py
index def03cdd2c33fab9e886ff94ee3ca05ccad4baad..80cf4b89bc4ef1a20caab6a8252a4c1b88bc152e 100644
--- a/modules/caissedepargne/cenet/pages.py
+++ b/modules/caissedepargne/cenet/pages.py
@@ -125,7 +125,7 @@ class CenetLoanPage(LoggedPage, CenetJsonPage):
class item(ItemElement):
klass = Loan
- obj_id = CleanText(Dict('IdentifiantUniqueContrat'))
+ obj_id = CleanText(Dict('IdentifiantUniqueContrat'), replace=[(' ', '-')])
obj_label = CleanText(Dict('Libelle'))
obj_total_amount = CleanDecimal(Dict('MontantInitial/Valeur'))
obj_currency = Currency(Dict('MontantInitial/Devise'))
@@ -136,16 +136,25 @@ class CenetLoanPage(LoggedPage, CenetJsonPage):
obj_next_payment_amount = CleanDecimal(Dict('MontantProchaineEcheance/Valeur'))
def obj_subscription_date(self):
- date = CleanDecimal(Dict('DateDebutEffet'))(self) / 1000
- return datetime.fromtimestamp(date).date()
+ sub_date = Dict('DateDebutEffet')(self)
+ if sub_date:
+ date = CleanDecimal().filter(sub_date) / 1000
+ return datetime.fromtimestamp(date).date()
+ return NotAvailable
def obj_maturity_date(self):
- date = CleanDecimal(Dict('DateDerniereEcheance'))(self) / 1000
- return datetime.fromtimestamp(date).date()
+ mat_date = Dict('DateDerniereEcheance')(self)
+ if mat_date:
+ date = CleanDecimal().filter(mat_date) / 1000
+ return datetime.fromtimestamp(date).date()
+ return NotAvailable
def obj_next_payment_date(self):
- date = CleanDecimal(Dict('DateProchaineEcheance'))(self) / 1000
- return datetime.fromtimestamp(date).date()
+ next_date = Dict('DateProchaineEcheance')(self)
+ if next_date:
+ date = CleanDecimal().filter(next_date) / 1000
+ return datetime.fromtimestamp(date).date()
+ return NotAvailable
class CenetCardsPage(LoggedPage, CenetJsonPage):
diff --git a/modules/caissedepargne/module.py b/modules/caissedepargne/module.py
index ef83f173daea134638cd0bc64f31008fc662a8b3..43eb6d385039e9c9d21bb9482ab0cf5806b3f407 100644
--- a/modules/caissedepargne/module.py
+++ b/modules/caissedepargne/module.py
@@ -95,6 +95,9 @@ class CaisseEpargneModule(Module, CapBankWealth, CapBankTransferAddRecipient, Ca
return self.browser.iter_recipients(origin_account)
def init_transfer(self, transfer, **params):
+ if 'otp_sms' in params:
+ return self.browser.otp_sms_continue_transfer(transfer, **params)
+
self.logger.info('Going to do a new transfer')
transfer.label = ' '.join(w for w in re.sub('[^0-9a-zA-Z/\-\?:\(\)\.,\'\+ ]+', '', transfer.label).split()).upper()
if transfer.account_iban:
diff --git a/modules/caissedepargne/pages.py b/modules/caissedepargne/pages.py
index e8443d2cec217d2838b1d468994b5f5967569c0d..8400c8a50be6178c2353752d1da8d39cfdad8861 100644
--- a/modules/caissedepargne/pages.py
+++ b/modules/caissedepargne/pages.py
@@ -34,7 +34,7 @@ from weboob.browser.filters.standard import Date, CleanDecimal, Regexp, CleanTex
from weboob.browser.filters.html import Link, Attr, TableCell
from weboob.capabilities import NotAvailable
from weboob.capabilities.bank import (
- Account, Investment, Recipient, TransferError, TransferBankError, Transfer,
+ Account, Investment, Recipient, TransferBankError, Transfer,
AddRecipientBankError, Loan,
)
from weboob.capabilities.bill import DocumentTypes, Subscription, Document
@@ -1016,8 +1016,7 @@ class TransferPage(TransferErrorPage, IndexPage):
def get_origin_account_value(self, account):
origin_value = [Attr('.', 'value')(o) for o in self.doc.xpath('//select[@id="MM_VIREMENT_SAISIE_VIREMENT_ddlCompteDebiter"]/option') if
Regexp(CleanText('.'), '- (\d+)')(o) in account.id]
- if len(origin_value) != 1:
- raise TransferError('error during origin account matching')
+ assert len(origin_value) == 1, 'error during origin account matching'
return origin_value[0]
def get_recipient_value(self, recipient):
@@ -1027,8 +1026,7 @@ class TransferPage(TransferErrorPage, IndexPage):
elif recipient.category == u'Interne':
recipient_value = [Attr('.', 'value')(o) for o in self.doc.xpath(self.RECIPIENT_XPATH) if
Regexp(CleanText('.'), '- (\d+)', default=NotAvailable)(o) and Regexp(CleanText('.'), '- (\d+)', default=NotAvailable)(o) in recipient.id]
- if len(recipient_value) != 1:
- raise TransferError('error during recipient matching')
+ assert len(recipient_value) == 1, 'error during recipient matching'
return recipient_value[0]
def init_transfer(self, account, recipient, transfer):
@@ -1047,15 +1045,30 @@ class TransferPage(TransferErrorPage, IndexPage):
class iter_recipients(MyRecipients):
pass
- def continue_transfer(self, origin_label, recipient, label):
+ def get_transfer_type(self):
+ sepa_inputs = self.doc.xpath('//input[contains(@id, "MM_VIREMENT_SAISIE_VIREMENT_SEPA")]')
+ intra_inputs = self.doc.xpath('//input[contains(@id, "MM_VIREMENT_SAISIE_VIREMENT_INTRA")]')
+
+ assert not (len(sepa_inputs) and len(intra_inputs)), 'There are sepa and intra transfer forms'
+
+ transfer_type = None
+ if len(sepa_inputs):
+ transfer_type = 'sepa'
+ elif len(intra_inputs):
+ transfer_type = 'intra'
+ assert transfer_type, 'Sepa nor intra transfer form was found'
+ return transfer_type
+
+ def continue_transfer(self, origin_label, recipient_label, label):
form = self.get_form(id='main')
- type_ = 'intra' if recipient.category == u'Interne' else 'sepa'
+
+ transfer_type = self.get_transfer_type()
fill = lambda s, t: s % (t.upper(), t.capitalize())
form['__EVENTTARGET'] = 'MM$VIREMENT$m_WizardBar$m_lnkNext$m_lnkButton'
- form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtIdentBenef', type_)] = recipient.label
- form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtIdent', type_)] = origin_label
- form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtRef', type_)] = label
- form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtMotif', type_)] = label
+ form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtIdentBenef', transfer_type)] = recipient_label
+ form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtIdent', transfer_type)] = origin_label
+ form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtRef', transfer_type)] = label
+ form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtMotif', transfer_type)] = label
form.submit()
def go_add_recipient(self):
@@ -1082,33 +1095,43 @@ class TransferConfirmPage(TransferErrorPage, IndexPage):
form['__EVENTTARGET'] = 'MM$VIREMENT$m_WizardBar$m_lnkNext$m_lnkButton'
form.submit()
- def create_transfer(self, account, recipient, transfer):
- transfer = Transfer()
- transfer.currency = FrenchTransaction.Currency('.//tr[td[contains(text(), "Montant")]]/td[not(@class)] | \
- .//tr[th[contains(text(), "Montant")]]/td[not(@class)]')(self.doc)
+ def update_transfer(self, transfer, account=None, recipient=None):
+ """update `Transfer` object with web information to use transfer check"""
+
+ # transfer informations
+ transfer.label = (
+ CleanText(u'.//tr[td[contains(text(), "Motif de l\'opération")]]/td[not(@class)]')(self.doc) or
+ CleanText(u'.//tr[td[contains(text(), "Libellé")]]/td[not(@class)]')(self.doc) or
+ CleanText(u'.//tr[th[contains(text(), "Libellé")]]/td[not(@class)]')(self.doc)
+ )
+ transfer.exec_date = Date(CleanText('.//tr[th[contains(text(), "En date du")]]/td[not(@class)]'), dayfirst=True)(self.doc)
transfer.amount = CleanDecimal('.//tr[td[contains(text(), "Montant")]]/td[not(@class)] | \
.//tr[th[contains(text(), "Montant")]]/td[not(@class)]', replace_dots=True)(self.doc)
- transfer.account_iban = account.iban
- if recipient.category == u'Externe':
- for word in Upper(CleanText(u'.//tr[th[contains(text(), "Compte à créditer")]]/td[not(@class)]'))(self.doc).split():
- if is_iban_valid(word):
- transfer.recipient_iban = word
- break
+ transfer.currency = FrenchTransaction.Currency('.//tr[td[contains(text(), "Montant")]]/td[not(@class)] | \
+ .//tr[th[contains(text(), "Montant")]]/td[not(@class)]')(self.doc)
+
+ # recipient transfer informations, update information if there is no OTP SMS validation
+ if recipient:
+ transfer.recipient_label = recipient.label
+ transfer.recipient_id = recipient.id
+
+ if recipient.category == u'Externe':
+ for word in Upper(CleanText(u'.//tr[th[contains(text(), "Compte à créditer")]]/td[not(@class)]'))(self.doc).split():
+ if is_iban_valid(word):
+ transfer.recipient_iban = word
+ break
+ else:
+ assert False, 'Unable to find IBAN (original was %s)' % recipient.iban
else:
- raise TransferError('Unable to find IBAN (original was %s)' % recipient.iban)
- else:
- transfer.recipient_iban = recipient.iban
- transfer.account_id = unicode(account.id)
- transfer.recipient_id = unicode(recipient.id)
- transfer.exec_date = Date(CleanText('.//tr[th[contains(text(), "En date du")]]/td[not(@class)]'), dayfirst=True)(self.doc)
- transfer.label = (CleanText(u'.//tr[td[contains(text(), "Motif de l\'opération")]]/td[not(@class)]')(self.doc) or
- CleanText(u'.//tr[td[contains(text(), "Libellé")]]/td[not(@class)]')(self.doc) or
- CleanText(u'.//tr[th[contains(text(), "Libellé")]]/td[not(@class)]')(self.doc))
- transfer.account_label = account.label
- transfer.recipient_label = recipient.label
- transfer._account = account
- transfer._recipient = recipient
- transfer.account_balance = account.balance
+ transfer.recipient_iban = recipient.iban
+
+ # origin account transfer informations, update information if there is no OTP SMS validation
+ if account:
+ transfer.account_id = account.id
+ transfer.account_iban = account.iban
+ transfer.account_label = account.label
+ transfer.account_balance = account.balance
+
return transfer
@@ -1133,7 +1156,7 @@ class ProTransferConfirmPage(TransferConfirmPage):
t.recipient_iban = word
break
else:
- raise TransferError('Unable to find IBAN (original was %s)' % recipient.iban)
+ assert False, 'Unable to find IBAN (original was %s)' % recipient.iban
else:
t.recipient_iban = recipient.iban
t.recipient_iban = recipient.iban
diff --git a/modules/cmes/browser.py b/modules/cmes/browser.py
index 7781a67e4d16638c95d7e185fddc63bf9cb9b685..5cbb319b8ef9cbf3d14162a208bd11ccd082afa6 100644
--- a/modules/cmes/browser.py
+++ b/modules/cmes/browser.py
@@ -34,7 +34,7 @@ class CmesBrowser(LoginBrowser):
accounts = URL('(?P.*)fr/espace/devbavoirs.aspx\?mode=net&menu=cpte$', AccountsPage)
fcpe_investment = URL(r'/fr/.*GoPositionsParFond.*',
r'/fr/espace/devbavoirs.aspx\?.*SituationParFonds.*GoOpenDetailFond.*',
- r'(?P.*)fr/espace/devbavoirs.aspx\?_tabi=(C|I1)&a_mode=net&a_menu=cpte&_pid=SituationGlobale&_fid=GoPositionsParFond',
+ r'(?P.*)fr/espace/devbavoirs.aspx\?_tabi=(C|I1)&a_mode=net&a_menu=cpte&_pid=Situation(Globale|ParPlan)&_fid=GoPositionsParFond',
r'(?P.*)fr/espace/devbavoirs.aspx\?_tabi=(C|I1)&a_mode=net&a_menu=cpte&_pid=SituationParFonds.*', FCPEInvestmentPage)
ccb_investment = URL(r'(?P.*)fr/espace/devbavoirs.aspx\?_tabi=C&a_mode=net&a_menu=cpte&_pid=SituationGlobale(&_fid=GoCCB|&k_support=CCB&_fid=GoPrint)', CCBInvestmentPage)
history = URL('(?P.*)fr/espace/devbavoirs.aspx\?mode=net&menu=cpte&page=operations',
diff --git a/modules/cmso/par/browser.py b/modules/cmso/par/browser.py
index a941ea12cf801ea8abd53accec7cf04eabcbedfb..83717f3d171d22e3a0d42d75f6d6c2d2d88a9dd3 100644
--- a/modules/cmso/par/browser.py
+++ b/modules/cmso/par/browser.py
@@ -82,7 +82,8 @@ class CmsoParBrowser(LoginBrowser, StatesMixin):
login = URL('/securityapi/tokens',
'/auth/checkuser', LoginPage)
logout = URL('/securityapi/revoke',
- '/auth/errorauthn', LogoutPage)
+ '/auth/errorauthn',
+ '/\/auth/errorauthn', LogoutPage)
infos = URL('/comptes/', InfosPage)
accounts = URL('/domiapi/oauth/json/accounts/synthese(?P.*)', AccountsPage)
history = URL('/domiapi/oauth/json/accounts/(?P.*)', HistoryPage)
diff --git a/modules/cmso/par/pages.py b/modules/cmso/par/pages.py
index 77a13a7388b042d772e76ffacaca1e781c2e4fc7..df16bfdc726dec72fa69587eac429741a115c073 100644
--- a/modules/cmso/par/pages.py
+++ b/modules/cmso/par/pages.py
@@ -289,6 +289,7 @@ class Transaction(FrenchTransaction):
(re.compile(r'^(?PVIR.*)'), FrenchTransaction.TYPE_TRANSFER),
(re.compile(r'^(?PANN.*)'), FrenchTransaction.TYPE_PAYBACK),
(re.compile(r'^(?P(VRST|VERSEMENT).*)'), FrenchTransaction.TYPE_DEPOSIT),
+ (re.compile(r'^(?PCHQ.*)'), FrenchTransaction.TYPE_CHECK),
(re.compile(r'^(?P.*)'), FrenchTransaction.TYPE_BANK)
]
diff --git a/modules/cmso/par/transfer_pages.py b/modules/cmso/par/transfer_pages.py
index d083620732e6f449fce283a0e279ba62529e13d0..eea18b7def0040f82ecd01d5b99a24041908b71d 100644
--- a/modules/cmso/par/transfer_pages.py
+++ b/modules/cmso/par/transfer_pages.py
@@ -119,6 +119,9 @@ class TransferInfoPage(LoggedPage, JsonPage):
@method
class iter_external_recipients(DictElement):
+ def condition(self):
+ return bool(self.el.get('listBeneficiaries'))
+
item_xpath = 'listBeneficiaries'
ignore_duplicate = True
diff --git a/modules/cragr/api/transfer_pages.py b/modules/cragr/api/transfer_pages.py
index 32a0d0cb005ee2de6eeea52de983120b720da1d0..13aa393d421c1f1f01a75e39da3320a3a8ea6278 100644
--- a/modules/cragr/api/transfer_pages.py
+++ b/modules/cragr/api/transfer_pages.py
@@ -27,7 +27,7 @@ from weboob.capabilities.bank import (
Account, Recipient, Transfer, TransferBankError,
)
from weboob.browser.filters.standard import (
- CleanDecimal, Date, CleanText,
+ CleanDecimal, Date, CleanText, Coalesce,
)
from weboob.browser.filters.json import Dict
@@ -47,7 +47,10 @@ class RecipientsPage(LoggedPage, JsonPage):
klass = Account
obj_id = obj_number = Dict('accountNumber')
- obj_label = Dict('accountNatureLongLabel')
+ obj_label = Coalesce(
+ Dict('accountNatureLongLabel', default=''),
+ Dict('accountNatureShortLabel', default='')
+ )
obj_iban = Dict('ibanCode')
obj_currency = Dict('currencyCode')
diff --git a/modules/cragr/web/browser.py b/modules/cragr/web/browser.py
index 3f8f8ab716b3d53f7197d9d196b46b0956a46c9a..5fe416e43f9b3b36ba287e4641180ee43a7afb4b 100644
--- a/modules/cragr/web/browser.py
+++ b/modules/cragr/web/browser.py
@@ -728,7 +728,9 @@ class Cragr(LoginBrowser, StatesMixin):
def execute_transfer(self, transfer, **params):
assert self.transfer_page.is_here()
assert self.page.is_confirm()
+
self.page.submit_confirm()
+ self.page.check_error()
assert self.page.is_sent()
return self.page.get_transfer()
diff --git a/modules/cragr/web/pages.py b/modules/cragr/web/pages.py
index 12b194767c99805cfa65b1fd9282aa0466d53a11..8153266bd9ba39d087663b09280b0854b2bff39b 100644
--- a/modules/cragr/web/pages.py
+++ b/modules/cragr/web/pages.py
@@ -1414,8 +1414,16 @@ class TransferPage(RecipientAddingMixin, CollectePageMixin, MyLoggedPage, BasePa
form['DEVISE'] = 'EUR'
form.submit()
+ def check_error(self):
+ # this is for transfer error, it's not a `AddRecipientBankError` but a `TransferBankError`
+
+ msg = CleanText('//tr[@bgcolor="#C74545"]', default='')(self.doc) # there is no id, class or anything...
+ if msg:
+ raise TransferBankError(message=msg)
+
def check_recipient_error(self):
# this is a copy-paste from RecipientMiscPage, i can't test if it works on this page...
+ # this is for add recipient by initiate transfer
msg = CleanText('//tr[@bgcolor="#C74545"]', default='')(self.doc) # there is no id, class or anything...
if msg:
diff --git a/modules/creditcooperatif/__init__.py b/modules/creditcooperatif/__init__.py
index 3c63b907c6b4cc8e72d925b571ee8da2afecaf91..8d91faf945308e78261dd2c9a4d3f7956adbe457 100644
--- a/modules/creditcooperatif/__init__.py
+++ b/modules/creditcooperatif/__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/creditcooperatif/caisseepargne_browser.py b/modules/creditcooperatif/caisseepargne_browser.py
index 6f3995a4bf0bc5edab73ee8a275b3d6a9c236b52..61fbdb04e4bd4d31c8bcc97f0b48f08cb03e363e 100644
--- a/modules/creditcooperatif/caisseepargne_browser.py
+++ b/modules/creditcooperatif/caisseepargne_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 weboob.browser import AbstractBrowser
diff --git a/modules/creditcooperatif/cenet_browser.py b/modules/creditcooperatif/cenet_browser.py
index d8c59a14c5d3d3ec8c95887d898775d3623af2c7..37f59e95606303fd5e04d78ee9cb0ebc60eb9618 100644
--- a/modules/creditcooperatif/cenet_browser.py
+++ b/modules/creditcooperatif/cenet_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 weboob.browser import AbstractBrowser
diff --git a/modules/creditcooperatif/linebourse_browser.py b/modules/creditcooperatif/linebourse_browser.py
index a5ebe807914fe23f84dbbd814bc284bdc1f19904..62730c7025e17d94ff6fcf8181a66cabb035174e 100644
--- a/modules/creditcooperatif/linebourse_browser.py
+++ b/modules/creditcooperatif/linebourse_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 weboob.browser import AbstractBrowser
diff --git a/modules/creditcooperatif/module.py b/modules/creditcooperatif/module.py
index dd0a138ac788d7fdfd44fcc22cc713c48075ed19..e2aba9b3b7d0e4be764936ad62a82d3601ca0750 100644
--- a/modules/creditcooperatif/module.py
+++ b/modules/creditcooperatif/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 .
from weboob.capabilities.bank import CapBankTransferAddRecipient
@@ -34,7 +34,7 @@ class CreditCooperatifModule(AbstractModule, CapBankTransferAddRecipient, CapPro
EMAIL = 'weboob@kevin.pouget.me'
VERSION = '1.6'
DESCRIPTION = u'Crédit Coopératif'
- LICENSE = 'AGPLv3+'
+ LICENSE = 'LGPLv3+'
auth_type = {'particular': "Interface Particuliers",
'weak' : "Code confidentiel (pro)",
'strong': "Sesame (pro)"}
diff --git a/modules/creditcooperatif/proxy_browser.py b/modules/creditcooperatif/proxy_browser.py
index 903b9a2e192c6d175b76f4d71f77dfe710296201..ec193ffbb0a10aa2669c55fa81d99f6d6d9a4994 100644
--- a/modules/creditcooperatif/proxy_browser.py
+++ b/modules/creditcooperatif/proxy_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 weboob.browser.switch import SwitchingBrowser
diff --git a/modules/creditcooperatif/test.py b/modules/creditcooperatif/test.py
index 52fe2a84d184dc6c23ce831c4be573a315ea6004..3ab64bd6d667eb7b8b08dd00d7f9432c795da3ce 100644
--- a/modules/creditcooperatif/test.py
+++ b/modules/creditcooperatif/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 .
diff --git a/modules/creditdunord/browser.py b/modules/creditdunord/browser.py
index b61c661d6ef41c62ab0ba1b4b600260a957854a0..092327b3a3682d4b00e8c9e3e2d6483079d29e2c 100644
--- a/modules/creditdunord/browser.py
+++ b/modules/creditdunord/browser.py
@@ -47,7 +47,7 @@ class CreditDuNordBrowser(LoginBrowser):
transactions = URL('/vos-comptes/IPT/appmanager/transac/particuliers\?_nfpb=true(.*)', TransactionsPage)
protransactions = URL('/vos-comptes/(.*)/transac/(professionnels|entreprises)', ProTransactionsPage)
iban = URL('/vos-comptes/IPT/cdnProxyResource/transacClippe/RIB_impress.asp', IbanPage)
- account_type_page = URL("/icd/zco/public-data/public-ws-menuespaceperso.json", AccountTypePage)
+ account_type_page = URL('/icd/zco/data/public-ws-menuespaceperso.json', AccountTypePage)
labels_page = URL("/icd/zco/public-data/ws-menu.json", LabelsPage)
profile_page = URL("/icd/zco/data/user.json", ProfilePage)
bypass_rgpd = URL('/icd/zcd/data/gdpr-get-out-zs-client.json', RgpdPage)
@@ -57,17 +57,11 @@ class CreditDuNordBrowser(LoginBrowser):
self.BASEURL = "https://%s" % website
super(CreditDuNordBrowser, self).__init__(*args, **kwargs)
- def is_logged(self):
+ @property
+ def logged(self):
return self.page is not None and not self.login.is_here() and \
not self.page.doc.xpath(u'//b[contains(text(), "vous devez modifier votre code confidentiel")]')
- def home(self):
- if self.is_logged():
- self.location("/icd/zco/")
- self.accounts.go(account_type=self.account_type)
- else:
- self.do_login()
-
def do_login(self):
self.login.go().login(self.username, self.password)
if self.accounts.is_here():
@@ -84,7 +78,7 @@ class CreditDuNordBrowser(LoginBrowser):
# we'll check what's happening.
assert False, "Still on login page."
- if not self.is_logged():
+ if not self.logged:
raise BrowserIncorrectPassword()
def _iter_accounts(self):
diff --git a/modules/creditdunord/pages.py b/modules/creditdunord/pages.py
index c3085c477a328c681150365f664e68c7ae48ddb9..19b9b4c3eb11c76c7b39b223c845f4558015635a 100755
--- a/modules/creditdunord/pages.py
+++ b/modules/creditdunord/pages.py
@@ -98,12 +98,10 @@ class CDNVirtKeyboard(GridVirtKeyboard):
class RedirectPage(HTMLPage):
- def on_load(self):
- for script in self.doc.xpath('//script'):
- self.browser.location(re.search(r'href="([^"]+)"', script.text).group(1))
+ pass
-class EntryPage(HTMLPage):
+class EntryPage(LoggedPage, HTMLPage):
pass
diff --git a/modules/creditdunordpee/module.py b/modules/creditdunordpee/module.py
index 24d4d5f246813893c639e0accd13e173522d8406..5b80fc4351394e0a0e58884cb784fa3602ac98f7 100644
--- a/modules/creditdunordpee/module.py
+++ b/modules/creditdunordpee/module.py
@@ -35,7 +35,7 @@ class CreditdunordpeeModule(AbstractModule, CapBank):
MAINTAINER = u'Ludovic LANGE'
EMAIL = 'llange@users.noreply.github.com'
LICENSE = 'LGPLv3+'
- VERSION = '1.4'
+ VERSION = '1.6'
CONFIG = BackendConfig(
ValueBackendPassword('login', label='Identifiant', masked=False),
ValueBackendPassword('password', label='Code secret', regexp='^(\d{6})$'),
diff --git a/modules/creditmutuel/browser.py b/modules/creditmutuel/browser.py
index ffd9f8e14c82e00c5eba9840cd9d4d5e35619610..f6ef29c3c207cd54d3766a2f9a0dcb03110557bf 100644
--- a/modules/creditmutuel/browser.py
+++ b/modules/creditmutuel/browser.py
@@ -21,7 +21,6 @@ from __future__ import unicode_literals
import re
from datetime import datetime
-from dateutil.relativedelta import relativedelta
from itertools import groupby
from operator import attrgetter
@@ -300,20 +299,18 @@ class CreditMutuelBrowser(LoginBrowser, StatesMixin):
if account.type == Account.TYPE_SAVINGS and "Capital Expansion" in account.label:
self.page.go_on_history_tab()
- # Getting about 6 months history on new website
+ if self.li.is_here():
+ return self.page.iter_history()
+
if self.is_new_website and self.page:
try:
- # Submit search form two times, at first empty, then filled based on available fields
- for x in range(2):
- form = self.page.get_form(id="I1:fm", submit='//input[@name="_FID_DoActivateSearch"]')
- if x == 1:
- form.update({
- next(k for k in form.keys() if "DateStart" in k): (datetime.now() - relativedelta(months=7)).strftime('%d/%m/%Y'),
- next(k for k in form.keys() if "DateEnd" in k): datetime.now().strftime('%d/%m/%Y')
- })
- for k in form.keys():
- if "_FID_Do" in k and "DoSearch" not in k:
- form.pop(k, None)
+ for page in range(1, 50):
+ # Need to reach the page with all transactions
+ if not self.page.has_more_operations():
+ break
+ form = self.page.get_form(id="I1:P:F")
+ form['_FID_DoLoadMoreTransactions'] = ''
+ form['_wxf2_pseq'] = page
form.submit()
# IndexError when form xpath returns [], StopIteration if next called on empty iterable
except (StopIteration, FormNotFound):
@@ -339,9 +336,6 @@ class CreditMutuelBrowser(LoginBrowser, StatesMixin):
break
raise
- if self.li.is_here():
- return self.page.iter_history()
-
if not self.operations.is_here():
return iter([])
diff --git a/modules/creditmutuel/module.py b/modules/creditmutuel/module.py
index 237a88ca1f5a7550d23e32536a16fc5c061aab7f..5228acf79c0976460f16e39017d697c40fd7f865 100644
--- a/modules/creditmutuel/module.py
+++ b/modules/creditmutuel/module.py
@@ -24,7 +24,7 @@ from decimal import Decimal
from weboob.capabilities.base import find_object, NotAvailable
from weboob.capabilities.bank import (
CapBankWealth, CapBankTransferAddRecipient, AccountNotFound, RecipientNotFound,
- Account, TransferError
+ Account,
)
from weboob.capabilities.contact import CapContact
from weboob.capabilities.profile import CapProfile
@@ -114,14 +114,10 @@ class CreditMutuelModule(
else:
recipient = find_object(self.iter_transfer_recipients(account.id), id=transfer.recipient_id, error=RecipientNotFound)
- try:
- assert account.id.isdigit()
- except AssertionError:
- raise TransferError('Account id is invalid')
- try: # quantize to show 2 decimals.
- amount = Decimal(transfer.amount).quantize(Decimal(10) ** -2)
- except ValueError:
- raise TransferError('Transfer amount is invalid')
+ assert account.id.isdigit(), 'Account id is invalid'
+
+ # quantize to show 2 decimals.
+ amount = Decimal(transfer.amount).quantize(Decimal(10) ** -2)
# drop characters that can crash website
transfer.label = transfer.label.encode('cp1252', errors="ignore").decode('cp1252')
diff --git a/modules/creditmutuel/pages.py b/modules/creditmutuel/pages.py
index 4213db9f4ecf79e8d82af461e0f1de19892af921..b1966916ac2e8ffb87d29c53713c091e6caef973 100644
--- a/modules/creditmutuel/pages.py
+++ b/modules/creditmutuel/pages.py
@@ -41,7 +41,7 @@ from weboob.exceptions import (
from weboob.capabilities import NotAvailable
from weboob.capabilities.base import empty, find_object
from weboob.capabilities.bank import (
- Account, Investment, Recipient, TransferError, TransferBankError,
+ Account, Investment, Recipient, TransferBankError,
Transfer, AddRecipientBankError, AddRecipientStep, Loan,
)
from weboob.capabilities.contact import Advisor
@@ -697,6 +697,9 @@ class OperationsPage(LoggedPage, HTMLPage):
else:
return a.attrib['href']
+ def has_more_operations(self):
+ return bool(self.doc.xpath('//a/span[contains(text(), "Plus d\'opérations")]'))
+
class CardsOpePage(OperationsPage):
def select_card(self, card_number):
@@ -1105,10 +1108,16 @@ class LIAccountsPage(LoggedPage, HTMLPage):
obj__is_inv = True
obj__card_number = None
+ @pagination
@method
class iter_history(ListElement):
item_xpath = '//table[has-class("liste")]/tbody/tr'
+ def next_page(self):
+ next_page = Link('//a[img[@alt="Page suivante"]]', default=None)(self.el)
+ if next_page:
+ return next_page
+
class item(ItemElement):
klass = FrenchTransaction
@@ -1352,7 +1361,7 @@ class InternalTransferPage(LoggedPage, HTMLPage):
if account.endswith(acct):
return inp.attrib['value']
else:
- raise TransferError("account %s not found" % account)
+ assert False, 'Transfer origin account %s not found' % account
def get_from_account_index(self, account):
return self.get_account_index('data_input_indiceCompteADebiter', account)
@@ -1400,8 +1409,8 @@ class InternalTransferPage(LoggedPage, HTMLPage):
def check_success(self):
# look for the known "all right" message
- if not self.doc.xpath('//span[contains(text(), $msg)]', msg=self.READY_FOR_TRANSFER_MSG):
- raise TransferError('The expected message "%s" was not found.' % self.READY_FOR_TRANSFER_MSG)
+ assert self.doc.xpath('//span[contains(text(), $msg)]', msg=self.READY_FOR_TRANSFER_MSG), \
+ 'The expected transfer message "%s" was not found.' % self.READY_FOR_TRANSFER_MSG
def check_data_consistency(self, account_id, recipient_id, amount, reason):
assert account_id in CleanText('//div[div[p[contains(text(), "Compte à débiter")]]]',
@@ -1451,8 +1460,8 @@ class InternalTransferPage(LoggedPage, HTMLPage):
transfer_ok_message = ['Votre virement a été exécuté',
'Ce virement a été enregistré ce jour',
'Ce virement a été enregistré ce jour']
- if not any(msg for msg in transfer_ok_message if msg in content):
- raise TransferError('The expected message "%s" was not found.' % transfer_ok_message)
+ assert any(msg for msg in transfer_ok_message if msg in content), \
+ 'The expected transfer message "%s" was not found.' % transfer_ok_message
exec_date, r_amount, currency = self.check_data_consistency(transfer.account_id, transfer.recipient_id, transfer.amount, transfer.label)
@@ -1465,7 +1474,7 @@ class InternalTransferPage(LoggedPage, HTMLPage):
if valid_state in state:
break
else:
- raise TransferError('Transfer state is %r' % state)
+ assert False, 'Transfer state is %r' % state
assert transfer.amount == r_amount
assert transfer.exec_date == exec_date
@@ -1887,7 +1896,7 @@ class NewCardsListPage(LoggedPage, HTMLPage):
def condition(self):
# Numerous cards are not differed card, we keep the card only if there is a coming
- return CleanText('.//div[1]/p')(self) == 'Active' and 'Dépenses' in CleanText('.//tr[1]/td/a[contains(@id,"C:more-card")]')(self)
+ return 'Dépenses' in CleanText('.//tr[1]/td/a[contains(@id,"C:more-card")]')(self) and (CleanText('.//div[1]/p')(self) == 'Active' or Field('coming')(self) != 0)
obj_balance = 0
obj_type = Account.TYPE_CARD
@@ -1937,18 +1946,23 @@ class NewCardsListPage(LoggedPage, HTMLPage):
def parse(self, el):
# We have to reach the good page with the information of the type of card
history_page = self.page.browser.open(Field('_link_id')(self)).page
- card_type_page = Link('//div/ul/li/a[contains(text(), "Fonctions")]')(history_page.doc)
- doc = self.page.browser.open(card_type_page).page.doc
- card_type_line = doc.xpath('//tbody/tr[th[contains(text(), "Débit des paiements")]]')
- if card_type_line:
- if CleanText('./td')(card_type_line[0]) != 'Différé':
- raise SkipItem()
- elif doc.xpath('//div/p[contains(text(), "Vous n\'avez pas l\'autorisation")]'):
- self.logger.warning("The user can't reach this page")
- elif doc.xpath('//td[contains(text(), "Problème technique")]'):
- raise BrowserUnavailable(CleanText(doc.xpath('//td[contains(text(), "Problème technique")]'))(self))
- else:
- assert False, 'xpath for card type information could have changed'
+ card_type_page = Link('//div/ul/li/a[contains(text(), "Fonctions")]', default=NotAvailable)(history_page.doc)
+ if card_type_page:
+ doc = self.page.browser.open(card_type_page).page.doc
+ card_type_line = doc.xpath('//tbody/tr[th[contains(text(), "Débit des paiements")]]')
+ if card_type_line:
+ if CleanText('./td')(card_type_line[0]) != 'Différé':
+ raise SkipItem()
+ elif doc.xpath('//div/p[contains(text(), "Vous n\'avez pas l\'autorisation")]'):
+ self.logger.warning("The user can't reach this page")
+ elif doc.xpath('//td[contains(text(), "Problème technique")]'):
+ raise BrowserUnavailable(CleanText(doc.xpath('//td[contains(text(), "Problème technique")]'))(self))
+ else:
+ assert False, 'xpath for card type information could have changed'
+ elif not CleanText('//ul//a[contains(@title, "Consulter le différé")]')(history_page.doc):
+ # If the card is not active the "Fonction" button is absent.
+ # However we can check "Consulter le différé" button is present
+ raise SkipItem()
def get_unavailable_cards(self):
cards = []
diff --git a/modules/fortuneo/pages/accounts_list.py b/modules/fortuneo/pages/accounts_list.py
index 0c18a5fe4db04387cbc60d3dda7eecd337c579bc..9ae95a975a37dcf9ced68058948422681ca9e299 100644
--- a/modules/fortuneo/pages/accounts_list.py
+++ b/modules/fortuneo/pages/accounts_list.py
@@ -467,9 +467,7 @@ class AccountsList(LoggedPage, HTMLPage):
if local_error_message:
raise BrowserUnavailable(CleanText('.')(local_error_message[0]))
- number = RawText('./a[contains(@class, "numero_compte")]')(cpt).replace(u'N° ', '')
-
- account.id = CleanText(None).filter(number).replace(u'N°', '')
+ account.id = account.number = CleanText('./a[contains(@class, "numero_compte")]/div')(cpt).replace(u'N° ', '')
account._ca = CleanText('./a[contains(@class, "numero_compte")]/@rel')(cpt)
account._card_links = []
diff --git a/modules/groupamaes/browser.py b/modules/groupamaes/browser.py
index 13e257e75037185f61607105b93858314de7a33e..28b151955214cde70c7f02b7c3bddddbab7611a9 100644
--- a/modules/groupamaes/browser.py
+++ b/modules/groupamaes/browser.py
@@ -17,54 +17,19 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see .
+from weboob.browser import AbstractBrowser, URL
-from weboob.browser import LoginBrowser, URL, need_login
-from weboob.exceptions import BrowserIncorrectPassword
-from weboob.tools.date import LinearDateGuesser
-from weboob.tools.capabilities.bank.transactions import sorted_transactions
-
-from .pages import LoginPage, LoginErrorPage, GroupamaesPage, GroupamaesPocketPage
+from .pages import LoginPage
__all__ = ['GroupamaesBrowser']
-class GroupamaesBrowser(LoginBrowser):
- BASEURL = 'https://www.gestion-epargne-salariale.fr'
+class GroupamaesBrowser(AbstractBrowser):
+ PARENT = 'cmes'
login = URL('/groupama-es/espace-client/fr/identification/authentification.html', LoginPage)
- login_error = URL('/groupama-es/fr/identification/default.cgi', LoginErrorPage)
- groupamaes_page = URL('/groupama-es/fr/espace/devbavoirs.aspx\?mode=net&menu=cpte(?P.*)', GroupamaesPage)
- groupamaes_pocket = URL('/groupama-es/fr/espace/devbavoirs.aspx\?_tabi=C&a_mode=net&a_mode=net&menu=cpte(?P.*)', GroupamaesPocketPage)
-
- def do_login(self):
- self.login.stay_or_go()
-
- self.page.login(self.username, self.password)
-
- if not self.page.logged or self.login_error.is_here():
- raise BrowserIncorrectPassword()
-
- @need_login
- def get_accounts_list(self):
- return self.groupamaes_page.stay_or_go(page='&page=situglob').iter_accounts()
-
- @need_login
- def get_history(self):
- transactions = list(self.groupamaes_page.go(page='&_pid=MenuOperations&_fid=GoOperationsTraitees').get_history(date_guesser=LinearDateGuesser()))
- transactions = sorted_transactions(transactions)
- return transactions
-
- @need_login
- def get_coming(self):
- transactions = list(self.groupamaes_page.go(page='&_pid=OperationsTraitees&_fid=GoWaitingOperations').get_history(date_guesser=LinearDateGuesser(), coming=True))
- transactions = sorted_transactions(transactions)
- return transactions
-
- @need_login
- def iter_investment(self, account):
- return self.groupamaes_pocket.go(page='&_pid=SituationParPlan&_fid=GoPositionsDetaillee').iter_investment(account.label)
- @need_login
- def iter_pocket(self, account):
- return self.groupamaes_pocket.go(page='&_pid=SituationParPlan&_fid=GoPositionsDetaillee').iter_pocket(account.label)
+ def __init__(self, login, password, baseurl, subsite, *args, **kwargs):
+ self.weboob = kwargs['weboob']
+ super(GroupamaesBrowser, self).__init__(login, password, baseurl, subsite, *args, **kwargs)
diff --git a/modules/groupamaes/module.py b/modules/groupamaes/module.py
index f771fafd74f4c2a9525f4c67a81b1e10c7d90b1c..260e5fa696e25f2b2eb85890dd9c1ab55ba3c06d 100644
--- a/modules/groupamaes/module.py
+++ b/modules/groupamaes/module.py
@@ -42,22 +42,24 @@ class GroupamaesModule(Module, CapBankPockets):
ValueBackendPassword('password', label='Mot de passe'))
def create_default_browser(self):
- return self.create_browser(self.config['login'].get(), self.config['password'].get())
+ return self.create_browser(
+ self.config['login'].get(),
+ self.config['password'].get(),
+ 'https://www.gestion-epargne-salariale.fr',
+ 'groupama-es/',
+ weboob=self.weboob)
- def iter_accounts(self):
- return self.browser.get_accounts_list()
+ def get_account(self, _id):
+ return find_object(self.browser.iter_accounts(), id=_id, error=AccountNotFound)
- def iter_coming(self, account):
- return self.browser.get_coming()
+ def iter_accounts(self):
+ return self.browser.iter_accounts()
def iter_history(self, account):
- return self.browser.get_history()
+ return self.browser.iter_history(account)
def iter_investment(self, account):
return self.browser.iter_investment(account)
- def get_account(self, _id):
- return find_object(self.browser.get_accounts_list(), id=_id, error=AccountNotFound)
-
def iter_pocket(self, account):
return self.browser.iter_pocket(account)
diff --git a/modules/groupamaes/pages.py b/modules/groupamaes/pages.py
index 593a69fd42a95fd199f0e2c9dee9b0c8a21e495d..369612fbf1c5f0f52cf19b86918347f493a1ae6d 100644
--- a/modules/groupamaes/pages.py
+++ b/modules/groupamaes/pages.py
@@ -17,205 +17,11 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see .
-from decimal import Decimal
-from datetime import date
-import re
-from collections import OrderedDict
+from weboob.browser.pages import AbstractPage
-from weboob.browser.pages import HTMLPage, LoggedPage
-from weboob.browser.elements import TableElement, ItemElement, method
-from weboob.browser.filters.standard import CleanText, CleanDecimal, Date, Env, Regexp, Field
-from weboob.browser.filters.html import TableCell
-from weboob.capabilities.bank import Account, Transaction, Investment, Pocket
-from weboob.capabilities.base import NotAvailable
-
-class LoginPage(HTMLPage):
- def login(self, login, passwd):
- form = self.get_form(nr=0)
- form['_cm_user'] = login
- form['_cm_pwd'] = passwd
- form.submit()
-
-
-class LoginErrorPage(HTMLPage):
- pass
-
-
-class GroupamaesPage(LoggedPage, HTMLPage):
- NEGATIVE_AMOUNT_LABELS = [u'Retrait', u'Transfert sortant', u'Frais']
-
- TYPES = OrderedDict([
- ('perco', Account.TYPE_PERCO),
- ('pee', Account.TYPE_PEE),
- ])
-
- @method
- class iter_accounts(TableElement):
- item_xpath = u'//table[@summary="Liste des échéances"]/tbody/tr'
- head_xpath = u'//table[@summary="Liste des échéances"]/thead/tr/th/text()'
-
- col_name = u'Plan'
- col_value = u'Evaluation en'
-
- class item(ItemElement):
- klass = Account
-
- def condition(self):
- return u'Vous n\'avez pas d\'avoirs.' not in CleanText(TableCell('name'))(self)
-
- obj_id = obj_label = CleanText(TableCell('name'))
- obj_balance = CleanDecimal(TableCell('value'), replace_dots=True, default=Decimal(0))
- obj_currency = CleanText(u'//table[@summary="Liste des échéances"]/thead/tr/th/small/text()')
-
- def obj_type(self):
- for k, v in self.page.TYPES.items():
- if k in Field('label')(self).lower():
- return v
- return Account.TYPE_PEE
-
- def iter_investment(self):
- item = self.doc.xpath(u'//table[@summary="Liste des échéances"]/tfoot/tr/td[@class="tot _c1 d _c1"]')[0]
- total = CleanDecimal(Regexp(CleanText('.'), '(.*) .*'),
- default=1, replace_dots=True)(item)
-
- item_xpath = u'((//table[@summary="Liste des échéances"])[1]/tbody/tr)[position() < last() and not(contains(./td[1]/@class, "tittot"))]'
-
- obj = None
- for tr in self.doc.xpath(item_xpath):
- tds = tr.xpath('./td')
- if len(tds) > 3:
- if obj is not None:
- obj.portfolio_share = (obj.valuation / total).quantize(Decimal('.0001'))
- yield obj
-
- obj = Investment()
- obj.label = CleanText('.')(tds[0])
- obj.vdate = date.today() # * En réalité derniere date de valorisation connue
- obj.unitvalue = CleanDecimal('.', replace_dots=True)(tds[2])
- obj.valuation = CleanDecimal('.', replace_dots=True)(tds[5])
- obj.quantity = CleanDecimal('.', replace_dots=True)(tds[4])
-
- elif obj is not None:
- obj.quantity += CleanDecimal('.', replace_dots=True)(tds[1])
- obj.valuation += CleanDecimal('.', replace_dots=True)(tds[2])
-
- if obj is not None:
- obj.portfolio_share = (obj.valuation / total).quantize(Decimal('.0001'))
- yield obj
-
- @method
- class get_history(TableElement):
- head_xpath = u'(//table[@summary="Liste des opérations"])[1]/thead/tr/th/text()'
- item_xpath = u'(//table[@summary="Liste des opérations"])[1]/tbody/tr'
-
- col_date = u'Date'
- col_operation = u'Opération'
- col_montant = [u'Montant net en EUR', 'Montant en']
-
- class item(ItemElement):
- klass = Transaction
-
- def condition(self):
- return u'Aucune opération' not in CleanText(TableCell('date'))(self)
-
- obj_date = Date(CleanText(TableCell('date')), Env('date_guesser'))
- obj_type = Transaction.TYPE_UNKNOWN
- obj_label = CleanText(TableCell('operation'))
-
- def obj_amount(self):
- amount = CleanDecimal(TableCell('montant'), replace_dots=True, default=NotAvailable)(self)
- if amount is NotAvailable:
- assert self.env.get('coming')
- return amount
-
- for pattern in GroupamaesPage.NEGATIVE_AMOUNT_LABELS:
- if Field('label')(self).startswith(pattern):
- amount = -amount
- return amount
-
- @method
- class get_list(TableElement):
- head_xpath = u'//table[@summary="Liste des opérations en attente"]/thead/tr/th/text()'
- item_xpath = u'//table[@summary="Liste des opérations en attente"]/tbody/tr'
-
- col_date = u'Date'
- col_operation = u'Opération'
- col_etat = u'Etat'
- col_montant = [u'Montant net en', re.compile(u'Montant en')]
- col_action = u'Action'
-
- class item(ItemElement):
- klass = Transaction
-
- def condition(self):
- return u'Aucune opération en attente' not in CleanText(TableCell('date'))(self)
-
- obj_date = Date(CleanText(TableCell('date')), Env('date_guesser'))
- obj_type = Transaction.TYPE_UNKNOWN
- obj_label = CleanText(TableCell('operation'))
- obj_amount = CleanDecimal(TableCell('montant'), replace_dots=True)
-
-
-class GroupamaesPocketPage(LoggedPage, HTMLPage):
- CONDITIONS = {
- u'immédiate': Pocket.CONDITION_AVAILABLE,
- u'à': Pocket.CONDITION_RETIREMENT,
- }
-
- def iter_investment(self, label):
- for tr in self.doc.xpath(u'//table[@summary="Liste des échéances"]/tbody/tr'):
- # list containing the numerical values
- # element 1 is the quantity
- # element 2 is the valuation
- tds = tr.findall('td[@class="i d"]')
- # var containing the label
- td_label = tr.find('td[@class="i g"]')
- inv = Investment()
-
- if len(tds) <= 2:
- continue
-
- inv.label = CleanText(td_label)(tr)
- inv.quantity = CleanDecimal(tds[1], replace_dots=True)(tr)
- inv.valuation = CleanDecimal(tds[2], replace_dots=True)(tr)
- if 'PEI' in label.split()[0]:
- label = 'PEE'
- if Regexp(CleanText(td_label), '\(([\w]+).*\)$')(tr) not in label.split()[0]:
- continue
-
- yield inv
-
- def iter_pocket(self, label):
- date_available, condition = 0, 0
- for tr in self.doc.xpath(u'//table[@summary="Liste des échéances"]/tbody/tr'):
- tds = tr.findall('td')
-
- pocket = Pocket()
- i = 0
-
- if len(tds) <= 2:
- continue
- elif len(tds) < 6:
- pocket.availability_date = date_available
- pocket.condition = condition
- else:
- i += 1
- pocket.availability_date = Date(Regexp(CleanText(tds[0]), '([\d\/]+)', default=NotAvailable), default=NotAvailable)(tr)
- date_available = pocket.availability_date
-
- pocket.condition = (Pocket.CONDITION_DATE if pocket.availability_date is not NotAvailable else
- self.CONDITIONS.get(CleanText(tds[0])(tr).lower().split()[0], Pocket.CONDITION_UNKNOWN))
- condition = pocket.condition
-
- pocket.label = CleanText(tds[i])(tr)
- pocket.quantity = CleanDecimal(tds[i+3], replace_dots=True)(tr)
- pocket.amount = CleanDecimal(tds[i+4], replace_dots=True)(tr)
-
- if 'PEI' in label.split()[0]:
- label = 'PEE'
- if Regexp(CleanText(tds[i]), '\(([\w]+).*\)$')(tr) not in label.split()[0]:
- continue
-
- yield pocket
+class LoginPage(AbstractPage):
+ PARENT = 'cmes'
+ PARENT_URL = 'login'
+ BROWSER_ATTR = 'package.browser.CmesBrowser'
diff --git a/modules/humanis/browser.py b/modules/humanis/browser.py
index 4f52f0f4753a80f379ae110c1fa7abf8efd11680..7b4cbb5fb6f8f83b289a8eb1bf2103a59db34fd5 100644
--- a/modules/humanis/browser.py
+++ b/modules/humanis/browser.py
@@ -25,7 +25,7 @@ from .pages import LoginPage
class HumanisBrowser(AbstractBrowser):
PARENT = 'cmes'
- login = URL('humanis/fr/identification/login.cgi', LoginPage)
+ login = URL('epsens/fr/identification/authentification.html', LoginPage)
def __init__(self, login, password, baseurl, subsite, *args, **kwargs):
self.weboob = kwargs['weboob']
diff --git a/modules/humanis/favicon.png b/modules/humanis/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..b32884a492805a8b31d32f538576fed568e25e61
Binary files /dev/null and b/modules/humanis/favicon.png differ
diff --git a/modules/ing/api/accounts_page.py b/modules/ing/api/accounts_page.py
index f3dccbda911b0f482e90572b82add63f23a41613..20f4367cec10fede14677756afd8fa88143f96c1 100644
--- a/modules/ing/api/accounts_page.py
+++ b/modules/ing/api/accounts_page.py
@@ -25,25 +25,36 @@ 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 (
- CleanText, CleanDecimal, Date, Eval, Lower, Format, Field,
+ CleanText, CleanDecimal, Date, Eval, Lower, Format, Field, Map, Upper,
)
from weboob.capabilities.bank import Account
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
class Transaction(FrenchTransaction):
- PATTERNS = [(re.compile(u'^retrait dab (?P\d{2})/(?P\d{2})/(?P\d{4}) (?P.*)'), FrenchTransaction.TYPE_WITHDRAWAL),
- # Withdrawal in foreign currencies will look like "retrait 123 currency"
- (re.compile(u'^retrait (?P.*)'), FrenchTransaction.TYPE_WITHDRAWAL),
- (re.compile(u'^carte (?P\d{2})/(?P\d{2})/(?P\d{4}) (?P.*)'), FrenchTransaction.TYPE_CARD),
- (re.compile(u'^virement (sepa )?(emis vers|recu|emis)? (?P.*)'), FrenchTransaction.TYPE_TRANSFER),
- (re.compile(u'^remise cheque(?P.*)'), FrenchTransaction.TYPE_DEPOSIT),
- (re.compile(u'^cheque (?P.*)'), FrenchTransaction.TYPE_CHECK),
- (re.compile(u'^prelevement (?P.*)'), FrenchTransaction.TYPE_ORDER),
- (re.compile(u'^prlv sepa (?P.*?) : .*'), FrenchTransaction.TYPE_ORDER),
- (re.compile(u'^prélèvement sepa en faveur de (?P.*)'), FrenchTransaction.TYPE_ORDER),
- (re.compile(u'^commission sur (?P.*)'), FrenchTransaction.TYPE_BANK),
- ]
+ PATTERNS = [
+ (re.compile(u'^retrait dab (?P\d{2})/(?P\d{2})/(?P\d{4}) (?P.*)'), FrenchTransaction.TYPE_WITHDRAWAL),
+ # Withdrawal in foreign currencies will look like "retrait 123 currency"
+ (re.compile(u'^retrait (?P.*)'), FrenchTransaction.TYPE_WITHDRAWAL),
+ (re.compile(u'^carte (?P\d{2})/(?P\d{2})/(?P\d{4}) (?P.*)'), FrenchTransaction.TYPE_CARD),
+ (re.compile(u'^virement (sepa )?(emis vers|recu|emis)? (?P.*)'), FrenchTransaction.TYPE_TRANSFER),
+ (re.compile(u'^remise cheque(?P.*)'), FrenchTransaction.TYPE_DEPOSIT),
+ (re.compile(u'^cheque (?P.*)'), FrenchTransaction.TYPE_CHECK),
+ (re.compile(u'^prelevement (?P.*)'), FrenchTransaction.TYPE_ORDER),
+ (re.compile(u'^prlv sepa (?P.*?) : .*'), FrenchTransaction.TYPE_ORDER),
+ (re.compile(u'^prélèvement sepa en faveur de (?P.*)'), FrenchTransaction.TYPE_ORDER),
+ (re.compile(u'^commission sur (?P.*)'), FrenchTransaction.TYPE_BANK),
+ ]
+
+ TYPES = {
+ 'PURCHASE_CARD': FrenchTransaction.TYPE_CARD,
+ 'TRANSFER': FrenchTransaction.TYPE_TRANSFER,
+ 'SEPA_DEBIT': FrenchTransaction.TYPE_ORDER,
+ 'CARD_WITHDRAWAL': FrenchTransaction.TYPE_WITHDRAWAL,
+ 'FEES': FrenchTransaction.TYPE_BANK,
+ 'CHECK': FrenchTransaction.TYPE_CHECK,
+ 'OTHER': FrenchTransaction.TYPE_UNKNOWN,
+ }
class AccountsPage(LoggedPage, JsonPage):
@@ -73,9 +84,12 @@ class HistoryPage(LoggedPage, JsonPage):
class item(ItemElement):
klass = Transaction
- obj_id = Eval(str, Dict('id'))
+ # Not sure that Dict('id') is unique and persist
+ # wait for the full API migration
+ obj__web_id = Eval(str, Dict('id'))
obj_amount = CleanDecimal(Dict('amount'))
obj_date = Date(Dict('effectiveDate'))
+ obj_type = Map(Upper(Dict('type')), Transaction.TYPES, Transaction.TYPE_UNKNOWN)
def obj_raw(self):
return Transaction.Raw(Lower(Dict('detail')))(self) or Format('%s %s', Field('date'), Field('amount'))(self)
@@ -92,6 +106,7 @@ class ComingPage(LoggedPage, JsonPage):
obj_amount = CleanDecimal(Dict('amount'))
obj_date = Date(Dict('effectiveDate'))
obj_vdate = Date(Dict('operationDate'))
+ obj_type = Map(Upper(Dict('type')), Transaction.TYPES, Transaction.TYPE_UNKNOWN)
def obj_raw(self):
return Transaction.Raw(Lower(Dict('label')))(self) or Format('%s %s', Field('date'), Field('amount'))(self)
diff --git a/modules/ing/api_browser.py b/modules/ing/api_browser.py
index 315d0120d430c37c7fc57600a2b4f08f974402a7..942c3b0a063e61bae9170102ca7bd877defce3c8 100644
--- a/modules/ing/api_browser.py
+++ b/modules/ing/api_browser.py
@@ -64,6 +64,15 @@ def need_to_be_on_website(website):
def decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
+ # if on other website than web or api, redirect to old website
+ if self.old_browser.url:
+ if 'https://bourse.ing.fr/' in self.old_browser.url:
+ self.old_browser.return_from_titre_page.go()
+ elif 'https://ingdirectvie.ing.fr/' in self.old_browser.url:
+ self.old_browser.lifeback.go()
+ elif 'https://subscribe.ing.fr/' in self.old_browser.url:
+ self.old_browser.return_from_loan_site()
+
if website == 'web' and self.is_on_new_website:
self.redirect_to_old_browser()
elif website == 'api' and not self.is_on_new_website:
@@ -112,14 +121,16 @@ class IngAPIBrowser(LoginBrowser):
if error['code'] == 'AUTHENTICATION.INVALID_PIN_CODE':
raise BrowserIncorrectPassword(error['message'])
- elif error['code'] == 'AUTHENTICATION.ACCOUNT_INACTIVE':
+ elif error['code'] in ('AUTHENTICATION.ACCOUNT_INACTIVE', 'AUTHENTICATION.ACCOUNT_LOCKED',
+ 'AUTHENTICATION.NO_COMPLETE_ACCOUNT_FOUND'):
raise ActionNeeded(error['message'])
assert error['code'] != 'INPUT_INVALID', error['message']
raise BrowserUnavailable(error['message'])
def do_login(self):
- assert self.password.isdigit()
assert self.birthday.isdigit()
+ if not self.password.isdigit():
+ raise BrowserIncorrectPassword()
# login on new website
# update cookies
@@ -213,6 +224,11 @@ class IngAPIBrowser(LoginBrowser):
else:
assert False, 'There should be same account in web and api website'
+ # can use this to use export session on old browser
+ # new website is an API, export session is not relevant
+ if self.logger.settings.get('export_session'):
+ self.logger.debug('logged in with session: %s', json.dumps(self.export_session()))
+
@need_to_be_on_website('web')
def get_web_history(self, account):
"""iter history on old website"""
@@ -238,7 +254,7 @@ class IngAPIBrowser(LoginBrowser):
for tr in self.page.iter_history():
# transaction id is decreasing
- first_transaction_id = int(tr.id)
+ first_transaction_id = int(tr._web_id)
yield tr
# like website, add 1 to the last transaction id of the list to get next transactions page
@@ -308,18 +324,23 @@ class IngAPIBrowser(LoginBrowser):
############# CapDocument #############
@need_login
+ @need_to_be_on_website('web')
def get_subscriptions(self):
- raise BrowserUnavailable()
+ return self.old_browser.get_subscriptions()
@need_login
+ @need_to_be_on_website('web')
def get_documents(self, subscription):
- raise BrowserUnavailable()
+ return self.old_browser.get_documents(subscription)
+ @need_login
+ @need_to_be_on_website('web')
def download_document(self, bill):
- raise BrowserUnavailable()
+ return self.old_browser.download_document(bill)
############# CapProfile #############
@need_login
+ @need_to_be_on_website('api')
def get_profile(self):
self.informations.go()
return self.page.get_profile()
diff --git a/modules/ing/browser.py b/modules/ing/browser.py
index 2b39d9532a8c133c8cef55e60ad788d802b2ceee..55ef197d5455c2a39b3e114c8c09aab25caea091 100644
--- a/modules/ing/browser.py
+++ b/modules/ing/browser.py
@@ -108,6 +108,8 @@ class IngBrowser(LoginBrowser):
# New website redirection
api_redirection_url = URL(r'/general\?command=goToSecureUICommand&redirectUrl=transfers', ApiRedirectionPage)
+ # Old website redirection from bourse website
+ return_from_titre_page = URL(r'https://bourse.ing.fr/priv/redirectIng\.php\?pageIng=CC')
__states__ = ['where']
@@ -451,6 +453,7 @@ class IngBrowser(LoginBrowser):
self.titrerealtime.go()
for inv in self.page.iter_investments(account):
yield inv
+ self.return_from_titre_page.go()
elif self.page.asv_has_detail or account._jid:
self.accountspage.stay_or_go()
shares = {}
@@ -483,6 +486,7 @@ class IngBrowser(LoginBrowser):
isin_codes = {}
for tr in self.page.iter_history():
transactions.append(tr)
+ self.return_from_titre_page.go()
if self.asv_history.is_here():
for tr in transactions:
page = tr._detail.result().page if tr._detail else None
@@ -513,11 +517,7 @@ class IngBrowser(LoginBrowser):
@need_login
def get_subscriptions(self):
self.billpage.go()
- if self.loginpage.is_here():
- self.do_login()
- subscriptions = list(self.billpage.go().iter_subscriptions())
- else:
- subscriptions = list(self.page.iter_subscriptions())
+ subscriptions = list(self.page.iter_subscriptions())
self.cache['subscriptions'] = {}
for sub in subscriptions:
diff --git a/modules/ing/module.py b/modules/ing/module.py
index 6d85a17edf1b6fe5ec332faad1c0f130e8665f75..7595d0075cf4d9f52a3a802d6a108030f03cb1a8 100644
--- a/modules/ing/module.py
+++ b/modules/ing/module.py
@@ -40,10 +40,11 @@ class INGModule(Module, CapBankWealth, CapBankTransfer, CapDocument, CapProfile)
EMAIL = 'weboob@flo.fourcot.fr'
VERSION = '1.6'
LICENSE = 'LGPLv3+'
- DESCRIPTION = 'ING Direct'
+ DESCRIPTION = 'ING France'
CONFIG = BackendConfig(ValueBackendPassword('login',
label='Numéro client',
- masked=False),
+ masked=False,
+ regexp='^(\d{1,10})$'),
ValueBackendPassword('password',
label='Code secret',
regexp='^(\d{6})$'),
diff --git a/modules/ing/web/accounts_list.py b/modules/ing/web/accounts_list.py
index 94028edf5a8380f089a32292fd3cb8dac72ac287..01ed8c84b4660b8497ddb5578976fe1eba651cdc 100644
--- a/modules/ing/web/accounts_list.py
+++ b/modules/ing/web/accounts_list.py
@@ -180,7 +180,12 @@ class AccountsList(LoggedPage, HTMLPage):
return -abs(balance) if Field('type')(self) == Account.TYPE_LOAN else balance
def obj__id(self):
- return CleanText('./span[@class="account-number"]')(self) or CleanText('./span[@class="life-insurance-application"]')(self)
+ return CleanText('./span[@class="account-number"]')(self)
+
+ def condition(self):
+ # do not return accounts in application state
+ # they are not displayed on new website
+ return not CleanText('./span[@class="life-insurance-application"]')(self)
@method
class get_detailed_loans(ListElement):
diff --git a/modules/lcl/browser.py b/modules/lcl/browser.py
index 0fd0d3f71c49268dc1915b1ebd9207bea95cc154..7813ca0dafab38e1ebf15b3c8f684fbe27c6e92a 100644
--- a/modules/lcl/browser.py
+++ b/modules/lcl/browser.py
@@ -434,7 +434,7 @@ class LCLBrowser(LoginBrowser, StatesMixin):
@need_login
def send_code(self, recipient, **params):
- res = self.open('/outil/UWBE/Otp/getValidationCodeOtp?codeOtp=%s' % params['code'])
+ res = self.open('/outil/UWBE/Otp/validationCodeOtp?codeOtp=%s' % params['code'])
if res.text == 'false':
raise AddRecipientBankError(message='Mauvais code sms.')
self.recip_recap.go().check_values(recipient.iban, recipient.label)
diff --git a/modules/linebourse/api/pages.py b/modules/linebourse/api/pages.py
index f1b5aff835987fe8acdcd37289cc422027b96fce..b0ec69defafe5410c97c636049cea0e37a5b010f 100644
--- a/modules/linebourse/api/pages.py
+++ b/modules/linebourse/api/pages.py
@@ -26,7 +26,7 @@ from weboob.browser.filters.standard import (
)
from weboob.browser.pages import JsonPage, HTMLPage, LoggedPage
from weboob.capabilities.bank import Investment, Transaction
-from weboob.capabilities.base import NotAvailable
+from weboob.capabilities.base import NotAvailable, empty
from weboob.tools.capabilities.bank.investments import is_isin_valid
@@ -49,6 +49,12 @@ class PortfolioPage(LoggedPage, JsonPage):
class item(ItemElement):
klass = Investment
+ def condition(self):
+ # Some rows do not contain an expected item format,
+ # There is no valuation (mnt) because some buy/sell orders are not yet finished.
+ # We want invalid values to fail in the CleanDecimal filter so we catch only when mnt is missing
+ return Dict('mnt', default=NotAvailable)(self) is not NotAvailable
+
obj_label = Dict('libval')
obj_code = Dict('codval')
obj_code_type = Eval(
@@ -59,7 +65,6 @@ class PortfolioPage(LoggedPage, JsonPage):
obj_unitvalue = CleanDecimal(Dict('crs'))
obj_valuation = CleanDecimal(Dict('mnt'))
obj_vdate = Env('date')
- obj_portfolio_share = Eval(lambda x: x / 100, CleanDecimal(Dict('pourcentageActif')))
def parse(self, el):
symbols = {
@@ -88,6 +93,12 @@ class PortfolioPage(LoggedPage, JsonPage):
elif Dict('pourcentagePlv', default=None)(self):
return CleanDecimal(Dict('pourcentagePlv'), sign=lambda x: Env('sign')(self))(self)
+ def obj_portfolio_share(self):
+ active_percent = Dict('pourcentageActif', default=NotAvailable)(self)
+ if empty(active_percent):
+ return NotAvailable
+ return Eval(lambda x: x / 100, CleanDecimal(active_percent))(self)
+
class AccountCodesPage(LoggedPage, JsonPage):
def get_contract_number(self, account_id):
diff --git a/modules/figgo/__init__.py b/modules/lucca/__init__.py
similarity index 79%
rename from modules/figgo/__init__.py
rename to modules/lucca/__init__.py
index 980a6e7625d1a35ee5df6d7a57ca1b706f092815..40e880ddb626100c243d8a8567629eff45515220 100644
--- a/modules/figgo/__init__.py
+++ b/modules/lucca/__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 .
from __future__ import unicode_literals
diff --git a/modules/figgo/browser.py b/modules/lucca/browser.py
similarity index 77%
rename from modules/figgo/browser.py
rename to modules/lucca/browser.py
index 2199b81bd2ae48267b6ee0ac7b32adc29041607e..2792bc406144988c7d532c2f38c03c2494707700 100644
--- a/modules/figgo/browser.py
+++ b/modules/lucca/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
@@ -26,7 +26,10 @@ from weboob.browser.exceptions import ClientError
from weboob.exceptions import BrowserIncorrectPassword
from weboob.tools.date import new_datetime
-from .pages import LoginPage, CalendarPage, HomePage, UsersPage
+from .pages import (
+ LoginPage, CalendarPage, HomePage, UsersPage,
+ DocumentsPage, SubscriptionPage,
+)
class LuccaBrowser(LoginBrowser):
@@ -36,6 +39,8 @@ class LuccaBrowser(LoginBrowser):
home = URL('/home', HomePage)
calendar = URL('/api/leaveAMPMs', CalendarPage)
users = URL(r'/api/departments\?fields=id%2Cname%2Ctype%2Clevel%2Cusers.id%2Cusers.displayName%2Cusers.dtContractStart%2Cusers.dtContractEnd%2Cusers.manager.id%2Cusers.manager2.id%2Cusers.legalEntityID%2Cusers.calendar.id&date=since%2C1970-01-01', UsersPage)
+ subscriptions = URL(r'/api/v3/users/me\?fields=id,firstName,lastName,allowsElectronicPayslip,culture,login,mail,personalemail', SubscriptionPage)
+ payslips = URL(r'/api/v3/payslips\?fields=id,import\[name,endDate\]&orderby=import\.endDate,desc,import\.startDate,desc,import\.creationDate,desc&ownerID=(?P\d+)', DocumentsPage)
def __init__(self, subdomain, *args, **kwargs):
super(LuccaBrowser, self).__init__(*args, **kwargs)
@@ -87,3 +92,13 @@ class LuccaBrowser(LoginBrowser):
last = new_datetime(event.start_date)
start = window_end
+
+ @need_login
+ def get_subscription(self):
+ self.subscriptions.go()
+ return self.page.get_subscription()
+
+ @need_login
+ def iter_documents(self, subid):
+ self.payslips.go(subid=subid)
+ return self.page.iter_documents(subid)
diff --git a/modules/figgo/module.py b/modules/lucca/module.py
similarity index 59%
rename from modules/figgo/module.py
rename to modules/lucca/module.py
index 04d0356fc025355d55af8960344fee18b98e00e2..e921abbe195635e631e976efa3e3bcdc8625d670 100644
--- a/modules/figgo/module.py
+++ b/modules/lucca/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 .
from __future__ import unicode_literals
@@ -22,7 +22,12 @@ from __future__ import unicode_literals
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import Value, ValueBackendPassword
+from weboob.capabilities.base import find_object
from weboob.capabilities.calendar import CapCalendarEvent
+from weboob.capabilities.bill import (
+ CapDocument, DocumentTypes, SubscriptionNotFound, DocumentNotFound,
+ Subscription,
+)
from .browser import LuccaBrowser
@@ -30,12 +35,12 @@ from .browser import LuccaBrowser
__all__ = ['LuccaModule']
-class LuccaModule(Module, CapCalendarEvent):
- NAME = 'figgo'
- DESCRIPTION = 'Figgo - Lucca congés et absences'
+class LuccaModule(Module, CapDocument, CapCalendarEvent):
+ NAME = 'lucca'
+ DESCRIPTION = 'Lucca RH'
MAINTAINER = 'Vincent A'
EMAIL = 'dev@indigo.re'
- LICENSE = 'AGPLv3+'
+ LICENSE = 'LGPLv3+'
VERSION = '1.6'
BROWSER = LuccaBrowser
@@ -46,6 +51,8 @@ class LuccaModule(Module, CapCalendarEvent):
ValueBackendPassword('password', label='Mot de passe'),
)
+ accepted_document_types = (DocumentTypes.BILL,)
+
def create_default_browser(self):
return self.create_browser(
self.config['subdomain'].get(),
@@ -74,3 +81,27 @@ class LuccaModule(Module, CapCalendarEvent):
yield ev
# TODO merge contiguous events?
+
+ def iter_subscription(self):
+ return [self.browser.get_subscription()]
+
+ def get_subscription(self, id):
+ return find_object(self.iter_subscription(), id=id, error=SubscriptionNotFound)
+
+ def iter_documents(self, subscription):
+ if not isinstance(subscription, str):
+ subscription = subscription.id
+ return self.browser.iter_documents(subscription)
+
+ def get_document(self, id):
+ subid = id.split('_')[0]
+ return find_object(self.iter_documents(subid), id=id, error=DocumentNotFound)
+
+ def download_document(self, document):
+ return self.browser.open(document.url).content
+
+ def iter_resources(self, objs, split_path):
+ if Subscription in objs:
+ return CapDocument.iter_resources(self, objs, split_path)
+ return CapCalendarEvent.iter_resources(self, objs, split_path)
+
diff --git a/modules/figgo/pages.py b/modules/lucca/pages.py
similarity index 71%
rename from modules/figgo/pages.py
rename to modules/lucca/pages.py
index 98388c28bdb2663faaf435e61ad67992b51ed3c0..749ec6ab5c77df8603a6723891071fdbe2660a30 100644
--- a/modules/figgo/pages.py
+++ b/modules/lucca/pages.py
@@ -5,26 +5,29 @@
# 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
from datetime import timedelta
-from dateutil.parser import parse as parse_date
from weboob.browser.pages import HTMLPage, LoggedPage, JsonPage
from weboob.capabilities.calendar import BaseCalendarEvent, STATUS
-from weboob.tools.date import new_date
+from weboob.capabilities.bill import (
+ Subscription, Document, DocumentTypes,
+)
+from weboob.tools.date import new_date, parse_date
+from weboob.tools.compat import urljoin
class LoginPage(HTMLPage):
@@ -96,3 +99,25 @@ class CalendarPage(LoggedPage, JsonPage):
continue
yield ev
+
+
+class SubscriptionPage(LoggedPage, JsonPage):
+ def get_subscription(self):
+ sub = Subscription()
+ sub.id = str(self.doc['data']['id'])
+ sub.subscriber = sub.label = self.doc['header']['principal']
+ return sub
+
+
+class DocumentsPage(LoggedPage, JsonPage):
+ def iter_documents(self, subid):
+ for d in self.doc['data']['items']:
+ doc = Document()
+ doc.id = '%s_%s' % (subid, d['id'])
+ doc._docid = d['id']
+ doc.label = d['import']['name']
+ doc.date = parse_date(d['import']['endDate'])
+ doc.url = urljoin(self.url, '/pagga/download/%s' % doc._docid)
+ doc.type = DocumentTypes.BILL
+ doc.format = 'pdf'
+ yield doc
diff --git a/modules/orange/browser.py b/modules/orange/browser.py
index ccd9d3c7ac66fd892020caab39b365101e287576..fc942b560119d8f405eab8a95ad896b0501d2766 100644
--- a/modules/orange/browser.py
+++ b/modules/orange/browser.py
@@ -22,8 +22,9 @@ from __future__ import unicode_literals
from requests.exceptions import ConnectTimeout
from weboob.browser import LoginBrowser, URL, need_login
-from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable
+from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded
from .pages import LoginPage, BillsPage
+from .pages.login import ManageCGI, HomePage
from .pages.bills import SubscriptionsPage, BillsApiPage, ContractsPage
from .pages.profile import ProfilePage
from weboob.browser.exceptions import ClientError, ServerError
@@ -37,12 +38,14 @@ __all__ = ['OrangeBillBrowser']
class OrangeBillBrowser(LoginBrowser):
BASEURL = 'https://espaceclientv3.orange.fr'
+ home_page = URL('https://businesslounge.orange.fr/$', HomePage)
loginpage = URL('https://login.orange.fr/\?service=sosh&return_url=https://www.sosh.fr/',
'https://login.orange.fr/front/login', LoginPage)
contracts = URL('https://espaceclientpro.orange.fr/api/contracts\?page=1&nbcontractsbypage=15', ContractsPage)
subscriptions = URL(r'https://espaceclientv3.orange.fr/js/necfe.php\?zonetype=bandeau&idPage=gt-home-page', SubscriptionsPage)
+ manage_cgi = URL('https://eui.orange.fr/manage_eui/bin/manage.cgi', ManageCGI)
billspage = URL('https://m.espaceclientv3.orange.fr/\?page=factures-archives',
'https://.*.espaceclientv3.orange.fr/\?page=factures-archives',
@@ -87,7 +90,19 @@ class OrangeBillBrowser(LoginBrowser):
@need_login
def get_subscription_list(self):
try:
- profile = self.profile.go().get_profile()
+ self.profile.go()
+
+ assert self.profile.is_here() or self.manage_cgi.is_here()
+
+ # we land on manage_cgi page when there is cgu to validate
+ if self.manage_cgi.is_here():
+ # but they are not in this page, we have to go to home_page to get message
+ self.home_page.go()
+ msg = self.page.get_error_message()
+ assert "Nos Conditions Générales d'Utilisation ont évolué" in msg, msg
+ raise ActionNeeded(msg)
+ else:
+ profile = self.page.get_profile()
except ConnectTimeout:
# sometimes server just doesn't answer
raise BrowserUnavailable()
diff --git a/modules/orange/pages/login.py b/modules/orange/pages/login.py
index 6fcaba5ceb577a7d25840878aebe24dd04c8eff4..76aae259a283795c32ad26a6b837a404bdef61bb 100644
--- a/modules/orange/pages/login.py
+++ b/modules/orange/pages/login.py
@@ -18,20 +18,31 @@
# along with this weboob module. If not, see .
-from weboob.browser.pages import HTMLPage
+from weboob.browser.pages import HTMLPage, LoggedPage
+from weboob.tools.json import json
+from weboob.browser.filters.standard import CleanText, Format
class LoginPage(HTMLPage):
def login(self, username, password):
json_data = {
- 'forcePwd': False,
'login': username,
- 'mem': True,
+ 'mem': False,
}
- self.browser.location('https://login.orange.fr/front/login', json=json_data)
+ response = self.browser.location('https://login.orange.fr/front/login', json=json_data)
json_data = {
'login': username,
'password': password,
+ 'loginEncrypt': json.loads(response.json()['options'])['loginEncrypt']
}
self.browser.location('https://login.orange.fr/front/password', json=json_data)
+
+
+class ManageCGI(HTMLPage):
+ pass
+
+
+class HomePage(LoggedPage, HTMLPage):
+ def get_error_message(self):
+ return Format('%s %s', CleanText('//div[has-class("modal-dialog")]//h3'), CleanText('//div[has-class("modal-dialog")]//p[1]'))(self.doc)
diff --git a/modules/societegenerale/pages/accounts_list.py b/modules/societegenerale/pages/accounts_list.py
index 388f04482c4ee044e9fc245c1a16d047c9363145..2b788a741461f8f2cc3006a17715de10e48149b8 100644
--- a/modules/societegenerale/pages/accounts_list.py
+++ b/modules/societegenerale/pages/accounts_list.py
@@ -304,6 +304,8 @@ class Transaction(FrenchTransaction):
(re.compile(r'^(?PVIR(EMEN)?T? \w+) (?P.*)'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile(r'^(CHEQUE) (?P.*)'), FrenchTransaction.TYPE_CHECK),
+ (re.compile(r'^REMISE CHEQUE (?P.*)'),
+ FrenchTransaction.TYPE_CHECK),
(re.compile(r'^(FRAIS) (?P.*)'), FrenchTransaction.TYPE_BANK),
(re.compile(r'^(?PECHEANCEPRET)(?P.*)'),
FrenchTransaction.TYPE_LOAN_PAYMENT),
@@ -317,6 +319,8 @@ class Transaction(FrenchTransaction):
FrenchTransaction.TYPE_CARD_SUMMARY),
(re.compile(r'^CREDIT MENSUEL CARTE (?P.*)'),
FrenchTransaction.TYPE_CARD_SUMMARY),
+ (re.compile(r'^Paiements CB (?P.*)'),
+ FrenchTransaction.TYPE_CARD_SUMMARY),
(re.compile(r'^CARTE \w+ (?P\d{2})\/(?P\d{2}) (?P.*)'),
FrenchTransaction.TYPE_CARD),
]
@@ -415,6 +419,9 @@ class HistoryPage(JsonBasePage):
def condition(self):
return Dict('statutOperation')(self) == 'COMPTABILISE'
+ obj_raw = Dict('libOpe')
+ obj_type = Transaction.TYPE_DEFERRED_CARD
+
@pagination
@method
class iter_intraday_comings(DictElement):
@@ -469,6 +476,7 @@ class CardHistoryPage(LoggedPage, HTMLPage):
klass = Transaction
obj_label = CleanText('.//td[@headers="Libelle"]/span')
+ obj_type = Transaction.TYPE_DEFERRED_CARD
def obj_date(self):
if not 'TOTAL DES FACTURES' in Field('label')(self):
@@ -484,7 +492,7 @@ class CardHistoryPage(LoggedPage, HTMLPage):
def obj_raw(self):
if not 'TOTAL DES FACTURES' in Field('label')(self):
- return Transaction.Raw(CleanText('.//td[@headers="Libelle"]/span'))(self)
+ return CleanText('.//td[@headers="Libelle"]/span')(self)
return NotAvailable
diff --git a/modules/tapatalk/module.py b/modules/tapatalk/module.py
index 395b9fb41582e478bc13fce45da0e9d8d725a58e..9cc6a7c99aa4afd3392f1738ea74198dc772bee1 100644
--- a/modules/tapatalk/module.py
+++ b/modules/tapatalk/module.py
@@ -74,7 +74,7 @@ class TapatalkModule(Module, CapMessages):
CONFIG = BackendConfig(Value('username', label='Username', default=''),
ValueBackendPassword('password', label='Password', default=''),
- Value('url', label='Site URL', default="https://support.tapatalk.com/"))
+ Value('url', label='Site URL', default="https://support.tapatalk.com/mobiquo/mobiquo.php"))
def __init__(self, *args, **kwargs):
super(TapatalkModule, self).__init__(*args, **kwargs)
@@ -83,7 +83,7 @@ class TapatalkModule(Module, CapMessages):
@property
def _conn(self):
if self._xmlrpc_client is None:
- url = self.config['url'].get().rstrip('/') + "/mobiquo/mobiquo.php"
+ url = self.config['url'].get().rstrip('/')
username = self.config['username'].get()
password = self.config['password'].get()
self._xmlrpc_client = TapatalkServerProxy(url)
diff --git a/modules/themisbanque/browser.py b/modules/themisbanque/browser.py
index 2821536689815d4fa3f3a8ac0e979075827d48f7..cfe71c944f1de80a9ab4c12500e6af7db7c8a8fa 100644
--- a/modules/themisbanque/browser.py
+++ b/modules/themisbanque/browser.py
@@ -27,6 +27,8 @@ from .pages import LoginPage, LoginConfirmPage, AccountsPage, RibPage, RibPDFPag
class ThemisBrowser(LoginBrowser):
BASEURL = 'https://esab.themisbanque.eu/'
+ TIMEOUT = 90
+
home = URL('/es@b/fr/esab.jsp')
login = URL('/es@b/fr/codeident.jsp', LoginPage)
login_confirm = URL('/es@b/servlet/internet0.ressourceWeb.servlet.Login', LoginConfirmPage)
@@ -57,8 +59,10 @@ class ThemisBrowser(LoginBrowser):
@need_login
def get_history(self, account):
- self.location(account._link)
- return self.page.get_operations()
+ if account._link:
+ self.location(account._link)
+ return self.page.get_operations()
+ return []
@need_login
def get_profile(self):
diff --git a/modules/themisbanque/pages.py b/modules/themisbanque/pages.py
index e2b11278c929137b91de8cbf6eb7221264c8cb41..43d99acedcbb9639c79d675059a6cbf14441721c 100644
--- a/modules/themisbanque/pages.py
+++ b/modules/themisbanque/pages.py
@@ -28,7 +28,7 @@ from weboob.capabilities.bank import Account
from weboob.capabilities.base import NotAvailable
from weboob.capabilities.profile import Profile
from weboob.browser.filters.standard import CleanText, CleanDecimal, Async, Regexp, Join, Field
-from weboob.browser.filters.html import Attr, Link, TableCell, ColumnNotFound
+from weboob.browser.filters.html import Link, TableCell, ColumnNotFound
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.tools.capabilities.bank.iban import is_iban_valid
from weboob.tools.compat import basestring
@@ -99,7 +99,7 @@ class AccountsPage(LoggedPage, HTMLPage):
obj_balance = CleanDecimal(TableCell('balance'), replace_dots=True)
def obj__link(self):
- return Attr(TableCell('id')(self)[0].xpath('./a'), 'href')(self)
+ return Link(TableCell('id')(self)[0].xpath('./a'), default=None)(self)
def obj__url(self):
return Link(TableCell('rib')(self)[0].xpath('./a[img[starts-with(@alt, "RIB")]]'), default=None)(self)
diff --git a/modules/yomoni/browser.py b/modules/yomoni/browser.py
index c6a7bbebca78534ec5e53a40d900d2de2033a4c9..4ce98051191c2a5614487e691240d0450136fba0 100644
--- a/modules/yomoni/browser.py
+++ b/modules/yomoni/browser.py
@@ -17,6 +17,10 @@
# 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
+
+
from base64 import b64encode
from functools import wraps
import json
@@ -28,6 +32,7 @@ from weboob.browser.filters.standard import CleanDecimal, Date
from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded
from weboob.capabilities.bank import Account, Investment, Transaction
from weboob.capabilities.base import NotAvailable
+from weboob.tools.capabilities.bank.investments import is_isin_valid
def need_login(func):
@@ -137,12 +142,21 @@ class YomoniBrowser(APIBrowser):
i = Investment()
i.label = "%s - %s" % (inv['classification'], inv['description'])
i.code = inv['isin']
- i.code_type = Investment.CODE_TYPE_ISIN
- i.quantity = CleanDecimal().filter(inv['nombreParts'])
+ if not is_isin_valid(i.code):
+ i.code = NotAvailable
+ i.code_type = NotAvailable
+ if u'Solde Espèces' in i.label:
+ i.code = 'XX-liquidity'
+ else:
+ i.code_type = Investment.CODE_TYPE_ISIN
+
+ i.quantity = CleanDecimal(default=NotAvailable).filter(inv['nombreParts'])
i.unitprice = CleanDecimal().filter(inv['prixMoyenAchat'])
i.unitvalue = CleanDecimal().filter(inv['valeurCotation'])
i.valuation = CleanDecimal().filter(inv['montantEuro'])
- i.vdate = Date().filter(inv['datePosition'])
+ # For some invests the vdate returned is None
+ # Consequently we set the default value at NotAvailable
+ i.vdate = Date(default=NotAvailable).filter(inv['datePosition'])
# performanceEuro is null sometimes in the JSON we retrieve.
if inv['performanceEuro']:
i.diff = CleanDecimal().filter(inv['performanceEuro'])