pax_global_header 0000666 0000000 0000000 00000000064 13434601454 0014516 g ustar 00root root 0000000 0000000 52 comment=d0a72914b1b13c7766ca764ce93a9d23b473bfe7
woob-d0a72914b1b13c7766ca764ce93a9d23b473bfe7-modules-bolden/ 0000775 0000000 0000000 00000000000 13434601454 0022376 5 ustar 00root root 0000000 0000000 woob-d0a72914b1b13c7766ca764ce93a9d23b473bfe7-modules-bolden/modules/ 0000775 0000000 0000000 00000000000 13434601454 0024046 5 ustar 00root root 0000000 0000000 woob-d0a72914b1b13c7766ca764ce93a9d23b473bfe7-modules-bolden/modules/bolden/ 0000775 0000000 0000000 00000000000 13434601454 0025311 5 ustar 00root root 0000000 0000000 woob-d0a72914b1b13c7766ca764ce93a9d23b473bfe7-modules-bolden/modules/bolden/__init__.py 0000664 0000000 0000000 00000001560 13434601454 0027424 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2018 Vincent A
#
# 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 .
from __future__ import unicode_literals
from .module import BoldenModule
__all__ = ['BoldenModule']
woob-d0a72914b1b13c7766ca764ce93a9d23b473bfe7-modules-bolden/modules/bolden/browser.py 0000664 0000000 0000000 00000006501 13434601454 0027350 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2018 Vincent A
#
# 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 .
from __future__ import unicode_literals
from datetime import timedelta, datetime
from weboob.browser import LoginBrowser, need_login, URL
from weboob.capabilities.bill import DocumentTypes, Document
from weboob.tools.capabilities.bank.investments import create_french_liquidity
from .pages import (
LoginPage, HomeLendPage, PortfolioPage, OperationsPage, MAIN_ID, ProfilePage,
)
class BoldenBrowser(LoginBrowser):
BASEURL = 'https://bolden.fr/'
login = URL(r'/connexion', LoginPage)
home_lend = URL(r'/tableau-de-bord-investisseur', HomeLendPage)
profile = URL(r'/mon-profil', ProfilePage)
portfolio = URL(r'/InvestorDashboard/GetPortfolio', PortfolioPage)
operations = URL(r'/InvestorDashboard/GetOperations\?startDate=(?P[\d-]+)&endDate=(?P[\d-]+)', OperationsPage)
def do_login(self):
self.login.go()
self.page.do_login(self.username, self.password)
if self.login.is_here():
self.page.check_error()
assert False, 'should not be on login page'
@need_login
def iter_accounts(self):
self.portfolio.go()
return self.page.iter_accounts()
def iter_investments(self):
self.portfolio.go()
yield create_french_liquidity(self.page.get_liquidity())
for inv in self.page.iter_investments():
yield inv
@need_login
def iter_history(self, account):
if account.id != MAIN_ID:
return
end = datetime.now()
while True:
start = end - timedelta(days=365)
self.operations.go(start=start.strftime('%Y-%m-%d'), end=end.strftime('%Y-%m-%d'))
transactions = list(self.page.iter_history())
if not transactions:
break
last_with_date = None
for tr in transactions:
if tr.date is None:
tr.date = last_with_date.date
tr.label = '%s %s' % (last_with_date.label, tr.label)
else:
last_with_date = tr
yield tr
end = start
@need_login
def get_profile(self):
self.profile.go()
return self.page.get_profile()
@need_login
def iter_documents(self):
for inv in self.iter_investments():
if inv.label == "Liquidités":
continue
doc = Document()
doc.id = inv.id
doc.url = inv._docurl
doc.label = 'Contrat %s' % inv.label
doc.type = DocumentTypes.OTHER
doc.format = 'pdf'
yield doc
woob-d0a72914b1b13c7766ca764ce93a9d23b473bfe7-modules-bolden/modules/bolden/module.py 0000664 0000000 0000000 00000006374 13434601454 0027162 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2018 Vincent A
#
# 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 .
from __future__ import unicode_literals
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword
from weboob.capabilities.bank import CapBankWealth, Account
from weboob.capabilities.base import find_object
from weboob.capabilities.bill import (
CapDocument, Subscription, SubscriptionNotFound, DocumentNotFound, Document,
DocumentTypes,
)
from weboob.capabilities.profile import CapProfile
from .browser import BoldenBrowser
__all__ = ['BoldenModule']
class BoldenModule(Module, CapBankWealth, CapDocument, CapProfile):
NAME = 'bolden'
DESCRIPTION = 'Bolden'
MAINTAINER = 'Vincent A'
EMAIL = 'dev@indigo.re'
LICENSE = 'AGPLv3+'
VERSION = '1.5'
BROWSER = BoldenBrowser
CONFIG = BackendConfig(
ValueBackendPassword('login', label='Email', masked=False),
ValueBackendPassword('password', label='Mot de passe'),
)
accepted_document_types = (DocumentTypes.OTHER,)
def create_default_browser(self):
return self.create_browser(self.config['login'].get(), self.config['password'].get())
def iter_accounts(self):
return self.browser.iter_accounts()
def iter_history(self, account):
return self.browser.iter_history(account)
def iter_investment(self, account):
return self.browser.iter_investments()
def get_profile(self):
return self.browser.get_profile()
def iter_subscription(self):
sub = Subscription()
sub.id = '_bolden_'
sub.subscriber = self.get_profile().name
sub.label = 'Bolden %s' % sub.subscriber
return [sub]
def get_subscription(self, _id):
if _id == '_bolden_':
return self.iter_subscription()[0]
raise SubscriptionNotFound()
def iter_documents(self, sub):
if not isinstance(sub, Subscription):
sub = self.get_subscription(sub)
return self.browser.iter_documents()
def get_document(self, id):
return find_object(self.browser.iter_documents(), id=id, error=DocumentNotFound)
def download_document(self, doc):
if not isinstance(doc, Document):
doc = self.get_document(doc)
return self.browser.open(doc.url).content
def iter_resources(self, objs, split_path):
if Account in objs:
self._restrict_level(split_path)
return self.iter_accounts()
if Subscription in objs:
self._restrict_level(split_path)
return self.iter_subscription()
woob-d0a72914b1b13c7766ca764ce93a9d23b473bfe7-modules-bolden/modules/bolden/pages.py 0000664 0000000 0000000 00000012316 13434601454 0026765 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2018 Vincent A
#
# 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 .
from __future__ import unicode_literals
from weboob.browser.elements import ListElement, ItemElement, method, TableElement
from weboob.browser.filters.html import TableCell, Link, Attr
from weboob.browser.filters.standard import (
CleanText, CleanDecimal, Slugify, Date, Field, Format,
)
from weboob.browser.pages import HTMLPage, LoggedPage
from weboob.capabilities.base import NotAvailable
from weboob.capabilities.bank import Account, Transaction, Investment
from weboob.capabilities.profile import Profile
from weboob.exceptions import BrowserIncorrectPassword
from weboob.tools.compat import urljoin
MAIN_ID = '_bolden_'
class LoginPage(HTMLPage):
def do_login(self, username, password):
form = self.get_form(id='loginform')
form['Email'] = username
form['Password'] = password
form.submit()
def check_error(self):
msg = CleanText('//div[has-class("validation-summary-errors")]')(self.doc)
if 'Tentative de connexion invalide' in msg:
raise BrowserIncorrectPassword(msg)
class HomeLendPage(LoggedPage, HTMLPage):
pass
class PortfolioPage(LoggedPage, HTMLPage):
@method
class iter_accounts(ListElement):
class item(ItemElement):
klass = Account
obj_id = MAIN_ID
obj_label = 'Compte Bolden'
obj_type = Account.TYPE_MARKET
obj_currency = 'EUR'
obj_balance = CleanDecimal.French('//div[p[has-class("investor-state") and contains(text(),"Total compte Bolden :")]]/p[has-class("investor-status")]')
obj_valuation_diff = CleanDecimal.French('//div[has-class("rent-total")]')
@method
class iter_investments(TableElement):
head_xpath = '//div[@class="tab-wallet"]/table/thead//td'
col_label = 'Emprunteur'
col_valuation = 'Capital restant dû'
col_doc = 'Contrat'
col_diff = 'Intérêts perçus'
item_xpath = '//div[@class="tab-wallet"]/table/tbody/tr'
class item(ItemElement):
klass = Investment
obj_label = CleanText(TableCell('label'))
obj_id = Slugify(Field('label'))
obj_valuation = CleanDecimal(TableCell('valuation'), replace_dots=True)
obj_diff = CleanDecimal(TableCell('diff'), replace_dots=True, default=NotAvailable)
obj_code = NotAvailable
obj_code_type = NotAvailable
def condition(self):
# Investments without valuation are expired.
return CleanDecimal(TableCell('valuation'))(self)
def obj__docurl(self):
return urljoin(self.page.url, Link('.//a', default=NotAvailable)(TableCell('doc')(self)[0]))
def get_liquidity(self):
return CleanDecimal.French('//div[p[contains(text(), "Fonds disponibles")]]/p[has-class("investor-status")]')(self.doc)
class OperationsPage(LoggedPage, HTMLPage):
@method
class iter_history(TableElement):
head_xpath = '//div[@class="tab-wallet"]/table/thead//td'
col_date = 'Date'
col_label = 'Opération'
col_amount = 'Montant'
item_xpath = '//div[@class="tab-wallet"]/table/tbody/tr'
class item(ItemElement):
klass = Transaction
def condition(self):
return not Field('label')(self).startswith('dont ')
obj_label = CleanText(TableCell('label'))
def obj_amount(self):
v = CleanDecimal(TableCell('amount'), replace_dots=True)(self)
if Field('label')(self).startswith('Investissement'):
v = -v
return v
obj_date = Date(CleanText(TableCell('date')), dayfirst=True, default=None)
class ProfilePage(LoggedPage, HTMLPage):
@method
class get_profile(ItemElement):
klass = Profile
obj_name = Format(
'%s %s',
Attr('//input[@id="SubModel_FirstName"]', 'value'),
Attr('//input[@id="SubModel_LastName"]', 'value'),
)
obj_phone = Attr('//input[@id="SubModel_Phone"]', 'value')
obj_address = Format(
'%s %s %s %s %s',
Attr('//input[@id="SubModel_Address_Street"]', 'value'),
Attr('//input[@id="SubModel_Address_Suplement"]', 'value'),
Attr('//input[@id="SubModel_Address_PostalCode"]', 'value'),
Attr('//input[@id="SubModel_Address_City"]', 'value'),
CleanText('//select[@id="SubModel_Address_Country"]/option[@selected]'),
)