weboob-devel/modules/hellobank/browser.py
2013-07-14 19:08:17 +02:00

227 lines
8.3 KiB
Python
Executable file

# -*- coding: utf-8 -*-
# Copyright(C) 2013 Christophe Lampin
# Copyright(C) 2009-2013 Romain Bignon
#
# 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/>.
import urllib
import mechanize
from datetime import datetime
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword, BrowserPasswordExpired
from weboob.capabilities.bank import TransferError, Transfer
from .perso.accounts_list import AccountsList, AccountPrelevement
from .perso.transactions import AccountHistory, AccountComing
from .perso.transfer import TransferPage, TransferConfirmPage, TransferCompletePage
from .perso.login import LoginPage, ConfirmPage, InfoMessagePage
from .perso.messages import MessagePage, MessagesPage
__all__ = ['HelloBank']
class HelloBank(BaseBrowser):
DOMAIN = 'client.hellobank.fr'
PROTOCOL = 'https'
ENCODING = None # refer to the HTML encoding
PAGES = {'.*TableauBord.*': AccountsList,
'.*type=folder.*': AccountHistory,
'.*pageId=mouvementsavenir.*': AccountComing,
'.*NS_AVEDP.*': AccountPrelevement,
'.*NS_VIRDF.*': TransferPage,
'.*NS_VIRDC.*': TransferConfirmPage,
'.*/NS_VIRDA\?stp=(?P<id>\d+).*': TransferCompletePage,
'.*type=homeconnex.*': LoginPage,
'.*layout=HomeConnexion.*': ConfirmPage,
'.*SAF_CHM_VALID.*': ConfirmPage,
'.*Action=DSP_MSG.*': InfoMessagePage,
'.*Messages_recus.*': MessagesPage,
'.*Lire_Message.*': MessagePage,
}
def __init__(self, *args, **kwargs):
BaseBrowser.__init__(self, *args, **kwargs)
def home(self):
self.location('https://client.hellobank.fr/banque/portail/digitale/HomeConnexion?type=homeconnex')
def is_logged(self):
return not self.is_on_page(LoginPage)
def login(self):
assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring)
assert self.password.isdigit()
if not self.is_on_page(LoginPage):
self.home()
self.page.login(self.username, self.password)
self.location('/NSFR?Action=DSP_VGLOBALE', no_login=True)
if self.is_on_page(LoginPage):
raise BrowserIncorrectPassword()
def get_accounts_list(self):
# We have to parse transfer page to get the IBAN numbers
if not self.is_on_page(TransferPage):
now = datetime.now()
self.location('/NS_VIRDF?Origine=DSP_VIR&stp=%s' % now.strftime("%Y%m%d%H%M%S"))
accounts = self.page.get_accounts()
if len(accounts) == 0:
print 'no accounts'
# oops, no accounts? check if we have not exhausted the allowed use
# of this password
for img in self.document.getroot().cssselect('img[align="middle"]'):
if img.attrib.get('alt', '') == 'Changez votre code secret':
raise BrowserPasswordExpired('Your password has expired')
self.location('/NSFR?Action=DSP_VGLOBALE')
return self.page.get_list(accounts)
def get_account(self, id):
assert isinstance(id, basestring)
l = self.get_accounts_list()
for a in l:
if a.id == id:
return a
return None
def get_IBAN_from_account(self, account):
self.go_to_history_page(account)
return self.page.get_IBAN()
def go_to_history_page(self,account):
if account._link_id is None:
return iter([])
if not self.is_on_page(AccountsList):
self.location('/NSFR?Action=DSP_VGLOBALE')
data = {'gt': 'homepage:basic-theme',
'externalIAId': 'IAStatements',
'cboFlowName': 'flow/iastatement',
'contractId': account._link_id,
'groupId': '-2',
'pastOrPendingOperations': 1,
'groupSelected':'-2',
'step': 'STAMENTS',
'pageId': 'releveoperations',
'sendEUD': 'true',
}
self.location('/udc', urllib.urlencode(data))
return None
def go_to_coming_operations_page(self,account):
if account._link_id is None:
return iter([])
if not self.is_on_page(AccountsList):
self.location('/NSFR?Action=DSP_VGLOBALE')
data = {'gt': 'homepage:basic-theme',
'externalIAId': 'IAStatements',
'cboFlowName': 'flow/iastatement',
'contractId': account._link_id,
'groupId': '-2',
'pastOrPendingOperations': 2,
'groupSelected':'-2',
'step': 'STAMENTS',
'pageId': 'mouvementsavenir',
'sendEUD': 'true',
}
self.location('/udc', urllib.urlencode(data))
return None
def iter_history(self, account):
self.go_to_history_page(account)
return self.page.iter_operations()
def iter_coming_operations(self, account):
self.go_to_coming_operations_page(account)
return self.page.iter_coming_operations()
def get_transfer_accounts(self):
if not self.is_on_page(TransferPage):
self.location('/NS_VIRDF')
assert self.is_on_page(TransferPage)
return self.page.get_accounts()
def transfer(self, from_id, to_id, amount, reason=None):
if not self.is_on_page(TransferPage):
self.location('/NS_VIRDF')
# Need to clean HTML before parse it
html = self.response().get_data().replace("<!input", "<input")
response = mechanize.make_response(
html, [("Content-Type", "text/html")],
"https://client.hellobank.fr/NS_VIRDF", 200, "OK")
self.set_response(response)
accounts = self.page.get_accounts()
self.page.transfer(from_id, to_id, amount, reason)
if not self.is_on_page(TransferCompletePage):
raise TransferError('An error occured during transfer')
transfer = Transfer(self.page.get_id())
transfer.amount = amount
transfer.origin = accounts[from_id].label
transfer.recipient = accounts[to_id].label
transfer.date = datetime.now()
return transfer
def messages_page(self):
if not self.is_on_page(MessagesPage):
if not self.is_on_page(AccountsList):
self.location('/NSFR?Action=DSP_VGLOBALE')
self.location(self.page.get_messages_link())
assert self.is_on_page(MessagesPage)
def iter_threads(self):
self.messages_page()
for thread in self.page.iter_threads():
yield thread
def get_thread(self, thread):
self.messages_page()
if not hasattr(thread, '_link_id') or not thread._link_id:
for t in self.iter_threads():
if t.id == thread.id:
thread = t
break
# mimic validerFormulaire() javascript
# yes, it makes no sense
page_id, unread = thread._link_id
self.select_form('listerMessages')
self.form.set_all_readonly(False)
self['identifiant'] = page_id
if len(thread.id):
self['idMessage'] = thread.id
# the JS does this, but it makes us unable to read unread messages
#if unread:
# self['newMsg'] = thread.id
self.submit()
assert self.is_on_page(MessagePage)
thread.root.content = self.page.get_content()
return thread