Romain Bignon
Builds for 1 pipeline failed in 62 minutes 35 seconds

backport master modules fixes

Showing 151 changed files with 1968 additions and 1106 deletions
......@@ -29,7 +29,6 @@ from .pages import LoginPage, IndexPage, BadLogin, AccountDetailPage, AccountHis
class AferBrowser(LoginBrowser):
BASEURL = 'https://adherent.gie-afer.fr'
VERIFY = 'afer.pem'
login = URL('/web/ega.nsf/listeAdhesions\?OpenForm', LoginPage)
bad_login = URL('/names.nsf\?Login', BadLogin)
......@@ -53,7 +52,9 @@ class AferBrowser(LoginBrowser):
raise BrowserIncorrectPassword()
if self.bad_login.is_here():
raise BrowserIncorrectPassword()
error = self.page.get_error()
assert "La saisie de l’identifiant ou du code confidentiel est incorrecte" in error, error
raise BrowserIncorrectPassword(error)
@need_login
def iter_accounts(self):
......
......@@ -17,6 +17,13 @@ class CapBankPockets(CapBank):
pass
class Rate(BaseObject, Currency):
pass
class CapCurrencyRate(CapBank):
pass
class CapBankTransfer(OLD.CapBankTransfer):
def transfer_check_label(self, old, new):
from unidecode import unidecode
......@@ -31,4 +38,3 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
......@@ -42,6 +42,11 @@ class LoginPage(HTMLPage):
return bool(self.doc.xpath('//form[@name="_DominoForm"]'))
class BadLogin(HTMLPage):
def get_error(self):
return CleanText('//div[@id="idDivErrorLogin"]')(self.doc)
class IndexPage(LoggedPage, HTMLPage):
def on_load(self):
HTMLPage.on_load(self)
......@@ -156,7 +161,3 @@ class AccountHistoryPage(LoggedPage, HTMLPage):
return am
return (Async('details') & CleanDecimal('//div//tr[2]/td[2]', replace_dots=True, default=NotAvailable))(
self)
class BadLogin(HTMLPage):
pass
......
......@@ -25,7 +25,10 @@ from .compat.weboob_exceptions import BrowserIncorrectPassword, BrowserUnavailab
from weboob.tools.value import Value
from weboob.browser.browsers import ClientError
from .pages import LoginPage, SubscriptionsPage, DocumentsPage, HomePage, PanelPage, SecurityPage, LanguagePage, HistoryPage
from .pages import (
LoginPage, SubscriptionsPage, DocumentsPage, DownloadDocumentPage, HomePage, PanelPage, SecurityPage,
LanguagePage, HistoryPage
)
class AmazonBrowser(LoginBrowser, StatesMixin):
......@@ -44,6 +47,7 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
documents = URL(r'/gp/your-account/order-history\?opt=ab&digitalOrders=1(.*)&orderFilter=year-(?P<year>.*)',
r'https://www.amazon.fr/gp/your-account/order-history',
DocumentsPage)
download_doc = URL(r'/gp/shared-cs/ajax/invoice/invoice.html', DownloadDocumentPage)
security = URL('/ap/dcq',
'/ap/cvf/',
'/ap/mfa',
......@@ -176,6 +180,8 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
documents = []
for y in range(date.today().year - 2, date.today().year + 1):
for doc in self.documents.go(year=y).iter_documents(subid=subscription.id, currency=self.CURRENCY):
self.documents.go(year=y)
request_id = self.page.response.headers['x-amz-rid']
for doc in self.page.iter_documents(subid=subscription.id, currency=self.CURRENCY, request_id=request_id):
documents.append(doc)
return documents
......
......@@ -19,12 +19,12 @@
from __future__ import unicode_literals
from weboob.browser.pages import HTMLPage, LoggedPage, FormNotFound
from weboob.browser.pages import HTMLPage, LoggedPage, FormNotFound, PartialHTMLPage
from weboob.browser.elements import ItemElement, ListElement, method
from weboob.browser.filters.html import Link
from weboob.browser.filters.standard import (
CleanText, CleanDecimal, Env, Regexp, Format,
Field, Currency, RegexpError, Date
Field, Currency, RegexpError, Date, Async, AsyncLoad
)
from weboob.capabilities.bill import Bill, Subscription
from weboob.capabilities.base import NotAvailable
......@@ -119,11 +119,14 @@ class DocumentsPage(LoggedPage, HTMLPage):
class item(ItemElement):
klass = Bill
load_details = Field('_pre_url') & AsyncLoad
obj__simple_id = CleanText('.//div[has-class("actions")]//span[has-class("value")]')
obj_id = Format('%s_%s', Env('subid'), Field('_simple_id'))
obj_url = Format('/gp/css/summary/print.html/ref=oh_aui_ajax_pi?ie=UTF8&orderID=%s', Field('_simple_id'))
obj_format = 'html'
obj__pre_url = Format('/gp/shared-cs/ajax/invoice/invoice.html?orderId=%s&relatedRequestId=%s&isADriveSubscription=&isHFC=',
Field('_simple_id'), Env('request_id'))
obj_url = Async('details') & Link('//a[contains(@href, "download")]')
obj_format = 'pdf'
obj_label = Format('Facture %s', Field('_simple_id'))
obj_type = 'bill'
......@@ -142,3 +145,7 @@ class DocumentsPage(LoggedPage, HTMLPage):
def obj_currency(self):
currency = Env('currency')(self)
return Currency('.//div[has-class("a-col-left")]//span[has-class("value") and contains(., "%s")]' % currency)(self)
class DownloadDocumentPage(LoggedPage, PartialHTMLPage):
pass
......
......@@ -17,6 +17,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from weboob.browser import LoginBrowser, URL, need_login
from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded
from weboob.tools.compat import basestring
......@@ -28,13 +30,13 @@ __all__ = ['AmeliBrowser']
class AmeliBrowser(LoginBrowser):
BASEURL = 'https://assure.ameli.fr'
loginp = URL('/PortailAS/appmanager/PortailAS/assure\?.*_pageLabel=as_login_page', LoginPage)
homep = URL('/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_accueil_page', HomePage)
cgup = URL('/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_conditions_generales_page', CguPage)
accountp = URL('/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_info_perso_page', AccountPage)
paymentsp = URL('/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_paiements_page', PaymentsPage)
paymentdetailsp = URL('/PortailAS/paiements.do\?actionEvt=chargerDetailPaiements.*', PaymentDetailsPage)
lastpaymentsp = URL('/PortailAS/paiements.do\?actionEvt=afficherPaiements.*', LastPaymentsPage)
loginp = URL(r'/PortailAS/appmanager/PortailAS/assure\?.*_pageLabel=as_login_page', LoginPage)
homep = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_accueil_page', HomePage)
cgup = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_conditions_generales_page', CguPage)
accountp = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_info_perso_page', AccountPage)
paymentsp = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_paiements_page', PaymentsPage)
paymentdetailsp = URL(r'/PortailAS/paiements.do\?actionEvt=chargerDetailPaiements.*', PaymentDetailsPage)
lastpaymentsp = URL(r'/PortailAS/paiements.do\?actionEvt=afficherPaiements.*', LastPaymentsPage)
pdf_page = URL(r'PortailAS/PDFServletReleveMensuel.dopdf\?PDF.moisRecherche=.*', Raw)
def do_login(self):
......
......@@ -17,6 +17,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from weboob.capabilities.bill import CapDocument, SubscriptionNotFound, DocumentNotFound, Subscription, Bill
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword
......@@ -27,8 +29,8 @@ __all__ = ['AmeliModule']
class AmeliModule(Module, CapDocument):
NAME = 'ameli'
DESCRIPTION = u'Ameli website: French Health Insurance'
MAINTAINER = u'Christophe Lampin'
DESCRIPTION = 'Ameli website: French Health Insurance'
MAINTAINER = 'Christophe Lampin'
EMAIL = 'weboob@lampin.net'
VERSION = '1.3'
LICENSE = 'AGPLv3+'
......@@ -75,6 +77,7 @@ class AmeliModule(Module, CapDocument):
def download_document(self, bill):
if not isinstance(bill, Bill):
bill = self.get_document(bill)
request = self.browser.open(bill.url, stream=True)
assert(request.headers['content-type'] == "application/pdf")
return request.content
response = self.browser.open(bill.url, stream=True)
if not response or response.headers['content-type'] != "application/pdf":
return None
return response.content
......
......@@ -27,17 +27,16 @@ from weboob.browser.filters.html import Attr, XPathNotFound
from weboob.browser.pages import HTMLPage, RawPage, LoggedPage
from weboob.capabilities.bill import Subscription, Detail, Bill
from weboob.browser.filters.standard import CleanText, Regexp
from weboob.tools.compat import urljoin
# Ugly array to avoid the use of french locale
FRENCH_MONTHS = [u'janvier', u'février', u'mars', u'avril', u'mai', u'juin', u'juillet', u'août', u'septembre', u'octobre', u'novembre', u'décembre']
FRENCH_MONTHS = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']
class AmeliBasePage(HTMLPage):
@property
def logged(self):
if self.doc.xpath(u'//a[contains(text(), "Déconnexion")]'):
if self.doc.xpath('//a[contains(text(), "Déconnexion")]'):
logged = True
else:
logged = False
......@@ -45,7 +44,7 @@ class AmeliBasePage(HTMLPage):
return logged
def is_error(self):
errors = self.doc.xpath(u'//*[@id="r_errors"]')
errors = self.doc.xpath('//*[@id="r_errors"]')
if errors:
return errors[0].text_content()
......@@ -86,7 +85,7 @@ class AccountPage(AmeliBasePage):
def iter_subscription_list(self):
names_list = self.doc.xpath('//span[@class="NomEtPrenomLabel"]')
fullname = CleanText(newlines=True).filter(names_list[0])
number = re.sub('[^\d]+', '', CleanText('//span[@class="blocNumSecu"]', replace=[(' ','')])(self.doc))
number = re.sub(r'[^\d]+', '', CleanText('//span[@class="blocNumSecu"]', replace=[(' ','')])(self.doc))
sub = Subscription(number)
sub._id = number
sub.label = fullname
......@@ -126,10 +125,10 @@ class LastPaymentsPage(LoggedPage, AmeliBasePage):
bil = Bill()
bil.id = sub._id + "." + date.strftime("%Y%m")
bil.date = date
bil.format = u'pdf'
bil.type = u'bill'
bil.label = u'' + date.strftime("%Y%m%d")
bil.url = urljoin(self.url, '/PortailAS/PDFServletReleveMensuel.dopdf?PDF.moisRecherche=%s' % date.strftime("%m%Y"))
bil.format = 'pdf'
bil.type = 'bill'
bil.label = date.strftime("%Y%m%d")
bil.url = '/PortailAS/PDFServletReleveMensuel.dopdf?PDF.moisRecherche=' + date.strftime("%m%Y")
yield bil
def get_document(self, bill):
......@@ -139,7 +138,7 @@ class LastPaymentsPage(LoggedPage, AmeliBasePage):
class PaymentDetailsPage(AmeliBasePage):
def iter_payment_details(self, sub):
id_str = self.doc.xpath('//div[@class="entete container"]/h2')[0].text.strip()
m = re.match('.*le (.*) pour un montant de.*', id_str)
m = re.match(r'.*le (.*) pour un montant de.*', id_str)
if m:
blocs_benes = self.doc.xpath('//span[contains(@id,"nomBeneficiaire")]')
blocs_prestas = self.doc.xpath('//table[@id="tableauPrestation"]')
......@@ -179,18 +178,18 @@ class PaymentDetailsPage(AmeliBasePage):
price = '0'
if date_str is None or date_str == '':
det.infos = u''
det.infos = ''
det.datetime = last_date
else:
det.infos = '%s (%sj) * %s€' % (date_str, re.sub(r'[^\d,-]+', '', jours), re.sub(r'[^\d,-]+', '', montant))
det.infos = date_str + ' (' + re.sub(r'[^\d,-]+', '', jours) + 'j) * ' + re.sub(r'[^\d,-]+', '', montant) + '€'
det.datetime = datetime.strptime(date_str.split(' ')[3], '%d/%m/%Y').date()
last_date = det.datetime
det.price = Decimal(re.sub('[^\d,-]+', '', price).replace(',', '.'))
det.price = Decimal(re.sub(r'[^\d,-]+', '', price).replace(',', '.'))
if len(tds) == 5:
date_str = Regexp(pattern=r'\w*(\d{2})/(\d{2})/(\d{4}).*', template='\\1/\\2/\\3', default="").filter("".join(tds[0].itertext()))
det.id = id + "." + str(line)
det.label = '%s - %s' % (bene, tds[0].xpath('.//span')[0].text.strip())
det.label = bene + ' - ' + tds[0].xpath('.//span')[0].text.strip()
paye = tds[1].text
if paye is None:
......@@ -213,13 +212,13 @@ class PaymentDetailsPage(AmeliBasePage):
price = tdprice.strip()
if date_str is None or date_str == '':
det.infos = u''
det.infos = ''
det.datetime = last_date
else:
det.infos = u' Payé %s€ / Base %s€ / Taux %s%%' % (re.sub(r'[^\d,-]+', '', paye), re.sub(r'[^\d,-]+', '', base), re.sub('[^\d,-]+', '', taux))
det.infos = ' Payé ' + re.sub(r'[^\d,-]+', '', paye) + '€ / Base ' + re.sub(r'[^\d,-]+', '', base) + '€ / Taux ' + re.sub(r'[^\d,-]+', '', taux) + '%'
det.datetime = datetime.strptime(date_str, '%d/%m/%Y').date()
last_date = det.datetime
det.price = Decimal(re.sub('[^\d,-]+', '', price).replace(',', '.'))
det.price = Decimal(re.sub(r'[^\d,-]+', '', price).replace(',', '.'))
line = line + 1
yield det
......
# -*- coding: utf-8 -*-
# Copyright(C) 2017 Vincent Ardisson
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from ast import literal_eval
from decimal import Decimal
import re
from weboob.browser.pages import LoggedPage, JsonPage, HTMLPage
from weboob.browser.elements import ItemElement, DictElement, method
from weboob.browser.filters.standard import Date, Eval, CleanText, Field, CleanDecimal
from weboob.browser.filters.json import Dict
from weboob.capabilities.bank import Account, Transaction
from weboob.capabilities.base import NotAvailable
from weboob.tools.json import json
from weboob.tools.compat import basestring
from .base import parse_decimal
def float_to_decimal(f):
return Decimal(str(f))
class DashboardPage(LoggedPage, HTMLPage):
def get_user_key(self):
script = CleanText('//script[@id="initial-state"]', replace=[('\\', '')])(self.doc)
m = re.search(r'account_key\",(\"(.*?)\")', script)
if m:
return m.group(2)
return None
class AccountsPage3(LoggedPage, HTMLPage):
def iter_accounts(self):
for line in self.doc.xpath('//script[@id="initial-state"]')[0].text.split('\n'):
m = re.search('window.__INITIAL_STATE__ = (.*);', line)
if m:
data = json.loads(literal_eval(m.group(1)))
break
else:
assert False, "data was not found"
assert data[13] == 'core'
assert len(data[14]) == 3
# search for products to get products list
for index, el in enumerate(data[14][2]):
if 'products' in el:
accounts_data = data[14][2][index+1]
assert len(accounts_data) == 2
assert accounts_data[1][4] == 'productsList'
accounts_data = accounts_data[1][5]
token = []
for account_data in accounts_data:
if isinstance(account_data, basestring):
token2 = account_data
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]
else:
acc._idforJSON = account_data[-5][-1]
acc.number = '-%s' % account_data[2][2]
acc._idforold = account_data[2][6]
acc.label = '%s %s' % (account_data[6][4], account_data[10][-1])
acc._token2 = acc.id = token2
acc._token = token[-1]
yield acc
class JsonBalances(LoggedPage, JsonPage):
def set_balances(self, accounts):
by_token = {a._token2: a for a in accounts}
for d in self.doc:
# coming is what should be refunded at a futur deadline
by_token[d['account_token']].coming = -float_to_decimal(d['total_debits_balance_amount'])
# balance is what is currently due
by_token[d['account_token']].balance = -float_to_decimal(d['remaining_statement_balance_amount'])
class JsonBalances2(LoggedPage, JsonPage):
def set_balances(self, accounts):
by_token = {a._token2: a for a in accounts}
for d in self.doc:
by_token[d['account_token']].balance = -float_to_decimal(d['total']['payments_credits_total_amount'])
by_token[d['account_token']].coming = -float_to_decimal(d['total']['debits_total_amount'])
# warning: payments_credits_total_amount is not the coming value here
class CurrencyPage(LoggedPage, JsonPage):
def get_currency(self):
return self.doc['currency']
class JsonPeriods(LoggedPage, JsonPage):
def get_periods(self):
return [p['statement_end_date'] for p in self.doc]
class JsonHistory(LoggedPage, JsonPage):
def get_count(self):
return self.doc['total_count']
@method
class iter_history(DictElement):
item_xpath = 'transactions'
class item(ItemElement):
klass = Transaction
def obj_type(self):
if Field('raw')(self) in self.page.browser.SUMMARY_CARD_LABEL:
return Transaction.TYPE_CARD_SUMMARY
elif Field('amount')(self) > 0:
return Transaction.TYPE_ORDER
else:
return Transaction.TYPE_DEFERRED_CARD
obj_raw = CleanText(Dict('description', default=''))
obj_date = Date(Dict('statement_end_date', default=None), default=None)
obj_rdate = Date(Dict('charge_date'))
obj_vdate = Date(Dict('post_date', default=None), default=NotAvailable)
obj_amount = Eval(lambda x: -float_to_decimal(x), Dict('amount'))
obj_original_currency = Dict('foreign_details/iso_alpha_currency_code', default=NotAvailable)
obj_commission = CleanDecimal(Dict('foreign_details/commission_amount', default=NotAvailable), sign=lambda x: -1, default=NotAvailable)
obj__owner = CleanText(Dict('embossed_name'))
def obj_original_amount(self):
# amount in the account's currency
amount = Field("amount")(self)
# amount in the transaction's currency
original_amount = Dict('foreign_details/amount', default=NotAvailable)(self)
if Field("original_currency")(self) == "XAF":
original_amount = abs(CleanDecimal(replace_dots=('.')).filter(original_amount))
elif not original_amount:
return NotAvailable
else:
original_amount = abs(parse_decimal(original_amount))
if amount < 0:
return -original_amount
else:
return original_amount
#obj__ref = Dict('reference_id')
obj__ref = Dict('identifier')
......@@ -17,6 +17,13 @@ class CapBankPockets(CapBank):
pass
class Rate(BaseObject, Currency):
pass
class CapCurrencyRate(CapBank):
pass
class CapBankTransfer(OLD.CapBankTransfer):
def transfer_check_label(self, old, new):
from unidecode import unidecode
......@@ -31,4 +38,3 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
......@@ -17,6 +17,13 @@ class CapBankPockets(CapBank):
pass
class Rate(BaseObject, Currency):
pass
class CapCurrencyRate(CapBank):
pass
class CapBankTransfer(OLD.CapBankTransfer):
def transfer_check_label(self, old, new):
from unidecode import unidecode
......@@ -31,4 +38,3 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
......@@ -20,6 +20,7 @@
import time
import datetime
from base64 import b64decode
from html2text import unescape
from dateutil import tz
from dateutil.parser import parse as _parse_dt
......@@ -66,7 +67,7 @@ class AuMModule(Module, CapMessages, CapMessagesPost, CapDating, CapChat, CapCon
Value('search_query', label='Search query', default=''))
STORAGE = {'profiles_walker': {'viewed': []},
'queries_queue': {'queue': []},
'sluts': {},
'contacts': {},
'notes': {},
}
BROWSER = AuMBrowser
......@@ -175,14 +176,14 @@ class AuMModule(Module, CapMessages, CapMessagesPost, CapDating, CapChat, CapCon
child = None
msg = None
slut = self._get_slut(id)
contact = self._get_contact(id)
if contacts is None:
contacts = {}
if not thread.title:
thread.title = u'Discussion with %s' % mails['who']['pseudo']
self.storage.set('sluts', int(thread.id), 'status', mails['status'])
self.storage.set('contacts', int(thread.id), 'status', mails['status'])
self.storage.save()
for mail in mails['results']:
......@@ -192,7 +193,7 @@ class AuMModule(Module, CapMessages, CapMessagesPost, CapDating, CapChat, CapCon
self.report_spam(thread.id)
break
if parse_dt(mail['date']) > slut['lastmsg']:
if parse_dt(mail['date']) > contact['lastmsg']:
flags |= Message.IS_UNREAD
if get_profiles:
......@@ -261,8 +262,8 @@ class AuMModule(Module, CapMessages, CapMessagesPost, CapDating, CapChat, CapCon
self.logger.info('Skipped a spam-unread-thread from %s' % thread['who']['pseudo'])
self.report_spam(thread['member']['id'])
continue
slut = self._get_slut(thread['who']['id'])
if parse_dt(thread['date']) > slut['lastmsg'] or thread['status'] != slut['status']:
contact = self._get_contact(thread['who']['id'])
if parse_dt(thread['date']) > contact['lastmsg'] or thread['status'] != contact['status']:
try:
t = self.get_thread(thread['who']['id'], contacts, get_profiles=True)
except BrowserUnavailable:
......@@ -275,17 +276,17 @@ class AuMModule(Module, CapMessages, CapMessagesPost, CapDating, CapChat, CapCon
return
# Send mail when someone added me in her basket.
# XXX possibly race condition if a slut adds me in her basket
# XXX possibly race condition if a contact adds me in her basket
# between the aum.nb_new_baskets() and aum.get_baskets().
with self.browser:
slut = self._get_slut(-self.MAGIC_ID_BASKET)
contact = self._get_contact(-self.MAGIC_ID_BASKET)
new_baskets = self.browser.nb_new_baskets()
if new_baskets > 0:
baskets = self.browser.get_baskets()
my_name = self.browser.get_my_name()
for basket in baskets:
if parse_dt(basket['date']) <= slut['lastmsg']:
if parse_dt(basket['date']) <= contact['lastmsg']:
continue
contact = self.get_contact(basket['who']['id'])
if self.antispam and not self.antispam.check_contact(contact):
......@@ -313,31 +314,33 @@ class AuMModule(Module, CapMessages, CapMessagesPost, CapDating, CapChat, CapCon
def set_message_read(self, message):
if int(message.id) == self.MAGIC_ID_BASKET:
# Save the last baskets checks.
slut = self._get_slut(-self.MAGIC_ID_BASKET)
if slut['lastmsg'] < message.date:
slut['lastmsg'] = message.date
self.storage.set('sluts', -self.MAGIC_ID_BASKET, slut)
contact = self._get_contact(-self.MAGIC_ID_BASKET)
if contact['lastmsg'] < message.date:
contact['lastmsg'] = message.date
self.storage.set('contacts', -self.MAGIC_ID_BASKET, contact)
self.storage.save()
return
slut = self._get_slut(message.thread.id)
if slut['lastmsg'] < message.date:
slut['lastmsg'] = message.date
self.storage.set('sluts', int(message.thread.id), slut)
contact = self._get_contact(message.thread.id)
if contact['lastmsg'] < message.date:
contact['lastmsg'] = message.date
self.storage.set('contacts', int(message.thread.id), contact)
self.storage.save()
def _get_slut(self, id):
def _get_contact(self, id):
id = int(id)
sluts = self.storage.get('sluts')
if not sluts or id not in sluts:
slut = {'lastmsg': datetime.datetime(1970,1,1),
'status': None}
contacts = self.storage.get('contacts')
if not contacts or id not in contacts:
contacts = self.storage.get(b64decode('c2x1dHM='))
if not contacts or id not in contacts:
contact = {'lastmsg': datetime.datetime(1970,1,1),
'status': None}
else:
slut = self.storage.get('sluts', id)
contact = contacts[id]
slut['lastmsg'] = slut.get('lastmsg', datetime.datetime(1970,1,1)).replace(tzinfo=tz.tzutc())
slut['status'] = slut.get('status', None)
return slut
contact['lastmsg'] = contact.get('lastmsg', datetime.datetime(1970,1,1)).replace(tzinfo=tz.tzutc())
contact['status'] = contact.get('status', None)
return contact
# ---- CapMessagesPost methods ---------------------
......
......@@ -420,7 +420,7 @@ class AXABanque(AXABrowser, StatesMixin):
for recipient in self.page.get_recipients():
if recipient.iban in seen:
recipient.category = 'EXTERNE'
recipient.category = 'Externe'
yield recipient
def copy_recipient_obj(self, recipient):
......@@ -428,7 +428,7 @@ class AXABanque(AXABrowser, StatesMixin):
rcpt.id = recipient.iban
rcpt.iban = recipient.iban
rcpt.label = recipient.label
rcpt.category = 'EXTERNE'
rcpt.category = 'Externe'
rcpt.enabled_at = date.today()
rcpt.currency = 'EUR'
return rcpt
......
......@@ -17,6 +17,13 @@ class CapBankPockets(CapBank):
pass
class Rate(BaseObject, Currency):
pass
class CapCurrencyRate(CapBank):
pass
class CapBankTransfer(OLD.CapBankTransfer):
def transfer_check_label(self, old, new):
from unidecode import unidecode
......@@ -31,4 +38,3 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
......@@ -32,7 +32,7 @@ from weboob.browser.filters.standard import (
CleanText, Date, Regexp, CleanDecimal, Currency, Format, Field,
)
from weboob.capabilities.bank import (
Recipient, Transfer, TransferError, AddRecipientError, RecipientNotFound,
Recipient, Transfer, TransferBankError, AddRecipientError, RecipientNotFound,
)
from .compat.weboob_tools_captcha_virtkeyboard import SimpleVirtualKeyboard
from weboob.capabilities.base import find_object, NotAvailable
......@@ -54,7 +54,7 @@ class TransferVirtualKeyboard(SimpleVirtualKeyboard):
'1': '12d398f7f389711c5f8298ee68a8af28',
'2': 'f43ca3a5dd649d30bf02060ab65c4eff',
'3': 'b6dd7864cfd941badb0784be37f7eeb3',
'4': '7138d0a663eef56c699d85dc6c3ac639',
'4': ('7138d0a663eef56c699d85dc6c3ac639', '0faced58777f371097a7a70bb9570dd7', ),
'5': 'b71bd38e71ce0b611642a01b6900218f',
'6': 'f71f7249413c189165da7b588c2f0493',
'7': '81fc65230d7df341e80d02e414f183d4',
......@@ -95,7 +95,7 @@ class RecipientsPage(LoggedPage, HTMLPage):
obj_id = CleanText(TableCell('id'), replace=[(' ', '')])
obj_iban = Field('id')
obj_label = Format('%s - %s', CleanText(TableCell('_acc_name')), CleanText(TableCell('_rcpt_name')))
obj_category = 'EXTERNE'
obj_category = 'Externe'
obj_enabled_at = date.today()
obj_currency = 'EUR'
obj_bank_name = NotAvailable
......@@ -218,7 +218,7 @@ class RegisterTransferPage(LoggedPage, HTMLPage):
error_xpath = '//span[@class="erreur_phrase"]'
if self.doc.xpath(error_xpath):
error_msg = CleanText(error_xpath)(self.doc)
raise TransferError(message=error_msg)
raise TransferBankError(message=error_msg)
def is_transfer_account(self, acc_id):
valide_accounts_xpath = '//select[@id="compteEmetteurSelectionne"]//option[not(contains(@value,"vide0"))]'
......@@ -255,7 +255,7 @@ class RegisterTransferPage(LoggedPage, HTMLPage):
rcpt.iban = CleanText('./@value')(recipient)
rcpt.id = rcpt.iban
rcpt.enabled_at = date.today()
rcpt.category = 'INTERNE'
rcpt.category = 'Interne'
yield rcpt
......@@ -357,7 +357,7 @@ class ConfirmTransferPage(LoggedPage, HTMLPage):
def on_load(self):
error_msg = '//p[@id="messErreur"]/span'
if self.doc.xpath(error_msg):
raise TransferError(message=CleanText(error_msg)(self.doc))
raise TransferBankError(message=CleanText(error_msg)(self.doc))
confirm_transfer_xpath = '//h2[contains(text(), "Virement enregistr")]'
assert self.doc.xpath(confirm_transfer_xpath)
......
......@@ -41,11 +41,12 @@ class AccountsPage(LoggedPage, HTMLPage):
TYPES = {u'assurance vie': Account.TYPE_LIFE_INSURANCE,
u'perp': Account.TYPE_PERP,
u'novial avenir': Account.TYPE_MADELIN,
u'epargne retraite novial': Account.TYPE_LIFE_INSURANCE,
}
@method
class iter_accounts(ListElement):
item_xpath = '//div[contains(@data-route, "/assurance-vie/")]'
item_xpath = '//div[contains(@data-route, "/savings/")]'
class item(ItemElement):
klass = Account
......@@ -56,7 +57,14 @@ class AccountsPage(LoggedPage, HTMLPage):
obj_label = CleanText('.//h3[has-class("card-title")]')
obj_balance = MyDecimal('.//p[has-class("amount-card")]')
obj_valuation_diff = MyDecimal('.//p[@class="performance"]')
obj_url = Attr('.', 'data-route')
def obj_url(self):
url = Attr('.', 'data-route')(self)
# The Assurance Vie xpath recently changed so we must verify that all
# the accounts now have "/savings/" instead of "/assurances-vie/".
assert "/savings/" in url
return url
obj_currency = Currency('.//p[has-class("amount-card")]')
obj__acctype = "investment"
......
......@@ -96,7 +96,6 @@ def no_need_login(func):
class BanquePopulaire(LoginBrowser):
VERIFY = False
login_page = URL(r'https://[^/]+/auth/UI/Login.*', LoginPage)
index_page = URL(r'https://[^/]+/cyber/internet/Login.do', IndexPage)
accounts_page = URL(r'https://[^/]+/cyber/internet/StartTask.do\?taskInfoOID=mesComptes.*',
......@@ -128,9 +127,14 @@ class BanquePopulaire(LoginBrowser):
transaction_detail_page = URL(r'https://[^/]+/cyber/internet/ContinueTask.do\?.*dialogActionPerformed=DETAIL_ECRITURE.*', TransactionDetailPage)
error_page = URL(r'https://[^/]+/cyber/internet/ContinueTask.do', ErrorPage)
error_page = URL(r'https://[^/]+/cyber/internet/ContinueTask.do',
r'https://[^/]+/_layouts/error.aspx',
ErrorPage)
unavailable_page = URL(r'https://[^/]+/s3f-web/.*',
r'https://[^/]+/static/errors/nondispo.html', UnavailablePage)
r'https://[^/]+/static/errors/nondispo.html',
r'/i-RIA/swc/1.0.0/desktop/index.html',
UnavailablePage)
redirect_page = URL(r'https://[^/]+/portailinternet/_layouts/Ibp.Cyi.Layouts/RedirectSegment.aspx.*', RedirectPage)
home_page = URL(r'https://[^/]+/portailinternet/Catalogue/Segments/.*.aspx(\?vary=(?P<vary>.*))?',
......
......@@ -17,6 +17,13 @@ class CapBankPockets(CapBank):
pass
class Rate(BaseObject, Currency):
pass
class CapCurrencyRate(CapBank):
pass
class CapBankTransfer(OLD.CapBankTransfer):
def transfer_check_label(self, old, new):
from unidecode import unidecode
......@@ -31,4 +38,3 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
......@@ -23,4 +23,3 @@ from weboob.browser import AbstractBrowser
class LinebourseBrowser(AbstractBrowser):
PARENT = 'linebourse'
VERIFY = False
......
......@@ -38,6 +38,7 @@ from weboob.capabilities.profile import Person
from weboob.capabilities.contact import Advisor
from weboob.capabilities import NotAvailable
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.tools.decorators import retry
from weboob.tools.compat import urlsplit, parse_qsl
from weboob.tools.json import json
from weboob.tools.misc import to_unicode
......@@ -274,6 +275,9 @@ class UnavailablePage(LoggedPage, MyHTMLPage):
h1 = CleanText('//h1[1]')(self.doc)
if "est indisponible" in h1:
raise BrowserUnavailable(h1)
body = CleanText(".")(self.doc)
if "An unexpected error has occurred." in body:
raise BrowserUnavailable(body)
a = Link('//a[@class="btn"][1]', default=None)(self.doc)
if not a:
......@@ -387,6 +391,9 @@ class HomePage(LoggedPage, MyHTMLPage):
return None
return super(MyHTMLPage, self).build_doc(data, *args, **kwargs)
@retry(KeyError)
# sometime the server redirects to a bad url, not containing token.