Commit 999860a6 authored by Romain Bignon's avatar Romain Bignon

Update of modules

parent 9695f103
......@@ -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():
......
......@@ -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."
......@@ -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."
......@@ -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
......@@ -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."
......@@ -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',
......
......@@ -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}))
......
......@@ -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
......@@ -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/'
......
......@@ -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 <http://www.gnu.org/licenses/>.
......
......@@ -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 <http://www.gnu.org/licenses/>.
......@@ -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
......
......@@ -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 <http://www.gnu.org/licenses/>.
......@@ -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'),
......
......@@ -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 <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
......@@ -126,6 +126,7 @@ class Transaction(FrenchTransaction):
(re.compile(r'^(?P<text>[A-Z][\sa-z]* )?AVOIR (?P<dd>\d{2})(?P<mm>\d{2})(?P<yy>\d{4}) (?P<text2>.*)'), FrenchTransaction.TYPE_PAYBACK),
(re.compile('^REM CHQ (?P<text>.*)'), 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 <div> sometimes it's <h3>
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):
......
......@@ -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 <http://www.gnu.org/licenses/>.
......
......@@ -43,7 +43,7 @@ class BouyguesBrowser(LoginBrowser):
subscriptions = URL(r'/personnes/(?P<idUser>\d+)/comptes-facturation', SubscriptionPage)
subscriptions_details = URL(r'/comptes-facturation/(?P<idSub>\d+)/contrats-payes', SubscriptionDetailPage)
document_file = URL(r'/comptes-facturation/(?P<idSub>\d+)/factures/\d+/documents', DocumentFilePage)
document_file = URL(r'/comptes-facturation/(?P<idSub>\d+)/factures/.*/documents', DocumentFilePage)
documents = URL(r'/comptes-facturation/(?P<idSub>\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
......
......@@ -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.'