The new woob repository is here: https://gitlab.com/woob/woob. This gitlab will be removed soon.

The new woob repository is here: https://gitlab.com/woob/woob. This gitlab will be removed soon.

Commit a2dda015 authored by Vincent A's avatar Vincent A

[bolden] new CapBank/CapDocument module for crowlending site

parent 6cbfb7e1
# -*- coding: utf-8 -*-
# Copyright(C) 2018 Vincent A
#
# 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 <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from .module import BoldenModule
__all__ = ['BoldenModule']
# -*- coding: utf-8 -*-
# Copyright(C) 2018 Vincent A
#
# 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 <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from datetime import timedelta, datetime
from weboob.browser import LoginBrowser, need_login, URL
from weboob.capabilities.bill import Document
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<start>[\d-]+)&endDate=(?P<end>[\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()
@need_login
def iter_history(self, account):
if account.id != MAIN_ID:
return []
return self._iter_all_history()
def _iter_all_history(self):
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 acc in self.iter_accounts():
if acc.id == MAIN_ID:
continue
doc = Document()
doc.id = acc.id
doc.url = acc._docurl
doc.label = 'Contrat %s' % acc.label
doc.type = 'other'
doc.format = 'pdf'
yield doc
# -*- coding: utf-8 -*-
# Copyright(C) 2018 Vincent A
#
# 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 <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword
from weboob.capabilities.bank import CapBank, Account
from weboob.capabilities.base import find_object
from weboob.capabilities.bill import (
CapDocument, Subscription, SubscriptionNotFound, DocumentNotFound, Document,
)
from weboob.capabilities.profile import CapProfile
from .browser import BoldenBrowser
__all__ = ['BoldenModule']
class BoldenModule(Module, CapBank, CapDocument, CapProfile):
NAME = 'bolden'
DESCRIPTION = 'Bolden'
MAINTAINER = 'Vincent A'
EMAIL = 'dev@indigo.re'
LICENSE = 'AGPLv3+'
VERSION = '1.4'
BROWSER = BoldenBrowser
CONFIG = BackendConfig(
ValueBackendPassword('login', label='Email', masked=False),
ValueBackendPassword('password', label='Mot de passe'),
)
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 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()
# -*- coding: utf-8 -*-
# Copyright(C) 2018 Vincent A
#
# 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 <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from decimal import Decimal
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.bank import Account, Transaction
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 get_main(ItemElement):
klass = Account
obj_id = MAIN_ID
obj_label = 'Compte Bolden'
obj_type = Account.TYPE_CHECKING
obj_currency = 'EUR'
obj_balance = CleanDecimal('//div[p[has-class("investor-state") and contains(text(),"Fonds disponibles :")]]/p[has-class("investor-status")]', replace_dots=True)
#obj_coming = CleanDecimal('//div[p[has-class("investor-state") and contains(text(),"Capital restant dû :")]]/p[has-class("investor-status")]', replace_dots=True)
class iter_lends(TableElement):
head_xpath = '//div[@class="tab-wallet"]/table/thead//td'
col_label = 'Emprunteur'
col_coming = 'Capital restant dû'
col_doc = 'Contrat'
item_xpath = '//div[@class="tab-wallet"]/table/tbody/tr'
class item(ItemElement):
klass = Account
obj_label = CleanText(TableCell('label'))
obj_id = Slugify(Field('label'))
obj_type = Account.TYPE_SAVINGS
obj_currency = 'EUR'
obj_coming = CleanDecimal(TableCell('coming'), replace_dots=True)
obj_balance = Decimal('0')
def obj__docurl(self):
return urljoin(self.page.url, Link('.//a')(TableCell('doc')(self)[0]))
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]'),
)
......@@ -28,6 +28,7 @@ blablacar
blogspot
bnporc
bnppere
bolden
boursorama
bp
bred
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment