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 +