new bnporc backend
This commit is contained in:
parent
655bc10e3e
commit
69bb9e4826
8 changed files with 498 additions and 0 deletions
21
weboob/backends/bnporc/__init__.py
Normal file
21
weboob/backends/bnporc/__init__.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Copyright(C) 2010 Romain Bignon
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
|
||||
from .backend import BNPorcBackend
|
||||
67
weboob/backends/bnporc/backend.py
Normal file
67
weboob/backends/bnporc/backend.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Copyright(C) 2010 Romain Bignon
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
|
||||
from weboob.backend import Backend
|
||||
from weboob.capabilities.bank import ICapBank, AccountNotFound
|
||||
|
||||
from .browser import BNPorc
|
||||
|
||||
class BNPorcBackend(Backend, ICapBank):
|
||||
NAME = 'bnporc'
|
||||
MAINTAINER = 'Romain Bignon'
|
||||
EMAIL = 'romain@peerfuse.org'
|
||||
VERSION = '1.0'
|
||||
LICENSE = 'GPLv3'
|
||||
|
||||
CONFIG = {'login': Backend.ConfigField(description='Account ID'),
|
||||
'password': Backend.ConfigField(description='Password of account', is_masked=True)
|
||||
}
|
||||
browser = None
|
||||
|
||||
def need_browser(func):
|
||||
def inner(self, *args, **kwargs):
|
||||
if not self.browser:
|
||||
self.browser = BNPorc(self.config['login'], self.config['password'])
|
||||
|
||||
return func(self, *args, **kwargs)
|
||||
return inner
|
||||
|
||||
@need_browser
|
||||
def iter_accounts(self):
|
||||
for account in self.browser.get_accounts_list():
|
||||
yield account
|
||||
|
||||
@need_browser
|
||||
def get_account(self, _id):
|
||||
try:
|
||||
_id = long(_id)
|
||||
except ValueError:
|
||||
raise AccountNotFound()
|
||||
else:
|
||||
account = self.browser.get_account(_id)
|
||||
if account:
|
||||
return account
|
||||
else:
|
||||
raise AccountNotFound()
|
||||
|
||||
@need_browser
|
||||
def iter_operations(self, account):
|
||||
for coming in self.browser.get_coming_operations(account):
|
||||
yield coming
|
||||
98
weboob/backends/bnporc/browser.py
Normal file
98
weboob/backends/bnporc/browser.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Copyright(C) 2009-2010 Romain Bignon
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
|
||||
from cStringIO import StringIO
|
||||
|
||||
from weboob.tools.browser import Browser, BrowserIncorrectPassword
|
||||
from weboob.tools.parser import StandardParser
|
||||
from weboob.backends.bnporc import pages
|
||||
|
||||
# Parser
|
||||
class BNParser(StandardParser):
|
||||
def parse(self, data, encoding):
|
||||
s = data.read()
|
||||
s = s.replace('<?Pub Caret>', '')
|
||||
data = StringIO(s)
|
||||
return StandardParser.parse(self, data)
|
||||
|
||||
# Browser
|
||||
class BNPorc(Browser):
|
||||
DOMAIN = 'www.secure.bnpparibas.net'
|
||||
PROTOCOL = 'https'
|
||||
ENCODING = None # refer to the HTML encoding
|
||||
PAGES = {'.*identifiant=DOSSIER_Releves_D_Operation.*': pages.AccountsList,
|
||||
'.*identifiant=DSP_HISTOCPT.*': pages.AccountHistory,
|
||||
'.*NS_AVEEC.*': pages.AccountComing,
|
||||
'.*NS_AVEDP.*': pages.AccountPrelevement,
|
||||
'.*Action=DSP_VGLOBALE.*': pages.LoginPage,
|
||||
'.*type=homeconnex.*': pages.LoginPage,
|
||||
'.*layout=HomeConnexion': pages.ConfirmPage,
|
||||
}
|
||||
|
||||
is_logging = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['parser'] = BNParser
|
||||
Browser.__init__(self, *args, **kwargs)
|
||||
|
||||
def home(self):
|
||||
self.location('https://www.secure.bnpparibas.net/banque/portail/particulier/HomeConnexion?type=homeconnex')
|
||||
|
||||
def is_logged(self):
|
||||
return not self.is_on_page(pages.LoginPage) or self.is_logging
|
||||
|
||||
def login(self):
|
||||
assert isinstance(self.username, (str,unicode))
|
||||
assert isinstance(self.password, (str,unicode))
|
||||
assert self.password.isdigit()
|
||||
|
||||
if not self.is_on_page(pages.LoginPage):
|
||||
self.location('https://www.secure.bnpparibas.net/banque/portail/particulier/HomeConnexion?type=homeconnex')
|
||||
|
||||
self.is_logging = True
|
||||
self.page.login(self.username, self.password)
|
||||
self.location('/NSFR?Action=DSP_VGLOBALE')
|
||||
|
||||
if self.is_on_page(pages.LoginPage):
|
||||
raise BrowserIncorrectPassword()
|
||||
self.is_logging = False
|
||||
|
||||
def get_accounts_list(self):
|
||||
if not self.is_on_page(pages.AccountsList):
|
||||
self.location('/NSFR?Action=DSP_VGLOBALE')
|
||||
return self.page.get_list()
|
||||
|
||||
def get_account(self, id):
|
||||
assert isinstance(id, (int, long))
|
||||
|
||||
if not self.is_on_page(pages.AccountsList):
|
||||
self.location('/NSFR?Action=DSP_VGLOBALE')
|
||||
|
||||
l = self.page.get_list()
|
||||
for a in l:
|
||||
if a.id == id:
|
||||
return a
|
||||
|
||||
return None
|
||||
|
||||
def get_coming_operations(self, account):
|
||||
if not self.is_on_page(pages.AccountComing) or self.page.account.id != account.id:
|
||||
self.location('/NS_AVEEC?ch4=%s' % account.link_id)
|
||||
return self.page.get_operations()
|
||||
122
weboob/backends/bnporc/captcha.py
Normal file
122
weboob/backends/bnporc/captcha.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Copyright(C) 2009-2010 Romain Bignon
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import sys
|
||||
import Image
|
||||
|
||||
class TileError(Exception):
|
||||
def __init__(self, msg, tile = None):
|
||||
Exception.__init__(self, msg)
|
||||
self.tile = tile
|
||||
|
||||
class Captcha:
|
||||
def __init__(self, file):
|
||||
self.inim = Image.open(file)
|
||||
self.nx,self.ny = self.inim.size
|
||||
self.inmat = self.inim.load()
|
||||
self.map = {}
|
||||
|
||||
self.tiles = [[Tile(x+5*y+1) for y in xrange(5)] for x in xrange(5)]
|
||||
|
||||
def __getitem__(self, (x, y)):
|
||||
return self.inmat[x % self.nx, y % self.ny]
|
||||
|
||||
def all_coords(self):
|
||||
for y in xrange(self.ny):
|
||||
for x in xrange(self.nx):
|
||||
yield x, y
|
||||
|
||||
def get_codes(self, code):
|
||||
s = ''
|
||||
for c in code:
|
||||
s += '%02d' % self.map[int(c)].id
|
||||
return s
|
||||
|
||||
def build_tiles(self):
|
||||
y = 5
|
||||
ty = 0
|
||||
while y < self.ny:
|
||||
x = 6
|
||||
tx = 0
|
||||
while x < self.nx:
|
||||
if self[x,y] == 8:
|
||||
tile = self.tiles[tx][ty]
|
||||
tile.valid = True
|
||||
yy = y
|
||||
while not self[x,yy] in (3,7):
|
||||
l = []
|
||||
tile.map.append(l)
|
||||
xx = x
|
||||
while not self[xx,yy] in (3,7):
|
||||
l.append(self[xx,yy])
|
||||
xx += 1
|
||||
|
||||
yy += 1
|
||||
|
||||
self.map[tile.getNum()] = tile
|
||||
|
||||
x += 26
|
||||
tx += 1
|
||||
|
||||
y += 25
|
||||
ty += 1
|
||||
|
||||
class Tile:
|
||||
hash = {'b2d25ae11efaaaec6dd6a4c00f0dfc29': 1,
|
||||
'600873fa288e75ca6cca092ae95bf129': 2,
|
||||
'da24ac28930feee169adcbc9bad4acaf': 3,
|
||||
'76294dec2a3c6a7b8d9fcc7a116d1d4f': 4,
|
||||
'd9531059e3834b6b8a97e29417a47dec': 5,
|
||||
'8ba0c0cfe5e64d6b4afb1aa6f3612c1a': 6,
|
||||
'19e0120231e7a9cf4544f96d8c388c8a': 7,
|
||||
'83d8ad340156cb7f5c1e64454b66c773': 8,
|
||||
'5ee8648d77eeb3e0979f6e59b2fbe66a': 9,
|
||||
'3f3fb79bf61ebad096e05287119169df': 0
|
||||
}
|
||||
|
||||
def __init__(self, _id):
|
||||
self.id = _id
|
||||
self.valid = False
|
||||
self.map = []
|
||||
|
||||
def __repr__(self):
|
||||
return "<Tile(%02d) valid=%s>" % (self.id, self.valid)
|
||||
|
||||
def checksum(self):
|
||||
s = ''
|
||||
for pxls in self.map:
|
||||
for pxl in pxls:
|
||||
s += '%02d' % pxl
|
||||
return hashlib.md5(s).hexdigest()
|
||||
|
||||
def getNum(self):
|
||||
sum = self.checksum()
|
||||
try:
|
||||
return self.hash[sum]
|
||||
except KeyError:
|
||||
raise TileError('Tile not found', self)
|
||||
|
||||
def display(self):
|
||||
for pxls in self.map:
|
||||
for pxl in pxls:
|
||||
sys.stdout.write('%02d' % pxl)
|
||||
sys.stdout.write('\n')
|
||||
|
||||
26
weboob/backends/bnporc/pages/__init__.py
Normal file
26
weboob/backends/bnporc/pages/__init__.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Copyright(C) 2009-2010 Romain Bignon
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
|
||||
from .accounts_list import AccountsList
|
||||
from .account_coming import AccountComing
|
||||
from .login import LoginPage, ConfirmPage
|
||||
|
||||
class AccountHistory(AccountsList): pass
|
||||
class AccountPrelevement(AccountsList): pass
|
||||
51
weboob/backends/bnporc/pages/account_coming.py
Normal file
51
weboob/backends/bnporc/pages/account_coming.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Copyright(C) 2009-2010 Romain Bignon
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
|
||||
from weboob.tools.browser import BasePage
|
||||
from weboob.capabilities.bank import Operation
|
||||
|
||||
class AccountComing(BasePage):
|
||||
|
||||
def loaded(self):
|
||||
self.operations = []
|
||||
|
||||
for tr in self.document.getiterator('tr'):
|
||||
if tr.attrib.get('class', '') == 'hdoc1' or tr.attrib.get('class', '') == 'hdotc1':
|
||||
operation = Operation()
|
||||
tds = tr.findall('td')
|
||||
if len(tds) != 3:
|
||||
continue
|
||||
date = tds[0].getchildren()[0].attrib.get('name', '')
|
||||
label = u''
|
||||
label += tds[1].text
|
||||
for child in tds[1].getchildren():
|
||||
if child.text: label += child.text
|
||||
if child.tail: label += child.tail
|
||||
label += tds[1].tail
|
||||
label = label.strip()
|
||||
amount = tds[2].text.replace('.','').replace(',','.')
|
||||
|
||||
operation.setDate(date)
|
||||
operation.setLabel(label)
|
||||
operation.setAmount(float(amount))
|
||||
self.operations.append(operation)
|
||||
|
||||
def get_operations(self):
|
||||
return self.operations
|
||||
61
weboob/backends/bnporc/pages/accounts_list.py
Normal file
61
weboob/backends/bnporc/pages/accounts_list.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Copyright(C) 2009-2010 Romain Bignon
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from weboob.capabilities.bank import Account
|
||||
from weboob.tools.browser import BasePage
|
||||
from weboob.tools.parser import tostring
|
||||
|
||||
class AccountsList(BasePage):
|
||||
LINKID_REGEXP = re.compile(".*ch4=(\w+).*")
|
||||
|
||||
def loaded(self):
|
||||
pass
|
||||
|
||||
def get_list(self):
|
||||
l = []
|
||||
for tr in self.document.getiterator('tr'):
|
||||
if tr.attrib.get('class', '') == 'comptes':
|
||||
account = Account()
|
||||
for td in tr.getiterator('td'):
|
||||
if td.attrib.get('headers', '').startswith('Numero_'):
|
||||
id = td.text
|
||||
account.setID(long(''.join(id.split(' '))))
|
||||
elif td.attrib.get('headers', '').startswith('Libelle_'):
|
||||
a = td.findall('a')
|
||||
label = unicode(a[0].text)
|
||||
account.setLabel(label)
|
||||
m = self.LINKID_REGEXP.match(a[0].attrib.get('href', ''))
|
||||
if m:
|
||||
account.setLinkID(m.group(1))
|
||||
elif td.attrib.get('headers', '').startswith('Solde'):
|
||||
a = td.findall('a')
|
||||
balance = a[0].text
|
||||
balance = balance.replace('.','').replace(',','.')
|
||||
account.setBalance(float(balance))
|
||||
elif td.attrib.get('headers', '').startswith('Avenir'):
|
||||
a = td.findall('a')
|
||||
coming = a[0].text
|
||||
coming = coming.replace('.','').replace(',','.')
|
||||
account.setComing(float(coming))
|
||||
|
||||
l.append(account)
|
||||
return l
|
||||
52
weboob/backends/bnporc/pages/login.py
Normal file
52
weboob/backends/bnporc/pages/login.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Copyright(C) 2009-2010 Romain Bignon
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
|
||||
import ClientForm
|
||||
import sys
|
||||
|
||||
from weboob.tools.browser import BasePage
|
||||
from weboob.backends.bnporc.captcha import Captcha, TileError
|
||||
|
||||
class LoginPage(BasePage):
|
||||
def loaded(self):
|
||||
pass
|
||||
|
||||
def login(self, login, password):
|
||||
img = Captcha(self.browser.openurl('/NSImgGrille'))
|
||||
self.browser.back()
|
||||
|
||||
try:
|
||||
img.build_tiles()
|
||||
except TileError, err:
|
||||
print >>sys.stderr, "Error: %s" % err
|
||||
if err.tile:
|
||||
err.tile.display()
|
||||
|
||||
self.browser.select_form('logincanalnet')
|
||||
# HACK because of fucking malformed HTML, the field isn't recognized by mechanize.
|
||||
self.browser.controls.append(ClientForm.TextControl('text', 'ch1', {'value': ''}))
|
||||
self.browser.set_all_readonly(False)
|
||||
|
||||
self.browser['ch1'] = login
|
||||
self.browser['ch5'] = img.get_codes(password)
|
||||
self.browser.submit()
|
||||
|
||||
class ConfirmPage(BasePage):
|
||||
pass
|
||||
Loading…
Add table
Add a link
Reference in a new issue