[weboob] [PATCH 1/2] boobank: updated transfer command for creditmutuel

Matthieu Weber mweber+weboob at free.fr
Fri Feb 6 19:16:22 CET 2015


From: Matthieu Weber <mweber at free.fr>


Signed-off-by: Matthieu Weber <mweber+weboob at free.fr>
---
 modules/creditmutuel/browser.py    |   49 +++++++++------
 modules/creditmutuel/pages.py      |   20 +++++-
 modules/ilmatieteenlaitos/pages.py |  121 ++++++++++++++++++++++++++++++++++++
 weboob/browser/elements.py         |    6 +-
 4 files changed, 173 insertions(+), 23 deletions(-)
 create mode 100644 modules/ilmatieteenlaitos/pages.py

diff --git a/modules/creditmutuel/browser.py b/modules/creditmutuel/browser.py
index b6b349f..1a118d0 100644
--- a/modules/creditmutuel/browser.py
+++ b/modules/creditmutuel/browser.py
@@ -24,6 +24,7 @@ except ImportError:
     from urllib.parse import urlsplit, parse_qsl, urlparse
 
 from datetime import datetime, timedelta
+from random import randint
 
 from weboob.tools.compat import basestring
 from weboob.browser.browsers import LoginBrowser, need_login
@@ -55,7 +56,8 @@ class CreditMutuelBrowser(LoginBrowser):
     card =        URL('/(?P<subbank>.*)/fr/banque/operations_carte.cgi.*',   CardPage)
     noop =        URL('/(?P<subbank>.*)/fr/banque/CR/arrivee.asp.*',         NoOperationsPage)
     info =        URL('/(?P<subbank>.*)/fr/banque/BAD.*',                    EmptyPage)
-    transfert =   URL('/(?P<subbank>.*)/fr/banque/WI_VPLV_VirUniSaiCpt.asp\?(?P<parameters>.*)', TransfertPage)
+    transfert =   URL('/(?P<subbank>.*)/fr/banque/virements/vplw_vi.html',   EmptyPage)
+    transfert_2 = URL('/(?P<subbank>.*)/fr/banque/virements/vplw_cmweb.aspx.*', TransfertPage)
     change_pass = URL('/(?P<subbank>.*)/fr/validation/change_password.cgi',  ChangePasswordPage)
     verify_pass = URL('/(?P<subbank>.*)/fr/validation/verif_code.cgi.*',     VerifCodePage)
     empty =       URL('/(?P<subbank>.*)/fr/$',
@@ -141,23 +143,29 @@ class CreditMutuelBrowser(LoginBrowser):
 
     def transfer(self, account, to, amount, reason=None):
         # access the transfer page
-        parameters = 'RAZ=ALL&Cat=6&PERM=N&CHX=A'
-        page = self.transfert.go(subbank=self.currentSubBank, parameters=parameters)
+        self.transfert.go(subbank=self.currentSubBank)
 
         # fill the form
-        form = self.page.get_form(name='FormVirUniSaiCpt')
-        form['IDB'] = account[-1]
-        form['ICR'] = to[-1]
-        form['MTTVIR'] = '%s' % str(amount).replace('.', ',')
+        form = self.page.get_form(xpath="//form[@id='P:F']")
+        try:
+            form['data_input_indiceCompteADebiter'] = self.page.get_from_account_index(account)
+            form['data_input_indiceCompteACrediter'] = self.page.get_to_account_index(to)
+        except ValueError as e:
+            raise TransferError(e.message)
+        form['[t:dbt%3adouble;]data_input_montant_value_0_'] = '%s' % str(amount).replace('.', ',')
         if reason is not None:
-            form['LIBDBT'] = reason
-            form['LIBCRT'] = reason
-        page = form.submit()
+            form['[t:dbt%3astring;x(27)]data_input_libelleCompteDebite'] = reason
+            form['[t:dbt%3astring;x(31)]data_input_motifCompteCredite'] = reason
+        del form['_FID_GoCancel']
+        del form['_FID_DoValidate']
+        form['_FID_DoValidate.x'] = str(randint(3, 125))
+        form['_FID_DoValidate.y'] = str(randint(3, 22))
+        form.submit()
 
         # look for known errors
-        content = page.response.text
-        insufficient_amount_message     = u'Montant insuffisant.'
-        maximum_allowed_balance_message = u'Solde maximum autorisé dépassé.'
+        content = self.page.get_unicode_content()
+        insufficient_amount_message =     u'Le montant du virement doit être positif, veuillez le modifier'
+        maximum_allowed_balance_message = u'Montant maximum autorisé au débit pour ce compte'
 
         if insufficient_amount_message in content:
             raise TransferError('The amount you tried to transfer is too low.')
@@ -166,18 +174,21 @@ class CreditMutuelBrowser(LoginBrowser):
             raise TransferError('The maximum allowed balance for the target account has been / would be reached.')
 
         # look for the known "all right" message
-        ready_for_transfer_message = u'Confirmez un virement entre vos comptes'
-        if ready_for_transfer_message in content:
+        ready_for_transfer_message = u'Confirmer un virement entre vos comptes'
+        if ready_for_transfer_message not in content:
             raise TransferError('The expected message "%s" was not found.' % ready_for_transfer_message)
 
         # submit the confirmation form
-        form = page.get_form(name='FormVirUniCnf')
+        form = self.page.get_form(xpath="//form[@id='P:F']")
+        del form['_FID_DoConfirm']
+        form['_FID_DoConfirm.x'] = str(randint(3, 125))
+        form['_FID_DoConfirm.y'] = str(randint(3, 22))
         submit_date = datetime.now()
-        page = form.submit()
+        form.submit()
 
         # look for the known "everything went well" message
-        content = page.response.text
-        transfer_ok_message = u'Votre virement a été exécuté ce jour'
+        content = self.page.get_unicode_content()
+        transfer_ok_message = u'Votre virement a été exécuté'
         if transfer_ok_message not in content:
             raise TransferError('The expected message "%s" was not found.' % transfer_ok_message)
 
diff --git a/modules/creditmutuel/pages.py b/modules/creditmutuel/pages.py
index d172e29..1451ad7 100644
--- a/modules/creditmutuel/pages.py
+++ b/modules/creditmutuel/pages.py
@@ -69,7 +69,25 @@ class VerifCodePage(LoggedPage, HTMLPage):
 
 
 class TransfertPage(LoggedPage, HTMLPage):
-    pass
+    def get_account_index(self, direction, account):
+        for div in self.doc.getroot().cssselect(".dw_dli_contents"):
+            inp = div.cssselect("input")[0]
+            if inp.name != direction:
+                continue
+            acct = div.cssselect("span.doux")[0].text.replace(" ", "")
+            if account.endswith(acct):
+                return inp.attrib['value']
+        else:
+            raise ValueError("account %s not found" % account)
+
+    def get_from_account_index(self, account):
+        return self.get_account_index('data_input_indiceCompteADebiter', account)
+
+    def get_to_account_index(self, account):
+        return self.get_account_index('data_input_indiceCompteACrediter', account)
+
+    def get_unicode_content(self):
+        return self.content.decode(self.detect_encoding())
 
 
 class AccountsPage(LoggedPage, HTMLPage):
diff --git a/modules/ilmatieteenlaitos/pages.py b/modules/ilmatieteenlaitos/pages.py
new file mode 100644
index 0000000..a43a7a8
--- /dev/null
+++ b/modules/ilmatieteenlaitos/pages.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Matthieu Weber
+#
+# 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 date
+from itertools import imap, ifilter
+
+from weboob.browser.pages import JsonPage, HTMLPage
+from weboob.browser.elements import ItemElement, ListElement, method
+from weboob.capabilities.weather import Forecast, Current, City, Temperature
+from weboob.browser.filters.json import Dict
+from weboob.browser.filters.standard import Filter, CleanText, CleanDecimal, Regexp, Format, Date
+
+
+class DictElement(ListElement):
+    def find_elements(self):
+        if self.item_xpath is not None:
+            for el in self.el:
+                yield el
+        else:
+            yield self.el
+
+class Id(Filter):
+    def filter(self, txt):
+        return txt.split(", ")[0]
+
+class SearchCitiesPage(JsonPage):
+    @method
+    class iter_cities(DictElement):
+        item_xpath = '.'
+        ignore_duplicate = True
+
+        class item(ItemElement):
+            klass = City
+
+            obj_id = Id(Dict('id'))
+            obj_name = Dict('value')
+
+class WeatherPage(HTMLPage):
+    @method
+    class iter_forecast(ListElement):
+        item_xpath = '//div[contains(@class, "mid") and contains(@class, "local-weather-forecast")]//tr[@class="meteogram-dates"]/td'
+
+        class item(ItemElement):
+            klass = Forecast
+
+            obj_id = CleanText('.//span/@title')
+            def obj_date(self):
+                months = [u'tammikuuta', u'helmikuuta', u'maaliskuuta', u'huhtikuuta', u'toukokuuta', u'kesäkuuta',
+                        u'heinäkuuta', u'elokuuta', u'syyskuuta', u'lokakuuta', u'marraskuuta', u'joulukuuta']
+                d = CleanText('.//span/@title')(self).split()
+                return date(int(d[2]), months.index(d[1])+1, int(d[0].strip(".")))
+
+
+            def temperatures(self):
+                offset = int(CleanText('string(sum(./preceding-sibling::td/@colspan))')(self))
+                length = int(CleanText('@colspan')(self))
+                temps = CleanText('../../../tbody/tr[@class="meteogram-temperatures"]/td[position() > %d and position() <= %d]/span' % (offset, offset+length))(self)
+                return [float(_.strip(u'\xb0')) for _ in temps.split()]
+
+            def obj_low(self):
+                return Temperature(min(self.temperatures()), u'C')
+
+            def obj_high(self):
+                return Temperature(max(self.temperatures()), u'C')
+
+            def obj_text(self):
+                offset = int(CleanText('string(sum(./preceding-sibling::td/@colspan))')(self))
+                length = int(CleanText('@colspan')(self))
+                hour_test = '../../tr[@class="meteogram-times"]/td[position() > %d and position() <= %d and .//text() = "%%s"]' % (offset, offset+length)
+                hour_offset = 'string(count(%s/preceding-sibling::td)+1)' % (hour_test)
+                values = [
+                        '../../../tbody/tr[@class="meteogram-weather-symbols"]/td[position() = %d]/div/@title',
+                        '../../../tbody/tr[@class="meteogram-apparent-temperatures"]/td[position() = %d]/div/@title',
+                        '../../../tbody/tr[@class="meteogram-wind-symbols"]/td[position() = %d]/div/@title',
+                        '../../../tbody/tr[@class="meteogram-probabilities-of-precipitation"]/td[position() = %d]/div/@title',
+                        '../../../tbody/tr[@class="meteogram-hourly-precipitation-values"]/td[position() = %d]/span/@title',
+                    ]
+                def descriptive_text_for_hour(hour):
+                    hour_exists = CleanText(hour_test % hour)(self) == hour
+                    if hour_exists:
+                        offset = int(CleanText(hour_offset % hour)(self))
+                        def info_for_value(value):
+                            return CleanText(value % offset)(self).replace(u'edeltävän tunnin ', u'')
+                        return ("klo %s: " % hour) + ", ".join(ifilter(bool, imap(info_for_value, values)))
+
+                return "\n" + "\n".join(ifilter(bool, imap(descriptive_text_for_hour, ["02", "14"])))
+
+
+    @method
+    class get_current(ItemElement):
+        klass = Current
+
+        obj_id = date.today()
+        obj_date = Date(Regexp(CleanText('//table[@class="observation-text"]//span[@class="time-stamp"]'), r'^(\d+\.\d+.\d+)'))
+        obj_text = Format(u'%s, %s, %s',
+                CleanText(u'(//table[@class="observation-text"])//tr[2]/td[2]'),
+                CleanText(u'(//table[@class="observation-text"])//tr[5]/td[1]'),
+                CleanText(u'(//table[@class="observation-text"])//tr[4]/td[2]'),
+                )
+
+        def obj_temp(self):
+            path = u'//table[@class="observation-text"]//span[@class="parameter-name" and text() = "Lämpötila"]/../span[@class="parameter-value"]'
+            temp = CleanDecimal(Regexp(CleanText(path), r'^([^ \xa0]+)'), replace_dots=True)(self)
+            unit = Regexp(CleanText(path), r'\xb0(\w)')(self)
+            return Temperature(float(temp), unit)
diff --git a/weboob/browser/elements.py b/weboob/browser/elements.py
index 2ce2e8d..a029642 100644
--- a/weboob/browser/elements.py
+++ b/weboob/browser/elements.py
@@ -21,7 +21,7 @@ import re
 import sys
 from copy import deepcopy
 
-from weboob.tools.log import getLogger, DEBUG_FILTERS
+from weboob.tools.log import getLogger  # , DEBUG_FILTERS
 from weboob.tools.ordereddict import OrderedDict
 from weboob.browser.pages import NextPage
 
@@ -271,8 +271,8 @@ class ItemElement(AbstractElement):
             # Help debugging as tracebacks do not give us the key
             self.logger.warning('Attribute %s raises %s' % (key, repr(e)))
             raise
-        logger = getLogger('b2filters')
-        logger.log(DEBUG_FILTERS, "%s.%s = %r" % (self._random_id, key, value))
+        # logger = getLogger('b2filters')
+        # logger.log(DEBUG_FILTERS, "%s.%s = %r" % (self._random_id, key, value))
         setattr(self.obj, key, value)
 
 
-- 
1.7.10.4



More information about the weboob mailing list