pax_global_header 0000666 0000000 0000000 00000000064 13434577234 0014526 g ustar 00root root 0000000 0000000 52 comment=80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/ 0000775 0000000 0000000 00000000000 13434577234 0023230 5 ustar 00root root 0000000 0000000 woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/ 0000775 0000000 0000000 00000000000 13434577234 0024700 5 ustar 00root root 0000000 0000000 woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/ 0000775 0000000 0000000 00000000000 13434577234 0025301 5 ustar 00root root 0000000 0000000 woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/ 0000775 0000000 0000000 00000000000 13434577234 0026400 5 ustar 00root root 0000000 0000000 woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/__init__.py 0000664 0000000 0000000 00000003370 13434577234 0030514 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 Nicolas Duhamel
#
# 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
# 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
from .login import LoginPage, Initident, CheckPassword,repositionnerCheminCourant, BadLoginPage, AccountDesactivate, UnavailablePage
from .accountlist import AccountList, AccountRIB, Advisor
from .accounthistory import AccountHistory, CardsList
from .transfer import TransferChooseAccounts, CompleteTransfer, TransferConfirm, TransferSummary, CreateRecipient, ValidateRecipient,\
ValidateCountry, ConfirmPage, RcptSummary
from .subscription import SubscriptionPage, DownloadPage, ProSubscriptionPage
__all__ = ['LoginPage', 'Initident', 'CheckPassword', 'repositionnerCheminCourant', "AccountList", 'AccountHistory', 'BadLoginPage',
'AccountDesactivate', 'TransferChooseAccounts', 'CompleteTransfer', 'TransferConfirm', 'TransferSummary', 'UnavailablePage',
'CardsList', 'AccountRIB', 'Advisor', 'CreateRecipient', 'ValidateRecipient', 'ValidateCountry', 'ConfirmPage', 'RcptSummary',
'SubscriptionPage', 'DownloadPage', 'ProSubscriptionPage']
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/accounthistory.py 0000664 0000000 0000000 00000036562 13434577234 0032044 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2012 Nicolas Duhamel
#
# 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
# 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
from __future__ import unicode_literals
import datetime
import re
from weboob.capabilities.base import NotAvailable, empty
from weboob.capabilities.bank import Investment, Transaction as BaseTransaction, Account
from weboob.exceptions import BrowserUnavailable
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.browser.pages import LoggedPage
from weboob.browser.elements import TableElement, ItemElement, method
from weboob.browser.filters.html import Link, TableCell
from weboob.browser.filters.standard import (
CleanDecimal, CleanText, Eval, Field, Async, AsyncLoad, Date, Env, Format,
Regexp,
)
from weboob.tools.compat import urljoin
from .base import MyHTMLPage
class Transaction(FrenchTransaction):
PATTERNS = [(re.compile(u'^(?PCHEQUE)( N)? (?P.*)'),
FrenchTransaction.TYPE_CHECK),
(re.compile(r'^(?PACHAT CB) (?P.*) (?P\d{2})\.(?P\d{2}).(?P\d{2,4}).*'),
FrenchTransaction.TYPE_CARD),
(re.compile('^(?P(PRELEVEMENT DE|TELEREGLEMENT|TIP)) (?P.*)'),
FrenchTransaction.TYPE_ORDER),
(re.compile('^(?PECHEANCEPRET)(?P.*)'),
FrenchTransaction.TYPE_LOAN_PAYMENT),
(re.compile(r'^CARTE \w+ (?P\d{2})/(?P\d{2})/(?P\d{2,4}) A \d+H\d+ (?PRETRAIT DAB) (?P.*)'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^(?PRETRAIT DAB) (?P\d{2})/(?P\d{2})/(?P\d{2,4}) \d+H\d+ (?P.*)'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^(?PRETRAIT) (?P.*) (?P\d{2})\.(?P\d{2})\.(?P\d{2,4})'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile('^(?PVIR(EMEN)?T?) (DE |POUR )?(?P.*)'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile('^(?PREMBOURST)(?P.*)'), FrenchTransaction.TYPE_PAYBACK),
(re.compile('^(?PCOMMISSIONS)(?P.*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?PFRAIS POUR)(?P.*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?P(?PREMUNERATION).*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?PREMISE DE CHEQUES?) (?P.*)'), FrenchTransaction.TYPE_DEPOSIT),
(re.compile(u'^(?PDEBIT CARTE BANCAIRE DIFFERE.*)'), FrenchTransaction.TYPE_CARD_SUMMARY),
(re.compile('^COTISATION TRIMESTRIELLE.*'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?PFRAIS (TRIMESTRIELS) DE TENUE DE COMPTE.*)'), FrenchTransaction.TYPE_BANK)
]
class AccountHistory(LoggedPage, MyHTMLPage):
def on_load(self):
if bool(CleanText(u'//h2[contains(text(), "ERREUR")]')(self.doc)):
raise BrowserUnavailable()
def is_here(self):
return not bool(CleanText(u'//h1[contains(text(), "tail de vos cartes")]')(self.doc))
def get_next_link(self):
for a in self.doc.xpath('//a[@class="btn_crt"]'):
txt = u''.join([txt.strip() for txt in a.itertext()])
if u'mois précédent' in txt:
return a.attrib['href']
def get_history(self, deferred=False):
"""
deffered is True when we are on a card page.
"""
mvt_table = self.doc.xpath("//table[@id='mouvements']", smart_strings=False)[0]
mvt_ligne = mvt_table.xpath("./tbody/tr")
operations = []
if deferred:
# look for the card number, debit date, and if it is already debited
txt = CleanText('//div[@class="infosynthese"]')(self.doc)
m = re.search(u'sur votre carte n°\*\*\*\*\*\*(\d+)\*', txt)
card_no = u'inconnu'
if m:
card_no = m.group(1)
m = re.search('(\d+)/(\d+)/(\d+)', txt)
if m:
debit_date = datetime.date(*map(int, reversed(m.groups())))
coming = 'En cours' in txt
if not coming:
# we must be on card account history: create a fake summary transaction
tr = self.generate_card_summary()
if tr.amount:
operations.append(tr)
else:
assert not self.has_transactions()
else:
coming = False
for mvt in mvt_ligne:
op = Transaction()
op.parse(date=CleanText('./td[1]/span')(mvt),
raw=CleanText('./td[2]/span')(mvt))
if op.label.startswith('DEBIT CARTE BANCAIRE DIFFERE'):
op.deleted = True
r = re.compile(r'\d+')
tmp = mvt.xpath("./td/span/strong")
if not tmp:
tmp = mvt.xpath("./td/span")
amount = None
if any("null" in t.text for t in tmp): # null amount, why not
continue
for t in tmp:
if r.search(t.text):
amount = t.text
op.set_amount(amount)
if deferred:
op._cardid = 'CARTE %s' % card_no
op.type = Transaction.TYPE_DEFERRED_CARD
op.rdate = op.date
op.date = debit_date
# on card page, amounts are without sign
if op.amount > 0:
op.amount = - op.amount
op._coming = coming
operations.append(op)
return operations
def generate_card_summary(self):
tr = Transaction()
text = CleanText('//div[@class="infosynthese"]')
# card account: positive summary amount
tr.amount = abs(CleanDecimal(Regexp(text, r'Montant imputé le \d+/\d+/\d+ : (.*) euros'), replace_dots=True)(self.doc))
tr.date = tr.rdate = Date(Regexp(text, r'Montant imputé le (\d+/\d+/\d+)'), dayfirst=True)(self.doc)
tr.type = tr.TYPE_CARD_SUMMARY
tr.label = 'DEBIT CARTE BANCAIRE DIFFERE'
tr._coming = False
return tr
def has_transactions(self):
return not CleanText(u'//table[@id="mouvementsTable" or @id="mouvements"]//tr[contains(., "pas d\'opérations") or contains(., "Pas d\'opération")]')(self.doc)
@method
class iter_transactions(TableElement):
head_xpath = u'//table[@id="mouvementsTable"]/thead/tr/th/a'
item_xpath = u'//table[@id="mouvementsTable"]/tbody/tr'
col_date = re.compile('Date')
col_label = re.compile(u'Libellé')
col_amount = re.compile('Valeur')
class item(ItemElement):
klass = Transaction
obj_raw = Transaction.Raw(Field('label'))
obj_date = Date(CleanText(TableCell('date')), dayfirst=True)
obj_amount = CleanDecimal(TableCell('amount'), replace_dots=True)
obj__coming = Env('coming', False)
def obj_label(self):
raw_label = CleanText(TableCell('label'))(self)
label = CleanText(TableCell('label')(self)[0].xpath('./br/following-sibling::text()'))(self)
if (label and label.split()[0] != raw_label.split()[0]) or not label:
label = raw_label
return CleanText(TableCell('label')(self)[0].xpath('./noscript'))(self) or label
def get_single_card(self, parent_id):
div, = self.doc.xpath('//div[@class="infosynthese"]')
ret = Account()
ret.type = Account.TYPE_CARD
ret.coming = CleanDecimal(Regexp(CleanText('.'), r'En cours prélevé au \d+/\d+/\d+ : ([\d\s,-]+) euros'), replace_dots=True)(div)
ret.number = Regexp(CleanText('.'), 'sur votre carte n°([\d*]+)')(div)
ret.id = '%s.%s' % (parent_id, ret.number)
ret.currency = 'EUR'
ret.label = 'CARTE %s' % ret.number
ret.url = self.url
return ret
class CardsList(LoggedPage, MyHTMLPage):
def is_here(self):
return bool(CleanText(u'//h1[contains(text(), "tail de vos cartes")]')(self.doc)) and not\
bool(CleanText(u'//h1[contains(text(), "tail de vos op")]')(self.doc))
@method
class get_cards(TableElement):
item_xpath = '//table[@class="dataNum"]/tbody/tr'
head_xpath = '//table[@class="dataNum"]/thead/tr/th'
col_label = re.compile('Vos cartes Encours actuel prélevé au')
col_balance = 'Euros'
col_number = 'Numéro'
col__credit = 'Crédit (euro)'
col__debit = 'Débit (euro)'
class item(ItemElement):
klass = Account
obj_type = Account.TYPE_CARD
obj_currency = 'EUR'
obj_number = CleanText(TableCell('number'))
obj_label = Format('%s %s', CleanText(TableCell('label')), obj_number)
obj_id = Format('%s.%s', Env('parent_id'), obj_number)
def obj_coming(self):
comings = (
CleanDecimal(TableCell('balance', default=None), replace_dots=True, default=None)(self),
CleanDecimal(TableCell('_credit', default=None), replace_dots=True, default=None)(self),
CleanDecimal(TableCell('_debit', default=None), replace_dots=True, default=None)(self)
)
for coming in comings:
if not empty(coming):
return coming
else:
# There should have at least 0.00 in debit column
assert False
def obj_url(self):
td = TableCell('label')(self)[0].xpath('.//a')[0]
return urljoin(self.page.url, td.attrib['href'])
class SavingAccountSummary(LoggedPage, MyHTMLPage):
def on_load(self):
link = Link('//ul[has-class("tabs")]//a[@title="Historique des mouvements"]', default=NotAvailable)(self.doc)
if link:
self.browser.location(link)
def get_balance(self):
return CleanDecimal(default=None, replace_dots=True).filter(
self.doc.xpath('//dt[span[text()="Total des versements bruts"]]/following::dd[1]/span/strong/text()'))
class InvestTable(TableElement):
col_label = 'Support'
col_share = [u'Poids en %', u'Répartition en %']
col_quantity = 'Nb U.C'
col_valuation = re.compile('Montant')
class InvestItem(ItemElement):
klass = Investment
obj_label = CleanText(TableCell('label', support_th=True))
obj_portfolio_share = Eval(lambda x: x / 100 if x else NotAvailable, CleanDecimal(TableCell('share', support_th=True), replace_dots=True, default=NotAvailable))
obj_quantity = CleanDecimal(TableCell('quantity', support_th=True), replace_dots=True, default=NotAvailable)
obj_valuation = CleanDecimal(TableCell('valuation', support_th=True), replace_dots=True, default=NotAvailable)
class CachemireCatalogPage(LoggedPage, MyHTMLPage):
def on_load(self):
self.product_codes = self.load_product_codes()
def load_product_codes(self):
# store ISIN codes in a dictionary with a (label: isin) fashion
product_codes = {}
for table in self.doc.xpath('//table/tbody'):
for row in table.xpath('//tr[contains(./th/@scope,"row")]'):
label = CleanText('./th[1]', default=None)(row)
isin_code = CleanText('./td[1]', default=None)(row)
if label and isin_code:
product_codes[label.upper()] = isin_code
return product_codes
class LifeInsuranceInvest(LoggedPage, MyHTMLPage):
def has_error(self):
return 'erreur' in CleanText('//p[has-class("titlePage")]')(self.doc) or 'ERREUR' in CleanText('//h2')(self.doc)
def get_cachemire_link(self):
return Link('//a[contains(@title, "espace cachemire")]', default=None)(self.doc)
@method
class iter_investments(InvestTable):
head_xpath = '//table[starts-with(@id, "mouvements")]/thead//th'
item_xpath = '//table[starts-with(@id, "mouvements")]/tbody//tr'
col_unitvalue = 'Valeur Liquidative'
col_vdate = 'Date'
class item(InvestItem):
obj_unitvalue = CleanDecimal(TableCell('unitvalue'), replace_dots=True, default=NotAvailable)
obj_vdate = Date(CleanText(TableCell('vdate')), dayfirst=True, default=NotAvailable)
class LifeInsuranceHistory(LoggedPage, MyHTMLPage):
@method
class get_history(TableElement):
head_xpath = '//table[@id="options"]/thead//th'
item_xpath = '//table[@id="options"]/tbody//tr'
col_date = 'Date de valeur'
col_amount = 'Montant'
col_label = u"Type d'opération"
class item(ItemElement):
klass = BaseTransaction
obj_label = CleanText(TableCell('label'))
obj_amount = CleanDecimal(TableCell('amount'), replace_dots=True)
obj_date = Date(CleanText(TableCell('date')), dayfirst=True)
obj__coming = False
load_invs = Link('.//a', default=NotAvailable) & AsyncLoad
def obj_investments(self):
try:
page = Async('invs').loaded_page(self)
return list(page.iter_investments())
except AttributeError: # No investments available
return list()
class LifeInsuranceHistoryInv(LoggedPage, MyHTMLPage):
@method
class iter_investments(InvestTable):
head_xpath = '//table/thead//th'
item_xpath = '//table/tbody//tr[count(td) >= 1 and count(th) = 1]'
def parse(self, el):
if len(el.xpath('//table/thead//th')) <= 2:
raise AttributeError() # Don't handle multiple invests in same tr
class item(InvestItem):
pass
class RetirementHistory(LoggedPage, MyHTMLPage):
@method
class get_history(TableElement):
head_xpath = '//table[@id="mvt" or @id="options" or @id="mouvements"]/thead//th'
item_xpath = '//table[@id="mvt" or @id="options" or @id="mouvements"]/tbody//tr'
col_date = re.compile('Date')
col_label = u"Type d'opération"
col_amount = 'Montant'
class item(ItemElement):
klass = BaseTransaction
obj_label = CleanText(TableCell('label'))
obj_date = Date(CleanText(TableCell('date')), dayfirst=True)
obj_amount = CleanDecimal(TableCell('amount'), replace_dots=True)
obj__coming = False
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/accountlist.py 0000664 0000000 0000000 00000047441 13434577234 0031314 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 Nicolas Duhamel
#
# 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
# 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
from __future__ import unicode_literals
from io import BytesIO
import re
from decimal import Decimal
from weboob.capabilities.base import NotAvailable
from weboob.capabilities.bank import Account, Loan
from weboob.capabilities.contact import Advisor
from weboob.capabilities.profile import Person
from weboob.browser.elements import ListElement, ItemElement, method, TableElement
from weboob.browser.pages import LoggedPage, RawPage, PartialHTMLPage, HTMLPage
from weboob.browser.filters.html import Link, TableCell
from weboob.browser.filters.standard import CleanText, CleanDecimal, Regexp, Env, Field, Currency, Async, Date, Format
from weboob.exceptions import BrowserUnavailable
from weboob.tools.compat import urljoin, unicode
from .base import MyHTMLPage
def MyDecimal(*args, **kwargs):
kwargs.update(replace_dots=True, default=NotAvailable)
return CleanDecimal(*args, **kwargs)
def MyDate(*args, **kwargs):
kwargs.update(dayfirst=True, default=NotAvailable)
return Date(*args, **kwargs)
class item_account_generic(ItemElement):
klass = Account
def condition(self):
return len(self.el.xpath('.//span[@class="number"]')) > 0
obj_id = CleanText('.//abbr/following-sibling::text()')
obj_currency = Currency('.//span[@class="number"]')
def obj_url(self):
url = Link(u'./a', default=NotAvailable)(self)
if url:
if 'CreditRenouvelable' in url:
url = Link(u'.//a[contains(text(), "espace de gestion crédit renouvelable")]')(self.el)
return urljoin(self.page.url, url)
return url
def obj_label(self):
return CleanText('.//div[@class="title"]/h3')(self).upper()
def obj_balance(self):
if Field('type')(self) == Account.TYPE_LOAN:
return -abs(CleanDecimal('.//span[@class="number"]', replace_dots=True)(self))
return CleanDecimal('.//span[@class="number"]', replace_dots=True, default=NotAvailable)(self)
def obj_coming(self):
if Field('type')(self) == Account.TYPE_CHECKING:
has_coming = False
coming = 0
details_page = self.page.browser.open(Field('url')(self))
coming_op_link = Link('//a[contains(text(), "Opérations à venir")]', default=NotAvailable)(details_page.page.doc)
if coming_op_link:
coming_op_link = Regexp(Link('//a[contains(text(), "Opérations à venir")]'), r'../(.*)')(details_page.page.doc)
coming_operations = self.page.browser.open(self.page.browser.BASEURL + '/voscomptes/canalXHTML/CCP/' + coming_op_link)
else:
coming_op_link = Link('//a[contains(text(), "Opérations en cours")]')(details_page.page.doc)
coming_operations = self.page.browser.open(coming_op_link)
if CleanText('//span[@id="amount_total"]')(coming_operations.page.doc):
has_coming = True
coming += CleanDecimal('//span[@id="amount_total"]', replace_dots=True)(coming_operations.page.doc)
if CleanText(u'.//dt[contains(., "Débit différé à débiter")]')(self):
has_coming = True
coming += CleanDecimal(u'.//dt[contains(., "Débit différé à débiter")]/following-sibling::dd[1]',
replace_dots=True)(self)
return coming if has_coming else NotAvailable
return NotAvailable
def obj_iban(self):
rib_link = Link('//a[abbr[contains(text(), "RIB")]]', default=NotAvailable)(self.el)
if rib_link:
response = self.page.browser.open(rib_link)
return response.page.get_iban()
elif Field('type')(self) == Account.TYPE_SAVINGS:
# The rib link is available on the history page (ex: Livret A)
his_page = self.page.browser.open(Field('url')(self))
rib_link = Link('//a[abbr[contains(text(), "RIB")]]', default=NotAvailable)(his_page.page.doc)
if rib_link:
response = self.page.browser.open(rib_link)
return response.page.get_iban()
return NotAvailable
def obj_type(self):
types = {'comptes? bancaires?': Account.TYPE_CHECKING,
'livrets?': Account.TYPE_SAVINGS,
'epargnes? logement': Account.TYPE_SAVINGS,
"autres produits d'epargne": Account.TYPE_SAVINGS,
'comptes? titres? et pea': Account.TYPE_MARKET,
'compte-titres': Account.TYPE_MARKET,
'assurances? vie': Account.TYPE_LIFE_INSURANCE,
'prêt': Account.TYPE_LOAN,
'crédits?': Account.TYPE_LOAN,
'plan d\'epargne en actions': Account.TYPE_PEA,
'comptes? attente': Account.TYPE_CHECKING,
'perp': Account.TYPE_PERP,
}
# first trying to match with label
label = Field('label')(self)
for atypetxt, atype in types.items():
if re.findall(atypetxt, label.lower()): # match with/without plurial in type
return atype
# then by type
type = Regexp(CleanText('../../preceding-sibling::div[@class="avoirs"][1]/span[1]'), r'(\d+) (.*)', '\\2')(self)
for atypetxt, atype in types.items():
if re.findall(atypetxt, type.lower()): # match with/without plurial in type
return atype
return Account.TYPE_UNKNOWN
def obj__has_cards(self):
return Link(u'.//a[contains(., "Débit différé")]', default=None)(self)
class AccountList(LoggedPage, MyHTMLPage):
def on_load(self):
MyHTMLPage.on_load(self)
if self.doc.xpath(u'//h2[text()="ERREUR"]'): # website sometime crash
self.browser.location('https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/securite/authentification/initialiser-identif.ea')
raise BrowserUnavailable()
def go_revolving(self):
form = self.get_form()
form.submit()
@property
def no_accounts(self):
return len(self.doc.xpath('//iframe[contains(@src, "/comptes_contrats/sans_")] |\
//iframe[contains(@src, "bel_particuliers/prets/prets_nonclient")]')) > 0
@property
def has_mandate_management_space(self):
return len(self.doc.xpath(u'//a[@title="Accéder aux Comptes Gérés Sous Mandat"]')) > 0
def mandate_management_space_link(self):
return Link(u'//a[@title="Accéder aux Comptes Gérés Sous Mandat"]')(self.doc)
@method
class iter_accounts(ListElement):
item_xpath = u'//ul/li//div[contains(@class, "account-resume")]'
class item_account(item_account_generic):
def condition(self):
return item_account_generic.condition(self)
def get_revolving_attributes(self, account):
loan = Loan()
loan.id = account.id
loan.label = '%s - %s' %(account.label, account.id)
loan.currency = account.currency
loan.url = account.url
loan.available_amount = CleanDecimal('//tr[td[contains(text(), "Montant Maximum Autorisé") or contains(text(), "Montant autorisé")]]/td[2]')(self.doc)
loan.used_amount = loan.used_amount = CleanDecimal('//tr[td[contains(text(), "Montant Utilisé") or contains(text(), "Montant utilisé")]]/td[2]')(self.doc)
loan.available_amount = CleanDecimal(Regexp(CleanText('//tr[td[contains(text(), "Montant Disponible") or contains(text(), "Montant disponible")]]/td[2]'), r'(.*) au'))(self.doc)
loan._has_cards = False
loan.type = Account.TYPE_REVOLVING_CREDIT
return loan
@method
class iter_revolving_loans(ListElement):
item_xpath = '//div[@class="bloc Tmargin"]//dl'
class item_account(ItemElement):
klass = Loan
obj_id = CleanText('./dd[1]//em')
obj_label = 'Crédit renouvelable'
obj_total_amount = MyDecimal('./dd[2]/span')
obj_used_amount = MyDecimal('./dd[3]/span')
obj_available_amount = MyDecimal('./dd[4]//em')
obj_insurance_label = CleanText('./dd[5]//em', children=False)
obj__has_cards = False
obj_type = Account.TYPE_LOAN
def obj_url(self):
return self.page.url
@method
class iter_loans(TableElement):
head_xpath = '//table[@id="pret" or @class="dataNum"]/thead//th'
item_xpath = '//table[@id="pret"]/tbody/tr'
col_label = (u'Numéro du prêt', "Numéro de l'offre")
col_total_amount = u'Montant initial emprunté'
col_subscription_date = u'MONTANT INITIAL EMPRUNTÉ'
col_next_payment_amount = u'Montant prochaine échéance'
col_next_payment_date = u'Date prochaine échéance'
col_balance = re.compile('Capital')
col_maturity_date = re.compile(u'Date dernière')
class item_loans(ItemElement):
# there is two cases : the mortgage and the consumption loan. These cases have differents way to get the details
# except for student loans, you may have 1 or 2 tables to deal with
# if 1 table, item_loans is used for student loan
# if 2 tables, get_student_loan is used
klass = Loan
def condition(self):
if CleanText(TableCell('balance'))(self) != u'Prêt non débloqué':
return bool(not self.xpath('//caption[contains(text(), "Période de franchise du")]'))
return CleanText(TableCell('balance'))(self) != u'Prêt non débloqué'
def load_details(self):
url = Link('.//a', default=NotAvailable)(self)
return self.page.browser.async_open(url=url)
obj_total_amount = CleanDecimal(TableCell('total_amount'), replace_dots=True, default=NotAvailable)
def obj_id(self):
if TableCell('label', default=None)(self):
return Regexp(CleanText(Field('label'), default=NotAvailable), '- (\w{16})')(self)
# student_loan
if CleanText('//select[@id="numOffrePretSelection"]/option[@selected="selected"]')(self):
return Regexp(CleanText('//select[@id="numOffrePretSelection"]/option[@selected="selected"]'), r'(\d+)')(self)
return CleanText('//form[contains(@action, "detaillerOffre") or contains(@action, "detaillerPretPartenaireListe-encoursPrets.ea")]/div[@class="bloc Tmargin"]/div[@class="formline"][2]/span/strong')(self)
obj_type = Account.TYPE_LOAN
def obj_label(self):
cell = TableCell('label', default=None)(self)
if cell:
return CleanText(cell, default=NotAvailable)(self).upper()
return CleanText('//form[contains(@action, "detaillerOffre") or contains(@action, "detaillerPretPartenaireListe-encoursPrets.ea")]/div[@class="bloc Tmargin"]/h2[@class="title-level2"]')(self).upper()
def obj_balance(self):
if CleanText(TableCell('balance'))(self) != u'Remboursé intégralement':
return -abs(CleanDecimal(TableCell('balance'), replace_dots=True)(self))
return Decimal(0)
def obj_subscription_date(self):
xpath = '//form[contains(@action, "detaillerOffre")]/div[1]/div[2]/span'
if 'souscrite le' in CleanText(xpath)(self):
return MyDate(Regexp(CleanText(xpath), ' (\d{2}/\d{2}/\d{4})', default=NotAvailable))(self)
return NotAvailable
obj_next_payment_amount = CleanDecimal(TableCell('next_payment_amount'), replace_dots=True, default=NotAvailable)
def obj_maturity_date(self):
if Field('subscription_date')(self):
async_page = Async('details').loaded_page(self)
date = MyDate(CleanText('//div[@class="bloc Tmargin"]/dl[2]/dd[4]', default=NotAvailable))(async_page.doc)
return date
return MyDate(CleanText(TableCell('maturity_date')), default=NotAvailable)(self)
def obj_last_payment_date(self):
xpath = '//div[@class="bloc Tmargin"]/div[@class="formline"][2]/span'
if 'dont le dernier' in CleanText(xpath)(self):
return MyDate(Regexp(CleanText(xpath), ' (\d{2}/\d{2}/\d{4})', default=NotAvailable))(self)
async_page = Async('details').loaded_page(self)
return MyDate(CleanText('//div[@class="bloc Tmargin"]/dl[1]/dd[2]'), default=NotAvailable)(async_page.doc)
obj_next_payment_date = MyDate(CleanText(TableCell('next_payment_date')), default=NotAvailable)
def obj_url(self):
url = Link(u'.//a', default=None)(self)
if url:
return urljoin(self.page.url, url)
return self.page.url
obj__has_cards = False
@method
class get_student_loan(ItemElement):
# 2 tables student loan
klass = Loan
def condition(self):
return bool(self.xpath('//caption[contains(text(), "Période de franchise du")]'))
# get all table headers
def obj__heads(self):
heads_xpath = '//table[@class="dataNum"]/thead//th'
return [CleanText('.')(head) for head in self.xpath(heads_xpath)]
# get all table elements
def obj__items(self):
items_xpath = '//table[@class="dataNum"]/tbody//td'
return [CleanText('.')(item) for item in self.xpath(items_xpath)]
def get_element(self, header_name):
for index, head in enumerate(Field('_heads')(self)):
if header_name in head:
return Field('_items')(self)[index]
assert False
obj_id = Regexp(CleanText('//select[@id="numOffrePretSelection"]/option[@selected="selected"]'), r'(\d+)')
obj_type = Account.TYPE_LOAN
obj__has_cards = False
def obj_total_amount(self):
return CleanDecimal(replace_dots=True).filter(self.get_element('Montant initial'))
def obj_label(self):
return Regexp(CleanText('//h2[@class="title-level2"]'), r'([\w ]+)', flags=re.U)(self)
def obj_balance(self):
return -CleanDecimal(replace_dots=True).filter(self.get_element('Capital restant'))
def obj_maturity_date(self):
return Date(dayfirst=True).filter(self.get_element('Date derni'))
def obj_duration(self):
return CleanDecimal().filter(self.get_element("de l'amortissement"))
def obj_next_payment_date(self):
return Date(dayfirst=True).filter(self.get_element('Date de prochaine'))
def obj_next_payment_amount(self):
if 'Donnée non disponible' in CleanText().filter(self.get_element('Montant prochaine')):
return NotAvailable
return CleanDecimal(replace_dots=True).filter(self.get_element('Montant prochaine'))
def obj_url(self):
return self.page.url
class Advisor(LoggedPage, MyHTMLPage):
@method
class get_advisor(ItemElement):
klass = Advisor
obj_name = Env('name')
obj_phone = Env('phone')
obj_mobile = Env('mobile', default=NotAvailable)
obj_agency = Env('agency', default=NotAvailable)
obj_email = NotAvailable
def obj_address(self):
return CleanText('//div[h3[contains(text(), "Bureau")]]/div[not(@class)][position() > 1]')(self) or NotAvailable
def parse(self, el):
# we have two kinds of page and sometimes we don't have any advisor
agency_phone = CleanText('//span/a[contains(@href, "rendezVous")]', replace=[(' ', '')], default=NotAvailable)(self) or \
CleanText('//div[has-class("lbp-numero")]/span', replace=[(' ', '')], default=NotAvailable)(self)
advisor_phone = Regexp(CleanText('//div[h3[contains(text(), "conseil")]]//span[2]', replace=[(' ', '')], default=""), '(\d+)', default="")(self)
if advisor_phone.startswith(("06", "07")):
self.env['phone'] = agency_phone
self.env['mobile'] = advisor_phone
else:
self.env['phone'] = advisor_phone or agency_phone
agency = CleanText('//div[h3[contains(text(), "Bureau")]]/div[not(@class)][1]')(self) or NotAvailable
name = CleanText('//div[h3[contains(text(), "conseil")]]//span[1]', default=None)(self) or \
CleanText('//div[@class="lbp-font-accueil"]/div[2]/div[1]/span[1]', default=None)(self)
if name:
self.env['name'] = name
self.env['agency'] = agency
else:
self.env['name'] = agency
class AccountRIB(LoggedPage, RawPage):
iban_regexp = r'[A-Z]{2}\d{12}[0-9A-Z]{11}\d{2}'
def __init__(self, *args, **kwargs):
super(AccountRIB, self).__init__(*args, **kwargs)
self.parsed_text = b''
try:
try:
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
newapi = True
except ImportError:
from pdfminer.pdfparser import PDFDocument
newapi = False
from pdfminer.pdfparser import PDFParser, PDFSyntaxError
from pdfminer.converter import TextConverter
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
except ImportError:
self.logger.warning('Please install python-pdfminer to get IBANs')
else:
parser = PDFParser(BytesIO(self.doc))
try:
if newapi:
doc = PDFDocument(parser)
else:
doc = PDFDocument()
parser.set_document(doc)
doc.set_parser(parser)
except PDFSyntaxError:
return
rsrcmgr = PDFResourceManager()
out = BytesIO()
device = TextConverter(rsrcmgr, out)
interpreter = PDFPageInterpreter(rsrcmgr, device)
if newapi:
pages = PDFPage.create_pages(doc)
else:
doc.initialize()
pages = doc.get_pages()
for page in pages:
interpreter.process_page(page)
self.parsed_text = out.getvalue()
def get_iban(self):
m = re.search(self.iban_regexp, self.parsed_text.decode('utf-8'))
if m:
return unicode(m.group(0))
return None
class MarketLoginPage(LoggedPage, PartialHTMLPage):
def on_load(self):
self.get_form(id='autoSubmit').submit()
class UselessPage(LoggedPage, HTMLPage):
pass
class ProfilePage(LoggedPage, HTMLPage):
def get_profile(self):
profile = Person()
profile.name = Format('%s %s', CleanText('//div[@id="persoIdentiteDetail"]//dd[3]'), CleanText('//div[@id="persoIdentiteDetail"]//dd[2]'))(self.doc)
profile.address = CleanText('//div[@id="persoAdresseDetail"]//dd')(self.doc)
profile.email = CleanText('//div[@id="persoEmailDetail"]//td[2]')(self.doc)
profile.job = CleanText('//div[@id="persoIdentiteDetail"]//dd[4]')(self.doc)
return profile
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/base.py 0000664 0000000 0000000 00000002063 13434577234 0027665 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2016 budget-insight
#
# 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
# 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
from weboob.browser.pages import HTMLPage
class MyHTMLPage(HTMLPage):
def on_load(self):
deconnexion = self.doc.xpath('//iframe[contains(@id, "deconnexion")] | //p[@class="txt" and contains(text(), "Session expir")]')
if deconnexion:
self.browser.do_login()
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/login.py 0000664 0000000 0000000 00000012144 13434577234 0030064 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 Nicolas Duhamel
#
# 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
# 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
from __future__ import unicode_literals, division
from io import BytesIO
from weboob.exceptions import BrowserUnavailable, BrowserIncorrectPassword, NoAccountsException
from weboob.browser.pages import LoggedPage
from weboob.browser.filters.standard import CleanText, Regexp
from weboob.tools.captcha.virtkeyboard import VirtKeyboard
from .base import MyHTMLPage
class UnavailablePage(MyHTMLPage):
def on_load(self):
raise BrowserUnavailable()
class Keyboard(VirtKeyboard):
symbols={'0':'daa52d75287bea58f505823ef6c8b96c',
'1':'f5da96c2592803a8cdc5a928a2e4a3b0',
'2':'9ff78367d5cb89cacae475368a11e3af',
'3':'908a0a42a424b95d4d885ce91bc3d920',
'4':'3fc069f33b801b3d0cdce6655a65c0ac',
'5':'58a2afebf1551d45ccad79fad1600fc3',
'6':'7fedfd9e57007f2985c3a1f44fb38ea1',
'7':'389b8ef432ae996ac0141a2fcc7b540f',
'8':'bf357ff09cc29ea544991642cd97d453',
'9':'b744015eb89c1b950e13a81364112cd6',
}
color=(0xff, 0xff, 0xff)
def __init__(self, page):
img_url = Regexp(CleanText('//style'), r'background:url\((.*?)\)', default=None)(page.doc) or \
Regexp(CleanText('//script'), r'IMG_ALL = "(.*?)"', default=None)(page.doc)
size = 252
if not img_url:
img_url = page.doc.xpath('//img[@id="imageCVS"]')[0].attrib['src']
size = 146
coords = {}
x, y, width, height = (0, 0, size // 4, size // 4)
for i, _ in enumerate(page.doc.xpath('//div[@id="imageclavier"]//button')):
code = '%02d' % i
coords[code] = (x+4, y+4, x+width-8, y+height-8)
if (x + width + 1) >= size:
y += height + 1
x = 0
else:
x += width + 1
data = page.browser.open(img_url).content
VirtKeyboard.__init__(self, BytesIO(data), coords, self.color)
self.check_symbols(self.symbols, page.browser.responses_dirname)
def get_symbol_code(self,md5sum):
code = VirtKeyboard.get_symbol_code(self,md5sum)
return '%02d' % int(code.split('_')[-1])
def get_string_code(self,string):
code = ''
for c in string:
code += self.get_symbol_code(self.symbols[c])
return code
def get_symbol_coords(self, coords):
# strip borders
x1, y1, x2, y2 = coords
return VirtKeyboard.get_symbol_coords(self, (x1+3, y1+3, x2-3, y2-3))
class LoginPage(MyHTMLPage):
def login(self, login, pwd):
vk = Keyboard(self)
form = self.get_form(name='formAccesCompte')
form['password'] = vk.get_string_code(pwd)
form['username'] = login
form.submit()
class repositionnerCheminCourant(LoggedPage, MyHTMLPage):
def on_load(self):
super(repositionnerCheminCourant, self).on_load()
response = self.browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/securite/authentification/initialiser-identif.ea")
if isinstance(response.page, Initident):
response.page.on_load()
if "vous ne disposez pas" in response.text:
raise BrowserIncorrectPassword("No online banking service for these ids")
if 'Nous vous invitons à renouveler votre opération ultérieurement' in response.text:
raise BrowserUnavailable()
class Initident(LoggedPage, MyHTMLPage):
def on_load(self):
self.browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/securite/authentification/verifierMotDePasse-identif.ea")
if self.doc.xpath(u'//span[contains(text(), "L\'identifiant utilisé est celui d\'une Entreprise ou d\'une Association")]'):
raise BrowserIncorrectPassword(u"L'identifiant utilisé est celui d'une Entreprise ou d'une Association")
no_accounts = CleanText(u'//div[@class="textFCK"]')(self.doc)
if no_accounts:
raise NoAccountsException(no_accounts)
MyHTMLPage.on_load(self)
class CheckPassword(LoggedPage, MyHTMLPage):
def on_load(self):
MyHTMLPage.on_load(self)
self.browser.location("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/comptesCommun/synthese_assurancesEtComptes/init-synthese.ea")
class BadLoginPage(MyHTMLPage):
pass
class AccountDesactivate(LoggedPage, MyHTMLPage):
pass
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/mandate.py 0000664 0000000 0000000 00000010760 13434577234 0030367 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2017 Baptiste Delpey
#
# 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
# 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
import re
from weboob.browser.pages import LoggedPage, HTMLPage, pagination
from weboob.browser.elements import TableElement, ItemElement, method
from weboob.browser.filters.html import Link, Attr, TableCell
from weboob.browser.filters.standard import CleanText, CleanDecimal, Regexp, \
Format, Currency
from weboob.capabilities.base import NotAvailable
from weboob.capabilities.bank import Account, Investment
class PreMandate(LoggedPage, HTMLPage):
def on_load(self):
form = self.get_form()
form.submit()
class PreMandateBis(LoggedPage, HTMLPage):
def on_load(self):
link = re.match("document.location.href = '([^']+)';$", CleanText(u'//script')(self.doc)).group(1)
self.browser.location(link)
class MandateAccountsList(LoggedPage, HTMLPage):
@method
class iter_accounts(TableElement):
item_xpath = '//table[@id="accounts"]/tbody/tr'
head_xpath = '//table[@id="accounts"]/thead/tr/th/a'
col_id = re.compile(u'N° de compte')
col_name = 'Nom'
col_type = 'Type'
col_valorisation = 'Valorisation'
col_perf = re.compile('Perf')
class Item(ItemElement):
TYPES = {'CIFO': Account.TYPE_MARKET,
'PEA': Account.TYPE_PEA,
'Excelis VIE': Account.TYPE_LIFE_INSURANCE,
'Satinium': Account.TYPE_LIFE_INSURANCE,
'Satinium CAPI': Account.TYPE_LIFE_INSURANCE,
'Excelis CAPI': Account.TYPE_LIFE_INSURANCE,
}
klass = Account
obj_id = CleanText(TableCell('id'))
obj_label = Format('%s %s', CleanText(TableCell('type')), CleanText(TableCell('name')))
obj_currency = Currency(TableCell('valorisation'))
obj_bank_name = u'La Banque postale'
obj_balance = CleanDecimal(TableCell('valorisation'), replace_dots=True)
obj_url = Link(TableCell('id'))
obj_iban = NotAvailable
def obj_url(self):
td = TableCell('id')(self)[0]
return Link(td.xpath('./a'))(self)
def obj_type(self):
return self.TYPES.get(CleanText(TableCell('type'))(self), Account.TYPE_UNKNOWN)
class Myiter_investments(TableElement):
col_isin = 'Code ISIN'
col_label = u'Libellé'
col_unitvalue = u'Cours'
col_valuation = 'Valorisation'
class MyInvestItem(ItemElement):
klass = Investment
obj_code = CleanText(TableCell('isin'))
obj_label = CleanText(TableCell('label'))
obj_quantity = CleanDecimal(TableCell('quantity'), replace_dots=True)
obj_unitvalue = CleanDecimal(TableCell('unitvalue'), replace_dots=True)
obj_valuation = CleanDecimal(TableCell('valuation'), replace_dots=True)
class MandateLife(LoggedPage, HTMLPage):
@pagination
@method
class iter_investments(Myiter_investments):
item_xpath = '//table[@id="asvSupportList"]/tbody/tr[count(td)>=5]'
head_xpath = '//table[@id="asvSupportList"]/thead/tr/th'
next_page = Regexp(Attr('//div[@id="turn_next"]/a', 'onclick'), 'href: "([^"]+)"')
col_quantity = u'Quantité'
class Item(MyInvestItem):
pass
class MandateMarket(LoggedPage, HTMLPage):
@method
class iter_investments(Myiter_investments):
# FIXME table was empty
item_xpath = '//table[@id="valuation"]/tbody/tr[count(td)>=9]'
head_xpath = '//table[@id="valuation"]/thead/tr/th'
col_quantity = u'Qté'
col_unitprice = u'Prix moyen'
col_share = u'Poids'
class Item(MyInvestItem):
obj_unitprice = CleanDecimal(TableCell('unitprice'), replace_dots=True)
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/pro.py 0000664 0000000 0000000 00000011535 13434577234 0027557 0 ustar 00root root 0000000 0000000 # -*- 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 Affero 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
import re
from decimal import Decimal
from weboob.browser.elements import ListElement, ItemElement, method
from weboob.browser.filters.standard import CleanText, CleanDecimal, Date
from weboob.browser.filters.html import Link
from weboob.browser.pages import LoggedPage, pagination
from weboob.capabilities.bank import Account
from weboob.capabilities.profile import Company
from weboob.capabilities.base import NotAvailable
from weboob.tools.compat import urljoin, unicode
from weboob.exceptions import BrowserUnavailable
from .accounthistory import Transaction
from .base import MyHTMLPage
class RedirectPage(LoggedPage, MyHTMLPage):
def check_for_perso(self):
return self.doc.xpath(u'//p[contains(text(), "L\'identifiant utilisé est celui d\'un compte de Particuliers")]')
class ProAccountsList(LoggedPage, MyHTMLPage):
def on_load(self):
if self.doc.xpath('//div[@id="erreur_generale"]'):
raise BrowserUnavailable(CleanText(u'//div[@id="erreur_generale"]//p[contains(text(), "Le service est momentanément indisponible")]')(self.doc))
ACCOUNT_TYPES = {u'comptes titres': Account.TYPE_MARKET,
u'comptes épargne': Account.TYPE_SAVINGS,
# wtf? ^
u'comptes épargne': Account.TYPE_SAVINGS,
u'comptes courants': Account.TYPE_CHECKING,
}
def get_accounts_list(self):
for table in self.doc.xpath('//div[@class="comptestabl"]/table'):
try:
account_type = self.ACCOUNT_TYPES[table.get('summary').lower()]
if not account_type:
account_type = self.ACCOUNT_TYPES[table.xpath('./caption/text()')[0].strip().lower()]
except (IndexError,KeyError):
account_type = Account.TYPE_UNKNOWN
for tr in table.xpath('./tbody/tr'):
cols = tr.findall('td')
link = cols[0].find('a')
if link is None:
continue
a = Account()
a.type = account_type
a.id = unicode(re.search('([A-Z\d]{4}[A-Z\d\*]{3}[A-Z\d]{4})', link.attrib['title']).group(1))
a.label = unicode(link.attrib['title'].replace('%s ' % a.id, ''))
tmp_balance = CleanText(None).filter(cols[1])
a.currency = a.get_currency(tmp_balance)
if not a.currency:
a.currency = u'EUR'
a.balance = Decimal(Transaction.clean_amount(tmp_balance))
a._has_cards = False
a.url = urljoin(self.url, link.attrib['href'])
yield a
class ProAccountHistory(LoggedPage, MyHTMLPage):
@pagination
@method
class iter_history(ListElement):
item_xpath = u'//div[@id="tabReleve"]//tbody/tr'
next_page = Link('//div[@class="pagination"]//li[@class="pagin-on-right"]/a')
class item(ItemElement):
klass = Transaction
obj_date = Date(CleanText('.//td[@headers="date"]'), dayfirst=True)
obj_raw = Transaction.Raw('.//td[@headers="libelle"]')
obj_amount = CleanDecimal('.//td[@headers="debit" or @headers="credit"]',
replace_dots=True, default=NotAvailable)
class DownloadRib(LoggedPage, MyHTMLPage):
def get_rib_value(self, acc_id):
opt = self.doc.xpath('//div[@class="rechform"]//option')
for o in opt:
if acc_id in o.text:
return o.xpath('./@value')[0]
return None
class RibPage(LoggedPage, MyHTMLPage):
def get_iban(self):
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])\
.replace(' ', '').strip()
return None
@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"]')
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/subscription.py 0000664 0000000 0000000 00000015131 13434577234 0031477 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2018 Célande Adrien
#
# 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
# 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
from __future__ import unicode_literals
import re
from weboob.capabilities.bill import DocumentTypes, Subscription, Document
from weboob.browser.pages import LoggedPage, HTMLPage
from weboob.browser.filters.standard import CleanText, Regexp, Env, Date, Format, Field
from weboob.browser.filters.html import Link, Attr, TableCell
from weboob.browser.elements import ListElement, ItemElement, method, TableElement
class SubscriptionPage(LoggedPage, HTMLPage):
# because of freaking JS from hell
STATEMENT_TYPES = ('RCE', 'RPT', 'RCO')
@method
class iter_subscriptions(ListElement):
item_xpath = '//select[@id="compte"]/option'
class item(ItemElement):
klass = Subscription
obj_id = Regexp(Attr('.', 'value'), r'\w-(\w+)')
obj_label = CleanText('.')
obj_subscriber = Env('subscriber')
@method
class iter_documents(ListElement):
def condition(self):
return not (
CleanText('//p[contains(text(), "est actuellement indisponible")]')(self)
or CleanText('//p[contains(text(), "Aucun e-Relevé n\'est disponible")]')(self)
)
item_xpath = '//ul[contains(@class, "liste-cpte")]/li'
# you can have twice the same statement: same month, same subscription
ignore_duplicate = True
class item(ItemElement):
klass = Document
obj_id = Format('%s_%s%s', Env('sub_id'), Regexp(CleanText('.//a/@title'), r' (\d{2}) '), CleanText('.//span[contains(@class, "date")]' ,symbols='/'))
obj_label = Format('%s - %s', CleanText('.//span[contains(@class, "lib")]'), CleanText('.//span[contains(@class, "date")]'))
obj_url = Format('/voscomptes/canalXHTML/relevePdf/relevePdf_historique/%s', Link('./a'))
obj_format = 'pdf'
obj_type = DocumentTypes.OTHER
def obj_date(self):
date = CleanText('.//span[contains(@class, "date")]')(self)
m = re.search(r'(\d{2}/\d{2}/\d{4})', date)
if m:
return Date(CleanText('.//span[contains(@class, "date")]'), dayfirst=True)(self)
else:
return Date(
Format(
'%s/%s',
Regexp(CleanText('.//a/@title'), r' (\d{2}) '),
CleanText('.//span[contains(@class, "date")]')
),
dayfirst=True
)(self)
def get_params(self, sub_label):
# the id is in the label
sub_value = Attr('//select[@id="compte"]/option[contains(text(), "%s")]' % sub_label, 'value')(self.doc)
form = self.get_form(name='formulaireHistorique')
form['formulaire.numeroCompteRecherche'] = sub_value
return form
def get_years(self):
return self.doc.xpath('//select[@id="annee"]/option/@value')
def has_error(self):
return (
CleanText('//p[contains(text(), "est actuellement indisponible")]')(self.doc)
or CleanText('//p[contains(text(), "Aucun e-Relevé n\'est disponible")]')(self.doc)
)
class DownloadPage(LoggedPage, HTMLPage):
def get_content(self):
if self.doc.xpath('//iframe'):
# the url has the form
# ../relevePdf_telechargement/affichagePDF-telechargementPDF.ea?date=XXX
part_link = Attr('//iframe', 'src')(self.doc).replace('..', '')
return self.browser.open('/voscomptes/canalXHTML/relevePdf%s' % part_link).content
return self.content
class ProSubscriptionPage(LoggedPage, HTMLPage):
@method
class iter_subscriptions(ListElement):
item_xpath = '//select[@id="numeroCompteRechercher"]/option'
class item(ItemElement):
klass = Subscription
obj_label = CleanText('.')
obj_id = Regexp(Field('label'), r'\w? ?- (\w+)')
obj_subscriber = Env('subscriber')
obj__number = Attr('.', 'value')
@method
class iter_documents(TableElement):
item_xpath = '//table[@id="relevesPDF"]//tr[td]'
head_xpath = '//table[@id="relevesPDF"]//th'
# may have twice the same statement for a given month
ignore_duplicate = True
col_date = re.compile('Date du relevé')
col_label = re.compile('Type de document')
class item(ItemElement):
klass = Document
obj_date = Date(CleanText(TableCell('date')), dayfirst=True)
obj_label = Format('%s %s', CleanText(TableCell('label')), CleanText(TableCell('date')))
obj_id = Format('%s_%s', Env('sub_id'), CleanText(TableCell('date'), symbols='/'))
# the url uses an id depending on the page where the document is
# by example, if the id is 0,
# it means that it is the first document that you can find
# on the page of the year XXX for the subscription YYYY
obj_url = Link('.//a')
obj_format = 'pdf'
obj_type = DocumentTypes.OTHER
def submit_form(self, sub_number, year):
form = self.get_form(name='formRechHisto')
form['historiqueReleveParametre.numeroCompteRecherche'] = sub_number
form['typeRecherche'] = 'annee'
form['anneeRechercheDefaut'] = year
form.submit()
def get_years(self):
return self.doc.xpath('//select[@name="anneeRechercheDefaut"]/option/@value')
def no_statement(self):
return self.doc.xpath('//p[has-class("noresult")]')
def has_document(self, date):
return self.doc.xpath('//td[@headers="dateReleve" and contains(text(), "%s")]' % date.strftime('%d/%m/%Y'))
def get_sub_number(self, doc_id):
sub_id = doc_id.split('_')[0]
return Attr('//select[@id="numeroCompteRechercher"]/option[contains(text(), "%s")]' % sub_id, 'value')(self.doc)
woob-80d8d4f930af694bb2ce2ffeb7ce41d7e40f45cd-modules-bp-pages/modules/bp/pages/transfer.py 0000664 0000000 0000000 00000027025 13434577234 0030604 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 Nicolas Duhamel
#
# 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
# 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see .
from datetime import datetime
from weboob.capabilities.bank import (
TransferError, TransferBankError, Transfer, TransferStep, NotAvailable, Recipient,
AccountNotFound, AddRecipientBankError
)
from weboob.capabilities.base import find_object
from weboob.browser.pages import LoggedPage
from weboob.browser.filters.standard import CleanText, Env, Regexp, Date, CleanDecimal
from weboob.browser.filters.html import Attr, Link
from weboob.browser.elements import ListElement, ItemElement, method, SkipItem
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.tools.capabilities.bank.iban import is_iban_valid
from weboob.tools.value import Value
from weboob.exceptions import BrowserUnavailable
from .base import MyHTMLPage
class CheckTransferError(MyHTMLPage):
def on_load(self):
MyHTMLPage.on_load(self)
error = CleanText(u'//span[@class="app_erreur"] | //p[@class="warning"] | //p[contains(text(), "Votre virement n\'a pas pu être enregistré")]')(self.doc)
if error and not u'Votre demande de virement a été enregistrée le' in error:
raise TransferBankError(message=error)
class TransferChooseAccounts(LoggedPage, MyHTMLPage):
def is_inner(self, text):
for option in self.doc.xpath('//select[@id="donneesSaisie.idxCompteEmetteur"]/option'):
if text == CleanText('.')(option):
return True
return False
@method
class iter_recipients(ListElement):
def condition(self):
return any(self.env['account_id'] in CleanText('.')(option) for option in self.page.doc.xpath('//select[@id="donneesSaisie.idxCompteEmetteur"]/option'))
# You're not dreaming, this is real life
item_xpath = '//select[@id="caca"]/option'
class Item(ItemElement):
klass = Recipient
def condition(self):
return self.el.attrib['value'] != '-1'
def validate(self, obj):
# Some international external recipients show those infos:
# INT - CA - 0815304220511006 - CEGEP CANADA
# Skipping those for the moment.
return not obj.iban or is_iban_valid(obj.iban)
obj_category = Env('category')
obj_label = Env('label')
obj_id = Env('id')
obj_currency = u'EUR'
obj_iban = Env('iban')
obj_bank_name = Env('bank_name')
obj__value = Attr('.', 'value')
def obj_enabled_at(self):
return datetime.now().replace(microsecond=0)
def parse(self, el):
if any(s in CleanText('.')(el) for s in ['Avoir disponible', 'Solde']) or self.page.is_inner(CleanText('.')(el)):
self.env['category'] = u'Interne'
else:
self.env['category'] = u'Externe'
if self.env['category'] == u'Interne':
_id = Regexp(CleanText('.'), '- (.*?) -')(el)
if _id == self.env['account_id']:
raise SkipItem()
try:
account = find_object(self.page.browser.get_accounts_list(), id=_id, error=AccountNotFound)
self.env['id'] = _id
self.env['label'] = account.label
self.env['iban'] = account.iban
except AccountNotFound:
self.env['id'] = Regexp(CleanText('.'), '- (.*?) -')(el).replace(' ', '')
self.env['iban'] = NotAvailable
label = CleanText('.')(el).split('-')
holder = label[-1] if not any(string in label[-1] for string in ['Avoir disponible', 'Solde']) else label[-2]
self.env['label'] = '%s %s' % (label[0].strip(), holder.strip())
self.env['bank_name'] = u'La Banque Postale'
else:
self.env['id'] = self.env['iban'] = Regexp(CleanText('.'), '- (.*?) -')(el).replace(' ', '')
self.env['label'] = Regexp(CleanText('.'), '- (.*?) - (.*)', template='\\2')(el).strip()
first_part = CleanText('.')(el).split('-')[0].strip()
self.env['bank_name'] = u'La Banque Postale' if first_part in ['CCP', 'PEL'] else NotAvailable
if self.env['id'] in self.parent.objects: # user add two recipients with same iban...
raise SkipItem()
def init_transfer(self, account_id, recipient_value):
matched_values = [Attr('.', 'value')(option) for option in self.doc.xpath('//select[@id="donneesSaisie.idxCompteEmetteur"]/option') \
if account_id in CleanText('.')(option)]
assert len(matched_values) == 1
form = self.get_form(xpath='//form[@class="formvirement"]')
form['donneesSaisie.idxCompteReceveur'] = recipient_value
form['donneesSaisie.idxCompteEmetteur'] = matched_values[0]
form.submit()
class CompleteTransfer(LoggedPage, CheckTransferError):
def complete_transfer(self, amount, transfer):
form = self.get_form(xpath='//form[@method]')
form['montant'] = amount
if 'commentaire' in form and transfer.label:
form['commentaire'] = transfer.label
form['dateVirement'] = transfer.exec_date.strftime('%d/%m/%Y')
form.submit()
class TransferConfirm(LoggedPage, CheckTransferError):
def is_here(self):
return not CleanText('//p[contains(text(), "Vous pouvez le consulter dans le menu")]')(self.doc)
def double_auth(self, transfer):
code_needed = CleanText('//label[@for="code_securite"]')(self.doc)
if code_needed:
raise TransferStep(transfer, Value('code', label= code_needed))
def confirm(self):
form = self.get_form(id='formID')
form.submit()
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')
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)
transfer = Transfer()
transfer.currency = currency
transfer.amount = r_amount
transfer.account_iban = account.iban
transfer.recipient_iban = recipient.iban
transfer.account_id = account.id
transfer.recipient_id = recipient.id
transfer.exec_date = exec_date
transfer.label = reason
transfer.account_label = account.label
transfer.recipient_label = recipient.label
transfer.account_balance = account.balance
return transfer
class TransferSummary(LoggedPage, CheckTransferError):
def handle_response(self, transfer):
# NotAvailable in case of future exec_date not on a working day.
transfer.id = Regexp(CleanText('//div[@class="bloc Tmargin"]'), 'virement N.+ (\d+) ', default=NotAvailable)(self.doc)
if not transfer.id:
# TODO handle transfer with sms code.
if u'veuillez saisir votre code de validation' in CleanText('//div[@class="bloc Tmargin"]')(self.doc):
raise NotImplementedError()
# there are several regexp for transfer date:
# Date ([\d\/]+)|le ([\d\/]+)|suivant \(([\d\/]+)\)
# be more passive to avoid impulsive reaction from user
transfer.exec_date = Date(Regexp(
CleanText('//div[@class="bloc Tmargin"]'),
r' (\d{2}/\d{2}/\d{4})'
), dayfirst=True)(self.doc)
return transfer
class CreateRecipient(LoggedPage, MyHTMLPage):
def on_load(self):
if self.doc.xpath(u'//h1[contains(text(), "Service Désactivé")]'):
raise BrowserUnavailable(CleanText('//p[img[@title="attention"]]/text()')(self.doc))
def choose_country(self, recipient, is_bp_account):
# if this is present, we can't add recipient currently
more_security_needed = self.doc.xpath(u'//iframe[@title="Gestion de compte par Internet"]')
if more_security_needed:
raise AddRecipientBankError(message=u"Pour activer le service Certicode, nous vous invitons à vous rapprocher de votre Conseiller en Bureau de Poste.")
form = self.get_form(name='SaisiePaysBeneficiaireVirement')
form['compteLBP'] = str(is_bp_account).lower()
form['beneficiaireBean.paysDestination'] = recipient.iban[:2]
form.submit()
class ValidateCountry(LoggedPage, MyHTMLPage):
def populate(self, recipient):
form = self.get_form(name='CaracteristiquesBeneficiaireVirement')
form['beneficiaireBean.nom'] = recipient.label
form['beneficiaireBean.ibans[1].valeur'] = recipient.iban[2:4]
form['beneficiaireBean.ibans[2].valeur'] = recipient.iban[4:8]
form['beneficiaireBean.ibans[3].valeur'] = recipient.iban[8:12]
form['beneficiaireBean.ibans[4].valeur'] = recipient.iban[12:16]
form['beneficiaireBean.ibans[5].valeur'] = recipient.iban[16:20]
form['beneficiaireBean.ibans[6].valeur'] = recipient.iban[20:24]
form['beneficiaireBean.ibans[7].valeur'] = recipient.iban[24:]
form['beneficiaireBean.intituleCompte'] = recipient.label
form.submit()
class ValidateRecipient(LoggedPage, MyHTMLPage):
def is_bp_account(self):
msg = CleanText('//span[has-class("app_erreur")]')(self.doc)
return u'Le n° de compte que vous avez saisi appartient à La Banque Postale, veuillez vérifier votre saisie.' in msg
def get_confirm_link(self):
return Link('//a[@title="confirmer la creation"]')(self.doc)
class ConfirmPage(LoggedPage, MyHTMLPage):
def on_load(self):
error_msg = CleanText('//h2[contains(text(), "Compte rendu")]/following-sibling::p')(self.doc)
if error_msg:
raise AddRecipientBankError(message=error_msg)
def set_browser_form(self):
form = self.get_form(name='SaisieOTP')
self.browser.recipient_form = dict((k, v) for k, v in form.items() if v)
self.browser.recipient_form['url'] = form.url
class RcptSummary(LoggedPage, MyHTMLPage):
pass