diff --git a/modules/ekwateur/__init__.py b/modules/ekwateur/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c85718c8d05a53e88d6f1bf69332fb47a174efb0 --- /dev/null +++ b/modules/ekwateur/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Phyks (Lucas Verney) +# +# 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 EkwateurModule + + +__all__ = ['EkwateurModule'] diff --git a/modules/ekwateur/browser.py b/modules/ekwateur/browser.py new file mode 100644 index 0000000000000000000000000000000000000000..e960a1d57f6bb235edc48544eba026dcf6c0644d --- /dev/null +++ b/modules/ekwateur/browser.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Phyks (Lucas Verney) +# +# 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 + +import itertools + + +from weboob.browser import LoginBrowser, need_login, URL +from weboob.exceptions import BrowserIncorrectPassword + +from .pages import BillsPage, DocumentsPage, LoginPage + + +class EkwateurBrowser(LoginBrowser): + BASEURL = 'https://mon-espace.ekwateur.fr/' + + login_page = URL('/se_connecter', LoginPage) + bills_page = URL('/mes_factures_et_acomptes', BillsPage) + documents_page = URL('/documents', DocumentsPage) + + def do_login(self): + self.login_page.go().do_login(self.username, self.password) + self.bills_page.stay_or_go() + if not self.bills_page.is_here(): + raise BrowserIncorrectPassword + + @need_login + def iter_subscriptions(self): + return self.bills_page.stay_or_go().get_subscriptions() + + @need_login + def iter_documents(self, sub_id): + return itertools.chain( + self.documents_page.stay_or_go().get_documents(sub_id=sub_id), + self.documents_page.stay_or_go().get_cgv(sub_id), + self.documents_page.stay_or_go().get_justificatif(sub_id), + self.bills_page.stay_or_go().get_bills(sub_id=sub_id) + ) diff --git a/modules/ekwateur/module.py b/modules/ekwateur/module.py new file mode 100644 index 0000000000000000000000000000000000000000..004900b2645a0f0f417d58fa08513bedf4d0d312 --- /dev/null +++ b/modules/ekwateur/module.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Phyks (Lucas Verney) +# +# 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.backend import Module, BackendConfig +from weboob.tools.value import Value, ValueBackendPassword +from weboob.capabilities.base import find_object +from weboob.capabilities.bill import ( + CapDocument, Document, DocumentNotFound, Subscription +) + +from .browser import EkwateurBrowser + + +__all__ = ['EkwateurModule'] + + +class EkwateurModule(Module, CapDocument): + NAME = 'ekwateur' + DESCRIPTION = 'ekwateur website' + MAINTAINER = 'Phyks (Lucas Verney)' + EMAIL = 'phyks@phyks.me' + LICENSE = 'AGPLv3+' + VERSION = '1.4' + + BROWSER = EkwateurBrowser + + CONFIG = BackendConfig( + Value('login', help='Email or identifier'), + ValueBackendPassword('password', help='Password'), + ) + + def create_default_browser(self): + return self.create_browser(self.config['login'].get(), self.config['password'].get()) + + def get_document(self, id): + """ + Get a document. + + :param id: ID of document + :rtype: :class:`Document` + :raises: :class:`DocumentNotFound` + """ + return find_object( + self.iter_documents(id.split("#")[-1]), + id=id, + error=DocumentNotFound + ) + + def download_document(self, doc): + if not isinstance(doc, Document): + doc = self.get_document(doc) + + if not doc.url: + return None + + return self.browser.open(doc.url).content + + def iter_documents(self, subscription): + """ + Iter documents. + + :param subscription: subscription to get documents + :type subscription: :class:`Subscription` + :rtype: iter[:class:`Document`] + """ + if isinstance(subscription, Subscription): + subscription = subscription.id + return self.browser.iter_documents(subscription) + + def iter_subscription(self): + """ + Iter subscriptions. + + :rtype: iter[:class:`Subscription`] + """ + return self.browser.iter_subscriptions() diff --git a/modules/ekwateur/pages.py b/modules/ekwateur/pages.py new file mode 100644 index 0000000000000000000000000000000000000000..46e47682fb50a7f75e3884c25f7083b7c5957235 --- /dev/null +++ b/modules/ekwateur/pages.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Phyks (Lucas Verney) +# +# 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 ( + ItemElement, ListElement, TableElement, method +) +from weboob.browser.pages import HTMLPage +from weboob.browser.filters.standard import ( + Date, CleanDecimal, CleanText, Currency, Env, Format, Regexp, Slugify, + TableCell +) +from weboob.browser.filters.html import AbsoluteLink, Attr, Link, XPath +from weboob.capabilities.base import NotAvailable +from weboob.capabilities.bill import Subscription, Bill, Document + + +class LoginPage(HTMLPage): + def do_login(self, login, password): + form = self.get_form(nr=0) + form['_username'] = login + form['_password'] = password + form.submit() + + +class EkwateurPage(HTMLPage): + @property + def logged(self): + return bool(self.doc.xpath(u'//*[has-class("menu__user__infos")]')) + + +class BillsPage(EkwateurPage): + @method + class get_subscriptions(ListElement): + item_xpath = '//a[has-class("nom_contrat")]' + + class item(ItemElement): + klass = Subscription + + def obj_id(self): + return Link('.')(self).split('/')[-1] + + def obj_label(self): + name = Attr('.', 'data-nomcontrat', default=None)(self) + if not name: + name = CleanText('.')(self) + return name + + @method + class get_bills(TableElement): + item_xpath = '//table[@id="dataHistorique"]/tbody/tr' + head_xpath = '//table[@id="dataHistorique"]/thead/tr/td/text()' + + col_date = 'Date' + col_type = 'Type' + col_amount = 'Montant' + col_status = 'Statut' + + class item(ItemElement): + klass = Bill + + obj_id = Format( + 'facture-%s-%s-%s#%s', + Slugify(CleanText(TableCell('date'))), + Slugify(CleanText(TableCell('amount'))), + Slugify(CleanText(TableCell('type'))), + Env('sub_id') + ) + obj_url = AbsoluteLink('./td[5]//a', default=NotAvailable) + obj_date = Date(CleanText(TableCell('date')), dayfirst=True) + obj_label = Format( + '%s %s %s', + CleanText(TableCell('type')), + CleanText(TableCell('amount')), + CleanText(TableCell('date')) + ) + obj_type = 'bill' + obj_price = CleanDecimal(TableCell('amount'), replace_dots=True) + obj_currency = Currency(TableCell('amount')) + obj_duedate = Date( + Regexp( + CleanText(TableCell('status')), + r'le (\d+)/(\d+)/(\d+)', + r'\1/\2/\3' + ), + dayfirst=True + ) + + def obj_format(self): + if self.obj_url(self): + return 'pdf' + return NotAvailable + + def obj_income(self): + if self.obj_price(self) < 0: + return True + return False + + +class DocumentsPage(EkwateurPage): + @method + class get_documents(TableElement): + item_xpath = '//table[@id="otherDocuments"]/tbody/tr' + head_xpath = '//table[@id="otherDocuments"]/thead/tr/td/text()' + + col_date = 'Date' + col_type = 'Type' + + class item(ItemElement): + klass = Document + + obj_date = Date(CleanText(TableCell('date')), dayfirst=True) + obj_format = 'pdf' + obj_label = CleanText(TableCell('type')) + obj_url = AbsoluteLink('./td[3]//a', default=NotAvailable) + obj_id = Format( + 'doc-%s-%s#%s', + Slugify(CleanText(TableCell('date'))), + Slugify(CleanText(TableCell('type'))), + Env('sub_id') + ) + + def get_justificatif(self, sub_id): + doc = Document() + doc.id = 'doc-justificatif#%s' % sub_id + doc.format = 'pdf' + doc.date = NotAvailable + doc.label = 'Justificatif de domicile' + doc.url = 'https://mon-espace.ekwateur.fr/client/justificatif_de_domicile' + yield doc + + def get_cgv(self, sub_id): + CGV = Document() + CGV.id = 'doc-CGV#%s' % sub_id + CGV.format = 'pdf' + CGV.date = NotAvailable + CGV.label = 'CGV électricité' + CGV.type = 'cgv' + for item in XPath( + './/div[has-class("table__foot__adobe-reader__link")]//a' + )(self.doc): + if 'CGV' in item.text: + CGV.url = item.attrib['href'] + yield CGV diff --git a/modules/ekwateur/test.py b/modules/ekwateur/test.py new file mode 100644 index 0000000000000000000000000000000000000000..98b18b815f4e0222c42999c4832b66e7f06c434c --- /dev/null +++ b/modules/ekwateur/test.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Phyks (Lucas Verney) +# +# 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 EkwateurTest(BackendTest): + MODULE = 'ekwateur' + + def test_document(self): + subscriptions = list(self.backend.iter_subscription()) + assert subscriptions + + for sub in subscriptions: + docs = list(self.backend.iter_documents(sub)) + assert docs + + for doc in docs: + content = self.backend.download_document(doc) + assert content diff --git a/tools/py3-compatible.modules b/tools/py3-compatible.modules index a3ed9b6a5f7a5dd7f7633d6820a149de4f8656cd..0850053873c68f0e7f3b689831baa98480cd5f85 100644 --- a/tools/py3-compatible.modules +++ b/tools/py3-compatible.modules @@ -43,6 +43,7 @@ delubac dlfp ebonics edf +ekwateur entreparticuliers erehsbc esalia