Newer
Older
# -*- coding: utf-8 -*-
# Copyright(C) 2013 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
import base64
from io import BytesIO
from PIL import Image
from weboob.browser.pages import HTMLPage, LoggedPage, pagination
from weboob.browser.elements import ListElement, TableElement, ItemElement, method
from weboob.browser.filters.standard import (
Regexp, Field, CleanText, CleanDecimal, Eval, Currency
from weboob.browser.filters.html import Link, TableCell, Attr, AttributeNotFound
from weboob.capabilities.bank import Account, Investment
from weboob.capabilities.base import NotAvailable
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class CarrefourBanqueKeyboard(object):
symbols = {
'0': '00111001111110111011011001111100011110001111000111100111110011111111100111100',
'1': '00011000011100011110011011000001100000110000011000001100000110000011000001100',
'2': '00111001111110110011100001110000110000111000111000011000011000011111111111111',
'3': '01111001111110100011100001110001110011110000111100000111000011111111111111110',
'4': '00001100001110001111000111100110110110011011001101111111111111100001100000110',
'5': '01111101111110111000011000001111100111111000001110000111000011111111101111100',
'6': '00011100111110111000011000001111110111111111001111100011110001111111110111110',
'7': '11111111111111000011100001100000110000110000011000011100001100001110000110000',
'8': '00111001111110110011111001111111110011110011111101100111110001111111110111110',
'9': '00110001111110110011111000111100011111111111111110000011000011011111101111100'
}
def __init__(self, data_code):
self.fingerprints = {}
for code, data in data_code.items():
img = Image.open(BytesIO(data))
img = img.convert('RGB')
matrix = img.load()
s = ""
# The digit is only displayed in the center of image
for y in range(11, 22):
for x in range(14, 21):
(r, g, b) = matrix[x, y]
# If the pixel is "white" enough
if r + g + b > 600:
s += "1"
else:
s += "0"
self.fingerprints[code] = s
def get_symbol_code(self, digit):
fingerprint = self.symbols[digit]
for code, string in self.fingerprints.items():
if string == fingerprint:
return code
# Image contains some noise, and the match is not always perfect
# (this is why we can't use md5 hashs)
# But if we can't find the perfect one, we can take the best one
best = 0
result = None
for code, string in self.fingerprints.items():
match = 0
for j, bit in enumerate(string):
if bit == fingerprint[j]:
match += 1
if match > best:
best = match
result = code
return result
def get_string_code(self, string):
code = ''
for c in string:
code += self.get_symbol_code(c) + '-'
return code
def MyDecimal(*args, **kwargs):
kwargs.update(replace_dots=True, default=NotAvailable)
return CleanDecimal(*args, **kwargs)
class LoginPage(HTMLPage):
"""
website may have identify us as a robot, if it happens login form won't be available in login page
and there will be nothing on body except a meta tag with robot name
"""
try:
attr = Attr('head/meta', 'name')(self.doc)
except AttributeNotFound:
# website have identify us as a human ;)
return
# sometimes robots is uppercase and there is an iframe
# sometimes it's lowercase and there is a script
if attr == 'ROBOTS':
self.browser.location(Attr('//iframe', 'src')(self.doc))
elif attr == 'robots':
self.browser.location(Attr('//script', 'src')(self.doc))
def enter_login(self, username):
form = self.get_form(nr=1)
form['name'] = username
form.submit()
def get_message_if_old_login(self):
return CleanText('//div[@class="messages error"]', children=False)(self.doc)
def enter_password(self, password):
data_code = {}
for img in self.doc.xpath('//img[@class="digit"]'):
data_code[img.attrib['data-code']] = base64.b64decode(re.search(r'base64,(.*)', img.attrib['src']).group(1))
codestring = CarrefourBanqueKeyboard(data_code).get_string_code(password)
form = self.get_form(nr=1)
form['pass'] = '*' * len(password)
form['cpass'] = codestring
form.pop('form_number') # don't remember me
form.submit()
class MaintenancePage(HTMLPage):
def get_message(self):
return CleanText('//div[@class="bloc-title"]/h1//div[has-class("field-item")]')(self.doc)
class IncapsulaResourcePage(HTMLPage):
def __init__(self, *args, **kwargs):
# this page can be a html page, or just javascript
super(IncapsulaResourcePage, self).__init__(*args, **kwargs)
self.is_javascript = None
def on_load(self):
self.is_javascript = 'html' not in CleanText('*')(self.doc)
def get_recaptcha_site_key(self):
return Attr('//div[@class="g-recaptcha"]', 'data-sitekey')(self.doc)
class Transaction(FrenchTransaction):
PATTERNS = [(re.compile(r'^(?P<text>.*?) (?P<dd>\d{2})/(?P<mm>\d{2})$'), FrenchTransaction.TYPE_CARD)]
class item_account_generic(ItemElement):
"""Generic accounts properties for Carrefour homepage"""
klass = Account
def obj_balance(self):
balance = CleanDecimal('.//div[contains(@class, "right_col")]//h2[1]', replace_dots=True)(self)
return (-balance if Field('type')(self) in (Account.TYPE_LOAN,) else balance)
obj_currency = Currency('.//div[contains(@class, "right_col")]//h2[1]')
obj_label = CleanText('.//div[contains(@class, "leftcol")]//h2[1]')
obj_id = Regexp(CleanText('.//div[contains(@class, "leftcol")]//p'), ":\s+([\d]+)")
obj_number = Field('id')
def obj_url(self):
acc_number = Field('id')(self)
xpath_link = '//li[contains(., "{acc_number}")]/ul/li/a'.format(acc_number=acc_number)
return Link(xpath_link)(self)
class iter_history_generic(Transaction.TransactionsElement):
head_xpath = u'//div[*[contains(text(), "opérations")]]/table//thead/tr/th'
item_xpath = u'//div[*[contains(text(), "opérations")]]/table/tbody/tr[td]'
def next_page(self):
next_page = Link(u'//a[contains(text(), "précédentes")]', default=None)(self)
if next_page:
return "/%s" % next_page
class item(Transaction.TransactionElement):
def obj_type(self):
if len(self.el.xpath('./td')) <= 3:
return Transaction.TYPE_BANK
debittype = CleanText(TableCell('debittype'))(self)
if debittype == 'Différé':
return Transaction.TYPE_DEFERRED_CARD
return Transaction.TYPE_CARD
def condition(self):
return TableCell('raw')(self)
class HomePage(LoggedPage, HTMLPage):
@method
class iter_loan_accounts(ListElement): # Prêts
item_xpath = '//div[@class="pp_espace_client"]'
class item(item_account_generic):
obj_type = Account.TYPE_LOAN
@method
class iter_card_accounts(ListElement): # PASS cards
item_xpath = '//div/div[contains(./h2, "Carte et Crédit") and contains(./p, "Numéro de compte")]/..'
class item(item_account_generic):
obj_type = Account.TYPE_CARD
available = CleanDecimal('.//p[contains(., "encours depuis le")]//preceding-sibling::h2', default=None, replace_dots=True)(self)
return NotAvailable if not available else -available
@method
class iter_saving_accounts(ListElement): # livrets
item_xpath = (
'//div[div[(contains(./h2, "Livret Carrefour") or contains(./h2, "Epargne")) and contains(./p, "Numéro de compte")]]'
)
class item(item_account_generic):
obj_type = Account.TYPE_SAVINGS
obj_url = Link('.//a[contains(., "Historique des opérations")]')
def obj_balance(self):
val = CleanDecimal('.//a[contains(text(), "versement")]//preceding-sibling::h2', replace_dots=True, default=NotAvailable)(self)
if val is not NotAvailable:
return val
val = CleanDecimal(Regexp(CleanText('./div[@class="right_col_wrapper"]//h2'), r'([\d ,]+€)'), replace_dots=True)(self)
return val
@method
class iter_life_accounts(ListElement): # Assurances vie
item_xpath = '//div/div[(contains(./h2, "Carrefour Horizons") or contains(./h2, "Carrefour Avenir")) and contains(./p, "Numéro de compte")]/..'
class item(item_account_generic):
obj_type = Account.TYPE_LIFE_INSURANCE
def obj_url(self):
acc_number = Field('id')(self)
xpath_link = '//li[contains(., "{acc_number}")]/ul/li/a[contains(text(), "Dernieres opérations")]'.format(acc_number=acc_number)
return Link(xpath_link)(self)
def obj__life_investments(self):
xpath_link = '//li[contains(., "{acc_number}")]/ul/li/a[contains(text(), "Solde")]'.format(acc_number=Field('id')(self))
return Link(xpath_link)(self)
class TransactionsPage(LoggedPage, HTMLPage):
@pagination
@method
class iter_history(iter_history_generic):
pass
class SavingHistoryPage(LoggedPage, HTMLPage):
@pagination
@method
class iter_history(iter_history_generic):
head_xpath = '//table[@id="creditHistory" or @id="TransactionHistory"]/thead/tr/th'
item_xpath = '//table[@id="creditHistory" or @id="TransactionHistory"]/tbody/tr'
class LifeInvestmentsPage(LoggedPage, HTMLPage):
@method
class get_investment(TableElement):
item_xpath = '//table[@id="assets"]/tbody/tr[position() > 1]'
head_xpath = '//table[@id="assets"]/tbody/tr[1]/td'
col_label = u'Fonds'
col_quantity = u'Nombre de parts'
col_unitvalue = u'Valeur part'
col_valuation = u'Total'
col_portfolio_share = u'Répartition'
class item(ItemElement):
klass = Investment
obj_label = CleanText(TableCell('label'))
obj_quantity = MyDecimal(TableCell('quantity'))
obj_unitvalue = MyDecimal(TableCell('unitvalue'))
obj_valuation = MyDecimal(TableCell('valuation'))
obj_portfolio_share = Eval(lambda x: x / 100, MyDecimal(TableCell('portfolio_share')))
class LifeHistoryPage(TransactionsPage):
pass
class LoanHistoryPage(TransactionsPage):
pass
class CardHistoryPage(TransactionsPage):
pass