Newer
Older
# 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 Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
Quentin Defenouillere
committed
from lxml import etree
from io import StringIO
from weboob.browser import LoginBrowser, URL, need_login
from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded
Quentin Defenouillere
committed
from weboob.browser.exceptions import HTTPNotFound, ServerError
from weboob.tools.capabilities.bank.investments import create_french_liquidity
from .pages import (
LoginPage, HomePage, AccountsPage, OldAccountsPage, HistoryPage, InvestmentPage, InvestDetailPage,
InvestmentListPage, QuestionPage, ChangePassPage, LogonFlowPage, ViewPage, SwitchPage,
class BinckBrowser(LoginBrowser):
BASEURL = 'https://web.binck.fr'
Quentin Defenouillere
committed
''' Delete this attribute when old website is obsolete '''
old_website_connection = False
login = URL(r'/Logon', LoginPage)
Quentin Defenouillere
committed
view = URL('/PersonIntroduction/Index', ViewPage)
logon_flow = URL(r'/AmlQuestionnairesOverview/LogonFlow$', LogonFlowPage)
accounts = URL(r'/PersonAccountOverview/Index', AccountsPage)
Quentin Defenouillere
committed
old_accounts = URL(r'/AccountsOverview/Index', OldAccountsPage)
account_switch = URL('/Header/SwitchAccount', SwitchPage)
Quentin Defenouillere
committed
home_page = URL(r'/$',
r'/Home/Index', HomePage)
investment = URL(r'/PortfolioOverview/GetPortfolioOverview', InvestmentPage)
investment_list = URL(r'PortfolioOverview$', InvestmentListPage)
invest_detail = URL(r'/SecurityInformation/Get', InvestDetailPage)
history = URL(r'/TransactionsOverview/GetTransactions',
r'/TransactionsOverview/FilteredOverview', HistoryPage)
questions = URL(r'/FDL_Complex_FR_Compte',
r'FsmaMandatoryQuestionnairesOverview', QuestionPage)
change_pass = URL(r'/ChangePassword/Index',
r'/EditSetting/GetSetting\?code=MutationPassword', ChangePassPage)
Baptiste Delpey
committed
if self.page and self.page.logged:
Quentin Defenouillere
committed
self.location('/Account/Logoff')
self.login.go().login(self.username, self.password)
error = self.page.get_error()
if error and 'mot de passe' in error:
raise BrowserIncorrectPassword(error)
'Votre compte a été bloqué / clôturé' in error,
'Votre compte est bloqué, veuillez contacter le Service Clients' in error,
raise AssertionError('Unhandled behavior at login: error is "{}"'.format(error))
Quentin Defenouillere
committed
@need_login
def switch_account(self, account_id):
self.accounts.stay_or_go()
if self.accounts.is_here():
token = self.page.get_token()
data = {'accountNumber': account_id}
# Important: the "switch" request without the token will return a 500 error
self.account_switch.go(data=data, headers=token)
# We should be automatically redirected to the accounts page:
assert self.accounts.is_here(), 'switch_account did not redirect to AccountsPage properly'
Quentin Defenouillere
committed
self.accounts.stay_or_go()
if self.page.has_accounts_table():
for a in self.page.iter_accounts():
''' Delete these attributes when old website is obsolete '''
Quentin Defenouillere
committed
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
a._histpages = None
self.switch_account(a.id)
# We must get the new token almost everytime we get a new page:
if self.accounts.is_here():
token = self.page.get_token()
# Get valuation_diff from the investment page
try:
data = {'grouping': 'SecurityCategory'}
a.valuation_diff = self.investment.go(data=data, headers=token).get_valuation_diff()
except HTTPNotFound:
# if it is not an invest account, the portfolio link may be present but hidden and return a 404
a.valuation_diff = None
yield a
# Some Binck connections don't have any accounts on the new AccountsPage,
# so we need to fetch them on the OldAccountsPage for now:
else:
''' Delete this part when old website is obsolete '''
self.old_website_connection = True
self.old_accounts.go()
for a in self.page.iter_accounts():
try:
self.old_accounts.stay_or_go().go_to_account(a.id)
except ServerError as exception:
# get html error to parse
parser = etree.HTMLParser()
html_error = etree.parse(StringIO(exception.response.text), parser)
account_error = html_error.xpath('//p[contains(text(), "Votre compte est")]/text()')
if account_error:
raise ActionNeeded(account_error[0])
else:
raise
a.iban = self.page.get_iban()
# Get token
token = self.page.get_token()
# Get investment page
data = {'grouping': "SecurityCategory"}
try:
a._invpage = self.investment.go(data=data, headers=token) \
if self.page.is_investment() else None
except HTTPNotFound:
# if it's not an invest account, the portfolio link may be present but hidden and return a 404
a._invpage = None
if a._invpage:
a.valuation_diff = a._invpage.get_valuation_diff()
# Get history page
data = [('currencyCode', a.currency), ('startDate', ""), ('endDate', "")]
a._histpages = [self.history.go(data=data, headers=token)]
while self.page.doc['EndOfData'] is False:
a._histpages.append(self.history.go(data=self.page.get_nextpage_data(data[:]), headers=token))
yield a
@need_login
def iter_investment(self, account):
Quentin Defenouillere
committed
# Start with liquidities:
if account._liquidity:
yield create_french_liquidity(account._liquidity)
Quentin Defenouillere
committed
''' Delete this part when old website is obsolete '''
if self.old_website_connection:
self.old_accounts.stay_or_go().go_to_account(account.id)
Quentin Defenouillere
committed
if account._invpage:
for inv in account._invpage.iter_investment(currency=account.currency):
if not inv.code:
params = {'securityId': inv._security_id}
self.invest_detail.go(params=params)
if self.invest_detail.is_here():
inv.code, inv.code_type = self.page.get_isin_code_and_type()
Quentin Defenouillere
committed
yield inv
return
self.switch_account(account.id)
token = self.page.get_token()
try:
data = {'grouping': 'SecurityCategory'}
self.investment.go(data=data, headers=token)
except HTTPNotFound:
return
for inv in self.page.iter_investment(currency=account.currency):
yield inv
@need_login
def iter_history(self, account):
Quentin Defenouillere
committed
''' Delete this part when old website is obsolete '''
if self.old_website_connection:
if account._histpages:
for page in account._histpages:
for tr in page.iter_history():
yield tr
return
self.switch_account(account.id)
token = self.page.get_token()
data = [('currencyCode', account.currency), ('startDate', ''), ('endDate', '')]
history_pages = [self.history.go(data=data, headers=token)]
while self.page.doc['EndOfData'] is False:
history_pages.append(self.history.go(data=self.page.get_nextpage_data(data[:]), headers=token))
for page in history_pages:
for tr in page.iter_history():
yield tr