Newer
Older
# -*- coding: utf-8 -*-
# Copyright(C) 2014 Romain Bignon
#
# This file is part of a weboob module.
# This weboob module is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This weboob module is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from weboob.tools.compat import parse_qsl, urlparse
from weboob.browser.elements import DictElement, ItemElement, method
from weboob.browser.filters.html import Attr
from weboob.browser.filters.json import Dict
from weboob.browser.filters.standard import CleanText, CleanDecimal, Date, Map, Field
from weboob.browser.pages import LoggedPage, JsonPage
from weboob.capabilities.profile import Company
from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword
from .accounthistory import Transaction
class RedirectPage(LoggedPage, MyHTMLPage):
def check_for_perso(self):
return self.doc.xpath('''//p[contains(text(), "L'identifiant utilisé est celui d'un compte de Particuliers")]''')
def get_error(self):
return CleanText('//div[contains(@class, "bloc-erreur")]/h3')(self.doc)
def is_logged(self):
return 'Vous êtes bien authentifié' in CleanText('//p[@class="txt"]')(self.doc)
# TODO: add new type names and remove old ones
'Comptes titres': Account.TYPE_MARKET, # old
'COMPTE_EPARGNE': Account.TYPE_SAVINGS,
TRANSACTION_TYPES = {
# TODO: 12+ categories ? (bank type id is at least up to 12)
'Prélèvement': Transaction.TYPE_ORDER,
'Achat CB': Transaction.TYPE_CHECK,
'Virement': Transaction.TYPE_TRANSFER,
'Frais/Taxes/Agios': Transaction.TYPE_BANK,
'Versement': Transaction.TYPE_CASH_DEPOSIT,
'Chèque': Transaction.TYPE_CHECK,
}
def find_elements(self):
"""
Structure of json:
{
"comptesBancaires": {
"comptes": [{...}],
"...": ...,
},
"comptesEpargnesEtPlacements": {
"comptes": [{...}],
"...": ...,
},
"financements": {
"...": ...,
}
"groupesPersos": ...,
"indicateurCarte": ...,
"numeroCampagne": ...
}
"""
for data in self.el.values():
if not isinstance(data, dict):
continue
for account in data.get('comptes', []):
yield account
class item(ItemElement):
klass = Account
obj_id = Dict('numero')
obj_balance = CleanDecimal.US(Dict('solde'))
obj_currency = 'EUR'
obj_type = Map(Dict('type'), ACCOUNT_TYPES, Account.TYPE_UNKNOWN)
# Comment from code of last pro website:
# Need to get rid of the id wherever we find it in account labels
# like "LIV A 0123456789N MR MOMO" (livret A) as well as
# "0123456789N MR MOMO" (checking account)
label = Dict('intituleLong')(self).replace(Field('id')(self), '')
return CleanText().filter(label)
class item(ItemElement):
klass = Transaction
obj_label = Dict('libelle')
obj_date = Date(Dict('date')) # skip time since it is always 00:00:00. Days last.
# transaction typing: don't rely on labels as the bank already provides types.
obj_type = Map(Dict('libelleNature'), TRANSACTION_TYPES, Transaction.TYPE_UNKNOWN)
def obj_amount(self):
amount = CleanDecimal.US(Dict('montant'))(self) # absolute value
sign = Dict('codeSens')(self)
if sign == 'D': # debit
return - amount
elif sign == 'C': # credit
return amount
else:
raise AssertionError('unhandled value for transaction sign')
class DownloadRib(LoggedPage, MyHTMLPage):
def get_rib_value(self, acc_id):
opt = self.doc.xpath('//select[@id="idxSelection"]/optgroup//option')
for o in opt:
if acc_id in o.text:
return o.xpath('./@value')[0]
return None
class RibPage(LoggedPage, MyHTMLPage):
if self.doc.xpath('//div[@class="blocbleu"][2]//table[@class="datalist"]'):
return (
CleanText()
.filter(self.doc.xpath('//div[@class="blocbleu"][2]//table[@class="datalist"]')[0])
@method
class get_profile(ItemElement):
klass = Company
obj_name = CleanText('//table[@class="datalistecart"]//td[@class="nom"]')
obj_address = CleanText('//table[@class="datalistecart"]//td[@class="adr"]')
class RedirectAfterVKPage(MyHTMLPage):
def check_pro_website_or_raise(self):
error_message = CleanText('//div[@id="erreur_identifiant_particulier"]//div[has-class("textFCK")]//p')(self.doc)
if error_message:
website_error = "L'identifiant utilisé est celui d'un compte de Particuliers"
if website_error in error_message:
raise BrowserIncorrectPassword(website_error)
raise AssertionError('Unhandled error message: %s' % error_message)
class Detect2FAPage(MyHTMLPage):
def raise_if_2fa_needed(self):
url = Attr('//iframe[@id="iFrame1"]', 'src', default='')(self.doc)
if url:
twofa_type = dict(parse_qsl(urlparse(url).query)).get('action', '')
if twofa_type != 'NULL': # seen so far: CERTICODE (sms), NULL (no 2fa activated by the user)
self.logger.info('A two factor auth is required on this connection')
raise ActionNeeded("Une authentification forte est requise sur votre espace client")