aioamazondevices 0.5.1__py3-none-any.whl → 0.7.0__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.5.1"
3
+ __version__ = "0.7.0"
4
4
 
5
5
 
6
6
  from .api import AmazonDevice, AmazonEchoApi
aioamazondevices/api.py CHANGED
@@ -5,26 +5,30 @@ import hashlib
5
5
  import secrets
6
6
  import uuid
7
7
  from dataclasses import dataclass
8
+ from datetime import UTC, datetime, timedelta
9
+ from http import HTTPStatus
8
10
  from pathlib import Path
9
11
  from typing import Any
10
- from urllib.parse import urlencode
12
+ from urllib.parse import parse_qs, urlencode
11
13
 
12
14
  import orjson
13
15
  from bs4 import BeautifulSoup, Tag
14
- from httpx import AsyncClient, Response
16
+ from httpx import URL, AsyncClient, Response
15
17
 
16
18
  from .const import (
17
19
  _LOGGER,
18
20
  AMAZON_APP_BUNDLE_ID,
19
21
  AMAZON_APP_ID,
22
+ AMAZON_APP_NAME,
20
23
  AMAZON_APP_VERSION,
21
- AMAZON_SERIAL_NUMBER,
22
- AMAZON_SOFTWARE_VERSION,
24
+ AMAZON_CLIENT_OS,
25
+ AMAZON_DEVICE_SOFTWARE_VERSION,
26
+ AMAZON_DEVICE_TYPE,
23
27
  DEFAULT_HEADERS,
24
28
  DOMAIN_BY_COUNTRY,
25
29
  URI_QUERIES,
26
30
  )
27
- from .exceptions import CannotAuthenticate
31
+ from .exceptions import CannotAuthenticate, CannotRegisterDevice
28
32
 
29
33
 
30
34
  @dataclass
@@ -69,6 +73,7 @@ class AmazonEchoApi:
69
73
  self._cookies = self._build_init_cookies()
70
74
  self._headers = DEFAULT_HEADERS
71
75
  self._save_html = save_html
76
+ self._serial = uuid.uuid4().hex.upper()
72
77
 
73
78
  self.session: AsyncClient
74
79
 
@@ -80,7 +85,7 @@ class AmazonEchoApi:
80
85
  map_md_dict = {
81
86
  "device_user_dictionary": [],
82
87
  "device_registration_data": {
83
- "software_version": AMAZON_SOFTWARE_VERSION,
88
+ "software_version": AMAZON_DEVICE_SOFTWARE_VERSION,
84
89
  },
85
90
  "app_identifier": {
86
91
  "app_version": AMAZON_APP_VERSION,
@@ -102,9 +107,9 @@ class AmazonEchoApi:
102
107
  m = hashlib.sha256(verifier)
103
108
  return base64.urlsafe_b64encode(m.digest()).rstrip(b"=")
104
109
 
105
- def _build_client_id(self, serial: str) -> str:
110
+ def _build_client_id(self) -> str:
106
111
  """Build client ID."""
107
- client_id = serial.encode() + AMAZON_SERIAL_NUMBER
112
+ client_id = self._serial.encode() + b"#" + AMAZON_DEVICE_TYPE.encode("utf-8")
108
113
  return client_id.hex()
109
114
 
110
115
  def _build_oauth_url(
@@ -153,7 +158,7 @@ class AmazonEchoApi:
153
158
  def _get_request_from_soup(self, soup: BeautifulSoup) -> tuple[str, str]:
154
159
  """Extract URL and method for the next request."""
155
160
  _LOGGER.debug("Get request data from HTML source")
156
- form = soup.find("form")
161
+ form = soup.find("form", {"name": "signIn"}) or soup.find("form")
157
162
  if isinstance(form, Tag):
158
163
  method = form["method"]
159
164
  url = form["action"]
@@ -161,6 +166,11 @@ class AmazonEchoApi:
161
166
  return method, url
162
167
  raise TypeError("Unable to extract form data from response.")
163
168
 
169
+ def _extract_code_from_url(self, url: URL) -> str:
170
+ """Extract the access token from url query after login."""
171
+ parsed_url = parse_qs(url.query.decode())
172
+ return parsed_url["openid.oa2.authorization_code"][0]
173
+
164
174
  def _client_session(self) -> None:
165
175
  """Create httpx ClientSession."""
166
176
  if not hasattr(self, "session") or self.session.is_closed:
@@ -225,13 +235,102 @@ class AmazonEchoApi:
225
235
  file.write(html_code)
226
236
  file.write("\n")
227
237
 
228
- async def login(self, otp_code: str) -> bool:
238
+ async def _register_device(
239
+ self,
240
+ data: dict[str, Any],
241
+ ) -> dict[str, Any]:
242
+ """Register a dummy Alexa device."""
243
+ authorization_code: str = data["authorization_code"]
244
+ code_verifier: bytes = data["code_verifier"]
245
+
246
+ body = {
247
+ "requested_extensions": ["device_info", "customer_info"],
248
+ "cookies": {"website_cookies": [], "domain": f".amazon.{self._domain}"},
249
+ "registration_data": {
250
+ "domain": "Device",
251
+ "app_version": AMAZON_APP_VERSION,
252
+ "device_type": AMAZON_DEVICE_TYPE,
253
+ "device_name": (
254
+ f"%FIRST_NAME%\u0027s%DUPE_STRATEGY_1ST%{AMAZON_APP_NAME}"
255
+ ),
256
+ "os_version": AMAZON_CLIENT_OS,
257
+ "device_serial": self._serial,
258
+ "device_model": "iPhone",
259
+ "app_name": AMAZON_APP_NAME,
260
+ "software_version": AMAZON_DEVICE_SOFTWARE_VERSION,
261
+ },
262
+ "auth_data": {
263
+ "client_id": self._build_client_id(),
264
+ "authorization_code": authorization_code,
265
+ "code_verifier": code_verifier.decode(),
266
+ "code_algorithm": "SHA-256",
267
+ "client_domain": "DeviceLegacy",
268
+ },
269
+ "user_context_map": {"frc": self._cookies["frc"]},
270
+ "requested_token_type": [
271
+ "bearer",
272
+ "mac_dms",
273
+ "website_cookies",
274
+ "store_authentication_cookie",
275
+ ],
276
+ }
277
+
278
+ headers = {"Content-Type": "application/json"}
279
+
280
+ _LOGGER.warning("_register_device: [data=%s],[headers=%s]", body, headers)
281
+ resp = await self.session.post(
282
+ f"https://api.amazon.{self._domain}/auth/register",
283
+ json=body,
284
+ headers=headers,
285
+ )
286
+ resp_json = resp.json()
287
+
288
+ if resp.status_code != HTTPStatus.OK:
289
+ _LOGGER.error(
290
+ "Cannot register device for %s: %s",
291
+ self._login_email,
292
+ resp_json["response"]["error"]["message"],
293
+ )
294
+ raise CannotRegisterDevice(resp_json)
295
+
296
+ success_response = resp_json["response"]["success"]
297
+
298
+ tokens = success_response["tokens"]
299
+ adp_token = tokens["mac_dms"]["adp_token"]
300
+ device_private_key = tokens["mac_dms"]["device_private_key"]
301
+ store_authentication_cookie = tokens["store_authentication_cookie"]
302
+ access_token = tokens["bearer"]["access_token"]
303
+ refresh_token = tokens["bearer"]["refresh_token"]
304
+ expires_s = int(tokens["bearer"]["expires_in"])
305
+ expires = (datetime.now(UTC) + timedelta(seconds=expires_s)).timestamp()
306
+
307
+ extensions = success_response["extensions"]
308
+ device_info = extensions["device_info"]
309
+ customer_info = extensions["customer_info"]
310
+
311
+ website_cookies = {}
312
+ for cookie in tokens["website_cookies"]:
313
+ website_cookies[cookie["Name"]] = cookie["Value"].replace(r'"', r"")
314
+
315
+ return {
316
+ "adp_token": adp_token,
317
+ "device_private_key": device_private_key,
318
+ "access_token": access_token,
319
+ "refresh_token": refresh_token,
320
+ "expires": expires,
321
+ "website_cookies": website_cookies,
322
+ "store_authentication_cookie": store_authentication_cookie,
323
+ "device_info": device_info,
324
+ "customer_info": customer_info,
325
+ }
326
+
327
+ async def login(self, otp_code: str) -> dict[str, Any]:
229
328
  """Login to Amazon."""
230
329
  _LOGGER.debug("Logging-in for %s [otp code %s]", self._login_email, otp_code)
231
330
  self._client_session()
232
331
 
233
332
  code_verifier = self._create_code_verifier()
234
- client_id = self._build_client_id(uuid.uuid4().hex.upper())
333
+ client_id = self._build_client_id()
235
334
 
236
335
  _LOGGER.debug("Build oauth URL")
237
336
  login_url = self._build_oauth_url(code_verifier, client_id)
@@ -279,7 +378,16 @@ class AmazonEchoApi:
279
378
  if authcode_url is None:
280
379
  raise CannotAuthenticate
281
380
 
282
- return True
381
+ device_login_data = {
382
+ "authorization_code": self._extract_code_from_url(authcode_url),
383
+ "code_verifier": code_verifier,
384
+ "domain": self._domain,
385
+ }
386
+
387
+ register_device = await self._register_device(device_login_data)
388
+
389
+ _LOGGER.warning("Register device: %s", register_device)
390
+ return register_device
283
391
 
284
392
  async def close(self) -> None:
285
393
  """Close httpx session."""
aioamazondevices/const.py CHANGED
@@ -40,8 +40,11 @@ URI_QUERIES = {
40
40
  "bluetooth": "/api/bluetooth",
41
41
  }
42
42
 
43
- AMAZON_APP_BUNDLE_ID = "com.audible.iphone"
43
+ # Amazon APP info
44
+ AMAZON_APP_BUNDLE_ID = "com.amazon.echo"
44
45
  AMAZON_APP_ID = "MAPiOSLib/6.0/ToHideRetailLink"
45
- AMAZON_APP_VERSION = "3.56.2"
46
- AMAZON_SOFTWARE_VERSION = "35602678"
47
- AMAZON_SERIAL_NUMBER = b"#A2CZJZGLK2JJVM"
46
+ AMAZON_APP_NAME = "AioAmazonDevices"
47
+ AMAZON_APP_VERSION = "2.2.556530.0"
48
+ AMAZON_DEVICE_SOFTWARE_VERSION = "35602678"
49
+ AMAZON_DEVICE_TYPE = "A2IVLV5VM2W81"
50
+ AMAZON_CLIENT_OS = "16.6"
@@ -17,3 +17,7 @@ class CannotAuthenticate(AmazonError):
17
17
 
18
18
  class CannotRetrieveData(AmazonError):
19
19
  """Exception raised when data retrieval fails."""
20
+
21
+
22
+ class CannotRegisterDevice(AmazonError):
23
+ """Exception raised when device registration fails."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioamazondevices
3
- Version: 0.5.1
3
+ Version: 0.7.0
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
@@ -0,0 +1,9 @@
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,,
@@ -1,9 +0,0 @@
1
- aioamazondevices/__init__.py,sha256=1i3Z3Rs_PtXiBemnoni30tKAwwe_sND1V6OOcgOj4tk,276
2
- aioamazondevices/api.py,sha256=XAUw079QI2CC6eL532_LVg566B-Nxo4WmO_IP2zL_P8,10789
3
- aioamazondevices/const.py,sha256=bZaeO8AeJbDc5hdlbJ3cMwM9teTgYhExSR1oEpRFMLk,1089
4
- aioamazondevices/exceptions.py,sha256=tERMur_gry9TmU3UyzndJO_CLViISn4b8ClrRbryFy8,444
5
- aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- aioamazondevices-0.5.1.dist-info/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
7
- aioamazondevices-0.5.1.dist-info/METADATA,sha256=e_42URMMuMsqZLIt2E2q-gioOV2SrSZaSjW77pr63HQ,4680
8
- aioamazondevices-0.5.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
9
- aioamazondevices-0.5.1.dist-info/RECORD,,