Newer
Older
# Copyright(C) 2010-2012 Romain Bignon, Pierre Mazière
# 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/>.
from weboob.exceptions import BrowserIncorrectPassword
from weboob.browser import LoginBrowser, URL, need_login, StatesMixin
from weboob.browser.exceptions import ServerError
from weboob.browser.pages import FormNotFound
from weboob.capabilities.bank import Account, AddRecipientError, AddRecipientStep, Recipient
from weboob.tools.compat import basestring, urlsplit, parse_qsl, unicode
from .pages import LoginPage, AccountsPage, AccountHistoryPage, \
CBListPage, CBHistoryPage, ContractsPage, ContractsChoicePage, BoursePage, \
AVPage, AVDetailPage, DiscPage, NoPermissionPage, RibPage, \
HomePage, LoansPage, TransferPage, AddRecipientPage, \
RecipientPage, RecipConfirmPage, SmsPage, RecipRecapPage, \
LoansProPage, Form2Page, DocumentsPage, ClientPage
__all__ = ['LCLBrowser','LCLProBrowser', 'ELCLBrowser']
BASEURL = 'https://particuliers.secure.lcl.fr'
login = URL('/outil/UAUT/Authentication/authenticate',
'/outil/UAUT\?from=.*',
'/outil/UWER/Accueil/majicER',
'/outil/UWER/Enregistrement/forwardAcc',
LoginPage)
contracts_page = URL('/outil/UAUT/Contrat/choixContrat.*',
'/outil/UAUT/Contract/getContract.*',
'/outil/UAUT/Contract/selectContracts.*',
'/outil/UAUT/Accueil/preRoutageLogin',
ContractsPage)
contracts_choice = URL('.*outil/UAUT/Contract/routing', ContractsChoicePage)
home = URL('/outil/UWHO/Accueil/', HomePage)
accounts = URL('/outil/UWSP/Synthese', AccountsPage)
client = URL('/outil/uwho', ClientPage)
history = URL('/outil/UWLM/ListeMouvements.*/accesListeMouvements.*',
'/outil/UWLM/DetailMouvement.*/accesDetailMouvement.*',
AccountHistoryPage)
rib = URL('/outil/UWRI/Accueil/detailRib',
'/outil/UWRI/Accueil/listeRib', RibPage)
finalrib = URL('/outil/UWRI/Accueil/', RibPage)
cb_list = URL('/outil/UWCB/UWCBEncours.*/listeCBCompte.*', CBListPage)
cb_history = URL('/outil/UWCB/UWCBEncours.*/listeOperations.*', CBHistoryPage)
skip = URL('/outil/UAUT/Contrat/selectionnerContrat.*',
'/index.html')
no_perm = URL('/outil/UAUT/SansDroit/affichePageSansDroit.*', NoPermissionPage)
bourse = URL('https://bourse.secure.lcl.fr/netfinca-titres/servlet/com.netfinca.frontcr.synthesis.HomeSynthesis',
'https://bourse.secure.lcl.fr/netfinca-titres/servlet/com.netfinca.frontcr.account.*',
'/outil/UWBO.*', BoursePage)
disc = URL('https://bourse.secure.lcl.fr/netfinca-titres/servlet/com.netfinca.frontcr.login.ContextTransferDisconnect',
r'https://assurance-vie-et-prevoyance.secure.lcl.fr/filiale/entreeBam\?.*\btypeaction=reroutage_retour\b',
r'https://assurance-vie-et-prevoyance.secure.lcl.fr/filiale/ServletReroutageCookie',
'/outil/UAUT/RetourPartenaire/retourCar', DiscPage)
form2 = URL(r'/outil/UWVI/Routage/', Form2Page)
assurancevie = URL('/outil/UWVI/AssuranceVie/accesSynthese',
'/outil/UWVI/AssuranceVie/accesDetail.*',
AVPage)
avdetail = URL('https://ASSURANCE-VIE-et-prevoyance.secure.lcl.fr.*',
'https://assurance-vie-et-prevoyance.secure.lcl.fr.*',
loans = URL('/outil/UWCR/SynthesePar/', LoansPage)
loans_pro = URL('/outil/UWCR/SynthesePro/', LoansProPage)
transfer_page = URL('/outil/UWVS/', TransferPage)
confirm_transfer = URL('/outil/UWVS/Accueil/redirectView', TransferPage)
recipients = URL('/outil/UWBE/Consultation/list', RecipientPage)
add_recip = URL('/outil/UWBE/Creation/creationSaisie', AddRecipientPage)
recip_confirm = URL('/outil/UWBE/Creation/creationConfirmation', RecipConfirmPage)
send_sms = URL('/outil/UWBE/Otp/envoiCodeOtp\?telChoisi=MOBILE', '/outil/UWBE/Otp/getValidationCodeOtp\?codeOtp', SmsPage)
recip_recap = URL('/outil/UWBE/Creation/executeCreation', RecipRecapPage)
documents = URL('/outil/UWDM/ConsultationDocument/derniersReleves',
'/outil/UWDM/Recherche/afficherPlus',
'/outil/UWDM/Recherche/rechercherAll', DocumentsPage)
__states__ = ('contracts', 'current_contract',)
def __init__(self, *args, **kwargs):
super(LCLBrowser, self).__init__(*args, **kwargs)
self.accounts_list = None
self.current_contract = None
self.contracts = None
def load_state(self, state):
super(LCLBrowser, self).load_state(state)
# lxml _ElementStringResult were put in the state, convert them to plain strs
# TODO to remove at some point
if self.contracts:
self.contracts = [unicode(s) for s in self.contracts]
if self.current_contract:
self.current_contract = unicode(self.current_contract)
assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring)
if not self.password.isdigit():
raise BrowserIncorrectPassword()
self.contracts = []
self.current_contract = None
# we force the browser to go to login page so it's work even
# if the session expire
self.login.go()
if not self.page.login(self.username, self.password) or \
(self.login.is_here() and self.page.is_error()) :
raise BrowserIncorrectPassword("invalid login/password.\nIf you did not change anything, be sure to check for password renewal request on the original web site.")
@need_login
def connexion_bourse(self):
self.location('/outil/UWBO/AccesBourse/temporisationCar?codeTicker=TICKERBOURSECLI')
if next_page:
self.location(self.page.get_next())
self.bourse.stay_or_go()
return True
self.disc.stay_or_go()
def select_contract(self, id_contract):
if self.current_contract and id_contract != self.current_contract:
# when we go on bourse page, we can't change contract anymore... we have to logout.
self.location('/outil/UAUT/Login/logout')
# we already passed all checks on do_login so we consider it's ok.
self.login.go().login(self.username, self.password)
self.contracts_choice.go().select_contract(id_contract)
def go_contract(f):
@wraps(f)
def wrapper(self, account, *args, **kwargs):
self.select_contract(account._contract)
return f(self, account, *args, **kwargs)
return wrapper
def check_accounts(self, account):
return all(account.id != acc.id for acc in self.accounts_list)
def update_accounts(self, account):
if self.check_accounts(account):
account._contract = self.current_contract
self.accounts_list.append(account)
def get_accounts(self):
self.assurancevie.stay_or_go()
# This is required in case the browser is left in the middle of add_recipient and the session expires.
if self.login.is_here():
return self.get_accounts_list()
if self.no_perm.is_here():
self.logger.warning('Life insurances are unavailable.')
else:
for a in self.page.get_list():
self.update_accounts(a)
self.accounts.stay_or_go()
for a in self.page.get_list():
if not self.check_accounts(a):
continue
self.location('/outil/UWRI/Accueil/')
if self.page.has_iban_choice():
self.rib.go(data={'compte': '%s/%s/%s' % (a.id[0:5], a.id[5:11], a.id[11:])})
if self.rib.is_here():
iban = self.page.get_iban()
a.iban = iban if iban and a.id[11:] in iban else NotAvailable
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
iban = self.page.check_iban_by_account(a.id)
a.iban = iban if iban is not None else NotAvailable
self.update_accounts(a)
self.loans.stay_or_go()
if self.no_perm.is_here():
self.logger.warning('Loans are unavailable.')
else:
for a in self.page.get_list():
self.update_accounts(a)
self.loans_pro.stay_or_go()
if self.no_perm.is_here():
self.logger.warning('Loans are unavailable.')
else:
for a in self.page.get_list():
self.update_accounts(a)
if self.connexion_bourse():
for a in self.page.get_list():
self.update_accounts(a)
self.deconnexion_bourse()
# Disconnecting from bourse portal before returning account list
# to be sure that we are on the banque portal
@need_login
def get_accounts_list(self):
if self.accounts_list is None:
if self.contracts and self.current_contract:
for id_contract in self.contracts:
self.select_contract(id_contract)
self.get_accounts()
def get_history(self, account):
if hasattr(account, '_market_link') and account._market_link:
self.connexion_bourse()
self.location(account._market_link)
self.location(account._link_id).page.get_fullhistory()
for tr in self.page.iter_history():
yield tr
self.deconnexion_bourse()
elif hasattr(account, '_link_id') and account._link_id:
try:
self.location(account._link_id)
except ServerError:
return
for tr in self.page.get_operations():
yield tr
for tr in self.get_cb_operations(account, 1):
yield tr
elif account.type == Account.TYPE_LIFE_INSURANCE and account._form:
self.assurancevie.stay_or_go()
account._form.submit()
try:
self.page.get_details(account, "OHIPU")
except FormNotFound:
assert self.page.is_restricted()
self.logger.warning('restricted access to account %s', account)
else:
for tr in self.page.iter_history():
yield tr
def get_cb_operations(self, account, month=0):
"""
Get CB operations.
* month=0 : current operations (non debited)
* month=1 : previous month operations (debited)
"""
if not hasattr(account, '_coming_links'):
return
for link in account._coming_links:
v = urlsplit(self.absurl(link))
args = dict(parse_qsl(v.query))
args['MOIS'] = month
for tr in self.page.get_operations():
yield tr
for card_link in self.page.get_cards():
self.location(card_link)
for tr in self.page.get_operations():
yield tr
@need_login
def get_investment(self, account):
if account.type == Account.TYPE_LIFE_INSURANCE and account._form:
self.assurancevie.stay_or_go()
account._form.submit()
if self.page.is_restricted():
self.logger.warning('restricted access to account %s', account)
else:
for inv in self.page.iter_investment():
yield inv
elif hasattr(account, '_market_link') and account._market_link:
for inv in self.location(account._market_link).page.iter_investment():
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
def locate_browser(self, state):
if state['url'] == 'https://particuliers.secure.lcl.fr/outil/UWBE/Creation/creationConfirmation':
self.logged = True
else:
super(LCLBrowser, self).locate_browser(state)
@need_login
def send_code(self, recipient, **params):
res = self.open('/outil/UWBE/Otp/getValidationCodeOtp?codeOtp=%s' % params['code'])
if res.text == 'false':
raise AddRecipientError('Mauvais code sms.')
self.recip_recap.go().check_values(recipient.iban, recipient.label)
return self.get_recipient_object(recipient.iban, recipient.label)
@need_login
def get_recipient_object(self, iban, label):
r = Recipient()
r.iban = iban
r.id = iban
r.label = label
r.category = u'Externe'
r.enabled_at = datetime.now().replace(microsecond=0) + timedelta(days=5)
r.currency = u'EUR'
r.bank_name = NotAvailable
return r
@need_login
def new_recipient(self, recipient, **params):
if 'code' in params:
return self.send_code(recipient, **params)
try:
assert recipient.iban[:2] in ['FR', 'MC']
except AssertionError:
raise AddRecipientError(u"LCL n'accepte que les iban commençant par MC ou FR.")
for _ in range(2):
self.add_recip.go()
if self.add_recip.is_here():
break
try:
assert self.add_recip.is_here()
except AssertionError:
raise AddRecipientError('Navigation failed: not on add_recip.')
self.page.validate(recipient.iban, recipient.label)
try:
assert self.recip_confirm.is_here()
except AssertionError:
raise AddRecipientError('Navigation failed: not on recip_confirm.')
self.page.check_values(recipient.iban, recipient.label)
# Send sms to user.
self.open('/outil/UWBE/Otp/envoiCodeOtp?telChoisi=MOBILE')
raise AddRecipientStep(self.get_recipient_object(recipient.iban, recipient.label), Value('code', label='Saisissez le code.'))
@need_login
def iter_recipients(self, origin_account):
if origin_account._transfer_id is None:
return
if self.no_perm.is_here() or not self.page.can_transfer(origin_account._transfer_id):
self.page.choose_origin(origin_account._transfer_id)
for recipient in self.page.iter_recipients(account_transfer_id=origin_account._transfer_id):
def init_transfer(self, account, recipient, amount, reason=None):
self.transfer_page.go()
self.page.choose_origin(account._transfer_id)
self.page.choose_recip(recipient)
self.page.transfer(amount, reason)
self.page.check_data_consistency(account, recipient, amount, reason)
return self.page.create_transfer(account, recipient, amount, reason)
@need_login
def execute_transfer(self, transfer):
@need_login
def get_advisor(self):
return iter([self.accounts.stay_or_go().get_advisor()])
@need_login
def iter_subscriptions(self):
yield self.client.go().get_item()
@need_login
def iter_documents(self, subscription):
documents = []
self.documents.go()
self.location('https://particuliers.secure.lcl.fr/outil/UWDM/Recherche/afficherPlus')
self.page.do_search_request()
for document in self.page.get_list():
documents.append(document)
return documents
class LCLProBrowser(LCLBrowser):
BASEURL = 'https://professionnels.secure.lcl.fr'
#We need to add this on the login form
IDENTIFIANT_ROUTING = 'CLA'
def __init__(self, *args, **kwargs):
super(LCLProBrowser, self).__init__(*args, **kwargs)
self.session.cookies.set("lclgen","professionnels")
class ELCLBrowser(LCLBrowser):
BASEURL = 'https://e.secure.lcl.fr'
IDENTIFIANT_ROUTING = 'ELCL'
def __init__(self, *args, **kwargs):
super(ELCLBrowser, self).__init__(*args, **kwargs)
self.session.cookies.set('lclgen', 'ecl')