From 6e7d12cba98dba3ab38a158c364da585cdecee25 Mon Sep 17 00:00:00 2001 From: sinopsysHK Date: Wed, 20 Nov 2019 14:08:22 +0000 Subject: [PATCH] [hsbchk] create new HSBC boobank module dedicated to HK accounts Despite looking close to French web site, it Hong Kong web site is completely different and need its own support. So far login, getting accounts and account history are working. There is no such "coming" entries in Hong Kong as all entries are recorded alnost in real time even for credit cards which are deferred toward parent account but real time toward own account. Profile might be added later. There is no support yet for investments, loans and deposits as I have no mean to test. There is an enhancement that could be considered: enrich entries from FPS (Fast Payment Service) additional details. Not yet supported --- modules/hsbchk/__init__.py | 3 + modules/hsbchk/browser.py | 165 +++++++++++++++++++++ modules/hsbchk/favicon.png | Bin 0 -> 2106 bytes modules/hsbchk/module.py | 67 +++++++++ modules/hsbchk/pages/__init__.py | 0 modules/hsbchk/pages/account_pages.py | 205 ++++++++++++++++++++++++++ modules/hsbchk/pages/login.py | 84 +++++++++++ modules/hsbchk/sbrowser.py | 146 ++++++++++++++++++ modules/hsbchk/test.py | 38 +++++ 9 files changed, 708 insertions(+) create mode 100644 modules/hsbchk/__init__.py create mode 100755 modules/hsbchk/browser.py create mode 100644 modules/hsbchk/favicon.png create mode 100755 modules/hsbchk/module.py create mode 100644 modules/hsbchk/pages/__init__.py create mode 100755 modules/hsbchk/pages/account_pages.py create mode 100755 modules/hsbchk/pages/login.py create mode 100755 modules/hsbchk/sbrowser.py create mode 100644 modules/hsbchk/test.py diff --git a/modules/hsbchk/__init__.py b/modules/hsbchk/__init__.py new file mode 100644 index 0000000000..a34dd8936f --- /dev/null +++ b/modules/hsbchk/__init__.py @@ -0,0 +1,3 @@ +from .module import HSBCHKModule + +__all__ = ['HSBCHKModule'] diff --git a/modules/hsbchk/browser.py b/modules/hsbchk/browser.py new file mode 100755 index 0000000000..bb3b017ddc --- /dev/null +++ b/modules/hsbchk/browser.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012-2013 Romain Bignon +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +from datetime import timedelta, date, datetime +from dateutil import parser + +from weboob.exceptions import NoAccountsException +from weboob.browser import PagesBrowser, URL, need_login, StatesMixin +from weboob.browser.selenium import SubSeleniumMixin +from weboob.browser.exceptions import LoggedOut, ClientError + +from .pages.account_pages import ( + OtherPage, JsonAccSum, JsonAccHist +) + +from .sbrowser import LoginBrowser + +__all__ = ['HSBCHK'] + + +class HSBCHK(StatesMixin, SubSeleniumMixin, PagesBrowser): + BASEURL = 'https://www.services.online-banking.hsbc.com.hk/gpib/group/gpib/cmn/layouts/default.html?uid=dashboard' + + STATE_DURATION = 15 + + app_gone = False + + acc_summary = URL(r'https://www.services.online-banking.hsbc.com.hk/gpib/channel/proxy/accountDataSvc/rtrvAcctSumm', JsonAccSum) + acc_history = URL('https://www.services.online-banking.hsbc.com.hk/gpib/channel/proxy/accountDataSvc/rtrvTxnSumm', JsonAccHist) + + # catch-all + other_page = URL( + r' https://www.services.online-banking.hsbc.com.hk/gpib/systemErrorRedirect.html.*', + OtherPage) + + __states__ = ('auth_token', 'logged', 'selenium_state') + + def __init__(self, username, password, secret, *args, **kwargs): + super(HSBCHK, self).__init__(*args, **kwargs) + # accounts index changes at each session + self.accounts_dict_idx = None + self.username = username + self.password = password + self.secret = secret + self.logged = False + self.auth_token = None + + def create_selenium_browser(self): + dirname = self.responses_dirname + if dirname: + dirname += '/selenium' + + return LoginBrowser( + self.username, + self.password, + self.secret, + logger=self.logger, + responses_dirname=dirname, + proxy=self.PROXIES + ) + + def load_selenium_session(self, selenium): + super(HSBCHK, self).load_selenium_session(selenium) + self.location( + selenium.url, + referrer="https://www.security.online-banking.hsbc.com.hk/gsa/SaaS30Resource/" + ) + + def load_state(self, state): + if ('expire' in state and parser.parse(state['expire']) > datetime.now()) or state.get('auth_token'): + return super(HSBCHK, self).load_state(state) + + def open(self, *args, **kwargs): + try: + return super(HSBCHK, self).open(*args, **kwargs) + except ClientError as e: + if e.response.status_code == 401: + self.auth_token = None + self.logged = False + self.session.cookies.clear() + raise LoggedOut() + if e.response.status_code == 409: + raise NoAccountsException() + raise + + def do_login(self): + self.auth_token = None + self.logger.debug("currrent state is Logged:%s", self.logged) + super(HSBCHK, self).do_login() + self.auth_token = self.session.cookies.get('SYNC_TOKEN') + self.logged = True + + @need_login + def iter_accounts(self): + # on new session initialize accounts dict + if not self.accounts_dict_idx: + self.accounts_dict_idx = dict() + + self.update_header() + jq = {"accountSummaryFilter":{"txnTypCdes":[],"entityCdes":[{"ctryCde":"HK","grpMmbr":"HBAP"}]}} + for a in self.acc_summary.go(json = jq).iter_accounts(): + self.accounts_dict_idx[a.id] = a + yield a + + @need_login + def get_history(self, account, coming=False, retry_li=True): + if not self.accounts_dict_idx: + self.iter_accounts() + + self.update_header() + + today = date.today() + fromdate = today - timedelta(100) + jq = { + "retreiveTxnSummaryFilter": { + "txnDatRnge": { + "fromDate": fromdate.isoformat(), + "toDate": today.isoformat() + }, + "numOfRec": -1, + "txnAmtRnge": None, + "txnHistType": None # "U" + }, + "acctIdr": { + "acctIndex": self.accounts_dict_idx[account.id]._idx, + "entProdTypCde": account._entProdTypCde, + "entProdCatCde": account._entProdCatCde + }, + "pagingInfo": { + "startDetail": None, + "pagingDirectionCode": "PD" + }, + "extensions": None + } + try: + self.acc_history.go(json = jq) + except NoAccountsException: + return [] + return self.page.iter_history() + + def update_header(self): + self.session.headers.update({ + "Origin":"https://www.services.online-banking.hsbc.com.hk", + "Referer":"https://www.services.online-banking.hsbc.com.hk/gpib/group/gpib/cmn/layouts/default.html?uid=dashboard", + "Content-type":"application/json", + "X-HDR-Synchronizer-Token": self.session.cookies.get('SYNC_TOKEN') + }) diff --git a/modules/hsbchk/favicon.png b/modules/hsbchk/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..d9c389378edb506f2f54118cac615b5c868830e2 GIT binary patch literal 2106 zcmV-A2*vk_P)Px#24YJ`L;%$Q0002ER!dd@000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyV# z4-+MQJzy&U000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000M-NklhOZV;L zp5b8}jg10O?}gvy8UVqlEl_^!7zJzA01)czc90IpaJ);M}>J zKiYv0y-;!60$_GJDL;N(ivQcS_uh;D^-kauFZ}AZ1He*If$P&xCv6_j?B~eycHOUm z{kZ@^R{+XeTEcm&R~OFxyUF307mI-9z!5K0-xdHk8XA1o6)P+%FLMGP8_5J+04UkE zZF2GM-S!MGcLGO^VuGdsO6oPjiy>@L$iZw0LpgmWXYaASr*|! zwsvc#7)7-!U;OH;8gwB9Ef!A`#VD$AKI@gA@n68~bZT*{(rg1`S^&&WC(h<(&77Z+ z!yZpY;X~`}kQRWFEnCPW)uqh@^Gjl-Ba{S$!(OQKLV;!g=;b^>bZji6-2+&XD8@oy z7b+XANdYK+?KMi?(5G`P`TYdj+cOR!F}o9iPy*~#vmHcWYBo1b6mQ;4*^V9D!b3(p2!`ZYkz|qh!Y`yP3!#Hg7c(l?2sYcpHs|fTg{pcfU@$TKF zf+(t{bfS-|<3?vuU0p^+kst^OKT1Y;V1UTrpizV?1rB)OnYlVa zw7>L{SnaPO-vx<=M(Pi zB;41B#pS}bY83#1)29jd^|9cghsdv~!CY2`V6`F#gGhk@M59Q70HW23n4gd2_apiJ z2sRsn)k<_^gy4k>OddHxeoYOg;$oscAHnwaS%BnKS7Tqd4q?FpB7J?Bmn{QXCUE8q z;l93^c@~!o%kt$jw#I{Cak&KBs#TbNYQZ@oLqi|-x3#VHz5jlb8@dE9T#5yYWGHG* z53|cf`RAWw^?1ky`=(l3>3-yqZlG2K{*^>k0SxJ3fT_3`tJ|Fu=i|nmz$OuB2D*}n zjK%11ejqY9h%8C-1X7~Tkt|87vP&uf1`rt?MU`(aj%DfO79-ej6 zWK0lDCInGbm0gl$D*!nZVz{Y^>#eP`g-{HH=Ye6P_!pg=G?eeMB+>o&QG&Bi$|%i$ZRPyhRRYilTFIUHlc?}1Y(0%N-3#Hu82gnN69D;)*F z(p6RUOWP^f{=6a1M@C0K?hu4veASz_A>y8Pd@ry*3pS+A z#V1ed?tJk3?^}NKc{FzJSUKowA%L^!1?L=sPoL^OAP;^4vm zbX8UTMrom?stk?+@mVZW45ipsieg6K+ix{>e)8zi|6QrCU#;evjQRj`(q%%q52Rh* zsg@SaoDZBoKiK=^lRsBmVrTq%JT6@xPeOY+-F66k_njur-#B}A{7OSZM(w4n4oJk( z5527W46H*dal`N0Pl#_M}c1&hKoSplpA{sO4dxcB;em=-R~OiO(E zp%3@)j6`YUF(yge+SO#t?(ue9Oxf? z>#Y@9^b|D(z&))G{sfHZr3EGr9s2P~W8+e-Y}Wj&a4b7KqqK$yb#^MgxBu9&3%yT2 zeYcj~WL*wRTKQ_Ype#}(Z(bouhtffrx=h1Ry}c3eKLqWmLpA)6W^IyUATc;SVT z1S>D!vH;AH&QAbsi42a;XTYpEayT69t*x~u$n&xmY21*1nu-O-O+f!VHF)MsN^LUo z_S;syoHq=BxcZ?HxS>=IPaHhRwRhgp&3Qush>d^7v_deQ91af*aHYO}?)yUtjob8w z0^kNeumji*1a3A)rcTW+DnmUz^gs7pBIh-?H)rprVuuz^0^*APek@CtO!GS$rDx3= zrcRvD-T7Q>fp|8w#xjExy>@Ndc}Zey*RHwFNA$GQL=ML*(`R^!gB>0BguANIV+(@_ga7~l07*qoM6N<$f-0Ke. + + +#from weboob.capabilities.bank import CapBankWealth, AccountNotFound +from weboob.capabilities.bank import CapBank, AccountNotFound +from weboob.capabilities.base import find_object +from weboob.tools.backend import Module, BackendConfig +from weboob.tools.value import ValueBackendPassword +from .browser import HSBCHK + + +__all__ = ['HSBCHKModule'] + + +class HSBCHKModule(Module, CapBank): + NAME = 'hsbchk' + MAINTAINER = u'sinopsysHK' + EMAIL = 'sinofwd@gmail.com' + VERSION = '1.6' + LICENSE = 'LGPLv3+' + DESCRIPTION = 'HSBC Hong Kong' + CONFIG = BackendConfig(ValueBackendPassword('login', label='User identifier', masked=False), + ValueBackendPassword('password', label='Password'), + ValueBackendPassword('secret', label=u'Memorable answer')) + BROWSER = HSBCHK + + def create_default_browser(self): + return self.create_browser( + self.config['login'].get(), + self.config['password'].get(), + self.config['secret'].get() + ) + + def iter_accounts(self): + for account in self.browser.iter_accounts(): + yield account + + def get_account(self, _id): + return find_object(self.browser.iter_accounts(), id=_id, error=AccountNotFound) + + def iter_history(self, account): + for tr in self.browser.get_history(account): + yield tr + + def iter_investment(self, account): + raise NotImplemented + + def iter_coming(self, account): + # No coming entries on HSBC HK + return [] diff --git a/modules/hsbchk/pages/__init__.py b/modules/hsbchk/pages/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/hsbchk/pages/account_pages.py b/modules/hsbchk/pages/account_pages.py new file mode 100755 index 0000000000..2e5bc308b5 --- /dev/null +++ b/modules/hsbchk/pages/account_pages.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010-2012 Julien Veyssier +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +import re +from decimal import Decimal +import requests +import json + +from weboob.browser.elements import DictElement, ItemElement, method +from weboob.browser.filters.json import Dict +from weboob.browser.filters.standard import ( + CleanDecimal, CleanText, Date +) +from weboob.browser.pages import HTMLPage, LoggedPage, pagination, JsonPage +from weboob.capabilities.bank import Account +from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword +from weboob.tools.capabilities.bank.transactions import FrenchTransaction + + +class Transaction(FrenchTransaction): + PATTERNS = [ + (re.compile(r'^PAYMENT - THANK YOU'), FrenchTransaction.TYPE_CARD_SUMMARY), + (re.compile(r'^CREDIT CARD PAYMENT (?P.*)'), FrenchTransaction.TYPE_CARD_SUMMARY), + (re.compile(r'^CREDIT INTEREST'), FrenchTransaction.TYPE_BANK), + (re.compile(r'DEBIT INTEREST'), FrenchTransaction.TYPE_BANK), + (re.compile(r'^UNAUTHORIZED OD CHARGE'), FrenchTransaction.TYPE_BANK), + (re.compile(r'^ATM WITHDRAWAL\ *\((?P
\d{2})(?P\w{3})(?P\d{2})\)'), FrenchTransaction.TYPE_WITHDRAWAL), + (re.compile(r'^POS CUP\ *\((?P
\d{2})(?P\w{3})(?P\d{2})\)\ *(?P.*)'), FrenchTransaction.TYPE_CARD), + (re.compile(r'^EPS\d*\ *\((?P
\d{2})(?P\w{3})(?P\d{2})\)\ *(?P.*)'), FrenchTransaction.TYPE_CARD), + (re.compile(r'^CR TO (?P.*)\((?P
\d{2})(?P\w{3})(?P\d{2})\)'), FrenchTransaction.TYPE_TRANSFER), + (re.compile(r'^FROM (?P.*)\((?P
\d{2})(?P\w{3})(?P\d{2})\)'), FrenchTransaction.TYPE_TRANSFER), + ] + + +class JsonBasePage(JsonPage): + + def on_load(self): + coderet = Dict('responseInfo/reasons/0/code')(self.doc) + conditions = ( + coderet == '000', + ) + assert any(conditions), 'Error %s is not handled yet' % coderet + + +class JsonAccSum(LoggedPage, JsonBasePage): + + PRODUCT_TO_TYPE = { + u"SAV": Account.TYPE_SAVINGS, + u"CUR": Account.TYPE_CHECKING, + u"TD": Account.TYPE_DEPOSIT, + u"INV": Account.TYPE_MARKET, + u"CC": Account.TYPE_CARD, + } + LABEL = { + u"SAV": "Savings", + u"CUR": "Current", + u"TD": "Time deposit", + u"INV": "Investment", + u"CC": "Credit card", + } + + def get_acc_dict(self, json): + acc_dict = {} + if json.get('hasAcctDetails'): + acc_dict = { + 'bank_name': 'HSBC HK', + 'id': '%s-%s-%s' % ( + json.get('displyID'), + json.get('prodCatCde'), + json.get('ldgrBal').get('ccy') + ), + '_idx': json.get('acctIndex'), + '_entProdCatCde': json.get('entProdCatCde'), + '_entProdTypCde': json.get('entProdTypCde'), + 'number': json.get('displyID'), + 'type': self.PRODUCT_TO_TYPE.get(json.get('prodCatCde')) or Account.TYPE_UNKNOWN, + 'label': '%s %s' % ( + self.LABEL.get(json.get('prodCatCde')), + json.get('ldgrBal').get('ccy') + ), + 'currency': json.get('ldgrBal').get('ccy'), + 'balance': Decimal(json.get('ldgrBal').get('amt')), + } + else: + acc_dict = { + 'bank_name': 'HSBC HK', + 'id': '%s-%s' % ( + json.get('displyID'), + json.get('prodCatCde') + ), + '_idx': json.get('acctIndex'), + 'number': json.get('displyID'), + 'type': self.PRODUCT_TO_TYPE.get(json.get('prodCatCde')) or Account.TYPE_UNKNOWN, + 'label': '%s' % ( + self.LABEL.get(json.get('prodCatCde')) + ) + } + return acc_dict + + def iter_accounts(self): + + for country in self.get('countriesAccountList'): + for acc in country.get('acctLiteWrapper'): + acc_prod = acc.get('prodCatCde') + if acc_prod == "MST": + for subacc in acc.get('subAcctInfo'): + res = Account.from_dict(self.get_acc_dict(subacc)) + if subacc.get('hasAcctDetails'): + yield res + else: + self.logger.debug("skip account with no history: %s", res) + elif acc_prod == "CC": + self.logger.debug("acc: %s", str(acc)) + res = Account.from_dict(self.get_acc_dict(acc)) + if acc.get('hasAcctDetails'): + yield res + else: + self.logger.debug("skip account with no history: %s", res) + else: + self.logger.error("Unknown account product code [%s]", acc_prod) + + +class JsonAccHist(LoggedPage, JsonBasePage): + @pagination + @method + class iter_history(DictElement): + + item_xpath = "txnSumm" + + def next_page(self): + self.logger.debug( + "paging (%s): %s", + Dict('responsePagingInfo/moreRecords', default='N')(self.page.doc), + self.page.doc.get('responsePagingInfo')) + if Dict('responsePagingInfo/moreRecords', default='N')(self.page.doc) == 'Y': + self.logger.info("more values are available") + """ + prev_req = self.page.response.request + jq = json.loads(prev_req.body) + jq['pagingInfo']['startDetail']=Dict('responsePagingInfo/endDetail')(self.page.doc) + return requests.Request( + self.page.url, + headers = prev_req.headers, + json = jq + ) + """ + return + + class item(ItemElement): + klass = Transaction + + obj_rdate = Date(Dict('txnDate')) + obj_date = Date(Dict('txnPostDate')) + obj_amount = CleanDecimal(Dict('txnAmt/amt')) + + def obj_raw(self): + return Transaction.Raw(Dict('txnDetail/0'))(self) + + def obj_type(self): + for pattern, type in Transaction.PATTERNS: + if pattern.match(Dict('txnDetail/0')(self)): + return type + + if Dict('txnHistType', default=None)(self) in ['U', 'B']: + return Transaction.TYPE_CARD + return Transaction.TYPE_TRANSFER + + +class AppGonePage(HTMLPage): + def on_load(self): + self.browser.app_gone = True + self.logger.info('Application has gone. Relogging...') + self.browser.do_logout() + self.browser.do_login() + + +class OtherPage(HTMLPage): + ERROR_CLASSES = [ + ('Votre contrat est suspendu', ActionNeeded), + ("Vos données d'identification (identifiant - code secret) sont incorrectes", BrowserIncorrectPassword), + ('Erreur : Votre contrat est clôturé.', ActionNeeded), + ] + + def on_load(self): + for msg, exc in self.ERROR_CLASSES: + for tag in self.doc.xpath('//p[@class="debit"]//strong[text()[contains(.,$msg)]]', msg=msg): + raise exc(CleanText('.')(tag)) diff --git a/modules/hsbchk/pages/login.py b/modules/hsbchk/pages/login.py new file mode 100755 index 0000000000..ab6530d283 --- /dev/null +++ b/modules/hsbchk/pages/login.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010-2012 Julien Veyssier +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +from weboob.browser.filters.standard import ( + CleanText +) +from weboob.browser.selenium import ( + SeleniumPage, VisibleXPath +) +from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable + +from selenium.webdriver.common.by import By + + +class LoginPage(SeleniumPage): + + is_here = VisibleXPath('//h2[text()[contains(.,"Log on to Internet Banking")]]') + + @property + def logged(self): + if self.doc.xpath('//p[contains(text(), "You are now being redirected to your Personal Internet Banking.")]'): + return True + return False + + def on_load(self): + for message in self.doc.xpath('//div[contains(@class, "alertBox")]'): + error_msg = CleanText('.')(message) + if any(msg in error_msg for msg in ['The username you entered doesn\'t match our records. Please try again.', + 'Please enter your memorable answer and password.', + 'The information you entered does not match our records. Please try again.', + 'mot de passe invalide']): + raise BrowserIncorrectPassword(error_msg) + else: + raise BrowserUnavailable(error_msg) + + def get_error(self): + for message in self.doc.xpath('//div[contains(@data-dojo-type, "hsbcwidget/alertBox")]'): + error_msg = CleanText('.')(message) + if any(msg in error_msg for msg in ['The username you entered doesn\'t match our records. Please try again.', + 'Please enter a valid Username.', + 'mot de passe invalide']): + raise BrowserIncorrectPassword(error_msg) + else: + raise BrowserUnavailable(error_msg) + return + + def login(self, login): + self.driver.find_element_by_name("userid").send_keys(login) + self.driver.find_element_by_class_name("submit_input").click() + + def get_no_secure_key(self): + self.driver.find_element_by_xpath('//a[span[contains(text(), "Without Security Device")]]').click() + + def login_w_secure(self, password, secret): + self.driver.find_element_by_name("memorableAnswer").send_keys(secret) + if len(password) < 8: + raise BrowserIncorrectPassword('The password must be at least %d characters' % 8) + elif len(password) > 8: + # HSBC only use 6 first and last two from the password + password = password[:6] + password[-2:] + + elts = self.driver.find_elements(By.XPATH, "//input[@type='password' and contains(@id,'pass')]") + for elt in elts: + if elt.get_attribute('disabled') is None and elt.get_attribute('class') == "smallestInput active": + elt.send_keys(password[int(elt.get_attribute('id')[-1]) - 1]) + self.driver.find_element_by_xpath("//input[@class='submit_input']").click() diff --git a/modules/hsbchk/sbrowser.py b/modules/hsbchk/sbrowser.py new file mode 100755 index 0000000000..1542a5510e --- /dev/null +++ b/modules/hsbchk/sbrowser.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012-2013 Romain Bignon +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +import os + +try: + from selenium import webdriver +except ImportError: + raise ImportError('Please install python-selenium') + +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.common.proxy import Proxy, ProxyType +from selenium.common.exceptions import ( + TimeoutException +) +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, BrowserHTTPError +from weboob.browser.selenium import (SeleniumBrowser, DirFirefoxProfile, VisibleXPath) +from weboob.browser import URL + +from .pages.login import ( + LoginPage +) + +class LoginBrowser(SeleniumBrowser): + BASEURL = 'https://www.hsbc.com.hk/' + + #DRIVER = webdriver.Remote + + app_gone = False + + preconnection = URL(r'https://www.ebanking.hsbc.com.hk/1/2/logon?LANGTAG=en&COUNTRYTAG=US', LoginPage) + login = URL(r'https://www.security.online-banking.hsbc.com.hk/gsa/SaaS30Resource/*', LoginPage) + + def __init__(self, username, password, secret, *args, **kwargs): + super(LoginBrowser, self).__init__(*args, **kwargs) + self.username = username + self.password = password + self.secret = secret + self.web_space = None + self.home_url = None + + def _build_capabilities(self): + capa = super(LoginBrowser, self)._build_capabilities() + capa['marionette'] = True + return capa + + def _setup_driver(self): + proxy = Proxy() + proxy.proxy_type = ProxyType.DIRECT + if 'http' in self.proxy: + proxy.http_proxy = self.proxy['http'] + if 'https' in self.proxy: + proxy.ssl_proxy = self.proxy['https'] + + capa = self._build_capabilities() + proxy.add_to_capabilities(capa) + + options = self._build_options() + # TODO some browsers don't need headless + # TODO handle different proxy setting? + options.set_headless(self.HEADLESS) + + if self.DRIVER is webdriver.Firefox: + if self.responses_dirname and not os.path.isdir(self.responses_dirname): + os.makedirs(self.responses_dirname) + + options.profile = DirFirefoxProfile(self.responses_dirname) + if self.responses_dirname: + capa['profile'] = self.responses_dirname + self.driver = self.DRIVER(options=options, capabilities=capa) + elif self.DRIVER is webdriver.Chrome: + self.driver = self.DRIVER(options=options, desired_capabilities=capa) + elif self.DRIVER is webdriver.PhantomJS: + if self.responses_dirname: + if not os.path.isdir(self.responses_dirname): + os.makedirs(self.responses_dirname) + log_path = os.path.join(self.responses_dirname, 'selenium.log') + else: + log_path = NamedTemporaryFile(prefix='weboob_selenium_', suffix='.log', delete=False).name + + self.driver = self.DRIVER(desired_capabilities=capa, service_log_path=log_path) + elif self.DRIVER is webdriver.Remote: + # self.HEADLESS = False + # for debugging purpose + self.driver = webdriver.Remote( + command_executor='http://:/wd/hub', + desired_capabilities=DesiredCapabilities.FIREFOX) + else: + raise NotImplementedError() + + if self.WINDOW_SIZE: + self.driver.set_window_size(*self.WINDOW_SIZE) + + + def load_state(self, state): + return + + def do_login(self): + self.logger.debug("start do_login") + + self.app_gone = False + self.preconnection.go() + try: + self.wait_until(VisibleXPath('//h2[text()[contains(.,"Log on to Internet Banking")]]'), timeout=20) + self.page.login(self.username) + self.wait_until_is_here(self.login, 10) + error = self.page.get_error() + if error: + raise BrowserIncorrectPassword(error) + + self.page.get_no_secure_key() + self.wait_until_is_here(self.login, 10) + error = self.page.get_error() + if error: + raise BrowserHTTPError(error) + self.page.login_w_secure(self.password, self.secret) + if self.login.is_here(): + error = self.page.get_error() + if error: + raise BrowserIncorrectPassword(error) + WebDriverWait(self.driver, 20).until(EC.title_contains("My banking")) + + except TimeoutException as e: + self.logger.exception("timeout while login") + raise BrowserUnavailable(e.msg) diff --git a/modules/hsbchk/test.py b/modules/hsbchk/test.py new file mode 100644 index 0000000000..92e3a6810e --- /dev/null +++ b/modules/hsbchk/test.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012 Romain Bignon +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + + +from weboob.tools.test import BackendTest +from weboob.capabilities.bank import Account + +class HSBCHKTest(BackendTest): + MODULE = 'hsbchk' + + def test_hsbchk(self): + l = list(self.backend.iter_accounts()) + if len(l) > 0: + a = l[0] + list(self.backend.iter_history(a)) + +# def test_investments(self): +# life_insurance_accounts = [account for account in self.backend.iter_accounts() if account.type == Account.TYPE_LIFE_INSURANCE] +# investments = {acc.id: list(self.backend.iter_investment(acc)) for acc in life_insurance_accounts} +# for acc in life_insurance_accounts: +# invs = investments[acc.id] +# self.assertLessEquals(sum([inv.valuation for inv in invs]), acc.balance) -- GitLab