From 8f6ccb18a97d6ebd1239f55f0d2088556bec8753 Mon Sep 17 00:00:00 2001 From: Florian Duguet Date: Tue, 20 Aug 2019 17:11:09 +0200 Subject: [PATCH] [banquepopulaire] Add CapDocument to get bank statements --- modules/banquepopulaire/browser.py | 49 +++++++++++++++ modules/banquepopulaire/document_pages.py | 72 +++++++++++++++++++++++ modules/banquepopulaire/module.py | 31 +++++++++- 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 modules/banquepopulaire/document_pages.py diff --git a/modules/banquepopulaire/browser.py b/modules/banquepopulaire/browser.py index 6b7e363276..0677cdf7ff 100644 --- a/modules/banquepopulaire/browser.py +++ b/modules/banquepopulaire/browser.py @@ -21,9 +21,11 @@ import re +from datetime import datetime from collections import OrderedDict from functools import wraps +from dateutil.relativedelta import relativedelta from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable from weboob.browser.exceptions import HTTPNotFound, ServerError from weboob.browser import LoginBrowser, URL, need_login @@ -41,6 +43,8 @@ LineboursePage, AlreadyLoginPage, ) +from .document_pages import BasicTokenPage, SubscriberPage, SubscriptionsPage, DocumentsPage + from .linebourse_browser import LinebourseBrowser @@ -169,6 +173,11 @@ class BanquePopulaire(LoginBrowser): advisor = URL(r'https://[^/]+/cyber/internet/StartTask.do\?taskInfoOID=accueil.*', r'https://[^/]+/cyber/internet/StartTask.do\?taskInfoOID=contacter.*', AdvisorPage) + basic_token_page = URL(r'/SRVATE/context/mde/1.1.5', BasicTokenPage) + subscriber_page = URL(r'https://[^/]+/api-bp/wapi/2.0/abonnes/current/mes-documents-electroniques', SubscriberPage) + subscription_page = URL(r'https://[^/]+/api-bp/wapi/2.0/abonnes/current/contrats', SubscriptionsPage) + documents_page = URL(r'/api-bp/wapi/2.0/abonnes/current/documents/recherche-avancee', DocumentsPage) + def __init__(self, website, *args, **kwargs): self.BASEURL = 'https://%s' % website # this url is required because the creditmaritime abstract uses an other url @@ -186,6 +195,7 @@ def __init__(self, website, *args, **kwargs): self.linebourse = LinebourseBrowser('https://www.linebourse.fr', logger=self.logger, responses_dirname=dirname, weboob=self.weboob, proxy=self.PROXIES) self.investments = {} + self.documents_headers = None def deinit(self): super(BanquePopulaire, self).deinit() @@ -563,6 +573,45 @@ def get_advisor(self): self.page.update_agency(advisor) return iter([advisor]) + @need_login + def iter_subscriptions(self): + self.location('/SRVATE/context/mde/1.1.5') + headers = {'Authorization': 'Basic %s' % self.page.get_basic_token()} + response = self.location('/as-bp/as/2.0/tokens', method='POST', headers=headers) + self.documents_headers = {'Authorization': 'Bearer %s' % response.json()['access_token']} + + self.location('/api-bp/wapi/2.0/abonnes/current/mes-documents-electroniques', headers=self.documents_headers) + subscriber = self.page.get_subscriber() + + params = {'type': 'dematerialisationEffective'} + self.location('/api-bp/wapi/2.0/abonnes/current/contrats', params=params, headers=self.documents_headers) + return self.page.get_subscriptions(subscriber=subscriber) + + @need_login + def iter_documents(self, subscription): + now = datetime.now() + # website says we can't get documents more than one year range at once but it seems it's just a javascript check + # no problem here so far + first_date = now - relativedelta(years=5) + start_date = first_date.strftime('%Y-%m-%dT00:00:00.000+00:00') + end_date = now.strftime('%Y-%m-%dT%H:%M:%S.000+00:00') + body = { + 'inTypeRecherche': {'type': 'typeRechercheDocument', 'code': 'DEMAT'}, + 'inDateDebut': start_date, + 'inDateFin': end_date, + 'inListeIdentifiantsContrats': [ + {'identifiantContrat': {'identifiant': subscription.id, 'codeBanque': subscription._bank_code}} + ], + 'inListeTypesDocuments': [ + {'typeDocument': {'code': 'EXTRAIT', 'label': 'Extrait de compte', 'type': 'referenceLogiqueDocument'}} + ] + } + self.location('/api-bp/wapi/2.0/abonnes/current/documents/recherche-avancee', json=body, headers=self.documents_headers) + return self.page.iter_documents(subid=subscription.id) + + def download_document(self, document): + return self.open(document.url, headers=self.documents_headers).content + class iter_retry(object): # when the callback is retried, it will create a new iterator, but we may already yielded diff --git a/modules/banquepopulaire/document_pages.py b/modules/banquepopulaire/document_pages.py new file mode 100644 index 0000000000..52d0e29ddc --- /dev/null +++ b/modules/banquepopulaire/document_pages.py @@ -0,0 +1,72 @@ +# -*- 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 __future__ import unicode_literals + +import base64 + +from weboob.browser.elements import method, DictElement, ItemElement +from weboob.browser.filters.standard import Date, Env, Format +from weboob.browser.filters.json import Dict +from weboob.capabilities.bill import Subscription, Document, DocumentTypes + +from weboob.browser.pages import LoggedPage, JsonPage + + +class BasicTokenPage(LoggedPage, JsonPage): + def get_basic_token(self): + token = ('BP_MDE.RIA_PROD_1.0:%s' % self.doc['appContext']['clientSecret']).encode('utf-8') + return base64.b64encode(token).decode('utf-8') + + +class SubscriberPage(LoggedPage, JsonPage): + def get_subscriber(self): + return self.doc['nomRaisonSociale'] + + +class SubscriptionsPage(LoggedPage, JsonPage): + @method + class get_subscriptions(DictElement): + item_xpath = '_embedded/content' + class item(ItemElement): + klass = Subscription + + obj_id = Dict('idContrat/identifiant') + obj_subscriber = Env('subscriber') + obj_label = Dict('intituleContrat') + obj__bank_code = Dict('idContrat/codeBanque') + + +class DocumentsPage(LoggedPage, JsonPage): + @method + class iter_documents(DictElement): + item_xpath = '_embedded/content' + + def condition(self): + return '_embedded' in self.page.doc + + class item(ItemElement): + klass = Document + + obj_id = Format('%s_%s', Env('subid'), Dict('identifiantDocument/identifiant')) + obj_date = Date(Dict('dateCreation')) + obj_label = Dict('libelle') + obj_format = 'pdf' + obj_type = DocumentTypes.STATEMENT + obj_url = Dict('_links/document/href') diff --git a/modules/banquepopulaire/module.py b/modules/banquepopulaire/module.py index 0b49803e1a..b569d4521f 100644 --- a/modules/banquepopulaire/module.py +++ b/modules/banquepopulaire/module.py @@ -23,6 +23,10 @@ from functools import reduce from weboob.capabilities.bank import CapBankWealth, AccountNotFound +from weboob.capabilities.base import find_object +from weboob.capabilities.bill import ( + CapDocument, SubscriptionNotFound, DocumentNotFound, Document, Subscription, DocumentTypes, +) from weboob.capabilities.contact import CapContact from weboob.capabilities.profile import CapProfile from weboob.tools.backend import Module, BackendConfig @@ -34,7 +38,7 @@ __all__ = ['BanquePopulaireModule'] -class BanquePopulaireModule(Module, CapBankWealth, CapContact, CapProfile): +class BanquePopulaireModule(Module, CapBankWealth, CapContact, CapProfile, CapDocument): NAME = 'banquepopulaire' MAINTAINER = 'Romain Bignon' EMAIL = 'romain@weboob.org' @@ -70,6 +74,8 @@ class BanquePopulaireModule(Module, CapBankWealth, CapContact, CapProfile): ValueBackendPassword('password', label='Mot de passe')) BROWSER = BanquePopulaire + accepted_document_types = (DocumentTypes.STATEMENT,) + def create_default_browser(self): repls = [ ('alsace', 'bpalc'), @@ -112,3 +118,26 @@ def iter_contacts(self): def get_profile(self): return self.browser.get_profile() + + def iter_subscription(self): + return self.browser.iter_subscriptions() + + def iter_documents(self, subscription): + if not isinstance(subscription, Subscription): + subscription = self.get_subscription(subscription) + return self.browser.iter_documents(subscription) + + def get_subscription(self, _id): + return find_object(self.iter_subscription(), id=_id, error=SubscriptionNotFound) + + def get_document(self, _id): + subid = _id.rsplit('_', 1)[0] + subscription = self.get_subscription(subid) + + return find_object(self.iter_documents(subscription), id=_id, error=DocumentNotFound) + + def download_document(self, document): + if not isinstance(document, Document): + document = self.get_document(document) + + return self.browser.download_document(document) -- GitLab