pygazpar 1.3.0b23__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygazpar/api_client.py CHANGED
@@ -1,6 +1,5 @@
1
- import http.cookiejar
2
- import json
3
1
  import logging
2
+ import re
4
3
  import time
5
4
  import traceback
6
5
  from datetime import date
@@ -9,21 +8,20 @@ from typing import Any
9
8
 
10
9
  from requests import Response, Session
11
10
 
12
- SESSION_TOKEN_URL = "https://connexion.grdf.fr/api/v1/authn"
13
- SESSION_TOKEN_PAYLOAD = """{{
14
- "username": "{0}",
15
- "password": "{1}",
16
- "options": {{
17
- "multiOptionalFactorEnroll": "false",
18
- "warnBeforePasswordExpired": "false"
19
- }}
11
+ START_URL = "https://monespace.grdf.fr/"
12
+
13
+ MAIL_SESSION_TOKEN_URL = "https://connexion.grdf.fr/idp/idx/identify"
14
+ MAIL_SESSION_TOKEN_PAYLOAD = """{{
15
+ "identifier": "{0}",
16
+ "stateHandle": "{1}"
20
17
  }}"""
21
18
 
22
- AUTH_TOKEN_URL = "https://connexion.grdf.fr/login/sessionCookieRedirect"
23
- AUTH_TOKEN_PARAMS = """{{
24
- "checkAccountSetupComplete": "true",
25
- "token": "{0}",
26
- "redirectUrl": "https://monespace.grdf.fr"
19
+ PASSWORD_SESSION_TOKEN_URL = "https://connexion.grdf.fr/idp/idx/challenge/answer"
20
+ PASSWORD_SESSION_TOKEN_PAYLOAD = """{{
21
+ "credentials": {{
22
+ "passcode": "{0}"
23
+ }},
24
+ "stateHandle": "{1}"
27
25
  }}"""
28
26
 
29
27
  API_BASE_URL = "https://monespace.grdf.fr/api"
@@ -71,7 +69,7 @@ class APIClient:
71
69
  self._username = username
72
70
  self._password = password
73
71
  self._retry_count = retry_count
74
- self._session = None
72
+ self._session: Session | None = None
75
73
 
76
74
  # ------------------------------------------------------
77
75
  def login(self):
@@ -79,38 +77,66 @@ class APIClient:
79
77
  return
80
78
 
81
79
  session = Session()
82
- session.headers.update({"domain": "grdf.fr"})
83
- session.headers.update({"Content-Type": "application/json"})
84
- session.headers.update({"X-Requested-With": "XMLHttpRequest"})
80
+ session.headers.update({"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"})
85
81
 
86
- payload = SESSION_TOKEN_PAYLOAD.format(self._username, self._password)
82
+ start_response = session.get(START_URL)
83
+ if start_response.status_code != 200:
84
+ raise ServerError(
85
+ f"An error occurred while logging in start. Status code: {start_response.status_code} - {start_response.url}",
86
+ start_response.status_code,
87
+ )
87
88
 
88
- response = session.post(SESSION_TOKEN_URL, data=payload)
89
+ pattern = r'"stateToken"\s*:\s*"([^"]+)"'
90
+ match = re.search(pattern, start_response.text)
91
+ if match:
92
+ state_token_html = match.group(1)
93
+ state_token = state_token_html.replace("\\x2D", "-")
94
+ else:
95
+ raise ValueError("Cannot retrieve stateToken inside HTML response")
96
+
97
+ payload = MAIL_SESSION_TOKEN_PAYLOAD.format(self._username, state_token)
98
+ session.cookies.set("ln", self._username)
99
+
100
+ mail_response = session.post(
101
+ MAIL_SESSION_TOKEN_URL,
102
+ data=payload,
103
+ headers={"Accept": "application/json; okta-version=1.0.0", "Content-Type": "application/json"},
104
+ )
89
105
 
90
- if response.status_code != 200:
106
+ if mail_response.status_code != 200:
91
107
  raise ServerError(
92
- f"An error occurred while logging in. Status code: {response.status_code} - {response.text}",
93
- response.status_code,
108
+ f"An error occurred while logging in mail. Status code: {mail_response.status_code} - {mail_response.text}",
109
+ mail_response.status_code,
94
110
  )
95
111
 
96
- session_token = response.json().get("sessionToken")
112
+ state_handle = mail_response.json().get("stateHandle")
97
113
 
98
- jar = http.cookiejar.CookieJar()
114
+ payload = PASSWORD_SESSION_TOKEN_PAYLOAD.format(self._password, state_handle)
99
115
 
100
- self._session = Session() # pylint: disable=attribute-defined-outside-init
101
- self._session.headers.update({"Content-Type": "application/json"})
102
- self._session.headers.update({"X-Requested-With": "XMLHttpRequest"})
116
+ password_response = session.post(
117
+ PASSWORD_SESSION_TOKEN_URL,
118
+ data=payload,
119
+ headers={"Accept": "application/json; okta-version=1.0.0", "Content-Type": "application/json"},
120
+ )
103
121
 
104
- params = json.loads(AUTH_TOKEN_PARAMS.format(session_token))
122
+ if password_response.status_code != 200:
123
+ raise ServerError(
124
+ f"An error occurred while logging in password. Status code: {password_response.status_code} - {password_response.text}",
125
+ password_response.status_code,
126
+ )
105
127
 
106
- response = self._session.get(AUTH_TOKEN_URL, params=params, allow_redirects=True, cookies=jar) # type: ignore
128
+ success_url = password_response.json()["success"]["href"]
107
129
 
108
- if response.status_code != 200:
130
+ response_redirect = session.get(success_url)
131
+
132
+ if response_redirect.status_code != 200:
109
133
  raise ServerError(
110
- f"An error occurred while getting the auth token. Status code: {response.status_code} - {response.text}",
111
- response.status_code,
134
+ f"An error occurred while logging in response_redirect. Status code: {response_redirect.status_code} - {response_redirect.url}",
135
+ response_redirect.status_code,
112
136
  )
113
137
 
138
+ self._session = session
139
+
114
140
  # ------------------------------------------------------
115
141
  def is_logged_in(self) -> bool:
116
142
  return self._session is not None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygazpar
3
- Version: 1.3.0b23
3
+ Version: 1.3.1
4
4
  Summary: Python library to download gas consumption from a GrDF (French Gas Company) account
5
5
  License: MIT License
6
6
 
@@ -24,8 +24,7 @@ License: MIT License
24
24
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
25
  SOFTWARE.
26
26
  Author: Stéphane Senart
27
- Requires-Python: >=3.9
28
- Classifier: Programming Language :: Python :: 3.9
27
+ Requires-Python: >=3.10
29
28
  Classifier: Programming Language :: Python :: 3.10
30
29
  Classifier: Programming Language :: Python :: 3.11
31
30
  Classifier: Programming Language :: Python :: 3.12
@@ -37,8 +36,6 @@ Description-Content-Type: text/markdown
37
36
 
38
37
  # PyGazpar
39
38
 
40
- ## $\text{\color{green}{!!! This library is working again. CAPTCHA has been removed !!!}}$
41
-
42
39
  PyGazpar is a Python library for getting natural gas consumption from GrDF French provider.
43
40
 
44
41
  Their natural gas meter is called Gazpar. It is wireless and transmit the gas consumption once per day.
@@ -1,6 +1,6 @@
1
1
  pygazpar/__init__.py,sha256=lz_ZTlZlLQsSX0r81JkDAEWp9nWfGwPemMCTzdS-ar0,344
2
2
  pygazpar/__main__.py,sha256=jPsyrQeDWCfpRRIn_yeV82jhmT-1ZOP9aVZHIqhpR8I,3220
3
- pygazpar/api_client.py,sha256=9YTgcnDsGDr8Rv5sub4HAFcfDlTFIDfcSZ9UkMNqfkw,7808
3
+ pygazpar/api_client.py,sha256=lXTN5feFxLkzIlaJYiFSZWbgCoGwwyn1ok9dMa5GESc,8856
4
4
  pygazpar/client.py,sha256=CDKaWoXoHSdQ0QmuoWM-yyJaWs7tfZ9cZ0Cnap2FAyM,3569
5
5
  pygazpar/datasource.py,sha256=qQuMPB0ifI_TGXmiSE9AXklEEQnPpBf2lxqKSrrnQwU,21192
6
6
  pygazpar/enum.py,sha256=3ZCk4SziXF6pxgP3MuQ1qxYfqB3X5DOV8Rtd0GHsK9w,898
@@ -12,7 +12,7 @@ pygazpar/resources/monthly_data_sample.json,sha256=yrr4SqrB2MubeVU2HX_FRDZKHIhC0
12
12
  pygazpar/resources/weekly_data_sample.json,sha256=AjNuZkZvdYUi-3A_Vho7MA50bUddVvvrZNXechodrAM,15587
13
13
  pygazpar/resources/yearly_data_sample.json,sha256=-h0Oy-4yV6cfTaZ2oLjzEqPtoYxSyJ_48gQ92_rDWcw,446
14
14
  pygazpar/version.py,sha256=y7qXgBBbnxrWN-de04Csq4SiWLqzQBkxux_ScYpNfug,83
15
- pygazpar-1.3.0b23.dist-info/LICENSE,sha256=6dtw1Dy7oQSsLgJsLXI99HychyH0Ml45-JuyidjmiHc,1094
16
- pygazpar-1.3.0b23.dist-info/METADATA,sha256=J6wc-yHT-_MPuWN1cayhMCrCv5BcUSOGjY5AU-wA5oI,7249
17
- pygazpar-1.3.0b23.dist-info/WHEEL,sha256=7dDg4QLnNKTvwIDR9Ac8jJaAmBC_owJrckbC0jjThyA,88
18
- pygazpar-1.3.0b23.dist-info/RECORD,,
15
+ pygazpar-1.3.1.dist-info/LICENSE,sha256=6dtw1Dy7oQSsLgJsLXI99HychyH0Ml45-JuyidjmiHc,1094
16
+ pygazpar-1.3.1.dist-info/METADATA,sha256=h33Sugfapk23M6h9nPTsSxce3HOUpkbkhEaYuI_hDnQ,7105
17
+ pygazpar-1.3.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
18
+ pygazpar-1.3.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.0
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any