pax_global_header 0000666 0000000 0000000 00000000064 13435444765 0014531 g ustar 00root root 0000000 0000000 52 comment=139760e841ad4875b59fe11e052af1bb56dc06ea
woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/ 0000775 0000000 0000000 00000000000 13435444765 0024324 5 ustar 00root root 0000000 0000000 woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/modules/ 0000775 0000000 0000000 00000000000 13435444765 0025774 5 ustar 00root root 0000000 0000000 woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/modules/carrefourbanque/ 0000775 0000000 0000000 00000000000 13435444765 0031160 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000001527 13435444765 0033217 0 ustar 00root root 0000000 0000000 woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/modules/carrefourbanque # -*- 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 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 .module import CarrefourBanqueModule
__all__ = ['CarrefourBanqueModule']
browser.py 0000664 0000000 0000000 00000012502 13435444765 0033136 0 ustar 00root root 0000000 0000000 woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/modules/carrefourbanque # -*- 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 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 time import sleep
from weboob.browser import LoginBrowser, URL, need_login, StatesMixin
from weboob.exceptions import BrowserIncorrectPassword, NocaptchaQuestion, BrowserUnavailable
from weboob.capabilities.bank import Account
from weboob.tools.compat import basestring
from .pages import (
LoginPage, MaintenancePage, HomePage, IncapsulaResourcePage, LoanHistoryPage, CardHistoryPage, SavingHistoryPage,
LifeInvestmentsPage, LifeHistoryPage
)
__all__ = ['CarrefourBanqueBrowser']
class CarrefourBanqueBrowser(LoginBrowser, StatesMixin):
BASEURL = 'https://www.carrefour-banque.fr'
login = URL('/espace-client/connexion', LoginPage)
maintenance = URL('/maintenance', MaintenancePage)
incapsula_ressource = URL('/_Incapsula_Resource', IncapsulaResourcePage)
home = URL('/espace-client$', HomePage)
loan_history = URL(r'/espace-client/pret-personnel/situation\?(.*)', LoanHistoryPage)
saving_history = URL(
r'/espace-client/compte-livret/solde-dernieres-operations\?(.*)',
r'/espace-client/epargne-pass/historique-des-operations\?(.*)',
r'/espace-client/epargne-libre/historique-des-operations\?(.*)',
SavingHistoryPage
)
card_history = URL(r'/espace-client/carte-credit/solde-dernieres-operations\?(.*)', CardHistoryPage)
life_history = URL(r'/espace-client/assurance-vie/historique-des-operations\?(.*)', LifeHistoryPage)
life_investments = URL(r'/espace-client/assurance-vie/solde-dernieres-operations\?(.*)', LifeInvestmentsPage)
def __init__(self, config, *args, **kwargs):
self.config = config
kwargs['username'] = self.config['login'].get()
kwargs['password'] = self.config['password'].get()
super(CarrefourBanqueBrowser, self).__init__(*args, **kwargs)
def locate_browser(self, state):
pass
def do_login(self):
"""
Attempt to log in.
Note: this method does nothing if we are already logged in.
"""
assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring)
if self.config['captcha_response'].get():
data = {'g-recaptcha-response': self.config['captcha_response'].get()}
self.incapsula_ressource.go(params={'SWCGHOEL': 'v2'}, data=data)
self.login.go()
if self.incapsula_ressource.is_here():
if self.page.is_javascript:
# wait several seconds and we'll get a recaptcha instead of obfuscated javascript code,
# (which is simpler to resolve)
sleep(5)
self.login.go()
if not self.page.is_javascript:
# cookie session is not available
website_key = self.page.get_recaptcha_site_key()
website_url = self.login.build()
raise NocaptchaQuestion(website_key=website_key, website_url=website_url)
else:
# we got javascript page again, this shouldn't happen
assert False, "obfuscated javascript not managed"
if self.maintenance.is_here():
raise BrowserUnavailable(self.page.get_message())
self.page.enter_login(self.username)
self.page.enter_password(self.password)
if not self.home.is_here():
raise BrowserIncorrectPassword()
@need_login
def get_account_list(self):
self.home.stay_or_go()
cards = list(self.page.iter_card_accounts())
life_insurances = list(self.page.iter_life_accounts())
savings = list(self.page.iter_saving_accounts())
loans = list(self.page.iter_loan_accounts())
return cards + life_insurances + savings + loans
@need_login
def iter_investment(self, account):
if account.type != Account.TYPE_LIFE_INSURANCE:
raise NotImplementedError()
self.home.stay_or_go()
self.location(account._life_investments)
assert self.life_investments.is_here()
return self.page.get_investment(account)
@need_login
def iter_history(self, account):
self.home.stay_or_go()
self.location(account.url)
if account.type == Account.TYPE_SAVINGS:
assert self.saving_history.is_here()
elif account.type == Account.TYPE_CARD:
assert self.card_history.is_here()
elif account.type == Account.TYPE_LOAN:
assert self.loan_history.is_here()
elif account.type == Account.TYPE_LIFE_INSURANCE:
assert self.life_history.is_here()
else:
raise NotImplementedError()
return self.page.iter_history(account)
favicon.png 0000664 0000000 0000000 00000002651 13435444765 0033240 0 ustar 00root root 0000000 0000000 woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/modules/carrefourbanque PNG
IHDR @ @ iq bKGD C pHYs
B(x tIME
5 6IDATx[lTE"(.!(6Rm"x!H!<`4Fd_LLH4Ƈ1QH1FQ/Xh@Qk4"Y4p9ggΙٙohYB恫ˁ߀qKC !w@u/^xʞgq q.5aq/@|80a8:i/2yOFȕY E5U3EXrȦHE`%pM_lHY%tpC_OnCgyfKS%Ou&@4yYc] ,M)`6':ajPy f31jɭ\!_ŷ"Bu:-!M,=Xr@+S'B.`kYp ꙥ<@GݚΫmUb _lUhorh2$OZYW?+qܭ1Ϩ\~a#)b !$^xxCB^@:`B~'C:=SG"#plFA` YGd-x ,w$iЂl
Cu y'G暟a0l gJK>/Eh+<.Up!5W$׀k( p[@6I
"bV9"Vw/ 'Wm
:\W4ŭ@kBi:+ј#:B"v6[5pO>VN:WVk8ZiQ\ǝ
{70k4*8͘:zK#98:K۔i2
̳$"J5??" :'
S:!,/gBSI3g9W+1:[Uq/=]P Vp\/e#gjPm{2Ӎع?dg ^Ņ{DX
,o]m^*w |xv_ƹ)$>g" mWh1Q:m؞|Tu6?(|RDI., 'u `JC
|x|:mQdlуjHBz& '&jLNr/1dK`r0mv4
y{ߐ3o]Z#oWx"RYeMc@mxx4)N; *P'yjk=u5t_BS窾4Xwmdv'@\'T|0JIt\Pe J A"Ԉ|
4pE0 IENDB` module.py 0000664 0000000 0000000 00000004147 13435444765 0032746 0 ustar 00root root 0000000 0000000 woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/modules/carrefourbanque # -*- 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 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.capabilities.base import find_object
from weboob.capabilities.bank import CapBankWealth, AccountNotFound
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword, Value
from .browser import CarrefourBanqueBrowser
__all__ = ['CarrefourBanqueModule']
class CarrefourBanqueModule(Module, CapBankWealth):
NAME = 'carrefourbanque'
MAINTAINER = u'Romain Bignon'
EMAIL = 'romain@weboob.org'
VERSION = '1.5'
DESCRIPTION = u'Carrefour Banque'
LICENSE = 'AGPLv3+'
CONFIG = BackendConfig(ValueBackendPassword('login', label=u'Votre Identifiant Internet', masked=False),
ValueBackendPassword('password', label=u"Code d'accès", regexp=u'\d+'),
Value('captcha_response', label='Captcha Response', default='', required=False))
BROWSER = CarrefourBanqueBrowser
def create_default_browser(self):
return self.create_browser(self.config)
def iter_accounts(self):
return self.browser.get_account_list()
def get_account(self, _id):
return find_object(self.browser.get_account_list(), id=_id, error=AccountNotFound)
def iter_history(self, account):
return self.browser.iter_history(account)
def iter_investment(self, account):
return self.browser.iter_investment(account)
pages.py 0000664 0000000 0000000 00000026730 13435444765 0032562 0 ustar 00root root 0000000 0000000 woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/modules/carrefourbanque # -*- 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 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
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
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):
def on_load(self):
"""
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 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.*?) (?P\d{2})/(?P\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]'
col_debittype = 'Mode'
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
def obj_balance(self):
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
test.py 0000664 0000000 0000000 00000002052 13435444765 0032431 0 ustar 00root root 0000000 0000000 woob-139760e841ad4875b59fe11e052af1bb56dc06ea-modules-carrefourbanque/modules/carrefourbanque # -*- 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 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.tools.test import BackendTest
class CarrefourBanqueTest(BackendTest):
MODULE = 'carrefourbanque'
def test_carrefourbanque(self):
l = list(self.backend.iter_accounts())
if len(l) > 0:
a = l[0]
list(self.backend.iter_history(a))