support new version of website
This commit is contained in:
parent
79da6e604f
commit
cf3b30b1d1
2 changed files with 125 additions and 50 deletions
|
|
@ -91,13 +91,13 @@ class CaisseEpargne(BaseBrowser):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_history(self, link_type, id):
|
def _get_history(self, info):
|
||||||
if self.is_on_page(IndexPage):
|
if self.is_on_page(IndexPage):
|
||||||
self.page.go_list()
|
self.page.go_list()
|
||||||
else:
|
else:
|
||||||
self.location(self.buildurl('/Portail.aspx'))
|
self.location(self.buildurl('/Portail.aspx'))
|
||||||
|
|
||||||
self.page.go_history(link_type, id)
|
self.page.go_history(info)
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
assert self.is_on_page(IndexPage)
|
assert self.is_on_page(IndexPage)
|
||||||
|
|
@ -109,9 +109,9 @@ class CaisseEpargne(BaseBrowser):
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_history(self, account):
|
def get_history(self, account):
|
||||||
return self._get_history(account._link_type, account.id)
|
return self._get_history(account._info)
|
||||||
|
|
||||||
def get_coming(self, account):
|
def get_coming(self, account):
|
||||||
for link_type, id in account._card_links:
|
for info in account._card_links:
|
||||||
for tr in self._get_history(link_type, id):
|
for tr in self._get_history(info):
|
||||||
yield tr
|
yield tr
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,14 @@ from weboob.tools.capabilities.bank.transactions import FrenchTransaction
|
||||||
__all__ = ['LoginPage', 'ErrorPage', 'IndexPage', 'UnavailablePage']
|
__all__ = ['LoginPage', 'ErrorPage', 'IndexPage', 'UnavailablePage']
|
||||||
|
|
||||||
|
|
||||||
class LoginPage(BasePage):
|
class _LogoutPage(BasePage):
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
try:
|
try:
|
||||||
raise BrowserIncorrectPassword(self.parser.tocleanstring(self.parser.select(self.document.getroot(), '.messErreur', 1)))
|
raise BrowserIncorrectPassword(self.parser.tocleanstring(self.parser.select(self.document.getroot(), '.messErreur', 1)))
|
||||||
except BrokenPageError:
|
except BrokenPageError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class LoginPage(_LogoutPage):
|
||||||
def login(self, login):
|
def login(self, login):
|
||||||
self.browser.select_form(name='Main')
|
self.browser.select_form(name='Main')
|
||||||
self.browser.set_all_readonly(False)
|
self.browser.set_all_readonly(False)
|
||||||
|
|
@ -64,7 +65,7 @@ class LoginPage(BasePage):
|
||||||
self.browser.submit(nologin=True)
|
self.browser.submit(nologin=True)
|
||||||
|
|
||||||
|
|
||||||
class ErrorPage(LoginPage):
|
class ErrorPage(_LogoutPage):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class UnavailablePage(BasePage):
|
class UnavailablePage(BasePage):
|
||||||
|
|
@ -76,33 +77,77 @@ class UnavailablePage(BasePage):
|
||||||
|
|
||||||
|
|
||||||
class Transaction(FrenchTransaction):
|
class Transaction(FrenchTransaction):
|
||||||
PATTERNS = [(re.compile('^CB (?P<text>.*?) FACT (?P<dd>\d{2})(?P<mm>\d{2})(?P<yy>\d{2})'),
|
PATTERNS = [(re.compile('^CB (?P<text>.*?) FACT (?P<dd>\d{2})(?P<mm>\d{2})(?P<yy>\d{2})', re.IGNORECASE),
|
||||||
FrenchTransaction.TYPE_CARD),
|
FrenchTransaction.TYPE_CARD),
|
||||||
(re.compile('^RET(RAIT)? DAB (?P<dd>\d+)-(?P<mm>\d+)-.*'),
|
(re.compile('^RET(RAIT)? DAB (?P<dd>\d+)-(?P<mm>\d+)-.*', re.IGNORECASE),
|
||||||
FrenchTransaction.TYPE_WITHDRAWAL),
|
FrenchTransaction.TYPE_WITHDRAWAL),
|
||||||
(re.compile('^RET(RAIT)? DAB (?P<text>.*?) (?P<dd>\d{2})(?P<mm>\d{2})(?P<yy>\d{2}) (?P<HH>\d{2})H(?P<MM>\d{2})'),
|
(re.compile('^RET(RAIT)? DAB (?P<text>.*?) (?P<dd>\d{2})(?P<mm>\d{2})(?P<yy>\d{2}) (?P<HH>\d{2})H(?P<MM>\d{2})', re.IGNORECASE),
|
||||||
FrenchTransaction.TYPE_WITHDRAWAL),
|
FrenchTransaction.TYPE_WITHDRAWAL),
|
||||||
(re.compile('^VIR(EMENT)?(\.PERIODIQUE)? (?P<text>.*)'), FrenchTransaction.TYPE_TRANSFER),
|
(re.compile('^VIR(EMENT)?(\.PERIODIQUE)? (?P<text>.*)', re.IGNORECASE),
|
||||||
(re.compile('^PRLV (?P<text>.*)'), FrenchTransaction.TYPE_ORDER),
|
FrenchTransaction.TYPE_TRANSFER),
|
||||||
(re.compile('^CHEQUE.*'), FrenchTransaction.TYPE_CHECK),
|
(re.compile('^PRLV (?P<text>.*)', re.IGNORECASE),
|
||||||
(re.compile('^(CONVENTION \d+ )?COTIS(ATION)? (?P<text>.*)'),
|
|
||||||
FrenchTransaction.TYPE_BANK),
|
|
||||||
(re.compile(r'^\* (?P<text>.*)'), FrenchTransaction.TYPE_BANK),
|
|
||||||
(re.compile('^REMISE (?P<text>.*)'), FrenchTransaction.TYPE_DEPOSIT),
|
|
||||||
(re.compile('^(?P<text>.*)( \d+)? QUITTANCE .*'),
|
|
||||||
FrenchTransaction.TYPE_ORDER),
|
FrenchTransaction.TYPE_ORDER),
|
||||||
(re.compile('^CB [\d\*]+ (?P<text>.*)'), FrenchTransaction.TYPE_CARD),
|
(re.compile('^CHEQUE.*', re.IGNORECASE), FrenchTransaction.TYPE_CHECK),
|
||||||
|
(re.compile('^(CONVENTION \d+ )?COTIS(ATION)? (?P<text>.*)', re.IGNORECASE),
|
||||||
|
FrenchTransaction.TYPE_BANK),
|
||||||
|
(re.compile(r'^\* (?P<text>.*)', re.IGNORECASE),
|
||||||
|
FrenchTransaction.TYPE_BANK),
|
||||||
|
(re.compile('^REMISE (?P<text>.*)', re.IGNORECASE),
|
||||||
|
FrenchTransaction.TYPE_DEPOSIT),
|
||||||
|
(re.compile('^(?P<text>.*)( \d+)? QUITTANCE .*', re.IGNORECASE),
|
||||||
|
FrenchTransaction.TYPE_ORDER),
|
||||||
|
(re.compile('^CB [\d\*]+ (?P<text>.*)', re.IGNORECASE),
|
||||||
|
FrenchTransaction.TYPE_CARD),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class IndexPage(BasePage):
|
class IndexPage(BasePage):
|
||||||
ACCOUNT_TYPES = {u'Epargne liquide': Account.TYPE_SAVINGS,
|
ACCOUNT_TYPES = {u'Epargne liquide': Account.TYPE_SAVINGS,
|
||||||
u'Compte Courant': Account.TYPE_CHECKING,
|
u'Compte Courant': Account.TYPE_CHECKING,
|
||||||
|
u'Mes comptes': Account.TYPE_CHECKING,
|
||||||
|
u'Mon épargne': Account.TYPE_SAVINGS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _get_account_info(self, a):
|
||||||
|
m = re.search("PostBack(Options)?\([\"'][^\"']+[\"'],\s*['\"]HISTORIQUE_([\d\w&]+)['\"]", a.attrib.get('href', ''))
|
||||||
|
if m is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
# it is in form CB&12345[&2]. the last part is only for new website
|
||||||
|
# and is necessary for navigation.
|
||||||
|
link = m.group(2)
|
||||||
|
parts = link.split('&')
|
||||||
|
return {'type': parts[0], 'id': parts[1], 'link': link}
|
||||||
|
|
||||||
|
def _add_account(self, accounts, link, label, account_type, balance):
|
||||||
|
info = self._get_account_info(link)
|
||||||
|
if info is None:
|
||||||
|
self.logger.warning('Unable to parse account %r' % label)
|
||||||
|
return
|
||||||
|
|
||||||
|
account = Account()
|
||||||
|
account.id = info['id']
|
||||||
|
account._info = info
|
||||||
|
account.label = label
|
||||||
|
account.type = account_type
|
||||||
|
account.balance = Decimal(FrenchTransaction.clean_amount(balance))
|
||||||
|
account.currency = account.get_currency(balance)
|
||||||
|
account._card_links = []
|
||||||
|
|
||||||
|
if account._info['type'] == 'CB' and account.id in accounts:
|
||||||
|
a = accounts[account.id]
|
||||||
|
if not a.coming:
|
||||||
|
a.coming = Decimal('0.0')
|
||||||
|
a.coming += account.balance
|
||||||
|
a._card_links.append(account._info)
|
||||||
|
return
|
||||||
|
|
||||||
|
accounts[account.id] = account
|
||||||
|
|
||||||
def get_list(self):
|
def get_list(self):
|
||||||
accounts = OrderedDict()
|
accounts = OrderedDict()
|
||||||
|
|
||||||
|
# Old website
|
||||||
for table in self.document.xpath('//table[@cellpadding="1"]'):
|
for table in self.document.xpath('//table[@cellpadding="1"]'):
|
||||||
account_type = Account.TYPE_UNKNOWN
|
account_type = Account.TYPE_UNKNOWN
|
||||||
for tr in table.xpath('./tr'):
|
for tr in table.xpath('./tr'):
|
||||||
|
|
@ -111,41 +156,55 @@ class IndexPage(BasePage):
|
||||||
account_type = self.ACCOUNT_TYPES.get(tds[1].text.strip(), Account.TYPE_UNKNOWN)
|
account_type = self.ACCOUNT_TYPES.get(tds[1].text.strip(), Account.TYPE_UNKNOWN)
|
||||||
else:
|
else:
|
||||||
a = tds[1].find('a')
|
a = tds[1].find('a')
|
||||||
m = re.match("^javascript:__doPostBack\('.*','HISTORIQUE_(\w+)&(\d+)'\)", a.attrib.get('href', ''))
|
if a is None:
|
||||||
|
|
||||||
if not m:
|
|
||||||
self.logger.warning('Unable to parse account %r' % (self.parser.tocleanstring(a)))
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
account = Account()
|
label = self.parser.tocleanstring(a)
|
||||||
account.id = m.group(2)
|
balance = u''.join([txt.strip() for txt in tds[-1].itertext()])
|
||||||
account._link_type = m.group(1)
|
self._add_account(accounts, a, label, account_type, balance)
|
||||||
account.label = self.parser.tocleanstring(a)
|
|
||||||
account.type = account_type
|
|
||||||
amount = u''.join([txt.strip() for txt in tds[-1].itertext()])
|
|
||||||
account.balance = Decimal(FrenchTransaction.clean_amount(amount))
|
|
||||||
account.currency = account.get_currency(amount)
|
|
||||||
account._card_links = []
|
|
||||||
|
|
||||||
if account._link_type == 'CB' and account.id in accounts:
|
if len(accounts) == 0:
|
||||||
a = accounts[account.id]
|
# New website
|
||||||
if not a.coming:
|
for table in self.document.xpath('//div[@class="panel"]'):
|
||||||
a.coming = Decimal('0.0')
|
title = table.getprevious()
|
||||||
a.coming += account.balance
|
if title is None:
|
||||||
a._card_links.append((account._link_type, account.id))
|
continue
|
||||||
|
account_type = self.ACCOUNT_TYPES.get(self.parser.tocleanstring(title), Account.TYPE_UNKNOWN)
|
||||||
|
for tr in table.xpath('.//tr'):
|
||||||
|
tds = tr.findall('td')
|
||||||
|
for i in xrange(len(tds)):
|
||||||
|
a = tds[i].find('a')
|
||||||
|
if a is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if a is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
accounts[account.id] = account
|
label = self.parser.tocleanstring(tds[0])
|
||||||
|
balance = self.parser.tocleanstring(tds[-1])
|
||||||
|
|
||||||
|
self._add_account(accounts, a, label, account_type, balance)
|
||||||
|
|
||||||
return accounts.itervalues()
|
return accounts.itervalues()
|
||||||
|
|
||||||
def go_list(self):
|
def go_list(self):
|
||||||
self.browser.select_form(name='main')
|
self.browser.select_form(name='main')
|
||||||
self.browser.set_all_readonly(False)
|
self.browser.set_all_readonly(False)
|
||||||
self.browser['__EVENTTARGET'] = 'Menu_AJAX'
|
|
||||||
self.browser['__EVENTARGUMENT'] = 'CPTSYNT0'
|
self.browser['__EVENTARGUMENT'] = 'CPTSYNT0'
|
||||||
self.browser.controls.append(ClientForm.TextControl('text', 'm_ScriptManager', {'value': ''}))
|
self.browser.controls.append(ClientForm.TextControl('text', 'm_ScriptManager', {'value': ''}))
|
||||||
|
|
||||||
|
# Ugly check to determine if we are on the new or old website.
|
||||||
|
try:
|
||||||
|
self.browser['MM$m_CH$IsMsgInit']
|
||||||
|
except ControlNotFoundError:
|
||||||
|
self.logger.debug('New website')
|
||||||
|
self.browser['__EVENTTARGET'] = 'MM$m_PostBack'
|
||||||
|
self.browser['m_ScriptManager'] = 'MM$m_UpdatePanel|MM$m_PostBack'
|
||||||
|
else:
|
||||||
|
self.logger.debug('Old website')
|
||||||
|
self.browser['__EVENTTARGET'] = 'Menu_AJAX'
|
||||||
self.browser['m_ScriptManager'] = 'm_ScriptManager|Menu_AJAX'
|
self.browser['m_ScriptManager'] = 'm_ScriptManager|Menu_AJAX'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.browser.controls.remove(self.browser.find_control(name='Cartridge$imgbtnMessagerie', type='image'))
|
self.browser.controls.remove(self.browser.find_control(name='Cartridge$imgbtnMessagerie', type='image'))
|
||||||
self.browser.controls.remove(self.browser.find_control(name='MM$m_CH$ButtonImageFondMessagerie', type='image'))
|
self.browser.controls.remove(self.browser.find_control(name='MM$m_CH$ButtonImageFondMessagerie', type='image'))
|
||||||
|
|
@ -156,12 +215,16 @@ class IndexPage(BasePage):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def go_history(self, link_type, id):
|
def go_history(self, info):
|
||||||
self.browser.select_form(name='main')
|
self.browser.select_form(name='main')
|
||||||
self.browser.set_all_readonly(False)
|
self.browser.set_all_readonly(False)
|
||||||
self.browser['__EVENTTARGET'] = 'MM$SYNTHESE'
|
self.browser['__EVENTTARGET'] = 'MM$SYNTHESE'
|
||||||
self.browser['__EVENTARGUMENT'] = 'HISTORIQUE_%s&%s' % (link_type, id)
|
self.browser['__EVENTARGUMENT'] = 'HISTORIQUE_%s' % info['link']
|
||||||
|
try:
|
||||||
self.browser['MM$m_CH$IsMsgInit'] = '0'
|
self.browser['MM$m_CH$IsMsgInit'] = '0'
|
||||||
|
except ControlNotFoundError:
|
||||||
|
# Not available on new website.
|
||||||
|
pass
|
||||||
self.browser.controls.append(ClientForm.TextControl('text', 'm_ScriptManager', {'value': ''}))
|
self.browser.controls.append(ClientForm.TextControl('text', 'm_ScriptManager', {'value': ''}))
|
||||||
self.browser['m_ScriptManager'] = 'MM$m_UpdatePanel|MM$SYNTHESE'
|
self.browser['m_ScriptManager'] = 'MM$m_UpdatePanel|MM$SYNTHESE'
|
||||||
try:
|
try:
|
||||||
|
|
@ -175,12 +238,15 @@ class IndexPage(BasePage):
|
||||||
def get_history(self):
|
def get_history(self):
|
||||||
i = 0
|
i = 0
|
||||||
ignore = False
|
ignore = False
|
||||||
for tr in self.document.xpath('//table[@cellpadding="1"]/tr'):
|
for tr in self.document.xpath('//table[@cellpadding="1"]/tr') + self.document.xpath('//tr[@class="rowClick" or @class="rowHover"]'):
|
||||||
tds = tr.findall('td')
|
tds = tr.findall('td')
|
||||||
|
|
||||||
if len(tds) < 5:
|
if len(tds) < 4:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# if there are more than 4 columns, ignore the first one.
|
||||||
|
i = min(len(tds) - 4, 1)
|
||||||
|
|
||||||
if tr.attrib.get('class', '') == 'DataGridHeader':
|
if tr.attrib.get('class', '') == 'DataGridHeader':
|
||||||
if tds[2].text == u'Titulaire':
|
if tds[2].text == u'Titulaire':
|
||||||
ignore = True
|
ignore = True
|
||||||
|
|
@ -191,10 +257,15 @@ class IndexPage(BasePage):
|
||||||
if ignore:
|
if ignore:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Remove useless details
|
||||||
|
detail = tr.cssselect('div.detail')
|
||||||
|
if len(detail) > 0:
|
||||||
|
detail[0].drop_tree()
|
||||||
|
|
||||||
t = Transaction(i)
|
t = Transaction(i)
|
||||||
|
|
||||||
date = u''.join([txt.strip() for txt in tds[1].itertext()])
|
date = u''.join([txt.strip() for txt in tds[i+0].itertext()])
|
||||||
raw = u' '.join([txt.strip() for txt in tds[2].itertext()])
|
raw = u' '.join([txt.strip() for txt in tds[i+1].itertext()])
|
||||||
debit = u''.join([txt.strip() for txt in tds[-2].itertext()])
|
debit = u''.join([txt.strip() for txt in tds[-2].itertext()])
|
||||||
credit = u''.join([txt.strip() for txt in tds[-1].itertext()])
|
credit = u''.join([txt.strip() for txt in tds[-1].itertext()])
|
||||||
|
|
||||||
|
|
@ -208,14 +279,18 @@ class IndexPage(BasePage):
|
||||||
|
|
||||||
def go_next(self):
|
def go_next(self):
|
||||||
link = self.document.xpath('//a[contains(@id, "lnkSuivante")]')
|
link = self.document.xpath('//a[contains(@id, "lnkSuivante")]')
|
||||||
if len(link) == 0:
|
if len(link) == 0 or 'disabled' in link[0].attrib:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.browser.select_form(name='main')
|
self.browser.select_form(name='main')
|
||||||
self.browser.set_all_readonly(False)
|
self.browser.set_all_readonly(False)
|
||||||
self.browser['__EVENTTARGET'] = 'MM$HISTORIQUE_COMPTE$lnkSuivante'
|
self.browser['__EVENTTARGET'] = 'MM$HISTORIQUE_COMPTE$lnkSuivante'
|
||||||
self.browser['__EVENTARGUMENT'] = ''
|
self.browser['__EVENTARGUMENT'] = ''
|
||||||
|
try:
|
||||||
self.browser['MM$m_CH$IsMsgInit'] = 'N'
|
self.browser['MM$m_CH$IsMsgInit'] = 'N'
|
||||||
|
except ControlNotFoundError:
|
||||||
|
# New website
|
||||||
|
pass
|
||||||
self.browser.controls.append(ClientForm.TextControl('text', 'm_ScriptManager', {'value': ''}))
|
self.browser.controls.append(ClientForm.TextControl('text', 'm_ScriptManager', {'value': ''}))
|
||||||
self.browser['m_ScriptManager'] = 'MM$m_UpdatePanel|MM$HISTORIQUE_COMPTE$lnkSuivante'
|
self.browser['m_ScriptManager'] = 'MM$m_UpdatePanel|MM$HISTORIQUE_COMPTE$lnkSuivante'
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue