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