aioamazondevices 0.6.0__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.
- aioamazondevices/__init__.py +1 -1
- aioamazondevices/api.py +119 -11
- aioamazondevices/const.py +7 -4
- aioamazondevices/exceptions.py +4 -0
- {aioamazondevices-0.6.0.dist-info → aioamazondevices-0.7.0.dist-info}/METADATA +1 -1
- aioamazondevices-0.7.0.dist-info/RECORD +9 -0
- aioamazondevices-0.6.0.dist-info/RECORD +0 -9
- {aioamazondevices-0.6.0.dist-info → aioamazondevices-0.7.0.dist-info}/LICENSE +0 -0
- {aioamazondevices-0.6.0.dist-info → aioamazondevices-0.7.0.dist-info}/WHEEL +0 -0
aioamazondevices/__init__.py
CHANGED
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
|
-
|
22
|
-
|
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":
|
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
|
110
|
+
def _build_client_id(self) -> str:
|
106
111
|
"""Build client ID."""
|
107
|
-
client_id =
|
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(
|
@@ -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
|
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(
|
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
|
-
|
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
|
-
|
43
|
+
# Amazon APP info
|
44
|
+
AMAZON_APP_BUNDLE_ID = "com.amazon.echo"
|
44
45
|
AMAZON_APP_ID = "MAPiOSLib/6.0/ToHideRetailLink"
|
45
|
-
|
46
|
-
|
47
|
-
|
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"
|
aioamazondevices/exceptions.py
CHANGED
@@ -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=yo5hkSgxhpMQLUQO67XfINgQNxrncsWR32abuZzsals,276
|
2
|
-
aioamazondevices/api.py,sha256=_KQtNmw7mOEMOxWmLl0MAQj_MLnTngL_zJzgxQUqfwg,10830
|
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.6.0.dist-info/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
|
7
|
-
aioamazondevices-0.6.0.dist-info/METADATA,sha256=kE5iT7Eid9foSh4dKTHaLVGw_0TezvDqCGDWZtH3Qes,4680
|
8
|
-
aioamazondevices-0.6.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
9
|
-
aioamazondevices-0.6.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|