From a3f9137df6d027d50e673f1b90a29bea57bd765d Mon Sep 17 00:00:00 2001 From: Vincent A Date: Sun, 6 Oct 2019 16:37:45 +0200 Subject: [PATCH] backport master modules fixes --- modules/afer/browser.py | 30 +-- .../afer/compat/weboob_capabilities_bank.py | 6 +- modules/afer/compat/weboob_exceptions.py | 38 ++++ modules/afer/module.py | 7 +- modules/afer/pages.py | 27 ++- modules/amazon/browser.py | 4 +- modules/amazon/compat/weboob_exceptions.py | 38 ++++ modules/amazonstorecard/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../compat/weboob_exceptions.py | 38 ++++ modules/ameli/browser.py | 4 +- modules/ameli/compat/weboob_exceptions.py | 38 ++++ modules/ameli/pages.py | 40 ++-- modules/amelipro/browser.py | 4 +- modules/amelipro/compat/__init__.py | 0 modules/amelipro/compat/weboob_exceptions.py | 38 ++++ modules/americanexpress/browser.py | 52 ++--- .../compat/weboob_capabilities_bank.py | 6 +- .../compat/weboob_exceptions.py | 38 ++++ modules/americanexpress/pages.py | 26 +-- modules/amundi/browser.py | 4 +- .../amundi/compat/weboob_capabilities_bank.py | 6 +- modules/amundi/compat/weboob_exceptions.py | 38 ++++ modules/amundi/pages.py | 2 +- modules/anticaptcha/browser.py | 4 +- modules/anticaptcha/compat/__init__.py | 0 .../compat/weboob_capabilities_captcha.py | 191 ++++++++++++++++ .../anticaptcha/compat/weboob_exceptions.py | 38 ++++ modules/anticaptcha/module.py | 2 +- modules/apivie/browser.py | 4 +- .../apivie/compat/weboob_capabilities_bank.py | 6 +- modules/apivie/compat/weboob_exceptions.py | 38 ++++ modules/asana/browser.py | 2 +- modules/asana/compat/__init__.py | 0 modules/asana/compat/weboob_exceptions.py | 38 ++++ modules/aum/browser.py | 2 +- modules/aum/compat/weboob_exceptions.py | 38 ++++ modules/aum/module.py | 2 +- modules/aum/optim/compat/__init__.py | 0 modules/aum/optim/compat/weboob_exceptions.py | 38 ++++ modules/aum/optim/profiles_walker.py | 2 +- modules/aum/optim/queries_queue.py | 2 +- modules/aum/optim/visibility.py | 2 +- modules/aum/test.py | 2 +- modules/axabanque/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/axabanque/compat/weboob_exceptions.py | 38 ++++ modules/axabanque/pages/bank.py | 2 +- .../pages/compat/weboob_capabilities_bank.py | 6 +- .../pages/compat/weboob_exceptions.py | 38 ++++ modules/axabanque/pages/login.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/banquepopulaire/browser.py | 8 +- .../compat/weboob_capabilities_bank.py | 6 +- .../compat/weboob_exceptions.py | 38 ++++ modules/banquepopulaire/pages.py | 2 +- modules/barclays/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/barclays/compat/weboob_exceptions.py | 38 ++++ modules/barclays/pages.py | 2 +- modules/becm/browser.py | 2 +- .../becm/compat/weboob_capabilities_bank.py | 6 +- modules/becm/compat/weboob_exceptions.py | 38 ++++ modules/becm/pages.py | 2 +- modules/bforbank/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/bforbank/compat/weboob_exceptions.py | 38 ++++ modules/bforbank/pages.py | 8 +- modules/binck/browser.py | 4 +- .../binck/compat/weboob_capabilities_bank.py | 6 +- modules/binck/compat/weboob_exceptions.py | 38 ++++ modules/binck/pages.py | 18 +- modules/biplan/compat/__init__.py | 0 modules/bnpcards/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/bnpcards/compat/weboob_exceptions.py | 38 ++++ modules/bnpcards/corporate/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- .../corporate/compat/weboob_exceptions.py | 38 ++++ modules/bnpcards/pages.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../company/compat/weboob_exceptions.py | 38 ++++ modules/bnporc/company/pages.py | 2 +- .../bnporc/compat/weboob_capabilities_bank.py | 6 +- modules/bnporc/enterprise/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../enterprise/compat/weboob_exceptions.py | 38 ++++ modules/bnporc/enterprise/pages.py | 50 ++--- modules/bnporc/pp/browser.py | 25 ++- .../pp/compat/weboob_capabilities_bank.py | 6 +- modules/bnporc/pp/compat/weboob_exceptions.py | 38 ++++ modules/bnporc/pp/pages.py | 2 +- modules/bnppere/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/bnppere/compat/weboob_exceptions.py | 38 ++++ .../bolden/compat/weboob_capabilities_bank.py | 6 +- modules/bolden/compat/weboob_exceptions.py | 38 ++++ modules/bolden/pages.py | 2 +- modules/boursorama/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../boursorama/compat/weboob_exceptions.py | 38 ++++ modules/boursorama/pages.py | 6 +- modules/bouygues/browser.py | 4 +- modules/bouygues/compat/weboob_exceptions.py | 38 ++++ modules/bouygues/pages.py | 2 +- modules/bp/browser.py | 5 +- modules/bp/compat/weboob_capabilities_bank.py | 6 +- modules/bp/compat/weboob_exceptions.py | 38 ++++ modules/bp/pages/accounthistory.py | 2 +- modules/bp/pages/accountlist.py | 13 +- .../pages/compat/weboob_capabilities_bank.py | 6 +- modules/bp/pages/compat/weboob_exceptions.py | 38 ++++ modules/bp/pages/login.py | 2 +- modules/bp/pages/pro.py | 2 +- modules/bp/pages/transfer.py | 2 +- modules/bred/bred/browser.py | 38 ++-- .../bred/compat/weboob_capabilities_bank.py | 6 +- modules/bred/bred/compat/weboob_exceptions.py | 38 ++++ modules/bred/bred/pages.py | 44 ++-- .../bred/compat/weboob_capabilities_bank.py | 6 +- modules/bred/dispobank/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- .../dispobank/compat/weboob_exceptions.py | 38 ++++ modules/bred/module.py | 25 ++- .../compat/weboob_capabilities_bank.py | 6 +- .../caels/compat/weboob_capabilities_bank.py | 6 +- modules/caissedepargne/browser.py | 110 +++++++--- modules/caissedepargne/cenet/browser.py | 2 +- .../cenet/compat/weboob_capabilities_bank.py | 6 +- .../cenet/compat/weboob_exceptions.py | 38 ++++ modules/caissedepargne/cenet/pages.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../compat/weboob_exceptions.py | 38 ++++ modules/caissedepargne/pages.py | 31 +-- .../compat/weboob_capabilities_bank.py | 6 +- modules/carrefourbanque/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../compat/weboob_exceptions.py | 38 ++++ modules/centquatre/browser.py | 4 +- .../centquatre/compat/weboob_exceptions.py | 38 ++++ .../cic/compat/weboob_capabilities_bank.py | 6 +- .../cices/compat/weboob_capabilities_bank.py | 6 +- modules/citibank/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/citibank/compat/weboob_exceptions.py | 38 ++++ modules/cityscoot/browser.py | 4 +- modules/cityscoot/compat/weboob_exceptions.py | 38 ++++ .../cmb/compat/weboob_capabilities_bank.py | 6 +- modules/cmes/browser.py | 4 +- .../cmes/compat/weboob_capabilities_bank.py | 6 +- modules/cmes/compat/weboob_exceptions.py | 38 ++++ modules/cmes/pages.py | 2 +- .../cmmc/compat/weboob_capabilities_bank.py | 6 +- .../cmso/compat/weboob_capabilities_bank.py | 6 +- modules/cmso/par/browser.py | 2 +- .../par/compat/weboob_capabilities_bank.py | 6 +- modules/cmso/par/compat/weboob_exceptions.py | 38 ++++ modules/cmso/par/pages.py | 2 +- modules/cmso/pro/browser.py | 2 +- .../pro/compat/weboob_capabilities_bank.py | 6 +- modules/cmso/pro/compat/weboob_exceptions.py | 38 ++++ modules/cmso/pro/pages.py | 2 +- modules/cragr/api/browser.py | 2 +- .../api/compat/weboob_capabilities_bank.py | 6 +- modules/cragr/api/compat/weboob_exceptions.py | 38 ++++ modules/cragr/api/pages.py | 4 +- .../cragr/compat/weboob_capabilities_bank.py | 6 +- modules/cragr/regions/browser.py | 15 +- .../compat/weboob_capabilities_bank.py | 6 +- .../cragr/regions/compat/weboob_exceptions.py | 38 ++++ modules/cragr/regions/pages.py | 4 +- modules/cragr/web/browser.py | 4 +- modules/cragr/web/compat/__init__.py | 0 modules/cragr/web/pages.py | 2 +- .../creditcooperatif/caisseepargne_browser.py | 13 +- .../creditcooperatif/caisseepargne_pages.py | 32 +++ .../compat/weboob_capabilities_bank.py | 6 +- modules/creditdunord/browser.py | 13 +- .../compat/weboob_capabilities_bank.py | 6 +- .../creditdunord/compat/weboob_exceptions.py | 38 ++++ modules/creditdunord/pages.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/creditdunordpee/pages.py | 2 +- modules/creditmutuel/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../creditmutuel/compat/weboob_exceptions.py | 38 ++++ modules/creditmutuel/pages.py | 167 +++++++------- .../dailymotion/compat/weboob_exceptions.py | 38 ++++ modules/dailymotion/pages.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/delubac/compat/weboob_exceptions.py | 38 ++++ modules/delubac/pages.py | 2 +- modules/dlfp/browser.py | 4 +- modules/dlfp/compat/__init__.py | 0 modules/dlfp/compat/weboob_exceptions.py | 38 ++++ modules/dlfp/module.py | 2 +- modules/edf/par/browser.py | 4 +- modules/edf/par/compat/weboob_exceptions.py | 38 ++++ modules/edf/pro/browser.py | 4 +- modules/edf/pro/compat/weboob_exceptions.py | 38 ++++ modules/edf/pro/pages.py | 2 +- modules/ekwateur/browser.py | 4 +- modules/ekwateur/compat/weboob_exceptions.py | 38 ++++ modules/ensap/browser.py | 4 +- modules/ensap/compat/weboob_exceptions.py | 38 ++++ .../compat/weboob_capabilities_bank.py | 6 +- .../esalia/compat/weboob_capabilities_bank.py | 6 +- modules/feedly/compat/weboob_exceptions.py | 38 ++++ modules/feedly/google.py | 4 +- modules/figgo/browser.py | 4 +- modules/figgo/compat/__init__.py | 0 modules/fortuneo/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/fortuneo/compat/weboob_exceptions.py | 38 ++++ modules/fortuneo/pages/accounts_list.py | 54 +++-- .../pages/compat/weboob_capabilities_bank.py | 6 +- .../pages/compat/weboob_exceptions.py | 38 ++++ modules/fortuneo/pages/login.py | 2 +- modules/freemobile/browser.py | 4 +- modules/freemobile/compat/__init__.py | 0 .../freemobile/compat/weboob_exceptions.py | 38 ++++ .../pages/compat/weboob_exceptions.py | 38 ++++ modules/freemobile/pages/history.py | 2 +- modules/funmooc/browser.py | 4 +- modules/funmooc/compat/weboob_exceptions.py | 38 ++++ .../compat/weboob_capabilities_bank.py | 6 +- modules/gmf/browser.py | 4 +- .../gmf/compat/weboob_capabilities_bank.py | 6 +- modules/gmf/compat/weboob_exceptions.py | 38 ++++ modules/gmf/pages.py | 2 +- modules/groupama/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/groupama/compat/weboob_exceptions.py | 38 ++++ .../compat/weboob_capabilities_bank.py | 6 +- modules/happn/browser.py | 2 +- modules/happn/compat/weboob_exceptions.py | 38 ++++ modules/happn/module.py | 2 +- modules/hsbc/browser.py | 13 +- .../hsbc/compat/weboob_capabilities_bank.py | 6 +- modules/hsbc/compat/weboob_exceptions.py | 38 ++++ modules/hsbc/pages/account_pages.py | 2 +- .../pages/compat/weboob_capabilities_bank.py | 6 +- .../hsbc/pages/compat/weboob_exceptions.py | 38 ++++ modules/hsbc/pages/investments.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/ideel/browser.py | 4 +- modules/ideel/compat/__init__.py | 0 modules/ideel/compat/weboob_exceptions.py | 38 ++++ modules/imdb/browser.py | 4 +- modules/imdb/compat/__init__.py | 0 modules/imdb/compat/weboob_exceptions.py | 38 ++++ modules/infomaniak/browser.py | 4 +- .../infomaniak/compat/weboob_exceptions.py | 38 ++++ .../api/compat/weboob_capabilities_bank.py | 6 +- modules/ing/api/transfer_page.py | 19 +- modules/ing/api_browser.py | 47 ++-- modules/ing/browser.py | 2 +- .../ing/compat/weboob_capabilities_bank.py | 6 +- modules/ing/compat/weboob_exceptions.py | 38 ++++ modules/ing/pages/compat/__init__.py | 0 modules/ing/pages/login.py | 2 +- .../web/compat/weboob_capabilities_bank.py | 6 +- modules/ing/web/compat/weboob_exceptions.py | 38 ++++ modules/ing/web/login.py | 2 +- modules/kiwibank/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/kiwibank/compat/weboob_exceptions.py | 38 ++++ modules/lampiris/browser.py | 4 +- modules/lampiris/compat/weboob_exceptions.py | 38 ++++ modules/lcl/browser.py | 102 +++++---- .../lcl/compat/weboob_capabilities_bank.py | 6 +- modules/lcl/compat/weboob_exceptions.py | 38 ++++ modules/lcl/enterprise/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../enterprise/compat/weboob_exceptions.py | 38 ++++ modules/lcl/enterprise/pages.py | 2 +- modules/lcl/pages.py | 86 ++++++-- modules/ldlc/browser.py | 4 +- modules/ldlc/compat/weboob_exceptions.py | 38 ++++ modules/ldlc/materielnet_pages.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../lendosphere/compat/weboob_exceptions.py | 38 ++++ modules/lendosphere/pages.py | 2 +- .../api/compat/weboob_capabilities_bank.py | 6 +- modules/linebourse/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- .../linebourse/compat/weboob_exceptions.py | 38 ++++ modules/linebourse/pages.py | 2 +- modules/lolix/compat/__init__.py | 0 modules/lucca/browser.py | 4 +- modules/lucca/compat/__init__.py | 0 modules/lucca/compat/weboob_exceptions.py | 38 ++++ modules/materielnet/browser.py | 4 +- .../materielnet/compat/weboob_exceptions.py | 38 ++++ modules/materielnet/pages.py | 2 +- modules/mediawiki/browser.py | 2 +- modules/mediawiki/compat/__init__.py | 0 modules/mediawiki/compat/weboob_exceptions.py | 38 ++++ modules/myedenred/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/myedenred/compat/weboob_exceptions.py | 38 ++++ modules/myfoncia/browser.py | 4 +- modules/myfoncia/compat/weboob_exceptions.py | 38 ++++ modules/myhabit/browser.py | 4 +- modules/myhabit/compat/__init__.py | 0 modules/myhabit/compat/weboob_exceptions.py | 38 ++++ modules/n26/browser.py | 2 +- .../n26/compat/weboob_capabilities_bank.py | 6 +- modules/n26/compat/weboob_exceptions.py | 38 ++++ .../nalo/compat/weboob_capabilities_bank.py | 6 +- modules/nef/browser.py | 4 +- .../nef/compat/weboob_capabilities_bank.py | 6 +- modules/nef/compat/weboob_exceptions.py | 38 ++++ modules/netfinca/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/netfinca/compat/weboob_exceptions.py | 38 ++++ modules/netfinca/pages.py | 2 + modules/okc/browser.py | 2 +- modules/okc/compat/weboob_exceptions.py | 38 ++++ modules/oney/browser.py | 97 +++++++-- .../oney/compat/weboob_capabilities_bank.py | 6 +- modules/oney/compat/weboob_exceptions.py | 38 ++++ .../weboob_tools_captcha_virtkeyboard.py | 203 ------------------ modules/oney/pages.py | 176 ++++++--------- modules/onlinenet/browser.py | 4 +- modules/onlinenet/compat/weboob_exceptions.py | 38 ++++ modules/openedx/browser.py | 4 +- modules/openedx/compat/__init__.py | 0 modules/openedx/compat/weboob_exceptions.py | 38 ++++ modules/orange/browser.py | 4 +- modules/orange/compat/__init__.py | 0 modules/orange/compat/weboob_exceptions.py | 38 ++++ modules/ovh/browser.py | 4 +- modules/ovh/compat/weboob_exceptions.py | 38 ++++ modules/ovh/pages.py | 2 +- modules/pastealacon/browser.py | 2 +- .../pastealacon/compat/weboob_exceptions.py | 38 ++++ modules/pastebin/browser.py | 2 +- modules/pastebin/compat/weboob_exceptions.py | 38 ++++ modules/paypal/browser.py | 3 +- .../paypal/compat/weboob_capabilities_bank.py | 6 +- modules/paypal/compat/weboob_exceptions.py | 38 ++++ modules/paypal/pages.py | 27 ++- modules/phpbb/browser.py | 4 +- modules/phpbb/compat/__init__.py | 0 modules/phpbb/compat/weboob_exceptions.py | 38 ++++ modules/pixabay/browser.py | 4 +- modules/pixabay/compat/__init__.py | 0 modules/pixabay/compat/weboob_exceptions.py | 38 ++++ modules/playme/browser.py | 4 +- modules/playme/compat/__init__.py | 0 modules/playme/compat/weboob_exceptions.py | 38 ++++ modules/poivy/browser.py | 4 +- modules/poivy/compat/weboob_exceptions.py | 38 ++++ modules/poivy/pages.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/redmine/browser.py | 4 +- modules/redmine/compat/__init__.py | 0 modules/redmine/compat/weboob_exceptions.py | 38 ++++ modules/redmine/module.py | 2 +- .../regionsjob/compat/weboob_exceptions.py | 38 ++++ modules/regionsjob/pages.py | 2 +- modules/relaiscolis/compat/__init__.py | 0 .../relaiscolis/compat/weboob_exceptions.py | 38 ++++ modules/relaiscolis/module.py | 2 +- modules/residentadvisor/browser.py | 4 +- .../compat/weboob_exceptions.py | 38 ++++ modules/s2e/browser.py | 5 +- .../s2e/compat/weboob_capabilities_bank.py | 6 +- modules/s2e/compat/weboob_exceptions.py | 38 ++++ modules/s2e/pages.py | 2 +- modules/sachsen/compat/weboob_exceptions.py | 38 ++++ modules/sachsen/pages.py | 2 +- modules/societegenerale/browser.py | 6 +- .../compat/weboob_capabilities_bank.py | 6 +- .../compat/weboob_exceptions.py | 38 ++++ .../societegenerale/pages/accounts_list.py | 2 +- .../pages/compat/weboob_capabilities_bank.py | 6 +- .../pages/compat/weboob_exceptions.py | 38 ++++ modules/societegenerale/pages/login.py | 10 +- modules/societegenerale/pages/transfer.py | 2 +- modules/societegenerale/sgpe/browser.py | 4 +- .../sgpe/compat/weboob_capabilities_bank.py | 6 +- .../sgpe/compat/weboob_exceptions.py | 38 ++++ modules/societegenerale/sgpe/json_pages.py | 2 +- modules/societegenerale/sgpe/pages.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../sogecartenet/compat/weboob_exceptions.py | 38 ++++ modules/sogecartenet/pages.py | 2 +- modules/spirica/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/spirica/compat/weboob_exceptions.py | 38 ++++ modules/spirica/pages.py | 4 +- modules/suravenir/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- modules/suravenir/compat/weboob_exceptions.py | 38 ++++ modules/t411/browser.py | 2 +- modules/t411/compat/weboob_exceptions.py | 38 ++++ modules/tapatalk/compat/__init__.py | 0 modules/tapatalk/compat/weboob_exceptions.py | 38 ++++ modules/tapatalk/module.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../themisbanque/compat/weboob_exceptions.py | 38 ++++ modules/themisbanque/pages.py | 2 +- modules/ticketscesu/browser.py | 4 +- .../compat/weboob_capabilities_bank.py | 6 +- .../ticketscesu/compat/weboob_exceptions.py | 38 ++++ modules/tinder/browser.py | 2 +- modules/tinder/compat/weboob_exceptions.py | 38 ++++ modules/trainline/browser.py | 2 +- modules/trainline/compat/weboob_exceptions.py | 38 ++++ modules/twitter/browser.py | 4 +- modules/twitter/compat/weboob_exceptions.py | 38 ++++ modules/twitter/module.py | 2 +- modules/vicsec/browser.py | 4 +- modules/vicsec/compat/__init__.py | 0 modules/vicsec/compat/weboob_exceptions.py | 38 ++++ modules/vicseccard/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../vicseccard/compat/weboob_exceptions.py | 38 ++++ modules/vimeo/compat/weboob_exceptions.py | 38 ++++ modules/vimeo/pages.py | 2 +- modules/wellsfargo/browser.py | 2 +- .../compat/weboob_capabilities_bank.py | 6 +- .../wellsfargo/compat/weboob_exceptions.py | 38 ++++ .../wiseed/compat/weboob_capabilities_bank.py | 6 +- modules/wiseed/compat/weboob_exceptions.py | 38 ++++ modules/wiseed/pages.py | 2 +- modules/yggtorrent/browser.py | 2 +- .../yggtorrent/compat/weboob_exceptions.py | 38 ++++ modules/yomoni/browser.py | 11 +- .../yomoni/compat/weboob_capabilities_bank.py | 6 +- modules/yomoni/compat/weboob_exceptions.py | 38 ++++ modules/youtube/module.py | 2 +- 434 files changed, 6434 insertions(+), 1069 deletions(-) create mode 100644 modules/afer/compat/weboob_exceptions.py create mode 100644 modules/amazon/compat/weboob_exceptions.py create mode 100644 modules/amazonstorecard/compat/weboob_exceptions.py create mode 100644 modules/ameli/compat/weboob_exceptions.py create mode 100644 modules/amelipro/compat/__init__.py create mode 100644 modules/amelipro/compat/weboob_exceptions.py create mode 100644 modules/americanexpress/compat/weboob_exceptions.py create mode 100644 modules/amundi/compat/weboob_exceptions.py create mode 100644 modules/anticaptcha/compat/__init__.py create mode 100644 modules/anticaptcha/compat/weboob_capabilities_captcha.py create mode 100644 modules/anticaptcha/compat/weboob_exceptions.py create mode 100644 modules/apivie/compat/weboob_exceptions.py create mode 100644 modules/asana/compat/__init__.py create mode 100644 modules/asana/compat/weboob_exceptions.py create mode 100644 modules/aum/compat/weboob_exceptions.py create mode 100644 modules/aum/optim/compat/__init__.py create mode 100644 modules/aum/optim/compat/weboob_exceptions.py create mode 100644 modules/axabanque/compat/weboob_exceptions.py create mode 100644 modules/axabanque/pages/compat/weboob_exceptions.py create mode 100644 modules/banquepopulaire/compat/weboob_exceptions.py create mode 100644 modules/barclays/compat/weboob_exceptions.py create mode 100644 modules/becm/compat/weboob_exceptions.py create mode 100644 modules/bforbank/compat/weboob_exceptions.py create mode 100644 modules/binck/compat/weboob_exceptions.py create mode 100644 modules/biplan/compat/__init__.py create mode 100644 modules/bnpcards/compat/weboob_exceptions.py create mode 100644 modules/bnpcards/corporate/compat/weboob_exceptions.py create mode 100644 modules/bnporc/company/compat/weboob_exceptions.py create mode 100644 modules/bnporc/enterprise/compat/weboob_exceptions.py create mode 100644 modules/bnporc/pp/compat/weboob_exceptions.py create mode 100644 modules/bnppere/compat/weboob_exceptions.py create mode 100644 modules/bolden/compat/weboob_exceptions.py create mode 100644 modules/boursorama/compat/weboob_exceptions.py create mode 100644 modules/bouygues/compat/weboob_exceptions.py create mode 100644 modules/bp/compat/weboob_exceptions.py create mode 100644 modules/bp/pages/compat/weboob_exceptions.py create mode 100644 modules/bred/bred/compat/weboob_exceptions.py create mode 100644 modules/bred/dispobank/compat/weboob_exceptions.py create mode 100644 modules/caissedepargne/cenet/compat/weboob_exceptions.py create mode 100644 modules/caissedepargne/compat/weboob_exceptions.py create mode 100644 modules/carrefourbanque/compat/weboob_exceptions.py create mode 100644 modules/centquatre/compat/weboob_exceptions.py create mode 100644 modules/citibank/compat/weboob_exceptions.py create mode 100644 modules/cityscoot/compat/weboob_exceptions.py create mode 100644 modules/cmes/compat/weboob_exceptions.py create mode 100644 modules/cmso/par/compat/weboob_exceptions.py create mode 100644 modules/cmso/pro/compat/weboob_exceptions.py create mode 100644 modules/cragr/api/compat/weboob_exceptions.py create mode 100644 modules/cragr/regions/compat/weboob_exceptions.py create mode 100644 modules/cragr/web/compat/__init__.py create mode 100644 modules/creditcooperatif/caisseepargne_pages.py create mode 100644 modules/creditdunord/compat/weboob_exceptions.py create mode 100644 modules/creditmutuel/compat/weboob_exceptions.py create mode 100644 modules/dailymotion/compat/weboob_exceptions.py create mode 100644 modules/delubac/compat/weboob_exceptions.py create mode 100644 modules/dlfp/compat/__init__.py create mode 100644 modules/dlfp/compat/weboob_exceptions.py create mode 100644 modules/edf/par/compat/weboob_exceptions.py create mode 100644 modules/edf/pro/compat/weboob_exceptions.py create mode 100644 modules/ekwateur/compat/weboob_exceptions.py create mode 100644 modules/ensap/compat/weboob_exceptions.py create mode 100644 modules/feedly/compat/weboob_exceptions.py create mode 100644 modules/figgo/compat/__init__.py create mode 100644 modules/fortuneo/compat/weboob_exceptions.py create mode 100644 modules/fortuneo/pages/compat/weboob_exceptions.py create mode 100644 modules/freemobile/compat/__init__.py create mode 100644 modules/freemobile/compat/weboob_exceptions.py create mode 100644 modules/freemobile/pages/compat/weboob_exceptions.py create mode 100644 modules/funmooc/compat/weboob_exceptions.py create mode 100644 modules/gmf/compat/weboob_exceptions.py create mode 100644 modules/groupama/compat/weboob_exceptions.py create mode 100644 modules/happn/compat/weboob_exceptions.py create mode 100644 modules/hsbc/compat/weboob_exceptions.py create mode 100644 modules/hsbc/pages/compat/weboob_exceptions.py create mode 100644 modules/ideel/compat/__init__.py create mode 100644 modules/ideel/compat/weboob_exceptions.py create mode 100644 modules/imdb/compat/__init__.py create mode 100644 modules/imdb/compat/weboob_exceptions.py create mode 100644 modules/infomaniak/compat/weboob_exceptions.py create mode 100644 modules/ing/compat/weboob_exceptions.py create mode 100644 modules/ing/pages/compat/__init__.py create mode 100644 modules/ing/web/compat/weboob_exceptions.py create mode 100644 modules/kiwibank/compat/weboob_exceptions.py create mode 100644 modules/lampiris/compat/weboob_exceptions.py create mode 100644 modules/lcl/compat/weboob_exceptions.py create mode 100644 modules/lcl/enterprise/compat/weboob_exceptions.py create mode 100644 modules/ldlc/compat/weboob_exceptions.py create mode 100644 modules/lendosphere/compat/weboob_exceptions.py create mode 100644 modules/linebourse/compat/weboob_exceptions.py create mode 100644 modules/lolix/compat/__init__.py create mode 100644 modules/lucca/compat/__init__.py create mode 100644 modules/lucca/compat/weboob_exceptions.py create mode 100644 modules/materielnet/compat/weboob_exceptions.py create mode 100644 modules/mediawiki/compat/__init__.py create mode 100644 modules/mediawiki/compat/weboob_exceptions.py create mode 100644 modules/myedenred/compat/weboob_exceptions.py create mode 100644 modules/myfoncia/compat/weboob_exceptions.py create mode 100644 modules/myhabit/compat/__init__.py create mode 100644 modules/myhabit/compat/weboob_exceptions.py create mode 100644 modules/n26/compat/weboob_exceptions.py create mode 100644 modules/nef/compat/weboob_exceptions.py create mode 100644 modules/netfinca/compat/weboob_exceptions.py create mode 100644 modules/okc/compat/weboob_exceptions.py create mode 100644 modules/oney/compat/weboob_exceptions.py delete mode 100644 modules/oney/compat/weboob_tools_captcha_virtkeyboard.py create mode 100644 modules/onlinenet/compat/weboob_exceptions.py create mode 100644 modules/openedx/compat/__init__.py create mode 100644 modules/openedx/compat/weboob_exceptions.py create mode 100644 modules/orange/compat/__init__.py create mode 100644 modules/orange/compat/weboob_exceptions.py create mode 100644 modules/ovh/compat/weboob_exceptions.py create mode 100644 modules/pastealacon/compat/weboob_exceptions.py create mode 100644 modules/pastebin/compat/weboob_exceptions.py create mode 100644 modules/paypal/compat/weboob_exceptions.py create mode 100644 modules/phpbb/compat/__init__.py create mode 100644 modules/phpbb/compat/weboob_exceptions.py create mode 100644 modules/pixabay/compat/__init__.py create mode 100644 modules/pixabay/compat/weboob_exceptions.py create mode 100644 modules/playme/compat/__init__.py create mode 100644 modules/playme/compat/weboob_exceptions.py create mode 100644 modules/poivy/compat/weboob_exceptions.py create mode 100644 modules/redmine/compat/__init__.py create mode 100644 modules/redmine/compat/weboob_exceptions.py create mode 100644 modules/regionsjob/compat/weboob_exceptions.py create mode 100644 modules/relaiscolis/compat/__init__.py create mode 100644 modules/relaiscolis/compat/weboob_exceptions.py create mode 100644 modules/residentadvisor/compat/weboob_exceptions.py create mode 100644 modules/s2e/compat/weboob_exceptions.py create mode 100644 modules/sachsen/compat/weboob_exceptions.py create mode 100644 modules/societegenerale/compat/weboob_exceptions.py create mode 100644 modules/societegenerale/pages/compat/weboob_exceptions.py create mode 100644 modules/societegenerale/sgpe/compat/weboob_exceptions.py create mode 100644 modules/sogecartenet/compat/weboob_exceptions.py create mode 100644 modules/spirica/compat/weboob_exceptions.py create mode 100644 modules/suravenir/compat/weboob_exceptions.py create mode 100644 modules/t411/compat/weboob_exceptions.py create mode 100644 modules/tapatalk/compat/__init__.py create mode 100644 modules/tapatalk/compat/weboob_exceptions.py create mode 100644 modules/themisbanque/compat/weboob_exceptions.py create mode 100644 modules/ticketscesu/compat/weboob_exceptions.py create mode 100644 modules/tinder/compat/weboob_exceptions.py create mode 100644 modules/trainline/compat/weboob_exceptions.py create mode 100644 modules/twitter/compat/weboob_exceptions.py create mode 100644 modules/vicsec/compat/__init__.py create mode 100644 modules/vicsec/compat/weboob_exceptions.py create mode 100644 modules/vicseccard/compat/weboob_exceptions.py create mode 100644 modules/vimeo/compat/weboob_exceptions.py create mode 100644 modules/wellsfargo/compat/weboob_exceptions.py create mode 100644 modules/wiseed/compat/weboob_exceptions.py create mode 100644 modules/yggtorrent/compat/weboob_exceptions.py create mode 100644 modules/yomoni/compat/weboob_exceptions.py diff --git a/modules/afer/browser.py b/modules/afer/browser.py index 8cb7fefa0d..5b7b704791 100644 --- a/modules/afer/browser.py +++ b/modules/afer/browser.py @@ -20,28 +20,29 @@ from __future__ import unicode_literals from random import randint -from weboob.browser import URL, LoginBrowser, need_login -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from weboob.browser.browsers import URL, LoginBrowser, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable, BrowserPasswordExpired from weboob.tools.compat import basestring -from .pages import LoginPage, IndexPage, BadLogin, AccountDetailPage, AccountHistoryPage +from .pages import ( + LoginPage, IndexPage, WrongPasswordPage, WrongWebsitePage, + AccountDetailPage, AccountHistoryPage, MigrationPage, +) class AferBrowser(LoginBrowser): BASEURL = 'https://adherent.gie-afer.fr' - login = URL('/web/ega.nsf/listeAdhesions\?OpenForm', LoginPage) - bad_login = URL('/names.nsf\?Login', BadLogin) + login = URL(r'/espaceadherent/MonCompte/Connexion$', LoginPage) + wrong_password = URL(r'/espaceadherent/MonCompte/Connexion\?err=6001', WrongPasswordPage) + wrong_website = URL(r'/espaceadherent/MonCompte/Connexion\?err=6008', WrongWebsitePage) + migration = URL(r'/espaceadherent/MonCompte/Migration', MigrationPage) index = URL('/web/ega.nsf/listeAdhesions\?OpenForm', IndexPage) account_detail = URL('/web/ega.nsf/soldeEpargne\?openForm', AccountDetailPage) account_history = URL('/web/ega.nsf/generationSearchModule\?OpenAgent', AccountHistoryPage) history_detail = URL('/web/ega.nsf/WOpendetailOperation\?OpenAgent', AccountHistoryPage) def do_login(self): - """ - Attempt to log in. - Note: this method does nothing if we are already logged in. - """ assert isinstance(self.username, basestring) assert isinstance(self.password, basestring) self.login.go() @@ -51,13 +52,14 @@ def do_login(self): except BrowserUnavailable: raise BrowserIncorrectPassword() - if self.bad_login.is_here(): + if self.migration.is_here(): + raise BrowserPasswordExpired(self.page.get_error()) + + if self.wrong_password.is_here(): error = self.page.get_error() - if "La saisie de l’identifiant ou du code confidentiel est incorrecte" in error or \ - "Veuillez-vous identifier" in error: + if error: raise BrowserIncorrectPassword(error) - else: - assert False, "Message d'erreur inconnu: %s" % error + assert False, 'We landed on WrongPasswordPage but no error message was fetched.' @need_login diff --git a/modules/afer/compat/weboob_capabilities_bank.py b/modules/afer/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/afer/compat/weboob_capabilities_bank.py +++ b/modules/afer/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/afer/compat/weboob_exceptions.py b/modules/afer/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/afer/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/afer/module.py b/modules/afer/module.py index 01a4a22255..5116d8393c 100644 --- a/modules/afer/module.py +++ b/modules/afer/module.py @@ -39,8 +39,11 @@ class AferModule(Module, CapBankWealth): VERSION = '1.5' BROWSER = AferBrowser - CONFIG = BackendConfig(ValueBackendPassword('login', label='Username', regexp='[A-z]\d+', masked=False), - ValueBackendPassword('password', label=u"mdp", regexp='\d{1,8}')) + CONFIG = BackendConfig( + ValueBackendPassword('login', label='Identifiant', regexp=r'.+', masked=False), + ValueBackendPassword('password', label="Mot de passe", regexp=r'\d{1,8}|[a-zA-Z0-9]{7,30}') + # TODO lose previous regex (and in backend) once users credentials migration is complete + ) def create_default_browser(self): return self.create_browser(self.config['login'].get(), diff --git a/modules/afer/pages.py b/modules/afer/pages.py index bbdf8baee7..ea02b77ed8 100644 --- a/modules/afer/pages.py +++ b/modules/afer/pages.py @@ -28,23 +28,36 @@ from weboob.browser.pages import HTMLPage, LoggedPage, pagination from .compat.weboob_capabilities_bank import Account, Investment, Transaction from weboob.capabilities.base import NotAvailable -from weboob.exceptions import BrowserUnavailable, ActionNeeded +from .compat.weboob_exceptions import BrowserUnavailable, ActionNeeded class LoginPage(HTMLPage): def login(self, login, passwd): - form = self.get_form(name='_DominoForm') - form['Username'] = login + form = self.get_form(id='loginForm') + form['username'] = login form['password'] = passwd form.submit() - def is_here(self): - return bool(self.doc.xpath('//form[@name="_DominoForm"]')) + +class WrongPasswordPage(HTMLPage): + def get_error(self): + return CleanText('//p[contains(text(), "Votre saisie est erronée")]')(self.doc) + + +class WrongWebsitePage(HTMLPage): + # We land on this page when the website indicates that + # an account is already created on the 'Aviva et moi' space, + # So we check the message and raise ActionNeeded with it + def on_load(self): + message = CleanText('//p[contains(text(), "Vous êtes déjà inscrit")]')(self.doc) + if message: + raise ActionNeeded(message) + assert False, 'We landed on WrongWebsitePage but no message was fetched.' -class BadLogin(HTMLPage): +class MigrationPage(HTMLPage): def get_error(self): - return CleanText('//div[@id="idDivErrorLogin"]')(self.doc) + return CleanText('//h1[contains(text(), "Votre nouvel identifiant et mot de passe")]')(self.doc) class IndexPage(LoggedPage, HTMLPage): diff --git a/modules/amazon/browser.py b/modules/amazon/browser.py index 6a3f88d881..cf1b096445 100644 --- a/modules/amazon/browser.py +++ b/modules/amazon/browser.py @@ -20,8 +20,8 @@ from __future__ import unicode_literals from datetime import date -from weboob.browser import LoginBrowser, URL, need_login, StatesMixin -from weboob.exceptions import ( +from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin +from .compat.weboob_exceptions import ( BrowserIncorrectPassword, BrowserUnavailable, ImageCaptchaQuestion, BrowserQuestion, ActionNeeded, WrongCaptchaResponse ) diff --git a/modules/amazon/compat/weboob_exceptions.py b/modules/amazon/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/amazon/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/amazonstorecard/browser.py b/modules/amazonstorecard/browser.py index 8f63a56d25..aaca60da68 100644 --- a/modules/amazonstorecard/browser.py +++ b/modules/amazonstorecard/browser.py @@ -25,7 +25,7 @@ from weboob.browser.browsers import URL, LoginBrowser, need_login from .compat.weboob_capabilities_bank import AccountNotFound -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.compat import unquote from .pages import ActivityPage, SomePage, StatementPage, StatementsPage, SummaryPage diff --git a/modules/amazonstorecard/compat/weboob_capabilities_bank.py b/modules/amazonstorecard/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/amazonstorecard/compat/weboob_capabilities_bank.py +++ b/modules/amazonstorecard/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/amazonstorecard/compat/weboob_exceptions.py b/modules/amazonstorecard/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/amazonstorecard/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ameli/browser.py b/modules/ameli/browser.py index 72b47aaeb1..43f316f57c 100644 --- a/modules/ameli/browser.py +++ b/modules/ameli/browser.py @@ -42,7 +42,7 @@ def do_login(self): @need_login def iter_subscription(self): self.subscription_page.go() - return self.page.iter_subscriptions() + yield self.page.get_subscription() @need_login def iter_documents(self, subscription): @@ -75,6 +75,6 @@ def iter_documents(self, subscription): # then we set Rechercher to actionEvt to filter for this subscription, within last 6 months # without first request we would have filter for this subscription but within last 2 months params['actionEvt'] = 'Rechercher' - params['Beneficiaire'] = subscription._param + params['Beneficiaire'] = 'tout_selectionner' self.documents_page.go(params=params) return self.page.iter_documents(subid=subscription.id) diff --git a/modules/ameli/compat/weboob_exceptions.py b/modules/ameli/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ameli/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ameli/pages.py b/modules/ameli/pages.py index d90467b404..91ff969d86 100644 --- a/modules/ameli/pages.py +++ b/modules/ameli/pages.py @@ -19,15 +19,15 @@ from __future__ import unicode_literals -import re from weboob.browser.elements import method, ListElement, ItemElement -from weboob.browser.filters.html import Attr, Link +from weboob.browser.filters.html import Link from .compat.weboob_browser_filters_standard import CleanText, Regexp, CleanDecimal, Currency, Field, Format, Env from weboob.browser.pages import LoggedPage, HTMLPage, PartialHTMLPage from weboob.capabilities.bill import Subscription, Bill -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from weboob.tools.date import parse_french_date +from weboob.tools.json import json class LoginPage(HTMLPage): @@ -45,32 +45,22 @@ def on_load(self): class SubscriptionPage(LoggedPage, HTMLPage): - @method - class iter_subscriptions(ListElement): - item_xpath = '//div[@id="corps-de-la-page"]//div[@class="tableau"]/div' - - class item(ItemElement): - klass = Subscription - - obj__labelid = Attr('.', 'aria-labelledby') + def get_subscription(self): + sub = Subscription() + # DON'T TAKE social security number for id because it's a very confidential data, take birth date instead + sub.id = CleanText('//button[@id="idAssure"]//td[@class="dateNaissance"]')(self.doc).replace('/', '') + sub.label = sub.subscriber = CleanText('//div[@id="pageAssure"]//span[@class="NomEtPrenomLabel"]')(self.doc) - def obj__birthdate(self): - return CleanText('//button[@id="%s"]//td[@class="dateNaissance"]' % Field('_labelid')(self))(self) + return sub - def obj_id(self): - # DON'T TAKE social security number for id because it's a very confidential data, take birth date instead - return ''.join(re.findall(r'\d+', Field('_birthdate')(self))) - def obj__param(self): - reversed_date = ''.join(reversed(re.findall(r'\d+', Field('_birthdate')(self)))) - name = CleanText('//button[@id="%s"]//td[@class="nom"]' % Field('_labelid')(self))(self) - return '%s!-!%s!-!1' % (reversed_date, name) - - obj_subscriber = CleanText('.//span[@class="NomEtPrenomLabel"]') - obj_label = obj_subscriber +class DocumentsPage(LoggedPage, PartialHTMLPage): + ENCODING = 'utf-8' + def build_doc(self, content): + res = json.loads(content) + return super(DocumentsPage, self).build_doc(res['tableauPaiement'].encode('utf-8')) -class DocumentsPage(LoggedPage, PartialHTMLPage): @method class iter_documents(ListElement): item_xpath = '//ul[@id="unordered_list"]//li[has-class("rowitem")]' @@ -82,7 +72,7 @@ class item(ItemElement): obj_label = CleanText('.//div[has-class("col-label")]') obj_price = CleanDecimal.French('.//div[has-class("col-montant")]/span') obj_currency = Currency('.//div[has-class("col-montant")]/span') - obj_url = Link('.//a[@class="downdetail"]') + obj_url = Link('.//div[@class="col-download"]/a') obj_format = 'pdf' def obj_date(self): diff --git a/modules/amelipro/browser.py b/modules/amelipro/browser.py index 8b695f771c..27a2f66b0a 100644 --- a/modules/amelipro/browser.py +++ b/modules/amelipro/browser.py @@ -17,8 +17,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.capabilities.bill import Detail from weboob.tools.compat import urlencode from decimal import Decimal diff --git a/modules/amelipro/compat/__init__.py b/modules/amelipro/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/amelipro/compat/weboob_exceptions.py b/modules/amelipro/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/amelipro/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/americanexpress/browser.py b/modules/americanexpress/browser.py index 0282188628..1ccee32834 100644 --- a/modules/americanexpress/browser.py +++ b/modules/americanexpress/browser.py @@ -17,17 +17,20 @@ # 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 + import datetime -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.browser.browsers import LoginBrowser, need_login from weboob.browser.exceptions import HTTPNotFound, ServerError from .compat.weboob_browser_url import URL +from dateutil.parser import parse as parse_date from .pages import ( AccountsPage, JsonBalances, JsonPeriods, JsonHistory, JsonBalances2, CurrencyPage, LoginPage, WrongLoginPage, AccountSuspendedPage, - NoCardPage, NotFoundPage + NoCardPage, NotFoundPage, ) @@ -49,16 +52,16 @@ class AmericanExpressBrowser(LoginBrowser): js_posted = URL(r'/account-data/v1/financials/transactions\?limit=1000&offset=(?P\d+)&statement_end_date=(?P[0-9-]+)&status=posted', JsonHistory) js_periods = URL(r'/account-data/v1/financials/statement_periods', JsonPeriods) - currency_page = URL(r'https://www.aexp-static.com/cdaas/axp-app/modules/axp-offers/1.11.1/fr-fr/axp-offers.json', CurrencyPage) + currency_page = URL(r'https://www.aexp-static.com/cdaas/axp-app/modules/axp-balance-summary/4.7.0/(?P\w\w-\w\w)/axp-balance-summary.json', CurrencyPage) - no_card = URL('https://www.americanexpress.com/us/content/no-card/', - 'https://www.americanexpress.com/us/no-card/', NoCardPage) + no_card = URL(r'https://www.americanexpress.com/us/content/no-card/', + r'https://www.americanexpress.com/us/no-card/', NoCardPage) not_found = URL(r'/accounts/error', NotFoundPage) SUMMARY_CARD_LABEL = [ - u'PAYMENT RECEIVED - THANK YOU', - u'PRELEVEMENT AUTOMATIQUE ENREGISTRE-MERCI' + 'PAYMENT RECEIVED - THANK YOU', + 'PRELEVEMENT AUTOMATIQUE ENREGISTRE-MERCI', ] def __init__(self, *args, **kwargs): @@ -73,7 +76,6 @@ def do_login(self): if self.wrong_login.is_here() or self.login.is_here() or self.account_suspended.is_here(): raise BrowserIncorrectPassword() - @need_login def get_accounts(self): self.accounts.go() @@ -91,7 +93,8 @@ def get_accounts(self): self.page.set_balances(accounts) # get currency - self.currency_page.go() + loc = self.session.cookies.get_dict(domain=".americanexpress.com")['axplocale'].lower() + self.currency_page.go(locale=loc) currency = self.page.get_currency() for acc in accounts: @@ -120,29 +123,30 @@ def iter_history(self, account): @need_login def iter_coming(self, account): - """ - Coming transactions can be found in a'pending' JSON if it exists (corresponding a 'Transactions en attente' tab on the website), - as well as in a 'posted' JSON (corresponding a 'Transactions enregistrées' tab on the website for futur transactions) - """ + # Coming transactions can be found in a 'pending' JSON if it exists + # ('En attente' tab on the website), as well as in a 'posted' JSON + # ('Enregistrées' tab on the website) + # "pending" have no vdate and debit date is in future self.js_periods.go(headers={'account_token': account._token}) - date = datetime.datetime.strptime(self.page.get_periods()[0], '%Y-%m-%d').date() periods = self.page.get_periods() + date = parse_date(periods[0]).date() today = datetime.date.today() - try: - self.js_pending.go(offset=0, headers={'account_token': account._token}) - # when the latest period ends today we can't know the coming debit date - if date != today: + # when the latest period ends today we can't know the coming debit date + if date != today: + try: + self.js_pending.go(offset=0, headers={'account_token': account._token}) + except ServerError as exc: + # At certain times of the month a connection might not have pendings; + # in that case, `js_pending.go` would throw a 502 error Bad Gateway + error_code = exc.response.json().get('code') + error_message = exc.response.json().get('message') + self.logger.warning('No pendings page to access to, got error %s and message "%s" instead.', error_code, error_message) + else: for tr in self.page.iter_history(periods=periods): if tr._owner == account._idforJSON: tr.date = date yield tr - except ServerError as exc: - # At certain time of the month a connection might not have pendings; - # in that case, `js_pending.go` would throw a 502 error Bad Gateway - error_code = exc.response.json().get('code') - error_message = exc.response.json().get('message') - self.logger.warning('No pendings page to access to, got error %s and message "%s" instead.' % (error_code, error_message)) # "posted" have a vdate but debit date can be future or past for p in periods: diff --git a/modules/americanexpress/compat/weboob_capabilities_bank.py b/modules/americanexpress/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/americanexpress/compat/weboob_capabilities_bank.py +++ b/modules/americanexpress/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/americanexpress/compat/weboob_exceptions.py b/modules/americanexpress/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/americanexpress/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/americanexpress/pages.py b/modules/americanexpress/pages.py index 99bac79e79..274efd56a3 100644 --- a/modules/americanexpress/pages.py +++ b/modules/americanexpress/pages.py @@ -22,7 +22,6 @@ from ast import literal_eval from decimal import Decimal import re -from dateutil.parser import parse as parse_date from weboob.browser.pages import LoggedPage, JsonPage, HTMLPage from weboob.browser.elements import ItemElement, DictElement, method @@ -32,12 +31,14 @@ from weboob.capabilities.base import NotAvailable from weboob.tools.json import json from weboob.tools.compat import basestring -from weboob.exceptions import ActionNeeded, BrowserUnavailable +from .compat.weboob_exceptions import ActionNeeded, BrowserUnavailable +from dateutil.parser import parse as parse_date def float_to_decimal(f): return Decimal(str(f)) + def parse_decimal(s): # we might get 1,399,680 in rupie indonésienne if s.count(',') > 1 and not s.count('.'): @@ -87,13 +88,13 @@ def iter_accounts(self): else: assert False, "data was not found" - assert data[13] == 'core' - assert len(data[14]) == 3 + assert data[15] == 'core' + assert len(data[16]) == 3 # search for products to get products list - for index, el in enumerate(data[14][2]): + for index, el in enumerate(data[16][2]): if 'products' in el: - accounts_data = data[14][2][index+1] + accounts_data = data[16][2][index + 1] assert len(accounts_data) == 2 assert accounts_data[1][4] == 'productsList' @@ -105,14 +106,14 @@ def iter_accounts(self): if isinstance(account_data, basestring): balances_token = account_data - elif isinstance(account_data, list) and not account_data[4][2][0]=="Canceled": + elif isinstance(account_data, list) and not account_data[4][2][0] == "Canceled": acc = Account() if len(account_data) > 15: token.append(account_data[-11]) - acc._idforJSON = account_data[10][-1] + acc._idforJSON = account_data[10][-1] else: acc._idforJSON = account_data[-5][-1] - acc._idforJSON = re.sub('\s+', ' ', acc._idforJSON) + acc._idforJSON = re.sub(r'\s+', ' ', acc._idforJSON) acc.number = '-%s' % account_data[2][2] acc.label = '%s %s' % (account_data[6][4], account_data[10][-1]) acc._balances_token = acc.id = balances_token @@ -142,7 +143,7 @@ def set_balances(self, accounts): class CurrencyPage(LoggedPage, JsonPage): def get_currency(self): - return self.doc['currency'] + return self.doc['localeSettings']['currency_code'] class JsonPeriods(LoggedPage, JsonPage): @@ -172,7 +173,8 @@ def obj_type(self): obj_raw = CleanText(Dict('description', default='')) def obj_date(self): - """ 'statement_end_date' might be absent from this json, we must match the rdate with the right date period """ + # 'statement_end_date' might be absent from this json, + # we must match the rdate with the right date period _date = Date(Dict('statement_end_date', default=None), default=NotAvailable)(self) if not _date: periods = Env('periods')(self) @@ -207,6 +209,4 @@ def obj_original_amount(self): else: return original_amount - # obj__ref = Dict('reference_id') obj__ref = Dict('identifier') - diff --git a/modules/amundi/browser.py b/modules/amundi/browser.py index 8064befe8e..b026292045 100644 --- a/modules/amundi/browser.py +++ b/modules/amundi/browser.py @@ -18,8 +18,8 @@ # along with this weboob module. If not, see . from .pages import LoginPage, AccountsPage, AccountHistoryPage -from weboob.browser import URL, LoginBrowser, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import URL, LoginBrowser, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.browser.exceptions import ClientError from weboob.capabilities.base import empty diff --git a/modules/amundi/compat/weboob_capabilities_bank.py b/modules/amundi/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/amundi/compat/weboob_capabilities_bank.py +++ b/modules/amundi/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/amundi/compat/weboob_exceptions.py b/modules/amundi/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/amundi/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/amundi/pages.py b/modules/amundi/pages.py index a32bc0eaad..32672f6be3 100644 --- a/modules/amundi/pages.py +++ b/modules/amundi/pages.py @@ -29,7 +29,7 @@ from weboob.browser.pages import LoggedPage, JsonPage from .compat.weboob_capabilities_bank import Account, Investment, Transaction from weboob.capabilities.base import NotAvailable, empty -from weboob.exceptions import NoAccountsException +from .compat.weboob_exceptions import NoAccountsException from weboob.tools.capabilities.bank.investments import is_isin_valid diff --git a/modules/anticaptcha/browser.py b/modules/anticaptcha/browser.py index 14a462d98e..1386e6fba5 100644 --- a/modules/anticaptcha/browser.py +++ b/modules/anticaptcha/browser.py @@ -22,8 +22,8 @@ from base64 import b64encode from weboob.browser.browsers import APIBrowser -from weboob.exceptions import BrowserIncorrectPassword, BrowserBanned -from weboob.capabilities.captcha import ( +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserBanned +from .compat.weboob_capabilities_captcha import ( ImageCaptchaJob, RecaptchaJob, RecaptchaV3Job, NocaptchaJob, FuncaptchaJob, CaptchaError, InsufficientFunds, UnsolvableCaptcha, InvalidCaptcha, ) diff --git a/modules/anticaptcha/compat/__init__.py b/modules/anticaptcha/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/anticaptcha/compat/weboob_capabilities_captcha.py b/modules/anticaptcha/compat/weboob_capabilities_captcha.py new file mode 100644 index 0000000000..3b8cc0d9e8 --- /dev/null +++ b/modules/anticaptcha/compat/weboob_capabilities_captcha.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2018 Vincent A +# +# This file is part of weboob. +# +# weboob 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. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with weboob. If not, see . + +from time import sleep + +from weboob.capabilities.base import Capability, BaseObject, StringField, UserError, BytesField +from weboob.exceptions import ( + RecaptchaQuestion, RecaptchaV3Question, NocaptchaQuestion, FuncaptchaQuestion, ImageCaptchaQuestion +) + + +__all__ = [ + 'CapCaptchaSolver', + 'SolverJob', 'RecaptchaJob', 'NocaptchaJob', 'ImageCaptchaJob', + 'CaptchaError', 'UnsolvableCaptcha', 'InvalidCaptcha', 'InsufficientFunds', + 'exception_to_job', +] + + +from weboob.capabilities.captcha import SolverJob as _SolverJob +class SolverJob(_SolverJob): + solution = StringField('CAPTCHA solution') + + +from weboob.capabilities.captcha import RecaptchaJob as _RecaptchaJob +class RecaptchaJob(_RecaptchaJob): + site_url = StringField('Site URL for ReCaptcha service') + site_key = StringField('Site key for ReCaptcha service') + + solution_challenge = StringField('Challenge ID of the solution (output value)') + + +class RecaptchaV3Job(SolverJob): + site_url = StringField('Site URL for ReCaptcha service') + site_key = StringField('Site key for ReCaptcha service') + action = StringField('Website owner defines what user is doing on the page through this parameter.') + + +from weboob.capabilities.captcha import NocaptchaJob as _NocaptchaJob +class NocaptchaJob(_NocaptchaJob): + site_url = StringField('Site URL for NoCaptcha service') + site_key = StringField('Site key for NoCaptcha service') + + +from weboob.capabilities.captcha import FuncaptchaJob as _FuncaptchaJob +class FuncaptchaJob(_FuncaptchaJob): + site_url = StringField('Site URL for FunCaptcha service') + site_key = StringField('Site key for FunCaptcha service') + sub_domain = StringField('Required for some complex cases, but Funcaptcha integrations run without it') + + +from weboob.capabilities.captcha import ImageCaptchaJob as _ImageCaptchaJob +class ImageCaptchaJob(_ImageCaptchaJob): + image = BytesField('data of the image to solve') + + +from weboob.capabilities.captcha import CaptchaError as _CaptchaError +class CaptchaError(_CaptchaError): + """Generic solving error""" + + +from weboob.capabilities.captcha import InvalidCaptcha as _InvalidCaptcha +class InvalidCaptcha(_InvalidCaptcha): + """CAPTCHA cannot be used (e.g. invalid image format)""" + + +from weboob.capabilities.captcha import UnsolvableCaptcha as _UnsolvableCaptcha +class UnsolvableCaptcha(_UnsolvableCaptcha): + """CAPTCHA is too hard or impossible""" + + +from weboob.capabilities.captcha import InsufficientFunds as _InsufficientFunds +class InsufficientFunds(_InsufficientFunds): + """Not enough funds to pay solution""" + + +def exception_to_job(exc): + if isinstance(exc, RecaptchaQuestion): + job = RecaptchaJob() + job.site_url = exc.website_url + job.site_key = exc.website_key + elif isinstance(exc, RecaptchaV3Question): + job = RecaptchaV3Job() + job.site_url = exc.website_url + job.site_key = exc.website_key + job.action = exc.action + elif isinstance(exc, NocaptchaQuestion): + job = NocaptchaJob() + job.site_url = exc.website_url + job.site_key = exc.website_key + elif isinstance(exc, FuncaptchaQuestion): + job = FuncaptchaJob() + job.site_url = exc.website_url + job.site_key = exc.website_key + job.sub_domain = exc.sub_domain + elif isinstance(exc, ImageCaptchaQuestion): + job = ImageCaptchaJob() + job.image = exc.image_data + else: + raise NotImplementedError() + + return job + + +from weboob.capabilities.captcha import CapCaptchaSolver as _CapCaptchaSolver +class CapCaptchaSolver(_CapCaptchaSolver): + """ + Provide CAPTCHA solving + """ + + RETRIES = 30 + WAIT_TIME = 2 + + def create_job(self, job): + """Start a CAPTCHA solving job + + The `job.id` shall be filled. The CAPTCHA is not solved yet when the method returns. + + :param job: job to start + :type job: :class:`SolverJob` + :raises: :class:`NotImplementedError` if CAPTCHA type is not supported + :raises: :class:`CaptchaError` in case of other error + """ + raise NotImplementedError() + + def poll_job(self, job): + """Check if a job was solved + + If `job` is solved, return True and fill `job.solution`. + Return False if solution is still pending. + In case of solving problem, an exception may be raised. + + It should not wait for the solution but return the current state. + + :param job: job to check and to fill when solved + :type job: :class:`SolverJob` + :returns: True if the job was solved + :rtype: bool + :raises: :class:`CaptchaError` + """ + raise NotImplementedError() + + def solve_catpcha_blocking(self, job): + """Start a CAPTCHA solving job and wait for its solution + + :param job: job to start and solve + :type job: :class:`SolverJob` + :raises: :class:`CaptchaError` + """ + + self.create_job(job) + for i in range(self.RETRIES): + sleep(self.WAIT_TIME) + if self.poll_job(job): + return job + + def report_wrong_solution(self, job): + """Report a solved job as a wrong solution + + Sometimes, jobs are solved, but the solution is rejected by the CAPTCHA + site because the solution is wrong. + This method reports the solution as wrong to the CAPTCHA solver. + + :param job: job to flag + :type job: :class:`SolverJob` + """ + raise NotImplementedError() + + def get_balance(self): + """Get the prepaid balance left + + :rtype: float + """ + raise NotImplementedError() + diff --git a/modules/anticaptcha/compat/weboob_exceptions.py b/modules/anticaptcha/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/anticaptcha/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/anticaptcha/module.py b/modules/anticaptcha/module.py index 3949ce2b4d..acd41d413d 100644 --- a/modules/anticaptcha/module.py +++ b/modules/anticaptcha/module.py @@ -21,7 +21,7 @@ from weboob.tools.backend import Module, BackendConfig -from weboob.capabilities.captcha import ( +from .compat.weboob_capabilities_captcha import ( CapCaptchaSolver, ImageCaptchaJob, RecaptchaJob, RecaptchaV3Job, NocaptchaJob, FuncaptchaJob ) from weboob.tools.value import ValueBackendPassword diff --git a/modules/apivie/browser.py b/modules/apivie/browser.py index 650d284f09..a4da7dd5a5 100644 --- a/modules/apivie/browser.py +++ b/modules/apivie/browser.py @@ -18,9 +18,9 @@ # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.capabilities.base import find_object -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, AccountsPage, InvestmentsPage, OperationsPage diff --git a/modules/apivie/compat/weboob_capabilities_bank.py b/modules/apivie/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/apivie/compat/weboob_capabilities_bank.py +++ b/modules/apivie/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/apivie/compat/weboob_exceptions.py b/modules/apivie/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/apivie/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/asana/browser.py b/modules/asana/browser.py index 9609f7a652..482dd9754b 100644 --- a/modules/asana/browser.py +++ b/modules/asana/browser.py @@ -25,7 +25,7 @@ from weboob.browser.exceptions import ClientError from weboob.capabilities.base import NotAvailable from weboob.capabilities.bugtracker import User, Project, Issue, Status, Update -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from dateutil.parser import parse as parse_date diff --git a/modules/asana/compat/__init__.py b/modules/asana/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/asana/compat/weboob_exceptions.py b/modules/asana/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/asana/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/aum/browser.py b/modules/aum/browser.py index 37f3c97bec..ae2c15ab3b 100644 --- a/modules/aum/browser.py +++ b/modules/aum/browser.py @@ -24,7 +24,7 @@ import math import re -from weboob.exceptions import BrowserIncorrectPassword, BrowserHTTPNotFound, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserHTTPNotFound, BrowserUnavailable from weboob.browser.exceptions import ClientError from weboob.browser.browsers import LoginBrowser, DomainBrowser from weboob.browser.pages import HTMLPage diff --git a/modules/aum/compat/weboob_exceptions.py b/modules/aum/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/aum/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/aum/module.py b/modules/aum/module.py index 7383b72f9b..d8bb7dde23 100644 --- a/modules/aum/module.py +++ b/modules/aum/module.py @@ -36,7 +36,7 @@ from weboob.capabilities.contact import CapContact, ContactPhoto, Query, QueryError from weboob.capabilities.account import CapAccount, StatusField from weboob.tools.backend import Module, BackendConfig -from weboob.exceptions import BrowserUnavailable, BrowserHTTPNotFound +from .compat.weboob_exceptions import BrowserUnavailable, BrowserHTTPNotFound from weboob.tools.value import Value, ValueBool, ValueBackendPassword from weboob.tools.date import local2utc from weboob.tools.misc import to_unicode diff --git a/modules/aum/optim/compat/__init__.py b/modules/aum/optim/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/aum/optim/compat/weboob_exceptions.py b/modules/aum/optim/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/aum/optim/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/aum/optim/profiles_walker.py b/modules/aum/optim/profiles_walker.py index 8563ce22e3..ffc54c3ee6 100644 --- a/modules/aum/optim/profiles_walker.py +++ b/modules/aum/optim/profiles_walker.py @@ -19,7 +19,7 @@ from random import randint -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from weboob.capabilities.dating import Optimization from weboob.tools.log import getLogger diff --git a/modules/aum/optim/queries_queue.py b/modules/aum/optim/queries_queue.py index c538e536af..2355a4ffdd 100644 --- a/modules/aum/optim/queries_queue.py +++ b/modules/aum/optim/queries_queue.py @@ -18,7 +18,7 @@ # along with this weboob module. If not, see . -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from weboob.capabilities.dating import Optimization from weboob.capabilities.contact import QueryError from weboob.tools.log import getLogger diff --git a/modules/aum/optim/visibility.py b/modules/aum/optim/visibility.py index 7197fbe146..e936feefc7 100644 --- a/modules/aum/optim/visibility.py +++ b/modules/aum/optim/visibility.py @@ -18,7 +18,7 @@ # along with this weboob module. If not, see . -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from weboob.capabilities.dating import Optimization diff --git a/modules/aum/test.py b/modules/aum/test.py index cbd9170cf7..ca1525cda8 100644 --- a/modules/aum/test.py +++ b/modules/aum/test.py @@ -19,7 +19,7 @@ from weboob.tools.test import BackendTest -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable class AuMTest(BackendTest): diff --git a/modules/axabanque/browser.py b/modules/axabanque/browser.py index f05269ac88..f9d698e739 100644 --- a/modules/axabanque/browser.py +++ b/modules/axabanque/browser.py @@ -28,7 +28,7 @@ from weboob.capabilities.base import NotAvailable from weboob.capabilities.bill import Subscription from .compat.weboob_capabilities_bank import Account, Transaction, AddRecipientStep, Recipient -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded from weboob.tools.value import Value from weboob.tools.capabilities.bank.transactions import sorted_transactions from weboob.tools.capabilities.bank.investments import create_french_liquidity diff --git a/modules/axabanque/compat/weboob_capabilities_bank.py b/modules/axabanque/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/axabanque/compat/weboob_capabilities_bank.py +++ b/modules/axabanque/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/axabanque/compat/weboob_exceptions.py b/modules/axabanque/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/axabanque/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/axabanque/pages/bank.py b/modules/axabanque/pages/bank.py index 2f53136143..3d5a7adcd7 100644 --- a/modules/axabanque/pages/bank.py +++ b/modules/axabanque/pages/bank.py @@ -24,7 +24,7 @@ from decimal import Decimal, InvalidOperation from datetime import datetime, timedelta -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from weboob.browser.pages import HTMLPage, PDFPage, LoggedPage, AbstractPage from weboob.browser.elements import ItemElement, TableElement, method from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Date, Regexp, Field, Env, Currency diff --git a/modules/axabanque/pages/compat/weboob_capabilities_bank.py b/modules/axabanque/pages/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/axabanque/pages/compat/weboob_capabilities_bank.py +++ b/modules/axabanque/pages/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/axabanque/pages/compat/weboob_exceptions.py b/modules/axabanque/pages/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/axabanque/pages/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/axabanque/pages/login.py b/modules/axabanque/pages/login.py index faacf2d9a1..25a80ba7e0 100644 --- a/modules/axabanque/pages/login.py +++ b/modules/axabanque/pages/login.py @@ -20,7 +20,7 @@ from io import BytesIO -from weboob.exceptions import BrowserBanned, ActionNeeded, BrowserUnavailable +from .compat.weboob_exceptions import BrowserBanned, ActionNeeded, BrowserUnavailable from weboob.browser.pages import HTMLPage, RawPage, JsonPage, PartialHTMLPage from weboob.browser.filters.json import Dict from .compat.weboob_browser_filters_standard import CleanText diff --git a/modules/banqueaccord/compat/weboob_capabilities_bank.py b/modules/banqueaccord/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/banqueaccord/compat/weboob_capabilities_bank.py +++ b/modules/banqueaccord/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/banquepopulaire/browser.py b/modules/banquepopulaire/browser.py index f1db06b5f5..ac893141dd 100644 --- a/modules/banquepopulaire/browser.py +++ b/modules/banquepopulaire/browser.py @@ -26,7 +26,7 @@ from functools import wraps from dateutil.relativedelta import relativedelta -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable from weboob.browser.exceptions import HTTPNotFound, ServerError from weboob.browser.browsers import LoginBrowser, URL, need_login from .compat.weboob_capabilities_bank import Account, AccountOwnership @@ -276,7 +276,11 @@ def get_accounts_list(self, get_iban=True): # thanks to stateful website next_pages = [] accounts = [] - owner_name = re.search(r' (.+)', self.get_profile().name).group(1).upper() + profile = self.get_profile() + if profile.name: + owner_name = re.search(r' (.+)', profile.name).group(1).upper() + else: + owner_name = re.search(r' (.+)', profile.company_name).group(1).upper() self.go_on_accounts_list() diff --git a/modules/banquepopulaire/compat/weboob_capabilities_bank.py b/modules/banquepopulaire/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/banquepopulaire/compat/weboob_capabilities_bank.py +++ b/modules/banquepopulaire/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/banquepopulaire/compat/weboob_exceptions.py b/modules/banquepopulaire/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/banquepopulaire/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/banquepopulaire/pages.py b/modules/banquepopulaire/pages.py index 21bed70012..cfcff859cb 100644 --- a/modules/banquepopulaire/pages.py +++ b/modules/banquepopulaire/pages.py @@ -31,7 +31,7 @@ from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Regexp, Eval, Date, Field from weboob.browser.filters.html import Attr, Link, AttributeNotFound from weboob.browser.filters.json import Dict -from weboob.exceptions import BrowserUnavailable, BrowserIncorrectPassword, ActionNeeded +from .compat.weboob_exceptions import BrowserUnavailable, BrowserIncorrectPassword, ActionNeeded from weboob.browser.pages import HTMLPage, LoggedPage, FormNotFound, JsonPage, RawPage, XMLPage diff --git a/modules/barclays/browser.py b/modules/barclays/browser.py index b69acdfbbe..fbfb380e21 100644 --- a/modules/barclays/browser.py +++ b/modules/barclays/browser.py @@ -23,7 +23,7 @@ from requests.exceptions import ConnectionError from weboob.browser.browsers import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded from .compat.weboob_capabilities_bank import Account from weboob.capabilities.base import NotAvailable from weboob.tools.decorators import retry diff --git a/modules/barclays/compat/weboob_capabilities_bank.py b/modules/barclays/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/barclays/compat/weboob_capabilities_bank.py +++ b/modules/barclays/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/barclays/compat/weboob_exceptions.py b/modules/barclays/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/barclays/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/barclays/pages.py b/modules/barclays/pages.py index ad2517f7c0..1eac9662a3 100644 --- a/modules/barclays/pages.py +++ b/modules/barclays/pages.py @@ -28,7 +28,7 @@ from .compat.weboob_capabilities_bank import Account, Investment, Loan, NotAvailable from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.capabilities.bank.iban import is_iban_valid -from weboob.exceptions import ActionNeeded, BrowserUnavailable +from .compat.weboob_exceptions import ActionNeeded, BrowserUnavailable def MyDecimal(*args, **kwargs): diff --git a/modules/becm/browser.py b/modules/becm/browser.py index e8eb0d9e42..69c7f9d496 100644 --- a/modules/becm/browser.py +++ b/modules/becm/browser.py @@ -23,7 +23,7 @@ from weboob.browser.profiles import Wget from .compat.weboob_browser_url import URL from weboob.browser.browsers import need_login -from weboob.exceptions import ActionNeeded, AuthMethodNotImplemented +from .compat.weboob_exceptions import ActionNeeded, AuthMethodNotImplemented from .pages import AdvisorPage, LoginPage diff --git a/modules/becm/compat/weboob_capabilities_bank.py b/modules/becm/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/becm/compat/weboob_capabilities_bank.py +++ b/modules/becm/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/becm/compat/weboob_exceptions.py b/modules/becm/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/becm/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/becm/pages.py b/modules/becm/pages.py index 4808f235d9..bab64a5b2c 100644 --- a/modules/becm/pages.py +++ b/modules/becm/pages.py @@ -22,7 +22,7 @@ from weboob.browser.elements import method, ItemElement from .compat.weboob_browser_filters_standard import CleanText, Format from weboob.capabilities import NotAvailable -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword class LoginPage(HTMLPage): diff --git a/modules/bforbank/browser.py b/modules/bforbank/browser.py index ecb89c78d3..4f6ed1e24c 100644 --- a/modules/bforbank/browser.py +++ b/modules/bforbank/browser.py @@ -18,7 +18,7 @@ # along with this weboob module. If not, see . import datetime from dateutil.relativedelta import relativedelta -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.browser.browsers import LoginBrowser, URL, need_login from .compat.weboob_capabilities_bank import Account, AccountNotFound from weboob.capabilities.base import empty diff --git a/modules/bforbank/compat/weboob_capabilities_bank.py b/modules/bforbank/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bforbank/compat/weboob_capabilities_bank.py +++ b/modules/bforbank/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bforbank/compat/weboob_exceptions.py b/modules/bforbank/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bforbank/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bforbank/pages.py b/modules/bforbank/pages.py index f076eb65e2..ac7e9aed8a 100644 --- a/modules/bforbank/pages.py +++ b/modules/bforbank/pages.py @@ -26,7 +26,7 @@ from PIL import Image -from weboob.exceptions import ActionNeeded +from .compat.weboob_exceptions import ActionNeeded from weboob.browser.pages import LoggedPage, HTMLPage, pagination, AbstractPage from weboob.browser.elements import method, ListElement, ItemElement, TableElement from .compat.weboob_capabilities_bank import Account @@ -301,11 +301,13 @@ def obj_date(self): class CardPage(LoggedPage, HTMLPage): def has_no_card(self): # Persistent message for cardless accounts - return CleanText('//div[@id="alert"]/p[contains(text(), "Aucune donnée n\'a été retournée par le service")]')(self.doc) + return ( + CleanText('//div[@id="alert"]/p[contains(text(), "Aucune donnée n\'a été retournée par le service")]')(self.doc) + or not self.doc.xpath('//div[@class="content-boxed"]') + ) def get_cards(self, account_id): divs = self.doc.xpath('//div[@class="content-boxed"]') - assert len(divs) msgs = re.compile( 'Vous avez fait opposition sur cette carte bancaire.' + '|Votre carte bancaire a été envoyée.' + diff --git a/modules/binck/browser.py b/modules/binck/browser.py index fbf7f75c89..5f91e11917 100644 --- a/modules/binck/browser.py +++ b/modules/binck/browser.py @@ -22,8 +22,8 @@ from lxml import etree from io import StringIO -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded from weboob.browser.exceptions import HTTPNotFound, ServerError from weboob.tools.capabilities.bank.investments import create_french_liquidity diff --git a/modules/binck/compat/weboob_capabilities_bank.py b/modules/binck/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/binck/compat/weboob_capabilities_bank.py +++ b/modules/binck/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/binck/compat/weboob_exceptions.py b/modules/binck/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/binck/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/binck/pages.py b/modules/binck/pages.py index 7cc58dbf16..2061a42947 100644 --- a/modules/binck/pages.py +++ b/modules/binck/pages.py @@ -23,14 +23,15 @@ from weboob.browser.pages import HTMLPage, JsonPage, LoggedPage from weboob.browser.elements import ItemElement, ListElement, DictElement, TableElement, method -from .compat.weboob_browser_filters_standard import CleanText, Date, Format, CleanDecimal, Eval, Env, Field +from .compat.weboob_browser_filters_standard import CleanText, Date, DateTime, Format, CleanDecimal, Eval, Env, Field from weboob.browser.filters.html import Attr, Link, TableCell from weboob.browser.filters.json import Dict -from weboob.exceptions import BrowserPasswordExpired, ActionNeeded +from .compat.weboob_exceptions import BrowserPasswordExpired, ActionNeeded from .compat.weboob_capabilities_bank import Account, Investment from weboob.capabilities.base import NotAvailable, empty from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.capabilities.bank.investments import is_isin_valid +from .compat.weboob_browser_filters_standard import FilterError def MyDecimal(*args, **kwargs): @@ -212,7 +213,7 @@ class item(ItemElement): obj_label = Dict('SecurityName') obj_quantity = MyDecimal(Dict('Quantity')) - obj_vdate = Date(CleanText(Dict('Time')), dayfirst=True) + obj_vdate = DateTime(CleanText(Dict('Time')), dayfirst=True, strict=False) obj_unitvalue = Env('unitvalue', default=NotAvailable) obj_unitprice = Env('unitprice', default=NotAvailable) obj_valuation = MyDecimal(Dict('ValueInEuro')) @@ -225,6 +226,17 @@ class item(ItemElement): obj_original_diff = Env('o_diff', default=NotAvailable) obj__security_id = Dict('SecurityId') + def obj_vdate(self): + try: + # during stocks closing hours only date (d/m/y) is given + return Date(CleanText(Dict('Time')), dayfirst=True)(self) + except FilterError: + # during stocks opening hours only time (h:m:s) is given, + # can even be realtime, depending on user settings, + # can be given in foreign markets time, + # e.g. 10:00 is displayed at 9:00 for an action in NASADQ Helsinki market + return DateTime(CleanText(Dict('Time')), dayfirst=True, strict=False)(self) + def obj_code(self): if is_isin_valid(Dict('IsinCode')(self)): return Dict('IsinCode')(self) diff --git a/modules/biplan/compat/__init__.py b/modules/biplan/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/bnpcards/browser.py b/modules/bnpcards/browser.py index 8900ba22ed..b68b958f98 100644 --- a/modules/bnpcards/browser.py +++ b/modules/bnpcards/browser.py @@ -18,8 +18,8 @@ # along with this weboob module. If not, see . -from weboob.exceptions import BrowserIncorrectPassword, BrowserPasswordExpired -from weboob.browser import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserPasswordExpired +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.browser.switch import SiteSwitch from weboob.tools.capabilities.bank.transactions import sorted_transactions from weboob.tools.compat import basestring diff --git a/modules/bnpcards/compat/weboob_capabilities_bank.py b/modules/bnpcards/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bnpcards/compat/weboob_capabilities_bank.py +++ b/modules/bnpcards/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bnpcards/compat/weboob_exceptions.py b/modules/bnpcards/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bnpcards/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bnpcards/corporate/browser.py b/modules/bnpcards/corporate/browser.py index 3ce446433f..ef6de61cf0 100644 --- a/modules/bnpcards/corporate/browser.py +++ b/modules/bnpcards/corporate/browser.py @@ -18,8 +18,8 @@ # along with this weboob module. If not, see . -from weboob.exceptions import BrowserIncorrectPassword, BrowserPasswordExpired -from weboob.browser import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserPasswordExpired +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.tools.capabilities.bank.transactions import sorted_transactions from weboob.tools.compat import basestring diff --git a/modules/bnpcards/corporate/compat/weboob_capabilities_bank.py b/modules/bnpcards/corporate/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bnpcards/corporate/compat/weboob_capabilities_bank.py +++ b/modules/bnpcards/corporate/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bnpcards/corporate/compat/weboob_exceptions.py b/modules/bnpcards/corporate/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bnpcards/corporate/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bnpcards/pages.py b/modules/bnpcards/pages.py index bf6dfc62aa..d39b8696c9 100644 --- a/modules/bnpcards/pages.py +++ b/modules/bnpcards/pages.py @@ -23,7 +23,7 @@ from datetime import date from decimal import Decimal -from weboob.exceptions import BrowserPasswordExpired +from .compat.weboob_exceptions import BrowserPasswordExpired from weboob.browser.pages import HTMLPage, LoggedPage, pagination from weboob.browser.elements import ListElement, ItemElement, method from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Field, Env, Format diff --git a/modules/bnporc/company/compat/weboob_capabilities_bank.py b/modules/bnporc/company/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bnporc/company/compat/weboob_capabilities_bank.py +++ b/modules/bnporc/company/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bnporc/company/compat/weboob_exceptions.py b/modules/bnporc/company/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bnporc/company/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bnporc/company/pages.py b/modules/bnporc/company/pages.py index 418ea90006..490ee49ae6 100644 --- a/modules/bnporc/company/pages.py +++ b/modules/bnporc/company/pages.py @@ -25,7 +25,7 @@ from datetime import datetime from .compat.weboob_capabilities_bank import Account -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.browser.pages import HTMLPage, JsonPage, LoggedPage from .compat.weboob_tools_captcha_virtkeyboard import MappedVirtKeyboard, VirtKeyboardError diff --git a/modules/bnporc/compat/weboob_capabilities_bank.py b/modules/bnporc/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bnporc/compat/weboob_capabilities_bank.py +++ b/modules/bnporc/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bnporc/enterprise/browser.py b/modules/bnporc/enterprise/browser.py index 47033002d9..6fb65f8967 100644 --- a/modules/bnporc/enterprise/browser.py +++ b/modules/bnporc/enterprise/browser.py @@ -27,7 +27,7 @@ from weboob.browser.browsers import LoginBrowser, need_login from weboob.capabilities.base import find_object from .compat.weboob_capabilities_bank import Account -from weboob.exceptions import BrowserIncorrectPassword, BrowserForbidden +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserForbidden from .compat.weboob_browser_url import URL from weboob.tools.capabilities.bank.transactions import sorted_transactions diff --git a/modules/bnporc/enterprise/compat/weboob_capabilities_bank.py b/modules/bnporc/enterprise/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bnporc/enterprise/compat/weboob_capabilities_bank.py +++ b/modules/bnporc/enterprise/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bnporc/enterprise/compat/weboob_exceptions.py b/modules/bnporc/enterprise/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bnporc/enterprise/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bnporc/enterprise/pages.py b/modules/bnporc/enterprise/pages.py index 29edf02cc4..ead43bd75e 100644 --- a/modules/bnporc/enterprise/pages.py +++ b/modules/bnporc/enterprise/pages.py @@ -30,14 +30,14 @@ from weboob.browser.elements import DictElement, ItemElement, method, TableElement from .compat.weboob_browser_filters_standard import ( CleanText, CleanDecimal, Date, Regexp, Format, Eval, BrowserURL, Field, - Async, Currency, + Currency, ) from .compat.weboob_capabilities_bank import Transaction, Account, Investment from weboob.capabilities.profile import Person from .compat.weboob_tools_captcha_virtkeyboard import MappedVirtKeyboard, VirtKeyboardError from weboob.tools.date import parse_french_date from weboob.capabilities import NotAvailable -from weboob.exceptions import ActionNeeded, BrowserForbidden +from .compat.weboob_exceptions import ActionNeeded, BrowserForbidden def fromtimestamp(milliseconds): return datetime.fromtimestamp(milliseconds/1000) @@ -200,37 +200,33 @@ def load_details(self): 'numero_mvt': Field('_trid')(self), }) - def obj__redacted_card(self): - raw = Field('raw')(self) - - if not raw.startswith('FACTURE CARTE') or ' SUIVANT RELEVE DU ' in raw: - return - - page = Async('details').loaded_page(self) - return page.get_redacted_card() - class AccountHistoryPage(LoggedPage, JsonPage): TYPES = { - u'CARTE': Transaction.TYPE_DEFERRED_CARD, # Cartes - u'CHEQU': Transaction.TYPE_CHECK, # Chèques - u'REMCB': Transaction.TYPE_DEFERRED_CARD, # Remises cartes - u'VIREM': Transaction.TYPE_TRANSFER, # Virements - u'VIRIT': Transaction.TYPE_TRANSFER, # Virements internationaux - u'VIRSP': Transaction.TYPE_TRANSFER, # Virements européens - u'VIRTR': Transaction.TYPE_TRANSFER, # Virements de trésorerie - u'VIRXX': Transaction.TYPE_TRANSFER, # Autres virements - u'PRLVT': Transaction.TYPE_LOAN_PAYMENT, # Prélèvements, TIP et télérèglements - u'AUTOP': Transaction.TYPE_UNKNOWN, # Autres opérations + 'CARTE': Transaction.TYPE_DEFERRED_CARD, # Cartes + 'CHEQU': Transaction.TYPE_CHECK, # Chèques + 'REMCB': Transaction.TYPE_DEFERRED_CARD, # Remises cartes + 'VIREM': Transaction.TYPE_TRANSFER, # Virements + 'VIRIT': Transaction.TYPE_TRANSFER, # Virements internationaux + 'VIRSP': Transaction.TYPE_TRANSFER, # Virements européens + 'VIRTR': Transaction.TYPE_TRANSFER, # Virements de trésorerie + 'VIRXX': Transaction.TYPE_TRANSFER, # Autres virements + 'PRLVT': Transaction.TYPE_ORDER, # Prélèvements, TIP et télérèglements + 'AUTOP': Transaction.TYPE_UNKNOWN, # Autres opérations 'FACCB': Transaction.TYPE_CARD, # Cartes } COMING_TYPES = { - u'0083': Transaction.TYPE_DEFERRED_CARD, - u'0813': Transaction.TYPE_LOAN_PAYMENT, - u'0568': Transaction.TYPE_TRANSFER, - u'1194': Transaction.TYPE_DEFERRED_CARD, # PAYBACK typed as DEFERRED_CARD + '0001': Transaction.TYPE_CHECK, # CHEQUE + '0029': Transaction.TYPE_BANK, # Interets et Commissions + '0083': Transaction.TYPE_DEFERRED_CARD, + '0099': Transaction.TYPE_PAYBACK, # REM. CARTE OU EROCHQ.* + '0512': Transaction.TYPE_TRANSFER, # VIREMENT FAVEUR TIERS + '0558': Transaction.TYPE_TRANSFER, # VIREMENT RECU TIERS.* + '0568': Transaction.TYPE_TRANSFER, # VIREMENT SEPA + '0813': Transaction.TYPE_ORDER, # PRLV SEPA .* + '1194': Transaction.TYPE_DEFERRED_CARD, # PAYBACK typed as DEFERRED_CARD } @method @@ -334,9 +330,7 @@ def obj_amount(self): class TransactionPage(LoggedPage, JsonPage): - def get_redacted_card(self): - # warning: the account on which the transaction is returned depends on this data! - return self.doc['carteNum'] + pass class MarketPage(LoggedPage, HTMLPage): diff --git a/modules/bnporc/pp/browser.py b/modules/bnporc/pp/browser.py index 175981056a..ed524531b8 100644 --- a/modules/bnporc/pp/browser.py +++ b/modules/bnporc/pp/browser.py @@ -24,7 +24,7 @@ import time from requests.exceptions import ConnectionError -from weboob.browser.browsers import LoginBrowser, URL, need_login +from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin from weboob.capabilities.base import find_object from .compat.weboob_capabilities_bank import ( AccountNotFound, Account, AddRecipientStep, AddRecipientTimeout, @@ -37,7 +37,7 @@ from weboob.tools.json import json from weboob.browser.exceptions import ServerError from weboob.browser.elements import DataError -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable from weboob.tools.value import Value, ValueBool from weboob.tools.capabilities.bank.investments import create_french_liquidity @@ -75,7 +75,7 @@ def open(self, *args, **kwargs): return super(JsonBrowserMixin, self).open(*args, **kwargs) -class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser): +class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser, StatesMixin): TIMEOUT = 30.0 login = URL(r'identification-wspl-pres/identification\?acceptRedirection=true×tamp=(?P\d+)', @@ -131,12 +131,19 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser): profile = URL(r'/kyc-wspl/rest/informationsClient', ProfilePage) list_detail_card = URL(r'/udcarte-wspl/rest/listeDetailCartes', ListDetailCardPage) + STATE_DURATION = 10 + + need_reload_state = False + + __states__ = ('need_reload_state', 'rcpt_transfer_id') + def __init__(self, config, *args, **kwargs): super(BNPParibasBrowser, self).__init__(config['login'].get(), config['password'].get(), *args, **kwargs) self.accounts_list = None self.card_to_transaction_type = {} self.rotating_password = config['rotating_password'].get() self.digital_key = config['digital_key'].get() + self.rcpt_transfer_id = None @retry(ConnectionError, tries=3) def open(self, *args, **kwargs): @@ -150,6 +157,13 @@ def do_login(self): if self.login.is_here(): self.page.login(self.username, self.password) + def load_state(self, state): + # reload state only for new recipient feature + if state.get('need_reload_state'): + state.pop('url', None) + self.need_reload_state = False + super(BNPParibasBrowser, self).load_state(state) + def change_pass(self, oldpass, newpass): res = self.open('/identification-wspl-pres/grille?accessible=false') url = '/identification-wspl-pres/grille/%s' % res.json()['data']['idGrille'] @@ -428,6 +442,8 @@ def new_recipient(self, recipient, **params): data=json.dumps(data), headers={'Content-Type': 'application/json'} ).get_recipient(recipient) + self.rcpt_transfer_id = recipient._transfer_id + self.need_reload_state = True raise AddRecipientStep(recipient, Value('code', label='Saisissez le code reçu par SMS.')) elif type_activation == 'digital_key': # recipient validated with digital key are immediatly available @@ -443,9 +459,10 @@ def send_code(self, recipient, **params): add recipient with sms otp authentication """ data = {} - data['idBeneficiaire'] = recipient._transfer_id + data['idBeneficiaire'] = self.rcpt_transfer_id data['typeActivation'] = 1 data['codeActivation'] = params['code'] + self.rcpt_transfer_id = None return self.activate_recip_sms.go(data=json.dumps(data), headers={'Content-Type': 'application/json'}).get_recipient(recipient) @need_login diff --git a/modules/bnporc/pp/compat/weboob_capabilities_bank.py b/modules/bnporc/pp/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bnporc/pp/compat/weboob_capabilities_bank.py +++ b/modules/bnporc/pp/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bnporc/pp/compat/weboob_exceptions.py b/modules/bnporc/pp/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bnporc/pp/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bnporc/pp/pages.py b/modules/bnporc/pp/pages.py index c5521ba8c4..f4b67a19e4 100644 --- a/modules/bnporc/pp/pages.py +++ b/modules/bnporc/pp/pages.py @@ -43,7 +43,7 @@ from weboob.capabilities.base import empty from weboob.capabilities.contact import Advisor from weboob.capabilities.profile import Person, ProfileMissing -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, BrowserPasswordExpired, ActionNeeded +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable, BrowserPasswordExpired, ActionNeeded from weboob.tools.capabilities.bank.iban import rib2iban, rebuild_rib, is_iban_valid from weboob.tools.capabilities.bank.transactions import FrenchTransaction from .compat.weboob_tools_captcha_virtkeyboard import GridVirtKeyboard diff --git a/modules/bnppere/browser.py b/modules/bnppere/browser.py index c2a1b2610d..7fb9beeae8 100644 --- a/modules/bnppere/browser.py +++ b/modules/bnppere/browser.py @@ -19,8 +19,8 @@ from __future__ import unicode_literals -from weboob.browser import AbstractBrowser, LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded +from weboob.browser.browsers import AbstractBrowser, LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded from .pages import LoginPage, ProfilePage, ErrorPage, AccountPage, TermPage, UnexpectedPage, HistoryPage diff --git a/modules/bnppere/compat/weboob_capabilities_bank.py b/modules/bnppere/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bnppere/compat/weboob_capabilities_bank.py +++ b/modules/bnppere/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bnppere/compat/weboob_exceptions.py b/modules/bnppere/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bnppere/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bolden/compat/weboob_capabilities_bank.py b/modules/bolden/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bolden/compat/weboob_capabilities_bank.py +++ b/modules/bolden/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bolden/compat/weboob_exceptions.py b/modules/bolden/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bolden/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bolden/pages.py b/modules/bolden/pages.py index 57259ed127..da0d70fd8e 100644 --- a/modules/bolden/pages.py +++ b/modules/bolden/pages.py @@ -29,7 +29,7 @@ from .compat.weboob_capabilities_bank import Account, Transaction, Investment from weboob.capabilities.profile import Profile from weboob.capabilities.bill import Document, DocumentTypes -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.compat import urljoin diff --git a/modules/boursorama/browser.py b/modules/boursorama/browser.py index cbd80d52bb..97c0b8bb1d 100644 --- a/modules/boursorama/browser.py +++ b/modules/boursorama/browser.py @@ -26,7 +26,7 @@ from weboob.browser.retry import login_method, retry_on_logout, RetryLoginBrowser from weboob.browser.browsers import need_login, StatesMixin from .compat.weboob_browser_url import URL -from weboob.exceptions import BrowserIncorrectPassword, BrowserHTTPNotFound, NoAccountsException, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserHTTPNotFound, NoAccountsException, BrowserUnavailable from weboob.browser.exceptions import LoggedOut, ClientError from .compat.weboob_capabilities_bank import ( Account, AccountNotFound, TransferError, TransferInvalidAmount, diff --git a/modules/boursorama/compat/weboob_capabilities_bank.py b/modules/boursorama/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/boursorama/compat/weboob_capabilities_bank.py +++ b/modules/boursorama/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/boursorama/compat/weboob_exceptions.py b/modules/boursorama/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/boursorama/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/boursorama/pages.py b/modules/boursorama/pages.py index 317b1c0301..5cd20d6e7c 100644 --- a/modules/boursorama/pages.py +++ b/modules/boursorama/pages.py @@ -49,7 +49,7 @@ from weboob.tools.date import parse_french_date from .compat.weboob_tools_captcha_virtkeyboard import VirtKeyboard, VirtKeyboardError from weboob.tools.compat import urljoin, urlencode, urlparse -from weboob.exceptions import BrowserQuestion, BrowserIncorrectPassword, BrowserHTTPNotFound, BrowserUnavailable, ActionNeeded +from .compat.weboob_exceptions import BrowserQuestion, BrowserIncorrectPassword, BrowserHTTPNotFound, BrowserUnavailable, ActionNeeded class BrowserAuthenticationCodeMaxLimit(BrowserIncorrectPassword): @@ -579,8 +579,8 @@ def obj_date(self): def obj_amount(self): if Field('type')(self) == Transaction.TYPE_CARD_SUMMARY: # '-' so the reimbursements appear positively in the card transactions: - return -CleanDecimal.US(Dict('amount'))(self) - return CleanDecimal.US(Dict('amount'))(self) + return -CleanDecimal.French(Dict('amount'))(self) + return CleanDecimal.French(Dict('amount'))(self) def obj_rdate(self): if self.obj.rdate: diff --git a/modules/bouygues/browser.py b/modules/bouygues/browser.py index 605e5f182d..659cfd0432 100644 --- a/modules/bouygues/browser.py +++ b/modules/bouygues/browser.py @@ -22,9 +22,9 @@ from time import time from jose import jwt -from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.browser.exceptions import HTTPNotFound -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.compat import urlparse, parse_qsl from .pages import ( diff --git a/modules/bouygues/compat/weboob_exceptions.py b/modules/bouygues/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bouygues/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bouygues/pages.py b/modules/bouygues/pages.py index 3b689498c1..60208ca498 100644 --- a/modules/bouygues/pages.py +++ b/modules/bouygues/pages.py @@ -28,7 +28,7 @@ from weboob.capabilities import NotAvailable from weboob.capabilities.bill import Subscription, Bill from .compat.weboob_browser_filters_standard import Date, CleanDecimal, Env, Format, Coalesce, CleanText -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword class LoginPage(HTMLPage): diff --git a/modules/bp/browser.py b/modules/bp/browser.py index 2d5f78b3f0..15c3c1d06e 100644 --- a/modules/bp/browser.py +++ b/modules/bp/browser.py @@ -24,7 +24,7 @@ from weboob.browser.browsers import StatesMixin from weboob.browser.exceptions import ServerError from weboob.capabilities.base import NotAvailable -from weboob.exceptions import BrowserIncorrectPassword, BrowserBanned, NoAccountsException, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserBanned, NoAccountsException, BrowserUnavailable from weboob.tools.compat import urlsplit, urlunsplit, parse_qsl from .pages import ( @@ -237,6 +237,7 @@ def get_accounts_list(self): accounts = [] to_check = [] + owner_name = self.get_profile().name self.par_accounts_checking.go() pages = [self.par_accounts_checking, self.par_accounts_savings_and_invests, self.par_accounts_loan] @@ -249,7 +250,7 @@ def get_accounts_list(self): no_accounts += 1 continue - for account in self.page.iter_accounts(): + for account in self.page.iter_accounts(name=owner_name): if account.type == Account.TYPE_LOAN: self.location(account.url) if 'initSSO' not in account.url: diff --git a/modules/bp/compat/weboob_capabilities_bank.py b/modules/bp/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bp/compat/weboob_capabilities_bank.py +++ b/modules/bp/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bp/compat/weboob_exceptions.py b/modules/bp/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bp/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bp/pages/accounthistory.py b/modules/bp/pages/accounthistory.py index 171459a34c..77733b1c0a 100644 --- a/modules/bp/pages/accounthistory.py +++ b/modules/bp/pages/accounthistory.py @@ -24,7 +24,7 @@ from weboob.capabilities.base import NotAvailable, empty from .compat.weboob_capabilities_bank import Investment, Transaction as BaseTransaction, Account -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.browser.pages import LoggedPage from weboob.browser.elements import TableElement, ItemElement, method diff --git a/modules/bp/pages/accountlist.py b/modules/bp/pages/accountlist.py index cb54d65ea2..f574d0ca13 100644 --- a/modules/bp/pages/accountlist.py +++ b/modules/bp/pages/accountlist.py @@ -24,14 +24,14 @@ from decimal import Decimal from weboob.capabilities.base import NotAvailable -from .compat.weboob_capabilities_bank import Account, Loan +from .compat.weboob_capabilities_bank import Account, Loan, AccountOwnership from weboob.capabilities.contact import Advisor from weboob.capabilities.profile import Person from weboob.browser.elements import ListElement, ItemElement, method, TableElement from weboob.browser.pages import LoggedPage, RawPage, PartialHTMLPage, HTMLPage from weboob.browser.filters.html import Link, TableCell, Attr from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Regexp, Env, Field, Currency, Async, Date, Format -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from weboob.tools.compat import urljoin, unicode from .base import MyHTMLPage @@ -73,6 +73,15 @@ def obj_url(self): def obj_label(self): return CleanText('.//div[@class="title"]/h3')(self).upper() + def obj_ownership(self): + account_holder = CleanText('.//div[@class="title"]/span')(self) + if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou ?(m|mr|me|mme|mlle|mle|ml)?\b(.*)', account_holder, re.IGNORECASE): + return AccountOwnership.CO_OWNER + elif all([n in account_holder for n in self.env['name'].split(' ')]): + return AccountOwnership.OWNER + else: + return AccountOwnership.ATTORNEY + def obj_balance(self): if Field('type')(self) == Account.TYPE_LOAN: balance = CleanDecimal('.//span[@class="number"]', replace_dots=True, default=NotAvailable)(self) diff --git a/modules/bp/pages/compat/weboob_capabilities_bank.py b/modules/bp/pages/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bp/pages/compat/weboob_capabilities_bank.py +++ b/modules/bp/pages/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bp/pages/compat/weboob_exceptions.py b/modules/bp/pages/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bp/pages/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bp/pages/login.py b/modules/bp/pages/login.py index cf339e0dd0..01ebedfc81 100644 --- a/modules/bp/pages/login.py +++ b/modules/bp/pages/login.py @@ -21,7 +21,7 @@ from io import BytesIO -from weboob.exceptions import BrowserUnavailable, BrowserIncorrectPassword, NoAccountsException +from .compat.weboob_exceptions import BrowserUnavailable, BrowserIncorrectPassword, NoAccountsException from weboob.browser.pages import LoggedPage from .compat.weboob_browser_filters_standard import CleanText, Regexp from .compat.weboob_tools_captcha_virtkeyboard import VirtKeyboard diff --git a/modules/bp/pages/pro.py b/modules/bp/pages/pro.py index 40ba7070bd..9907aca90e 100644 --- a/modules/bp/pages/pro.py +++ b/modules/bp/pages/pro.py @@ -26,7 +26,7 @@ from .compat.weboob_capabilities_bank import Account from weboob.capabilities.profile import Company from weboob.capabilities.base import NotAvailable -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from .accounthistory import Transaction from .base import MyHTMLPage diff --git a/modules/bp/pages/transfer.py b/modules/bp/pages/transfer.py index 295625e176..b0679c2bba 100644 --- a/modules/bp/pages/transfer.py +++ b/modules/bp/pages/transfer.py @@ -32,7 +32,7 @@ from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.capabilities.bank.iban import is_iban_valid from weboob.tools.value import Value -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable from .base import MyHTMLPage diff --git a/modules/bred/bred/browser.py b/modules/bred/bred/browser.py index a07a65455e..2024ffed83 100644 --- a/modules/bred/bred/browser.py +++ b/modules/bred/bred/browser.py @@ -43,24 +43,24 @@ class BredBrowser(LoginBrowser): BASEURL = 'https://www.bred.fr' - home = URL('/$', HomePage) - login = URL('/transactionnel/Authentication', LoginPage) - error = URL('.*gestion-des-erreurs/erreur-pwd', - '.*gestion-des-erreurs/opposition', - '/pages-gestion-des-erreurs/erreur-technique', - '/pages-gestion-des-erreurs/message-tiers-oppose', ErrorPage) - universe = URL('/transactionnel/services/applications/menu/getMenuUnivers', UniversePage) - token = URL(r'/transactionnel/services/rest/User/nonce\?random=(?P.*)', TokenPage) - move_universe = URL('/transactionnel/services/applications/listes/(?P.*)/default', MoveUniversePage) - switch = URL('/transactionnel/services/rest/User/switch', SwitchPage) - loans = URL('/transactionnel/services/applications/prets/liste', LoansPage) - accounts = URL('/transactionnel/services/rest/Account/accounts', AccountsPage) - iban = URL('/transactionnel/services/rest/Account/account/(?P.*)/iban', IbanPage) - life_insurances = URL('/transactionnel/services/applications/avoirsPrepar/getAvoirs', LifeInsurancesPage) - search = URL('/transactionnel/services/applications/operations/getSearch/', SearchPage) - profile = URL('/transactionnel/services/rest/User/user', ProfilePage) - emails = URL('/transactionnel/services/applications/gestionEmail/getAdressesMails', EmailsPage) - error_code = URL('/.*\?errorCode=.*', ErrorCodePage) + home = URL(r'/$', HomePage) + login = URL(r'/transactionnel/Authentication', LoginPage) + error = URL(r'.*gestion-des-erreurs/erreur-pwd', + r'.*gestion-des-erreurs/opposition', + r'/pages-gestion-des-erreurs/erreur-technique', + r'/pages-gestion-des-erreurs/message-tiers-oppose', ErrorPage) + universe = URL(r'/transactionnel/services/applications/menu/getMenuUnivers', UniversePage) + token = URL(r'/transactionnel/services/rest/User/nonce\?random=(?P.*)', TokenPage) + move_universe = URL(r'/transactionnel/services/applications/listes/(?P.*)/default', MoveUniversePage) + switch = URL(r'/transactionnel/services/rest/User/switch', SwitchPage) + loans = URL(r'/transactionnel/services/applications/prets/liste', LoansPage) + accounts = URL(r'/transactionnel/services/rest/Account/accounts', AccountsPage) + iban = URL(r'/transactionnel/services/rest/Account/account/(?P.*)/iban', IbanPage) + life_insurances = URL(r'/transactionnel/services/applications/avoirsPrepar/getAvoirs', LifeInsurancesPage) + search = URL(r'/transactionnel/services/applications/operations/getSearch/', SearchPage) + profile = URL(r'/transactionnel/services/rest/User/user', ProfilePage) + emails = URL(r'/transactionnel/services/applications/gestionEmail/getAdressesMails', EmailsPage) + error_code = URL(r'/.*\?errorCode=.*', ErrorCodePage) def __init__(self, accnum, login, password, *args, **kwargs): kwargs['username'] = login @@ -198,7 +198,7 @@ def get_history(self, account, coming=False): self.logger.debug('stopping coming after %s', t) return - next_page = len(transactions) == 50 + next_page = len(transactions) > 0 offset += 50 # This assert supposedly prevents infinite loops, diff --git a/modules/bred/bred/compat/weboob_capabilities_bank.py b/modules/bred/bred/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bred/bred/compat/weboob_capabilities_bank.py +++ b/modules/bred/bred/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bred/bred/compat/weboob_exceptions.py b/modules/bred/bred/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bred/bred/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bred/bred/pages.py b/modules/bred/bred/pages.py index ad56f208b8..9823c72f98 100644 --- a/modules/bred/bred/pages.py +++ b/modules/bred/bred/pages.py @@ -24,7 +24,7 @@ from decimal import Decimal from weboob.tools.date import parse_french_date -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded, ParseError +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded from weboob.capabilities.base import find_object from weboob.browser.pages import JsonPage, LoggedPage, HTMLPage from weboob.capabilities import NotAvailable @@ -38,21 +38,22 @@ class Transaction(FrenchTransaction): - PATTERNS = [(re.compile(r'^.*Virement (?P.*)'), FrenchTransaction.TYPE_TRANSFER), - (re.compile(r'PRELEV SEPA (?P.*)'), FrenchTransaction.TYPE_ORDER), - (re.compile(r'.*Prélèvement.*'), FrenchTransaction.TYPE_ORDER), - (re.compile(r'^(REGL|Rgt)(?P.*)'), FrenchTransaction.TYPE_ORDER), - (re.compile(r'^(?P.*) Carte \d+\s+ LE (?P
\d{2})/(?P\d{2})/(?P\d{2})'), - FrenchTransaction.TYPE_CARD), - (re.compile(r'^Débit mensuel.*'), FrenchTransaction.TYPE_CARD_SUMMARY), - (re.compile(r"^Retrait d'espèces à un DAB (?P.*) CARTE [X\d]+ LE (?P
\d{2})/(?P\d{2})/(?P\d{2})"), - FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile(r'^Paiement de chèque (?P.*)'), FrenchTransaction.TYPE_CHECK), - (re.compile(r'^(Cotisation|Intérêts) (?P.*)'), FrenchTransaction.TYPE_BANK), - (re.compile(r'^(Remise Chèque|Remise de chèque)\s*(?P.*)'), FrenchTransaction.TYPE_DEPOSIT), - (re.compile(r'^Versement (?P.*)'), FrenchTransaction.TYPE_DEPOSIT), - (re.compile(r'^(?P.*)LE (?P
\d{2})/(?P\d{2})/(?P\d{2})\s*(?P.*)'), - FrenchTransaction.TYPE_UNKNOWN), + PATTERNS = [ + (re.compile(r'^.*Virement (?P.*)'), FrenchTransaction.TYPE_TRANSFER), + (re.compile(r'PRELEV SEPA (?P.*)'), FrenchTransaction.TYPE_ORDER), + (re.compile(r'.*Prélèvement.*'), FrenchTransaction.TYPE_ORDER), + (re.compile(r'^(REGL|Rgt)(?P.*)'), FrenchTransaction.TYPE_ORDER), + (re.compile(r'^(?P.*) Carte \d+\s+ LE (?P
\d{2})/(?P\d{2})/(?P\d{2})'), + FrenchTransaction.TYPE_CARD), + (re.compile(r'^Débit mensuel.*'), FrenchTransaction.TYPE_CARD_SUMMARY), + (re.compile(r"^Retrait d'espèces à un DAB (?P.*) CARTE [X\d]+ LE (?P
\d{2})/(?P\d{2})/(?P\d{2})"), + FrenchTransaction.TYPE_WITHDRAWAL), + (re.compile(r'^Paiement de chèque (?P.*)'), FrenchTransaction.TYPE_CHECK), + (re.compile(r'^(Cotisation|Intérêts) (?P.*)'), FrenchTransaction.TYPE_BANK), + (re.compile(r'^(Remise Chèque|Remise de chèque)\s*(?P.*)'), FrenchTransaction.TYPE_DEPOSIT), + (re.compile(r'^Versement (?P.*)'), FrenchTransaction.TYPE_DEPOSIT), + (re.compile(r'^(?P.*)LE (?P
\d{2})/(?P\d{2})/(?P\d{2})\s*(?P.*)'), + FrenchTransaction.TYPE_UNKNOWN), ] @@ -135,7 +136,7 @@ def iter_accounts(self, accnum, current_univers): accounts_list = [] - for content in self.get_content(): + for content in self.get_content(): if accnum != '00000000000' and content['numero'] != accnum: continue for poste in content['postes']: @@ -263,15 +264,16 @@ def iter_history(self, account, operation_list, seen, today, coming): t = Transaction() t.id = str(op['id']) if op['id'] in seen: - raise ParseError('There are several transactions with the same ID, probably an infinite loop') + self.logger.debug('Skipped transaction : "%s %s"' % (op['id'], op['libelle'])) + continue seen.add(t.id) - d = date.fromtimestamp(op.get('dateDebit', op.get('dateOperation'))/1000) + d = date.fromtimestamp(op.get('dateDebit', op.get('dateOperation')) / 1000) op['details'] = [re.sub(r'\s+', ' ', i).replace('\x00', '') for i in op['details'] if i] # sometimes they put "null" elements... label = re.sub(r'\s+', ' ', op['libelle']).replace('\x00', '') raw = ' '.join([label] + op['details']) - t.rdate = date.fromtimestamp(op.get('dateOperation', op.get('dateDebit'))/1000) - vdate = date.fromtimestamp(op.get('dateValeur', op.get('dateDebit', op.get('dateOperation')))/1000) + t.rdate = date.fromtimestamp(op.get('dateOperation', op.get('dateDebit')) / 1000) + vdate = date.fromtimestamp(op.get('dateValeur', op.get('dateDebit', op.get('dateOperation'))) / 1000) t.parse(d, raw, vdate=vdate) t.amount = Decimal(str(op['montant'])) if 'categorie' in op: diff --git a/modules/bred/compat/weboob_capabilities_bank.py b/modules/bred/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bred/compat/weboob_capabilities_bank.py +++ b/modules/bred/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bred/dispobank/browser.py b/modules/bred/dispobank/browser.py index 01c6f0647b..817697b8bd 100644 --- a/modules/bred/dispobank/browser.py +++ b/modules/bred/dispobank/browser.py @@ -18,8 +18,8 @@ # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, need_login, URL -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, need_login, URL +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, LoginResultPage, AccountsPage, EmptyPage, TransactionsPage diff --git a/modules/bred/dispobank/compat/weboob_capabilities_bank.py b/modules/bred/dispobank/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/bred/dispobank/compat/weboob_capabilities_bank.py +++ b/modules/bred/dispobank/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/bred/dispobank/compat/weboob_exceptions.py b/modules/bred/dispobank/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/bred/dispobank/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/bred/module.py b/modules/bred/module.py index 0f7df0441c..ff620eec55 100644 --- a/modules/bred/module.py +++ b/modules/bred/module.py @@ -17,6 +17,7 @@ # 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 .compat.weboob_capabilities_bank import CapBankWealth, AccountNotFound, Account from weboob.capabilities.base import find_object @@ -38,21 +39,23 @@ class BredModule(Module, CapBankWealth, CapProfile): VERSION = '1.5' DESCRIPTION = u'Bred' LICENSE = 'LGPLv3+' - CONFIG = BackendConfig(ValueBackendPassword('login', label='Identifiant', masked=False), - ValueBackendPassword('password', label='Mot de passe'), - Value('website', label=u"Site d'accès", default='bred', - choices={'bred': 'BRED', 'dispobank': 'DispoBank'}), - Value('accnum', label=u'Numéro du compte bancaire (optionnel)', default='', masked=False) - ) - - BROWSERS = {'bred': BredBrowser, - 'dispobank': DispoBankBrowser, - } + CONFIG = BackendConfig( + ValueBackendPassword('login', label='Identifiant', masked=False), + ValueBackendPassword('password', label='Mot de passe'), + Value('website', label="Site d'accès", default='bred', + choices={'bred': 'BRED', 'dispobank': 'DispoBank'}), + Value('accnum', label='Numéro du compte bancaire (optionnel)', default='', masked=False), + ) + + BROWSERS = { + 'bred': BredBrowser, + 'dispobank': DispoBankBrowser, + } def create_default_browser(self): self.BROWSER = self.BROWSERS[self.config['website'].get()] - return self.create_browser(self.config['accnum'].get().replace(' ','').zfill(11), + return self.create_browser(self.config['accnum'].get().replace(' ', '').zfill(11), self.config['login'].get(), self.config['password'].get()) diff --git a/modules/btpbanque/compat/weboob_capabilities_bank.py b/modules/btpbanque/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/btpbanque/compat/weboob_capabilities_bank.py +++ b/modules/btpbanque/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/caels/compat/weboob_capabilities_bank.py b/modules/caels/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/caels/compat/weboob_capabilities_bank.py +++ b/modules/caels/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/caissedepargne/browser.py b/modules/caissedepargne/browser.py index 5666933f87..d89acc9dbb 100644 --- a/modules/caissedepargne/browser.py +++ b/modules/caissedepargne/browser.py @@ -22,6 +22,7 @@ import re import datetime import json +from hashlib import sha256 from decimal import Decimal from dateutil import parser @@ -29,11 +30,15 @@ from weboob.browser.browsers import LoginBrowser, need_login, StatesMixin from weboob.browser.switch import SiteSwitch from .compat.weboob_browser_url import URL -from .compat.weboob_capabilities_bank import Account, AddRecipientStep, Recipient, TransferBankError, Transaction, TransferStep +from .compat.weboob_capabilities_bank import ( + Account, AddRecipientStep, Recipient, TransferBankError, Transaction, TransferStep, + TransferInvalidOTP, RecipientInvalidOTP, +) from weboob.capabilities.base import NotAvailable, find_object +from weboob.capabilities.bill import Subscription from weboob.capabilities.profile import Profile from weboob.browser.exceptions import BrowserHTTPNotFound, ClientError, ServerError -from weboob.exceptions import ( +from .compat.weboob_exceptions import ( BrowserIncorrectPassword, BrowserUnavailable, BrowserHTTPError, BrowserPasswordExpired, ActionNeeded ) from weboob.tools.capabilities.bank.transactions import ( @@ -119,9 +124,15 @@ class CaisseEpargne(LoginBrowser, StatesMixin): r'https://.*/particuliers/epargner.*', GarbagePage) sms = URL(r'https://www.icgauth.caisse-epargne.fr/dacswebssoissuer/AuthnRequestServlet', SmsPage) sms_option = URL(r'https://www.icgauth.caisse-epargne.fr/dacstemplate-SOL/index.html\?transactionID=.*', SmsPageOption) - request_sms = URL(r'https://www.icgauth.caisse-epargne.fr/dacsrest/api/v1u0/transaction/(?P)', SmsRequest) + request_sms = URL( + r'https://(?Pwww.icgauth.[^/]+)/dacsrest/api/v1u0/transaction/(?P)', + SmsRequest, + ) - __states__ = ('BASEURL', 'multi_type', 'typeAccount', 'is_cenet_website', 'recipient_form', 'is_send_sms') + __states__ = ( + 'BASEURL', 'multi_type', 'typeAccount', 'is_cenet_website', 'recipient_form', + 'is_send_sms', 'otp_validation', 'otp_url', + ) # Accounts managed in life insurance space (not in linebourse) @@ -158,11 +169,14 @@ def __init__(self, nuser, *args, **kwargs): self.nuser = nuser self.recipient_form = None self.is_send_sms = None + self.otp_validation = None + self.otp_url = None self.weboob = kwargs['weboob'] self.market_url = kwargs.pop( 'market_url', 'https://www.caisse-epargne.offrebourse.com', ) + self.has_subscription = True super(CaisseEpargne, self).__init__(*args, **kwargs) @@ -187,8 +201,7 @@ def load_state(self, state): if state.get('expire') and parser.parse(state['expire']) < datetime.datetime.now(): return self.logger.info('State expired, not reloading it from storage') - # Reload session only for add recipient step - transfer_states = ('recipient_form', 'is_send_sms') + transfer_states = ('recipient_form', 'is_send_sms', 'otp_validation', 'otp_url') for transfer_state in transfer_states: if transfer_state in state and state[transfer_state] is not None: @@ -196,10 +209,10 @@ def load_state(self, state): self.logged = True break - # need to post to valid otp when adding recipient. def locate_browser(self, state): - if 'is_send_sms' in state and state['is_send_sms']: - super(CaisseEpargne, self).locate_browser(state) + # in case of transfer/add recipient, we shouldn't go back to previous page + # site will crash else + pass def do_login(self): """ @@ -927,6 +940,9 @@ def init_transfer(self, account, recipient, transfer): if self.sms_option.is_here(): self.is_send_sms = True + self.otp_update_state() + self.otp_choose_sms() + raise TransferStep( transfer, Value( @@ -946,7 +962,7 @@ def otp_sms_continue_transfer(self, transfer, **params): self.is_send_sms = False assert 'otp_sms' in params, 'OTP SMS is missing' - self.otp_sms_validation(params['otp_sms']) + self.otp_sms_validation(params['otp_sms'], TransferInvalidOTP) if self.transfer.is_here(): self.page.continue_transfer(transfer.account_label, transfer.recipient_label, transfer.label) return self.page.update_transfer(transfer) @@ -968,29 +984,42 @@ def get_recipient_obj(self, recipient): r.bank_name = NotAvailable return r - def otp_sms_validation(self, otp_sms): - tr_id = re.search(r'transactionID=(.*)', self.page.url) - if tr_id: - transaction_id = tr_id.group(1) + def otp_update_state(self): + transaction_id = re.search(r'transactionID=(.*)', self.page.url) + if transaction_id: + transaction_id = transaction_id.group(1) else: assert False, 'Transfer transaction id was not found in url' - self.request_sms.go(param=transaction_id) + self.request_sms.go(domain=urlparse(self.url).netloc, param=transaction_id) + self.otp_validation = self.page.validation_unit() + + self.otp_url = self.url + if not self.url.endswith('/step'): + self.otp_url += '/step' + + def otp_choose_sms(self): + key = next(iter(self.otp_validation)) + if self.otp_validation[key][0]['type'] == 'SMS': + return + + self.location(self.otp_url, json={'fallback': {}}) + self.otp_validation = self.page.validation_unit() - key = self.page.validate_key() + def otp_sms_validation(self, otp_sms, otp_exception): + key = next(iter(self.otp_validation)) data = { 'validate': { key: [{ - 'id': self.page.validation_id(key), + 'id': self.otp_validation[key][0]['id'], 'otp_sms': otp_sms, - 'type': 'SMS' - }] - } + 'type': 'SMS', + }], + }, } - headers = {'Content-Type': 'application/json'} - self.location(self.url + '/step', json=data, headers=headers) + self.location(self.otp_url, json=data) - saml = self.page.get_saml() + saml = self.page.get_saml(otp_exception) action = self.page.get_action() self.location(action, data={'SAMLResponse': saml}) @@ -1025,7 +1054,7 @@ def new_recipient(self, recipient, **params): return self.end_sms_recipient(recipient, **params) if 'otp_sms' in params: - self.otp_sms_validation(params['otp_sms']) + self.otp_sms_validation(params['otp_sms'], RecipientInvalidOTP) if self.authent.is_here(): self.page.go_on() @@ -1038,8 +1067,15 @@ def new_recipient(self, recipient, **params): # This send sms to user. self.page.go_add_recipient() + if self.transfer.is_here(): + self.page.handle_error() + assert False, 'We should not be on this page.' + if self.sms_option.is_here(): self.is_send_sms = True + self.otp_update_state() + self.otp_choose_sms() + raise AddRecipientStep( self.get_recipient_obj(recipient), Value( @@ -1058,6 +1094,10 @@ def new_recipient(self, recipient, **params): self.page.set_browser_form() raise AddRecipientStep(self.get_recipient_obj(recipient), Value('sms_password', label=self.page.get_prompt_text())) + def go_documents_without_sub(self): + self.home_tache.go(tache='CPTSYNT0') + assert self.subscription.is_here(), "Couldn't go to documents page" + @need_login def iter_subscription(self): self.home.go() @@ -1068,6 +1108,20 @@ def iter_subscription(self): if self.unavailable_page.is_here(): # some users don't have checking account self.home_tache.go(tache='EPASYNT0') + if self.garbage.is_here(): # User has no subscription, checking if they have documents, if so creating fake subscription + self.has_subscription = False + self.home_tache.go(tache='CPTSYNT0') + if not self.subscription.is_here(): # Looks like there is nothing to return + return [] + self.logger.warning("Couldn't find subscription, creating a fake one to return documents available") + + profile = self.get_profile() + + sub = Subscription() + sub.label = sub.subscriber = profile.name + sub.id = sha256(profile.name.lower().encode('utf-8')).hexdigest() + + return [sub] self.page.go_subscription() if not self.subscription.is_here(): # if user is not allowed to have subscription we are redirected to IndexPage @@ -1081,6 +1135,9 @@ def iter_subscription(self): @need_login def iter_documents(self, subscription): self.home.go() + if not self.has_subscription: + self.go_documents_without_sub() + return self.page.iter_documents(sub_id=subscription.id, has_subscription=self.has_subscription) self.home_tache.go(tache='CPTSYNT1') if self.unavailable_page.is_here(): # some users don't have checking account @@ -1088,11 +1145,14 @@ def iter_documents(self, subscription): self.page.go_subscription() assert self.subscription.is_here() - return self.page.iter_documents(sub_id=subscription.id) + return self.page.iter_documents(sub_id=subscription.id, has_subscription=self.has_subscription) @need_login def download_document(self, document): self.home.go() + if not self.has_subscription: + self.go_documents_without_sub() + return self.page.download_document(document).content self.home_tache.go(tache='CPTSYNT1') if self.unavailable_page.is_here(): # some users don't have checking account diff --git a/modules/caissedepargne/cenet/browser.py b/modules/caissedepargne/cenet/browser.py index a6ab24e6ec..fbd7e1f23b 100644 --- a/modules/caissedepargne/cenet/browser.py +++ b/modules/caissedepargne/cenet/browser.py @@ -24,7 +24,7 @@ from weboob.browser.browsers import LoginBrowser, need_login, StatesMixin from .compat.weboob_browser_url import URL from weboob.browser.exceptions import ClientError -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable from weboob.capabilities.base import find_object from .compat.weboob_capabilities_bank import Account from weboob.tools.capabilities.bank.transactions import sorted_transactions, FrenchTransaction diff --git a/modules/caissedepargne/cenet/compat/weboob_capabilities_bank.py b/modules/caissedepargne/cenet/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/caissedepargne/cenet/compat/weboob_capabilities_bank.py +++ b/modules/caissedepargne/cenet/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/caissedepargne/cenet/compat/weboob_exceptions.py b/modules/caissedepargne/cenet/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/caissedepargne/cenet/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/caissedepargne/cenet/pages.py b/modules/caissedepargne/cenet/pages.py index 9b6c70e125..2ae07b61a1 100644 --- a/modules/caissedepargne/cenet/pages.py +++ b/modules/caissedepargne/cenet/pages.py @@ -31,7 +31,7 @@ from weboob.capabilities.profile import Profile from weboob.capabilities.bill import DocumentTypes, Subscription, Document from weboob.tools.capabilities.bank.transactions import FrenchTransaction -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable class Transaction(FrenchTransaction): diff --git a/modules/caissedepargne/compat/weboob_capabilities_bank.py b/modules/caissedepargne/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/caissedepargne/compat/weboob_capabilities_bank.py +++ b/modules/caissedepargne/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/caissedepargne/compat/weboob_exceptions.py b/modules/caissedepargne/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/caissedepargne/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/caissedepargne/pages.py b/modules/caissedepargne/pages.py index 92a3419f21..a390e03450 100644 --- a/modules/caissedepargne/pages.py +++ b/modules/caissedepargne/pages.py @@ -38,7 +38,7 @@ from weboob.capabilities import NotAvailable from .compat.weboob_capabilities_bank import ( Account, Investment, Recipient, TransferBankError, Transfer, - AddRecipientBankError, Loan, RecipientInvalidOTP, + AddRecipientBankError, Loan, ) from weboob.capabilities.bill import DocumentTypes, Subscription, Document from weboob.tools.capabilities.bank.investments import is_isin_valid @@ -46,7 +46,7 @@ from weboob.tools.capabilities.bank.iban import is_rib_valid, rib2iban, is_iban_valid from .compat.weboob_tools_captcha_virtkeyboard import GridVirtKeyboard from weboob.tools.compat import unicode -from weboob.exceptions import NoAccountsException, BrowserUnavailable, ActionNeeded +from .compat.weboob_exceptions import NoAccountsException, BrowserUnavailable, ActionNeeded from weboob.browser.filters.json import Dict def MyDecimal(*args, **kwargs): @@ -1057,7 +1057,7 @@ def get_card_coming_info(self, number, info): if CleanText('//a[contains(text(),"%s")]' % number)(self.doc): # For all cards except the first one for the same check account, we have to get info through their href info link = CleanText(Link('//a[contains(text(),"%s")]' % number))(self.doc) - infos = re.match(r'.*(DETAIL_OP_M0&[^\"]+).*', link) + infos = re.match(r'.*(DETAIL_OP_M\d&[^\"]+).*', link) info['link'] = infos.group(1) return info @@ -1506,6 +1506,12 @@ def go_add_recipient(self): form['__EVENTARGUMENT'] = m.group(2) form.submit() + def handle_error(self): + # the website cannot add recipients from out of France + error_msg = CleanText('//div[@id="divPopinInfoAjout"]/p[not(a)]')(self.doc) + if error_msg: + raise AddRecipientBankError(message=error_msg) + class TransferConfirmPage(TransferErrorPage, IndexPage): def build_doc(self, content): @@ -1662,18 +1668,17 @@ class SmsRequestStep(LoggedPage, JsonPage): class SmsRequest(LoggedPage, JsonPage): - def validate_key(self): - return list(self.doc['step']['validationUnits'][0].keys())[0] - - def validation_id(self, key): - return self.doc['step']['validationUnits'][0][key][0]['id'] + def validation_unit(self): + if 'step' in self.doc: + return self.doc['step']['validationUnits'][0] + return self.doc['validationUnits'][0] - def get_saml(self): + def get_saml(self, otp_exception): if not 'response' in self.doc: error = self.doc['phase']['previousResult'] if error == 'FAILED_AUTHENTICATION': - raise RecipientInvalidOTP() + raise otp_exception() assert not error, 'Error during recipient validation: %s' % error return self.doc['response']['saml2_post']['samlResponse'] @@ -1852,14 +1857,16 @@ class iter_documents(ListElement): ignore_duplicate = True @property def item_xpath(self): - return '//h3[contains(text(), "%s")]//following-sibling::div[@class="panel"][1]/table/tbody/tr' % Env('sub_id')(self) + if Env('has_subscription')(self): + return '//h3[contains(text(), "%s")]//following-sibling::div[@class="panel"][1]/table/tbody/tr' % Env('sub_id')(self) + return '//div[@id="MM_CONSULTATION_RELEVES_COURRIERS_EDOCUMENTS_divRelevesCourriers"]/table/tbody/tr' class item(ItemElement): klass = Document obj_type = DocumentTypes.OTHER obj_format = 'pdf' - obj_url = Regexp(Link('.//td[@class="telecharger"]/a'), r'WebForm_PostBackOptions\("(\S*)"') + obj_url = Regexp(Link('.//td[@class="telecharger"]//a'), r'WebForm_PostBackOptions\("(\S*)"') obj_id = Format('%s_%s_%s', Env('sub_id'), CleanText('./td[2]', symbols='/', replace=[(' ', '_')]), Regexp(CleanText('./td[3]'), r'([\wé]*)')) obj_label = Format('%s %s', CleanText('./td[3]'), CleanText('./td[2]')) obj_date = Date(CleanText('./td[2]'), dayfirst=True) diff --git a/modules/capeasi/compat/weboob_capabilities_bank.py b/modules/capeasi/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/capeasi/compat/weboob_capabilities_bank.py +++ b/modules/capeasi/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/carrefourbanque/browser.py b/modules/carrefourbanque/browser.py index daf6c544ce..f0384be92e 100644 --- a/modules/carrefourbanque/browser.py +++ b/modules/carrefourbanque/browser.py @@ -19,7 +19,7 @@ from time import sleep from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin -from weboob.exceptions import BrowserIncorrectPassword, NocaptchaQuestion, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, NocaptchaQuestion, BrowserUnavailable from .compat.weboob_capabilities_bank import Account from weboob.tools.compat import basestring diff --git a/modules/carrefourbanque/compat/weboob_capabilities_bank.py b/modules/carrefourbanque/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/carrefourbanque/compat/weboob_capabilities_bank.py +++ b/modules/carrefourbanque/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/carrefourbanque/compat/weboob_exceptions.py b/modules/carrefourbanque/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/carrefourbanque/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/centquatre/browser.py b/modules/centquatre/browser.py index a0f1b155ad..9395611dbd 100644 --- a/modules/centquatre/browser.py +++ b/modules/centquatre/browser.py @@ -20,8 +20,8 @@ import itertools -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import CentQuatrePage, LoginPage, TicketsPage, TicketsDetailsPage diff --git a/modules/centquatre/compat/weboob_exceptions.py b/modules/centquatre/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/centquatre/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/cic/compat/weboob_capabilities_bank.py b/modules/cic/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cic/compat/weboob_capabilities_bank.py +++ b/modules/cic/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cices/compat/weboob_capabilities_bank.py b/modules/cices/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cices/compat/weboob_capabilities_bank.py +++ b/modules/cices/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/citibank/browser.py b/modules/citibank/browser.py index cc7d26c885..5aefd74e24 100644 --- a/modules/citibank/browser.py +++ b/modules/citibank/browser.py @@ -26,7 +26,7 @@ from weboob.browser.browsers import URL, LoginBrowser, need_login from weboob.browser.pages import HTMLPage, JsonPage, RawPage from .compat.weboob_capabilities_bank import Account, AccountNotFound, Transaction -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.capabilities.bank.transactions import AmericanTransaction as AmTr from weboob.tools.js import Javascript diff --git a/modules/citibank/compat/weboob_capabilities_bank.py b/modules/citibank/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/citibank/compat/weboob_capabilities_bank.py +++ b/modules/citibank/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/citibank/compat/weboob_exceptions.py b/modules/citibank/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/citibank/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/cityscoot/browser.py b/modules/cityscoot/browser.py index 68cc92393e..ad7cc47cb7 100644 --- a/modules/cityscoot/browser.py +++ b/modules/cityscoot/browser.py @@ -20,8 +20,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, SubscriptionsPage, DocumentsPage diff --git a/modules/cityscoot/compat/weboob_exceptions.py b/modules/cityscoot/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/cityscoot/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/cmb/compat/weboob_capabilities_bank.py b/modules/cmb/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cmb/compat/weboob_capabilities_bank.py +++ b/modules/cmb/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cmes/browser.py b/modules/cmes/browser.py index f75fba03b7..29e1ae5d92 100644 --- a/modules/cmes/browser.py +++ b/modules/cmes/browser.py @@ -22,8 +22,8 @@ from datetime import datetime from dateutil.relativedelta import relativedelta -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import ( LoginPage, AccountsPage, OperationsListPage, OperationPage, ActionNeededPage, InvestmentPage, ) diff --git a/modules/cmes/compat/weboob_capabilities_bank.py b/modules/cmes/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cmes/compat/weboob_capabilities_bank.py +++ b/modules/cmes/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cmes/compat/weboob_exceptions.py b/modules/cmes/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/cmes/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/cmes/pages.py b/modules/cmes/pages.py index 95b083f8d2..49279359e1 100644 --- a/modules/cmes/pages.py +++ b/modules/cmes/pages.py @@ -29,7 +29,7 @@ from weboob.browser.filters.html import Link from .compat.weboob_capabilities_bank import Account, Investment, Pocket, NotAvailable from weboob.tools.capabilities.bank.transactions import FrenchTransaction -from weboob.exceptions import ActionNeeded +from .compat.weboob_exceptions import ActionNeeded class Transaction(FrenchTransaction): diff --git a/modules/cmmc/compat/weboob_capabilities_bank.py b/modules/cmmc/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cmmc/compat/weboob_capabilities_bank.py +++ b/modules/cmmc/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cmso/compat/weboob_capabilities_bank.py b/modules/cmso/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cmso/compat/weboob_capabilities_bank.py +++ b/modules/cmso/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cmso/par/browser.py b/modules/cmso/par/browser.py index 9af33b7218..5d142b166f 100644 --- a/modules/cmso/par/browser.py +++ b/modules/cmso/par/browser.py @@ -27,7 +27,7 @@ from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin from weboob.browser.exceptions import ClientError, ServerError -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable from .compat.weboob_capabilities_bank import Account, Transaction, AccountNotFound from weboob.capabilities.base import find_object from weboob.tools.capabilities.bank.transactions import sorted_transactions diff --git a/modules/cmso/par/compat/weboob_capabilities_bank.py b/modules/cmso/par/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cmso/par/compat/weboob_capabilities_bank.py +++ b/modules/cmso/par/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cmso/par/compat/weboob_exceptions.py b/modules/cmso/par/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/cmso/par/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/cmso/par/pages.py b/modules/cmso/par/pages.py index 4a411a95ea..95248c52d4 100644 --- a/modules/cmso/par/pages.py +++ b/modules/cmso/par/pages.py @@ -37,7 +37,7 @@ from weboob.capabilities.base import NotAvailable from weboob.capabilities.profile import Profile from weboob.tools.capabilities.bank.transactions import FrenchTransaction -from weboob.exceptions import ParseError +from .compat.weboob_exceptions import ParseError from weboob.tools.capabilities.bank.investments import is_isin_valid from weboob.tools.compat import unicode diff --git a/modules/cmso/pro/browser.py b/modules/cmso/pro/browser.py index 9cfdb82559..b53668fdec 100644 --- a/modules/cmso/pro/browser.py +++ b/modules/cmso/pro/browser.py @@ -26,7 +26,7 @@ from weboob.tools.capabilities.bank.transactions import sorted_transactions from weboob.capabilities.base import find_object from .compat.weboob_capabilities_bank import Account -from weboob.exceptions import BrowserHTTPError, BrowserIncorrectPassword, ActionNeeded, BrowserUnavailable +from .compat.weboob_exceptions import BrowserHTTPError, BrowserIncorrectPassword, ActionNeeded, BrowserUnavailable from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.browser.exceptions import ServerError from weboob.tools.date import LinearDateGuesser diff --git a/modules/cmso/pro/compat/weboob_capabilities_bank.py b/modules/cmso/pro/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cmso/pro/compat/weboob_capabilities_bank.py +++ b/modules/cmso/pro/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cmso/pro/compat/weboob_exceptions.py b/modules/cmso/pro/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/cmso/pro/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/cmso/pro/pages.py b/modules/cmso/pro/pages.py index d71685abe5..505edcc655 100644 --- a/modules/cmso/pro/pages.py +++ b/modules/cmso/pro/pages.py @@ -21,7 +21,7 @@ import re -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.browser.pages import HTMLPage, JsonPage, pagination from weboob.browser.elements import ListElement, ItemElement, TableElement, method from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, DateGuesser, Env, Field, Filter, Regexp, Currency, Date diff --git a/modules/cragr/api/browser.py b/modules/cragr/api/browser.py index 80758d777c..69de701676 100644 --- a/modules/cragr/api/browser.py +++ b/modules/cragr/api/browser.py @@ -28,7 +28,7 @@ from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.browser.switch import SiteSwitch from weboob.browser.exceptions import ServerError, ClientError, BrowserHTTPNotFound, HTTPNotFound -from weboob.exceptions import BrowserUnavailable, BrowserIncorrectPassword, ActionNeeded +from .compat.weboob_exceptions import BrowserUnavailable, BrowserIncorrectPassword, ActionNeeded from weboob.tools.capabilities.bank.iban import is_iban_valid from weboob.tools.capabilities.bank.transactions import sorted_transactions diff --git a/modules/cragr/api/compat/weboob_capabilities_bank.py b/modules/cragr/api/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cragr/api/compat/weboob_capabilities_bank.py +++ b/modules/cragr/api/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cragr/api/compat/weboob_exceptions.py b/modules/cragr/api/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/cragr/api/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/cragr/api/pages.py b/modules/cragr/api/pages.py index a20e405772..bb0cb92f5f 100644 --- a/modules/cragr/api/pages.py +++ b/modules/cragr/api/pages.py @@ -25,7 +25,7 @@ import dateutil from weboob.browser.pages import HTMLPage, JsonPage, LoggedPage -from weboob.exceptions import ActionNeeded +from .compat.weboob_exceptions import ActionNeeded from weboob.capabilities import NotAvailable from weboob.capabilities.base import empty from .compat.weboob_capabilities_bank import ( @@ -41,7 +41,7 @@ from weboob.browser.filters.json import Dict from weboob.tools.capabilities.bank.investments import is_isin_valid -from weboob.exceptions import BrowserPasswordExpired +from .compat.weboob_exceptions import BrowserPasswordExpired def float_to_decimal(f): return Decimal(str(f)) diff --git a/modules/cragr/compat/weboob_capabilities_bank.py b/modules/cragr/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cragr/compat/weboob_capabilities_bank.py +++ b/modules/cragr/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cragr/regions/browser.py b/modules/cragr/regions/browser.py index 3922d5d056..df99df1151 100644 --- a/modules/cragr/regions/browser.py +++ b/modules/cragr/regions/browser.py @@ -26,7 +26,7 @@ from weboob.browser.browsers import LoginBrowser, URL, need_login from .compat.weboob_browser_url import BrowserParamURL from weboob.browser.exceptions import ServerError, BrowserHTTPNotFound -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded from weboob.tools.compat import urlparse from weboob.tools.capabilities.bank.transactions import sorted_transactions from weboob.tools.capabilities.bank.investments import create_french_liquidity @@ -338,8 +338,11 @@ def iter_perimeter_accounts(self, iban, all_accounts): self.accounts.stay_or_go() self.page.set_cragr_code() for account in self.page.iter_accounts(): + self.accounts.go() if iban and account._form: - account.iban = self.get_account_iban(account._form) + # Refresh account form in case it expired + refreshed_account = find_object(self.page.iter_accounts(), id=account.id) + account.iban = self.get_account_iban(refreshed_account._form) if account.id not in [a.id for a in cragr_accounts]: cragr_accounts.append(account) @@ -619,7 +622,13 @@ def iter_history(self, account, coming=False): ): self.unhandled_method(account.id) - date_guesser = LinearDateGuesser(date_max_bump=timedelta(30)) + class NoCopyLinearDateGuesser(LinearDateGuesser): + # params passed to a @method are deepcopied, in each iteration of ItemElement + # so we want to avoid repeatedly copying objects since we wan't to keep using the same object + def __deepcopy__(self, memo): + return self + + date_guesser = NoCopyLinearDateGuesser(date_max_bump=timedelta(30)) for tr in self.page.iter_history(date_guesser=date_guesser): yield tr diff --git a/modules/cragr/regions/compat/weboob_capabilities_bank.py b/modules/cragr/regions/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/cragr/regions/compat/weboob_capabilities_bank.py +++ b/modules/cragr/regions/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/cragr/regions/compat/weboob_exceptions.py b/modules/cragr/regions/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/cragr/regions/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/cragr/regions/pages.py b/modules/cragr/regions/pages.py index ceea65a5d0..4e84176595 100644 --- a/modules/cragr/regions/pages.py +++ b/modules/cragr/regions/pages.py @@ -24,7 +24,7 @@ from decimal import Decimal import re -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded, BrowserPasswordExpired +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded, BrowserPasswordExpired from weboob.browser.pages import HTMLPage, LoggedPage, JsonPage, FormNotFound, pagination from weboob.browser.elements import ListElement, TableElement, DictElement, ItemElement, method @@ -212,9 +212,11 @@ def get_iban(self): 'LDD': Account.TYPE_SAVINGS, 'PEL': Account.TYPE_SAVINGS, 'CEL': Account.TYPE_SAVINGS, + 'CEL2': Account.TYPE_SAVINGS, 'CODEBIS': Account.TYPE_SAVINGS, 'LJMO': Account.TYPE_SAVINGS, 'CSL': Account.TYPE_SAVINGS, + 'CSLB5': Account.TYPE_SAVINGS, 'LEP': Account.TYPE_SAVINGS, 'LEF': Account.TYPE_SAVINGS, 'TIWI': Account.TYPE_SAVINGS, diff --git a/modules/cragr/web/browser.py b/modules/cragr/web/browser.py index c71eeeae77..9b884a3245 100644 --- a/modules/cragr/web/browser.py +++ b/modules/cragr/web/browser.py @@ -32,9 +32,9 @@ from weboob.capabilities.profile import ProfileMissing from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin from weboob.browser.pages import FormNotFound -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable from weboob.tools.date import ChaoticDateGuesser, LinearDateGuesser -from weboob.exceptions import BrowserHTTPError, ActionNeeded +from .compat.weboob_exceptions import BrowserHTTPError, ActionNeeded from .compat.weboob_browser_filters_standard import CleanText from weboob.tools.value import Value from weboob.tools.compat import urlparse, urljoin, basestring diff --git a/modules/cragr/web/compat/__init__.py b/modules/cragr/web/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/cragr/web/pages.py b/modules/cragr/web/pages.py index 4c00b4817a..28a2831ba9 100644 --- a/modules/cragr/web/pages.py +++ b/modules/cragr/web/pages.py @@ -33,7 +33,7 @@ ) from weboob.capabilities.contact import Advisor from weboob.capabilities.profile import Profile -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded from weboob.tools.capabilities.bank.transactions import FrenchTransaction as Transaction from weboob.tools.date import parse_french_date, LinearDateGuesser from weboob.tools.compat import urlparse, urljoin, unicode diff --git a/modules/creditcooperatif/caisseepargne_browser.py b/modules/creditcooperatif/caisseepargne_browser.py index 61fbdb04e4..239f9f7c68 100644 --- a/modules/creditcooperatif/caisseepargne_browser.py +++ b/modules/creditcooperatif/caisseepargne_browser.py @@ -17,8 +17,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . -from weboob.browser import AbstractBrowser +from weboob.browser import AbstractBrowser, URL + from .linebourse_browser import LinebourseAPIBrowser +from .caisseepargne_pages import SmsPage, SmsPageOption __all__ = ['CaisseEpargneBrowser'] @@ -30,6 +32,15 @@ class CaisseEpargneBrowser(AbstractBrowser): LINEBOURSE_BROWSER = LinebourseAPIBrowser + sms = URL( + r'https://www.icgauth.credit-cooperatif.coop/dacswebssoissuer/AuthnRequestServlet', + SmsPage, + ) + sms_option = URL( + r'https://www.icgauth.credit-cooperatif.coop/dacstemplate-SOL/_(?P\d+)/index.html\?transactionID=.*', + SmsPageOption, + ) + def __init__(self, nuser, *args, **kwargs): kwargs['market_url'] = 'https://www.offrebourse.com' super(CaisseEpargneBrowser, self).__init__(nuser, *args, **kwargs) diff --git a/modules/creditcooperatif/caisseepargne_pages.py b/modules/creditcooperatif/caisseepargne_pages.py new file mode 100644 index 0000000000..266187cb8a --- /dev/null +++ b/modules/creditcooperatif/caisseepargne_pages.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2012 Budget Insight +# +# 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 weboob.browser.pages import AbstractPage + + +class SmsPage(AbstractPage): + PARENT = 'caissedepargne' + BROWSER_ATTR = 'package.browser.CaisseEpargne' + PARENT_URL = 'sms' + + +class SmsPageOption(AbstractPage): + PARENT = 'caissedepargne' + BROWSER_ATTR = 'package.browser.CaisseEpargne' + PARENT_URL = 'sms_option' diff --git a/modules/creditcooperatif/compat/weboob_capabilities_bank.py b/modules/creditcooperatif/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/creditcooperatif/compat/weboob_capabilities_bank.py +++ b/modules/creditcooperatif/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/creditdunord/browser.py b/modules/creditdunord/browser.py index 0cff12388e..d1951bb53b 100644 --- a/modules/creditdunord/browser.py +++ b/modules/creditdunord/browser.py @@ -20,7 +20,7 @@ from __future__ import unicode_literals from weboob.browser.browsers import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword, BrowserPasswordExpired, ActionNeeded, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserPasswordExpired, ActionNeeded, BrowserUnavailable from .compat.weboob_capabilities_bank import Account from weboob.capabilities.base import find_object from weboob.tools.capabilities.bank.investments import create_french_liquidity @@ -30,6 +30,7 @@ ProTransactionsPage, LabelsPage, RgpdPage, ) + class CreditDuNordBrowser(LoginBrowser): ENCODING = 'UTF-8' BASEURL = "https://www.credit-du-nord.fr/" @@ -41,9 +42,13 @@ class CreditDuNordBrowser(LoginBrowser): redirect = URL('/swm/redirectCDN.html', RedirectPage) entrypage = URL('/icd/zco/#zco', EntryPage) multitype_av = URL('/vos-comptes/IPT/appmanager/transac/professionnels\?_nfpb=true&_eventName=onRestart&_pageLabel=synthese_contrats_assurance_vie', AVPage) - loans = URL('/vos-comptes/IPT/appmanager/transac/(?P.*)\?_nfpb=true&_eventName=onRestart&_pageLabel=(?P(creditPersoImmobilier|credit__en_cours|credit_en_cours))', ProAccountsPage) - proaccounts = URL('/vos-comptes/IPT/appmanager/transac/(professionnels|entreprises)\?_nfpb=true&_eventName=onRestart&_pageLabel=(?P(transac_tableau_de_bord|page__synthese_v1|page_synthese_v1))', ProAccountsPage) - accounts = URL('/vos-comptes/IPT/appmanager/transac/(?P.*)\?_nfpb=true&_eventName=onRestart&_pageLabel=(?P(transac_tableau_de_bord|page__synthese_v1|page_synthese_v1))', AccountsPage) + loans = URL(r'/vos-comptes/IPT/appmanager/transac/(?P.*)\?_nfpb=true&_eventName=onRestart&_pageLabel=(?P(creditPersoImmobilier|credit_?_en_cours))', ProAccountsPage) + proaccounts = URL(r'/vos-comptes/IPT/appmanager/transac/(professionnels|entreprises)\?_nfpb=true&_eventName=onRestart&_pageLabel=(?P(transac_tableau_de_bord|page_?_synthese_v1))', + r'/vos-comptes/(professionnels|entreprises)/page_?_synthese', + ProAccountsPage) + accounts = URL(r'/vos-comptes/IPT/appmanager/transac/(?P.*)\?_nfpb=true&_eventName=onRestart&_pageLabel=(?P(transac_tableau_de_bord|page_?_synthese_v1))', + r'/vos-comptes/particuliers', + AccountsPage) multitype_iban = URL('/vos-comptes/IPT/appmanager/transac/professionnels\?_nfpb=true&_eventName=onRestart&_pageLabel=impression_rib', ProIbanPage) transactions = URL('/vos-comptes/IPT/appmanager/transac/particuliers\?_nfpb=true(.*)', TransactionsPage) protransactions = URL('/vos-comptes/(.*)/transac/(professionnels|entreprises)', ProTransactionsPage) diff --git a/modules/creditdunord/compat/weboob_capabilities_bank.py b/modules/creditdunord/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/creditdunord/compat/weboob_capabilities_bank.py +++ b/modules/creditdunord/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/creditdunord/compat/weboob_exceptions.py b/modules/creditdunord/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/creditdunord/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/creditdunord/pages.py b/modules/creditdunord/pages.py index d61698cf7d..a55ddf95cf 100755 --- a/modules/creditdunord/pages.py +++ b/modules/creditdunord/pages.py @@ -32,7 +32,7 @@ from .compat.weboob_browser_filters_standard import CleanText, Date, CleanDecimal, Regexp, Format, Field, Eval, Lower from weboob.browser.filters.json import Dict from weboob.browser.filters.html import Attr, TableCell -from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable, BrowserPasswordExpired +from .compat.weboob_exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable, BrowserPasswordExpired from .compat.weboob_capabilities_bank import Account, Investment from weboob.capabilities.profile import Profile from weboob.capabilities.base import Currency, find_object @@ -143,7 +143,7 @@ def vk_login(self, username, password): self.browser.location('/swm/redirectCDN.html', data=data) def classic_login(self, username, password): - m = re.match('www.([^\.]+).fr', self.browser.BASEURL) + m = re.match('https://www.([^\.]+).fr', self.browser.BASEURL) if not m: bank_name = 'credit-du-nord' self.logger.error('Unable to find bank name for %s' % self.browser.BASEURL) diff --git a/modules/creditdunordpee/compat/weboob_capabilities_bank.py b/modules/creditdunordpee/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/creditdunordpee/compat/weboob_capabilities_bank.py +++ b/modules/creditdunordpee/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/creditdunordpee/pages.py b/modules/creditdunordpee/pages.py index 2787d42789..9dc398a5fd 100644 --- a/modules/creditdunordpee/pages.py +++ b/modules/creditdunordpee/pages.py @@ -29,7 +29,7 @@ from weboob.browser.filters.html import CleanHTML, TableCell from .compat.weboob_capabilities_bank import Account, Transaction, Investment from weboob.capabilities.base import NotAvailable -from weboob.exceptions import NoAccountsException +from .compat.weboob_exceptions import NoAccountsException class VirtKeyboard(MappedVirtKeyboard): diff --git a/modules/creditmutuel/browser.py b/modules/creditmutuel/browser.py index f06fb5534c..a8bf97d85c 100644 --- a/modules/creditmutuel/browser.py +++ b/modules/creditmutuel/browser.py @@ -32,7 +32,7 @@ from .compat.weboob_browser_url import URL from weboob.browser.pages import FormNotFound from weboob.browser.exceptions import ClientError, ServerError -from weboob.exceptions import BrowserIncorrectPassword, AuthMethodNotImplemented, BrowserUnavailable, NoAccountsException +from .compat.weboob_exceptions import BrowserIncorrectPassword, AuthMethodNotImplemented, BrowserUnavailable, NoAccountsException from .compat.weboob_capabilities_bank import Account, AddRecipientStep, Recipient, AccountOwnership from weboob.tools.capabilities.bank.investments import create_french_liquidity from weboob.capabilities import NotAvailable diff --git a/modules/creditmutuel/compat/weboob_capabilities_bank.py b/modules/creditmutuel/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/creditmutuel/compat/weboob_capabilities_bank.py +++ b/modules/creditmutuel/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/creditmutuel/compat/weboob_exceptions.py b/modules/creditmutuel/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/creditmutuel/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/creditmutuel/pages.py b/modules/creditmutuel/pages.py index 5c116cc491..8e6deebe06 100644 --- a/modules/creditmutuel/pages.py +++ b/modules/creditmutuel/pages.py @@ -34,7 +34,7 @@ Filter, Env, CleanText, CleanDecimal, Field, Regexp, Async, AsyncLoad, Date, Format, Type, Currency, ) from weboob.browser.filters.html import Link, Attr, TableCell, ColumnNotFound -from weboob.exceptions import ( +from .compat.weboob_exceptions import ( BrowserIncorrectPassword, ParseError, ActionNeeded, BrowserUnavailable, AuthMethodNotImplemented, AppValidation, ) @@ -116,7 +116,7 @@ def on_load(self): self.logger.warning('This connexion cannot bypass mobile confirmation') msg = CleanText('//div[@id="inMobileAppMessage"]')(self.doc) if msg: - display_msg = re.search(r'Confirmer votre connexion depuis votre appareil "[\w ]+"', msg).group() + display_msg = re.search(r'Confirmer votre connexion depuis votre appareil ".+"', msg).group() raise AppValidation(display_msg) assert False, "Mobile authentication method not handled" @@ -137,9 +137,6 @@ class UserSpacePage(LoggedPage, HTMLPage): def on_load(self): if self.doc.xpath('//form[@id="GoValider"]'): raise ActionNeeded("Le site du contrat Banque à Distance a besoin d'informations supplémentaires") - elif 'Afin de compléter vos informations personnelles, renseignez le formulaire ci-dessous' in self.doc.xpath('//form[@class="_devb_act ___Form"]//div[contains(@class, "bloctxt")]/p[1]/text()')[0]: - raise ActionNeeded("Le site nécessite la saisie des informations personnelles de l'utilisateur.") - super(UserSpacePage, self).on_load() @@ -152,57 +149,59 @@ class item_account_generic(ItemElement): klass = Account TYPES = OrderedDict([ - ('Credits Promoteurs', Account.TYPE_CHECKING), # it doesn't fit loan's model - ('Compte Cheque', Account.TYPE_CHECKING), - ('Compte Courant', Account.TYPE_CHECKING), - ('Cpte Courant', Account.TYPE_CHECKING), - ('Contrat Personnel', Account.TYPE_CHECKING), - ('Cc Contrat Personnel', Account.TYPE_CHECKING), - ('C/C', Account.TYPE_CHECKING), - ('Start', Account.TYPE_CHECKING), - ('Comptes courants', Account.TYPE_CHECKING), - ('Service Accueil', Account.TYPE_CHECKING), - ('Eurocompte Serenite', Account.TYPE_CHECKING), - ('Eurocompte Confort', Account.TYPE_CHECKING), - ('Catip', Account.TYPE_DEPOSIT), - ('Cic Immo', Account.TYPE_MORTGAGE), - ('Credit', Account.TYPE_LOAN), - ('Crédits', Account.TYPE_LOAN), - ('Eco-Prêt', Account.TYPE_LOAN), - ('Mcne', Account.TYPE_LOAN), - ('Nouveau Prêt', Account.TYPE_LOAN), - ('Pret', Account.TYPE_LOAN), - ('Regroupement De Credits', Account.TYPE_LOAN), - ('Nouveau Pret 0%', Account.TYPE_LOAN), - ('Global Auto', Account.TYPE_LOAN), - ('Passeport Credit', Account.TYPE_REVOLVING_CREDIT), - ('Allure', Account.TYPE_REVOLVING_CREDIT), # 'Allure Libre' or 'credit Allure' - ('Preference', Account.TYPE_REVOLVING_CREDIT), - ('Plan 4', Account.TYPE_REVOLVING_CREDIT), - ('P.E.A', Account.TYPE_PEA), - ('Pea', Account.TYPE_PEA), - ('Compte De Liquidite Pea', Account.TYPE_PEA), - ('Compte Epargne', Account.TYPE_SAVINGS), - ('Etalis', Account.TYPE_SAVINGS), - ('Ldd', Account.TYPE_SAVINGS), - ('Livret', Account.TYPE_SAVINGS), - ("Plan D'Epargne", Account.TYPE_SAVINGS), - ('Tonic Croissance', Account.TYPE_SAVINGS), - ('Tonic Societaire', Account.TYPE_SAVINGS), - ('Capital Expansion', Account.TYPE_SAVINGS), - ('Épargne', Account.TYPE_SAVINGS), - ('Capital Plus', Account.TYPE_SAVINGS), - ('Pep', Account.TYPE_SAVINGS), - ('Compte Duo', Account.TYPE_SAVINGS), - ('Compte Garantie Titres', Account.TYPE_MARKET), + (re.compile(r'Credits Promoteurs'), Account.TYPE_CHECKING), # it doesn't fit loan's model + (re.compile(r'Compte Cheque'), Account.TYPE_CHECKING), + (re.compile(r'Compte Courant'), Account.TYPE_CHECKING), + (re.compile(r'Cpte Courant'), Account.TYPE_CHECKING), + (re.compile(r'Contrat Personnel'), Account.TYPE_CHECKING), + (re.compile(r'Cc Contrat Personnel'), Account.TYPE_CHECKING), + (re.compile(r'C/C'), Account.TYPE_CHECKING), + (re.compile(r'Start\b'), Account.TYPE_CHECKING), + (re.compile(r'Comptes courants'), Account.TYPE_CHECKING), + (re.compile(r'Service Accueil'), Account.TYPE_CHECKING), + (re.compile(r'Eurocompte Serenite'), Account.TYPE_CHECKING), + (re.compile(r'Eurocompte Confort'), Account.TYPE_CHECKING), + (re.compile(r'Compte Service Bancaire De Base'), Account.TYPE_CHECKING), + (re.compile(r'Catip\b'), Account.TYPE_DEPOSIT), + (re.compile(r'Cic Immo'), Account.TYPE_MORTGAGE), + (re.compile(r'Credit'), Account.TYPE_LOAN), + (re.compile(r'Crédits'), Account.TYPE_LOAN), + (re.compile(r'Eco-Prêt'), Account.TYPE_LOAN), + (re.compile(r'Mcne'), Account.TYPE_LOAN), + (re.compile(r'Nouveau Prêt'), Account.TYPE_LOAN), + (re.compile(r'Pret\b'), Account.TYPE_LOAN), + (re.compile(r'Regroupement De Credits'), Account.TYPE_LOAN), + (re.compile(r'Nouveau Pret 0%'), Account.TYPE_LOAN), + (re.compile(r'Global Auto'), Account.TYPE_LOAN), + (re.compile(r'Passeport Credit'), Account.TYPE_REVOLVING_CREDIT), + (re.compile(r'Allure\b'), Account.TYPE_REVOLVING_CREDIT), # 'Allure Libre' or 'credit Allure' + (re.compile(r'Preference'), Account.TYPE_REVOLVING_CREDIT), + (re.compile(r'Plan 4'), Account.TYPE_REVOLVING_CREDIT), + (re.compile(r'P.E.A'), Account.TYPE_PEA), + (re.compile(r'Pea\b'), Account.TYPE_PEA), + (re.compile(r'Compte De Liquidite Pea'), Account.TYPE_PEA), + (re.compile(r'Compte Epargne'), Account.TYPE_SAVINGS), + (re.compile(r'Etalis'), Account.TYPE_SAVINGS), + (re.compile(r'Ldd'), Account.TYPE_SAVINGS), + (re.compile(r'Livret'), Account.TYPE_SAVINGS), + (re.compile(r"Plan D'Epargne"), Account.TYPE_SAVINGS), + (re.compile(r'Tonic Croissance'), Account.TYPE_SAVINGS), + (re.compile(r'Tonic Societaire'), Account.TYPE_SAVINGS), + (re.compile(r'Capital Expansion'), Account.TYPE_SAVINGS), + (re.compile(r'Épargne'), Account.TYPE_SAVINGS), + (re.compile(r'Capital Plus'), Account.TYPE_SAVINGS), + (re.compile(r'Pep\b'), Account.TYPE_SAVINGS), + (re.compile(r'Compte Duo'), Account.TYPE_SAVINGS), + (re.compile(r'Compte Garantie Titres'), Account.TYPE_MARKET), + (re.compile(r'Ppe'), Account.TYPE_LOAN), ]) - REVOLVING_LOAN_LABELS = [ - 'Passeport Credit', - 'Allure', - 'Preference', - 'Plan 4', - 'Credit En Reserve', + REVOLVING_LOAN_REGEXES = [ + re.compile(r'Passeport Credit'), + re.compile(r'Allure'), + re.compile(r'Preference'), + re.compile(r'Plan 4'), + re.compile(r'Credit En Reserve'), ] def condition(self): @@ -215,14 +214,38 @@ def condition(self): and (first_td.find('a') is not None or (first_td.find('.//span') is not None and "cartes" in first_td.findtext('.//span') and first_td.find('./div/a') is not None))) + def loan_condition(self, check_no_details=False): + _type = Field('type')(self) + label = Field('label')(self) + details_link = Link('.//a', default=None)(self) + + # mobile accounts are leading to a 404 error when parsing history + # furthermore this is not exactly a loan account + if re.search(r'Le Mobile +([0-9]{2} ?){5}', label): + return False + + if ( + details_link and + item_account_generic.condition and + _type in (Account.TYPE_LOAN, Account.TYPE_MORTGAGE) and + not self.is_revolving(label) + ): + details = self.page.browser.open(details_link).page + if details and 'cloturé' not in CleanText('//form[@id="P:F"]//div[@class="blocmsg info"]//p')(details.doc): + fiche_details = CleanText('//table[@class="fiche"]')(details.doc) + if check_no_details: # check_no_details is used to determine if condition should check the absence of details, otherwise we still check the presence of details + return not fiche_details + return fiche_details + return False + class Label(Filter): def filter(self, text): return text.lstrip(' 0123456789').title() class Type(Filter): def filter(self, label): - for pattern, actype in item_account_generic.TYPES.items(): - if pattern in label: + for regex, actype in item_account_generic.TYPES.items(): + if regex.search(label): return actype return Account.TYPE_UNKNOWN @@ -390,8 +413,8 @@ def parse(self, el): self.env['coming'] = coming or NotAvailable def is_revolving(self, label): - return (any(revolving_loan_label in label - for revolving_loan_label in item_account_generic.REVOLVING_LOAN_LABELS) + return (any(revolving_loan_regex.search(label) + for revolving_loan_regex in item_account_generic.REVOLVING_LOAN_REGEXES) or label.lower() in self.page.browser.revolving_accounts) @@ -411,11 +434,22 @@ def condition(self): return False return item_account_generic.condition(self) and _type not in (Account.TYPE_LOAN, Account.TYPE_MORTGAGE) + class item_loan_low_details(item_account_generic): + klass = Loan + + def condition(self): + return item_account_generic.loan_condition(self, check_no_details=True) + + obj__parent_id = NotAvailable + class item_loan(item_account_generic): klass = Loan load_details = Link('.//a') & AsyncLoad + def condition(self): + return item_account_generic.loan_condition(self) + obj_total_amount = Async('details') & MyDecimal('//div[@id="F4:expContent"]/table/tbody/tr[1]/td[1]/text()') obj_rate = Async('details') & MyDecimal('//div[@id="F4:expContent"]/table/tbody/tr[2]/td[1]') obj_nb_payments_left = Async('details') & Type(CleanText( @@ -444,23 +478,6 @@ def obj__parent_id(self): return parent_id.replace(' ', '') return NotAvailable - def condition(self): - _type = Field('type')(self) - label = Field('label')(self) - details_link = Link('.//a', default=None)(self) - - # mobile accounts are leading to a 404 error when parsing history - # furthermore this is not exactly a loan account - if re.search(r'Le\sMobile\s+([0-9]{2}\s?){5}', label): - return False - - if (details_link and item_account_generic.condition and _type in (Account.TYPE_LOAN, Account.TYPE_MORTGAGE) - and not self.is_revolving(label)): - details = self.page.browser.open(details_link) - if details.page and not 'cloturé' in CleanText('//form[@id="P:F"]//div[@class="blocmsg info"]//p')(details.page.doc): - return True - return False - class item_revolving_loan(item_account_generic): klass = Loan diff --git a/modules/dailymotion/compat/weboob_exceptions.py b/modules/dailymotion/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/dailymotion/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/dailymotion/pages.py b/modules/dailymotion/pages.py index d131dfc4ea..ab9c860df6 100644 --- a/modules/dailymotion/pages.py +++ b/modules/dailymotion/pages.py @@ -26,7 +26,7 @@ from weboob.capabilities.video import BaseVideo from weboob.capabilities.image import Thumbnail -from weboob.exceptions import ParseError +from .compat.weboob_exceptions import ParseError from weboob.tools.json import json from datetime import timedelta diff --git a/modules/delubac/compat/weboob_capabilities_bank.py b/modules/delubac/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/delubac/compat/weboob_capabilities_bank.py +++ b/modules/delubac/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/delubac/compat/weboob_exceptions.py b/modules/delubac/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/delubac/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/delubac/pages.py b/modules/delubac/pages.py index ea7702742e..e2669b2f22 100644 --- a/modules/delubac/pages.py +++ b/modules/delubac/pages.py @@ -21,7 +21,7 @@ from io import BytesIO from weboob.browser.pages import HTMLPage, LoggedPage -from weboob.exceptions import ParseError, ActionNeeded +from .compat.weboob_exceptions import ParseError, ActionNeeded from .compat.weboob_tools_captcha_virtkeyboard import GridVirtKeyboard from .compat.weboob_browser_filters_standard import CleanText diff --git a/modules/dlfp/browser.py b/modules/dlfp/browser.py index f71ae6c033..6384110df2 100644 --- a/modules/dlfp/browser.py +++ b/modules/dlfp/browser.py @@ -23,9 +23,9 @@ from requests.exceptions import HTTPError -from weboob.browser import LoginBrowser, need_login, URL +from weboob.browser.browsers import LoginBrowser, need_login, URL from weboob.browser.exceptions import HTTPNotFound -from weboob.exceptions import BrowserIncorrectPassword, ParseError +from .compat.weboob_exceptions import BrowserIncorrectPassword, ParseError from weboob.capabilities.messages import CantSendMessage from .pages.index import IndexPage, LoginPage diff --git a/modules/dlfp/compat/__init__.py b/modules/dlfp/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/dlfp/compat/weboob_exceptions.py b/modules/dlfp/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/dlfp/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/dlfp/module.py b/modules/dlfp/module.py index 3c9cca8e0c..c86144c837 100644 --- a/modules/dlfp/module.py +++ b/modules/dlfp/module.py @@ -22,7 +22,7 @@ import time from weboob.tools.backend import Module, BackendConfig -from weboob.exceptions import BrowserForbidden +from .compat.weboob_exceptions import BrowserForbidden from weboob.tools.newsfeed import Newsfeed from weboob.tools.value import Value, ValueBool, ValueBackendPassword from weboob.capabilities.messages import CapMessages, CapMessagesPost, Message, Thread, CantSendMessage diff --git a/modules/edf/par/browser.py b/modules/edf/par/browser.py index f62d3f397a..05341a9886 100644 --- a/modules/edf/par/browser.py +++ b/modules/edf/par/browser.py @@ -20,8 +20,8 @@ from time import time -from weboob.browser import LoginBrowser, URL, need_login, StatesMixin -from weboob.exceptions import BrowserIncorrectPassword, BrowserQuestion +from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserQuestion from weboob.tools.decorators import retry from weboob.tools.json import json from weboob.tools.value import Value diff --git a/modules/edf/par/compat/weboob_exceptions.py b/modules/edf/par/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/edf/par/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/edf/pro/browser.py b/modules/edf/pro/browser.py index 1590b3c91b..2b6d25d1f8 100644 --- a/modules/edf/pro/browser.py +++ b/modules/edf/pro/browser.py @@ -21,9 +21,9 @@ from datetime import datetime, timedelta -from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.capabilities.base import NotAvailable -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded, BrowserUnavailable from weboob.browser.exceptions import ServerError, ClientError from .pages import ( diff --git a/modules/edf/pro/compat/weboob_exceptions.py b/modules/edf/pro/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/edf/pro/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/edf/pro/pages.py b/modules/edf/pro/pages.py index ce99a68967..f4a475bdbf 100644 --- a/modules/edf/pro/pages.py +++ b/modules/edf/pro/pages.py @@ -26,7 +26,7 @@ from .compat.weboob_browser_filters_standard import CleanDecimal, CleanText from weboob.browser.filters.json import Dict from weboob.capabilities.bill import DocumentTypes, Subscription, Bill -from weboob.exceptions import ActionNeeded +from .compat.weboob_exceptions import ActionNeeded from weboob.capabilities.profile import Profile diff --git a/modules/ekwateur/browser.py b/modules/ekwateur/browser.py index 21b021472a..157ac2a8d3 100644 --- a/modules/ekwateur/browser.py +++ b/modules/ekwateur/browser.py @@ -22,8 +22,8 @@ import itertools -from weboob.browser import LoginBrowser, need_login, URL -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, need_login, URL +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import BillsPage, DocumentsPage, LoginPage diff --git a/modules/ekwateur/compat/weboob_exceptions.py b/modules/ekwateur/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ekwateur/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ensap/browser.py b/modules/ensap/browser.py index 2631ad0f6c..3e9dc85574 100644 --- a/modules/ensap/browser.py +++ b/modules/ensap/browser.py @@ -20,9 +20,9 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, need_login, URL +from weboob.browser.browsers import LoginBrowser, need_login, URL from weboob.browser.profiles import Firefox -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.capabilities.base import find_object from weboob.capabilities.bill import DocumentNotFound from .pages import LoginPage, DocumentsPage, HomePage, LoginControlPage,\ diff --git a/modules/ensap/compat/weboob_exceptions.py b/modules/ensap/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ensap/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/erehsbc/compat/weboob_capabilities_bank.py b/modules/erehsbc/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/erehsbc/compat/weboob_capabilities_bank.py +++ b/modules/erehsbc/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/esalia/compat/weboob_capabilities_bank.py b/modules/esalia/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/esalia/compat/weboob_capabilities_bank.py +++ b/modules/esalia/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/feedly/compat/weboob_exceptions.py b/modules/feedly/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/feedly/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/feedly/google.py b/modules/feedly/google.py index 1360d13657..ac862611f4 100644 --- a/modules/feedly/google.py +++ b/modules/feedly/google.py @@ -18,9 +18,9 @@ # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL +from weboob.browser.browsers import LoginBrowser, URL from weboob.browser.pages import HTMLPage, LoggedPage -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.compat import urlparse, parse_qs diff --git a/modules/figgo/browser.py b/modules/figgo/browser.py index 2199b81bd2..fa89394f0d 100644 --- a/modules/figgo/browser.py +++ b/modules/figgo/browser.py @@ -21,9 +21,9 @@ from datetime import timedelta -from weboob.browser import LoginBrowser, need_login, URL +from weboob.browser.browsers import LoginBrowser, need_login, URL from weboob.browser.exceptions import ClientError -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.date import new_datetime from .pages import LoginPage, CalendarPage, HomePage, UsersPage diff --git a/modules/figgo/compat/__init__.py b/modules/figgo/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/fortuneo/browser.py b/modules/fortuneo/browser.py index 66040ffc56..61a270552e 100644 --- a/modules/fortuneo/browser.py +++ b/modules/fortuneo/browser.py @@ -25,7 +25,7 @@ from datetime import datetime, timedelta from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin -from weboob.exceptions import AuthMethodNotImplemented, BrowserIncorrectPassword, ActionNeeded +from .compat.weboob_exceptions import AuthMethodNotImplemented, BrowserIncorrectPassword, ActionNeeded from .compat.weboob_capabilities_bank import Account, AddRecipientStep, Recipient from weboob.tools.capabilities.bank.transactions import sorted_transactions from weboob.tools.value import Value diff --git a/modules/fortuneo/compat/weboob_capabilities_bank.py b/modules/fortuneo/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/fortuneo/compat/weboob_capabilities_bank.py +++ b/modules/fortuneo/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/fortuneo/compat/weboob_exceptions.py b/modules/fortuneo/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/fortuneo/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/fortuneo/pages/accounts_list.py b/modules/fortuneo/pages/accounts_list.py index 74a6c541b2..271740ff1f 100644 --- a/modules/fortuneo/pages/accounts_list.py +++ b/modules/fortuneo/pages/accounts_list.py @@ -31,14 +31,14 @@ from weboob.browser.filters.html import Link, Attr from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, RawText, Regexp, Date from weboob.capabilities import NotAvailable -from .compat.weboob_capabilities_bank import Account, Investment, Loan +from .compat.weboob_capabilities_bank import Account, Investment, Loan, AccountOwnership from weboob.capabilities.profile import Person from weboob.browser.pages import HTMLPage, LoggedPage, FormNotFound, CsvPage from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.capabilities.bank.investments import create_french_liquidity from weboob.tools.json import json from weboob.tools.date import parse_french_date -from weboob.exceptions import ActionNeeded, BrowserUnavailable +from .compat.weboob_exceptions import ActionNeeded, BrowserUnavailable from weboob.tools.capabilities.bank.investments import is_isin_valid @@ -71,6 +71,15 @@ class PeaHistoryPage(LoggedPage, HTMLPage): COL_PERF = 6 COL_WEIGHT = 7 + def on_load(self): + err_msgs = [ + "vos informations personnelles n'ayant pas été modifiées récemment, nous vous remercions de bien vouloir les compléter", + "nous vous remercions de mettre à jour et/ou de compléter vos informations personnelles", + ] + text = CleanText('//div[@class="block_cadre"]//div/p')(self.doc) + if any(err_msg in text for err_msg in err_msgs): + raise ActionNeeded(text) + def get_investments(self, account): if account is not None: # the balance is highly dynamic, fetch it along with the investments to grab a snapshot @@ -174,10 +183,10 @@ def get_investments(self, account): inv.label = CleanText(None).filter(cols[self.COL_LABEL]) inv.quantity = self.parse_decimal(cols[self.COL_QUANTITY], True) - inv.unitprice = self.parse_decimal(cols[self.COL_UNITPRICE], False) - inv.unitvalue = self.parse_decimal(cols[self.COL_UNITVALUE], False) + inv.unitprice = self.parse_decimal(cols[self.COL_UNITPRICE], True) + inv.unitvalue = self.parse_decimal(cols[self.COL_UNITVALUE], True) inv.vdate = Date(CleanText(cols[self.COL_DATE], default=NotAvailable), dayfirst=True, default=NotAvailable)(self.doc) - inv.valuation = self.parse_decimal(cols[self.COL_VALUATION], False) + inv.valuation = self.parse_decimal(cols[self.COL_VALUATION], True) inv.diff = self.parse_decimal(cols[self.COL_PERF], True) diff_percent = self.parse_decimal(cols[self.COL_PERF_PERCENT], True) inv.diff_ratio = diff_percent / 100 if diff_percent else NotAvailable @@ -470,6 +479,7 @@ def get_list(self): account.account_label = account_history_page.get_account_label() account.subscription_date = account_history_page.get_subscription_date() account.maturity_date = account_history_page.get_maturity_date() + account.ownership = account_history_page.get_owner() if len(accounts) == 0: global_error_message = page.doc.xpath('//div[@id="as_renouvellementMIFID.do_"]/div[contains(text(), "Bonjour")] ' @@ -502,31 +512,41 @@ def get_list(self): break investment_page = None - if account.type in {Account.TYPE_PEA, Account.TYPE_MARKET, Account.TYPE_LIFE_INSURANCE}: + if account.type in (Account.TYPE_PEA, Account.TYPE_MARKET, Account.TYPE_LIFE_INSURANCE): account._investment_link = Link('./ul/li/a[contains(@id, "portefeuille")]')(cpt) - investment_page = self.browser.open(account._investment_link).page + investment_page = self.browser.location(account._investment_link).page balance = investment_page.get_balance(account.type) - if account.type in {Account.TYPE_PEA, Account.TYPE_MARKET}: + if account.type in (Account.TYPE_PEA, Account.TYPE_MARKET): self.browser.investments[account.id] = list(self.browser.open(account._investment_link).page.get_investments(account)) else: balance = page.get_balance() if account.type is not Account.TYPE_LOAN: account.coming = page.get_coming() - if account.type in {Account.TYPE_PEA, Account.TYPE_MARKET}: + if account.type in (Account.TYPE_PEA, Account.TYPE_MARKET): account.currency = investment_page.get_currency() elif balance: account.currency = account.get_currency(balance) - if account.type == Account.TYPE_LIFE_INSURANCE: - # Life Insurance balance uses '.' instead of ',' - account.balance = CleanDecimal.SI().filter(balance) - else: - account.balance = CleanDecimal.French().filter(balance) + + account.balance = CleanDecimal.French().filter(balance) if account.type in (Account.TYPE_CHECKING, Account.TYPE_SAVINGS): # Need a token sent by SMS to customers account.iban = NotAvailable + if account.type is not Account.TYPE_LOAN: + regexp = re.search(r'(m\. |mme\. )(.+)', CleanText('//span[has-class("mon_espace_nom")]')(self.doc), re.IGNORECASE) + if regexp and len(regexp.groups()) == 2: + gender = regexp.group(1).replace('.', '').rstrip() + name = regexp.group(2) + label = account.label + if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', label, re.IGNORECASE): + account.ownership = AccountOwnership.CO_OWNER + elif re.search(r'{} {}'.format(gender, name), label, re.IGNORECASE): + account.ownership = AccountOwnership.OWNER + else: + account.ownership = AccountOwnership.ATTORNEY + if (account.label, account.id, account.balance) not in [(a.label, a.id, a.balance) for a in accounts]: accounts.append(account) return accounts @@ -557,6 +577,11 @@ def get_subscription_date(self): def get_maturity_date(self): return Date(CleanText(u'//p[@id="c_dateFin"]//strong'), dayfirst=True)(self.doc) + def get_owner(self): + if bool(CleanText('//p[@id="c_emprunteurSecondaire"]')(self.doc)): + return AccountOwnership.CO_OWNER + return AccountOwnership.OWNER + class ProfilePage(LoggedPage, HTMLPage): def get_csv_link(self): @@ -599,4 +624,3 @@ def get_profile(self): class SecurityPage(LoggedPage, HTMLPage): pass - diff --git a/modules/fortuneo/pages/compat/weboob_capabilities_bank.py b/modules/fortuneo/pages/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/fortuneo/pages/compat/weboob_capabilities_bank.py +++ b/modules/fortuneo/pages/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/fortuneo/pages/compat/weboob_exceptions.py b/modules/fortuneo/pages/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/fortuneo/pages/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/fortuneo/pages/login.py b/modules/fortuneo/pages/login.py index e034cf49cb..01a551343e 100644 --- a/modules/fortuneo/pages/login.py +++ b/modules/fortuneo/pages/login.py @@ -20,7 +20,7 @@ from weboob.browser.pages import HTMLPage from .compat.weboob_browser_filters_standard import CleanText -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable class LoginPage(HTMLPage): diff --git a/modules/freemobile/browser.py b/modules/freemobile/browser.py index b1f9cd4f06..f4be6c7141 100644 --- a/modules/freemobile/browser.py +++ b/modules/freemobile/browser.py @@ -17,9 +17,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.capabilities.messages import CantSendMessage -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.compat import basestring from .pages import HomePage, LoginPage, HistoryPage, DetailsPage, OptionsPage, ProfilePage diff --git a/modules/freemobile/compat/__init__.py b/modules/freemobile/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/freemobile/compat/weboob_exceptions.py b/modules/freemobile/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/freemobile/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/freemobile/pages/compat/weboob_exceptions.py b/modules/freemobile/pages/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/freemobile/pages/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/freemobile/pages/history.py b/modules/freemobile/pages/history.py index 6878af25ad..e44fd1b0a3 100644 --- a/modules/freemobile/pages/history.py +++ b/modules/freemobile/pages/history.py @@ -29,7 +29,7 @@ from weboob.browser.filters.html import AbsoluteLink, Attr from weboob.capabilities.bill import DocumentTypes, Detail, Bill from weboob.capabilities.base import NotAvailable -from weboob.exceptions import ParseError +from .compat.weboob_exceptions import ParseError from weboob.tools.compat import unicode diff --git a/modules/funmooc/browser.py b/modules/funmooc/browser.py index 27867f782d..ecf099b721 100644 --- a/modules/funmooc/browser.py +++ b/modules/funmooc/browser.py @@ -19,9 +19,9 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.browser.exceptions import HTTPNotFound -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.capabilities.image import Thumbnail from .pages import PageLogin, PageDashboard, PageChapter, PageSection diff --git a/modules/funmooc/compat/weboob_exceptions.py b/modules/funmooc/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/funmooc/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ganassurances/compat/weboob_capabilities_bank.py b/modules/ganassurances/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/ganassurances/compat/weboob_capabilities_bank.py +++ b/modules/ganassurances/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/gmf/browser.py b/modules/gmf/browser.py index a2830bd929..c727f3b41f 100644 --- a/modules/gmf/browser.py +++ b/modules/gmf/browser.py @@ -19,8 +19,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import ( LoginPage, HomePage, AccountsPage, TransactionsInvestmentsPage, AllTransactionsPage, diff --git a/modules/gmf/compat/weboob_capabilities_bank.py b/modules/gmf/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/gmf/compat/weboob_capabilities_bank.py +++ b/modules/gmf/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/gmf/compat/weboob_exceptions.py b/modules/gmf/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/gmf/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/gmf/pages.py b/modules/gmf/pages.py index 370d1fefc9..2de0174f13 100644 --- a/modules/gmf/pages.py +++ b/modules/gmf/pages.py @@ -34,7 +34,7 @@ from weboob.capabilities.base import NotAvailable from .compat.weboob_tools_captcha_virtkeyboard import SimpleVirtualKeyboard from weboob.tools.capabilities.bank.transactions import FrenchTransaction -from weboob.exceptions import ActionNeeded +from .compat.weboob_exceptions import ActionNeeded class Transaction(FrenchTransaction): diff --git a/modules/groupama/browser.py b/modules/groupama/browser.py index ffaeaf3df2..786d721315 100644 --- a/modules/groupama/browser.py +++ b/modules/groupama/browser.py @@ -20,7 +20,7 @@ import re from weboob.browser.browsers import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .compat.weboob_capabilities_bank import Account from weboob.capabilities.base import empty diff --git a/modules/groupama/compat/weboob_capabilities_bank.py b/modules/groupama/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/groupama/compat/weboob_capabilities_bank.py +++ b/modules/groupama/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/groupama/compat/weboob_exceptions.py b/modules/groupama/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/groupama/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/groupamaes/compat/weboob_capabilities_bank.py b/modules/groupamaes/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/groupamaes/compat/weboob_capabilities_bank.py +++ b/modules/groupamaes/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/happn/browser.py b/modules/happn/browser.py index cf9c81ee80..43d183ccee 100644 --- a/modules/happn/browser.py +++ b/modules/happn/browser.py @@ -24,7 +24,7 @@ from weboob.browser.profiles import IPhone from weboob.browser.pages import HTMLPage from .compat.weboob_browser_filters_standard import CleanText -from weboob.exceptions import BrowserIncorrectPassword, ParseError +from .compat.weboob_exceptions import BrowserIncorrectPassword, ParseError from weboob.tools.json import json diff --git a/modules/happn/compat/weboob_exceptions.py b/modules/happn/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/happn/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/happn/module.py b/modules/happn/module.py index 918ff9f391..873d29d663 100644 --- a/modules/happn/module.py +++ b/modules/happn/module.py @@ -31,7 +31,7 @@ from weboob.capabilities.messages import CapMessages, CapMessagesPost, Thread, Message from weboob.capabilities.dating import CapDating, Optimization from weboob.capabilities.contact import CapContact, Contact, ProfileNode -from weboob.exceptions import BrowserHTTPError +from .compat.weboob_exceptions import BrowserHTTPError from weboob.tools.backend import Module, BackendConfig from weboob.tools.value import Value, ValueBackendPassword from weboob.tools.log import getLogger diff --git a/modules/hsbc/browser.py b/modules/hsbc/browser.py index 6700c3d69d..10673c78aa 100644 --- a/modules/hsbc/browser.py +++ b/modules/hsbc/browser.py @@ -25,10 +25,10 @@ from collections import OrderedDict from weboob.tools.date import LinearDateGuesser -from .compat.weboob_capabilities_bank import Account, AccountNotFound +from .compat.weboob_capabilities_bank import Account, AccountNotFound, AccountOwnership from weboob.tools.capabilities.bank.transactions import sorted_transactions, keep_only_card_transactions from weboob.tools.compat import parse_qsl, urlparse -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.browser.exceptions import HTTPNotFound from weboob.capabilities.base import find_object @@ -223,6 +223,12 @@ def iter_account_owners(self): for a in self.accounts_dict[owner].values(): a._owner = owner + # The first space is the PSU owner space + if owner == 0: + a.ownership = AccountOwnership.OWNER + else: + a.ownership = AccountOwnership.ATTORNEY + # go on cards page if there are cards accounts for a in self.accounts_dict[owner].values(): if a.type == Account.TYPE_CARD: @@ -253,6 +259,9 @@ def iter_account_owners(self): for account in owner.values(): if account.id not in self.unique_accounts_dict.keys(): self.unique_accounts_dict[account.id] = account + else: + # If an account is in multiple space, that's mean it is shared between this owners. + self.unique_accounts_dict[account.id].ownership = AccountOwnership.CO_OWNER if self.unique_accounts_dict: for account in self.unique_accounts_dict.values(): diff --git a/modules/hsbc/compat/weboob_capabilities_bank.py b/modules/hsbc/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/hsbc/compat/weboob_capabilities_bank.py +++ b/modules/hsbc/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/hsbc/compat/weboob_exceptions.py b/modules/hsbc/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/hsbc/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/hsbc/pages/account_pages.py b/modules/hsbc/pages/account_pages.py index 84a9a980b2..1319210278 100644 --- a/modules/hsbc/pages/account_pages.py +++ b/modules/hsbc/pages/account_pages.py @@ -32,7 +32,7 @@ from weboob.capabilities import NotAvailable from .compat.weboob_capabilities_bank import Account, AccountOwnerType from weboob.capabilities.profile import Person -from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.compat import urljoin from .landing_pages import GenericLandingPage diff --git a/modules/hsbc/pages/compat/weboob_capabilities_bank.py b/modules/hsbc/pages/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/hsbc/pages/compat/weboob_capabilities_bank.py +++ b/modules/hsbc/pages/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/hsbc/pages/compat/weboob_exceptions.py b/modules/hsbc/pages/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/hsbc/pages/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/hsbc/pages/investments.py b/modules/hsbc/pages/investments.py index 3f84a161ec..0ac2b8d5aa 100644 --- a/modules/hsbc/pages/investments.py +++ b/modules/hsbc/pages/investments.py @@ -19,7 +19,7 @@ from weboob.browser.filters.html import TableCell, Link from weboob.browser.filters.json import Dict from weboob.browser.filters.javascript import JSVar -from weboob.exceptions import BrowserUnavailable +from .compat.weboob_exceptions import BrowserUnavailable class LogonInvestmentPage(LoggedPage, HTMLPage): diff --git a/modules/humanis/compat/weboob_capabilities_bank.py b/modules/humanis/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/humanis/compat/weboob_capabilities_bank.py +++ b/modules/humanis/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/ideel/browser.py b/modules/ideel/browser.py index 9d1ec3239d..f06118702b 100644 --- a/modules/ideel/browser.py +++ b/modules/ideel/browser.py @@ -23,11 +23,11 @@ from decimal import Decimal from itertools import count, takewhile -from weboob.browser import URL, LoginBrowser, need_login +from weboob.browser.browsers import URL, LoginBrowser, need_login from weboob.browser.pages import HTMLPage from weboob.capabilities.base import Currency from weboob.capabilities.shop import Item, Order, OrderNotFound, Payment -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.capabilities.bank.transactions import AmericanTransaction as AmTr from weboob.tools.compat import unicode diff --git a/modules/ideel/compat/__init__.py b/modules/ideel/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/ideel/compat/weboob_exceptions.py b/modules/ideel/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ideel/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/imdb/browser.py b/modules/imdb/browser.py index 546a86cd81..9270a198d8 100644 --- a/modules/imdb/browser.py +++ b/modules/imdb/browser.py @@ -25,9 +25,9 @@ except ImportError: from html.parser import HTMLParser -from weboob.browser import PagesBrowser, URL +from weboob.browser.browsers import PagesBrowser, URL from weboob.browser.profiles import Wget -from weboob.exceptions import BrowserHTTPNotFound +from .compat.weboob_exceptions import BrowserHTTPNotFound from weboob.capabilities.base import NotAvailable, NotLoaded from weboob.capabilities.cinema import Movie, Person from weboob.tools.compat import unicode diff --git a/modules/imdb/compat/__init__.py b/modules/imdb/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/imdb/compat/weboob_exceptions.py b/modules/imdb/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/imdb/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/infomaniak/browser.py b/modules/infomaniak/browser.py index 8c3f4309f8..7c00d3cbcb 100644 --- a/modules/infomaniak/browser.py +++ b/modules/infomaniak/browser.py @@ -19,8 +19,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, SubscriptionsPage, DocumentsPage diff --git a/modules/infomaniak/compat/weboob_exceptions.py b/modules/infomaniak/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/infomaniak/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ing/api/compat/weboob_capabilities_bank.py b/modules/ing/api/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/ing/api/compat/weboob_capabilities_bank.py +++ b/modules/ing/api/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/ing/api/transfer_page.py b/modules/ing/api/transfer_page.py index f12092fa95..5743e3ee97 100644 --- a/modules/ing/api/transfer_page.py +++ b/modules/ing/api/transfer_page.py @@ -30,9 +30,7 @@ from weboob.browser.elements import method, DictElement, ItemElement from weboob.browser.filters.json import Dict from .compat.weboob_browser_filters_standard import Env, Field, Date -from .compat.weboob_capabilities_bank import ( - Recipient, RecipientInvalidIban, RecipientInvalidOTP, -) +from .compat.weboob_capabilities_bank import Recipient class TransferINGVirtKeyboard(SimpleVirtualKeyboard): @@ -168,13 +166,6 @@ def check_recipient(self, recipient): rcpt = self.doc return rcpt['accountHolderName'] == recipient.label and rcpt['iban'] == recipient.iban - def handle_error(self): - if 'error' in self.doc: - if self.doc['error']['code'] == 'EXTERNAL_ACCOUNT.IBAN_NOT_FRENCH': - # not using the bank message because it is too generic - raise RecipientInvalidIban(message="L'IBAN doit correpondre à celui d'une banque domiciliée en France.") - assert False, 'Recipient error not handled' - class OtpChannelsPage(LoggedPage, JsonPage): def get_sms_info(self): @@ -186,10 +177,4 @@ def get_sms_info(self): class ConfirmOtpPage(LoggedPage, JsonPage): - def handle_error(self): - if 'error' in self.doc: - error_code = self.doc['error']['code'] - if error_code == 'SCA.WRONG_OTP_ATTEMPT': - raise RecipientInvalidOTP(message=self.doc['error']['message']) - - assert False, 'Recipient OTP error not handled' + pass diff --git a/modules/ing/api_browser.py b/modules/ing/api_browser.py index 823de832a7..c01d3a72f9 100644 --- a/modules/ing/api_browser.py +++ b/modules/ing/api_browser.py @@ -25,12 +25,12 @@ import re from weboob.browser.browsers import LoginBrowser, URL, StatesMixin -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded from weboob.browser.exceptions import ClientError from .compat.weboob_capabilities_bank import ( TransferBankError, TransferInvalidAmount, AddRecipientStep, RecipientInvalidOTP, - AddRecipientTimeout, AddRecipientBankError, + AddRecipientTimeout, AddRecipientBankError, RecipientInvalidIban, ) from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.tools.value import Value @@ -436,23 +436,38 @@ def send_sms_to_user(self, recipient, sms_info): raise AddRecipientStep(recipient, Value('code', label='Veuillez saisir le code temporaire envoyé par SMS')) def handle_recipient_error(self, r): + # The bank gives an error message when an error occures. + # But sometimes the message is not relevant. + # So I may replace it by nothing or by a custom message. + # The exception to raise can be coupled with: + # * Nothing: empty message + # * None: message of the bank + # * String: custom message + RECIPIENT_ERROR = { + 'SENSITIVE_OPERATION.SENSITIVE_OPERATION_NOT_FOUND': (AddRecipientTimeout,), + 'SENSITIVE_OPERATION.EXPIRED_TEMPORARY_CODE': (AddRecipientTimeout, None), + 'EXTERNAL_ACCOUNT.EXTERNAL_ACCOUNT_ALREADY_EXISTS': (AddRecipientBankError, None), + 'EXTERNAL_ACCOUNT.ACCOUNT_RESTRICTION': (AddRecipientBankError, None), + 'EXTERNAL_ACCOUNT.EXTERNAL_ACCOUNT_IS_INTERNAL_ACCOUT': (AddRecipientBankError, None), # nice spelling + 'EXTERNAL_ACCOUNT.IBAN_NOT_FRENCH': (RecipientInvalidIban, "L'IBAN doit correpondre à celui d'une banque domiciliée en France."), + 'SCA.WRONG_OTP_ATTEMPT': (RecipientInvalidOTP, None), + 'INPUT_INVALID': (AssertionError, None), # invalid request + } + error_page = r.response.json() if 'error' in error_page: error = error_page['error'] - # the error message may seem generic - # but after testing multiple cases - # it is the only time that it appears - if error['code'] == 'SENSITIVE_OPERATION.SENSITIVE_OPERATION_NOT_FOUND': - raise AddRecipientTimeout() - elif error['code'] in ( - 'EXTERNAL_ACCOUNT.EXTERNAL_ACCOUNT_ALREADY_EXISTS', - # not allowed to add a recipient - 'EXTERNAL_ACCOUNT.ACCOUNT_RESTRICTION', - ): - raise AddRecipientBankError(message=error['message']) + error_exception = RECIPIENT_ERROR.get(error['code']) + if error_exception: + if len(error_exception) == 1: + raise error_exception[0]() + elif error_exception[1] is None: + raise error_exception[0](message=error['message']) + else: + raise error_exception[0](message=error_exception[1]) - assert False, 'Recipient error not handled' + assert False, 'Recipient error "%s" not handled' % error['code'] @need_login def end_sms_recipient(self, recipient, code): @@ -477,8 +492,6 @@ def end_sms_recipient(self, recipient, code): self.handle_recipient_error(e) raise - self.page.handle_error() - @need_login @need_to_be_on_website('api') def new_recipient(self, recipient, **params): @@ -507,8 +520,6 @@ def new_recipient(self, recipient, **params): self.handle_recipient_error(e) raise - self.page.handle_error() - assert self.page.check_recipient(recipient), "The recipients don't match." self.add_recipient_info = self.page.doc diff --git a/modules/ing/browser.py b/modules/ing/browser.py index b648a4e609..63acf73bfa 100644 --- a/modules/ing/browser.py +++ b/modules/ing/browser.py @@ -27,7 +27,7 @@ from requests.exceptions import SSLError from weboob.browser.browsers import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserUnavailable, BrowserHTTPNotFound +from .compat.weboob_exceptions import BrowserUnavailable, BrowserHTTPNotFound from weboob.browser.exceptions import ServerError from .compat.weboob_capabilities_bank import Account, AccountNotFound from weboob.capabilities.base import find_object, NotAvailable diff --git a/modules/ing/compat/weboob_capabilities_bank.py b/modules/ing/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/ing/compat/weboob_capabilities_bank.py +++ b/modules/ing/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/ing/compat/weboob_exceptions.py b/modules/ing/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ing/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ing/pages/compat/__init__.py b/modules/ing/pages/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/ing/pages/login.py b/modules/ing/pages/login.py index 1be8b63880..8e29208b7e 100644 --- a/modules/ing/pages/login.py +++ b/modules/ing/pages/login.py @@ -19,7 +19,7 @@ from io import BytesIO -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded from .compat.weboob_tools_captcha_virtkeyboard import VirtKeyboard from weboob.browser.pages import HTMLPage, LoggedPage from weboob.browser.filters.html import Attr diff --git a/modules/ing/web/compat/weboob_capabilities_bank.py b/modules/ing/web/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/ing/web/compat/weboob_capabilities_bank.py +++ b/modules/ing/web/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/ing/web/compat/weboob_exceptions.py b/modules/ing/web/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ing/web/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ing/web/login.py b/modules/ing/web/login.py index e55dc1b07b..fd2b24b537 100644 --- a/modules/ing/web/login.py +++ b/modules/ing/web/login.py @@ -18,7 +18,7 @@ # along with weboob. If not, see . -from weboob.exceptions import ActionNeeded +from .compat.weboob_exceptions import ActionNeeded from weboob.browser.pages import HTMLPage, LoggedPage from .compat.weboob_browser_filters_standard import CleanText diff --git a/modules/kiwibank/browser.py b/modules/kiwibank/browser.py index 63667e3913..e6a10c5f30 100644 --- a/modules/kiwibank/browser.py +++ b/modules/kiwibank/browser.py @@ -18,8 +18,8 @@ # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, AccountPage, HistoryPage diff --git a/modules/kiwibank/compat/weboob_capabilities_bank.py b/modules/kiwibank/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/kiwibank/compat/weboob_capabilities_bank.py +++ b/modules/kiwibank/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/kiwibank/compat/weboob_exceptions.py b/modules/kiwibank/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/kiwibank/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/lampiris/browser.py b/modules/lampiris/browser.py index 942abe676e..25e0ef6de0 100644 --- a/modules/lampiris/browser.py +++ b/modules/lampiris/browser.py @@ -20,9 +20,9 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.browser.exceptions import ClientError -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, BillsPage diff --git a/modules/lampiris/compat/weboob_exceptions.py b/modules/lampiris/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/lampiris/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/lcl/browser.py b/modules/lcl/browser.py index 2de5a43d53..5f23e8fc45 100644 --- a/modules/lcl/browser.py +++ b/modules/lcl/browser.py @@ -24,7 +24,7 @@ from datetime import datetime, timedelta, date from functools import wraps -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin from weboob.browser.exceptions import ServerError from weboob.capabilities.base import NotAvailable @@ -39,7 +39,7 @@ AVPage, AVDetailPage, DiscPage, NoPermissionPage, RibPage, HomePage, LoansPage, TransferPage, AddRecipientPage, RecipientPage, RecipConfirmPage, SmsPage, RecipRecapPage, LoansProPage, Form2Page, DocumentsPage, ClientPage, SendTokenPage, CaliePage, ProfilePage, DepositPage, - AVHistoryPage, AVInvestmentsPage, CardsPage, AVListPage, + AVHistoryPage, AVInvestmentsPage, CardsPage, AVListPage, CalieContractsPage, ) @@ -91,13 +91,19 @@ class LCLBrowser(LoginBrowser, StatesMixin): form2 = URL(r'/outil/UWVI/Routage', Form2Page) send_token = URL('/outil/UWVI/AssuranceVie/envoyerJeton', SendTokenPage) - calie = URL('https://www.my-calie.fr/FO.HoldersWebSite/Disclaimer/Disclaimer.aspx.*', - 'https://www.my-calie.fr/FO.HoldersWebSite/Contract/ContractDetails.aspx.*', - 'https://www.my-calie.fr/FO.HoldersWebSite/Contract/ContractOperations.aspx.*', CaliePage) - - assurancevie = URL('/outil/UWVI/AssuranceVie/accesSynthese', - '/outil/UWVI/AssuranceVie/accesDetail.*', - AVPage) + calie_detail = URL( + r'https://www.my-calie.fr/FO.HoldersWebSite/Disclaimer/Disclaimer.aspx.*', + r'https://www.my-calie.fr/FO.HoldersWebSite/Contract/ContractDetails.aspx.*', + r'https://www.my-calie.fr/FO.HoldersWebSite/Contract/ContractOperations.aspx.*', + CaliePage + ) + calie_contracts = URL(r'https://www.my-calie.fr/FO.HoldersWebSite/Contract/SearchContract.aspx', CalieContractsPage) + + assurancevie = URL( + r'/outil/UWVI/AssuranceVie/accesSynthese', + r'/outil/UWVI/AssuranceVie/accesDetail.*', + AVPage + ) av_list = URL('https://assurance-vie-et-prevoyance.secure.lcl.fr/rest/assurance/synthesePartenaire', AVListPage) avdetail = URL('https://assurance-vie-et-prevoyance.secure.lcl.fr/consultation/epargne', AVDetailPage) @@ -248,10 +254,36 @@ def get_accounts(self): if self.no_perm.is_here(): self.logger.warning('Life insurances are unavailable.') else: + # retrieve life insurances from popups for a in self.page.get_popup_life_insurance(): self.update_accounts(a) - # retrieve life insurance on special lcl life insurance website + # retrieve life insurances from calie website + calie_index = self.page.get_calie_life_insurances_first_index() + if calie_index: + form = self.page.get_form(id="formRedirectPart") + form['INDEX'] = calie_index + form.submit() + # if only one calie insurance, request directly leads to details on CaliePage + if self.calie_detail.is_here(): + self.page.check_error() + a = Account() + a.url = self.url + self.page.fill_account(obj=a) + self.update_accounts(a) + # if several calie insurances, request leads to CalieContractsPage + elif self.calie_contracts.is_here(): + for a in self.page.iter_calie_life_insurance(): + if a.url: + self.location(a.url) + self.page.fill_account(obj=a) + self.update_accounts(a) + else: + self.logger.warning('%s has no url to parse detail to' % a) + # get back to life insurances list page + self.assurancevie.stay_or_go() + + # retrieve life insurances on special lcl life insurance website if self.page.is_website_life_insurance(): self.go_life_insurance_website() for life_insurance in self.page.iter_life_insurance(): @@ -381,21 +413,20 @@ def get_history(self, account): self.logger.warning('This account is limited, there is no available history.') return - self.assurancevie.stay_or_go() - self.go_life_insurance_website() - - if self.calie.is_here(): - # Get back to Synthèse - self.assurancevie.go() - return - - assert self.av_list.is_here(), 'Something went wrong during iter life insurance history' - # Need to be on account details page to do history request - self.av_investments.go(life_insurance_id=account.id) - self.av_history.go() - for tr in self.page.iter_history(): - yield tr - self.go_back_from_life_insurance_website() + if account._is_calie_account: + # TODO build parsing of history page, all-you-can-eat js in it + # follow 'account._history_url' for that + raise NotImplementedError() + else: + self.assurancevie.stay_or_go() + self.go_life_insurance_website() + assert self.av_list.is_here(), 'Something went wrong during iter life insurance history' + # Need to be on account details page to do history request + self.av_investments.go(life_insurance_id=account.id) + self.av_history.go() + for tr in self.page.iter_history(): + yield tr + self.go_back_from_life_insurance_website() @need_login def get_coming(self, account): @@ -453,20 +484,17 @@ def get_investment(self, account): return self.assurancevie.stay_or_go() - self.go_life_insurance_website() - - if self.calie.is_here(): + if account._is_calie_account: + calie_details = self.open(account.url) + for inv in calie_details.page.iter_investment(): + yield inv + else: + self.go_life_insurance_website() + assert self.av_list.is_here(), 'Something went wrong during iter life insurance investments' + self.av_investments.go(life_insurance_id=account.id) for inv in self.page.iter_investment(): yield inv - # Get back to Life Insurance space - self.assurancevie.go() - return - - assert self.av_list.is_here(), 'Something went wrong during iter life insurance investments' - self.av_investments.go(life_insurance_id=account.id) - for inv in self.page.iter_investment(): - yield inv - self.go_back_from_life_insurance_website() + self.go_back_from_life_insurance_website() elif hasattr(account, '_market_link') and account._market_link: self.connexion_bourse() diff --git a/modules/lcl/compat/weboob_capabilities_bank.py b/modules/lcl/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/lcl/compat/weboob_capabilities_bank.py +++ b/modules/lcl/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/lcl/compat/weboob_exceptions.py b/modules/lcl/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/lcl/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/lcl/enterprise/browser.py b/modules/lcl/enterprise/browser.py index 331cb53788..9a516d9add 100644 --- a/modules/lcl/enterprise/browser.py +++ b/modules/lcl/enterprise/browser.py @@ -19,7 +19,7 @@ from weboob.browser.browsers import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .compat.weboob_capabilities_bank import AccountOwnerType from .pages import LoginPage, MovementsPage, ProfilePage, PassExpiredPage diff --git a/modules/lcl/enterprise/compat/weboob_capabilities_bank.py b/modules/lcl/enterprise/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/lcl/enterprise/compat/weboob_capabilities_bank.py +++ b/modules/lcl/enterprise/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/lcl/enterprise/compat/weboob_exceptions.py b/modules/lcl/enterprise/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/lcl/enterprise/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/lcl/enterprise/pages.py b/modules/lcl/enterprise/pages.py index 37aacb5cdf..75b7cb0176 100644 --- a/modules/lcl/enterprise/pages.py +++ b/modules/lcl/enterprise/pages.py @@ -27,7 +27,7 @@ from .compat.weboob_capabilities_bank import Account from weboob.capabilities.profile import Profile from weboob.capabilities.base import NotAvailable -from weboob.exceptions import BrowserPasswordExpired +from .compat.weboob_exceptions import BrowserPasswordExpired from ..pages import Transaction diff --git a/modules/lcl/pages.py b/modules/lcl/pages.py index a55ef7714d..4d485c84bb 100644 --- a/modules/lcl/pages.py +++ b/modules/lcl/pages.py @@ -27,8 +27,7 @@ from io import BytesIO from datetime import datetime, timedelta -from weboob.capabilities import NotAvailable -from weboob.capabilities.base import find_object +from weboob.capabilities.base import empty, find_object, NotAvailable from .compat.weboob_capabilities_bank import ( Account, Investment, Recipient, TransferError, TransferBankError, Transfer, ) @@ -36,20 +35,19 @@ from weboob.capabilities.profile import Person, ProfileMissing from weboob.capabilities.contact import Advisor from weboob.browser.elements import method, ListElement, TableElement, ItemElement, DictElement -from weboob.exceptions import ParseError +from .compat.weboob_exceptions import ParseError, ActionNeeded from weboob.browser.exceptions import ServerError from weboob.browser.pages import LoggedPage, HTMLPage, JsonPage, FormNotFound, pagination -from weboob.browser.filters.html import Attr, Link, TableCell, AttributeNotFound +from weboob.browser.filters.html import Attr, Link, TableCell, AttributeNotFound, AbsoluteLink from .compat.weboob_browser_filters_standard import ( CleanText, Field, Regexp, Format, Date, CleanDecimal, Map, AsyncLoad, Async, Env, Slugify, BrowserURL, Eval, Currency, ) from weboob.browser.filters.json import Dict -from weboob.exceptions import BrowserUnavailable, BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserUnavailable, BrowserIncorrectPassword from weboob.tools.capabilities.bank.transactions import FrenchTransaction from .compat.weboob_tools_captcha_virtkeyboard import MappedVirtKeyboard, VirtKeyboardError -from weboob.tools.compat import unicode -from weboob.tools.compat import urlparse, parse_qs +from weboob.tools.compat import unicode, urlparse, parse_qs, urljoin from weboob.tools.html import html2text from weboob.tools.date import parse_french_date from weboob.tools.capabilities.bank.investments import is_isin_valid @@ -792,6 +790,16 @@ def is_website_life_insurance(self): # because we just need to go on life insurance external website return bool(self.get_routage_url()) + def get_calie_life_insurances_first_index(self): + # indices are associated to calie life insurances to make requests to them + # if only one life insurance, this request directly leads to details on CaliePage + # otherwise, any index will lead to CalieContractsPage, + # so we stop at the first index + for account in self.doc.xpath('//table[@class]/tbody/tr'): + if account.xpath('.//td[has-class("nomContrat")]//a[contains(@class, "redirect")][@href="#"]'): + index = Attr(account.xpath('.//td[has-class("nomContrat")]//a[contains(@class, "redirect")][@href="#"]'), 'id')(self) + return index + @method class get_popup_life_insurance(ListElement): item_xpath = '//table[@class]/tbody/tr' @@ -817,6 +825,7 @@ def condition(self): obj__transfer_id = None obj_number = Field('id') obj__external_website = False + obj__is_calie_account = False def obj_id(self): _id = CleanText('.//td/@id')(self) @@ -875,6 +884,24 @@ def obj__form(self): return form +class CalieContractsPage(LoggedPage, HTMLPage): + @method + class iter_calie_life_insurance(TableElement): + head_xpath = '//table[contains(@id, "MainTable")]//tr[contains(@id, "HeadersRow")]//td[text()]' + item_xpath = '//table[contains(@id, "MainTable")]//tr[contains(@id, "DataRow")]' + + col_number = 'Numéro contrat' # internal contrat number + + class item(ItemElement): + klass = Account + + # internal contrat number, to be replaced by external number in CaliePage.fill_account() + # obj_id is needed here though, to avoid dupicate account errors + obj_id = CleanText(TableCell('number')) + + obj_url = AbsoluteLink('.//a') # need AbsoluteLink since we moved out of basurl domain + + class SendTokenPage(LoggedPage, LCLBasePage): def on_load(self): form = self.get_form('//form') @@ -904,17 +931,17 @@ def get_colnum(self, name): class CaliePage(LoggedPage, HTMLPage): - def on_load(self): - # TODO raise the ActionNeeded when backend handles it. - if self.doc.xpath('//button[@id="acceptDisclaimerButton"]'): - self.logger.warning('Action Needed on website: %s', CleanText('//div[@class="data-header"]')(self.doc)) + def check_error(self): + message = CleanText('//div[contains(@class, "disclaimer-div")]//text()[contains(., "utilisation vaut acceptation")]')(self.doc) + if self.doc.xpath('//button[@id="acceptDisclaimerButton"]') and message: + raise ActionNeeded(message) @method class iter_investment(CalieTableElement): # Careful, contains many nested
# Two first lines are titles, two last are investment sum-ups - item_xpath = '//table[@class="dxgvTable dxgvRBB"]//tr[position() > 2 and position() < last()-1]' - head_xpath = '//table[@class="dxgvTable dxgvRBB"]//tr[1]/td//tr/td[1]' + item_xpath = '//table[@class="dxgvTable dxgvRBB"]//tr[contains(@class, "DataRow")]' + head_xpath = '//table[contains(@id, "MainTable")]//tr[contains(@id, "HeadersRow")]//td[text()]' col_label = 'Support' col_vdate = 'Date de valeur' @@ -922,7 +949,7 @@ class iter_investment(CalieTableElement): col_valuation = 'Valeur dans la devise du support (EUR)' col_unitvalue = 'Valeur unitaire' col_quantity = 'Parts' - col_diff = 'Performance' + col_diff_ratio = 'Performance' col_portfolio_share = 'Répartition (%)' class item(ItemElement): @@ -932,16 +959,40 @@ class item(ItemElement): obj_original_valuation = CleanDecimal(TableCell('original_valuation'), replace_dots=True) obj_valuation = CleanDecimal(TableCell('valuation'), replace_dots=True) obj_vdate = Date(CleanText(TableCell('vdate')), dayfirst=True) - obj_unitvalue = CleanDecimal(TableCell('unitvalue'), replace_dots=True) - obj_quantity = CleanDecimal(TableCell('quantity'), replace_dots=True) + obj_unitvalue = CleanDecimal(TableCell('unitvalue'), replace_dots=True, default=NotAvailable) # displayed with format '123.456,78 EUR' + obj_quantity = CleanDecimal(TableCell('quantity'), replace_dots=True, default=NotAvailable) # displayed with format '1.234,5678 u.' obj_portfolio_share = Eval(lambda x: x / 100, CleanDecimal(TableCell('portfolio_share'))) - obj_diff = CleanDecimal(TableCell('diff')) + + def obj_diff_ratio(self): + _diff_ratio = CleanDecimal(TableCell('diff_ratio'), default=NotAvailable)(self) + if not empty(_diff_ratio): + return Eval(lambda x: x / 100, _diff_ratio)(self) + return NotAvailable # Unfortunately on the Calie space the links to the # invest details return Forbidden even on the website obj_code = NotAvailable obj_code_type = NotAvailable + @method + class fill_account(ItemElement): + obj_number = obj_id = Regexp(CleanText('.'), r'Numéro externe (.{10})') + obj_label = Format( + '%s %s', + Regexp(CleanText('.'), r'Produit (.*) Statut'), + Field('id') + ) + obj_balance = CleanDecimal('//tr[contains(@id, "FooterRow")]', replace_dots=True) + obj_type = Account.TYPE_LIFE_INSURANCE + obj_currency = 'EUR' + obj__external_website = True + obj__is_calie_account = True + obj__transfer_id = None + + def obj__history_url(self): + relative_url = Regexp(Attr('//a[contains(text(), "Opérations")]', 'onclick'), r'href=\'(.*)\'')(self) + return urljoin(self.page.url, relative_url) + class AVDetailPage(LoggedPage, LCLBasePage): def come_back(self): @@ -987,6 +1038,7 @@ def condition(self): obj__market_link = None obj__coming_links = [] obj__transfer_id = None + obj__is_calie_account = False class AVHistoryPage(LoggedPage, JsonPage): diff --git a/modules/ldlc/browser.py b/modules/ldlc/browser.py index fb1102abf5..a1b486e1a5 100644 --- a/modules/ldlc/browser.py +++ b/modules/ldlc/browser.py @@ -17,8 +17,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, AbstractBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword, NocaptchaQuestion +from weboob.browser.browsers import LoginBrowser, AbstractBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword, NocaptchaQuestion from .pages import HomePage, LoginPage, ProBillsPage, DocumentsPage diff --git a/modules/ldlc/compat/weboob_exceptions.py b/modules/ldlc/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ldlc/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ldlc/materielnet_pages.py b/modules/ldlc/materielnet_pages.py index 5deb713b6e..1724671d1b 100644 --- a/modules/ldlc/materielnet_pages.py +++ b/modules/ldlc/materielnet_pages.py @@ -27,7 +27,7 @@ from weboob.browser.filters.html import Attr, Link from weboob.capabilities.bill import DocumentTypes, Bill, Subscription from weboob.capabilities.base import NotAvailable -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword class LoginPage(PartialHTMLPage): diff --git a/modules/lendosphere/compat/weboob_capabilities_bank.py b/modules/lendosphere/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/lendosphere/compat/weboob_capabilities_bank.py +++ b/modules/lendosphere/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/lendosphere/compat/weboob_exceptions.py b/modules/lendosphere/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/lendosphere/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/lendosphere/pages.py b/modules/lendosphere/pages.py index 0ea4c454c4..313ea3249b 100644 --- a/modules/lendosphere/pages.py +++ b/modules/lendosphere/pages.py @@ -27,7 +27,7 @@ from weboob.browser.pages import HTMLPage, CsvPage, LoggedPage from weboob.capabilities.base import NotAvailable from .compat.weboob_capabilities_bank import Account, Transaction -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword MAIN_ID = '_lendosphere_' diff --git a/modules/linebourse/api/compat/weboob_capabilities_bank.py b/modules/linebourse/api/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/linebourse/api/compat/weboob_capabilities_bank.py +++ b/modules/linebourse/api/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/linebourse/browser.py b/modules/linebourse/browser.py index b6622d3194..5366d9a27c 100644 --- a/modules/linebourse/browser.py +++ b/modules/linebourse/browser.py @@ -21,8 +21,8 @@ import time -from weboob.browser import LoginBrowser, URL -from weboob.exceptions import BrowserUnavailable +from weboob.browser.browsers import LoginBrowser, URL +from .compat.weboob_exceptions import BrowserUnavailable from .pages import ( MessagePage, InvestmentPage, HistoryPage, BrokenPage, diff --git a/modules/linebourse/compat/weboob_capabilities_bank.py b/modules/linebourse/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/linebourse/compat/weboob_capabilities_bank.py +++ b/modules/linebourse/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/linebourse/compat/weboob_exceptions.py b/modules/linebourse/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/linebourse/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/linebourse/pages.py b/modules/linebourse/pages.py index 83c1a23002..29815c2600 100644 --- a/modules/linebourse/pages.py +++ b/modules/linebourse/pages.py @@ -32,7 +32,7 @@ from weboob.tools.capabilities.bank.transactions import FrenchTransaction as Transaction from weboob.tools.capabilities.bank.investments import create_french_liquidity from weboob.tools.compat import quote_plus -from weboob.exceptions import ActionNeeded +from .compat.weboob_exceptions import ActionNeeded def MyDecimal(*args, **kwargs): diff --git a/modules/lolix/compat/__init__.py b/modules/lolix/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/lucca/browser.py b/modules/lucca/browser.py index 2792bc4061..a07316fbe7 100644 --- a/modules/lucca/browser.py +++ b/modules/lucca/browser.py @@ -21,9 +21,9 @@ from datetime import timedelta -from weboob.browser import LoginBrowser, need_login, URL +from weboob.browser.browsers import LoginBrowser, need_login, URL from weboob.browser.exceptions import ClientError -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.date import new_datetime from .pages import ( diff --git a/modules/lucca/compat/__init__.py b/modules/lucca/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/lucca/compat/weboob_exceptions.py b/modules/lucca/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/lucca/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/materielnet/browser.py b/modules/materielnet/browser.py index 890d446f2c..1768454c9c 100644 --- a/modules/materielnet/browser.py +++ b/modules/materielnet/browser.py @@ -20,8 +20,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword, NocaptchaQuestion +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword, NocaptchaQuestion from .pages import LoginPage, CaptchaPage, ProfilePage, DocumentsPage, DocumentsDetailsPage diff --git a/modules/materielnet/compat/weboob_exceptions.py b/modules/materielnet/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/materielnet/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/materielnet/pages.py b/modules/materielnet/pages.py index 5deb713b6e..1724671d1b 100644 --- a/modules/materielnet/pages.py +++ b/modules/materielnet/pages.py @@ -27,7 +27,7 @@ from weboob.browser.filters.html import Attr, Link from weboob.capabilities.bill import DocumentTypes, Bill, Subscription from weboob.capabilities.base import NotAvailable -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword class LoginPage(PartialHTMLPage): diff --git a/modules/mediawiki/browser.py b/modules/mediawiki/browser.py index 7da971d929..f425c4f916 100644 --- a/modules/mediawiki/browser.py +++ b/modules/mediawiki/browser.py @@ -24,7 +24,7 @@ import dateutil.parser from weboob.browser.browsers import DomainBrowser -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.capabilities.content import Revision from weboob.tools.compat import urlsplit, urljoin, basestring diff --git a/modules/mediawiki/compat/__init__.py b/modules/mediawiki/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/mediawiki/compat/weboob_exceptions.py b/modules/mediawiki/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/mediawiki/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/myedenred/browser.py b/modules/myedenred/browser.py index 363ff03ab1..c45339b903 100644 --- a/modules/myedenred/browser.py +++ b/modules/myedenred/browser.py @@ -21,8 +21,8 @@ from datetime import timedelta -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.date import LinearDateGuesser from .pages import LoginPage, AccountsPage, AccountDetailsPage, TransactionsPage diff --git a/modules/myedenred/compat/weboob_capabilities_bank.py b/modules/myedenred/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/myedenred/compat/weboob_capabilities_bank.py +++ b/modules/myedenred/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/myedenred/compat/weboob_exceptions.py b/modules/myedenred/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/myedenred/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/myfoncia/browser.py b/modules/myfoncia/browser.py index 3c6f029d83..55a0386ee4 100644 --- a/modules/myfoncia/browser.py +++ b/modules/myfoncia/browser.py @@ -20,8 +20,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, need_login, URL -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, need_login, URL +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, MonBienPage, MesChargesPage diff --git a/modules/myfoncia/compat/weboob_exceptions.py b/modules/myfoncia/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/myfoncia/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/myhabit/browser.py b/modules/myhabit/browser.py index 1f3e4b88c2..8b020b519a 100644 --- a/modules/myhabit/browser.py +++ b/modules/myhabit/browser.py @@ -23,11 +23,11 @@ from requests.exceptions import Timeout -from weboob.browser import URL, LoginBrowser, need_login +from weboob.browser.browsers import URL, LoginBrowser, need_login from weboob.browser.pages import HTMLPage from weboob.capabilities.base import Currency from weboob.capabilities.shop import Item, Order, OrderNotFound, Payment -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.capabilities.bank.transactions import AmericanTransaction as AmTr from weboob.tools.compat import unicode diff --git a/modules/myhabit/compat/__init__.py b/modules/myhabit/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/myhabit/compat/weboob_exceptions.py b/modules/myhabit/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/myhabit/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/n26/browser.py b/modules/n26/browser.py index 2f5af884b9..cd46bcb0bd 100644 --- a/modules/n26/browser.py +++ b/modules/n26/browser.py @@ -25,7 +25,7 @@ from weboob.capabilities.base import find_object, NotAvailable from .compat.weboob_capabilities_bank import Account, Transaction, AccountNotFound from .compat.weboob_browser_filters_standard import CleanText -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable from weboob.browser.exceptions import ClientError # Do not use an APIBrowser since APIBrowser sends all its requests bodies as diff --git a/modules/n26/compat/weboob_capabilities_bank.py b/modules/n26/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/n26/compat/weboob_capabilities_bank.py +++ b/modules/n26/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/n26/compat/weboob_exceptions.py b/modules/n26/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/n26/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/nalo/compat/weboob_capabilities_bank.py b/modules/nalo/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/nalo/compat/weboob_capabilities_bank.py +++ b/modules/nalo/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/nef/browser.py b/modules/nef/browser.py index cda7704d6b..f026ec46af 100644 --- a/modules/nef/browser.py +++ b/modules/nef/browser.py @@ -21,8 +21,8 @@ import datetime -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, HomePage, AccountsPage, RecipientsPage, TransactionsPage diff --git a/modules/nef/compat/weboob_capabilities_bank.py b/modules/nef/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/nef/compat/weboob_capabilities_bank.py +++ b/modules/nef/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/nef/compat/weboob_exceptions.py b/modules/nef/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/nef/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/netfinca/browser.py b/modules/netfinca/browser.py index 34afddbdfb..b27cdd75b6 100644 --- a/modules/netfinca/browser.py +++ b/modules/netfinca/browser.py @@ -20,8 +20,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL -from weboob.exceptions import BrowserUnavailable +from weboob.browser.browsers import LoginBrowser, URL +from .compat.weboob_exceptions import BrowserUnavailable from .pages import InvestmentsPage, AccountsPage diff --git a/modules/netfinca/compat/weboob_capabilities_bank.py b/modules/netfinca/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/netfinca/compat/weboob_capabilities_bank.py +++ b/modules/netfinca/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/netfinca/compat/weboob_exceptions.py b/modules/netfinca/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/netfinca/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/netfinca/pages.py b/modules/netfinca/pages.py index 992042ae15..0cc638a2a2 100644 --- a/modules/netfinca/pages.py +++ b/modules/netfinca/pages.py @@ -71,6 +71,8 @@ def obj_id(self): _id = tablecell.xpath('./div[position()=2]') return CleanText(_id)(self) + obj_number = obj_id + def obj_label(self): tablecell = TableCell('label')(self)[0] label = tablecell.xpath('./div[position()=1]') diff --git a/modules/okc/browser.py b/modules/okc/browser.py index bddc12dcf3..bed55c41cd 100644 --- a/modules/okc/browser.py +++ b/modules/okc/browser.py @@ -23,7 +23,7 @@ from weboob.browser.browsers import DomainBrowser from weboob.browser.pages import HTMLPage from .compat.weboob_browser_filters_standard import CleanText -from weboob.exceptions import BrowserIncorrectPassword, ParseError +from .compat.weboob_exceptions import BrowserIncorrectPassword, ParseError from weboob.tools.json import json __all__ = ['OkCBrowser'] diff --git a/modules/okc/compat/weboob_exceptions.py b/modules/okc/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/okc/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/oney/browser.py b/modules/oney/browser.py index a7f5ee4f53..1f13893666 100644 --- a/modules/oney/browser.py +++ b/modules/oney/browser.py @@ -17,17 +17,21 @@ # 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 datetime import date, timedelta from dateutil.relativedelta import relativedelta from itertools import chain -from weboob.exceptions import BrowserIncorrectPassword -from weboob.browser import LoginBrowser, URL, need_login +from .compat.weboob_capabilities_bank import Account +from .compat.weboob_exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.tools.date import new_date from .pages import ( LoginPage, ClientPage, OperationsPage, ChoicePage, CreditHome, CreditAccountPage, CreditHistory, LastHistoryPage, + ContextInitPage, SendUsernamePage, SendPasswordPage, CheckTokenPage, ) __all__ = ['OneyBrowser'] @@ -36,19 +40,29 @@ class OneyBrowser(LoginBrowser): BASEURL = 'https://www.oney.fr' - login = URL(r'/site/s/login/login.html', LoginPage) + home_login = URL(r'/site/s/login/login.html', + LoginPage) + login = URL(r'https://login.oney.fr/login', + r'https://login.oney.fr/context', + LoginPage) + + send_username = URL(r'https://login.oney.fr/middle/authenticationflowinit', SendUsernamePage) + send_password = URL(r'https://login.oney.fr/middle/completeauthflowstep', SendPasswordPage) + context_init = URL(r'https://login.oney.fr/middle/context', ContextInitPage) - choice = URL(r'/site/s/multimarque/choixsite.html', ChoicePage) + check_token = URL(r'https://login.oney.fr/middle/check_token', CheckTokenPage) + + choice = URL(r'/site/s/multimarque/choixsite.html', ChoicePage) choice_portal = URL(r'/site/s/login/loginidentifiant.html') - client = URL(r'/oney/client', ClientPage) - operations = URL(r'/oney/client', OperationsPage) - card_page = URL(r'/oney/client\?task=Synthese&process=SyntheseMultiCompte&indexSelectionne=(?P\d+)') + client = URL(r'/oney/client', ClientPage) + operations = URL(r'/oney/client', OperationsPage) + card_page = URL(r'/oney/client\?task=Synthese&process=SyntheseMultiCompte&indexSelectionne=(?P\d+)') credit_home = URL(r'/site/s/detailcompte/detailcompte.html', CreditHome) credit_info = URL(r'/site/s/detailcompte/ongletdetailcompte.html', CreditAccountPage) credit_hist = URL(r'/site/s/detailcompte/exportoperations.html', CreditHistory) - last_hist = URL(r'/site/s/detailcompte/ongletdernieresoperations.html', LastHistoryPage) + last_hist = URL(r'/site/s/detailcompte/ongletdernieresoperations.html', LastHistoryPage) has_oney = False has_other = False @@ -57,9 +71,42 @@ class OneyBrowser(LoginBrowser): def do_login(self): self.session.cookies.clear() - self.login.go() + self.home_login.go(method="POST") + context_token = self.page.get_context_token() + assert context_token is not None, "Should not have context_token=None" - self.page.login(self.username, self.password) + self.context_init.go(params={'contextToken': context_token}) + success_url = self.page.get_success_url() + customer_session_id = self.page.get_customer_session_id() + + self.session.headers.update({'Client-id': self.page.get_client_id()}) + + # There is a VK on the website but it does not encode the password + self.login.go() + self.send_username.go(json={ + 'authentication_type': 'LIGHT', + 'authentication_factor': { + 'public_value': self.username, + 'type': 'IAD', + } + }) + + flow_id = self.page.get_flow_id() + + self.send_password.go(json={ + 'flow_id': flow_id, + 'step_type': 'IAD_ACCESS_CODE', + 'value': self.password, + }) + + self.page.check_error() + token = self.page.get_token() + + self.check_token.go(params={'token': token}) + self.location(success_url, params={ + 'token': token, + 'customer_session_id': customer_session_id, + }) if self.choice.is_here(): self.has_other = self.has_oney = True @@ -97,10 +144,16 @@ def get_accounts_list(self): if self.has_other: self.go_site('other') - self.credit_home.stay_or_go() - self.card_name = self.page.get_name() - self.credit_info.go() - accounts.append(self.page.get_account()) + for acc_id in self.page.get_accounts_ids(): + self.credit_home.go(data={'numeroCompte': acc_id}) + label = self.page.get_label() + if 'prêt' in label.lower(): + acc = self.page.get_loan() + else: + self.credit_info.go() + acc = self.page.get_account() + acc.label = label + accounts.append(acc) if self.has_oney: self.go_site('oney') @@ -133,7 +186,7 @@ def _build_hist_form(self, last_months=False): form['anneeFin'] = str(d.year) form['typeOpe'] = 'deux' - form['formatFichier'] = 'xls' # or pdf... great choice + form['formatFichier'] = 'xls' # or pdf... great choice return form @need_login @@ -148,8 +201,10 @@ def iter_history(self, account): for tr in self.page.iter_transactions(seen=set()): yield tr - elif account._site == 'other': - if self.last_hist.go().has_transactions(): + elif account._site == 'other' and account.type != Account.TYPE_LOAN: + self.credit_home.go(data={'numeroCompte': account.id}) + self.last_hist.go() + if self.page.has_transactions(): # transactions are missing from the xls from 2016 to today # so two requests are needed d = date.today() @@ -176,10 +231,12 @@ def iter_coming(self, account): for tr in self.page.iter_transactions(seen=set()): yield tr - elif account._site == 'other': - if self.last_hist.go().has_transactions(): + elif account._site == 'other' and account.type != Account.TYPE_LOAN: + self.credit_home.go(data={'numeroCompte': account.id}) + self.last_hist.go() + if self.page.has_transactions(): self.credit_hist.go(params=self._build_hist_form()) - d = date.today().replace(day=1) # TODO is it the right date? + d = date.today().replace(day=1) # TODO is it the right date? for tr in self.page.iter_history(): if new_date(tr.date) >= d: yield tr diff --git a/modules/oney/compat/weboob_capabilities_bank.py b/modules/oney/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/oney/compat/weboob_capabilities_bank.py +++ b/modules/oney/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/oney/compat/weboob_exceptions.py b/modules/oney/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/oney/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/oney/compat/weboob_tools_captcha_virtkeyboard.py b/modules/oney/compat/weboob_tools_captcha_virtkeyboard.py deleted file mode 100644 index acf6006884..0000000000 --- a/modules/oney/compat/weboob_tools_captcha_virtkeyboard.py +++ /dev/null @@ -1,203 +0,0 @@ - -import weboob.tools.captcha.virtkeyboard as OLD - -# can't import *, __all__ is incomplete... -for attr in dir(OLD): - globals()[attr] = getattr(OLD, attr) - - -try: - __all__ = OLD.__all__ -except AttributeError: - pass - - -class SimpleVirtualKeyboard(object): - """Handle a virtual keyboard where "keys" are distributed on a simple grid. - - Parameters: - :param cols: Column count of the grid - :type cols: int - :param rows: Row count of the grid - :type rows: int - :param image: File-like object to be used as data source - :type image: file - :param convert: Mode to which convert color of pixels, see - :meth:`Image.Image.convert` for more information - :param matching_symbols: symbol that match all case of image grid from left to right and top - to down, European reading way. - :type matching_symbols: iterable - :param matching_symbols_coords: dict mapping matching website symbols to their image coords - (x0, y0, x1, y1) on grid image from left to right and top to - down, European reading way. It's not symbols in the image. - :type matching_symbols_coords: dict[str:4-tuple(int)] - :param browser: Browser of weboob session. - Allow to dump tiles files in same directory than session folder - :type browser: obj(Browser) - - Attributes: - :attribute codesep: Output separator between matching symbols - :type codesep: str - :param margin: Useless image pixel to cut. - See :func:`cut_margin`. - :type margin: 4-tuple(int), same as HTML margin: (top, right, bottom, left). - or 2-tuple(int), (top = bottom, right = left), - or int, top = right = bottom = left - :attribute tile_margin: Useless tile pixel to cut. - See :func:`cut_margin`. - :attribute symbols: Association table between image symbols and md5s - :type symbols: dict[str:str] or dict[str:n-tuple(str)] - :attribute convert: Mode to which convert color of pixels, see - :meth:`Image.Image.convert` for more information - :attribute alter: Allow custom main image alteration. Then overwrite :func:`alter_image`. - :type alter: boolean - """ - - codesep = '' - margin = None - tile_margin = None - symbols = None - convert = None - - def __init__(self, file, cols, rows, matching_symbols=None, matching_symbols_coords=None, browser=None): - self.cols = cols - self.rows = rows - - # Needed even if init is overwrite - self.path = self.build_path(browser) - - # Get self.image - self.load_image(file, self.margin, self.convert) - - # Get self.tiles - self.get_tiles( matching_symbols=matching_symbols, - matching_symbols_coords=matching_symbols_coords) - - # Tiles processing - self.cut_tiles(self.tile_margin) - self.hash_md5_tiles() - - def build_path(self, browser=None): - if browser and browser.responses_dirname: - return browser.responses_dirname - else: - return tempfile.mkdtemp(prefix='weboob_session_') - - def load_image(self, file, margin=None, convert=None): - self.image = Image.open(file) - # Resize image if margin is given - if margin: - self.image = self.cut_margin(self.image, margin) - if convert: - self.image = self.image.convert(convert) - # Give possibility to alter image before get tiles, overwrite :func:`alter_image`. - self.alter_image() - self.width, self.height = self.image.size - - def alter_image(self): - pass - - def cut_margin(self, image, margin): - width, height = image.size - - # Verify the magin value format - if type(margin) is int: - margin = (margin, margin, margin, margin) - elif len(margin) == 2: - margin = (margin[0], margin[1], margin[0], margin[1]) - elif len(margin) == 4: - margin = margin - else: - assert (len(margin) == 3) & (len(margin) > 4), \ - "Margin format is wrong." - - assert ((margin[0] + margin[2]) < height) & ((margin[1] + margin[3]) < width), \ - "Margin is too high, there is not enough pixel to cut." - - image = image.crop((0 + margin[3], - 0 + margin[0], - width - margin[1], - height - margin[2] - )) - return image - - def get_tiles(self, matching_symbols=None, matching_symbols_coords=None): - self.tiles = [] - - # Tiles coords are given - if matching_symbols_coords: - for matching_symbol in matching_symbols_coords: - self.tiles.append(Tile( matching_symbol=matching_symbol, - coords=matching_symbols_coords[matching_symbol] - )) - return - - assert (not self.width%self.cols) & (not self.height%self.rows), \ - "Image width and height are not multiple of cols and rows. Please resize image with attribute `margin`." - - # Tiles coords aren't given, calculate them - self.tileW = self.width // self.cols - self.tileH = self.height // self.rows - - # Matching symbols aren't given, default value is range(columns*rows) - if not matching_symbols: - matching_symbols = ['%s' % i for i in range(self.cols*self.rows)] - - assert len(matching_symbols) == (self.cols*self.rows), \ - "Number of website matching symbols is not equal to the number of cases on the image." - - # Calculate tiles coords for each matching symbol from 1-dimension to 2-dimensions - for index, matching_symbol in enumerate(matching_symbols): - coords = self.get_tile_coords_in_grid(index) - self.tiles.append(Tile(matching_symbol=matching_symbol, coords=coords)) - - def get_tile_coords_in_grid(self, case_index): - # Get the top left pixel coords of the tile - x0 = (case_index % self.cols) * self.tileW - y0 = (case_index // self.cols) * self.tileH - - # Get the bottom right coords of the tile - x1 = x0 + self.tileW - y1 = y0 + self.tileH - - coords = (x0, y0, x1, y1) - return(coords) - - def cut_tiles(self, tile_margin=None): - for tile in self.tiles: - tile.image = self.image.crop(tile.coords) - - # Resize tile if margin is given - if tile_margin: - for tile in self.tiles: - tile.image = self.cut_margin(tile.image, tile_margin) - - def hash_md5_tiles(self): - for tile in self.tiles: - tile.md5 = hashlib.md5(tile.image.tobytes()).hexdigest() - - def dump_tiles(self, path): - for tile in self.tiles: - tile.image.save('{}/{}.png'.format(path, tile.md5)) - - def get_string_code(self, password): - word = [] - - for digit in password: - for tile in self.tiles: - if tile.md5 in self.symbols[digit]: - word.append(tile.matching_symbol) - break - else: - # Dump file only if the symbol is not found - self.dump_tiles(self.path) - raise VirtKeyboardError("Symbol '%s' not found; all symbol hashes are available in %s" - % (digit, self.path)) - return self.codesep.join(word) - -class SplitKeyboard(OLD.SplitKeyboard): - def convert(self, buffer): - return buffer - - def checksum(self, buffer): - return hashlib.md5(self.convert(buffer)).hexdigest() diff --git a/modules/oney/pages.py b/modules/oney/pages.py index 531c308cdd..069b328a75 100644 --- a/modules/oney/pages.py +++ b/modules/oney/pages.py @@ -20,21 +20,19 @@ from __future__ import unicode_literals import re -from io import BytesIO from decimal import Decimal import requests from .compat.weboob_capabilities_bank import Account from weboob.tools.capabilities.bank.transactions import FrenchTransaction, sorted_transactions -from .compat.weboob_tools_captcha_virtkeyboard import MappedVirtKeyboard, VirtKeyboardError -from weboob.browser.pages import HTMLPage, LoggedPage, pagination, XLSPage, PartialHTMLPage +from weboob.browser.pages import HTMLPage, LoggedPage, pagination, XLSPage, PartialHTMLPage, JsonPage from weboob.browser.elements import ListElement, ItemElement, method, DictElement from .compat.weboob_browser_filters_standard import Env, CleanDecimal, CleanText, Field, Format, Currency, Date from weboob.browser.filters.html import Attr from weboob.browser.filters.json import Dict -from weboob.exceptions import BrowserIncorrectPassword - +from .compat.weboob_exceptions import BrowserIncorrectPassword +from weboob.tools.compat import urlparse, parse_qsl class Transaction(FrenchTransaction): PATTERNS = [(re.compile(r'^(?PRetrait .*?) - traité le \d+/\d+$'), FrenchTransaction.TYPE_WITHDRAWAL), @@ -46,109 +44,40 @@ class Transaction(FrenchTransaction): (re.compile(r'^(?P.*?)(, taux de change de(.*)?)? - traité le( (\d+|/\d+)*$|$)'), FrenchTransaction.TYPE_CARD)] # some labels are really badly formed so the regex needs to be this nasty to catch all edge cases -class VirtKeyboard(MappedVirtKeyboard): - symbols = {'0': ('8664b9cdfa66b4c3a1ec99c35a2bf64b', - '9eb80c6e99410eaac32905b2c77e65e5', - '37717277dc2471c8a7bf37e2068a8f01', - '6e3a1ee9bae6f7fdcfc70784e4377b1a', - ), - '1': ('1f36986f9d27dde54ce5b08e8e285476', - '9d0aa7a0a2bbab4f2c01ef1e820cb3f1', - 'a4ef89b1c1741158cac0e20ccb0c06b8', - ), - '2': ('b560b0cce2ca74d3d499d73775152ab7', - 'aa7dfbd005c98c0bd1ebc4135cc196be', - 'de01032b31aa17a9f251f554fe1f765b', - ), - '3': ('d16e426e71fc29b1b55d0fbded99a473', - 'ce25f07ca5df54f6b7934512b65a4653', - ), - '4': ('19c68066e414e08d17c86fc5c4acc949', - 'c43354a7f7739508f76c538d5b3bce26', - '93e9066313113b7219f60fd9fd1c9ace', - ), - '5': ('4b9abf98e30a1475997ec770cbe5e702', - '2059b4aa95c7b3156b171255fa10bbdd', - '1eb285164fae7666c274203f7f429d87', - ), - '6': ('804be4171d61f9cc10e9978c43b1d2a0', - 'a41b091d4a11a318406a5a8bd3ed3837', - 'd51645d63e85c373cbd253b99634fe8c', - 'aa0e99bef5c3b7cc350b4b18b528f31b', - ), - '7': ('8adf951f4eea5f446f714214e101d555', - '7989c1f32113391d7855db195939be56', - '0c4411e5e8ed8732eb1c7ad834b03c37', - '2f8fb9d5aad4b2b17b5b5e5d056db159', - ), - '8': ('568135f3844213c30f2c7880be867d3d', - 'b1a92ad131b163b3e380cf7ed8a7bf53', - 'b0f68949d5af30f4821891062d80ef39', - 'b842758c339e32f41d75df741787137e', - ), - '9': ('a3750995c511ea1492ac244421109e77', - 'eeb3a8ba804f19380dfe94a91a37595b', - '7ff11918f2cbc8ff6191f878b9b7d56c', - 'cb24fe526094ea0a4917bde5d2bf02a1', - ), - } - - color=(0,0,0) - - def __init__(self, page): - img = page.doc.find("//img[@usemap='#cv']") - res = page.browser.open(img.attrib['src']) - MappedVirtKeyboard.__init__(self, BytesIO(res.content), page.doc, img, self.color, 'href', convert='RGB') - - self.check_symbols(self.symbols, page.browser.responses_dirname) - - def check_color(self, pixel): - for p in pixel: - if p >= 0xd5: - return False - - return True - - def get_symbol_coords(self, coords): - # strip borders - x1, y1, x2, y2 = coords - return MappedVirtKeyboard.get_symbol_coords(self, (x1+10, y1+10, x2-10, y2-10)) - - def get_symbol_code(self, md5sum_list): - for md5sum in md5sum_list: - try: - code = MappedVirtKeyboard.get_symbol_code(self,md5sum) - except VirtKeyboardError: - continue - else: - return ''.join(re.findall(r"'(\d+)'", code)[-2:]) - raise VirtKeyboardError('Symbol not found') - - def get_string_code(self, string): - code = '' - for c in string: - code += self.get_symbol_code(self.symbols[c]) - return code +class ContextInitPage(JsonPage): + def get_client_id(self): + return self.doc['context']['client_id'] + + def get_success_url(self): + return self.doc['context']['success_url'] + + def get_customer_session_id(self): + return self.doc['context']['customer_session_id'] + + +class SendUsernamePage(JsonPage): + def get_flow_id(self): + return self.doc['authenticationFlowInit']['flow_id'] + + +class SendPasswordPage(JsonPage): + def get_token(self): + return self.doc['completeAuthFlowStep']['token'] + + def check_error(self): + errors = self.doc['completeAuthFlowStep']['errors'] + if errors: + raise BrowserIncorrectPassword(errors[0]['label']) + + +class CheckTokenPage(JsonPage): + pass class LoginPage(HTMLPage): - def login(self, login, password): - if login.isdigit(): - vk = VirtKeyboard(self) - - form = self.get_form('//form[@id="formulaire-login"]') - code = vk.get_string_code(password) - try: - assert len(code)==10 - except AssertionError: - raise BrowserIncorrectPassword("Wrong number of character") - form['accordirect.identifiant'] = login - form['accordirect.code'] = code - else: - form = self.get_form('//form[@id="formulaire-login-email"]') - form['email.identifiant'] = login - form['email.code'] = password - form.submit() + def get_context_token(self): + parameters = dict(parse_qsl(urlparse(self.url).query)) + return parameters.get('context_token', None) class ChoicePage(LoggedPage, HTMLPage): @@ -239,15 +168,41 @@ def condition(self): def next_page(self): options = self.page.doc.xpath('//select[@id="periode"]//option[@selected="selected"]/preceding-sibling::option[1]') if options: - data = {'numReleve':options[0].values(),'task':'Releve','process':'Releve','eventid':'select','taskid':'','hrefid':'','hrefext':''} + data = { + 'numReleve': options[0].values(), + 'task': 'Releve', + 'process': 'Releve', + 'eventid': 'select', + 'taskid': '', + 'hrefid': '', + 'hrefext': '', + } return requests.Request("POST", self.page.url, data=data) class CreditHome(LoggedPage, HTMLPage): - def get_name(self): - # boulanger/auchan/etc. + def get_accounts_ids(self): + ids = [] + for elem in self.doc.xpath('//li[@id="menu-n2-mesproduits"]//a/@onclick'): + acc_id = re.search(r'afficherDetailCompte\(\'(\d+)\'\)', elem).group(1) + if acc_id not in ids: + ids.append(acc_id) + return ids + + def get_label(self): + # 'Ma carte Alinea', 'Mon Prêt Oney', ... return CleanText('//div[@class="conteneur"]/h1')(self.doc) + @method + class get_loan(ItemElement): + klass = Account + + obj_type = Account.TYPE_LOAN + obj__site = 'other' + obj_label = CleanText('//div[@class="conteneur"]/h1') + obj_number = obj_id = CleanText('//td[contains(text(), "Mon numéro de compte")]/following-sibling::td', replace=[(' ', '')]) + obj_coming = CleanDecimal.US('//td[strong[contains(text(), "Montant de la")]]/following-sibling::td/strong') + class CreditAccountPage(LoggedPage, HTMLPage): @method @@ -257,13 +212,10 @@ class get_account(ItemElement): obj_type = Account.TYPE_CHECKING obj__site = 'other' obj_balance = 0 - obj_id = CleanText('//tr[td[text()="Mon numéro de compte"]]/td[@class="droite"]', replace=[(' ', '')]) + obj_number = obj_id = CleanText('//tr[td[text()="Mon numéro de compte"]]/td[@class="droite"]', replace=[(' ', '')]) obj_coming = CleanDecimal('//div[@id="mod-paiementcomptant"]//tr[td[contains(text(),"débité le")]]/td[@class="droite"]', sign=lambda _: -1, default=0) obj_currency = Currency('//div[@id="mod-paiementcomptant"]//tr[td[starts-with(normalize-space(text()),"Montant disponible")]]/td[@class="droite"]') - def obj_label(self): - return self.page.browser.card_name - class CreditHistory(LoggedPage, XLSPage): # this history doesn't contain the monthly recharges, so the balance isn't consistent with the transactions? diff --git a/modules/onlinenet/browser.py b/modules/onlinenet/browser.py index cf8cbd69b4..0a963216f1 100644 --- a/modules/onlinenet/browser.py +++ b/modules/onlinenet/browser.py @@ -18,8 +18,8 @@ # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, ProfilPage, DocumentsPage diff --git a/modules/onlinenet/compat/weboob_exceptions.py b/modules/onlinenet/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/onlinenet/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/openedx/browser.py b/modules/openedx/browser.py index 7719b99913..381aba24a1 100644 --- a/modules/openedx/browser.py +++ b/modules/openedx/browser.py @@ -17,10 +17,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser.browsers import LoginBrowser, URL, need_login from weboob.browser.pages import RawPage, JsonPage, HTMLPage from weboob.browser.exceptions import ClientError -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword class LoginPage(HTMLPage): def login(self, username, password): diff --git a/modules/openedx/compat/__init__.py b/modules/openedx/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/openedx/compat/weboob_exceptions.py b/modules/openedx/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/openedx/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/orange/browser.py b/modules/orange/browser.py index 34c058be1c..782eefa991 100644 --- a/modules/orange/browser.py +++ b/modules/orange/browser.py @@ -21,8 +21,8 @@ from requests.exceptions import ConnectTimeout -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded, BrowserPasswordExpired +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded, BrowserPasswordExpired from .pages import LoginPage, BillsPage from .pages.login import ManageCGI, HomePage, PasswordPage from .pages.bills import SubscriptionsPage, BillsApiProPage, BillsApiParPage, ContractsPage diff --git a/modules/orange/compat/__init__.py b/modules/orange/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/orange/compat/weboob_exceptions.py b/modules/orange/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/orange/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ovh/browser.py b/modules/ovh/browser.py index 2eb5f3c319..24f6cd4f63 100644 --- a/modules/ovh/browser.py +++ b/modules/ovh/browser.py @@ -20,8 +20,8 @@ from requests.exceptions import HTTPError, TooManyRedirects from datetime import datetime, timedelta -from weboob.browser import LoginBrowser, URL, need_login, StatesMixin -from weboob.exceptions import BrowserIncorrectPassword, BrowserQuestion +from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserQuestion from weboob.tools.value import Value from .pages import LoginPage, ProfilePage, BillsPage diff --git a/modules/ovh/compat/weboob_exceptions.py b/modules/ovh/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ovh/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/ovh/pages.py b/modules/ovh/pages.py index 66e58a0a49..0774d20686 100644 --- a/modules/ovh/pages.py +++ b/modules/ovh/pages.py @@ -24,7 +24,7 @@ from weboob.browser.filters.html import Attr from weboob.browser.filters.json import Dict from weboob.browser.elements import ListElement, ItemElement, method, DictElement -from weboob.exceptions import ActionNeeded, AuthMethodNotImplemented +from .compat.weboob_exceptions import ActionNeeded, AuthMethodNotImplemented class LoginPage(HTMLPage): diff --git a/modules/pastealacon/browser.py b/modules/pastealacon/browser.py index f261e87e31..c2056b2d01 100644 --- a/modules/pastealacon/browser.py +++ b/modules/pastealacon/browser.py @@ -25,7 +25,7 @@ from weboob.browser.browsers import PagesBrowser from .compat.weboob_browser_url import URL from weboob.browser.elements import ItemElement, method -from weboob.exceptions import BrowserHTTPNotFound +from .compat.weboob_exceptions import BrowserHTTPNotFound class Spam(Exception): diff --git a/modules/pastealacon/compat/weboob_exceptions.py b/modules/pastealacon/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/pastealacon/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/pastebin/browser.py b/modules/pastebin/browser.py index 81d0c87bb7..e2a522046e 100644 --- a/modules/pastebin/browser.py +++ b/modules/pastebin/browser.py @@ -26,7 +26,7 @@ from .compat.weboob_browser_filters_standard import Base, BrowserURL, CleanText, DateTime, Env, Field, Filter, FilterError, RawText from weboob.browser.pages import HTMLPage, RawPage from weboob.capabilities.paste import BasePaste, PasteNotFound -from weboob.exceptions import BrowserHTTPNotFound, BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserHTTPNotFound, BrowserIncorrectPassword, BrowserUnavailable class PastebinPaste(BasePaste): diff --git a/modules/pastebin/compat/weboob_exceptions.py b/modules/pastebin/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/pastebin/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/paypal/browser.py b/modules/paypal/browser.py index 6f565e3fa4..c8bff6f5ae 100644 --- a/modules/paypal/browser.py +++ b/modules/paypal/browser.py @@ -22,7 +22,7 @@ from dateutil.relativedelta import relativedelta from weboob.tools.compat import basestring -from weboob.exceptions import BrowserHTTPError, BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserHTTPError, BrowserIncorrectPassword, BrowserUnavailable from weboob.browser.browsers import LoginBrowser, need_login from .compat.weboob_browser_url import URL @@ -65,6 +65,7 @@ class Paypal(LoginBrowser): promo = URL('https://www.paypal.com/fr/webapps/mpp/clickthru/paypal-app-promo-2.*', '/fr/webapps/mpp/clickthru.*', PromoPage) account = URL('https://www.paypal.com/businessexp/money', + 'https://www.paypal.com/myaccount/money', 'https://www.paypal.com/webapps/business/money', AccountPage) pro_history = URL('https://\w+.paypal.com/businessexp/transactions/activity\?.*', ProHistoryPage) diff --git a/modules/paypal/compat/weboob_capabilities_bank.py b/modules/paypal/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/paypal/compat/weboob_capabilities_bank.py +++ b/modules/paypal/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/paypal/compat/weboob_exceptions.py b/modules/paypal/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/paypal/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/paypal/pages.py b/modules/paypal/pages.py index 0a83eb689c..ff0444837b 100644 --- a/modules/paypal/pages.py +++ b/modules/paypal/pages.py @@ -24,8 +24,8 @@ from weboob.tools.compat import unicode from .compat.weboob_capabilities_bank import Account -from weboob.capabilities.base import NotAvailable, Currency -from weboob.exceptions import BrowserUnavailable, ActionNeeded +from weboob.capabilities.base import NotAvailable +from .compat.weboob_exceptions import BrowserUnavailable, ActionNeeded from weboob.browser.exceptions import ServerError from weboob.browser.pages import HTMLPage, JsonPage, LoggedPage from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal @@ -93,7 +93,7 @@ def exec_decoder(mtc): cleaner_code = re.sub(r"%s\('([^']+)'\)" % re.escape(decoder_name), exec_decoder, cleaner_code) cookie = re.search(r'xppcts = (\w+);', cleaner_code).group(1) - sessionID = re.search(r"%s'([^']+)'" % re.escape("'&_sessionID='+encodeURIComponent("), cleaner_code).group(1) + sessionID = re.search(r"%s\w+\('([^']+)'" % re.escape("'&_sessionID='+encodeURIComponent("), cleaner_code).group(1) csrf = re.search(r"%s'([^']+)'" % re.escape("'&_csrf='+encodeURIComponent("), cleaner_code).group(1) key, value = re.findall(r"'(\w+)','(\w+)'", cleaner_code)[-1] @@ -106,9 +106,12 @@ def exec_decoder(mtc): get_token_func_declaration = "var " + get_token_func_name + "=" cleaner_code = cleaner_code.replace(get_token_func_declaration, get_token_func_declaration + "window.ADS_JS_TOKEN=") - # Remove the call to an infinite loop - loop_func_name = re.search(r"\(function\(\w+,\s?\w+,\s?\w+,\s?\w+\)\{var\s(\w+)=", cleaner_code).group(1) - cleaner_code = cleaner_code.replace(loop_func_name + "();", "") + # Paypal will try to create an infinite loop to make the parse fail, based on different + # weird things like a check of 'ind\\u0435xOf' vs 'indexOf'. + cleaner_code = cleaner_code.replace(r"'ind\\u0435xOf'", "'indexOf'") + # It also calls "data" which is undefined instead of a return (next call is an infinite + # recursive function). This should theorically not happen if window.domain is correctly set + # to "paypal.com" though. cleaner_code = cleaner_code.replace("data;", "return;") # Add a function that returns the token @@ -166,22 +169,18 @@ def get_account(self, _id): def get_accounts(self): accounts = {} - content = self.doc.xpath('//div[@id="moneyPage" or @id="MoneyPage"]')[0] + content = self.doc.xpath('//section[@id="contents"]')[0] # Multiple accounts - lines = content.xpath('(//div[@class="col-md-8 multi-currency"])[1]/ul/li') + lines = content.xpath('.//ul[@class="multiCurrency-container"][1]/li') for li in lines: account = Account() account.iban = NotAvailable account.type = Account.TYPE_CHECKING - currency_code = CleanText().filter((li.xpath('./span[@class="currencyUnit"]/span') or li.xpath('./span[1]'))[0]) - currency = Currency.get_currency(currency_code) - if not currency: - self.logger.warning('Unable to find currency %r', currency_code) - continue + currency = CleanText().filter(li.xpath('.//span[contains(@class, "multiCurrency-label_alignMiddle")]')[0]) account.id = currency account.currency = currency - account.balance = CleanDecimal(replace_dots=True).filter(li.xpath('./span[@class="amount"]/text()')) + account.balance = CleanDecimal(replace_dots=True).filter(li.xpath('.//span[contains(@class, "multiCurrency-label_right")]/text()')[0]) account.label = u'%s %s*' % (self.browser.username, account.currency) accounts[account.id] = account self.browser.account_currencies.append(account.currency) diff --git a/modules/phpbb/browser.py b/modules/phpbb/browser.py index 27ada9278c..0b9ea4f072 100644 --- a/modules/phpbb/browser.py +++ b/modules/phpbb/browser.py @@ -20,9 +20,9 @@ import re -from weboob.browser import URL, LoginBrowser, need_login +from weboob.browser.browsers import URL, LoginBrowser, need_login from weboob.capabilities.messages import CantSendMessage -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages.forum import ForumPage, PostingPage, TopicPage from .pages.index import LoginPage diff --git a/modules/phpbb/compat/__init__.py b/modules/phpbb/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/phpbb/compat/weboob_exceptions.py b/modules/phpbb/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/phpbb/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/pixabay/browser.py b/modules/pixabay/browser.py index c16b5130ff..145525955b 100644 --- a/modules/pixabay/browser.py +++ b/modules/pixabay/browser.py @@ -18,10 +18,10 @@ # along with this weboob module. If not, see . import re -from weboob.browser import URL +from weboob.browser.browsers import URL from weboob.browser.browsers import LoginBrowser, need_login from weboob.capabilities.image import CapImage -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.compat import quote_plus from .pages import AccountPage, LoginPage, SearchAPI, ViewPage diff --git a/modules/pixabay/compat/__init__.py b/modules/pixabay/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/pixabay/compat/weboob_exceptions.py b/modules/pixabay/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/pixabay/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/playme/browser.py b/modules/playme/browser.py index 7c49b7d707..8fb3438695 100644 --- a/modules/playme/browser.py +++ b/modules/playme/browser.py @@ -20,11 +20,11 @@ import re -from weboob.browser import DomainBrowser +from weboob.browser.browsers import DomainBrowser from weboob.browser.exceptions import ClientError from weboob.browser.pages import HTMLPage from weboob.browser.profiles import Profile -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.json import json diff --git a/modules/playme/compat/__init__.py b/modules/playme/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/playme/compat/weboob_exceptions.py b/modules/playme/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/playme/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/poivy/browser.py b/modules/poivy/browser.py index 3be886e87d..e9e3357812 100644 --- a/modules/poivy/browser.py +++ b/modules/poivy/browser.py @@ -18,8 +18,8 @@ # along with this weboob module. If not, see . from weboob.tools.compat import basestring -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import HomePage, LoginPage, HistoryPage, BillsPage, ErrorPage diff --git a/modules/poivy/compat/weboob_exceptions.py b/modules/poivy/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/poivy/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/poivy/pages.py b/modules/poivy/pages.py index d95190020f..05b0760d1a 100644 --- a/modules/poivy/pages.py +++ b/modules/poivy/pages.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . -from weboob.exceptions import BrowserBanned +from .compat.weboob_exceptions import BrowserBanned from weboob.browser.pages import HTMLPage, LoggedPage, pagination from weboob.browser.elements import ListElement, ItemElement, method from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Field, DateTime, Format diff --git a/modules/pradoepargne/compat/weboob_capabilities_bank.py b/modules/pradoepargne/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/pradoepargne/compat/weboob_capabilities_bank.py +++ b/modules/pradoepargne/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/redmine/browser.py b/modules/redmine/browser.py index 0d9b644a16..acc2c53e52 100644 --- a/modules/redmine/browser.py +++ b/modules/redmine/browser.py @@ -21,8 +21,8 @@ import lxml.html from weboob.capabilities.bugtracker import IssueError -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.compat import quote from .pages.index import LoginPage, IndexPage, MyPage, ProjectsPage diff --git a/modules/redmine/compat/__init__.py b/modules/redmine/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/redmine/compat/weboob_exceptions.py b/modules/redmine/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/redmine/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/redmine/module.py b/modules/redmine/module.py index 4a971cd950..7cfe20891a 100644 --- a/modules/redmine/module.py +++ b/modules/redmine/module.py @@ -24,7 +24,7 @@ Query, Change from weboob.capabilities.collection import CapCollection, Collection, CollectionNotFound from weboob.tools.backend import Module, BackendConfig -from weboob.exceptions import BrowserHTTPNotFound +from .compat.weboob_exceptions import BrowserHTTPNotFound from weboob.tools.compat import basestring, unicode from weboob.tools.value import ValueBackendPassword, Value diff --git a/modules/regionsjob/compat/weboob_exceptions.py b/modules/regionsjob/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/regionsjob/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/regionsjob/pages.py b/modules/regionsjob/pages.py index ca20f176f5..1f8d8ea09b 100644 --- a/modules/regionsjob/pages.py +++ b/modules/regionsjob/pages.py @@ -23,7 +23,7 @@ from weboob.browser.filters.html import CleanHTML, Link from weboob.browser.filters.json import Dict from weboob.capabilities.job import BaseJobAdvert -from weboob.exceptions import ParseError +from .compat.weboob_exceptions import ParseError from datetime import date, timedelta from weboob.capabilities import NotAvailable diff --git a/modules/relaiscolis/compat/__init__.py b/modules/relaiscolis/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/relaiscolis/compat/weboob_exceptions.py b/modules/relaiscolis/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/relaiscolis/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/relaiscolis/module.py b/modules/relaiscolis/module.py index d7c2f4cb17..61a6d5a35f 100644 --- a/modules/relaiscolis/module.py +++ b/modules/relaiscolis/module.py @@ -23,7 +23,7 @@ from weboob.capabilities.base import NotAvailable from weboob.capabilities.parcel import CapParcel, Parcel, ParcelNotFound from weboob.tools.value import Value -from weboob.exceptions import BrowserQuestion +from .compat.weboob_exceptions import BrowserQuestion from .browser import RelaiscolisBrowser diff --git a/modules/residentadvisor/browser.py b/modules/residentadvisor/browser.py index ae8702e93e..6f3c5b9394 100644 --- a/modules/residentadvisor/browser.py +++ b/modules/residentadvisor/browser.py @@ -18,8 +18,8 @@ # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, EventPage, ListPage, SearchPage diff --git a/modules/residentadvisor/compat/weboob_exceptions.py b/modules/residentadvisor/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/residentadvisor/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/s2e/browser.py b/modules/s2e/browser.py index d2330f65ff..f8275c2c98 100644 --- a/modules/s2e/browser.py +++ b/modules/s2e/browser.py @@ -23,7 +23,7 @@ import re from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded, NoAccountsException +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded, NoAccountsException from .compat.weboob_capabilities_bank import Investment from weboob.tools.capabilities.bank.investments import is_isin_valid @@ -165,7 +165,8 @@ def update_investments(self, investments): elif self.amfcode_sg.match(inv._link) or self.lyxorfunds.match(inv._link): # Esalia (Société Générale Épargne Salariale) or Lyxor investments # Not all sggestion-ede.com or lyxorfunds.com have available performances. - self.location(inv._link) + # For those requests to work in every case we need the headers from AccountsPage + self.location(inv._link, headers={'Referer': self.accounts.build(slug=self.SLUG)}) inv.performance_history = self.page.get_investment_performances() return investments diff --git a/modules/s2e/compat/weboob_capabilities_bank.py b/modules/s2e/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/s2e/compat/weboob_capabilities_bank.py +++ b/modules/s2e/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/s2e/compat/weboob_exceptions.py b/modules/s2e/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/s2e/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/s2e/pages.py b/modules/s2e/pages.py index 770b3049a4..2f4e48445d 100644 --- a/modules/s2e/pages.py +++ b/modules/s2e/pages.py @@ -35,7 +35,7 @@ from .compat.weboob_capabilities_bank import Account, Investment, Pocket, Transaction from weboob.capabilities.base import NotAvailable, empty from .compat.weboob_tools_captcha_virtkeyboard import MappedVirtKeyboard -from weboob.exceptions import BrowserUnavailable, ActionNeeded, BrowserQuestion, BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserUnavailable, ActionNeeded, BrowserQuestion, BrowserIncorrectPassword from weboob.tools.value import Value from weboob.tools.compat import urljoin from weboob.tools.capabilities.bank.investments import is_isin_valid diff --git a/modules/sachsen/compat/weboob_exceptions.py b/modules/sachsen/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/sachsen/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/sachsen/pages.py b/modules/sachsen/pages.py index d0fe998fe0..fa723304d3 100644 --- a/modules/sachsen/pages.py +++ b/modules/sachsen/pages.py @@ -25,7 +25,7 @@ from weboob.capabilities.gauge import Gauge, GaugeMeasure, GaugeSensor from weboob.capabilities.base import NotAvailable, NotLoaded -from weboob.exceptions import ParseError +from .compat.weboob_exceptions import ParseError import re diff --git a/modules/societegenerale/browser.py b/modules/societegenerale/browser.py index d52924b51b..af199c5de8 100644 --- a/modules/societegenerale/browser.py +++ b/modules/societegenerale/browser.py @@ -25,7 +25,7 @@ from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin from weboob.capabilities.bill import Document, DocumentTypes -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded, BrowserUnavailable from .compat.weboob_capabilities_bank import Account, TransferBankError, AddRecipientStep, TransactionType, AccountOwnerType from weboob.capabilities.base import find_object, NotAvailable from weboob.browser.exceptions import BrowserHTTPNotFound, ClientError @@ -433,6 +433,10 @@ def new_recipient(self, recipient, **params): return self.end_oob_recipient(recipient, **params) self.add_recipient.go() + if self.main_page.is_here(): + self.page.handle_error() + assert False, 'Should not be on this page.' + self.page.post_iban(recipient) self.page.post_label(recipient) diff --git a/modules/societegenerale/compat/weboob_capabilities_bank.py b/modules/societegenerale/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/societegenerale/compat/weboob_capabilities_bank.py +++ b/modules/societegenerale/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/societegenerale/compat/weboob_exceptions.py b/modules/societegenerale/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/societegenerale/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/societegenerale/pages/accounts_list.py b/modules/societegenerale/pages/accounts_list.py index 1d54e5cf7d..3f7f7650ca 100644 --- a/modules/societegenerale/pages/accounts_list.py +++ b/modules/societegenerale/pages/accounts_list.py @@ -40,7 +40,7 @@ ) from weboob.browser.filters.html import Link, TableCell, Attr from weboob.browser.pages import HTMLPage, XMLPage, JsonPage, LoggedPage, pagination -from weboob.exceptions import BrowserUnavailable, ActionNeeded, NoAccountsException +from .compat.weboob_exceptions import BrowserUnavailable, ActionNeeded, NoAccountsException def MyDecimal(*args, **kwargs): diff --git a/modules/societegenerale/pages/compat/weboob_capabilities_bank.py b/modules/societegenerale/pages/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/societegenerale/pages/compat/weboob_capabilities_bank.py +++ b/modules/societegenerale/pages/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/societegenerale/pages/compat/weboob_exceptions.py b/modules/societegenerale/pages/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/societegenerale/pages/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/societegenerale/pages/login.py b/modules/societegenerale/pages/login.py index 526cd3a1c6..46cffe08af 100644 --- a/modules/societegenerale/pages/login.py +++ b/modules/societegenerale/pages/login.py @@ -24,10 +24,11 @@ import re from weboob.tools.json import json -from weboob.exceptions import BrowserUnavailable, BrowserPasswordExpired, ActionNeeded +from .compat.weboob_exceptions import BrowserUnavailable, BrowserPasswordExpired, ActionNeeded from weboob.browser.pages import HTMLPage, JsonPage from .compat.weboob_browser_filters_standard import CleanText from weboob.browser.filters.json import Dict +from .compat.weboob_capabilities_bank import AddRecipientBankError from .base import BasePage from ..captcha import Captcha, TileError @@ -105,6 +106,13 @@ def login(self, login, password): } self.browser.location(self.browser.absurl('/sec/vk/authent.json'), data=data) + def handle_error(self): + error_msg = CleanText('//span[@class="error_msg"]')(self.doc) + if error_msg: + # WARNING: this error occured during a recipient adding + # I don't know if it can happen at another time + raise AddRecipientBankError(message=error_msg) + class LoginPage(JsonPage): def get_error(self): diff --git a/modules/societegenerale/pages/transfer.py b/modules/societegenerale/pages/transfer.py index 852e1a8a77..5f06f00764 100644 --- a/modules/societegenerale/pages/transfer.py +++ b/modules/societegenerale/pages/transfer.py @@ -33,7 +33,7 @@ from weboob.browser.filters.html import Link, ReplaceEntities from weboob.browser.filters.json import Dict from weboob.tools.json import json -from weboob.exceptions import BrowserUnavailable, ActionNeeded +from .compat.weboob_exceptions import BrowserUnavailable, ActionNeeded from .base import BasePage from .login import MainPage diff --git a/modules/societegenerale/sgpe/browser.py b/modules/societegenerale/sgpe/browser.py index b43a21172c..143f682f0b 100644 --- a/modules/societegenerale/sgpe/browser.py +++ b/modules/societegenerale/sgpe/browser.py @@ -25,7 +25,7 @@ from weboob.browser.browsers import LoginBrowser, need_login, StatesMixin from .compat.weboob_browser_url import URL from weboob.browser.exceptions import ClientError -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded, NoAccountsException +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded, NoAccountsException from weboob.capabilities.base import find_object from .compat.weboob_capabilities_bank import ( AccountNotFound, RecipientNotFound, AddRecipientStep, AddRecipientBankError, @@ -472,7 +472,7 @@ def init_transfer(self, account, recipient, transfer): raise TransferBankError(message="La date d'exécution du virement est invalide. Elle doit correspondre aux horaires et aux dates d'ouvertures d'agence.") # update account and recipient info - recipient = find_object(self.iter_recipients(account), iban=recipient.iban, error=RecipientNotFound) + recipient = find_object(self.iter_recipients(account), iban=recipient.iban, id=recipient.id, error=RecipientNotFound) data = [ ('an_codeAction', 'C'), diff --git a/modules/societegenerale/sgpe/compat/weboob_capabilities_bank.py b/modules/societegenerale/sgpe/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/societegenerale/sgpe/compat/weboob_capabilities_bank.py +++ b/modules/societegenerale/sgpe/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/societegenerale/sgpe/compat/weboob_exceptions.py b/modules/societegenerale/sgpe/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/societegenerale/sgpe/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/societegenerale/sgpe/json_pages.py b/modules/societegenerale/sgpe/json_pages.py index b24c3e35ce..130147159a 100644 --- a/modules/societegenerale/sgpe/json_pages.py +++ b/modules/societegenerale/sgpe/json_pages.py @@ -31,7 +31,7 @@ from weboob.capabilities import NotAvailable from .compat.weboob_capabilities_bank import Account, Investment from weboob.capabilities.bill import Document, Subscription, DocumentTypes -from weboob.exceptions import ( +from .compat.weboob_exceptions import ( BrowserUnavailable, NoAccountsException, BrowserIncorrectPassword, BrowserPasswordExpired, AuthMethodNotImplemented, ) diff --git a/modules/societegenerale/sgpe/pages.py b/modules/societegenerale/sgpe/pages.py index d44ba83a88..c6e9ad92a5 100644 --- a/modules/societegenerale/sgpe/pages.py +++ b/modules/societegenerale/sgpe/pages.py @@ -33,7 +33,7 @@ from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.capabilities.profile import Profile, Person from weboob.capabilities.bill import Document, Subscription, DocumentTypes -from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable from weboob.tools.json import json from weboob.capabilities.base import NotAvailable diff --git a/modules/sogecartenet/compat/weboob_capabilities_bank.py b/modules/sogecartenet/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/sogecartenet/compat/weboob_capabilities_bank.py +++ b/modules/sogecartenet/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/sogecartenet/compat/weboob_exceptions.py b/modules/sogecartenet/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/sogecartenet/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/sogecartenet/pages.py b/modules/sogecartenet/pages.py index 25336dead5..b6e962c235 100644 --- a/modules/sogecartenet/pages.py +++ b/modules/sogecartenet/pages.py @@ -20,7 +20,7 @@ import requests from weboob.browser.pages import HTMLPage, CsvPage, pagination -from weboob.exceptions import BrowserIncorrectPassword, BrowserPasswordExpired, NoAccountsException +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserPasswordExpired, NoAccountsException from weboob.browser.elements import DictElement, ItemElement, method, TableElement from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Date, Env from weboob.browser.filters.html import TableCell diff --git a/modules/spirica/browser.py b/modules/spirica/browser.py index cdd06a6e36..76f006a365 100644 --- a/modules/spirica/browser.py +++ b/modules/spirica/browser.py @@ -19,8 +19,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, AccountsPage, DetailsPage, MaintenancePage diff --git a/modules/spirica/compat/weboob_capabilities_bank.py b/modules/spirica/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/spirica/compat/weboob_capabilities_bank.py +++ b/modules/spirica/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/spirica/compat/weboob_exceptions.py b/modules/spirica/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/spirica/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/spirica/pages.py b/modules/spirica/pages.py index 7df59217a3..30b223f663 100644 --- a/modules/spirica/pages.py +++ b/modules/spirica/pages.py @@ -28,7 +28,7 @@ from weboob.browser.filters.html import Attr, Link, TableCell from .compat.weboob_capabilities_bank import Account, Investment, Transaction from weboob.capabilities.base import NotAvailable, empty -from weboob.exceptions import BrowserUnavailable, BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserUnavailable, BrowserIncorrectPassword from weboob.tools.compat import urljoin @@ -199,7 +199,7 @@ def obj_portfolio_share(self): path = 'ancestor::tr/preceding-sibling::tr[@data-ri][position() = 1][1]/td[%d]' % (share_idx + 1) profile_share = MyDecimal(path)(self) - assert profile_share + assert not empty(profile_share), 'profile_share is %s' % profile_share profile_share = Eval(lambda x: x / 100, profile_share)(self) return inv_share * profile_share else: diff --git a/modules/suravenir/browser.py b/modules/suravenir/browser.py index f3c8632782..df746b13aa 100644 --- a/modules/suravenir/browser.py +++ b/modules/suravenir/browser.py @@ -20,8 +20,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages import LoginPage, AccountsList, InvestmentList, AccountHistory diff --git a/modules/suravenir/compat/weboob_capabilities_bank.py b/modules/suravenir/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/suravenir/compat/weboob_capabilities_bank.py +++ b/modules/suravenir/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/suravenir/compat/weboob_exceptions.py b/modules/suravenir/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/suravenir/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/t411/browser.py b/modules/t411/browser.py index 5da7429f31..15bb4ee75d 100644 --- a/modules/t411/browser.py +++ b/modules/t411/browser.py @@ -23,7 +23,7 @@ from weboob.browser.browsers import LoginBrowser, need_login from .compat.weboob_browser_url import URL from weboob.browser.profiles import Wget -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages.index import HomePage, LoginPage from .pages.torrents import TorrentPage, SearchPage, DownloadPage diff --git a/modules/t411/compat/weboob_exceptions.py b/modules/t411/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/t411/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/tapatalk/compat/__init__.py b/modules/tapatalk/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/tapatalk/compat/weboob_exceptions.py b/modules/tapatalk/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/tapatalk/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/tapatalk/module.py b/modules/tapatalk/module.py index b161483f85..5002485495 100644 --- a/modules/tapatalk/module.py +++ b/modules/tapatalk/module.py @@ -28,7 +28,7 @@ from weboob.tools.backend import Module, BackendConfig from weboob.tools.value import Value, ValueBackendPassword from weboob.capabilities.messages import CapMessages, Thread, Message -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword __all__ = ['TapatalkModule'] diff --git a/modules/themisbanque/compat/weboob_capabilities_bank.py b/modules/themisbanque/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/themisbanque/compat/weboob_capabilities_bank.py +++ b/modules/themisbanque/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/themisbanque/compat/weboob_exceptions.py b/modules/themisbanque/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/themisbanque/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/themisbanque/pages.py b/modules/themisbanque/pages.py index 0908a29f18..81a67fe509 100644 --- a/modules/themisbanque/pages.py +++ b/modules/themisbanque/pages.py @@ -21,7 +21,7 @@ import re -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.browser.pages import LoggedPage, HTMLPage, pagination, PDFPage from weboob.browser.elements import method, ItemElement, TableElement from .compat.weboob_capabilities_bank import Account diff --git a/modules/ticketscesu/browser.py b/modules/ticketscesu/browser.py index 646b4629ca..0b5f932496 100644 --- a/modules/ticketscesu/browser.py +++ b/modules/ticketscesu/browser.py @@ -20,8 +20,8 @@ from __future__ import unicode_literals -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import ActionNeeded +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import ActionNeeded from .pages import AccountsPage, LoginPage, ProfilePage diff --git a/modules/ticketscesu/compat/weboob_capabilities_bank.py b/modules/ticketscesu/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/ticketscesu/compat/weboob_capabilities_bank.py +++ b/modules/ticketscesu/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/ticketscesu/compat/weboob_exceptions.py b/modules/ticketscesu/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/ticketscesu/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/tinder/browser.py b/modules/tinder/browser.py index a6e342b1e5..825edd6ce9 100644 --- a/modules/tinder/browser.py +++ b/modules/tinder/browser.py @@ -24,7 +24,7 @@ from .compat.weboob_browser_filters_standard import CleanText from weboob.browser.pages import HTMLPage from weboob.browser.profiles import IPhone, Android -from weboob.exceptions import BrowserIncorrectPassword, ParseError +from .compat.weboob_exceptions import BrowserIncorrectPassword, ParseError from weboob.tools.json import json diff --git a/modules/tinder/compat/weboob_exceptions.py b/modules/tinder/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/tinder/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/trainline/browser.py b/modules/trainline/browser.py index 0abdc8c679..d912b9d8f8 100644 --- a/modules/trainline/browser.py +++ b/modules/trainline/browser.py @@ -22,7 +22,7 @@ from dateutil.relativedelta import relativedelta from weboob.browser.browsers import APIBrowser -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .compat.weboob_browser_filters_standard import CleanDecimal, Date from weboob.browser.exceptions import ClientError from weboob.capabilities.bill import DocumentTypes, Bill, Subscription diff --git a/modules/trainline/compat/weboob_exceptions.py b/modules/trainline/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/trainline/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/twitter/browser.py b/modules/twitter/browser.py index 7143854069..2ef0735ccd 100644 --- a/modules/twitter/browser.py +++ b/modules/twitter/browser.py @@ -17,8 +17,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this weboob module. If not, see . -from weboob.browser import LoginBrowser, URL, need_login -from weboob.exceptions import BrowserIncorrectPassword +from weboob.browser.browsers import LoginBrowser, URL, need_login +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.capabilities.messages import Message from .pages import LoginPage, LoginErrorPage, ThreadPage, Tweet, TrendsPage,\ TimelinePage, HomeTimelinePage, SearchTimelinePage, SearchPage diff --git a/modules/twitter/compat/weboob_exceptions.py b/modules/twitter/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/twitter/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/twitter/module.py b/modules/twitter/module.py index a6ef0684e4..b646511eac 100644 --- a/modules/twitter/module.py +++ b/modules/twitter/module.py @@ -23,7 +23,7 @@ from weboob.capabilities.messages import CapMessages, Thread, CapMessagesPost from weboob.capabilities.collection import CapCollection, CollectionNotFound, Collection from weboob.capabilities.base import find_object -from weboob.exceptions import BrowserForbidden +from .compat.weboob_exceptions import BrowserForbidden from .browser import TwitterBrowser import itertools diff --git a/modules/vicsec/browser.py b/modules/vicsec/browser.py index 3e2a781ad5..e34c1a291a 100644 --- a/modules/vicsec/browser.py +++ b/modules/vicsec/browser.py @@ -23,11 +23,11 @@ from decimal import Decimal from itertools import chain -from weboob.browser import URL, LoginBrowser, need_login +from weboob.browser.browsers import URL, LoginBrowser, need_login from weboob.browser.pages import HTMLPage from weboob.capabilities.base import Currency from weboob.capabilities.shop import Item, Order, Payment -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.capabilities.bank.transactions import AmericanTransaction as AmTr from weboob.tools.compat import unicode diff --git a/modules/vicsec/compat/__init__.py b/modules/vicsec/compat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/vicsec/compat/weboob_exceptions.py b/modules/vicsec/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/vicsec/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/vicseccard/browser.py b/modules/vicseccard/browser.py index 963e94bd8d..4576c6c347 100644 --- a/modules/vicseccard/browser.py +++ b/modules/vicseccard/browser.py @@ -25,7 +25,7 @@ from weboob.browser.exceptions import ServerError from weboob.browser.pages import HTMLPage from .compat.weboob_capabilities_bank import Account, AccountNotFound, Transaction -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from weboob.tools.capabilities.bank.transactions import AmericanTransaction as AmTr from weboob.tools.compat import unicode diff --git a/modules/vicseccard/compat/weboob_capabilities_bank.py b/modules/vicseccard/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/vicseccard/compat/weboob_capabilities_bank.py +++ b/modules/vicseccard/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/vicseccard/compat/weboob_exceptions.py b/modules/vicseccard/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/vicseccard/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/vimeo/compat/weboob_exceptions.py b/modules/vimeo/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/vimeo/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/vimeo/pages.py b/modules/vimeo/pages.py index 77fde4c4a7..24515a2297 100644 --- a/modules/vimeo/pages.py +++ b/modules/vimeo/pages.py @@ -22,7 +22,7 @@ from weboob.capabilities.image import Thumbnail from weboob.capabilities.collection import Collection -from weboob.exceptions import ParseError +from .compat.weboob_exceptions import ParseError from weboob.browser.elements import ItemElement, ListElement, method, DictElement from weboob.browser.pages import HTMLPage, pagination, JsonPage, XMLPage from .compat.weboob_browser_filters_standard import Regexp, Env, CleanText, DateTime, Duration, Field, BrowserURL diff --git a/modules/wellsfargo/browser.py b/modules/wellsfargo/browser.py index 7adbf75a52..6b9a3b59d1 100644 --- a/modules/wellsfargo/browser.py +++ b/modules/wellsfargo/browser.py @@ -26,7 +26,7 @@ from weboob.browser.browsers import URL, LoginBrowser, need_login from .compat.weboob_capabilities_bank import AccountNotFound -from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable +from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailable from weboob.tools.compat import unquote from .pages import (ActivityCardPage, ActivityCashPage, CodeRequestPage, CodeSubmitPage, DocumentsPage, LoggedInPage, diff --git a/modules/wellsfargo/compat/weboob_capabilities_bank.py b/modules/wellsfargo/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/wellsfargo/compat/weboob_capabilities_bank.py +++ b/modules/wellsfargo/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/wellsfargo/compat/weboob_exceptions.py b/modules/wellsfargo/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/wellsfargo/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/wiseed/compat/weboob_capabilities_bank.py b/modules/wiseed/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/wiseed/compat/weboob_capabilities_bank.py +++ b/modules/wiseed/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/wiseed/compat/weboob_exceptions.py b/modules/wiseed/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/wiseed/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/wiseed/pages.py b/modules/wiseed/pages.py index 7d83008dfa..9f32d20f91 100644 --- a/modules/wiseed/pages.py +++ b/modules/wiseed/pages.py @@ -23,7 +23,7 @@ from weboob.browser.filters.html import TableCell from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Regexp from weboob.browser.elements import method, ItemElement, TableElement -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .compat.weboob_capabilities_bank import Investment from weboob.tools.capabilities.bank.investments import create_french_liquidity diff --git a/modules/yggtorrent/browser.py b/modules/yggtorrent/browser.py index 6b30f05308..7548ea0344 100644 --- a/modules/yggtorrent/browser.py +++ b/modules/yggtorrent/browser.py @@ -22,7 +22,7 @@ from weboob.browser.browsers import LoginBrowser, need_login from .compat.weboob_browser_url import URL from weboob.browser.profiles import Wget -from weboob.exceptions import BrowserIncorrectPassword +from .compat.weboob_exceptions import BrowserIncorrectPassword from .pages.index import HomePage, LoginPage from .pages.torrents import TorrentPage, SearchPage, DownloadPage diff --git a/modules/yggtorrent/compat/weboob_exceptions.py b/modules/yggtorrent/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/yggtorrent/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/yomoni/browser.py b/modules/yomoni/browser.py index 1098de076d..8b968546f7 100644 --- a/modules/yomoni/browser.py +++ b/modules/yomoni/browser.py @@ -29,7 +29,8 @@ from weboob.browser.browsers import APIBrowser from weboob.browser.exceptions import ClientError from .compat.weboob_browser_filters_standard import CleanDecimal, Date -from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded +from weboob.browser.filters.html import ReplaceEntities +from .compat.weboob_exceptions import BrowserIncorrectPassword, ActionNeeded from .compat.weboob_capabilities_bank import Account, Investment, Transaction from weboob.capabilities.base import NotAvailable from weboob.tools.capabilities.bank.investments import is_isin_valid @@ -172,7 +173,13 @@ def iter_history(self, account): self.open('/user/%s/project/%s/activity' % (self.users['userId'], account._project_id), method="OPTIONS") for activity in [acc for acc in self.request('/user/%s/project/%s/activity' % (self.users['userId'], account._project_id), headers=self.request_headers)['activities'] \ if acc['details'] is not None]: - m = re.search(u'([\d\,]+)(?=[\s]+€|[\s]+euro)', activity['details']) + + m = re.search( + r'([\d\,]+)(?=[\s]+€|[\s]+euro)', + ReplaceEntities().filter(activity['details']), + flags=re.UNICODE, + ) + if "Souscription" not in activity['title'] and not m: continue diff --git a/modules/yomoni/compat/weboob_capabilities_bank.py b/modules/yomoni/compat/weboob_capabilities_bank.py index 4d5ed8d992..74c6887039 100644 --- a/modules/yomoni/compat/weboob_capabilities_bank.py +++ b/modules/yomoni/compat/weboob_capabilities_bank.py @@ -27,6 +27,10 @@ class RecipientInvalidOTP(AddRecipientError): code = 'invalidOTP' +class TransferInvalidOTP(TransferError): + code = 'invalidOTP' + + class AccountOwnership(object): """ Relationship between the credentials owner (PSU) and the account @@ -43,6 +47,6 @@ class AccountOwnership(object): try: - __all__ += ['AccountOwnership', 'RecipientInvalidOTP'] + __all__ += ['AccountOwnership', 'RecipientInvalidOTP', 'TransferInvalidOTP'] except NameError: pass diff --git a/modules/yomoni/compat/weboob_exceptions.py b/modules/yomoni/compat/weboob_exceptions.py new file mode 100644 index 0000000000..bdb1af4703 --- /dev/null +++ b/modules/yomoni/compat/weboob_exceptions.py @@ -0,0 +1,38 @@ + +import weboob.exceptions as OLD + +# can't import *, __all__ is incomplete... +for attr in dir(OLD): + globals()[attr] = getattr(OLD, attr) + + +try: + __all__ = OLD.__all__ +except AttributeError: + pass + + +class BrowserInteraction(Exception): + pass + + +class BrowserQuestion(BrowserInteraction): + """ + When raised by a browser, + """ + def __init__(self, *fields): + self.fields = fields + + +class DecoupledValidation(BrowserInteraction): + def __init__(self, message='', resource=None, *values): + super(DecoupledValidation, self).__init__(*values) + self.message = message + self.resource = resource + + def __str__(self): + return self.message + + +class AppValidation(DecoupledValidation): + pass diff --git a/modules/youtube/module.py b/modules/youtube/module.py index 5adbe0b69f..93a111b8b7 100644 --- a/modules/youtube/module.py +++ b/modules/youtube/module.py @@ -38,7 +38,7 @@ # so apiclient must be imported after from apiclient.discovery import build as ytbuild except ImportError: - raise ImportError("Please install python-googleapi") + raise ImportError("Please install python3-googleapi") __all__ = ['YoutubeModule'] -- GitLab