weboob.tools.browser -> weboob.deprecated.browser weboob.tools.parsers -> weboob.deprecated.browser.parsers weboob.tools.mech -> weboob.deprecated.mech weboob.browser2 -> weboob.browser weboob.core.exceptions -> weboob.exceptions Also, the new tree for browser2 is: weboob.browser: import weboob.browser.browsers.* and weboob.browser.url.* weboob.browser.browsers: all browsers (including PagesBrowser and LoginBrowser) weboob.browser.url: the URL class weboob.browser.profiles: all Profile classes weboob.browser.sessions: WeboobSession and FuturesSession weboob.browser.cookies: that's a cookies thing weboob.browser.pages: all Page and derivated classes, and Form class weboob.browser.exceptions: specific browser exceptions weboob.browser.elements: AbstractElement classes, and 'method' decorator weboob.browser.filters.*: all filters
119 lines
4.4 KiB
Python
119 lines
4.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright(C) 2013 Laurent Bachelier
|
|
#
|
|
# This file is part of weboob.
|
|
#
|
|
# weboob is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# weboob is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
from decimal import Decimal
|
|
import datetime
|
|
import re
|
|
|
|
from weboob.deprecated.browser import Page, BrowserIncorrectPassword
|
|
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
|
|
|
|
|
|
class Transaction(FrenchTransaction):
|
|
@classmethod
|
|
def clean_amount(cls, text):
|
|
text = text.strip()
|
|
# Convert "American" UUU.CC format to "French" UUU,CC format
|
|
if re.search(r'\d\.\d\d$', text):
|
|
text = text.replace(',', ' ').replace('.', ',')
|
|
return FrenchTransaction.clean_amount(text)
|
|
|
|
|
|
class LoginPage(Page):
|
|
def login(self, merchant_id, login, password):
|
|
self.browser.select_form(name='loginForm')
|
|
self.browser['merchant'] = merchant_id.encode(self.browser.ENCODING)
|
|
self.browser['login'] = login.encode(self.browser.ENCODING)
|
|
self.browser['password'] = password.encode(self.browser.ENCODING)
|
|
self.browser.submit()
|
|
|
|
|
|
class SummaryPage(Page):
|
|
def clean_amount(self, el, debit):
|
|
amount = Decimal(Transaction.clean_amount(el.text_content()))
|
|
if amount == Decimal('0.00'):
|
|
return None
|
|
if debit and amount > Decimal('0'):
|
|
return -1 * amount
|
|
return amount
|
|
|
|
def get_balance(self):
|
|
zone = self.parser.select(self.document.getroot(), '#ActionZone_Euro', 1)
|
|
for tr in self.parser.select(zone, '#transactionError tr'):
|
|
tds = self.parser.select(tr, 'td')
|
|
if tds and tds[0].text_content().strip() == 'Total':
|
|
debit, credit = self.parser.select(tr, 'td.amount', 4)[-2:] # keep the last 2
|
|
debit = self.clean_amount(debit, debit=True)
|
|
credit = self.clean_amount(credit, debit=False)
|
|
amount = credit or debit
|
|
return amount
|
|
|
|
|
|
class UselessPage(Page):
|
|
def on_loaded(self):
|
|
for error in self.document.xpath('//li[@class="error"]'):
|
|
raise BrowserIncorrectPassword(self.parser.tocleanstring(error))
|
|
|
|
|
|
class TransactionSearchPage(Page):
|
|
def search(self, accepted=True, refused=False):
|
|
self.browser.select_form(name='transactionSearchForm')
|
|
self.browser['selectedDateCriteria'] = ['thisweek'] # TODO ask for more
|
|
self.browser['transactionAccepted'] = ['0'] if accepted else []
|
|
self.browser['transactionRefused'] = ['0'] if refused else []
|
|
|
|
# simulate the javascript
|
|
nonce = self.parser.select(self.document.getroot(), '#menu li.global a')[0] \
|
|
.attrib['href'].partition('CSRF_NONCE=')[2]
|
|
self.browser.form.action = '%s://%s/transactionSearch.do?reqCode=%s&org.apache.catalina.filters.CSRF_NONCE=%s&screen=new' % (self.browser.PROTOCOL, self.browser.DOMAIN, 'search', nonce)
|
|
self.browser.submit()
|
|
|
|
|
|
class TransactionsPage(Page):
|
|
def get_csv_url(self):
|
|
for a in self.parser.select(self.document.getroot(), '.exportlinks a'):
|
|
if len(self.parser.select(a, 'span.csv')):
|
|
return a.attrib['href']
|
|
|
|
|
|
class TransactionsCsvPage(Page):
|
|
def guess_format(self, amount):
|
|
if re.search(r'\d\.\d\d$', amount):
|
|
date_format = "%m/%d/%Y"
|
|
else:
|
|
date_format = "%d/%m/%Y"
|
|
time_format = "%H:%M:%S"
|
|
return date_format + ' ' + time_format
|
|
|
|
def iter_transactions(self):
|
|
ID = 0
|
|
DATE = 2
|
|
AMOUNT = 4
|
|
CARD = 7
|
|
NUMBER = 8
|
|
for row in self.document.rows:
|
|
t = Transaction(row[ID])
|
|
date = row[DATE]
|
|
amount = row[AMOUNT]
|
|
datetime_format = self.guess_format(amount)
|
|
t.set_amount(amount)
|
|
t.parse(datetime.datetime.strptime(date, datetime_format),
|
|
row[CARD] + ' ' + row[NUMBER])
|
|
yield t
|