-
Notifications
You must be signed in to change notification settings - Fork 15
/
linky.py
122 lines (91 loc) · 4.19 KB
/
linky.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Retrieves energy consumption data from your Enedis (ERDF) account."""
# Linkindle - Linky energy consumption curves on a Kindle display.
# Copyright (C) 2016 Baptiste Candellier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import requests
import html
from fake_useragent import UserAgent
LOGIN_BASE_URI = 'https://espace-client-connexion.enedis.fr'
API_BASE_URI = 'https://espace-client-particuliers.enedis.fr/group/espace-particuliers'
API_ENDPOINT_LOGIN = '/auth/UI/Login'
API_ENDPOINT_HOME = '/home'
API_ENDPOINT_DATA = '/suivi-de-consommation'
DATA_NOT_REQUESTED = -1
DATA_NOT_AVAILABLE = -2
class LinkyLoginException(Exception):
"""Thrown if an error was encountered while retrieving energy consumption data."""
pass
class LinkyServiceException(Exception):
"""Thrown when the webservice threw an exception."""
pass
def login(username, password):
"""Logs the user into the Linky API.
"""
session = requests.Session()
session.headers.update({'User-agent': str(UserAgent().random)})
payload = {'IDToken1': username,
'IDToken2': password,
'SunQueryParamsString': base64.b64encode(b'realm=particuliers'),
'encoded': 'true',
'gx_charset': 'UTF-8'}
req = session.post(LOGIN_BASE_URI + API_ENDPOINT_LOGIN, data=payload, allow_redirects=False)
if not 'iPlanetDirectoryPro' in session.cookies:
raise LinkyLoginException("Login unsuccessful. Check your credentials.")
return session
def get_data_per_hour(session, start_date, end_date):
"""Retrieves hourly energy consumption data."""
return _get_data(session, 'urlCdcHeure', start_date, end_date)
def get_data_per_day(session, start_date, end_date):
"""Retrieves daily energy consumption data."""
return _get_data(session, 'urlCdcJour', start_date, end_date)
def get_data_per_month(session, start_date, end_date):
"""Retrieves monthly energy consumption data."""
return _get_data(session, 'urlCdcMois', start_date, end_date)
def get_data_per_year(session):
"""Retrieves yearly energy consumption data."""
return _get_data(session, 'urlCdcAn')
def _get_data(session, resource_id, start_date=None, end_date=None):
req_part = 'lincspartdisplaycdc_WAR_lincspartcdcportlet'
# We send the session token so that the server knows who we are
payload = {
'_' + req_part + '_dateDebut': start_date,
'_' + req_part + '_dateFin': end_date
}
params = {
'p_p_id': req_part,
'p_p_lifecycle': 2,
'p_p_state': 'normal',
'p_p_mode': 'view',
'p_p_resource_id': resource_id,
'p_p_cacheability': 'cacheLevelPage',
'p_p_col_id': 'column-1',
'p_p_col_pos': 1,
'p_p_col_count': 3
}
req = session.post(API_BASE_URI + API_ENDPOINT_DATA, allow_redirects=False, data=payload, params=params)
if 300 <= req.status_code < 400:
# So... apparently, we may need to do that once again if we hit a 302
# ¯\_(ツ)_/¯
req = session.post(API_BASE_URI + API_ENDPOINT_DATA, allow_redirects=False, data=payload, params=params)
if req.status_code == 200 and req.text is not None and "Conditions d'utilisation" in req.text:
raise LinkyLoginException("You need to accept the latest Terms of Use. Please manually log into the website, "
"then come back.")
res = req.json()
if res['etat'] and res['etat']['valeur'] == 'erreur' and res['etat']['erreurText']:
raise LinkyServiceException(html.unescape(res['etat']['erreurText']))
return res