aioamazondevices 1.2.0__tar.gz → 1.3.0__tar.gz
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-1.2.0 → aioamazondevices-1.3.0}/PKG-INFO +3 -1
- {aioamazondevices-1.2.0 → aioamazondevices-1.3.0}/README.md +1 -0
- {aioamazondevices-1.2.0 → aioamazondevices-1.3.0}/pyproject.toml +2 -1
- {aioamazondevices-1.2.0 → aioamazondevices-1.3.0}/src/aioamazondevices/__init__.py +1 -1
- {aioamazondevices-1.2.0 → aioamazondevices-1.3.0}/src/aioamazondevices/api.py +86 -5
- {aioamazondevices-1.2.0 → aioamazondevices-1.3.0}/src/aioamazondevices/const.py +2 -0
- {aioamazondevices-1.2.0 → aioamazondevices-1.3.0}/LICENSE +0 -0
- {aioamazondevices-1.2.0 → aioamazondevices-1.3.0}/src/aioamazondevices/exceptions.py +0 -0
- {aioamazondevices-1.2.0 → aioamazondevices-1.3.0}/src/aioamazondevices/py.typed +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: aioamazondevices
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.3.0
|
4
4
|
Summary: Python library to control Amazon devices
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: Simone Chemelli
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
17
17
|
Classifier: Topic :: Software Development :: Libraries
|
18
|
+
Requires-Dist: babel
|
18
19
|
Requires-Dist: beautifulsoup4
|
19
20
|
Requires-Dist: colorlog
|
20
21
|
Requires-Dist: httpx
|
@@ -82,6 +83,7 @@ The script accept command line arguments or a library_test.json config file:
|
|
82
83
|
"country": "IT",
|
83
84
|
"email": "<my_address@gmail.com>",
|
84
85
|
"password": "<my_password>",
|
86
|
+
"device_name": "Echo Dot Livingroom",
|
85
87
|
"login_data_file": "out/login_data.json",
|
86
88
|
"save_raw_data": "True"
|
87
89
|
}
|
@@ -54,6 +54,7 @@ The script accept command line arguments or a library_test.json config file:
|
|
54
54
|
"country": "IT",
|
55
55
|
"email": "<my_address@gmail.com>",
|
56
56
|
"password": "<my_password>",
|
57
|
+
"device_name": "Echo Dot Livingroom",
|
57
58
|
"login_data_file": "out/login_data.json",
|
58
59
|
"save_raw_data": "True"
|
59
60
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "aioamazondevices"
|
3
|
-
version = "1.
|
3
|
+
version = "1.3.0"
|
4
4
|
description = "Python library to control Amazon devices"
|
5
5
|
authors = ["Simone Chemelli <simone.chemelli@gmail.com>"]
|
6
6
|
license = "Apache-2.0"
|
@@ -23,6 +23,7 @@ packages = [
|
|
23
23
|
|
24
24
|
[tool.poetry.dependencies]
|
25
25
|
python = "^3.12"
|
26
|
+
babel = "*"
|
26
27
|
beautifulsoup4 = "*"
|
27
28
|
colorlog = "*"
|
28
29
|
httpx = "*"
|
@@ -13,6 +13,7 @@ from typing import Any, cast
|
|
13
13
|
from urllib.parse import parse_qs, urlencode
|
14
14
|
|
15
15
|
import orjson
|
16
|
+
from babel import Locale
|
16
17
|
from bs4 import BeautifulSoup, Tag
|
17
18
|
from httpx import URL, AsyncClient, Response
|
18
19
|
|
@@ -25,6 +26,8 @@ from .const import (
|
|
25
26
|
AMAZON_CLIENT_OS,
|
26
27
|
AMAZON_DEVICE_SOFTWARE_VERSION,
|
27
28
|
AMAZON_DEVICE_TYPE,
|
29
|
+
BIN_EXTENSION,
|
30
|
+
CSRF_COOKIE,
|
28
31
|
DEFAULT_ASSOC_HANDLE,
|
29
32
|
DEFAULT_HEADERS,
|
30
33
|
DOMAIN_BY_ISO3166_COUNTRY,
|
@@ -48,6 +51,7 @@ class AmazonDevice:
|
|
48
51
|
capabilities: list[str]
|
49
52
|
device_family: str
|
50
53
|
device_type: str
|
54
|
+
device_owner_customer_id: str
|
51
55
|
online: bool
|
52
56
|
serial_number: str
|
53
57
|
software_version: str
|
@@ -82,13 +86,14 @@ class AmazonEchoApi:
|
|
82
86
|
|
83
87
|
self._login_email = login_email
|
84
88
|
self._login_password = login_password
|
89
|
+
self._login_country_code = country_code
|
85
90
|
self._domain = domain
|
86
91
|
self._cookies = self._build_init_cookies()
|
92
|
+
self._csrf_cookie: str | None = None
|
87
93
|
self._headers = DEFAULT_HEADERS
|
88
94
|
self._save_raw_data = save_raw_data
|
89
95
|
self._login_stored_data = login_data
|
90
96
|
self._serial = self._serial_number()
|
91
|
-
self._website_cookies: dict[str, Any] = self._load_website_cookies()
|
92
97
|
|
93
98
|
self.session: AsyncClient
|
94
99
|
|
@@ -232,12 +237,24 @@ class AmazonEchoApi:
|
|
232
237
|
input_data,
|
233
238
|
json_data,
|
234
239
|
)
|
240
|
+
|
241
|
+
headers = DEFAULT_HEADERS
|
242
|
+
if self._csrf_cookie and CSRF_COOKIE not in headers:
|
243
|
+
csrf = {CSRF_COOKIE: self._csrf_cookie}
|
244
|
+
_LOGGER.debug("Adding <%s> to headers", csrf)
|
245
|
+
headers.update(csrf)
|
246
|
+
|
247
|
+
if json_data:
|
248
|
+
json_header = {"Content-Type": "application/json"}
|
249
|
+
_LOGGER.debug("Adding %s to headers", json_header)
|
250
|
+
headers.update(json_header)
|
251
|
+
|
235
252
|
resp = await self.session.request(
|
236
253
|
method,
|
237
254
|
url,
|
238
255
|
data=input_data if not json_data else orjson.dumps(input_data),
|
239
|
-
cookies=self.
|
240
|
-
headers=
|
256
|
+
cookies=self._load_website_cookies(),
|
257
|
+
headers=headers,
|
241
258
|
)
|
242
259
|
content_type: str = resp.headers.get("Content-Type", "")
|
243
260
|
_LOGGER.debug(
|
@@ -278,7 +295,7 @@ class AmazonEchoApi:
|
|
278
295
|
|
279
296
|
if type(raw_data) is dict:
|
280
297
|
data = orjson.dumps(raw_data, option=orjson.OPT_INDENT_2).decode("utf-8")
|
281
|
-
elif extension
|
298
|
+
elif extension in [HTML_EXTENSION, BIN_EXTENSION]:
|
282
299
|
data = raw_data
|
283
300
|
else:
|
284
301
|
data = orjson.dumps(
|
@@ -456,6 +473,7 @@ class AmazonEchoApi:
|
|
456
473
|
}
|
457
474
|
|
458
475
|
register_device = await self._register_device(device_login_data)
|
476
|
+
self._login_stored_data = register_device
|
459
477
|
|
460
478
|
_LOGGER.info("Register device: %s", register_device)
|
461
479
|
return register_device
|
@@ -496,10 +514,14 @@ class AmazonEchoApi:
|
|
496
514
|
)
|
497
515
|
_LOGGER.debug("Response URL: %s", raw_resp.url)
|
498
516
|
response_code = raw_resp.status_code
|
499
|
-
_LOGGER.debug("Response code:
|
517
|
+
_LOGGER.debug("Response code: |%s|", response_code)
|
500
518
|
|
501
519
|
response_data = raw_resp.text
|
502
520
|
_LOGGER.debug("Response data: |%s|", response_data)
|
521
|
+
|
522
|
+
if not self._csrf_cookie:
|
523
|
+
self._csrf_cookie = raw_resp.cookies.get(CSRF_COOKIE)
|
524
|
+
|
503
525
|
json_data = {} if len(response_data) == 0 else raw_resp.json()
|
504
526
|
|
505
527
|
_LOGGER.debug("JSON data: |%s|", json_data)
|
@@ -527,6 +549,7 @@ class AmazonEchoApi:
|
|
527
549
|
capabilities=device[NODE_DEVICES]["capabilities"],
|
528
550
|
device_family=device[NODE_DEVICES]["deviceFamily"],
|
529
551
|
device_type=device[NODE_DEVICES]["deviceType"],
|
552
|
+
device_owner_customer_id=device[NODE_DEVICES]["deviceOwnerCustomerId"],
|
530
553
|
online=device[NODE_DEVICES]["online"],
|
531
554
|
serial_number=serial_number,
|
532
555
|
software_version=device[NODE_DEVICES]["softwareVersion"],
|
@@ -558,3 +581,61 @@ class AmazonEchoApi:
|
|
558
581
|
authenticated = authentication.get("authenticated")
|
559
582
|
_LOGGER.debug("Session authenticated: %s", authenticated)
|
560
583
|
return bool(authenticated)
|
584
|
+
|
585
|
+
async def call_alexa_speak(
|
586
|
+
self,
|
587
|
+
device: AmazonDevice,
|
588
|
+
message_body: str,
|
589
|
+
) -> dict[str, Any]:
|
590
|
+
"""Call Alexa.Speak to send a message."""
|
591
|
+
locale_data = Locale.parse(f"und_{self._login_country_code}")
|
592
|
+
locale = f"{locale_data.language}-{locale_data.language}"
|
593
|
+
|
594
|
+
if not self._login_stored_data:
|
595
|
+
_LOGGER.warning("Trying to send message before login")
|
596
|
+
return {}
|
597
|
+
|
598
|
+
sequence = {
|
599
|
+
"@type": "com.amazon.alexa.behaviors.model.Sequence",
|
600
|
+
"startNode": {
|
601
|
+
"@type": "com.amazon.alexa.behaviors.model.SerialNode",
|
602
|
+
"nodesToExecute": [
|
603
|
+
{
|
604
|
+
"@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode", # noqa: E501
|
605
|
+
"type": "Alexa.Speak",
|
606
|
+
"operationPayload": {
|
607
|
+
"deviceType": device.device_type,
|
608
|
+
"deviceSerialNumber": device.serial_number,
|
609
|
+
"locale": locale,
|
610
|
+
"customerId": device.device_owner_customer_id,
|
611
|
+
"textToSpeak": message_body,
|
612
|
+
"target": {
|
613
|
+
"customerId": device.device_owner_customer_id,
|
614
|
+
"devices": [
|
615
|
+
{
|
616
|
+
"deviceSerialNumber": device.serial_number,
|
617
|
+
"deviceTypeId": device.device_type,
|
618
|
+
},
|
619
|
+
],
|
620
|
+
},
|
621
|
+
"skillId": "amzn1.ask.1p.saysomething",
|
622
|
+
},
|
623
|
+
},
|
624
|
+
],
|
625
|
+
},
|
626
|
+
}
|
627
|
+
node_data = {
|
628
|
+
"behaviorId": "PREVIEW",
|
629
|
+
"sequenceJson": orjson.dumps(sequence).decode("utf-8"),
|
630
|
+
"status": "ENABLED",
|
631
|
+
}
|
632
|
+
|
633
|
+
_LOGGER.debug("Preview data payload: %s", node_data)
|
634
|
+
await self._session_request(
|
635
|
+
method="POST",
|
636
|
+
url=f"https://alexa.amazon.{self._domain}/api/behaviors/preview",
|
637
|
+
input_data=node_data,
|
638
|
+
json_data=True,
|
639
|
+
)
|
640
|
+
|
641
|
+
return node_data
|
@@ -43,6 +43,7 @@ DEFAULT_HEADERS = {
|
|
43
43
|
"Accept-Language": "en-US",
|
44
44
|
"Accept-Encoding": "gzip",
|
45
45
|
}
|
46
|
+
CSRF_COOKIE = "csrf"
|
46
47
|
|
47
48
|
NODE_DEVICES = "devices"
|
48
49
|
NODE_DO_NOT_DISTURB = "doNotDisturbDeviceStatusList"
|
@@ -60,6 +61,7 @@ URI_QUERIES = {
|
|
60
61
|
SAVE_PATH = "out"
|
61
62
|
HTML_EXTENSION = ".html"
|
62
63
|
JSON_EXTENSION = ".json"
|
64
|
+
BIN_EXTENSION = ".bin"
|
63
65
|
|
64
66
|
DEVICE_TYPE_TO_MODEL = {
|
65
67
|
"A1RABVCI4QCIKC": "Echo Dot (Gen3)",
|
File without changes
|
File without changes
|
File without changes
|