From 6794e5209f42e3c11ba4827b161df168532472a2 Mon Sep 17 00:00:00 2001 From: Arthur Huillet Date: Tue, 1 May 2018 13:27:54 +0200 Subject: [PATCH] add CapBank suravenir module This module adds life insurance provider suravenir (previ-direct.com). It can be accessed through several brokers, at least assurancevie.com (tested) and Linxea (not tested because I'm not a customer). This implementation supports boobank's "list", "history" and "investment" commands, albeit minimally. --- modules/suravenir/__init__.py | 26 +++++++++ modules/suravenir/browser.py | 71 +++++++++++++++++++++++ modules/suravenir/favicon.png | Bin 0 -> 521 bytes modules/suravenir/module.py | 68 +++++++++++++++++++++++ modules/suravenir/pages.py | 102 ++++++++++++++++++++++++++++++++++ modules/suravenir/test.py | 32 +++++++++++ tools/py3-compatible.modules | 1 + 7 files changed, 300 insertions(+) create mode 100644 modules/suravenir/__init__.py create mode 100644 modules/suravenir/browser.py create mode 100644 modules/suravenir/favicon.png create mode 100644 modules/suravenir/module.py create mode 100644 modules/suravenir/pages.py create mode 100644 modules/suravenir/test.py diff --git a/modules/suravenir/__init__.py b/modules/suravenir/__init__.py new file mode 100644 index 0000000000..30c74dee43 --- /dev/null +++ b/modules/suravenir/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Arthur Huillet +# +# 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 . + +from __future__ import unicode_literals + + +from .module import SuravenirModule + + +__all__ = ['SuravenirModule'] diff --git a/modules/suravenir/browser.py b/modules/suravenir/browser.py new file mode 100644 index 0000000000..cc1f03f91f --- /dev/null +++ b/modules/suravenir/browser.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Arthur Huillet +# +# 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 . + +from __future__ import unicode_literals + + +from weboob.browser import LoginBrowser, URL, need_login +from weboob.exceptions import BrowserIncorrectPassword + +from .pages import LoginPage, AccountsList, InvestmentList, AccountHistory + +__all__ = ['Suravenir'] + + +class Suravenir(LoginBrowser): + BASEURL = 'https://www.previ-direct.com/' + + login_page = URL('/web/eclient-(?P.*)', LoginPage) + accounts_page = URL('/group/eclient-(?P.*)/home$', AccountsList) + summary_page = URL('/group/eclient.*tabulateur.tabulation.resume', None) + investments_page = URL('/group/eclient.*tabulateur.tabulation.supports', InvestmentList) + history_page = URL('/group/eclient.*tabulateur.tabulation.operations', AccountHistory) + +# detail_link does not contain the type of page. the suffix for the pages are: +# résumé +# _portletespaceClientmonCompte_WAR_portletespaceclient_INSTANCE_Q4n1_tabName=detailsContrat.tabulateur.tabulation.resume +# mes supports +# _portletespaceClientmonCompte_WAR_portletespaceclient_INSTANCE_Q4n1_tabName=detailsContrat.tabulateur.tabulation.supports +# mes opérations +# _portletespaceClientmonCompte_WAR_portletespaceclient_INSTANCE_Q4n1_tabName=detailsContrat.tabulateur.tabulation.operations + + def __init__(self, broker, *args, **kwargs): + self.broker = broker + LoginBrowser.__init__(self, *args, **kwargs) + + def do_login(self): + self.login_page.stay_or_go(broker=self.broker).login(self.username, self.password) + + if self.login_page.is_here(): + raise BrowserIncorrectPassword() + + @need_login + def get_accounts_list(self): + self.accounts_page.stay_or_go(broker=self.broker) + return self.page.get_contracts() + + @need_login + def iter_investments(self, account): + self.location(account._detail_link + '&_portletespaceClientmonCompte_WAR_portletespaceclient_INSTANCE_Q4n1_tabName=detailsContrat.tabulateur.tabulation.supports') + return self.page.iter_investments() + + @need_login + def iter_history(self, account): + self.location(account._detail_link + '&_portletespaceClientmonCompte_WAR_portletespaceclient_INSTANCE_Q4n1_tabName=detailsContrat.tabulateur.tabulation.operations') + return self.page.iter_history() diff --git a/modules/suravenir/favicon.png b/modules/suravenir/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..f7bab69f065badde311dbf629af141c0cbcbe302 GIT binary patch literal 521 zcmV+k0`~ohP)LM7?1qp&z9{}b(1^faq5OcQ!BmmS7 z3)=x3fbX4PJK(vSfbLze9Z>y-a~H@uz#xQo09sNZvnddyci!YSfN!A*M|8~3#-=PT z00IG!z-rwM#s?^AG<9_-jV|?>0~#Q-tpOIdya(6f@#*Kzu*L9`0qF-|JOv`65CFq4 zxaLLWi+OM;Cc$khB-*hR(xn4J%D zAlYw;*%ZyDiGB`{*!%TTAd*|l4_^d(1y5?WP?V9D1t2YYVylfJjrKPH+M*{nT4~xy zFVbPADfV8dy-u$lMmQ8c?kuqQpD7egupR)T7b_{E=zai>uF+CN(Z^A^4j{mVX^JR1 zzaLGf?o+wvcSP6A;l<(h2;uH#eAVw>K$M;ZOquW_p+jULyG00000 LNkvXXu0mjfEL+S_ literal 0 HcmV?d00001 diff --git a/modules/suravenir/module.py b/modules/suravenir/module.py new file mode 100644 index 0000000000..e811c4354f --- /dev/null +++ b/modules/suravenir/module.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Arthur Huillet +# +# 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 . + +from __future__ import unicode_literals + + +from weboob.capabilities.base import find_object +from weboob.capabilities.bank import CapBankWealth, AccountNotFound +from weboob.tools.backend import Module, BackendConfig +from weboob.tools.value import ValueBackendPassword + +from .browser import Suravenir + + +__all__ = ['SuravenirModule'] + + +class SuravenirModule(Module, CapBankWealth): + NAME = 'suravenir' + MAINTAINER = 'Arthur Huillet' + EMAIL = 'arthur.huillet+weboob@free.fr' + VERSION = '1.4' + LICENSE = 'AGPLv3+' + DESCRIPTION = u'Assurance-vie Suravenir à travers différents courtiers (assurancevie.com, linxea, ...)' + CONFIG = BackendConfig( + ValueBackendPassword('broker', label='Courtier', choices=['assurancevie.com', 'linxea'], masked=False, required=True), + ValueBackendPassword('login', label='Identifiant', masked=False, required=True), + ValueBackendPassword('password', label='Mot de passe', required=True)) + BROWSER = Suravenir + + def create_default_browser(self): + return self.create_browser( + self.config['broker'].get(), + self.config['login'].get(), + self.config['password'].get() + ) + + def get_account(self, id): + return find_object(self.iter_accounts(), id=id, error=AccountNotFound) + + def iter_accounts(self): + return self.browser.get_accounts_list() + + def iter_coming(self, account): + raise NotImplementedError() + + def iter_history(self, account): + return self.browser.iter_history(account) + + def iter_investment(self, account): + return self.browser.iter_investments(account) + diff --git a/modules/suravenir/pages.py b/modules/suravenir/pages.py new file mode 100644 index 0000000000..502709cc50 --- /dev/null +++ b/modules/suravenir/pages.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Copyright(C) 2018 Arthur Huillet +# +# 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 . + + +from __future__ import unicode_literals + +from weboob.browser.elements import ListElement, TableElement, ItemElement, method +from weboob.browser.filters.html import AbsoluteLink, TableCell, Link +from weboob.browser.filters.standard import CleanText, CleanDecimal, Date +from weboob.capabilities import NotAvailable +from weboob.capabilities.bank import Account, Investment, Transaction +from weboob.browser.pages import HTMLPage, LoggedPage, pagination + + +class LoginPage(HTMLPage): + def login(self, login, passwd): + form = self.get_form(id='_58_fm') + form['_58_login'] = login + form['_58_password'] = passwd + form.submit() + + +class AccountsList(LoggedPage, HTMLPage): + @method + class get_contracts(ListElement): + item_xpath = '//tr[contains(@class, "results-row")]' + + class item(ItemElement): + klass = Account + + obj_label = CleanText('./td[contains(@class, "col-1")]/a') + obj_id = CleanText('./td[contains(@class, "col-2")]/a', replace=[(' ', '')]) + obj_balance = CleanDecimal('./td[contains(@class, "col-3")]', replace_dots=True) + obj__detail_link = AbsoluteLink('./td[contains(@class, "col-2")]/a') + obj_type = Account.TYPE_LIFE_INSURANCE + + +class InvestmentList(LoggedPage, HTMLPage): + @method + class iter_investments(TableElement): + head_xpath = '//thead[@class="table-columns"]/tr/th/text()' + item_xpath = '//tbody[@class="table-data"]/tr[contains(@class, "results-row")]' + + col_ISIN = u"Code ISIN" + col_fund = u"Libellé support" + col_qty = u"Nb parts" + col_date = u"Date VL*" + col_unitvalue = u"VL*" + col_unitprice = u"Prix de revient" + col_perf = u"Perf." + col_valuation = u"Solde" + + class item(ItemElement): + klass = Investment + obj_label = CleanText(TableCell("fund")) + obj_description = obj_label + obj_code = CleanText(TableCell("ISIN"), default=NotAvailable) + obj_code_type = Investment.CODE_TYPE_ISIN + obj_quantity = CleanDecimal(TableCell("qty"), replace_dots=True, default=NotAvailable) + obj_unitprice = CleanDecimal(TableCell("unitprice"), replace_dots=True, default=NotAvailable) + obj_unitvalue = CleanDecimal(TableCell("unitvalue"), replace_dots=True, default=NotAvailable) + obj_valuation = CleanDecimal(TableCell("valuation"), replace_dots=True, default=NotAvailable) + obj_vdate = Date(CleanText(TableCell("date")), dayfirst=True, default=NotAvailable) + obj_diff_percent = CleanDecimal(TableCell("perf"), replace_dots=True, default=NotAvailable) + + +class AccountHistory(LoggedPage, HTMLPage): + @pagination + @method + class iter_history(TableElement): + next_page = Link('(//ul[contains(@class, "lfr-pagination-buttons")])[2]/li[@class=" next"]/a[contains(text(), "Suivant")]') + head_xpath = '//thead[@class="table-columns"]/tr/th/div/a/text()[1]' + item_xpath = '//tbody[@class="table-data"]/tr[contains(@class, "results-row")]' + + col_date = u"Date de l'opération" + col_label = u"Libellé de l'opération" + col_amount = u"Montant" + + class item(ItemElement): + klass = Transaction + obj_date = Date(CleanText(TableCell("date")), dayfirst=True, default=NotAvailable) + obj_raw = CleanText(TableCell("label")) + obj_label = CleanText(TableCell("label")) + obj_amount = CleanDecimal(TableCell("amount"), replace_dots=True, default=NotAvailable) + + def obj__transaction_detail(self): + return AbsoluteLink((TableCell("label")(self)[0]).xpath('.//a')) diff --git a/modules/suravenir/test.py b/modules/suravenir/test.py new file mode 100644 index 0000000000..84881f1619 --- /dev/null +++ b/modules/suravenir/test.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Arthur Huillet +# +# 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 . + +from __future__ import unicode_literals + + +from weboob.tools.test import BackendTest + + +class SuravenirTest(BackendTest): + MODULE = 'suravenir' + def test_suravenir(self): + l = list(self.backend.iter_accounts()) + self.assertTrue(len(l) > 0) + a = l[0] + list(self.backend.iter_history(a)) diff --git a/tools/py3-compatible.modules b/tools/py3-compatible.modules index 6da804819a..e11893825d 100644 --- a/tools/py3-compatible.modules +++ b/tools/py3-compatible.modules @@ -120,6 +120,7 @@ spirica sprunge sueurdemetal supertoinette +suravenir tumblr tvsubtitles twitter -- GitLab