[weboob] [PATCH 5/6] Add module for Ameli website (French Health Insurance)

Kitof git at kitof.net
Mon Jul 22 16:11:28 CEST 2013


From: Christophe Lampin <weboob at lampin.net>


Signed-off-by: Christophe Lampin <weboob at lampin.net>
---
 modules/ameli/__init__.py |  24 ++++++++
 modules/ameli/backend.py  |  91 ++++++++++++++++++++++++++++
 modules/ameli/browser.py  | 119 +++++++++++++++++++++++++++++++++++++
 modules/ameli/favicon.png | Bin 0 -> 6570 bytes
 modules/ameli/pages.py    | 147 ++++++++++++++++++++++++++++++++++++++++++++++
 modules/ameli/test.py     |  34 +++++++++++
 6 files changed, 415 insertions(+)
 create mode 100644 modules/ameli/__init__.py
 create mode 100755 modules/ameli/backend.py
 create mode 100755 modules/ameli/browser.py
 create mode 100755 modules/ameli/favicon.png
 create mode 100755 modules/ameli/pages.py
 create mode 100755 modules/ameli/test.py

diff --git a/modules/ameli/__init__.py b/modules/ameli/__init__.py
new file mode 100644
index 0000000..2d8d3b8
--- /dev/null
+++ b/modules/ameli/__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 <http://www.gnu.org/licenses/>.
+
+
+from .backend import AmeliBackend
+
+
+__all__ = ['AmeliBackend']
diff --git a/modules/ameli/backend.py b/modules/ameli/backend.py
new file mode 100755
index 0000000..f501f19
--- /dev/null
+++ b/modules/ameli/backend.py
@@ -0,0 +1,91 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+
+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 AmeliBrowser
+
+__all__ = ['AmeliBackend']
+
+
+class AmeliBackend(BaseBackend, ICapBill):
+    NAME = 'ameli'
+    DESCRIPTION = u'Ameli website: French Health Insurance'
+    MAINTAINER = u'Christophe Lampin'
+    EMAIL = 'weboob at lampin.net'
+    VERSION = '0.g'
+    LICENSE = 'AGPLv3+'
+    BROWSER = AmeliBrowser
+    CONFIG = BackendConfig(ValueBackendPassword('login',
+                                                label='numero de SS',
+                                                masked=False),
+                           ValueBackendPassword('password',
+                                                label='Password',
+                                                masked=True)
+                           )
+    BROWSER = AmeliBrowser
+
+    def create_default_browser(self):
+        return self.create_browser(self.config['login'].get(),
+                                   self.config['password'].get())
+
+    def iter_subscription(self):
+        return self.browser.iter_subscription_list()
+
+    def get_subscription(self, _id):
+        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.iter_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(subscription)
+
+    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/ameli/browser.py b/modules/ameli/browser.py
new file mode 100755
index 0000000..246e63b
--- /dev/null
+++ b/modules/ameli/browser.py
@@ -0,0 +1,119 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+
+import urllib
+import mechanize
+from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
+from weboob.capabilities.bill import Detail
+from decimal import Decimal
+from .pages import AmeliBasePage, LoginPage, HomePage, AccountPage, LastPaymentsPage, PaymentDetailsPage, BillsPage
+
+__all__ = ['AmeliBrowser']
+
+class AmeliBrowser(BaseBrowser):
+    PROTOCOL = 'https'
+    DOMAIN = 'assure.ameli.fr'
+    ENCODING = None
+
+    PAGES = {'.*_pageLabel=as_login_page.*': LoginPage,
+             '.*_pageLabel=as_accueil_page.*': HomePage,
+             '.*_pageLabel=as_etat_civil_page.*': AccountPage,
+             '.*_pageLabel=as_revele_mensuel_presta_page.*': BillsPage,
+             '.*_pageLabel=as_dernier_paiement_page': LastPaymentsPage,
+             '.*_actionOverride=%2Fportlets%2Fpaiements%2Fdetailpaiements&paiements.*' : PaymentDetailsPage
+             }
+
+    loginp = '/PortailAS/appmanager/PortailAS/assure?_somtc=true&_pageLabel=as_login_page'
+    homep = '/PortailAS/appmanager/PortailAS/assure?_nfpb=true&_pageLabel=as_accueil_page'
+    accountp = '/PortailAS/appmanager/PortailAS/assure?_nfpb=true&_pageLabel=as_etat_civil_page'
+    billsp = '/PortailAS/appmanager/PortailAS/assure?_nfpb=true&_pageLabel=as_revele_mensuel_presta_page'
+    lastpaymentsp = '/PortailAS/appmanager/PortailAS/assure?_nfpb=true&_pageLabel=as_dernier_paiement_page'
+
+    is_logging = False
+
+    def home(self):
+        if not self.is_logged():
+            self.login()
+        self.location(self.homep)
+
+    def is_logged(self):
+        logged = self.page and self.page.is_logged() or self.is_logging
+        self.logger.debug('logged: %s' % (logged))
+        return logged
+
+    def login(self):
+        # Do we really need to login?
+        if self.is_logged():
+            self.logger.debug('Already logged in')
+            return
+
+        self.is_logging = True
+
+        self.location(self.loginp)
+        self.page.login(self.username, self.password)
+
+        self.is_logging = False
+
+        if not self.is_logged():
+            raise BrowserIncorrectPassword()
+
+    def iter_subscription_list(self):
+        if not self.is_on_page(AccountPage):
+            self.location(self.accountp)
+        return self.page.iter_subscription_list()
+
+    def get_subscription(self, id):
+        assert isinstance(id, basestring)
+        for sub in self.iter_subscription_list():
+            if id == sub._id:
+               return sub
+        return None
+
+    def iter_history(self, sub):
+        if not self.is_on_page(LastPaymentsPage):
+            self.location(self.lastpaymentsp)
+        urls = self.page.iter_last_payments()
+        for url in urls:
+            self.location(url)
+            assert self.is_on_page(PaymentDetailsPage)
+            for payment in self.page.iter_payment_details(sub):
+                yield payment
+
+    def iter_details(self, sub):
+        det = Detail()
+        det.id = sub.id
+        det.label = sub.label
+        det.infos = ''
+        det.price = Decimal('0.0')
+        yield det
+
+    def iter_bills(self, sub):
+        if not sub._id.isdigit():
+            return []
+        if not self.is_on_page(BillsPage):
+            self.location(self.billsp)
+            return self.page.iter_bills(sub)
+
+    def get_bill(self, id):
+        assert isinstance(id, basestring)
+        subs = self.iter_subscription_list()
+        for sub in subs:
+            for b in self.iter_bills(sub):
+                if id == b.id:
+                    return b
diff --git a/modules/ameli/favicon.png b/modules/ameli/favicon.png
new file mode 100755
index 0000000000000000000000000000000000000000..f3b2c28d4d340fd735eb65b506d574fe877ae2ac
GIT binary patch
literal 6570
zcmV;b8CB+qP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02*{fSaefwW^{L9
za%BKeVQFr3E>1;MAa*k at H7+qQF!XYv000>oNkl<ZcwX&V32<Fkd1gY0vrq^rZKs`(
zq%$o;DIq1y0G)JTD72*o$`pon0u)GM$4Q#t1SddDRsvaTJB||)h!YdbvL)H_B0G{7
zc~i8n*3<g*B)xs#-_zTl^!5ACz4z(Lkz^%dh7Rp7eed3T?%DqD{O3RaIq$s-zn3z{
zKlwbryj=oz>ev6JGK;x6!TBlpJ+%Oi{#;p+a6Wm<1RS-v@%}ZhDg4{!<9h1vPK>??
z+9v`kZFAGmiu?GM3Hbi6&Mauu;)?2Tem7$7%R3-oWiG=WN-bzmaL>*T{q65Y%zZHk
z5TOTHp%WBGp`3-x7mWz*vqDK+y8w^w>s!#E;Ocd|5l!dx9GaL%K*|V7l24wm+fc8j
zkwET^I`(!=>!mf3I;9>XkKl7ZKdjQs?HTU`1_5`z*4ARARV=`ykcja}0;sq`nU5N8
z@@LRt7DEYv`TpF_n+bR;Fs73zD9SFDM<DMEF{c{@Y-+Mp(i8zoTYjj4p4U&HTYyf2
zo-8!TomoUlqf+U>tEa7%XBj24m<bS~L^0`{ktQ%k(mcYFc5ZpGAQ8!H_CE&c#<SqO
z1nBx{1k6E(oCN4Eb#~@6pq&pDk&28;Mfr36fqDL!6)-!Y#qyi$H3(R4^bHuqnA4<A
zS^C~YfD%JLvyO8H0S2 at i`7_J8%F45ddGyTA at SPH1&bJhddd+DqW1gt2|C<*jz$~Jl
zW)1TMm=e#G#hl+_Fr3pnCBRIqILqb4$R}vGda1_Z1Q=>6Rh4M^nwCLPTxNw}UPLLg
z!&?r90`HUnGvS|o>=0b>%%u1cb3cso#<NNj0%p%ofIt&Y0qwz1zNDVaVs?H4!~6{~
zJ0`;!zFh(~v7F4pNJ>_LLkseoanH_nc3t~3r?*>uRAqKW1PDZQ0_1aX0?ZD{y!;q)
zE<s|lixH3~%=y|#fGBGwKv at 9CVs3U0kz`IOdX;I^g_O-rgDfZg)U4HbCupDWv+&%K
z17Ikw>8yz<6gDTneMuEdrwi;vUPpmxf+A676SGX?zk)J6fsvrz(KnL-1^&sRT;`vA
z%EF}}1Sn{Ow(wa>@zZEyS&>!Xw=6TolIo^(+*oTxc|`<!RG?yl5<4e^XsD=0f>@5v
z%%YbUXzFWqN-ANFT|{x61Vy0akn)5kl^aduQ6(*01}7wGB@)dQP-G{y#<Z at nd1P5n
zyfh2%OdgZbG#se{Ld7|xnV#W$16e|*gb*X;bd^LE)+R7&)vQ4NwIOAWS(YGF+d`DQ
zBD7EKNo6;ZDi4c`@&Qu!N+n(>J}Sy<rYxPHff9MP5--nUEINaRK_{L+ZozBaZk+aH
zFq+E4mde7G%wRH=fnz2Ie>RU8ttF$ZLF&^+%uK>O0me%qfNuqCQwT7iSp#i_S7kwb
zV(NKCBS5=fk(Ai*>|Bw3DX`5vuQ?QX%`nZA$>OntU3kweI~A_Fdk at yv58#>RVLaZ@
zfxCA!<MP|+)NbF6%Wtp2#cOus>L1o4o-ZO>DkE1WkyKJpU^@kxbcvXqEwU}PSI!Yx
z-e*x}yCtTTXbmy|>Hvgli}Gq(v9Bv3fduF*uR!wUd_0qjBboLik#r-Gav`2_a at lgh
zpYp at vbK|nr)fxe-iJNuEvvIvee{bAZ(s$yLHPv`yVj9j^i1mdKVx7^H7jbDT=^`Aw
zw<DHvs5XOq7i7M8Hc0}DYFcX6qba{a0wmkYBv(u#9J9meIg2T07p7eum~yqj=IX?V
zdk`&F3%>LG-UUj{!>qUtGbuM~@2~mcZXC0;qu()rad#gkU0rOeoo%&Y%H0Z^rxjLD
zJBB at -=y7zR)!L2jsWD7NB8U}=TzL$?qA9N+Q)CHw7yawP&HOS_ET at pp`VdV_A{-w@
zBtAq~5ahixZpT2tgX)1%+_k4wQ_fJ(M97ym9>z1LhEUaS!_MwuY&+9~A5<OX=<mcT
zzF+(NE2!@{h9Qpy-q;x18D<-U#94$A0|+Dr;Z6)=IAlT7csqXC+<;978&KQckD*W;
zG1|W<R3Skrzw{ulco~$rSfZ;d&CPJm$2s>RoQEMIh}>)p-b at 0g?0#%%9mndL9(;al
zC$8Vviw`|`2CHt}T_NO}d-vkFFOCU1Ku4j3Njk%UND at cKCa|mJ3~EmwLzit3fwYf4
zA<A~bY$HJU*{+{$(d3!l8*t$IN9q-+AG~b`?%Z}5^+R6RGP8(N-<((iVL0ohWW at v&
zSqWVqNsx7(&X0 at P++3CoWRay49HkP^(%D@}$0v%eqFG;ZYYi at 0%V(1neD;ag(Gy7{
z$U5byDZx(0xOxP0dH7OE&gnQ3`7{-!lVkKi*GOb|PvUva^Epz^qtWW9Se1dF9va0#
zYaD}-47}MgQnUt%Jxad~0%WP;>dVf^lBF$wf>|O%uieX0y{~qD1*kwDdAVOj$GGL#
z5QgI!gs6CgOPiQ%iq{P5&kM<N<uaGd=Ml+fIj;+RPxaYOnyn{Ey at xBtE{p3;?Oa6C
zZ?EdXmJ<^=VvWKYV;#jZ%gQuZTvkMY7MZC<Mps at 7LIe`U%}{|3PYicdb?Tj|!lhL9
zBE4gROK;hMTVFViy?t(sCiAdzgwDnb9IZU2(?!)$At6I+2ry>ov^sIPXA~{A07hdO
zIJs1MIBlH700XQoT{-~<!8)W(A^I{-yTATv13JA)xN;J5EiNl2zzBL~WnEU#f)1G)
zNm{@eR|Ge0f1@%Y*ZyojzW?ee-1tHhzPYIZYhG-`izh~KYAT8oQz7i=wPWLnQEa3|
z>}(&$fH#6D;{XY!?6EWs_m1I5Rn3eUs&U1gHTai}O?aiHA5~ozY&m1WE!FLnwk|XX
zkZ1E%C!}G-qigbJIWGZJJs@#{5Fmj^u9!ilkitwpi4^B*ET4fZlfi*eCqDPY{z}7J
zdTls3?M3~#6T64(Xt24^LM0o=z4*m}F1-KNUF>L8rL$k%+=z)p3jSOcu2ce at b_YKB
z2<wygMm__ojrp_-CFU@{Gp@=CtMOm2H0#>DS<X*@ZtCJlk_DP##Dj6qIM?V=jQb{V
z)-#Qx<5oOz_)I1ExtPJwzVS)4dneK19z&~Z3 at x51G)+0MvAG`~xI+(s&cVyAgRE}~
zqn=UpP50wKPYbU4;VxWaC at A?98HBm)If}1ZTZ^kV9L3e^_uymexs85chpx?=WqAU`
z>^Z+>o+ml?CL&Rs92vx+?$bDFX+xir%Gs^>^~uw?Y17ME2Z)<rJ%bLH6XU at NOazB8
z9vVg;$*LV3#g{i9tmM-={p!3TRJ6x6g^9o at Z2kdQef=2r_M?kr92xAuWBZzL!(%nL
z{-NEt?!g-T(K?3G_txPGhSf%WG9BLcg9d!){v){NzIuG>;d*>yOOvk9o8|ljQ289^
zr7e;|eV-Li?LUr3chzD0$rJSa9t?zn*wNC8bz8ULpB~+g)!UAt(-TG@%V|=IFhuns
zUGl><6GFYkhE+;w4Hq%^xnzwon$?UKcA+<#geM<Iw8-_o7~qB}h+rWKXD)%^1Y-?%
z0DDHJQ9WkIwox~}SJjSd)-n1t5g-=u!5`G(uYR%@H~pp^&$YXBh2AVn62O(SSLQw`
zK#I%GNzUE%ubsjT_rHd}S@#m|-o}82bKI86(SiAKoTGbcWD>)HD8hOA3T}?dj1^{)
z;%d%ypT6QSJFJw|J1YT_#0nY#1TCP}7D0P323tA_FUtgqIk at sUjHhSlL!!8|wj1Br
z+KeZT^<j_Aj}3<`3+lY^mR*X-_vm|)4Gn|n3CRY0aaoc8V^hI>7)PUwgF`-i`-Rh$
z#{T)KIvkwzW19PaKc|OmZ-e;~Vk9fAc6JPz)K0BP0!oOKN@(*ZIWl#uLmTyr*VJMa
zp*n9suV1XA$?8L^JAv2f#a}qykN4kZ>?f-5-_0X9JRQdOws&Z0y8pRwO${zmK0|y(
zH8!8NAy|AHfsm}6a%dq37(P}|KNi8aw)N^2bD4W^AND%J7-t~k;WY7(h{;qD1D+`Q
zCOzD=1QAPT)#;C%6@;0uGge?^$k8#BTwO;GUPmWqB0+ywxj*+=zcx+BamJs*&A(}@
zykEu9ujEzFJ84BFbs)2pEJ*;BpGa`-yEA!o`(xPJI)$&iFu(SG at UB{H>6^kKR{$qm
zoC4E6d~xG(zGDc?ZEaJh1@=&kK at wwgT7(2MyDUMiyi%-+qfcvCBlEWot8Sv{-u#;B
zJ)uUGqppvxfvsmIFqtc(E1bgLKiR025v(>5z<jTCO(Vp%We}H=Wu2fqmBZ0tC)QV;
zn(vt2z$%*Pmp1Ojziz6--#*OEj?pj)kn0;ZH=MM90H?=bFpgg|(0WYeFIruN_pYr`
zBwTolSb$!KuB0(dBu$ppKfZGhS|)vPvCb$%<shxDE0n;;?%!9bQ_SBeE79Qo5ii2D
z%%wfRG6ZC2=bXd7D7TuO_`q7Nfyzi29n~8?_x@|oHNl#i!6d`iGh7j_T*r}?cczj2
z)Dwqr&5!ny1g)G9V!RX7J9Bv5o}eG_(gH)Ub5)6y(zGaU$+;s9&lS*4hx;dY&69tj
z;ZIop%6s-I0zxEUM%QUES$Ya^&RXbopWfez&p)j1==Axo6wybzGE&54w^m~`Ji)!X
zgG-?c2S;tR64Sa*yq^l+bEpkB at 9D;;pFE5!?%2-J+D?b~DlXFN`Gd99=#0iO8V_MI
z>Vh+2hc9hMcxDRG89FuIPo?}gGRZCRgW^=Rx-VQ!GF0EH at RvW{kNu-Q1WCZcuW}a3
z(oT at h%QI8Jq0s>Dsp-R~9z482*+0B<H?Dp35FTyrM#IEeoObl$4SP3ET060)>olI(
ze*m|?u%psBx!zxY99!Fa at XLX5tZzJvzkB+1MZhH_;IcKZRPy~PSBh5GBnI6UjJvuq
z;p%|R-Hs^_;cmm2tB0ZTFm`kg;<F5lDs32GKR^EPVKj^f-X;NavjG+Tkpv63lv`Wu
z`0{_BR69J?n3`kNc=)6phq(!AVL;Sq8%I at 751wdf#I4V7XRL2hp7_bWPLkxM<BFhR
z+K;>HdvNVT2k<^xgw!kF;uxL(v%B|TI1+?2=7K+JLnu0qP;4AwA{4X26P?Cb*o%XV
zJO1HuJqS7npZr-9j!iQrp(9W0YAq&<5}+M*&noX}&lItHz>jae)QYP(>J_DU{ly74
zPWdw2-FdL}7=6VZCTD0;{xgp?B9N0<pscp0jkXZJv#kYJ-d(3+fs45eUe1-nC?gKc
zStue=W<WKYWjry1@@$$&p;S&ITb4tS654$ce0`H_n2hOYv{}ti`E|6oliXo)yRNCV
zsE`12kWjX0=j~;+`H_MSMSzzCaAR_p-bq42+6N9;x%?$?m?r({2lRN{=nQf3nqBDe
z#MH@*98qNm2lxKF`@Q(e#%5e`8=cl_j^OG&xJ>4+UQW;+N;4#8P{n at 8NtF6-NRHzr
zuwq)Au|;<%&h55LO(~=MCv~`5g->t_ba=U2r4=u`463cJ_Gi(^P{$sO-Z%yLG at 8g@
z<I%IqRJG1&4So3T-FTpW2xlB|j07ThxtV!x+l6b^@53p3Oq+Y0M#i^5a!{Ef0oHf{
z+d4e>;*-ZYO=|FN5^#aU0umL{iE2vh#A8QC&}>g~07~k_Nc%EIJCKoMt|-yXW$>nL
zC$v7X%%yVnsGQ(V-0<Wv3`FusoR0uaWtzQuluwEOwNA(RI1o-^L(NIX?4r2t1lhnB
z9;(GNjl<~or5NaxxbZ161TJEx%pCwbndgYfiJqJuit;iKL!6HtzBGQ`XvJq9Zp3 at n
z?8XI*1TSExFJS!sZi8fTwmK1Ot2*iN^Ryy4e9?~NgaAL6*ygDKZhT%3gqZiw?>$f<
z;GVr_F`giK)M42;z#u?ih-`o)6i%}6NGy%bO<nljOO5#2FNJMspc?<N=^*y>*kMm(
zIZwIqnWL%-1(w&;mQ&=sI?kL^$`3?mu&v9E8((a}U#zd^+OJP3Rj0sET)*mRU*Gx$
z>?v-k7*)!2$a9K1;z{i79m9V<FUzcE_W$@)1HSUuZhUL=0qnE*;hd54K+XD=lFC<O
z0z<?GglVyY0AuMkXB4#~emwJrRd0-YewyB(H;h;=gFFM2QaM4y6^i93_W)4_Qwgpl
zIr!<&`e?4d>agM4zdnVJ{z#mkD5^s#ZdO_xp?<!glX3k+uQRgD(aQ43lx8p$@}j=0
z2TvV5$@kKyYJ8Vb>35&ri6{4-MwdIrO`PU?M31FJtpW=PkUOyfjRZ!7G`Cp{r^=`s
z=5|(RySA+#lR+OsN$%N`9`zMjJmug9$bkqEoN>WD<A*gF$4N&3Pao;QH=aF&kK9|0
ziw(tfT+8Y7)t8RoeYfiyB~h9%0waTzW89KY$07))0^Dazqiu2+N5;m`IBrM7h#l1(
zL#XQ<V!9h%?qRYt>jKqT)&lhFA_SEADc=Lgku@!WY_>T<_`=Wk<7-duM~lURN$(WA
zp(*&nHuxe|xFh4RM<!s4PH`P~qSfodZi@{Y_IKdJ_nCKU^P_#;1NGRzaC=+-Bw at qn
zo at toRXAq#1 at bv4wRL%iM*Z~*Yw1uZI7V&Tw8i%J)fR~mQl;u;l<Mb2q#fbbaVfnp4
zMRQpRH1%clW&Aa-t5qZ<cv4w(SsiE_8pVj+26xzrVBAf(;7htVI(7{BCU9bM1bZyQ
zc&?=%*FD6!ZYX31mHPKBP1t&p-hR at LK?X=ekr29le(vGN_}=6n)U^s%GeX=yV8c|{
z2VdO5Hl6UMJP7CF^baMDwEQAP`~E_F8dqOs0p}+`zz^+K{xl1iKmXUhX3B7Bi_Bzc
z_DRHM;z;F_NET9v7G~hhrl~{_b^T|tuId0Tt9W!1sKk4@)ZO^P89aG(lwr0XmQYF^
zrX-3Pq>3qCxzQ;WFwHQ$$riy!H|Q3j*Ku!MHzw%-<M{;AMQ*o?QREmLl_W-xeyctU
zs0$&^{4MPUH4-!e)R_<~lbQ??WRFmyH<s at Tb3}s7I~nDja>a1zT0NLi%4!N5&!2c;
zA8voO6^|btLT$eXeL<2&-y-3*X09c+DhsEaATfMpnmt{@p1~j^#f1kQ{Q(kCVwA|7
zMSjYvBjc|1RqL-PBA2&JfF>FN5)sZ5Kw4O#K=ujpBZdf9h|x$|Th4SvHRUz)z5KRn
ze3PN-BYRu%O6vrgCnC%*zYZxON&A$a)W{swzRA at lp8Tn`o at sIVolbWeKdzCJD5+N?
z^xB^uBzcTER6f0g8g;Fo{I5XjUXc?t3#(vZ?R!6S#iG1eL7q2ZnrNRRj1S$T at AVDk
ziH|>c5YKW0RX5_pNoR<o9mgc&g<y`MEfuF;N=XBn@&<$q4KxrgeMQvt`SFqa_OWhR
z9fXLTIBpLiD%Qg?GUv_8t3hH})nBQHSV|TpK>IF`%vPXH2<f=|06~-(_Jr{w_w`1H
z1Wz^fqIST6Q;rx0qXpQhL4du8v3#1H)C^UWR4$g#egLP9xppTc7=LO<O20D{$>QY}
zD=s&kC(3n-gP_7>=2hkN!4kz4_)+5XTPA>I^h%@?>>jVuaS1`Qgq#(2PkZsmYsN-|
zU<le1OfY6Ba2Ybb(JgWHki&5aiDh=nQMnc=N{U2^^7Xg+s$IeG<5WqG)frXhi74yq
z^+fO-39hUt`YPd0%h&VblSFZC<uWWmuF`i|36_#2e1J>?tpce~k-%;i81kkH83Z#4
z^xEy%a_~5Axc?P;b~%6?;zlQmXeNqOCP)O3<TarXQrBe0k5tx+G?B`9`7Bot(it~q
zX57f|c_!l{{7mx^UY7GA!nz!ZAdU=Mv2NR;dD7_<9^c;$XC#3f_ao(5_L2}w&>2vJ
zDART+S&{%@K&wO86lUpfi)jSn3_N^e=&|)-&l at N3NL39s{H7M$Pcr<p4Px3e2#32H
zc2_5+1 at 10P5%N2tDG#5$9STz(!p(bEJ6x_dxSfQn11{#ZyX5yrQnnM5EHmNh#;|Ju
z&BJ|oX5UfV`YgB6BtfiUOWiS?9vDMBBR|#<<z+P><fn8(22EM4c_~?z098_;D&~??
z<~lv&MIbf-XJ`b&?qQr9I*UWS12{c046An>{>U(b;eMhI0fm0}!vpa0>WK`%9UX*=
za7Kn0%?&Y%97HHIfN-cEVde|**-v;0cf_LdIU*wrekL%)$n(^g75jRIQPVbv+SWld
zbXqVl?Pg3OQ%hDYg8-cbHGrB*i^=i?NJ&M2_+;?{oU^mC at rk3r4Ns<&K%7$|lF##+
z<#Uqh5xx(iY(l9VVtN=^zMEmWbU8*OkRkGF0~KMuu!7vnW-7-#8NQQz>2i|W;Tigt
z9D}X`f>hR@%PWKk`Qlc#${cI<Q_No!*OXV5Ecz at VOB1j_qzZvFz>3x4B7}%%kWYCd
z9T0CW?<D+K2+fU3up_ at QGW@^fpRaGeUZZXe=jJw-(AqXDY4mkIWjR?P0l&ZU{{-mQ
c|Dyu at 4~%H7u<n36i~s-t07*qoM6N<$g2upq3IG5A

literal 0
HcmV?d00001

diff --git a/modules/ameli/pages.py b/modules/ameli/pages.py
new file mode 100755
index 0000000..c650304
--- /dev/null
+++ b/modules/ameli/pages.py
@@ -0,0 +1,147 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+
+
+from datetime import datetime
+import re
+from decimal import Decimal
+import locale
+from weboob.tools.browser import BasePage
+from weboob.capabilities.bill import Subscription, Detail, Bill
+
+
+__all__ = ['AmeliBasePage', 'LoginPage', 'HomePage', 'AccountPage', 'LastPaymentsPage', 'PaymentDetailsPage', '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 AmeliBasePage(BasePage):
+    def is_logged(self):
+        return len(self.document.xpath('//a[@id="logout"]')) > 0
+
+class LoginPage(AmeliBasePage):
+    def login(self, login, password):
+        self.browser.select_form('connexionCompteForm')
+        self.browser["connexioncompte_2numSecuriteSociale"] = login.encode('utf8')
+        self.browser["connexioncompte_2codeConfidentiel"] = password.encode('utf8')
+        self.browser.submit()
+
+class HomePage(AmeliBasePage):
+
+    def on_loaded(self):
+        pass
+
+
+class AccountPage(AmeliBasePage):
+
+    def iter_subscription_list(self):
+        idents = self.document.xpath('//div[contains(@class, "blocfond")]')
+        enfants = 0
+        for ident in idents:
+            if len(ident.xpath('.//h4')) == 0:
+                continue
+
+            name = self.parser.tocleanstring(ident.xpath('.//h4')[0])
+            lis = ident.xpath('.//li')
+            if len(lis) > 3:
+                number = re.sub('[^\d]+', '', ident.xpath('.//li')[3].text)
+            else:
+                enfants = enfants + 1
+                number = "AFFILIE" + str(enfants)
+            sub = Subscription(number)
+            sub._id = number
+            sub.label = unicode(name)
+            sub.subscriber = unicode(name)
+            yield sub
+
+class LastPaymentsPage(AmeliBasePage):
+
+    def iter_last_payments(self):
+        list_table = self.document.xpath('//table[@id="ligneTabDerniersPaiements"]')
+        if len(list_table) > 0:
+            table = list_table[0].xpath('.//tr')
+            for tr in table:
+                list_a = tr.xpath('.//a')
+                if len(list_a) == 0:
+                    continue
+                yield list_a[0].attrib.get('href')
+
+class PaymentDetailsPage(AmeliBasePage):
+
+    def iter_payment_details(self, sub):
+        if sub._id.isdigit():
+            idx = 0
+        else:
+            idx = sub._id.replace('AFFILIE','')
+        if len(self.document.xpath('//div[@class="centrepage"]/h3')) > idx or self.document.xpath('//table[@id="DetailPaiement3"]') > idx:
+            id_str = self.document.xpath('//div[@class="centrepage"]/h3')[idx].text.strip()
+            m = re.match('.*le (.*) pour un montant de.*', id_str)
+            if m:
+                id_str = m.group(1)
+                id_date = datetime.strptime(id_str, '%d/%m/%Y').date()
+                id = sub._id + "." + datetime.strftime(id_date, "%Y%m%d")
+                table = self.document.xpath('//table[@id="DetailPaiement3"]')[idx].xpath('.//tr')
+                line = 1
+                for tr in table:
+                    tds = tr.xpath('.//td');
+                    if len(tds) == 0:
+                        continue
+                    date_str = tds[0].text
+                    det = Detail()
+                    det.id = id + "." + str(line)
+                    det.label = unicode(tds[1].text.strip())
+                    if date_str is None or date_str == '':
+                        det.infos = u''
+                        det.datetime = last_date
+                    else:
+                        det.infos = u'Payé ' + unicode(re.sub('[^\d,-]+', '', tds[2].text)) + u'€ / Base ' + unicode(re.sub('[^\d,-]+', '', tds[3].text)) + u'€ / Taux ' + unicode(re.sub('[^\d,-]+', '', tds[4].text)) + '%'
+                        det.datetime = datetime.strptime(date_str, '%d/%m/%Y').date()
+                        last_date = det.datetime
+                    det.price = Decimal(re.sub('[^\d,-]+', '', tds[5].text).replace(',','.'))
+                    line = line + 1
+                    yield det
+
+class BillsPage(AmeliBasePage):
+
+    def iter_bills(self,sub):
+        table = self.document.xpath('//table[@id="tableauDecompte"]')[0].xpath('.//tr')
+        for tr in table:
+            list_tds = tr.xpath('.//td')
+            if len(list_tds) == 0:
+                continue
+            date_str = list_tds[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 = list_tds[1].text
+            if amount is None:
+                continue
+            amount = re.sub(' euros','',amount)
+            bil = Bill()
+            bil.id = sub._id + "." + date.strftime("%Y%m")
+            bil.date = date
+            bil.label = u''+amount.strip()
+            bil.format = u'pdf'
+            filedate = date.strftime("%m%Y")
+            bil._url = '/PortailAS/PDFServletReleveMensuel.dopdf'
+            bil._args = {'PDF.moisRecherche': filedate}
+            yield bil
+
+    def get_bill(self,bill):
+       self.location(bill._url, urllib.urlencode(bill._args))
+
diff --git a/modules/ameli/test.py b/modules/ameli/test.py
new file mode 100755
index 0000000..8df432a
--- /dev/null
+++ b/modules/ameli/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 <http://www.gnu.org/licenses/>.
+
+
+from weboob.tools.test import BackendTest
+
+
+__all__ = ['AmeliTest']
+
+
+class AmeliTest(BackendTest):
+    BACKEND = 'ameli'
+
+    def test_ameli(self):
+        for subscription in self.backend.iter_subscription():
+            list(self.backend.iter_bills_history(subscription.id))
+            for bill in self.backend.iter_bills(subscription.id):
+                self.backend.download_bill(bill.id)
-- 
1.8.1.4




More information about the weboob mailing list