Romain Bignon
Builds for 1 pipeline failed in 61 minutes 9 seconds

backport master modules fixes

Showing 189 changed files with 1790 additions and 539 deletions
......@@ -35,6 +35,11 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
pass
class AddRecipientBankError(AddRecipientError):
code = 'bankMessage'
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
......@@ -28,7 +28,7 @@ from weboob.browser.filters.standard import CleanText, Regexp, CleanDecimal, Dat
from weboob.browser.pages import HTMLPage, LoggedPage, pagination
from weboob.capabilities.bank import Account, Investment, Transaction
from weboob.capabilities.base import NotAvailable
from weboob.exceptions import BrowserUnavailable
from weboob.exceptions import BrowserUnavailable, ActionNeeded
class LoginPage(HTMLPage):
......@@ -51,6 +51,10 @@ class IndexPage(LoggedPage, HTMLPage):
def on_load(self):
HTMLPage.on_load(self)
msg = CleanText('//div[has-class("form-input-label")]', default='')(self.doc)
if "prendre connaissance des nouvelles conditions" in msg:
raise ActionNeeded(msg)
# website sometime crash
if self.doc.xpath(u'//div[@id="divError"]/span[contains(text(),"Une erreur est survenue")]'):
raise BrowserUnavailable()
......
......@@ -17,19 +17,20 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from weboob.capabilities.calendar import BaseCalendarEvent, TRANSP, STATUS, CATEGORIES
from weboob.capabilities.collection import Collection
from weboob.capabilities.video import BaseVideo
from weboob.capabilities.image import Thumbnail
from weboob.capabilities.base import NotAvailable, NotLoaded, find_object
from weboob.capabilities.cinema import Movie, Person
from weboob.browser.browsers import APIBrowser
from weboob.browser.profiles import Android
from weboob.tools.compat import urlencode
import base64
import hashlib
from datetime import datetime, date, timedelta
import time
from datetime import date, datetime, timedelta
from weboob.browser.browsers import APIBrowser
from weboob.browser.profiles import Android
from weboob.capabilities.base import NotAvailable, NotLoaded, find_object
from weboob.capabilities.calendar import CATEGORIES, STATUS, TRANSP, BaseCalendarEvent
from weboob.capabilities.cinema import Movie, Person
from weboob.capabilities.collection import Collection
from weboob.capabilities.image import Thumbnail
from weboob.capabilities.video import BaseVideo
from weboob.tools.compat import unicode, urlencode
__all__ = ['AllocineBrowser']
......
......@@ -19,11 +19,12 @@
import re
from weboob.capabilities.base import UserError
from weboob.capabilities.calendar import CapCalendarEvent, CATEGORIES, BaseCalendarEvent
from weboob.capabilities.video import CapVideo, BaseVideo
from weboob.capabilities.collection import CapCollection, CollectionNotFound, Collection
from weboob.capabilities.cinema import CapCinema, Person, Movie
from weboob.capabilities.calendar import CATEGORIES, BaseCalendarEvent, CapCalendarEvent
from weboob.capabilities.cinema import CapCinema, Movie, Person
from weboob.capabilities.collection import CapCollection, Collection, CollectionNotFound
from weboob.capabilities.video import BaseVideo, CapVideo
from weboob.tools.backend import Module
from weboob.tools.compat import unicode
from .browser import AllocineBrowser
......
......@@ -41,7 +41,7 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
L_SUBSCRIBER = 'Nom : (.*) Modifier E-mail'
login = URL(r'/ap/signin(.*)', LoginPage)
home = URL(r'/$', HomePage)
home = URL(r'/$', r'/\?language=\w+$', HomePage)
panel = URL('/gp/css/homepage.html/ref=nav_youraccount_ya', PanelPage)
subscriptions = URL(r'/ap/cnep(.*)', SubscriptionsPage)
documents = URL(r'/gp/your-account/order-history\?opt=ab&digitalOrders=1(.*)&orderFilter=year-(?P<year>.*)',
......@@ -170,7 +170,11 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
def iter_subscription(self):
self.location(self.panel.go().get_sub_link())
if not self.subscriptions.is_here():
if self.home.is_here():
if self.page.get_login_link():
self.is_login()
self.location(self.page.get_panel_link())
elif not self.subscriptions.is_here():
self.is_login()
yield self.page.get_item()
......
......@@ -35,6 +35,9 @@ class HomePage(HTMLPage):
def get_login_link(self):
return self.doc.xpath('//a[./span[contains(., "%s")]]/@href' % self.browser.L_SIGNIN)[0]
def get_panel_link(self):
return Link('//a[contains(@href, "homepage.html") and has-class(@nav-link)]')(self.doc)
class PanelPage(LoggedPage, HTMLPage):
def get_sub_link(self):
......@@ -125,7 +128,7 @@ class DocumentsPage(LoggedPage, HTMLPage):
obj_id = Format('%s_%s', Env('subid'), Field('_simple_id'))
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_url = Async('details') & Link('//a[contains(@href, "download")]|//a[contains(@href, "generated_invoices")]')
obj_format = 'pdf'
obj_label = Format('Facture %s', Field('_simple_id'))
obj_type = 'bill'
......
......@@ -18,18 +18,17 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from weboob.capabilities.bank import AccountNotFound
from weboob.browser import LoginBrowser, URL, need_login
from weboob.exceptions import BrowserIncorrectPassword
from weboob.tools.compat import unquote
import json
import os
from subprocess import STDOUT, CalledProcessError, check_output
from tempfile import mkstemp
from subprocess import check_output, STDOUT, CalledProcessError
from .pages import SomePage, StatementsPage, StatementPage, SummaryPage, \
ActivityPage
from weboob.browser import URL, LoginBrowser, need_login
from weboob.capabilities.bank import AccountNotFound
from weboob.exceptions import BrowserIncorrectPassword
from weboob.tools.compat import unquote
from .pages import ActivityPage, SomePage, StatementPage, StatementsPage, SummaryPage
__all__ = ['AmazonStoreCard']
......@@ -66,24 +65,24 @@ class AmazonStoreCard(LoginBrowser):
'agent': self.session.headers['User-Agent']})
os.close(scrf)
os.close(cookf)
for i in xrange(self.MAX_RETRIES):
for i in range(self.MAX_RETRIES):
try:
check_output(["phantomjs", scrn], stderr=STDOUT)
break
except CalledProcessError as error:
pass
last_error = error
else:
raise error
raise last_error
with open(cookn) as cookf:
cookies = json.loads(cookf.read())
os.remove(scrn)
os.remove(cookn)
self.session.cookies.clear()
for c in cookies:
for k in ['expiry', 'expires', 'httponly']:
c.pop(k, None)
c['value'] = unquote(c['value'])
self.session.cookies.set(**c)
for k in ['expiry', 'expires', 'httponly']:
c.pop(k, None)
c['value'] = unquote(c['value'])
self.session.cookies.set(**c)
if not self.summary.go().logged:
raise BrowserIncorrectPassword()
......@@ -106,6 +105,7 @@ class AmazonStoreCard(LoginBrowser):
for t in s.iter_transactions():
yield t
LOGIN_JS = u'''\
var TIMEOUT = %(timeout)s*1000; // milliseconds
var page = require('webpage').create();
......
......@@ -19,12 +19,11 @@
from weboob.capabilities.bank import CapBank
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.backend import BackendConfig, Module
from weboob.tools.value import ValueBackendPassword
from .browser import AmazonStoreCard
__all__ = ['AmazonStoreCardModule']
......@@ -39,16 +38,16 @@ class AmazonStoreCardModule(Module, CapBank):
ValueBackendPassword('username', label='User ID', masked=False),
ValueBackendPassword('password', label='Password'),
ValueBackendPassword('phone',
label='Phone to send verification code to', masked=False),
label='Phone to send verification code to', masked=False),
ValueBackendPassword('code_file',
label='File to read the verification code from', masked=False))
label='File to read the verification code from', masked=False))
BROWSER = AmazonStoreCard
def create_default_browser(self):
return self.create_browser(username = self.config['username'].get(),
password = self.config['password'].get(),
phone = self.config['phone'].get(),
code_file = self.config['code_file'].get())
return self.create_browser(username=self.config['username'].get(),
password=self.config['password'].get(),
phone=self.config['phone'].get(),
code_file=self.config['code_file'].get())
def iter_accounts(self):
return self.browser.iter_accounts()
......
......@@ -26,10 +26,18 @@ from weboob.tools.date import closest_date
from weboob.tools.pdf import decompress_pdf
from weboob.tools.tokenizer import ReTokenizer
from datetime import datetime, timedelta
from weboob.tools.compat import unicode
import re
import json
try:
cmp = cmp
except NameError:
def cmp(x, y):
return (x > y) - (x < y)
class SomePage(HTMLPage):
@property
def logged(self):
......@@ -39,7 +47,7 @@ class SomePage(HTMLPage):
class SummaryPage(SomePage):
def account(self):
label = u' '.join(self.doc.xpath(
'//div[contains(@class,"myCreditCardDetails")]')[0]\
'//div[contains(@class,"myCreditCardDetails")]')[0]
.text_content().split())
balance = self.amount(u'Balance')
cardlimit = self.doc.xpath(
......@@ -69,8 +77,8 @@ class SummaryPage(SomePage):
def amount(self, name):
return u''.join(self.doc.xpath(
u'//li[text()[.="%s"]]/../li[1]'%name)[0].text_content().split())\
.replace(u'\xb7',u'.').replace(u'*',u'')
u'//li[text()[.="%s"]]/../li[1]' % name)[0].text_content().split())\
.replace(u'\xb7', u'.').replace(u'*', u'')
class ActivityPage(SomePage):
......@@ -98,19 +106,20 @@ class ActivityPage(SomePage):
def parse_date(recdate):
return datetime.strptime(recdate, u'%B %d, %Y')
class StatementsPage(SomePage):
def iter_statements(self):
jss = self.doc.xpath(u'//a/@onclick[contains(.,"eBillViewPDFAction")]')
for js in jss:
url = re.match("window.open\('([^']*).*\)", js).group(1)
for i in xrange(self.browser.MAX_RETRIES):
for i in range(self.browser.MAX_RETRIES):
try:
self.browser.location(url)
break
except ServerError as e:
pass
last_error = e
else:
raise e
raise last_error
yield self.browser.page
......@@ -163,7 +172,7 @@ class StatementPage(RawPage):
pos, amount_layout = self.read_layout_td(pos)
pos, amount = self.read_amount(pos)
if tdate is None or pdate is None \
or desc is None or amount is None or amount == 0:
or desc is None or amount is None or amount == 0:
return startPos, None
else:
tdate = closest_date(tdate, date_from, date_to)
......@@ -208,7 +217,7 @@ class StatementPage(RawPage):
def read_text(self, pos):
t = self._tok.tok(pos)
#TODO: handle PDF encodings properly.
# TODO: handle PDF encodings properly.
return (pos+1, unicode(t.value(), errors='ignore')) \
if t.is_text() else (pos, None)
......
......@@ -85,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(r'[^\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
......@@ -118,7 +118,7 @@ class LastPaymentsPage(LoggedPage, AmeliBasePage):
try:
elt.xpath('.//a[contains(@id,"lienPDFReleve")]')[0]
except IndexError:
continue
continue
date_str = elt.xpath('.//span[contains(@id,"moisEnCours")]')[0].text
month_str = date_str.split()[0]
date = datetime.strptime(re.sub(month_str, str(FRENCH_MONTHS.index(month_str) + 1), date_str), "%m %Y").date()
......
......@@ -17,8 +17,6 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
import ssl
from .pages import LoginPage, AccountsPage, AccountHistoryPage
from weboob.browser import URL, LoginBrowser, need_login
from weboob.tools.json import json
......@@ -29,35 +27,18 @@ from weboob.browser.exceptions import ClientError
class AmundiBrowser(LoginBrowser):
TIMEOUT = 120.0
login = URL('/psf/authenticate', LoginPage)
authorize = URL('/psf/authorize', LoginPage)
accounts = URL('/psf/api/individu/positionFonds\?flagUrlFicheFonds=true&inclurePositionVide=false', AccountsPage)
account_history = URL('/psf/api/individu/operations\?valeurExterne=false&filtreStatutModeExclusion=false&statut=CPTA', AccountHistoryPage)
def __init__(self, website, *args, **kwargs):
self.BASEURL = website
super(AmundiBrowser, self).__init__(*args, **kwargs)
def prepare_request(self, req):
"""
Amundi uses TLS v1.0.
"""
preq = super(AmundiBrowser, self).prepare_request(req)
conn = self.session.adapters['https://'].get_connection(preq.url)
conn.ssl_version = ssl.PROTOCOL_TLSv1
return preq
login = URL(r'authenticate', LoginPage)
authorize = URL(r'authorize', LoginPage)
accounts = URL(r'api/individu/positionFonds\?flagUrlFicheFonds=true&inclurePositionVide=false', AccountsPage)
account_history = URL(r'api/individu/operations\?valeurExterne=false&filtreStatutModeExclusion=false&statut=CPTA', 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)
try:
self.login.go(data=json.dumps({'username' : self.username, 'password' : self.password}), \
self.login.go(data=json.dumps({'username': self.username, 'password': self.password}),
headers={'Content-Type': 'application/json;charset=UTF-8'})
self.token = self.authorize.go().get_token()
except ClientError:
......@@ -65,13 +46,22 @@ class AmundiBrowser(LoginBrowser):
@need_login
def iter_accounts(self):
return self.accounts.go(headers={'X-noee-authorization': ('noeprd %s' % self.token)}).iter_accounts()
return (self.accounts.go(headers={'X-noee-authorization': ('noeprd %s' % self.token)})
.iter_accounts())
@need_login
def iter_investments(self, account):
return self.accounts.go(headers={'X-noee-authorization': ('noeprd %s' % self.token)})\
.iter_investments(account_id=account.id)
return (self.accounts.go(headers={'X-noee-authorization': ('noeprd %s' % self.token)})
.iter_investments(account_id=account.id))
@need_login
def iter_history(self, account):
return self.account_history.go(headers={'X-noee-authorization': ('noeprd %s' % self.token)}).iter_history(account=account)
return (self.account_history.go(headers={'X-noee-authorization': ('noeprd %s' % self.token)})
.iter_history(account=account))
class EEAmundi(AmundiBrowser):
BASEURL = 'https://www.amundi-ee.com/psf/'
class TCAmundi(AmundiBrowser):
BASEURL = 'https://epargnants.amundi-tc.com/psf/'
......
......@@ -35,6 +35,11 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
pass
class AddRecipientBankError(AddRecipientError):
code = 'bankMessage'
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
......@@ -23,7 +23,7 @@ from weboob.capabilities.base import find_object
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword, Value
from .browser import AmundiBrowser
from .browser import EEAmundi, TCAmundi
__all__ = ['AmundiModule']
......@@ -35,59 +35,27 @@ class AmundiModule(Module, CapBankWealth):
EMAIL = 'james.galt.bi@gmail.com'
LICENSE = 'AGPLv3+'
VERSION = '1.3'
CONFIG = BackendConfig(ValueBackendPassword('login', label='Identifiant', regexp='\d+', masked=False),
ValueBackendPassword('password', label=u"Mot de passe", regexp='\d+'),
CONFIG = BackendConfig(ValueBackendPassword('login', label='Identifiant', regexp=r'\d+', masked=False),
ValueBackendPassword('password', label=u"Mot de passe", regexp=r'\d+'),
Value('website', label='Type de compte', default='ee',
choices={'ee': 'Amundi Epargne Entreprise',
'tc': 'Amundi Tenue de Compte'}))
BROWSER = AmundiBrowser
choices={'ee': 'Amundi Epargne Entreprise',
'tc': 'Amundi Tenue de Compte'}))
def create_default_browser(self):
w = {'ee': 'https://www.amundi-ee.com', 'tc': 'https://epargnants.amundi-tc.com'}
return self.create_browser(w[self.config['website'].get()], self.config['login'].get(), self.config['password'].get())
b = {'ee': EEAmundi, 'tc': TCAmundi}
self.BROWSER = b[self.config['website'].get()]
return self.create_browser(self.config['login'].get(), self.config['password'].get())
def get_account(self, id):
"""
Get an account from its ID.
:param id: ID of the account
:type id: :class:`str`
:rtype: :class:`Account`
:raises: :class:`AccountNotFound`
"""
return find_object(self.iter_accounts(), id=id, error=AccountNotFound)
def iter_accounts(self):
"""
Iter accounts.
:rtype: iter[:class:`Account`]
"""
return self.browser.iter_accounts()
def iter_investment(self, account):
"""
Iter investment of a market account
:param account: account to get investments
:type account: :class:`Account`
:rtype: iter[:class:`Investment`]
:raises: :class:`AccountNotFound`
"""
for inv in self.browser.iter_investments(account):
if inv.valuation != 0:
yield inv
def iter_history(self, account):
"""
Iter history of transactions on a specific account.
:param account: account to get history
:type account: :class:`Account`
:rtype: iter[:class:`Transaction`]
:raises: :class:`AccountNotFound`
"""
return self.browser.iter_history(account)
......
......@@ -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 datetime import datetime
from weboob.browser.elements import ItemElement, method, DictElement
......@@ -26,6 +28,7 @@ from weboob.browser.pages import LoggedPage, JsonPage
from weboob.capabilities.bank import Account, Investment, Transaction
from weboob.capabilities.base import NotAvailable
from weboob.exceptions import NoAccountsException
from weboob.tools.capabilities.bank.investments import is_isin_valid
class LoginPage(JsonPage):
......@@ -59,7 +62,7 @@ class AccountsPage(LoggedPage, JsonPage):
# just the id is a kind of company id so it can be unique on a backend but not unique on multiple backends
return '%s_%s' % (Field('id')(self), self.page.browser.username)
obj_currency = u"EUR"
obj_currency = 'EUR'
def obj_type(self):
return self.page.ACCOUNT_TYPES.get(Dict('typeDispositif')(self), Account.TYPE_LIFE_INSURANCE)
......@@ -67,7 +70,7 @@ class AccountsPage(LoggedPage, JsonPage):
def obj_label(self):
try:
return Dict('libelleDispositif')(self).encode('iso-8859-2').decode('utf8')
except (UnicodeEncodeError, UnicodeDecodeError):
except UnicodeError:
try:
return Dict('libelleDispositif')(self).encode('latin1').decode('utf8')
except UnicodeDecodeError:
......@@ -92,12 +95,17 @@ class AccountsPage(LoggedPage, JsonPage):
obj_code = Dict('codeIsin', default=NotAvailable)
obj_vdate = Date(Dict('dtVl'))
def obj_code_type(self):
if is_isin_valid(Field('code')(self)):
return Investment.CODE_TYPE_ISIN
return NotAvailable
class AccountHistoryPage(LoggedPage, JsonPage):
def belongs(self, instructions, account):
for ins in instructions:
if 'nomDispositif' in ins and 'codeDispositif' in ins and '%s%s' % (ins['nomDispositif'], ins['codeDispositif']) == \
'%s%s' % (account.label, account.id):
if 'nomDispositif' in ins and 'codeDispositif' in ins and '%s%s' % (
ins['nomDispositif'], ins['codeDispositif']) == '%s%s' % (account.label, account.id):
return True
return False
......@@ -105,8 +113,9 @@ class AccountHistoryPage(LoggedPage, JsonPage):
amount = 0
for ins in instructions:
if 'nomDispositif' in ins and 'montantNet' in ins and 'codeDispositif' in ins and '%s%s' % (ins['nomDispositif'], ins['codeDispositif']) == \
'%s%s' % (account.label, account.id):
if ('nomDispositif' in ins and 'montantNet' in ins and 'codeDispositif' in ins
and '%s%s' % (ins['nomDispositif'], ins['codeDispositif'])
== '%s%s' % (account.label, account.id)):
amount += ins['montantNet']
return CleanDecimal().filter(amount)
......@@ -119,21 +128,7 @@ class AccountHistoryPage(LoggedPage, JsonPage):
tr.amount = self.get_amount(hist['instructions'], account)
tr.rdate = datetime.strptime(hist['dateComptabilisation'].split('T')[0], '%Y-%m-%d')
tr.date = tr.rdate
tr.label = hist['libelleOperation'] if 'libelleOperation' in hist else hist['libelleCommunication']
tr.label = hist.get('libelleOperation') or hist['libelleCommunication']
tr.type = Transaction.TYPE_UNKNOWN
# Bypassed because we don't have the ISIN code
# tr.investments = []
# for ins in hist['instructions']:
# inv = Investment()
# inv.code = NotAvailable
# inv.label = ins['nomFonds']
# inv.description = ' '.join([ins['type'], ins['nomDispositif']])
# inv.vdate = datetime.strptime(ins.get('dateVlReel', ins.get('dateVlExecution')).split('T')[
# 0], '%Y-%m-%d')
# inv.valuation = Decimal(ins['montantNet'])
# inv.quantity = Decimal(ins['nombreDeParts'])
# inv.unitprice = inv.unitvalue = Decimal(ins['vlReel'])
# tr.investments.append(inv)
yield tr
......
......@@ -35,6 +35,11 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
pass
class AddRecipientBankError(AddRecipientError):
code = 'bankMessage'
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
......@@ -24,7 +24,7 @@ from calendar import day_name
from dateutil.relativedelta import relativedelta
import re
from weboob.browser import LoginBrowser, URL, need_login, StatesMixin
from weboob.browser.browsers import LoginBrowser, URL, need_login, StatesMixin
from weboob.browser.exceptions import ClientError, HTTPNotFound
from weboob.capabilities.base import NotAvailable
from weboob.capabilities.bill import Subscription
......@@ -32,6 +32,7 @@ from weboob.capabilities.bank import Account, Transaction, AddRecipientStep, Rec
from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded
from weboob.tools.value import Value
from weboob.tools.capabilities.bank.transactions import sorted_transactions
from .compat.weboob_tools_capabilities_bank_investments import create_french_liquidity
from .pages.login import (
KeyboardPage, LoginPage, ChangepasswordPage, PredisconnectedPage, DeniedPage,
......@@ -39,9 +40,9 @@ from .pages.login import (
)
from .pages.bank import (
AccountsPage as BankAccountsPage, CBTransactionsPage, TransactionsPage,
UnavailablePage, IbanPage, LifeInsuranceIframe, BoursePage,
UnavailablePage, IbanPage, LifeInsuranceIframe, BoursePage, BankProfilePage,
)
from .pages.wealth import AccountsPage as WealthAccountsPage, InvestmentPage, HistoryPage
from .pages.wealth import AccountsPage as WealthAccountsPage, InvestmentPage, HistoryPage, ProfilePage
from .pages.transfer import (
RecipientsPage, AddRecipientPage, ValidateTransferPage, RegisterTransferPage,
ConfirmTransferPage, RecipientConfirmationPage,
......@@ -135,6 +136,7 @@ class AXABanque(AXABrowser, StatesMixin):
'webapp/axabanque/jsp/virementSepa/saisieVirementSepa.faces',
RegisterTransferPage)
confirm_transfer = URL('/webapp/axabanque/jsp/virementSepa/confirmationVirementSepa.faces', ConfirmTransferPage)
profile_page = URL('/transactionnel/client/coordonnees.html', BankProfilePage)
reload_state = None
......@@ -174,8 +176,8 @@ class AXABanque(AXABrowser, StatesMixin):
ids.add(a.id)
#The url giving life insurrance investments seems to be temporary.
#That's why we have to get them now
# The url giving life insurrance investments seems to be temporary.
# That's why we have to get them now
if a.type == a.TYPE_LIFE_INSURANCE:
self.cache['invs'][a.id] = list(self.open(a._url).page.iter_investment())
args = a._args
......@@ -186,7 +188,7 @@ class AXABanque(AXABrowser, StatesMixin):
'codeFamille': args['paramCodeFamille'],
'codeProduit': args['paramCodeProduit'],
'codeSousProduit': args['paramCodeSousProduit']
}
}
try:
r = self.open('/webapp/axabanque/popupPDF', params=iban_params)
a.iban = r.page.get_iban()
......@@ -256,7 +258,7 @@ class AXABanque(AXABrowser, StatesMixin):
self.transactions.go()
if account._acctype == 'bank' and account.type in (Account.TYPE_PEA, Account.TYPE_MARKET):
if 'Liquidités' in account.label:
return [self.page.get_liquidity_investment(account)]
return [create_french_liquidity(account.balance)]
account = self.get_netfinca_account(account)
self.location(account._market_link)
......@@ -290,7 +292,7 @@ class AXABanque(AXABrowser, StatesMixin):
self.location(acc._market_link)
self.bourse_history.go()
if not 'Liquidités' in account.label:
if 'Liquidités' not in account.label:
self.page.go_history_filter(cash_filter="market")
else:
self.page.go_history_filter(cash_filter="liquidity")
......@@ -488,6 +490,11 @@ class AXABanque(AXABrowser, StatesMixin):
def download_document(self, url):
raise NotImplementedError()
@need_login
def get_profile(self):
self.profile_page.go()
return self.page.get_profile()
class AXAAssurance(AXABrowser):
BASEURL = 'https://espaceclient.axa.fr'
......@@ -500,6 +507,7 @@ class AXAAssurance(AXABrowser):
download = URL('/content/ecc-popin-cards/technical/detailed/document.downloadPdf.html',
'/content/ecc-popin-cards/technical/detailed/document/_jcr_content/',
DownloadPage)
profile = URL(r'/content/ecc-popin-cards/transverse/userprofile.content-inner.html\?_=\d+', ProfilePage)
def __init__(self, *args, **kwargs):
super(AXAAssurance, self).__init__(*args, **kwargs)
......@@ -578,3 +586,8 @@ class AXAAssurance(AXABrowser):
self.location(url)
self.page.create_document()
return self.page.content
@need_login
def get_profile(self):
self.profile.go()
return self.page.get_profile()
......
......@@ -35,6 +35,11 @@ class CapBankTransferAddRecipient(CapBankTransfer, OLD.CapBankTransferAddRecipie
pass
class AddRecipientBankError(AddRecipientError):
code = 'bankMessage'
Account.TYPE_MORTGAGE = 17
Account.TYPE_CONSUMER_CREDIT = 18
Account.TYPE_REVOLVING_CREDIT = 19
......
# -*- coding: utf-8 -*-
# Copyright(C) 2017 Jonathan Schmidt
#
# 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
import re
from weboob.tools.compat import basestring
from weboob.capabilities.base import NotAvailable
from weboob.capabilities.bank import Investment
def is_isin_valid(isin):
"""
Méthode générale
Table de conversion des lettres en chiffres
A=10 B=11 C=12 D=13 E=14 F=15 G=16 H=17 I=18
J=19 K=20 L=21 M=22 N=23 O=24 P=25 Q=26 R=27
S=28 T=29 U=30 V=31 W=32 X=33 Y=34 Z=35
1 - Mettre de côté la clé, qui servira de référence à la fin de la vérification.
2 - Convertir toutes les lettres en nombres via la table de conversion ci-contre. Si le nombre obtenu est supérieur ou égal à 10, prendre les deux chiffres du nombre séparément (exemple : 27 devient 2 et 7).
3 - Pour chaque chiffre, multiplier sa valeur par deux si sa position est impaire en partant de la droite. Si le nombre obtenu est supérieur ou égal à 10, garder les deux chiffres du nombre séparément (exemple : 14 devient 1 et 4).
4 - Faire la somme de tous les chiffres.
5 - Soustraire cette somme de la dizaine supérieure ou égale la plus proche (exemples : si la somme vaut 22, la dizaine « supérieure ou égale » est 30, et la clé vaut donc 8 ; si la somme vaut 30, la dizaine « supérieure ou égale » est 30, et la clé vaut 0 ; si la somme vaut 31, la dizaine « supérieure ou égale » est 40, et la clé vaut 9).
6 - Comparer la valeur obtenue à la clé mise initialement de côté.
Étapes 1 et 2 :
F R 0 0 0 3 5 0 0 0 0 (+ 8 : clé)
15 27 0 0 0 3 5 0 0 0 0
Étape 3 : le traitement se fait sur des chiffres
1 5 2 7 0 0 0 3 5 0 0 0 0
I P I P I P I P I P I P I : position en partant de la droite (P = Pair, I = Impair)
2 1 2 1 2 1 2 1 2 1 2 1 2 : coefficient multiplicateur
2 5 4 7 0 0 0 3 10 0 0 0 0 : résultat
Étape 4 :
2 + 5 + 4 + 7 + 0 + 0 + 0 + 3 + (1 + 0)+ 0 + 0 + 0 + 0 = 22
Étapes 5 et 6 : 30 - 22 = 8 (valeur de la clé)
"""
if not isinstance(isin, basestring):
return False
if not re.match(r'^[A-Z]{2}[A-Z0-9]{9}\d$', isin):
return False
isin_in_digits = ''.join(str(ord(x) - ord('A') + 10) if not x.isdigit() else x for x in isin[:-1])
key = isin[-1:]
result = ''
for k, val in enumerate(isin_in_digits[::-1], start=1):
if k % 2 == 0:
result = ''.join((result, val))
else:
result = ''.join((result, str(int(val)*2)))
return str(sum(int(x) for x in result) + int(key))[-1] == '0'
def create_french_liquidity(valuation):
"""
Automatically fills a liquidity investment with label, code and code_type.
"""
liquidity = Investment()
liquidity.label = "Liquidités"
liquidity.code = "XX-liquidity"
liquidity.code_type = NotAvailable
liquidity.valuation = valuation
return liquidity
......@@ -21,6 +21,7 @@
from .compat.weboob_capabilities_bank import CapBankWealth, CapBankTransferAddRecipient, AccountNotFound, RecipientNotFound
from weboob.capabilities.base import find_object, NotAvailable
from weboob.capabilities.bank import Account, TransferInvalidLabel
from weboob.capabilities.profile import CapProfile
from weboob.capabilities.bill import CapDocument, Subscription, Document, DocumentNotFound, SubscriptionNotFound
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword
......@@ -31,7 +32,7 @@ from .browser import AXABanque, AXAAssurance
__all__ = ['AXABanqueModule']
class AXABanqueModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapDocument):
class AXABanqueModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapDocument, CapProfile):
NAME = 'axabanque'
MAINTAINER = u'Romain Bignon'
EMAIL = 'romain@weboob.org'
......@@ -81,9 +82,10 @@ class AXABanqueModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapDoc
raise TransferInvalidLabel()
self.logger.info('Going to do a new transfer')
if transfer.account_iban:
account = find_object(self.iter_accounts(), iban=transfer.account_iban, error=AccountNotFound)
else:
# origin account iban can be NotAvailable
account = find_object(self.iter_accounts(), iban=transfer.account_iban)
if not account:
account = find_object(self.iter_accounts(), id=transfer.account_id, error=AccountNotFound)
if transfer.recipient_iban:
......@@ -106,7 +108,17 @@ class AXABanqueModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapDoc
def transfer_check_account_id(self, old, new):
old = old[:11]
return super(AXABanqueModule, self).transfer_check_label(old, new)
return old == new
def transfer_check_account_iban(self, old, new):
# Skip origin account iban check and force origin account iban
if new is NotAvailable: