'ls' and 'history' boobank commands implemented for the new backend fortuneo

This commit is contained in:
sputnick 2012-04-20 23:58:18 +02:00 committed by Romain Bignon
commit d9c8e9ccdb
6 changed files with 155 additions and 267 deletions

View file

@ -21,3 +21,5 @@
from .backend import FortuneoBackend from .backend import FortuneoBackend
__all__ = ['FortuneoBackend'] __all__ = ['FortuneoBackend']
# vim:ts=4:sw=4

View file

@ -20,7 +20,6 @@
# python2.5 compatibility # python2.5 compatibility
from __future__ import with_statement from __future__ import with_statement
from weboob.capabilities.bank import ICapBank, AccountNotFound from weboob.capabilities.bank import ICapBank, AccountNotFound
from weboob.tools.backend import BaseBackend, BackendConfig from weboob.tools.backend import BaseBackend, BackendConfig
from weboob.tools.value import ValueBackendPassword from weboob.tools.value import ValueBackendPassword
@ -38,40 +37,51 @@ class FortuneoBackend(BaseBackend, ICapBank):
VERSION = '0.c' VERSION = '0.c'
LICENSE = 'AGPLv3+' LICENSE = 'AGPLv3+'
DESCRIPTION = u'Fortuneo French bank website' DESCRIPTION = u'Fortuneo French bank website'
CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False, required=True), CONFIG = BackendConfig(
ValueBackendPassword('password', label='Password', required=True)) ValueBackendPassword(
'login',
label='Account ID',
masked=False,
required=True
),
ValueBackendPassword(
'password',
label='Password',
required=True
)
)
BROWSER = Fortuneo BROWSER = Fortuneo
def create_default_browser(self): def create_default_browser(self):
return self.create_browser(self.config['login'].get(), return self.create_browser(
self.config['password'].get()) self.config['login'].get(),
self.config['password'].get()
)
def iter_accounts(self): def iter_accounts(self):
"""Iter accounts"""
for account in self.browser.get_accounts_list(): for account in self.browser.get_accounts_list():
yield account yield account
def get_account(self, _id): def get_account(self, _id):
# _id = "fortuneo"
#print "DEBUG\n\n\n", _id, "DEBUG\n\n\n"
if not _id.isdigit():
raise AccountNotFound()
with self.browser: with self.browser:
account = self.browser.get_account(_id) account = self.browser.get_account(_id)
print "DEBUG 2\n\n\n", account, "\n\n\nEND DEBUG 2"
if account: if account:
return account return account
else: else:
raise AccountNotFound() raise AccountNotFound()
def iter_history(self, account):
pass
#with self.browser:
# for tr in self.browser.iter_history(account._link_id):
# if not tr._coming:
# yield tr
def iter_coming(self, account): def iter_coming(self, account):
"""Iter coming transactions on a specific account Not supported yet"""
return iter([])
def iter_history(self, account):
"""Iter history of transactions on a specific account"""
with self.browser: with self.browser:
for tr in self.browser.iter_history(account._link_id): for history in self.browser.get_history(account):
if tr._coming: yield history
yield tr
# vim:ts=4:sw=4

View file

@ -19,81 +19,79 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>. # along with weboob. If not, see <http://www.gnu.org/licenses/>.
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword from weboob.tools.browser import BaseBrowser #, BrowserIncorrectPassword
from .pages.accounts_list import AccountHistory #AccountsList, IndexPage
from .pages.login import LoginPage #, BadLoginPage
from .pages.login import LoginPage
from .pages.accounts_list import AccountsList, AccountHistoryPage
__all__ = ['Fortuneo'] __all__ = ['Fortuneo']
# https://www.fortuneo.fr/fr/prive/mes-comptes/livret/consulter-situation/consulter-solde.jsp?COMPTE_ACTIF=FT00991337
class Fortuneo(BaseBrowser): class Fortuneo(BaseBrowser):
DOMAIN_LOGIN = 'www.fortuneo.fr' DOMAIN_LOGIN = 'www.fortuneo.fr'
DOMAIN = 'www.fortuneo.fr' DOMAIN = 'www.fortuneo.fr'
PROTOCOL = 'https' PROTOCOL = 'https'
ENCODING = None # refer to the HTML encoding ENCODING = None # refer to the HTML encoding
PAGES = { PAGES = {
'.*identification.jsp.*': LoginPage, '.*identification.jsp.*':
#'.*/prive/default.jsp.*': IndexPage, LoginPage,
#'.*/prive/default.jsp.*': AccountsList, '.*/prive/mes-comptes/synthese-tous-comptes\.jsp.*':
'.*/prive/default.jsp.*': AccountHistory, AccountsList,
#'https://www.fortuneo.fr/fr/identification.jsp': BadLoginPage, '.*/prive/mes-comptes/livret/consulter-situation/consulter-solde\.jsp\?COMPTE_ACTIF=.*':
#'.*/prive/mes-comptes/livret/consulter-situation/consulter-solde\.jsp.*': AccountsList, AccountHistoryPage
#'.*/prive/mes-comptes/livret/consulter-situation/consulter-solde\.jsp.*': AccountHistory,
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
BaseBrowser.__init__(self, *args, **kwargs) BaseBrowser.__init__(self, *args, **kwargs)
def home(self): def home(self):
"""main page (login)"""
if not self.is_on_page(AccountHistoryPage):
self.location('/fr/prive/identification.jsp') self.location('/fr/prive/identification.jsp')
#self.location('https://' + self.DOMAIN_LOGIN + '/fr/identification.jsp')
#self.location('https://' + self.DOMAIN_LOGIN + '/fr/prive/mes-comptes/synthese-tous-comptes.jsp')
def is_logged(self): def is_logged(self):
return not self.is_on_page(LoginPage) """Return True if we are logged on website"""
if self.is_on_page(AccountHistoryPage) or self.is_on_page(AccountsList):
return True
else:
return False
def login(self): def login(self):
"""Login to the website.
This function is called when is_logged() returns False and the
password attribute is not None."""
assert isinstance(self.username, basestring) assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring) assert isinstance(self.password, basestring)
#assert self.password.isdigit()
if not self.is_on_page(LoginPage): if not self.is_on_page(LoginPage):
self.location('https://' + self.DOMAIN_LOGIN + '/fr/identification.jsp') self.location('https://' + self.DOMAIN_LOGIN + '/fr/identification.jsp')
self.page.login(self.username, self.password) self.page.login(self.username, self.password)
self.location('/fr/prive/mes-comptes/synthese-tous-comptes.jsp')
#if self.is_on_page(LoginPage) or \ def get_history(self, account):
# self.is_on_page(BadLoginPage): if not self.is_on_page(AccountHistoryPage):
# raise BrowserIncorrectPassword() self.location(account._link_id)
return self.page.get_operations(account)
def get_accounts_list(self): def get_accounts_list(self):
"""accounts list"""
if not self.is_on_page(AccountsList): if not self.is_on_page(AccountsList):
self.location('/fr/prive/default.jsp?ANav=1') self.location('/fr/prive/mes-comptes/synthese-tous-comptes.jsp')
#self.location('')
return self.page.get_list() return self.page.get_list()
def get_account(self, id): def get_account(self, id):
"""Get an account from its ID"""
assert isinstance(id, basestring) assert isinstance(id, basestring)
l = self.get_accounts_list()
if not self.is_on_page(AccountsList): for a in l:
self.location('/fr/prive/default.jsp?ANav=1') if a.id == id:
return a
print "\n\n\n", self.page, "\n\n\n"
#l = self.page.get_list()
#for a in l:
# if a.id == id:
# return a
return None return None
def iter_history(self, url): # vim:ts=4:sw=4
self.location(url)
if not self.is_on_page(AccountHistory):
# TODO: support other kind of accounts
return iter([])
return self.page.iter_transactions()

View file

@ -17,146 +17,88 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>. # along with weboob. If not, see <http://www.gnu.org/licenses/>.
# AccountsList, IndexPage
from urlparse import parse_qs, urlparse
from lxml.etree import XML
from cStringIO import StringIO
from decimal import Decimal from decimal import Decimal
import re import re
import datetime
from weboob.capabilities.bank import Account from weboob.capabilities.bank import Account
from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.capabilities.bank.transactions import Transaction
from weboob.tools.browser import BasePage, BrokenPageError from weboob.tools.browser import BasePage #, BrokenPageError
__all__ = ['AccountsList', 'AccountHistoryPage']
#__all__ = ['IndexPage', 'AccountsList', 'AccountHistory'] class AccountHistoryPage(BasePage):
__all__ = ['AccountHistory'] def get_operations(self, _id):
"""history, see http://docs.weboob.org/api/capabilities/bank.html?highlight=transaction#weboob.capabilities.bank.Transaction"""
operations = []
tables = self.document.findall(".//*[@id='tabHistoriqueOperations']/tbody/tr")
#class IndexPage(BasePage): for i in range(len(tables)):
# def on_loaded(self): operation = Transaction(len(operations))
# pass
#
# def get_list(self):
# l = []
# account.label = "test"
# account.id = "Livret +"
# account.balance = "20"
# account._link_id = "https://www.fortuneo.fr/fr/prive/default.jsp?ANav=1"
# l.append(account)
# return l
#class AccountsList(BasePage): date_oper = tables[i].xpath("./td[2]/text()")[0]
# def on_loaded(self): date_val = tables[i].xpath("./td[3]/text()")[0]
# pass label = tables[i].xpath("./td[4]/text()")[0]
# label = label.strip()
# def get_list(self): amount = tables[i].xpath("./td[5]/text() | ./td[6]/text()")
# #print "DEBUG self.document="+self.document operation.date = datetime.datetime.strptime(date_val, "%d/%m/%Y")
# account = [] operation.rdate = datetime.datetime.strptime(date_oper,"%d/%m/%Y")
# account.append('test') operation.type = 0
# account.append('Livret +') operation.raw = label
# account.append('20') operation.label = u""+label
# account.append('https://www.fortuneo.fr/fr/prive/default.jsp?ANav=1')
# return account
# #account.append(account)
# #for el in self.document.xpath('//table[@id="tableauComptesTitEtCotit"]/tbody/'):
# #l.append(account)
# ##for tr in self.document.getiterator('tr'):
# ## if 'LGNTableRow' in tr.attrib.get('class', '').split():
# ## account = Account()
# ## for td in tr.getiterator('td'):
# ## if td.attrib.get('headers', '') == 'TypeCompte':
# ## a = td.find('a')
# ## account.label = unicode(a.find("span").text)
# ## account._link_id = a.get('href', '')
#
# ## elif td.attrib.get('headers', '') == 'NumeroCompte':
# ## id = td.text
# ## id = id.replace(u'\xa0','')
# ## account.id = id
#
# ## elif td.attrib.get('headers', '') == 'Libelle':
# ## pass
#
# ## elif td.attrib.get('headers', '') == 'Solde':
# ## balance = td.find('div').text
# ## if balance != None:
# ## balance = balance.replace(u'\xa0','').replace(',','.')
# ## account.balance = Decimal(balance)
# ## else:
# ## account.balance = Decimal(0)
#
# ## l.append(account)
#
# #return l
#class Transaction(FrenchTransaction): if amount[1] == u'\xa0':
# pass amount = amount[0].replace(u"\xa0", "").replace(",", ".").strip()
#PATTERNS = [(re.compile(r'^CARTE \w+ RETRAIT DAB.* (?P<dd>\d{2})/(?P<mm>\d{2}) (?P<HH>\d+)H(?P<MM>\d+) (?P<text>.*)'), else:
# FrenchTransaction.TYPE_WITHDRAWAL), amount = amount[1].replace(u"\xa0", "").replace(",", ".").strip()
# (re.compile(r'^(?P<category>CARTE) \w+ (?P<dd>\d{2})/(?P<mm>\d{2}) (?P<text>.*)'), operation.amount = Decimal(amount)
# FrenchTransaction.TYPE_CARD),
# (re.compile(r'^(?P<category>(COTISATION|PRELEVEMENT|TELEREGLEMENT|TIP)) (?P<text>.*)'),
# FrenchTransaction.TYPE_ORDER),
# (re.compile(r'^(?P<category>VIR(EMEN)?T? \w+) (?P<text>.*)'),
# FrenchTransaction.TYPE_TRANSFER),
# (re.compile(r'^(CHEQUE) (?P<text>.*)'), FrenchTransaction.TYPE_CHECK),
# (re.compile(r'^(FRAIS) (?P<text>.*)'), FrenchTransaction.TYPE_BANK),
# (re.compile(r'^(?P<category>ECHEANCEPRET)(?P<text>.*)'),
# FrenchTransaction.TYPE_LOAN_PAYMENT),
# (re.compile(r'^(?P<category>REMISE CHEQUES)(?P<text>.*)'),
# FrenchTransaction.TYPE_DEPOSIT),
# ]
class AccountHistory(BasePage): operation.category = u" " # blank category
get_list = [1, 2, 3, 4] operation.origin = u" " # blank origin
def get_part_url(self): operation.recipient = u" " # blank rcpt
print "DEBUG AccountHistory.get_part_url a implementer"
pass
#for script in self.document.getiterator('script'):
# if script.text is None:
# continue
# m = re.search('var listeEcrCavXmlUrl="(.*)";', script.text) operations.append(operation)
# if m:
# return m.group(1)
#raise BrokenPageError('Unable to find link to history part') return operations
#def iter_transactions(self): class AccountsList(BasePage):
# print "DEBUG iter_transactions a implementer" def get_list(self):
# pass l = []
# #url = self.get_part_url()
# #while 1:
# # d = XML(self.browser.readurl(url))
# # el = d.xpath('//dataBody')[0]
# # s = StringIO(el.text)
# # doc = self.browser.get_document(s)
# # for tr in self._iter_transactions(doc): for cpt in self.document.xpath(".//*[@id='tableauComptesTitEtCotit']/tbody/tr"):
# # yield tr account = Account()
# # el = d.xpath('//dataHeader')[0] # account.id
# # if int(el.find('suite').text) != 1: account.id = cpt.xpath("./td[1]/a/text()")[0]
# # return account.id = str(account.id)
# # url = urlparse(url) # account balance
# # p = parse_qs(url.query) account.balance = Decimal(cpt.xpath("./td[3]/text()")[0].replace("EUR", "").replace("\n", "").replace("\t", "").replace(u"\xa0", ""))
# # url = self.browser.buildurl(url.path, n10_nrowcolor=0,
# # operationNumberPG=el.find('operationNumber').text,
# # operationTypePG=el.find('operationType').text,
# # pageNumberPG=el.find('pageNumber').text,
# # sign=p['sign'][0],
# # src=p['src'][0])
# account coming
mycomingval = cpt.xpath("./td[4]/text()")[0].replace("EUR", "").replace("\n", "").replace("\t", "")
def _iter_transactions(self, doc): if mycomingval == '-':
print "DEBUG _iter_transactions a implementer" account.coming = float(0)
pass else:
#for i, tr in enumerate(self.parser.select(doc.getroot(), 'tr')): account.coming = float(mycomingval)
# t = Transaction(i)
# t.parse(date=tr.xpath('./td[@headers="Date"]')[0].text, # account._link_id
# raw=tr.attrib['title'].strip()) url_to_parse = cpt.xpath('./td[1]/a/@href')[0] # link
# t.set_amount(*reversed([el.text for el in tr.xpath('./td[@class="right"]')])) compte_id_re = re.compile(r'.*COMPTE_ACTIF=([^\&]+)\&.*')
# t._coming = tr.xpath('./td[@headers="AVenir"]')[0].text account._link_id = '/fr/prive/mes-comptes/livret/consulter-situation/consulter-solde.jsp?COMPTE_ACTIF='+compte_id_re.search(str(url_to_parse)).groups()[0]
# yield t account._link_id = str(account._link_id)
# account.label
tpl = cpt.xpath("./td[2]/a/text()")[0].split(' ')
account.label = tpl[0] + u" " + tpl[1]
account.label = str(u""+account.label)
account.raw = str(u""+account.label)
l.append(account)
return l
# vim:ts=4:sw=4

View file

@ -18,85 +18,18 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>. # along with weboob. If not, see <http://www.gnu.org/licenses/>.
from logging import error #from logging import error
from weboob.tools.browser import BasePage, BrowserUnavailable from weboob.tools.browser import BasePage #, BrowserUnavailable
#from lxml import etree
__all__ = ['LoginPage'] __all__ = ['LoginPage']
def dump(obj):
for attr in dir(obj):
print "obj.%s = %s" % (attr, getattr(obj, attr))
class LoginPage(BasePage): class LoginPage(BasePage):
def login(self, login, passwd): def login(self, login, passwd):
#print "DEBUG BasePage=", BasePage.url
#dump(BasePage)
self.browser.select_form(nr=3) self.browser.select_form(nr=3)
#self.browser['locale'] = 'fr'
self.browser['login'] = login self.browser['login'] = login
self.browser['passwd'] = passwd self.browser['passwd'] = passwd
#self.browser['idDyn'] = 'false'
self.browser.submit() self.browser.submit()
#print "DEBUG ", self.page
#class LoginPage(BasePage): # vim:ts=4:sw=4
# def on_loaded(self):
# pass
# #for td in self.document.getroot().cssselect('td.LibelleErreur'):
# # if td.text is None:
# # continue
# # msg = td.text.strip()
# # if 'indisponible' in msg:
# # raise BrowserUnavailable(msg)
#
# def login(self, login, password):
# DOMAIN_LOGIN = self.browser.DOMAIN_LOGIN
# DOMAIN = self.browser.DOMAIN
#
# url_login = 'https://' + DOMAIN_LOGIN + '/index.html'
#
# base_url = 'https://' + DOMAIN
# url = base_url + '/cvcsgenclavier?mode=jsom&estSession=0'
# headers = {
# 'Referer': url_login
# }
# request = self.browser.request_class(url, None, headers)
# infos_data = self.browser.readurl(request)
# infos_xml = etree.XML(infos_data)
# infos = {}
# for el in ("cryptogramme", "nblignes", "nbcolonnes"):
# infos[el] = infos_xml.find(el).text
#
# infos["grille"] = ""
# for g in infos_xml.findall("grille"):
# infos["grille"] += g.text + ","
# infos["keyCodes"] = infos["grille"].split(",")
#
# url = base_url + '/cvcsgenimage?modeClavier=0&cryptogramme=' + infos["cryptogramme"]
# img = Captcha(self.browser.openurl(url), infos)
#
# try:
# img.build_tiles()
# except TileError, err:
# error("Error: %s" % err)
# if err.tile:
# err.tile.display()
#
# self.browser.openurl(url_login)
# self.browser.select_form('authentification')
# self.browser.set_all_readonly(False)
#
# self.browser['codcli'] = login
# self.browser['codsec'] = img.get_codes(password)
# self.browser['cryptocvcs'] = infos["cryptogramme"]
# self.browser.submit()
#class BadLoginPage(BasePage):
#print "DEBUG BasePage"
# import sys
#sys.exit(1)

View file

@ -29,3 +29,6 @@ class FortuneoTest(BackendTest):
a = l[0] a = l[0]
list(self.backend.iter_coming(a)) list(self.backend.iter_coming(a))
list(self.backend.iter_history(a)) list(self.backend.iter_history(a))
# vim:ts=4:sw=4