diff --git a/modules/nalo/__init__.py b/modules/nalo/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b0d1c8d2bb8f914f4d1337c7d40d33ab5d97ad8
--- /dev/null
+++ b/modules/nalo/__init__.py
@@ -0,0 +1,26 @@
+# -*- 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 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 .
+
+from __future__ import unicode_literals
+
+
+from .module import NaloModule
+
+
+__all__ = ['NaloModule']
diff --git a/modules/nalo/browser.py b/modules/nalo/browser.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b1aa5ec84cab611f403bc63fbcb72fa74e277b0
--- /dev/null
+++ b/modules/nalo/browser.py
@@ -0,0 +1,77 @@
+# -*- 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 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 .
+
+from __future__ import unicode_literals
+
+from weboob.browser import LoginBrowser, need_login, URL
+from weboob.capabilities.bank import Investment
+
+from .pages import LoginPage, AccountsPage, AccountPage, InvestPage
+
+
+class NaloBrowser(LoginBrowser):
+ BASEURL = 'https://nalo.fr'
+
+ login = URL(r'/api/v1/login', LoginPage)
+ accounts = URL(r'/api/v1/projects/mine/without_details', AccountsPage)
+ history = URL(r'/api/v1/projects/(?P\d+)/history')
+ account = URL(r'/api/v1/projects/(?P\d+)', AccountPage)
+ invests = URL(r'https://app.nalo.fr/scripts/data/data.json', InvestPage)
+
+ token = None
+
+ def do_login(self):
+ self.login.go(json={
+ 'email': self.username,
+ 'password': self.password,
+ 'userToken': False,
+ })
+ self.token = self.page.get_token()
+
+ def build_request(self, *args, **kwargs):
+ if 'json' in kwargs:
+ kwargs.setdefault('headers', {})['Accept'] = 'application/json'
+ if self.token:
+ kwargs.setdefault('headers', {})['Authorization'] = 'Token %s' % self.token
+ return super(NaloBrowser, self).build_request(*args, **kwargs)
+
+ @need_login
+ def iter_accounts(self):
+ self.accounts.go()
+ return self.page.iter_accounts()
+
+ @need_login
+ def iter_history(self, account):
+ self.history.go(id=account.id)
+ return self.page.iter_history()
+
+ @need_login
+ def iter_investment(self, account):
+ self.account.go(id=account.id)
+ key = self.page.get_invest_key()
+
+ self.invests.go()
+ data = self.page.get_invest(*key)
+ for item in data:
+ inv = Investment()
+ inv.code = item['isin']
+ inv.label = item['name']
+ inv.portfolio_share = item['share']
+ inv.valuation = account.balance * inv.portfolio_share
+ yield inv
diff --git a/modules/nalo/module.py b/modules/nalo/module.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1aefccbb5424e08e2b60eb0d4b53c1ec49d9a0f
--- /dev/null
+++ b/modules/nalo/module.py
@@ -0,0 +1,54 @@
+# -*- 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 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 .
+
+from __future__ import unicode_literals
+
+from weboob.tools.backend import Module, BackendConfig
+from weboob.tools.value import ValueBackendPassword
+from weboob.capabilities.bank import CapBankWealth
+
+from .browser import NaloBrowser
+
+
+__all__ = ['NaloModule']
+
+
+class NaloModule(Module, CapBankWealth):
+ NAME = 'nalo'
+ DESCRIPTION = 'Nalo'
+ MAINTAINER = 'Vincent A'
+ EMAIL = 'dev@indigo.re'
+ LICENSE = 'AGPLv3+'
+ VERSION = '1.4'
+
+ BROWSER = NaloBrowser
+
+ CONFIG = BackendConfig(
+ ValueBackendPassword('login', label='E-mail', masked=False, regexp='.+@.+'),
+ ValueBackendPassword('password', label='Mot de passe'),
+ )
+
+ def create_default_browser(self):
+ return self.create_browser(self.config['login'].get(), self.config['password'].get())
+
+ def iter_accounts(self):
+ return self.browser.iter_accounts()
+
+ def iter_investment(self, account):
+ return self.browser.iter_investment(account)
diff --git a/modules/nalo/pages.py b/modules/nalo/pages.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f8b64fe076fe06ed2c96d207178abda16adcb55
--- /dev/null
+++ b/modules/nalo/pages.py
@@ -0,0 +1,94 @@
+# -*- 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 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 .
+
+from __future__ import unicode_literals
+
+from decimal import Decimal
+
+from weboob.browser.pages import LoggedPage, JsonPage
+from weboob.browser.elements import method, DictElement, ItemElement
+from weboob.browser.filters.json import Dict
+from weboob.browser.filters.standard import Eval
+from weboob.capabilities.bank import Account
+
+
+def float_to_decimal(v):
+ return Decimal(str(v))
+
+
+class LoginPage(JsonPage):
+ def get_token(self):
+ return self.doc['detail']['token']
+
+
+class AccountsPage(LoggedPage, JsonPage):
+ ENCODING = 'utf-8' # chardet is shit
+
+ @method
+ class iter_accounts(DictElement):
+ item_xpath = 'detail'
+
+ class item(ItemElement):
+ klass = Account
+
+ obj_id = Eval(str, Dict('id'))
+ obj_label = Dict('name')
+ obj_balance = Eval(float_to_decimal, Dict('current_value'))
+ obj_valuation_diff = Eval(float_to_decimal, Dict('absolute_performance'))
+ obj_currency = 'EUR'
+ obj_type = Account.TYPE_LIFE_INSURANCE
+
+
+class AccountPage(LoggedPage, JsonPage):
+ def get_invest_key(self):
+ return self.doc['detail']['project_kind'], self.doc['detail']['risk_level']
+
+ def get_kind(self):
+ return self.doc['detail']['project_kind']
+
+ def get_risk(self):
+ return self.doc['detail']['risk_level']
+
+
+class HistoryPage(LoggedPage, JsonPage):
+ pass
+
+
+class InvestPage(LoggedPage, JsonPage):
+ ENCODING = 'utf-8'
+
+ def get_invest(self, kind, risk):
+ for pk in self.doc['portfolios']:
+ if pk['kind'] == kind:
+ break
+ else:
+ assert False
+
+ for p in pk['target_portfolios']:
+ if p['risk_id'] == risk:
+ break
+ else:
+ assert False
+
+ for line in p['lines']:
+ yield {
+ 'isin': line['isin'],
+ 'name': line['name'],
+ 'share': float_to_decimal(line['weight']) / 100,
+ }
diff --git a/tools/py3-compatible.modules b/tools/py3-compatible.modules
index 71b7e2d0a95abe43b5ecb83da6085a552c4a272b..a773dba0224ddec49cd38ce96136b0b7c6782b00 100644
--- a/tools/py3-compatible.modules
+++ b/tools/py3-compatible.modules
@@ -96,6 +96,7 @@ meteofrance
monster
myfoncia
n26
+nalo
nectarine
nova
oney