diff --git a/modules/enercoop/__init__.py b/modules/enercoop/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..24b5bab946bc4499400835ec78a040a6ec53814e --- /dev/null +++ b/modules/enercoop/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2020 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 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 + + +from .module import EnercoopModule + + +__all__ = ['EnercoopModule'] diff --git a/modules/enercoop/browser.py b/modules/enercoop/browser.py new file mode 100644 index 0000000000000000000000000000000000000000..8cc7a6b2707a295c977e3d782af3c320a6d1653b --- /dev/null +++ b/modules/enercoop/browser.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2020 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 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 . + +# flake8: compatible + +from __future__ import unicode_literals + +from weboob.browser import LoginBrowser, URL, need_login + +from .pages import BillsPage, ProfilePage + + +class EnercoopBrowser(LoginBrowser): + BASEURL = 'https://espace-client.enercoop.fr' + + login = URL('/login') + bills = URL( + r'/mon-espace/factures/', + r'/mon-espace/factures/\?c=(?P\d+)', + BillsPage + ) + + profile = URL( + r'/mon-espace/compte/', + r'/mon-espace/compte/\?c=(?P\d+)', + ProfilePage + ) + + def do_login(self): + self.login.go(data={ + 'email': self.username, + 'password': self.password, + }) + + def export_session(self): + return { + **super().export_session(), + 'url': self.bills.build(), + } + + @need_login + def iter_subscription(self): + self.bills.go() + subs = {sub.id: sub for sub in self.page.iter_other_subscriptions()} + if subs: + self.bills.go(id=next(iter(subs))) + subs.update({sub.id: sub for sub in self.page.iter_other_subscriptions()}) + + for sub in subs: + self.profile.go(id=sub) + self.page.fill_sub(subs[sub]) + + return subs.values() + + raise NotImplementedError("how to get info when no selector?") + + @need_login + def iter_documents(self, id): + self.bills.go(id=id) + return self.page.iter_documents() + + @need_login + def download_document(self, document): + return self.open(document.url).content diff --git a/modules/enercoop/favicon.png b/modules/enercoop/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..49969fe89054178cd11d2b9056a07a02e6f49c5a Binary files /dev/null and b/modules/enercoop/favicon.png differ diff --git a/modules/enercoop/module.py b/modules/enercoop/module.py new file mode 100644 index 0000000000000000000000000000000000000000..194767f2b525a4879efb36c898db452e272a91a1 --- /dev/null +++ b/modules/enercoop/module.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2020 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 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 . + +# flake8: compatible + +from __future__ import unicode_literals + +from weboob.tools.backend import Module, BackendConfig +from weboob.tools.value import ValueBackendPassword +from weboob.capabilities.bill import ( + DocumentTypes, CapDocument, Subscription, +) + +from .browser import EnercoopBrowser + + +__all__ = ['EnercoopModule'] + + +class EnercoopModule(Module, CapDocument): + NAME = 'enercoop' + DESCRIPTION = 'Enercoop' + MAINTAINER = 'Vincent A' + EMAIL = 'dev@indigo.re' + LICENSE = 'LGPLv3+' + VERSION = '2.1' + + BROWSER = EnercoopBrowser + + CONFIG = BackendConfig( + ValueBackendPassword('email', regexp='.+@.+', masked=False), + ValueBackendPassword('password'), + ) + + accepted_document_types = (DocumentTypes.BILL,) + + def create_default_browser(self): + return self.create_browser(self.config['email'].get(), self.config['password'].get()) + + def iter_subscription(self): + return self.browser.iter_subscription() + + def iter_documents(self, subscription): + if isinstance(subscription, Subscription): + subscription = subscription.id + return self.browser.iter_documents(subscription) + + def download_document(self, id): + return self.browser.download_document(id) diff --git a/modules/enercoop/pages.py b/modules/enercoop/pages.py new file mode 100644 index 0000000000000000000000000000000000000000..70235a98b9507bf831f06e56764a0f1c17b1a5a2 --- /dev/null +++ b/modules/enercoop/pages.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2020 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 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 . + +# flake8: compatible + +from __future__ import unicode_literals + +from weboob.browser.elements import ( + ItemElement, ListElement, method +) +from weboob.browser.pages import HTMLPage +from weboob.browser.filters.standard import ( + Date, CleanDecimal, CleanText, Format, Regexp, QueryValue, +) +from weboob.browser.filters.html import ( + AbsoluteLink, Attr, FormValue, +) +from weboob.capabilities.base import NotAvailable +from weboob.capabilities.address import PostalAddress +from weboob.capabilities.profile import Person +from weboob.capabilities.bill import ( + Subscription, Bill, +) + + +class LoggedMixin: + @property + def logged(self): + return bool(self.doc.xpath('//a[@id="logout"]')) + + +class BillsPage(LoggedMixin, HTMLPage): + @method + class iter_other_subscriptions(ListElement): + item_xpath = '//li[@id="contract-switch"]//a[@role="menuitem"][@href]' + + class item(ItemElement): + klass = Subscription + + obj_url = AbsoluteLink('.') + obj_id = QueryValue(obj_url, 'c') + obj_number = Regexp(CleanText('.'), r'(CNT-\d+-\d+)') + obj_label = Format( + '%s %s', + CleanText('./span', children=False), + obj_number, + ) + + @method + class iter_documents(ListElement): + item_xpath = '//div[@id="invoices-container"]/ul/li' + + class item(ItemElement): + klass = Bill + + obj_id = Attr('.', 'data-invoice-id') + + obj_total_price = CleanDecimal.French('.//div[has-class("amount")]') + obj_currency = 'EUR' + + obj_date = Date(CleanText('.//div[has-class("dueDate")]'), dayfirst=True) + + obj_format = 'pdf' + + def obj_url(self): + url = AbsoluteLink('.//a[@target="_blank"]')(self) + if '//download' in url: + return NotAvailable + return url + + def obj_has_file(self): + return bool(self.obj_url()) + + +class ProfilePage(LoggedMixin, HTMLPage): + @method + class get_profile(ItemElement): + klass = Person + + obj_name = FormValue('//input[@name="name"]') + obj_email = FormValue('//input[@name="email"]') + obj_phone = Format( + '%s%s', + Regexp( + CleanText(FormValue('//select[@id="phone_number_indic"]')), + r'\+\d+' + ), + FormValue('//input[@id="phone_number"]') + ) + obj_country = 'France' + + class obj_postal_address(ItemElement): + klass = PostalAddress + + # there can be a lot of whitespace in city name + obj_city = CleanText(FormValue('//select[@id="cities"]')) + + obj_street = Format( + '%s %s', + FormValue('//input[@name="num"]'), + FormValue('//input[@name="street"]') + ) + obj_postal_code = FormValue('//input[@name="zip_code"]') + obj_country = 'France' + + def fill_sub(self, sub): + sub._profile = self.get_profile() + sub.subscriber = sub._profile.name diff --git a/modules/enercoop/test.py b/modules/enercoop/test.py new file mode 100644 index 0000000000000000000000000000000000000000..f0e7da705215bb0b6c6f5848bd45ad6c55817ad6 --- /dev/null +++ b/modules/enercoop/test.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2020 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 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 + + +from weboob.tools.test import BackendTest + + +class EnercoopTest(BackendTest): + MODULE = 'enercoop' + + def test_subs(self): + subs = list(self.backend.iter_subscription()) + assert subs + sub = subs[0] + assert subs[0].id + assert subs[0].label + + docs = list(self.backend.iter_documents(sub)) + assert docs + + doc = docs[0] + assert doc.id + assert doc.label + assert doc.total_price + assert doc.url