diff --git a/modules/amelipro/__init__.py b/modules/amelipro/__init__.py
new file mode 100644
index 00000000..48b13b92
--- /dev/null
+++ b/modules/amelipro/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Christophe Lampin
+#
+# 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 .backend import AmeliProBackend
+
+
+__all__ = ['AmeliProBackend']
diff --git a/modules/amelipro/backend.py b/modules/amelipro/backend.py
new file mode 100755
index 00000000..3f0285f5
--- /dev/null
+++ b/modules/amelipro/backend.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Christophe Lampin
+#
+# 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 .
+
+import urllib
+from weboob.capabilities.bill import ICapBill, SubscriptionNotFound, BillNotFound, Subscription, Bill
+from weboob.tools.backend import BaseBackend, BackendConfig
+from weboob.tools.value import ValueBackendPassword
+from .browser import AmeliProBrowser
+
+__all__ = ['AmeliProBackend']
+
+
+class AmeliProBackend(BaseBackend, ICapBill):
+ NAME = 'amelipro'
+ DESCRIPTION = u'Ameli website: French Health Insurance for Professionals'
+ MAINTAINER = u'Christophe Lampin'
+ EMAIL = 'weboob@lampin.net'
+ VERSION = '0.g'
+ LICENSE = 'AGPLv3+'
+ BROWSER = AmeliProBrowser
+ CONFIG = BackendConfig(ValueBackendPassword('login',
+ label='numero de SS',
+ masked=False),
+ ValueBackendPassword('password',
+ label='Password',
+ masked=True)
+ )
+ BROWSER = AmeliProBrowser
+
+ def create_default_browser(self):
+ 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):
+ if not _id.isdigit():
+ raise SubscriptionNotFound()
+ with self.browser:
+ subscription = self.browser.get_subscription(_id)
+ if not subscription:
+ raise SubscriptionNotFound()
+ else:
+ return subscription
+
+ def iter_bills_history(self, subscription):
+ if not isinstance(subscription, Subscription):
+ subscription = self.get_subscription(subscription)
+ with self.browser:
+ return self.browser.iter_history(subscription)
+
+ def get_details(self, subscription):
+ if not isinstance(subscription, Subscription):
+ subscription = self.get_subscription(subscription)
+ with self.browser:
+ return self.browser.get_details(subscription)
+
+ def iter_bills(self, subscription):
+ if not isinstance(subscription, Subscription):
+ subscription = self.get_subscription(subscription)
+ with self.browser:
+ return self.browser.iter_bills()
+
+ def get_bill(self, id):
+ with self.browser:
+ bill = self.browser.get_bill(id)
+ if not bill:
+ raise BillNotFound()
+ else:
+ return bill
+
+ def download_bill(self, bill):
+ if not isinstance(bill, Bill):
+ bill = self.get_bill(bill)
+ with self.browser:
+ return self.browser.readurl(bill._url,urllib.urlencode(bill._args))
diff --git a/modules/amelipro/browser.py b/modules/amelipro/browser.py
new file mode 100755
index 00000000..d65c5998
--- /dev/null
+++ b/modules/amelipro/browser.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Christophe Lampin
+#
+# 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 .
+
+import urllib
+import mechanize
+from weboob.tools.browser import BaseBrowser
+from weboob.capabilities.bill import Detail
+from decimal import *
+from .pages import LoginPage, HomePage, AccountPage, HistoryPage, BillsPage
+
+__all__ = ['AmeliProBrowser']
+
+class AmeliProBrowser(BaseBrowser):
+ PROTOCOL = 'https'
+ DOMAIN = 'espacepro.ameli.fr'
+ ENCODING = None
+
+ PAGES = {'.*_pageLabel=vp_login_page.*': LoginPage,
+ '.*_pageLabel=vp_accueil.*': HomePage,
+ '.*_pageLabel=vp_coordonnees_infos_perso_page.*': AccountPage,
+ '.*_pageLabel=vp_recherche_par_date_paiements_page.*': HistoryPage,
+ '.*_pageLabel=vp_releves_mensuels_page.*': BillsPage,
+ }
+
+ loginp = '/PortailPS/appmanager/portailps/professionnelsante?_nfpb=true&_pageLabel=vp_login_page'
+ homep = '/PortailPS/appmanager/portailps/professionnelsante?_nfpb=true&_pageLabel=vp_accueil_book'
+ accountp = '/PortailPS/appmanager/portailps/professionnelsante?_nfpb=true&_pageLabel=vp_coordonnees_infos_perso_page'
+ billsp = '/PortailPS/appmanager/portailps/professionnelsante?_nfpb=true&_pageLabel=vp_releves_mensuels_page'
+ searchp = '/PortailPS/appmanager/portailps/professionnelsante?_nfpb=true&_pageLabel=vp_recherche_par_date_paiements_page'
+ historyp = '/PortailPS/appmanager/portailps/professionnelsante?_nfpb=true&_windowLabel=vp_recherche_paiement_tiers_payant_portlet_1&vp_recherche_paiement_tiers_payant_portlet_1_actionOverride=%2Fportlets%2Fpaiements%2Frecherche&_pageLabel=vp_recherche_par_date_paiements_page'
+
+ def home(self):
+ self.location(self.homep)
+
+ def is_logged(self):
+ if self.is_on_page(LoginPage):
+ return False
+ return True
+
+ def login(self):
+ assert isinstance(self.username, basestring)
+ assert isinstance(self.password, basestring)
+ if not self.is_on_page(LoginPage):
+ self.location(self.loginp)
+ self.page.login(self.username, self.password)
+ if self.is_on_page(LoginPage):
+ raise BrowserIncorrectPassword()
+
+ def get_subscription_list(self):
+ if not self.is_on_page(AccountPage):
+ self.location(self.accountp)
+ return self.page.get_subscription_list()
+
+ def get_subscription(self, id):
+ assert isinstance(id, basestring)
+ return self.get_subscription_list()
+
+ def iter_history(self, subscription):
+ if not self.is_on_page(HistoryPage):
+ self.location(self.searchp)
+
+ date_deb = self.page.document.xpath('//input[@name="vp_recherche_paiement_tiers_payant_portlet_1dateDebutRecherche"]')[0].value
+ date_fin = self.page.document.xpath('//input[@name="vp_recherche_paiement_tiers_payant_portlet_1dateFinRecherche"]')[0].value
+
+ data = {'vp_recherche_paiement_tiers_payant_portlet_1dateDebutRecherche': date_deb,
+ 'vp_recherche_paiement_tiers_payant_portlet_1dateFinRecherche': date_fin,
+ 'vp_recherche_paiement_tiers_payant_portlet_1codeOrganisme': 'null',
+ 'vp_recherche_paiement_tiers_payant_portlet_1actionEvt': 'rechercheParDate',
+ 'vp_recherche_paiement_tiers_payant_portlet_1codeRegime': '01',
+ }
+
+ self.location(self.historyp, urllib.urlencode(data))
+ return self.page.iter_history()
+
+ def get_details(self, sub):
+ det = Detail()
+ det.id = sub.id
+ det.label = sub.label
+ det.infos = ''
+ det.price = Decimal('0.0')
+ return det
+
+ def iter_bills(self):
+ if not self.is_on_page(BillsPage):
+ self.location(self.billsp)
+ return self.page.iter_bills()
+
+ def get_bill(self, id):
+ assert isinstance(id, basestring)
+ for b in self.get_bills():
+ if id == b.id:
+ return b
+ return None
diff --git a/modules/amelipro/favicon.png b/modules/amelipro/favicon.png
new file mode 100755
index 00000000..a6098aee
Binary files /dev/null and b/modules/amelipro/favicon.png differ
diff --git a/modules/amelipro/pages.py b/modules/amelipro/pages.py
new file mode 100755
index 00000000..c9f059c1
--- /dev/null
+++ b/modules/amelipro/pages.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Christophe Lampin
+#
+# 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 datetime import datetime
+from decimal import Decimal
+import re
+from weboob.tools.browser import BasePage
+from weboob.capabilities.bill import Subscription, Detail, Bill
+
+
+__all__ = ['LoginPage', 'HomePage', 'AccountPage', 'HistoryPage', 'BillsPage']
+
+# Ugly array to avoid the use of french locale
+FRENCH_MONTHS = [u'janvier', u'février', u'mars', u'avril', u'mai', u'juin', u'juillet', u'août', u'septembre', u'octobre', u'novembre', u'décembre']
+
+class LoginPage(BasePage):
+ def login(self, login, password):
+ self.browser.select_form('connexionCompteForm')
+ self.browser["vp_connexion_portlet_1numPS"] = login.encode('utf8')
+ self.browser["vp_connexion_portlet_1password"] = password.encode('utf8')
+ self.browser.submit()
+
+class HomePage(BasePage):
+
+ def on_loaded(self):
+ pass
+
+
+class AccountPage(BasePage):
+
+ def get_subscription_list(self):
+ ident = self.document.xpath('//div[@id="identification"]')[0]
+ prof = self.document.xpath('//div[@id="profession"]')[0]
+ name = ident.xpath('//p/b')[0].text.replace(' ', ' ').strip()
+ number = ident.xpath('//p')[1].text.replace('Cabinet', '').strip()
+ label = prof.xpath('//div[@class="zoneTexte"]')[0].text.strip()
+ sub = Subscription(number)
+ sub._id = number
+ sub.label = unicode(name) + ' ' + unicode(label)
+ sub.subscriber = unicode(name)
+ return sub
+
+class HistoryPage(BasePage):
+
+ def iter_history(self):
+ table = self.document.xpath('//table[contains(concat(" ", @class, " "), " cTableauTriable ")]')[0].xpath('.//tr')
+ for tr in table:
+ list_a = tr.xpath('.//a')
+ if len(list_a) == 0:
+ continue
+ date = tr.xpath('.//td')[0].text.strip()
+ lot = list_a[0].text
+ factures = tr.xpath('.//div[@class="cAlignGauche"]/a')
+ factures_lbl = ''
+ for a in factures:
+ factures_lbl = factures_lbl + a.text + ' '
+ montant = tr.xpath('.//div[@class="cAlignDroite"]')[0].text.strip()
+ det = Detail()
+ det.id = lot
+ det.label = lot
+ det.infos = factures_lbl
+ det.datetime = datetime.strptime(date, "%d/%m/%Y").date()
+ det.price = Decimal(montant.replace(',','.'))
+ yield det
+
+class BillsPage(BasePage):
+
+ def iter_bills(self):
+ table = self.document.xpath('//table[@id="releveCompteMensuel"]')[0].xpath('.//tr')
+ for tr in table:
+ list_tds = tr.xpath('.//td')
+ if len(list_tds) == 0:
+ continue
+
+ date_str = tr.xpath('.//td[@class="cAlignGauche"]')[0].text
+ month_str = date_str.split()[0]
+ date = datetime.strptime(re.sub(month_str,str(FRENCH_MONTHS.index(month_str) + 1),date_str),"%m %Y").date()
+ amount = tr.xpath('.//td[@class="cAlignDroite"]')[0].text
+ for format in ('CSV', 'PDF'):
+ bil = Bill()
+ bil.id = date.strftime("%Y%m") + format
+ bil.date = date
+ bil.label = u''+amount.strip()
+ bil.format = u''+format
+ filedate = date.strftime("%m%Y")
+ bil._url = '/PortailPS/fichier.do'
+ bil._args = {'FICHIER.type': format.lower() + '.releveCompteMensuel',
+ 'dateReleve': filedate,
+ 'FICHIER.titre': '',
+ }
+ yield bil
+
+ def get_bill(self,bill):
+ self.location(bill._url, urllib.urlencode(bill._args))
diff --git a/modules/amelipro/test.py b/modules/amelipro/test.py
new file mode 100755
index 00000000..97bcebe2
--- /dev/null
+++ b/modules/amelipro/test.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Christophe Lampin
+#
+# 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 weboob.tools.test import BackendTest
+
+
+__all__ = ['AmeliProTest']
+
+
+class AmeliProTest(BackendTest):
+ BACKEND = 'AmeliPro'
+
+ def test_AmeliPro(self):
+ for subscription in self.backend.iter_subscription():
+ list(self.backend.iter_history(subscription.id))
+ for bill in self.backend.iter_bills(subscription.id):
+ self.backend.download_bill(bill.id)