aioamazondevices 0.7.0__py3-none-any.whl → 0.7.2__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.
@@ -1,6 +1,6 @@
1
1
  """aioamazondevices library."""
2
2
 
3
- __version__ = "0.7.0"
3
+ __version__ = "0.7.2"
4
4
 
5
5
 
6
6
  from .api import AmazonDevice, AmazonEchoApi
aioamazondevices/api.py CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  import base64
4
4
  import hashlib
5
+ import json
6
+ import mimetypes
5
7
  import secrets
6
8
  import uuid
7
9
  from dataclasses import dataclass
8
10
  from datetime import UTC, datetime, timedelta
9
11
  from http import HTTPStatus
10
12
  from pathlib import Path
11
- from typing import Any
13
+ from typing import Any, cast
12
14
  from urllib.parse import parse_qs, urlencode
13
15
 
14
16
  import orjson
@@ -24,8 +26,12 @@ from .const import (
24
26
  AMAZON_CLIENT_OS,
25
27
  AMAZON_DEVICE_SOFTWARE_VERSION,
26
28
  AMAZON_DEVICE_TYPE,
29
+ DEFAULT_ASSOC_HANDLE,
27
30
  DEFAULT_HEADERS,
28
31
  DOMAIN_BY_COUNTRY,
32
+ HTML_EXTENSION,
33
+ JSON_EXTENSION,
34
+ SAVE_PATH,
29
35
  URI_QUERIES,
30
36
  )
31
37
  from .exceptions import CannotAuthenticate, CannotRegisterDevice
@@ -61,9 +67,10 @@ class AmazonEchoApi:
61
67
  locale = DOMAIN_BY_COUNTRY.get(country_code)
62
68
  domain = locale["domain"] if locale else country_code
63
69
 
64
- assoc_handle = "amzn_dp_project_dee_ios"
65
- if not locale:
66
- assoc_handle += f"_{country_code}"
70
+ if locale and (assoc := locale.get("openid.assoc_handle")):
71
+ assoc_handle = assoc
72
+ else:
73
+ assoc_handle = f"{DEFAULT_ASSOC_HANDLE}_{country_code}"
67
74
  self._assoc_handle = assoc_handle
68
75
 
69
76
  self._login_email = login_email
@@ -73,10 +80,24 @@ class AmazonEchoApi:
73
80
  self._cookies = self._build_init_cookies()
74
81
  self._headers = DEFAULT_HEADERS
75
82
  self._save_html = save_html
76
- self._serial = uuid.uuid4().hex.upper()
83
+ self._serial = self._serial_number()
77
84
 
78
85
  self.session: AsyncClient
79
86
 
87
+ def _serial_number(self) -> str:
88
+ """Get or calculate device serial number."""
89
+ fullpath = Path(SAVE_PATH, "login_data.json")
90
+ if not fullpath.exists():
91
+ # Create a new serial number
92
+ _LOGGER.debug("Cannot find previous login data, creating new serial number")
93
+ return uuid.uuid4().hex.upper()
94
+
95
+ with Path.open(fullpath, "rb") as file:
96
+ data = json.load(file)
97
+
98
+ _LOGGER.debug("Found previous login data, loading serial number")
99
+ return cast(str, data["device_info"]["device_serial_number"])
100
+
80
101
  def _build_init_cookies(self) -> dict[str, str]:
81
102
  """Build initial cookies to prevent captcha in most cases."""
82
103
  token_bytes = secrets.token_bytes(313)
@@ -195,32 +216,23 @@ class AmazonEchoApi:
195
216
  url,
196
217
  data=input_data,
197
218
  )
198
- content_type = resp.headers.get("Content-Type", "")
219
+ content_type: str = resp.headers.get("Content-Type", "")
199
220
  _LOGGER.debug("Response content type: %s", content_type)
200
221
 
201
- if "text/html" in content_type:
202
- await self._save_to_file(
203
- resp.text,
204
- url,
205
- )
206
- elif content_type == "application/json":
207
- await self._save_to_file(
208
- orjson.dumps(
209
- orjson.loads(resp.text),
210
- option=orjson.OPT_INDENT_2,
211
- ).decode("utf-8"),
212
- url,
213
- extension="json",
214
- )
222
+ await self._save_to_file(
223
+ resp.text,
224
+ url,
225
+ mimetypes.guess_extension(content_type.split(";")[0]) or ".raw",
226
+ )
215
227
 
216
228
  return BeautifulSoup(resp.content, "html.parser"), resp
217
229
 
218
230
  async def _save_to_file(
219
231
  self,
220
- html_code: str,
232
+ raw_data: str | dict,
221
233
  url: str,
222
- extension: str = "html",
223
- output_path: str = "out",
234
+ extension: str = HTML_EXTENSION,
235
+ output_path: str = SAVE_PATH,
224
236
  ) -> None:
225
237
  """Save response data to disk."""
226
238
  if not self._save_html:
@@ -229,10 +241,33 @@ class AmazonEchoApi:
229
241
  output_dir = Path(output_path)
230
242
  output_dir.mkdir(parents=True, exist_ok=True)
231
243
 
232
- url_split = url.split("/")
233
- filename = f"{url_split[3]}-{url_split[4].split('?')[0]}.{extension}"
234
- with Path.open(output_dir / filename, "w+") as file:
235
- file.write(html_code)
244
+ if url.startswith("http"):
245
+ url_split = url.split("/")
246
+ base_filename = f"{url_split[3]}-{url_split[4].split('?')[0]}"
247
+ else:
248
+ base_filename = url
249
+ fullpath = Path(output_dir, base_filename + extension)
250
+
251
+ if type(raw_data) is dict:
252
+ data = orjson.dumps(raw_data, option=orjson.OPT_INDENT_2).decode("utf-8")
253
+ elif extension == HTML_EXTENSION:
254
+ data = raw_data
255
+ else:
256
+ data = orjson.dumps(
257
+ orjson.loads(raw_data),
258
+ option=orjson.OPT_INDENT_2,
259
+ ).decode("utf-8")
260
+
261
+ i = 2
262
+ while fullpath.exists():
263
+ filename = f"{base_filename}_{i!s}{extension}"
264
+ fullpath = Path(output_dir, filename)
265
+ i += 1
266
+
267
+ _LOGGER.warning("Saving data to %s", fullpath)
268
+
269
+ with Path.open(fullpath, "w+") as file:
270
+ file.write(data)
236
271
  file.write("\n")
237
272
 
238
273
  async def _register_device(
@@ -277,9 +312,9 @@ class AmazonEchoApi:
277
312
 
278
313
  headers = {"Content-Type": "application/json"}
279
314
 
280
- _LOGGER.warning("_register_device: [data=%s],[headers=%s]", body, headers)
315
+ register_url = f"https://api.amazon.{self._domain}/auth/register"
281
316
  resp = await self.session.post(
282
- f"https://api.amazon.{self._domain}/auth/register",
317
+ register_url,
283
318
  json=body,
284
319
  headers=headers,
285
320
  )
@@ -293,6 +328,11 @@ class AmazonEchoApi:
293
328
  )
294
329
  raise CannotRegisterDevice(resp_json)
295
330
 
331
+ await self._save_to_file(
332
+ resp.text,
333
+ url=register_url,
334
+ extension=JSON_EXTENSION,
335
+ )
296
336
  success_response = resp_json["response"]["success"]
297
337
 
298
338
  tokens = success_response["tokens"]
@@ -312,7 +352,7 @@ class AmazonEchoApi:
312
352
  for cookie in tokens["website_cookies"]:
313
353
  website_cookies[cookie["Name"]] = cookie["Value"].replace(r'"', r"")
314
354
 
315
- return {
355
+ login_data = {
316
356
  "adp_token": adp_token,
317
357
  "device_private_key": device_private_key,
318
358
  "access_token": access_token,
@@ -323,6 +363,8 @@ class AmazonEchoApi:
323
363
  "device_info": device_info,
324
364
  "customer_info": customer_info,
325
365
  }
366
+ await self._save_to_file(login_data, "login_data", JSON_EXTENSION)
367
+ return login_data
326
368
 
327
369
  async def login(self, otp_code: str) -> dict[str, Any]:
328
370
  """Login to Amazon."""
@@ -386,7 +428,7 @@ class AmazonEchoApi:
386
428
 
387
429
  register_device = await self._register_device(device_login_data)
388
430
 
389
- _LOGGER.warning("Register device: %s", register_device)
431
+ _LOGGER.info("Register device: %s", register_device)
390
432
  return register_device
391
433
 
392
434
  async def close(self) -> None:
aioamazondevices/const.py CHANGED
@@ -4,10 +4,12 @@ import logging
4
4
 
5
5
  _LOGGER = logging.getLogger(__package__)
6
6
 
7
+ DEFAULT_ASSOC_HANDLE = "amzn_dp_project_dee_ios"
8
+
7
9
  DOMAIN_BY_COUNTRY = {
8
10
  "us": {
9
11
  "domain": "com",
10
- "openid.assoc_handle": "amzn_dp_project_dee_ios",
12
+ "openid.assoc_handle": DEFAULT_ASSOC_HANDLE,
11
13
  },
12
14
  "uk": {
13
15
  "domain": "co.uk",
@@ -17,6 +19,7 @@ DOMAIN_BY_COUNTRY = {
17
19
  },
18
20
  "jp": {
19
21
  "domain": "co.jp",
22
+ "openid.assoc_handle": "jpflex",
20
23
  },
21
24
  "br": {
22
25
  "domain": "com.br",
@@ -48,3 +51,8 @@ AMAZON_APP_VERSION = "2.2.556530.0"
48
51
  AMAZON_DEVICE_SOFTWARE_VERSION = "35602678"
49
52
  AMAZON_DEVICE_TYPE = "A2IVLV5VM2W81"
50
53
  AMAZON_CLIENT_OS = "16.6"
54
+
55
+ # File extensions
56
+ SAVE_PATH = "out"
57
+ HTML_EXTENSION = ".html"
58
+ JSON_EXTENSION = ".json"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioamazondevices
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Python library to control Amazon devices
5
5
  Home-page: https://github.com/chemelli74/aioamazondevices
6
6
  License: Apache Software License 2.0
@@ -15,8 +15,10 @@ Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
18
19
  Classifier: Topic :: Software Development :: Libraries
19
20
  Requires-Dist: beautifulsoup4
21
+ Requires-Dist: colorlog
20
22
  Requires-Dist: httpx
21
23
  Requires-Dist: orjson
22
24
  Project-URL: Bug Tracker, https://github.com/chemelli74/aioamazondevices/issues
@@ -0,0 +1,9 @@
1
+ aioamazondevices/__init__.py,sha256=FlgefNZC0TGxmUGApXRvVc4d17xca_QM2YL9ZMzQjR4,276
2
+ aioamazondevices/api.py,sha256=3eagygo9Kx1Qx2v602wZW6MyCTuA5Tmq545TmGX6zWc,16518
3
+ aioamazondevices/const.py,sha256=BnA9rU9S9vH9mmmE-In443PECLUmGll9-iWu-oETo4s,1348
4
+ aioamazondevices/exceptions.py,sha256=yQ9nL4UwBdHNXvdRj8TRemed6PXBmExP8lbHaAp04vY,546
5
+ aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ aioamazondevices-0.7.2.dist-info/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
7
+ aioamazondevices-0.7.2.dist-info/METADATA,sha256=Q36yYnRumtcxIq9mRdPziSLCJkJPCJ2q3AXgLeOfEDU,4755
8
+ aioamazondevices-0.7.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
9
+ aioamazondevices-0.7.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,9 +0,0 @@
1
- aioamazondevices/__init__.py,sha256=fEz-KfjtZ3198YaYkTtbCjFrFy7tPcsxD2FCXiZmemQ,276
2
- aioamazondevices/api.py,sha256=BBk4pW2IwmPvZFYeBl2ZtWumIflVwiosaKktBDGM_I0,15124
3
- aioamazondevices/const.py,sha256=GIJWFWQ3v5K-Bh2_eEVilPKdfe761j0QloRbYwSRHic,1175
4
- aioamazondevices/exceptions.py,sha256=yQ9nL4UwBdHNXvdRj8TRemed6PXBmExP8lbHaAp04vY,546
5
- aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- aioamazondevices-0.7.0.dist-info/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
7
- aioamazondevices-0.7.0.dist-info/METADATA,sha256=cFCuQinFK3QiOv0RqJXEJB8R1atXT9_W-f5UdoF2nw4,4680
8
- aioamazondevices-0.7.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
9
- aioamazondevices-0.7.0.dist-info/RECORD,,