'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
__all__ = ['FortuneoBackend']
# vim:ts=4:sw=4

View file

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

View file

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

View file

@ -17,146 +17,88 @@
# You should have received a copy of the GNU Affero General Public License
# 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
import re
import datetime
from weboob.capabilities.bank import Account
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.tools.browser import BasePage, BrokenPageError
from weboob.tools.capabilities.bank.transactions import Transaction
from weboob.tools.browser import BasePage #, BrokenPageError
__all__ = ['AccountsList', 'AccountHistoryPage']
#__all__ = ['IndexPage', 'AccountsList', 'AccountHistory']
__all__ = ['AccountHistory']
class AccountHistoryPage(BasePage):
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):
# def on_loaded(self):
# 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
for i in range(len(tables)):
operation = Transaction(len(operations))
#class AccountsList(BasePage):
# def on_loaded(self):
# pass
#
# def get_list(self):
# #print "DEBUG self.document="+self.document
# account = []
# account.append('test')
# account.append('Livret +')
# account.append('20')
# 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
date_oper = tables[i].xpath("./td[2]/text()")[0]
date_val = tables[i].xpath("./td[3]/text()")[0]
label = tables[i].xpath("./td[4]/text()")[0]
label = label.strip()
amount = tables[i].xpath("./td[5]/text() | ./td[6]/text()")
operation.date = datetime.datetime.strptime(date_val, "%d/%m/%Y")
operation.rdate = datetime.datetime.strptime(date_oper,"%d/%m/%Y")
operation.type = 0
operation.raw = label
operation.label = u""+label
#class Transaction(FrenchTransaction):
# pass
#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>.*)'),
# FrenchTransaction.TYPE_WITHDRAWAL),
# (re.compile(r'^(?P<category>CARTE) \w+ (?P<dd>\d{2})/(?P<mm>\d{2}) (?P<text>.*)'),
# 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),
# ]
if amount[1] == u'\xa0':
amount = amount[0].replace(u"\xa0", "").replace(",", ".").strip()
else:
amount = amount[1].replace(u"\xa0", "").replace(",", ".").strip()
operation.amount = Decimal(amount)
class AccountHistory(BasePage):
get_list = [1, 2, 3, 4]
def get_part_url(self):
print "DEBUG AccountHistory.get_part_url a implementer"
pass
#for script in self.document.getiterator('script'):
# if script.text is None:
# continue
operation.category = u" " # blank category
operation.origin = u" " # blank origin
operation.recipient = u" " # blank rcpt
# m = re.search('var listeEcrCavXmlUrl="(.*)";', script.text)
# if m:
# return m.group(1)
operations.append(operation)
#raise BrokenPageError('Unable to find link to history part')
return operations
#def iter_transactions(self):
# print "DEBUG iter_transactions a implementer"
# pass
# #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)
class AccountsList(BasePage):
def get_list(self):
l = []
# # for tr in self._iter_transactions(doc):
# # yield tr
for cpt in self.document.xpath(".//*[@id='tableauComptesTitEtCotit']/tbody/tr"):
account = Account()
# # el = d.xpath('//dataHeader')[0]
# # if int(el.find('suite').text) != 1:
# # return
# account.id
account.id = cpt.xpath("./td[1]/a/text()")[0]
account.id = str(account.id)
# account balance
account.balance = Decimal(cpt.xpath("./td[3]/text()")[0].replace("EUR", "").replace("\n", "").replace("\t", "").replace(u"\xa0", ""))
# account coming
mycomingval = cpt.xpath("./td[4]/text()")[0].replace("EUR", "").replace("\n", "").replace("\t", "")
if mycomingval == '-':
account.coming = float(0)
else:
account.coming = float(mycomingval)
# # url = urlparse(url)
# # p = parse_qs(url.query)
# # 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._link_id
url_to_parse = cpt.xpath('./td[1]/a/@href')[0] # link
compte_id_re = re.compile(r'.*COMPTE_ACTIF=([^\&]+)\&.*')
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]
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)
def _iter_transactions(self, doc):
print "DEBUG _iter_transactions a implementer"
pass
#for i, tr in enumerate(self.parser.select(doc.getroot(), 'tr')):
# t = Transaction(i)
# t.parse(date=tr.xpath('./td[@headers="Date"]')[0].text,
# raw=tr.attrib['title'].strip())
# t.set_amount(*reversed([el.text for el in tr.xpath('./td[@class="right"]')]))
# t._coming = tr.xpath('./td[@headers="AVenir"]')[0].text
# yield t
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/>.
from logging import error
#from logging import error
from weboob.tools.browser import BasePage, BrowserUnavailable
#from lxml import etree
from weboob.tools.browser import BasePage #, BrowserUnavailable
__all__ = ['LoginPage']
def dump(obj):
for attr in dir(obj):
print "obj.%s = %s" % (attr, getattr(obj, attr))
class LoginPage(BasePage):
def login(self, login, passwd):
#print "DEBUG BasePage=", BasePage.url
#dump(BasePage)
self.browser.select_form(nr=3)
#self.browser['locale'] = 'fr'
self.browser['login'] = login
self.browser['passwd'] = passwd
#self.browser['idDyn'] = 'false'
self.browser.submit()
#print "DEBUG ", self.page
#class LoginPage(BasePage):
# 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)
# vim:ts=4:sw=4

View file

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