From 3d03dd0f050ae19f27e13a6372061a0221e80961 Mon Sep 17 00:00:00 2001 From: Florian Duguet Date: Tue, 14 May 2019 12:28:48 +0200 Subject: [PATCH] [ldlc + materielnet] ldlc is now an abstract module of materielnet update materielnet because some features are different between them Closes: 40300@sibi Closes: 10942@zendesk --- modules/ldlc/browser.py | 41 +++++++++--------- modules/ldlc/materielnet_pages.py | 1 + modules/ldlc/module.py | 38 ++++++----------- modules/ldlc/pages.py | 71 +++++++++++++------------------ modules/materielnet/browser.py | 27 ++++++++---- modules/materielnet/pages.py | 16 ++++--- 6 files changed, 94 insertions(+), 100 deletions(-) create mode 120000 modules/ldlc/materielnet_pages.py diff --git a/modules/ldlc/browser.py b/modules/ldlc/browser.py index 63fcb98c68..9935d53a3c 100644 --- a/modules/ldlc/browser.py +++ b/modules/ldlc/browser.py @@ -17,11 +17,29 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . - -from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser import LoginBrowser, AbstractBrowser, URL, need_login from weboob.exceptions import BrowserIncorrectPassword -from .pages import LoginPage, HomePage, ParBillsPage, ProBillsPage +from .pages import HomePage, LoginPage, ProBillsPage, DocumentsPage + + +class LdlcParBrowser(AbstractBrowser): + PARENT = 'materielnet' + BASEURL = 'https://secure2.ldlc.com' + + documents = URL(r'/fr-fr/Orders/PartialCompletedOrdersHeader', DocumentsPage) + + def __init__(self, *args, **kwargs): + super(LdlcParBrowser, self).__init__(*args, **kwargs) + self.lang = 'fr-fr/' + + @need_login + def iter_documents(self, subscription): + json_response = self.location('/fr-fr/Orders/CompletedOrdersPeriodSelection').json() + + for data in json_response: + for doc in self.location('/fr-fr/Orders/PartialCompletedOrdersHeader', data=data).page.get_documents(subid=subscription.id): + yield doc class LdlcBrowser(LoginBrowser): @@ -38,22 +56,7 @@ def do_login(self): @need_login def get_subscription_list(self): - return self.home.stay_or_go().get_list() - - -class LdlcParBrowser(LdlcBrowser): - BASEURL = 'https://secure.ldlc.com' - - bills = URL(r'/Account/CommandListingPage.aspx', ParBillsPage) - - @need_login - def iter_documents(self, subscription): - self.bills.stay_or_go() - for value in self.page.get_range(): - self.bills.go(data={'ctl00$ctl00$cphMainContent$cphMainContent$ddlDate': value, '__EVENTTARGET': 'ctl00$cphMainContent$ddlDate'}) - - for bill in self.page.iter_documents(subid=subscription.id): - yield bill + return self.home.stay_or_go().get_subscriptions() class LdlcProBrowser(LdlcBrowser): diff --git a/modules/ldlc/materielnet_pages.py b/modules/ldlc/materielnet_pages.py new file mode 120000 index 0000000000..aae0d17730 --- /dev/null +++ b/modules/ldlc/materielnet_pages.py @@ -0,0 +1 @@ +../materielnet/pages.py \ No newline at end of file diff --git a/modules/ldlc/module.py b/modules/ldlc/module.py index c66ec87726..3ddb0d1a5d 100644 --- a/modules/ldlc/module.py +++ b/modules/ldlc/module.py @@ -17,10 +17,11 @@ # 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 -from weboob.capabilities.bill import CapDocument, Subscription, Bill, SubscriptionNotFound, DocumentNotFound -from weboob.capabilities.base import find_object -from weboob.tools.backend import Module, BackendConfig +from weboob.capabilities.bill import CapDocument, Bill +from weboob.capabilities.base import empty +from weboob.tools.backend import AbstractModule, BackendConfig from weboob.tools.value import ValueBackendPassword, Value from .browser import LdlcParBrowser, LdlcProBrowser @@ -29,10 +30,10 @@ __all__ = ['LdlcModule'] -class LdlcModule(Module, CapDocument): +class LdlcModule(AbstractModule, CapDocument): NAME = 'ldlc' - DESCRIPTION = u'ldlc website' - MAINTAINER = u'Vincent Paredes' + DESCRIPTION = 'ldlc website' + MAINTAINER = 'Vincent Paredes' EMAIL = 'vparedes@budget-insight.com' LICENSE = 'LGPLv3+' VERSION = '1.6' @@ -41,34 +42,21 @@ class LdlcModule(Module, CapDocument): Value('website', label='Site web', default='part', choices={'pro': 'Professionnels', 'part': 'Particuliers'})) + PARENT = 'materielnet' + def create_default_browser(self): if self.config['website'].get() == 'part': self.BROWSER = LdlcParBrowser + return self.create_browser(self.config['login'].get(), self.config['password'].get(), weboob=self.weboob) else: self.BROWSER = LdlcProBrowser - - return self.create_browser(self.config['login'].get(), self.config['password'].get()) - - def iter_subscription(self): - return self.browser.get_subscription_list() - - 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 iter_documents(self, subscription): - if not isinstance(subscription, Subscription): - subscription = self.get_subscription(subscription) - return self.browser.iter_documents(subscription) + return self.create_browser(self.config['login'].get(), self.config['password'].get()) def download_document(self, bill): if not isinstance(bill, Bill): bill = self.get_document(bill) + if empty(bill.url): + return if self.config['website'].get() == 'part': return self.browser.open(bill.url).content else: diff --git a/modules/ldlc/pages.py b/modules/ldlc/pages.py index 9e0003bf8c..c9694d9162 100644 --- a/modules/ldlc/pages.py +++ b/modules/ldlc/pages.py @@ -19,12 +19,16 @@ from __future__ import unicode_literals -from weboob.browser.pages import HTMLPage, LoggedPage -from weboob.browser.filters.standard import CleanDecimal, CleanText, Env, Format, QueryValue, TableCell, Currency +from weboob.browser.pages import HTMLPage, LoggedPage, PartialHTMLPage +from weboob.browser.filters.standard import ( + CleanDecimal, CleanText, Env, Format, QueryValue, TableCell, Currency, Regexp, Async, Date, Field, +) from weboob.browser.elements import ListElement, ItemElement, method, TableElement -from weboob.browser.filters.html import Attr -from weboob.capabilities.bill import Bill, Subscription +from weboob.browser.filters.html import Attr, Link +from weboob.capabilities import NotAvailable +from weboob.capabilities.bill import Bill, Subscription, DocumentTypes from weboob.tools.date import parse_french_date +from .materielnet_pages import MyAsyncLoad class HiddenFieldPage(HTMLPage): @@ -35,7 +39,7 @@ def get_ctl00_actScriptManager_HiddenField(self): class HomePage(LoggedPage, HTMLPage): @method - class get_list(ListElement): + class get_subscriptions(ListElement): item_xpath = '//div[@id="divAccueilInformationClient"]//div[@id="divInformationClient"]' class item(ItemElement): klass = Subscription @@ -45,56 +49,41 @@ class item(ItemElement): obj_label = CleanText('.//div[@id="divlblTitleFirstNameLastName"]//span') -class LoginPage(HiddenFieldPage): - def login(self, username, password, website): - form = self.get_form(id='aspnetForm') - if website == 'part': - form["ctl00$ctl00$cphMainContent$cphMainContent$txbMail"] = username - form["ctl00$ctl00$cphMainContent$cphMainContent$txbPassword"] = password - form["__EVENTTARGET"] = "ctl00$ctl00$cphMainContent$cphMainContent$butConnexion" - form["ctl00_ctl00_actScriptManager_HiddenField"] = self.get_ctl00_actScriptManager_HiddenField() - else: - form["ctl00$cphMainContent$txbMail"] = username - form["ctl00$cphMainContent$txbPassword"] = password - form["__EVENTTARGET"] = "ctl00$cphMainContent$butConnexion" - form["ctl00_ctl00_actScriptManager_HiddenField"] = self.get_ctl00_actScriptManager_HiddenField() +class LoginPage(HTMLPage): + def login(self, username, password): + form = self.get_form(xpath='//form[contains(@action, "/Login/Login")]') + form['Email'] = username + form['Password'] = password form.submit() def get_error(self): return CleanText('//span[contains(text(), "Identifiants incorrects")]')(self.doc) -class BillsPage(LoggedPage, HiddenFieldPage): - def get_range(self): - for value in self.doc.xpath('//div[@class="commandListing content clearfix"]//select/option/@value'): - yield value - - -class ParBillsPage(BillsPage): +class DocumentsPage(LoggedPage, PartialHTMLPage): @method - class iter_documents(TableElement): - ignore_duplicate = True - item_xpath = '//table[@id="TopListing"]/tr[position()>1]' - head_xpath = '//table[@id="TopListing"]/tr[@class="TopListingHeader"]/td' - - col_id = 'N° de commande' - col_date = 'Date' - col_price = 'Montant TTC' + class get_documents(ListElement): + item_xpath = '//div[@class="dsp-row"]' class item(ItemElement): klass = Bill - obj_id = Format('%s_%s', Env('subid'), CleanText(TableCell('id'))) - obj_url = Attr('./td[@class="center" or @class="center pdf"]/a', 'href') + load_details = Link('.//a[contains(text(), "détails")]') & MyAsyncLoad + + obj_id = Format('%s_%s', Env('subid'), Field('label')) + obj_url = Async('details') & Link('//a[span[contains(text(), "Télécharger la facture")]]', default=NotAvailable) + obj_date = Date(CleanText('./div[contains(@class, "cell-date")]'), dayfirst=True) obj_format = 'pdf' - obj_price = CleanDecimal(TableCell('price'), replace_dots=True) - obj_currency = Currency(TableCell('price')) + obj_label = Regexp(CleanText('./div[contains(@class, "cell-nb-order")]'), r' (.*)') + obj_type = DocumentTypes.BILL + obj_price = CleanDecimal(CleanText('./div[contains(@class, "cell-value")]'), replace_dots=(' ', '€')) + obj_currency = 'EUR' - def obj_date(self): - return parse_french_date(CleanText(TableCell('date'))(self)).date() - def condition(self): - return CleanText().filter(self.el.xpath('.//td')[-1]) != "" and len(self.el.xpath('./td[@class="center" or @class="center pdf"]/a/@href')) == 1 +class BillsPage(LoggedPage, HiddenFieldPage): + def get_range(self): + for value in self.doc.xpath('//div[@class="commandListing content clearfix"]//select/option/@value'): + yield value class ProBillsPage(BillsPage): diff --git a/modules/materielnet/browser.py b/modules/materielnet/browser.py index b1695a62f5..e86cf501b1 100644 --- a/modules/materielnet/browser.py +++ b/modules/materielnet/browser.py @@ -21,28 +21,37 @@ from weboob.browser import LoginBrowser, URL, need_login from weboob.exceptions import BrowserIncorrectPassword -from .pages import LoginPage, CaptchaPage, ProfilPage, DocumentsPage, DocumentsDetailsPage +from .pages import LoginPage, CaptchaPage, ProfilePage, DocumentsPage, DocumentsDetailsPage + + +class MyURL(URL): + def go(self, *args, **kwargs): + kwargs['lang'] = self.browser.lang + return super(MyURL, self).go(*args, **kwargs) class MaterielnetBrowser(LoginBrowser): BASEURL = 'https://secure.materiel.net' - login = URL(r'/Login/Login', LoginPage) + login = MyURL(r'/(?P.*)Login/Login', LoginPage) captcha = URL('/pm/client/captcha.html', CaptchaPage) - profil = URL(r'/Account/InformationsSection', - r'/pro/Account/InformationsSection', ProfilPage) - documents = URL(r'/Orders/PartialCompletedOrdersHeader', - r'/pro/Orders/PartialCompletedOrdersHeader', DocumentsPage) - document_details = URL(r'/Orders/PartialCompletedOrderContent', - r'/pro/Orders/PartialCompletedOrderContent', DocumentsDetailsPage) + profile = MyURL(r'/(?P.*)Account/InformationsSection', + r'/pro/Account/InformationsSection', ProfilePage) + documents = MyURL(r'/(?P.*)Orders/PartialCompletedOrdersHeader', + r'/pro/Orders/PartialCompletedOrdersHeader', DocumentsPage) + document_details = MyURL(r'/(?P.*)Orders/PartialCompletedOrderContent', + r'/pro/Orders/PartialCompletedOrderContent', DocumentsDetailsPage) def __init__(self, *args, **kwargs): super(MaterielnetBrowser, self).__init__(*args, **kwargs) self.is_pro = None + self.lang = '' def par_or_pro_location(self, url, *args, **kwargs): if self.is_pro: url = '/pro' + url + elif self.lang: + url = '/' + self.lang[:-1] + url return super(MaterielnetBrowser, self).location(url, *args, **kwargs) @@ -63,7 +72,7 @@ def do_login(self): @need_login def get_subscription_list(self): - return self.par_or_pro_location('/Account/InformationsSection').page.get_list() + return self.par_or_pro_location('/Account/InformationsSection').page.get_subscriptions() @need_login def iter_documents(self, subscription): diff --git a/modules/materielnet/pages.py b/modules/materielnet/pages.py index 36f0fb09ed..2fd24b7194 100644 --- a/modules/materielnet/pages.py +++ b/modules/materielnet/pages.py @@ -58,18 +58,22 @@ def get_error(self): return CleanText('//div[@class="captcha-block"]/p[1]/text()')(self.doc) -class ProfilPage(LoggedPage, HTMLPage): +class ProfilePage(LoggedPage, HTMLPage): @method - class get_list(ListElement): + class get_subscriptions(ListElement): class item(ItemElement): klass = Subscription obj_subscriber = Format('%s %s', Attr('//input[@id="FirstName"]', 'value'), Attr('//input[@id="LastName"]', 'value')) - obj_id = Env('subid') - obj_label = obj_id - def parse(self, el): - self.env['subid'] = self.page.browser.username + def obj_id(self): + if 'Materielnet' in self.page.browser.__class__.__name__: + filter_id = CleanText('//p[@class="NumCustomer"]/span') + else: # ldlc + filter_id = Regexp(CleanText('//span[@class="nclient"]'), r'Nº client : (.*)') + + return filter_id(self) + obj_label = obj_id class MyAsyncLoad(Filter): -- GitLab