diff --git a/modules/banqueaccord/backend.py b/modules/banqueaccord/backend.py index cb761bad..beab7077 100644 --- a/modules/banqueaccord/backend.py +++ b/modules/banqueaccord/backend.py @@ -18,6 +18,7 @@ # along with weboob. If not, see . +from weboob.capabilities.base import find_object from weboob.capabilities.bank import ICapBank, AccountNotFound from weboob.tools.backend import BaseBackend, BackendConfig from weboob.tools.value import ValueBackendPassword @@ -45,18 +46,10 @@ class BanqueAccordBackend(BaseBackend, ICapBank): self.config['password'].get()) def iter_accounts(self): - with self.browser: - return self.browser.get_accounts_list() + return self.browser.get_accounts_list() def get_account(self, _id): - with self.browser: - account = self.browser.get_account(_id) - - if account: - return account - else: - raise AccountNotFound() + return find_object(self.browser.get_accounts_list(), id=_id, error=AccountNotFound) def iter_history(self, account): - with self.browser: - return self.browser.iter_history(account) + return self.browser.iter_history(account) diff --git a/modules/banqueaccord/browser.py b/modules/banqueaccord/browser.py index cc1cb53b..d2f7ff97 100644 --- a/modules/banqueaccord/browser.py +++ b/modules/banqueaccord/browser.py @@ -18,9 +18,8 @@ # along with weboob. If not, see . -import urllib - -from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword +from weboob.tools.browser2 import LoginBrowser, need_login, URL +from weboob.tools.browser import BrowserIncorrectPassword from .pages import LoginPage, IndexPage, AccountsPage, OperationsPage @@ -28,80 +27,53 @@ from .pages import LoginPage, IndexPage, AccountsPage, OperationsPage __all__ = ['BanqueAccordBrowser'] -class BanqueAccordBrowser(BaseBrowser): - PROTOCOL = 'https' - DOMAIN = 'www.banque-accord.fr' - ENCODING = None +class BanqueAccordBrowser(LoginBrowser): + BASEURL = 'https://www.banque-accord.fr/site/s/' + TIMEOUT = 20.0 - PAGES = { - 'https://www.banque-accord.fr/site/s/login/login.html': LoginPage, - 'https://www.banque-accord.fr/site/s/detailcompte/detailcompte.html': IndexPage, - 'https://www.banque-accord.fr/site/s/detailcompte/ongletdetailcompte.html': AccountsPage, - 'https://www.banque-accord.fr/site/s/detailcompte/ongletdernieresoperations.html': OperationsPage, - } + login = URL('login/login.html', LoginPage) + index = URL('detailcompte/detailcompte.html', IndexPage) + accounts = URL('detailcompte/ongletdetailcompte.html', AccountsPage) + operations = URL('detailcompte/ongletdernieresoperations.html', OperationsPage) - def is_logged(self): - return self.page is not None and not self.is_on_page(LoginPage) - - def home(self): - if self.is_logged(): - self.location('/site/s/detailcompte/detailcompte.html') - else: - self.login() - - def login(self): + 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.is_logged(): - return - - self.location('/site/s/login/login.html', no_login=True) - assert self.is_on_page(LoginPage) + self.login.go() self.page.login(self.username, self.password) - if not self.is_logged(): + if not self.index.is_here(): raise BrowserIncorrectPassword() + @need_login def get_accounts_list(self): - if not self.is_on_page(IndexPage): - self.location('https://www.banque-accord.fr/site/s/detailcompte/detailcompte.html') + self.index.stay_or_go() for a in self.page.get_list(): post = {'numeroCompte': a.id,} - self.location('/site/s/detailcompte/detailcompte.html', urllib.urlencode(post)) + self.index.go(data=post) a.balance = self.page.get_loan_balance() if a.balance is not None: a.type = a.TYPE_LOAN else: - self.location('/site/s/detailcompte/ongletdetailcompte.html') + self.accounts.go() a.balance = self.page.get_balance() a.type = a.TYPE_CARD yield a - def get_account(self, id): - assert isinstance(id, basestring) - if not self.is_on_page(IndexPage): - self.home() - - for a in self.get_accounts_list(): - if a.id == id: - return a - return None - + @need_login def iter_history(self, account): if account.type != account.TYPE_CARD: return iter([]) post = {'numeroCompte': account.id} - self.location('/site/s/detailcompte/detailcompte.html', urllib.urlencode(post)) - self.location('/site/s/detailcompte/ongletdernieresoperations.html') + self.index.go(data=post) + self.operations.go() - assert self.is_on_page(OperationsPage) return self.page.get_history() diff --git a/modules/banqueaccord/pages.py b/modules/banqueaccord/pages.py index 1487061b..e1681826 100644 --- a/modules/banqueaccord/pages.py +++ b/modules/banqueaccord/pages.py @@ -18,11 +18,13 @@ # along with weboob. If not, see . -from decimal import Decimal +from decimal import Decimal, InvalidOperation import re +from cStringIO import StringIO from weboob.capabilities.bank import Account -from weboob.tools.browser import BasePage, BrokenPageError +from weboob.tools.browser2.page import HTMLPage, method, ListElement, ItemElement, LoggedPage +from weboob.tools.browser2.filters import ParseError, CleanText, Regexp, Attr, CleanDecimal, Env from weboob.tools.captcha.virtkeyboard import MappedVirtKeyboard, VirtKeyboardError from weboob.tools.capabilities.bank.transactions import FrenchTransaction @@ -46,9 +48,9 @@ class VirtKeyboard(MappedVirtKeyboard): color=(0,0,0) def __init__(self, page): - img = page.document.find("//img[@usemap='#cv']") - img_file = page.browser.openurl(img.attrib['src']) - MappedVirtKeyboard.__init__(self, img_file, page.document, img, self.color, 'href', convert='RGB') + img = page.doc.find("//img[@usemap='#cv']") + res = page.browser.open(img.attrib['src']) + MappedVirtKeyboard.__init__(self, StringIO(res.content), page.doc, img, self.color, 'href', convert='RGB') self.check_symbols(self.symbols, page.browser.responses_dirname) @@ -70,7 +72,7 @@ class VirtKeyboard(MappedVirtKeyboard): except VirtKeyboardError: continue else: - return ''.join(re.findall("'(\d+)'", code)[-2:]) + return ''.join(re.findall(r"'(\d+)'", code)[-2:]) raise VirtKeyboardError('Symbol not found') def get_string_code(self, string): @@ -79,39 +81,44 @@ class VirtKeyboard(MappedVirtKeyboard): code += self.get_symbol_code(self.symbols[c]) return code -class LoginPage(BasePage): +class LoginPage(HTMLPage): def login(self, login, password): vk = VirtKeyboard(self) - form = self.document.xpath('//form[@id="formulaire-login"]')[0] + form = self.get_form('//form[@id="formulaire-login"]') code = vk.get_string_code(password) - assert len(code)==10, BrokenPageError("Wrong number of character.") - self.browser.location(self.browser.buildurl(form.attrib['action'], identifiant=login, code=code), no_login=True) + assert len(code)==10, ParseError("Wrong number of character.") + form['identifiant'] = login + form['code'] = code + form.submit() -class IndexPage(BasePage): - def get_list(self): - for line in self.document.xpath('//li[@id="menu-n2-mesproduits"]//li//a'): - if line.get('onclick') is None: - continue - account = Account() - account.id = line.get('onclick').split("'")[1] - account.label = self.parser.tocleanstring(line) - yield account +class IndexPage(LoggedPage, HTMLPage): + @method + class get_list(ListElement): + item_xpath = '//li[@id="menu-n2-mesproduits"]//li//a' + + class item(ItemElement): + klass = Account + obj_id = Regexp(Attr('.', 'onclick'), r"^[^']+'([^']+)'.*", r"\1") + obj_label = CleanText('.') + + def condition(self): + return self.el.get('onclick') is not None def get_loan_balance(self): xpath = '//table//td/strong[contains(text(), "Montant emprunt")]/../../td[2]' try: - return - Decimal(FrenchTransaction.clean_amount(self.parser.tocleanstring(self.document.xpath(xpath)[0]))) - except IndexError: + return - CleanDecimal(xpath, replace_dots=False)(self.doc) + except InvalidOperation: return None def get_card_name(self): - return self.parser.tocleanstring(self.document.xpath('//h1')[0]) + return CleanText('//h1[1]')(self.doc) -class AccountsPage(BasePage): +class AccountsPage(LoggedPage, HTMLPage): def get_balance(self): balance = Decimal('0.0') - for line in self.document.xpath('//div[@class="detail"]/table//tr'): + for line in self.doc.xpath('//div[@class="detail"]/table//tr'): try: left = line.xpath('./td[@class="gauche"]')[0] right = line.xpath('./td[@class="droite"]')[0] @@ -119,35 +126,43 @@ class AccountsPage(BasePage): #useless line continue - if len(left.xpath('./span[@class="precision"]')) == 0 and (left.text is None or not 'total' in left.text.lower()): + if len(left.xpath('./span[@class="precision"]')) == 0 or \ + (left.text is None or + not 'total' in left.text.lower() or + u'prélevé' in left.xpath('./span[@class="precision"]')[0].text.lower()): continue - balance -= Decimal(FrenchTransaction.clean_amount(right.text)) + balance -= CleanDecimal('.', replace_dots=False)(right) return balance -class OperationsPage(BasePage): - def get_history(self): - for tr in self.document.xpath('//div[contains(@class, "mod-listeoperations")]//table/tbody/tr'): - cols = tr.findall('td') +class Transaction(FrenchTransaction): + PATTERNS = [(re.compile(ur'^(?P.*?) - traité le \d+/\d+$'), FrenchTransaction.TYPE_CARD)] - date = self.parser.tocleanstring(cols[0]) - raw = self.parser.tocleanstring(cols[1]) - label = re.sub(u' - traité le \d+/\d+', '', raw) +class OperationsPage(LoggedPage, HTMLPage): + @method + class get_history(ListElement): + item_xpath = '//div[contains(@class, "mod-listeoperations")]//table/tbody/tr' - debit = self.parser.tocleanstring(cols[3]) - if len(debit) > 0: - t = FrenchTransaction(0) - t.parse(date, raw) - t.label = label - t.set_amount(debit) - yield t + class credit(ItemElement): + klass = Transaction + obj_type = Transaction.TYPE_CARD + obj_date = Transaction.Date('./td[1]') + obj_raw = Transaction.Raw('./td[2]') + obj_amount = Env('amount') - amount = self.parser.tocleanstring(cols[2]) - if len(amount) > 0: - t = FrenchTransaction(0) - t.parse(date, raw) - t.label = label - t.set_amount(amount) - t.amount = - t.amount - yield t + def condition(self): + self.env['amount'] = Transaction.Amount('./td[4]')(self.el) + return self.env['amount'] > 0 + + + class debit(ItemElement): + klass = Transaction + obj_type = Transaction.TYPE_CARD + obj_date = Transaction.Date('./td[1]') + obj_raw = Transaction.Raw('./td[2]') + obj_amount = Env('amount') + + def condition(self): + self.env['amount'] = Transaction.Amount('', './td[3]')(self.el) + return self.env['amount'] < 0