support professionnal accounts
This commit is contained in:
parent
a51b63148d
commit
9856c27f8c
2 changed files with 137 additions and 38 deletions
|
|
@ -23,7 +23,7 @@ import urllib
|
||||||
|
|
||||||
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
|
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
|
||||||
|
|
||||||
from .pages import LoginPage, AccountsPage, TransactionsPage
|
from .pages import LoginPage, AccountsPage, ProAccountsPage, TransactionsPage, ProTransactionsPage
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['CreditDuNordBrowser']
|
__all__ = ['CreditDuNordBrowser']
|
||||||
|
|
@ -35,8 +35,11 @@ class CreditDuNordBrowser(BaseBrowser):
|
||||||
PAGES = {'https://[^/]+/?': LoginPage,
|
PAGES = {'https://[^/]+/?': LoginPage,
|
||||||
'https://[^/]+/.*\?.*_pageLabel=page_erreur_connexion': LoginPage,
|
'https://[^/]+/.*\?.*_pageLabel=page_erreur_connexion': LoginPage,
|
||||||
'https://[^/]+/vos-comptes/particuliers(\?.*)?': AccountsPage,
|
'https://[^/]+/vos-comptes/particuliers(\?.*)?': AccountsPage,
|
||||||
'https://[^/]+/vos-comptes/.*/transac/.*': TransactionsPage,
|
'https://[^/]+/vos-comptes/.*/transac/particuliers.*': TransactionsPage,
|
||||||
|
'https://[^/]+/vos-comptes/professionnels.*': ProAccountsPage,
|
||||||
|
'https://[^/]+/vos-comptes/.*/transac/professionnels.*': ProTransactionsPage,
|
||||||
}
|
}
|
||||||
|
account_type = 'particuliers'
|
||||||
|
|
||||||
def __init__(self, website, *args, **kwargs):
|
def __init__(self, website, *args, **kwargs):
|
||||||
self.DOMAIN = website
|
self.DOMAIN = website
|
||||||
|
|
@ -47,11 +50,9 @@ class CreditDuNordBrowser(BaseBrowser):
|
||||||
|
|
||||||
def home(self):
|
def home(self):
|
||||||
if self.is_logged():
|
if self.is_logged():
|
||||||
self.location(self.buildurl('/vos-comptes/particuliers'))
|
self.location(self.buildurl('/vos-comptes/%s' % self.account_type))
|
||||||
else:
|
else:
|
||||||
self.login()
|
self.login()
|
||||||
return
|
|
||||||
return self.location(self.buildurl('/vos-comptes/particuliers'))
|
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
assert isinstance(self.username, basestring)
|
assert isinstance(self.username, basestring)
|
||||||
|
|
@ -79,9 +80,13 @@ class CreditDuNordBrowser(BaseBrowser):
|
||||||
if not self.is_logged():
|
if not self.is_logged():
|
||||||
raise BrowserIncorrectPassword()
|
raise BrowserIncorrectPassword()
|
||||||
|
|
||||||
|
m = re.match('https://[^/]+/vos-comptes/(\w+).*', self.page.url)
|
||||||
|
if m:
|
||||||
|
self.account_type = m.group(1)
|
||||||
|
|
||||||
def get_accounts_list(self):
|
def get_accounts_list(self):
|
||||||
if not self.is_on_page(AccountsPage):
|
if not self.is_on_page(AccountsPage):
|
||||||
self.location(self.buildurl('/vos-comptes/particuliers'))
|
self.location(self.buildurl('/vos-comptes/%s' % self.account_type))
|
||||||
return self.page.get_list()
|
return self.page.get_list()
|
||||||
|
|
||||||
def get_account(self, id):
|
def get_account(self, id):
|
||||||
|
|
@ -94,20 +99,12 @@ class CreditDuNordBrowser(BaseBrowser):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def iter_transactions(self, link, link_id, execution, is_coming=None):
|
def iter_transactions(self, link, args, is_coming=None):
|
||||||
if link_id is None:
|
if args is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
event = 'clicDetailCompte'
|
while args is not None:
|
||||||
while 1:
|
self.location(link, urllib.urlencode(args))
|
||||||
data = {'_eventId': event,
|
|
||||||
'_ipc_eventValue': '',
|
|
||||||
'_ipc_fireEvent': '',
|
|
||||||
'deviseAffichee': 'DEVISE',
|
|
||||||
'execution': execution,
|
|
||||||
'idCompteClique': link_id,
|
|
||||||
}
|
|
||||||
self.location(link, urllib.urlencode(data))
|
|
||||||
|
|
||||||
assert self.is_on_page(TransactionsPage)
|
assert self.is_on_page(TransactionsPage)
|
||||||
|
|
||||||
|
|
@ -116,22 +113,17 @@ class CreditDuNordBrowser(BaseBrowser):
|
||||||
for tr in self.page.get_history():
|
for tr in self.page.get_history():
|
||||||
yield tr
|
yield tr
|
||||||
|
|
||||||
is_last = self.page.is_last()
|
|
||||||
if is_last:
|
|
||||||
return
|
|
||||||
|
|
||||||
event = 'clicChangerPageSuivant'
|
|
||||||
execution = self.page.get_execution()
|
|
||||||
is_coming = self.page.is_coming
|
is_coming = self.page.is_coming
|
||||||
|
args = self.page.get_next_args(args)
|
||||||
|
|
||||||
def get_history(self, account):
|
def get_history(self, account):
|
||||||
for tr in self.iter_transactions(account._link, account._link_id, account._execution):
|
for tr in self.iter_transactions(account._link, account._args):
|
||||||
yield tr
|
yield tr
|
||||||
|
|
||||||
for tr in self.get_card_operations(account):
|
for tr in self.get_card_operations(account):
|
||||||
yield tr
|
yield tr
|
||||||
|
|
||||||
def get_card_operations(self, account):
|
def get_card_operations(self, account):
|
||||||
for link_id in account._card_ids:
|
for link_args in account._card_ids:
|
||||||
for tr in self.iter_transactions(account._link, link_id, account._execution, True):
|
for tr in self.iter_transactions(account._link, link_args, True):
|
||||||
yield tr
|
yield tr
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
from urllib import quote
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import re
|
import re
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
|
|
@ -97,14 +98,19 @@ class AccountsPage(CDNBasePage):
|
||||||
a.label = self.parser.tocleanstring(self.parser.parse(fp, self.browser.ENCODING).xpath('//div[@class="libelleCompteTDB"]')[0])
|
a.label = self.parser.tocleanstring(self.parser.parse(fp, self.browser.ENCODING).xpath('//div[@class="libelleCompteTDB"]')[0])
|
||||||
a.balance = Decimal(FrenchTransaction.clean_amount(line[self.COL_BALANCE]))
|
a.balance = Decimal(FrenchTransaction.clean_amount(line[self.COL_BALANCE]))
|
||||||
a._link = self.get_history_link()
|
a._link = self.get_history_link()
|
||||||
a._execution = self.get_execution()
|
|
||||||
if line[self.COL_HISTORY] == 'true':
|
if line[self.COL_HISTORY] == 'true':
|
||||||
a._link_id = line[self.COL_ID]
|
a._args = {'_eventId': 'clicDetailCompte',
|
||||||
|
'_ipc_eventValue': '',
|
||||||
|
'_ipc_fireEvent': '',
|
||||||
|
'deviseAffichee': 'DEVISE',
|
||||||
|
'execution': self.get_execution(),
|
||||||
|
'idCompteClique': line[self.COL_ID],
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
a._link_id = None
|
a._args = None
|
||||||
|
|
||||||
if a.id.find('_CarteVisa') >= 0:
|
if a.id.find('_CarteVisa') >= 0:
|
||||||
accounts[0]._card_ids.append(a._link_id)
|
accounts[0]._card_ids.append(a._args)
|
||||||
if not accounts[0].coming:
|
if not accounts[0].coming:
|
||||||
accounts[0].coming = Decimal('0.0')
|
accounts[0].coming = Decimal('0.0')
|
||||||
accounts[0].coming += a.balance
|
accounts[0].coming += a.balance
|
||||||
|
|
@ -116,6 +122,45 @@ class AccountsPage(CDNBasePage):
|
||||||
return iter(accounts)
|
return iter(accounts)
|
||||||
|
|
||||||
|
|
||||||
|
class ProAccountsPage(AccountsPage):
|
||||||
|
COL_ID = 0
|
||||||
|
COL_BALANCE = 1
|
||||||
|
|
||||||
|
ARGS = ['Banque', 'Agence', 'classement', 'Serie', 'SSCompte', 'Devise', 'CodeDeviseCCB', 'LibelleCompte', 'IntituleCompte', 'Indiceclassement', 'IndiceCompte', 'NomClassement']
|
||||||
|
def params_from_js(self, text):
|
||||||
|
l = []
|
||||||
|
for sub in re.findall("'([^']*)'", text):
|
||||||
|
l.append(sub)
|
||||||
|
|
||||||
|
url = '/vos-comptes/IPT/appmanager/transac/professionnels?_nfpb=true&_windowLabel=portletInstance_18&_pageLabel=page_synthese_v1' + '&_cdnCltUrl=' + "/transacClippe/" + quote(l.pop(0))
|
||||||
|
args = {}
|
||||||
|
|
||||||
|
for i, key in enumerate(self.ARGS):
|
||||||
|
args[key] = l[self.ARGS.index(key)]
|
||||||
|
|
||||||
|
return url, args
|
||||||
|
|
||||||
|
|
||||||
|
def get_list(self):
|
||||||
|
for tr in self.document.xpath('//table[@class="datas"]//tr'):
|
||||||
|
if tr.attrib.get('class', '') == 'entete':
|
||||||
|
continue
|
||||||
|
|
||||||
|
cols = tr.findall('td')
|
||||||
|
|
||||||
|
a = Account()
|
||||||
|
a.id = cols[self.COL_ID].xpath('.//span[@class="right-underline"]')[0].text.strip()
|
||||||
|
a.label = unicode(cols[self.COL_ID].xpath('.//span[@class="left-underline"]')[0].text.strip())
|
||||||
|
balance = self.parser.tocleanstring(cols[self.COL_BALANCE])
|
||||||
|
a.balance = Decimal(FrenchTransaction.clean_amount(balance))
|
||||||
|
a.currency = a.get_currency(balance)
|
||||||
|
a._link, a._args = self.params_from_js(cols[self.COL_ID].find('a').attrib['href'])
|
||||||
|
|
||||||
|
a._card_ids = []
|
||||||
|
|
||||||
|
yield a
|
||||||
|
|
||||||
|
|
||||||
class Transaction(FrenchTransaction):
|
class Transaction(FrenchTransaction):
|
||||||
PATTERNS = [(re.compile(r'^(?P<text>RET DAB \w+ .*?) LE (?P<dd>\d{2})(?P<mm>\d{2})$'),
|
PATTERNS = [(re.compile(r'^(?P<text>RET DAB \w+ .*?) LE (?P<dd>\d{2})(?P<mm>\d{2})$'),
|
||||||
FrenchTransaction.TYPE_WITHDRAWAL),
|
FrenchTransaction.TYPE_WITHDRAWAL),
|
||||||
|
|
@ -143,6 +188,15 @@ class TransactionsPage(CDNBasePage):
|
||||||
|
|
||||||
is_coming = None
|
is_coming = None
|
||||||
|
|
||||||
|
def get_next_args(self, args):
|
||||||
|
if self.is_last():
|
||||||
|
return None
|
||||||
|
|
||||||
|
args['_eventId'] = 'clicChangerPageSuivant'
|
||||||
|
args['execution'] = self.get_execution()
|
||||||
|
args.pop('idCompteClique', None)
|
||||||
|
return args
|
||||||
|
|
||||||
def is_last(self):
|
def is_last(self):
|
||||||
for script in self.document.xpath('//script'):
|
for script in self.document.xpath('//script'):
|
||||||
txt = script.text
|
txt = script.text
|
||||||
|
|
@ -154,11 +208,28 @@ class TransactionsPage(CDNBasePage):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def set_coming(self, t):
|
||||||
|
if self.is_coming is not None and t.raw.startswith('TOTAL DES') and t.amount > 0:
|
||||||
|
# ignore card credit and next transactions are already debited
|
||||||
|
self.is_coming = False
|
||||||
|
return True
|
||||||
|
if self.is_coming is None and t.raw.startswith('ACHATS CARTE'):
|
||||||
|
# Ignore card debit
|
||||||
|
return True
|
||||||
|
|
||||||
|
t._is_coming = bool(self.is_coming)
|
||||||
|
return False
|
||||||
|
|
||||||
def get_history(self):
|
def get_history(self):
|
||||||
txt = self.get_from_js('ListeMvts_data = new Array(', ');')
|
txt = self.get_from_js('ListeMvts_data = new Array(', ');')
|
||||||
|
|
||||||
if txt is None:
|
if txt is None:
|
||||||
raise BrokenPageError('Unable to find transactions list in scripts')
|
no_trans = self.get_from_js('js_noMvts = new Ext.Panel(', ')')
|
||||||
|
if no_trans is not None:
|
||||||
|
# there is no transactions for this account, this is normal.
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise BrokenPageError('Unable to find transactions list in scripts')
|
||||||
|
|
||||||
data = json.loads('[%s]' % txt.replace('"', '\\"').replace("'", '"'))
|
data = json.loads('[%s]' % txt.replace('"', '\\"').replace("'", '"'))
|
||||||
|
|
||||||
|
|
@ -175,13 +246,49 @@ class TransactionsPage(CDNBasePage):
|
||||||
t.parse(date, raw)
|
t.parse(date, raw)
|
||||||
t.set_amount(line[self.COL_VALUE])
|
t.set_amount(line[self.COL_VALUE])
|
||||||
|
|
||||||
if self.is_coming is not None and raw.startswith('TOTAL DES') and t.amount > 0:
|
if self.set_coming(t):
|
||||||
# ignore card credit and next transactions are already debited
|
continue
|
||||||
self.is_coming = False
|
|
||||||
continue
|
yield t
|
||||||
if self.is_coming is None and raw.startswith('ACHATS CARTE'):
|
|
||||||
# Ignore card debit
|
class ProTransactionsPage(TransactionsPage):
|
||||||
|
def get_next_args(self, args):
|
||||||
|
txt = self.get_from_js('myPage.setPiedPage(oNavSuivantPrec_1(', ')')
|
||||||
|
|
||||||
|
if txt is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
l = txt.split(',')
|
||||||
|
if int(l[4]) <= 40:
|
||||||
|
return None
|
||||||
|
|
||||||
|
args['PageDemandee'] = int(args.get('PageDemandee', 1)) + 1
|
||||||
|
return args
|
||||||
|
|
||||||
|
def parse_transactions(self):
|
||||||
|
transactions = {}
|
||||||
|
for script in self.document.xpath('//script'):
|
||||||
|
txt = script.text
|
||||||
|
if txt is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for i, key, value in re.findall('listeopecv\[(\d+)\]\[\'(\w+)\'\]="(.*)";', txt):
|
||||||
|
i = int(i)
|
||||||
|
if not i in transactions:
|
||||||
|
transactions[i] = {}
|
||||||
|
transactions[i][key] = value
|
||||||
|
|
||||||
|
return transactions.iteritems()
|
||||||
|
|
||||||
|
def get_history(self):
|
||||||
|
for i, tr in self.parse_transactions():
|
||||||
|
t = Transaction(i)
|
||||||
|
date = tr['date']
|
||||||
|
raw = self.parser.strip('<p>%s</p>' % (' '.join([tr['typeope'], tr['LibComp']])))
|
||||||
|
t.parse(date, raw)
|
||||||
|
t.set_amount(tr['mont'])
|
||||||
|
|
||||||
|
if self.set_coming(t):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
t._is_coming = bool(self.is_coming)
|
|
||||||
yield t
|
yield t
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue