diff --git a/modules/fortuneo/__init__.py b/modules/fortuneo/__init__.py
index 19c1ccba..9390277f 100644
--- a/modules/fortuneo/__init__.py
+++ b/modules/fortuneo/__init__.py
@@ -21,3 +21,5 @@
from .backend import FortuneoBackend
__all__ = ['FortuneoBackend']
+
+# vim:ts=4:sw=4
diff --git a/modules/fortuneo/backend.py b/modules/fortuneo/backend.py
index b04b4e51..1b63d798 100644
--- a/modules/fortuneo/backend.py
+++ b/modules/fortuneo/backend.py
@@ -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
diff --git a/modules/fortuneo/browser.py b/modules/fortuneo/browser.py
index fa4a51e4..1d67cb82 100644
--- a/modules/fortuneo/browser.py
+++ b/modules/fortuneo/browser.py
@@ -19,81 +19,79 @@
# along with weboob. If not, see .
-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
diff --git a/modules/fortuneo/pages/accounts_list.py b/modules/fortuneo/pages/accounts_list.py
index 67303b70..c31a958b 100644
--- a/modules/fortuneo/pages/accounts_list.py
+++ b/modules/fortuneo/pages/accounts_list.py
@@ -17,146 +17,88 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
-# 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
\d{2})/(?P\d{2}) (?P\d+)H(?P\d+) (?P.*)'),
- # FrenchTransaction.TYPE_WITHDRAWAL),
- # (re.compile(r'^(?PCARTE) \w+ (?P\d{2})/(?P\d{2}) (?P.*)'),
- # FrenchTransaction.TYPE_CARD),
- # (re.compile(r'^(?P(COTISATION|PRELEVEMENT|TELEREGLEMENT|TIP)) (?P.*)'),
- # FrenchTransaction.TYPE_ORDER),
- # (re.compile(r'^(?PVIR(EMEN)?T? \w+) (?P.*)'),
- # FrenchTransaction.TYPE_TRANSFER),
- # (re.compile(r'^(CHEQUE) (?P.*)'), FrenchTransaction.TYPE_CHECK),
- # (re.compile(r'^(FRAIS) (?P.*)'), FrenchTransaction.TYPE_BANK),
- # (re.compile(r'^(?PECHEANCEPRET)(?P.*)'),
- # FrenchTransaction.TYPE_LOAN_PAYMENT),
- # (re.compile(r'^(?PREMISE CHEQUES)(?P.*)'),
- # 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
diff --git a/modules/fortuneo/pages/login.py b/modules/fortuneo/pages/login.py
index 81f1454e..7e1fdac3 100644
--- a/modules/fortuneo/pages/login.py
+++ b/modules/fortuneo/pages/login.py
@@ -18,85 +18,18 @@
# along with weboob. If not, see .
-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
diff --git a/modules/fortuneo/test.py b/modules/fortuneo/test.py
index d4d243c3..729018bd 100644
--- a/modules/fortuneo/test.py
+++ b/modules/fortuneo/test.py
@@ -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
+