aioamazondevices 3.1.14__tar.gz → 3.1.19__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-3.1.14 → aioamazondevices-3.1.19}/PKG-INFO +4 -9
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/pyproject.toml +17 -14
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/src/aioamazondevices/__init__.py +1 -1
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/src/aioamazondevices/api.py +18 -15
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/src/aioamazondevices/const.py +45 -1
- aioamazondevices-3.1.19/src/aioamazondevices/utils.py +58 -0
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/LICENSE +0 -0
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/README.md +0 -0
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/src/aioamazondevices/exceptions.py +0 -0
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/src/aioamazondevices/py.typed +0 -0
- {aioamazondevices-3.1.14 → aioamazondevices-3.1.19}/src/aioamazondevices/sounds.py +0 -0
@@ -1,29 +1,24 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: aioamazondevices
|
3
|
-
Version: 3.1.
|
3
|
+
Version: 3.1.19
|
4
4
|
Summary: Python library to control Amazon devices
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: Simone Chemelli
|
7
7
|
Author-email: simone.chemelli@gmail.com
|
8
|
-
Requires-Python: >=3.12
|
9
|
-
Classifier: Development Status ::
|
8
|
+
Requires-Python: >=3.12
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
10
10
|
Classifier: Intended Audience :: Developers
|
11
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
12
11
|
Classifier: Natural Language :: English
|
13
12
|
Classifier: Operating System :: OS Independent
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
17
13
|
Classifier: Topic :: Software Development :: Libraries
|
18
14
|
Requires-Dist: aiohttp
|
19
|
-
Requires-Dist: babel
|
20
15
|
Requires-Dist: beautifulsoup4
|
21
16
|
Requires-Dist: colorlog
|
17
|
+
Requires-Dist: langcodes
|
22
18
|
Requires-Dist: orjson
|
23
19
|
Requires-Dist: yarl
|
24
20
|
Project-URL: Bug Tracker, https://github.com/chemelli74/aioamazondevices/issues
|
25
21
|
Project-URL: Changelog, https://github.com/chemelli74/aioamazondevices/blob/main/CHANGELOG.md
|
26
|
-
Project-URL: Repository, https://github.com/chemelli74/aioamazondevices
|
27
22
|
Description-Content-Type: text/markdown
|
28
23
|
|
29
24
|
# aioamazondevices
|
@@ -1,13 +1,16 @@
|
|
1
|
-
[
|
1
|
+
[project]
|
2
2
|
name = "aioamazondevices"
|
3
|
-
version = "3.1.
|
3
|
+
version = "3.1.19"
|
4
|
+
requires-python = ">=3.12"
|
4
5
|
description = "Python library to control Amazon devices"
|
5
|
-
authors = [
|
6
|
+
authors = [
|
7
|
+
{ name = "Simone Chemelli", email = "simone.chemelli@gmail.com" },
|
8
|
+
]
|
6
9
|
license = "Apache-2.0"
|
7
10
|
readme = "README.md"
|
8
11
|
repository = "https://github.com/chemelli74/aioamazondevices"
|
9
12
|
classifiers = [
|
10
|
-
"Development Status ::
|
13
|
+
"Development Status :: 4 - Beta",
|
11
14
|
"Intended Audience :: Developers",
|
12
15
|
"Natural Language :: English",
|
13
16
|
"Operating System :: OS Independent",
|
@@ -16,22 +19,22 @@ classifiers = [
|
|
16
19
|
packages = [
|
17
20
|
{ include = "aioamazondevices", from = "src" },
|
18
21
|
]
|
22
|
+
dependencies = [
|
23
|
+
"aiohttp",
|
24
|
+
"beautifulsoup4",
|
25
|
+
"colorlog",
|
26
|
+
"langcodes",
|
27
|
+
"orjson",
|
28
|
+
"yarl",
|
29
|
+
]
|
19
30
|
|
20
|
-
[
|
31
|
+
[project.urls]
|
21
32
|
"Bug Tracker" = "https://github.com/chemelli74/aioamazondevices/issues"
|
22
33
|
"Changelog" = "https://github.com/chemelli74/aioamazondevices/blob/main/CHANGELOG.md"
|
23
34
|
|
24
|
-
[tool.poetry.dependencies]
|
25
|
-
aiohttp = "*"
|
26
|
-
python = "^3.12"
|
27
|
-
babel = "*"
|
28
|
-
beautifulsoup4 = "*"
|
29
|
-
colorlog = "*"
|
30
|
-
orjson = "*"
|
31
|
-
yarl = "*"
|
32
35
|
|
33
36
|
[tool.poetry.group.dev.dependencies]
|
34
|
-
pytest = "^8.
|
37
|
+
pytest = "^8.4"
|
35
38
|
pytest-cov = ">=5,<7"
|
36
39
|
|
37
40
|
[tool.semantic_release]
|
@@ -16,8 +16,8 @@ from urllib.parse import parse_qs, urlencode
|
|
16
16
|
|
17
17
|
import orjson
|
18
18
|
from aiohttp import ClientConnectorError, ClientResponse, ClientSession
|
19
|
-
from babel import Locale
|
20
19
|
from bs4 import BeautifulSoup, Tag
|
20
|
+
from langcodes import Language
|
21
21
|
from multidict import CIMultiDictProxy, MultiDictProxy
|
22
22
|
from yarl import URL
|
23
23
|
|
@@ -58,6 +58,7 @@ from .exceptions import (
|
|
58
58
|
CannotRetrieveData,
|
59
59
|
WrongMethod,
|
60
60
|
)
|
61
|
+
from .utils import obfuscate_email, scrub_fields
|
61
62
|
|
62
63
|
|
63
64
|
@dataclass
|
@@ -317,7 +318,7 @@ class AmazonEchoApi:
|
|
317
318
|
"%s request: %s with payload %s [json=%s]",
|
318
319
|
method,
|
319
320
|
url,
|
320
|
-
input_data,
|
321
|
+
scrub_fields(input_data) if input_data else None,
|
321
322
|
json_data,
|
322
323
|
)
|
323
324
|
|
@@ -473,7 +474,7 @@ class AmazonEchoApi:
|
|
473
474
|
msg = resp_json["response"]["error"]["message"]
|
474
475
|
_LOGGER.error(
|
475
476
|
"Cannot register device for %s: %s",
|
476
|
-
self._login_email,
|
477
|
+
obfuscate_email(self._login_email),
|
477
478
|
msg,
|
478
479
|
)
|
479
480
|
raise CannotRegisterDevice(f"{HTTPStatus(resp.status).phrase}: {msg}")
|
@@ -601,7 +602,11 @@ class AmazonEchoApi:
|
|
601
602
|
|
602
603
|
async def login_mode_interactive(self, otp_code: str) -> dict[str, Any]:
|
603
604
|
"""Login to Amazon interactively via OTP."""
|
604
|
-
_LOGGER.debug(
|
605
|
+
_LOGGER.debug(
|
606
|
+
"Logging-in for %s [otp code: %s]",
|
607
|
+
obfuscate_email(self._login_email),
|
608
|
+
bool(otp_code),
|
609
|
+
)
|
605
610
|
self._client_session()
|
606
611
|
|
607
612
|
code_verifier = self._create_code_verifier()
|
@@ -671,7 +676,7 @@ class AmazonEchoApi:
|
|
671
676
|
|
672
677
|
_LOGGER.debug(
|
673
678
|
"Logging-in for %s with stored data",
|
674
|
-
self._login_email,
|
679
|
+
obfuscate_email(self._login_email),
|
675
680
|
)
|
676
681
|
|
677
682
|
self._client_session()
|
@@ -723,7 +728,11 @@ class AmazonEchoApi:
|
|
723
728
|
|
724
729
|
final_devices_list: dict[str, AmazonDevice] = {}
|
725
730
|
for device in self._devices.values():
|
726
|
-
|
731
|
+
# Remove stale, orphaned and virtual devices
|
732
|
+
devices_node = device.get(NODE_DEVICES)
|
733
|
+
if not devices_node or (devices_node.get("deviceType") in DEVICE_TO_IGNORE):
|
734
|
+
continue
|
735
|
+
|
727
736
|
preferences_node = device.get(NODE_PREFERENCES)
|
728
737
|
do_not_disturb_node = device[NODE_DO_NOT_DISTURB]
|
729
738
|
bluetooth_node = device[NODE_BLUETOOTH]
|
@@ -736,13 +745,6 @@ class AmazonEchoApi:
|
|
736
745
|
if _device_id == identifier_node["entityId"]:
|
737
746
|
sensors = _device_sensors
|
738
747
|
|
739
|
-
# Remove stale, orphaned and virtual devices
|
740
|
-
if (
|
741
|
-
NODE_DEVICES not in device
|
742
|
-
or devices_node.get("deviceType") in DEVICE_TO_IGNORE
|
743
|
-
):
|
744
|
-
continue
|
745
|
-
|
746
748
|
serial_number: str = devices_node["serialNumber"]
|
747
749
|
final_devices_list[serial_number] = AmazonDevice(
|
748
750
|
account_name=devices_node["accountName"],
|
@@ -819,8 +821,9 @@ class AmazonEchoApi:
|
|
819
821
|
message_source: AmazonMusicSource | None = None,
|
820
822
|
) -> None:
|
821
823
|
"""Send message to specific device."""
|
822
|
-
|
823
|
-
|
824
|
+
lang_object = Language.make(territory=self._login_country_code.upper())
|
825
|
+
lang_maximized = lang_object.maximize()
|
826
|
+
locale = f"{lang_maximized.language}-{lang_maximized.region}"
|
824
827
|
|
825
828
|
if not self._login_stored_data:
|
826
829
|
_LOGGER.warning("Trying to send message before login")
|
@@ -6,6 +6,25 @@ _LOGGER = logging.getLogger(__package__)
|
|
6
6
|
|
7
7
|
DEFAULT_ASSOC_HANDLE = "amzn_dp_project_dee_ios"
|
8
8
|
|
9
|
+
TO_REDACT = {
|
10
|
+
"address1",
|
11
|
+
"address2",
|
12
|
+
"address3",
|
13
|
+
"city",
|
14
|
+
"county",
|
15
|
+
"deviceAccountId",
|
16
|
+
"deviceAddress",
|
17
|
+
"deviceOwnerCustomerId",
|
18
|
+
"given_name",
|
19
|
+
"name",
|
20
|
+
"password",
|
21
|
+
"postalCode",
|
22
|
+
"searchCustomerId",
|
23
|
+
"state",
|
24
|
+
"street",
|
25
|
+
"user_id",
|
26
|
+
}
|
27
|
+
|
9
28
|
DOMAIN_BY_ISO3166_COUNTRY = {
|
10
29
|
"ar": {
|
11
30
|
"domain": "com",
|
@@ -115,7 +134,8 @@ SPEAKER_GROUP_FAMILY = "WHA"
|
|
115
134
|
SPEAKER_GROUP_MODEL = "Speaker Group"
|
116
135
|
|
117
136
|
DEVICE_TO_IGNORE: list[str] = [
|
118
|
-
AMAZON_DEVICE_TYPE, # Alexa App for
|
137
|
+
AMAZON_DEVICE_TYPE, # Alexa App for iOS
|
138
|
+
"A2TF17PFR55MTB", # Alexa App for Android
|
119
139
|
"A1RTAM01W29CUP", # Alexa App for PC
|
120
140
|
]
|
121
141
|
|
@@ -140,6 +160,10 @@ DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
|
|
140
160
|
"model": "Echo Show 15",
|
141
161
|
"hw_version": "Gen1",
|
142
162
|
},
|
163
|
+
"A1NL4BVLQ4L3N3": {
|
164
|
+
"model": "Echo Show",
|
165
|
+
"hw_version": "Gen1",
|
166
|
+
},
|
143
167
|
"A1Q6UGEXJZWJQ0": {
|
144
168
|
"model": "Fire TV Stick 4K",
|
145
169
|
"hw_version": "Gen2",
|
@@ -165,6 +189,10 @@ DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
|
|
165
189
|
"model": "FireTV 4k MAX",
|
166
190
|
"hw_version": "Gen2",
|
167
191
|
},
|
192
|
+
"A1XWJRHALS1REP": {
|
193
|
+
"model": "Echo Show 5",
|
194
|
+
"hw_version": "Gen2",
|
195
|
+
},
|
168
196
|
"A1Z88NGR2BK6A2": {
|
169
197
|
"model": "Echo Show 8",
|
170
198
|
"hw_version": "Gen1",
|
@@ -197,10 +225,18 @@ DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
|
|
197
225
|
"model": "Fire TV Stick",
|
198
226
|
"hw_version": "Gen2",
|
199
227
|
},
|
228
|
+
"A2M35JJZWCQOMZ": {
|
229
|
+
"model": "Echo Plus",
|
230
|
+
"hw_version": "Gen1",
|
231
|
+
},
|
200
232
|
"A2M4YX06LWP8WI": {
|
201
233
|
"model": "Fire Tablet 7",
|
202
234
|
"hw_version": "Gen5",
|
203
235
|
},
|
236
|
+
"A2N49KXGVA18AR": {
|
237
|
+
"model": "Fire HD 10 Plus",
|
238
|
+
"hw_version": "Gen11",
|
239
|
+
},
|
204
240
|
"A2U21SRK4QGSE1": {
|
205
241
|
"model": "Echo Dot",
|
206
242
|
"hw_version": "Gen4",
|
@@ -250,6 +286,10 @@ DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
|
|
250
286
|
"model": "Sonos Beam",
|
251
287
|
"hw_version": None,
|
252
288
|
},
|
289
|
+
"A3RBAYBE7VM004": {
|
290
|
+
"model": "Echo Studio",
|
291
|
+
"hw_version": None,
|
292
|
+
},
|
253
293
|
"A3RMGO6LYLH7YN": {
|
254
294
|
"model": "Echo Dot",
|
255
295
|
"hw_version": "Gen4",
|
@@ -299,6 +339,10 @@ DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
|
|
299
339
|
"model": "Fire TV Stick 4K",
|
300
340
|
"hw_version": "Gen1",
|
301
341
|
},
|
342
|
+
"AP1F6KUH00XPV": {
|
343
|
+
"model": "Echo Stereo Pair",
|
344
|
+
"hw_version": "Virtual",
|
345
|
+
},
|
302
346
|
"ASQZWP4GPYUT7": {
|
303
347
|
"model": "Echo pop",
|
304
348
|
"hw_version": "Gen1",
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""Utils module for Amazon devices."""
|
2
|
+
|
3
|
+
from collections.abc import Collection
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from .const import TO_REDACT
|
7
|
+
|
8
|
+
|
9
|
+
def obfuscate_email(email: str) -> str:
|
10
|
+
"""Obfuscate an email address partially."""
|
11
|
+
try:
|
12
|
+
username, domain = email.split("@")
|
13
|
+
domain_name, domain_ext = domain.rsplit(".", 1)
|
14
|
+
|
15
|
+
def obfuscate_part(part: str, visible: int = 1) -> str:
|
16
|
+
if len(part) <= visible:
|
17
|
+
return "*" * len(part)
|
18
|
+
return part[:visible] + "*" * (len(part) - visible)
|
19
|
+
|
20
|
+
# Obfuscate username and domain parts
|
21
|
+
obf_user = ".".join(obfuscate_part(u, 1) for u in username.split("."))
|
22
|
+
obf_domain = obfuscate_part(domain_name, 1)
|
23
|
+
|
24
|
+
except (SyntaxError, ValueError):
|
25
|
+
return "[invalid email]"
|
26
|
+
else:
|
27
|
+
return f"{obf_user}@{obf_domain}.{domain_ext}"
|
28
|
+
|
29
|
+
|
30
|
+
def scrub_fields(
|
31
|
+
obj: Any, # noqa: ANN401
|
32
|
+
field_names: Collection[str] = TO_REDACT,
|
33
|
+
replacement: str = "[REDACTED]",
|
34
|
+
) -> Any: # noqa: ANN401
|
35
|
+
"""Return a deep-copied version of *obj* with redacted keys."""
|
36
|
+
if isinstance(obj, dict):
|
37
|
+
result = {}
|
38
|
+
for k, v in obj.items():
|
39
|
+
# If the key itself is sensitive → overwrite its value
|
40
|
+
if k in field_names:
|
41
|
+
result[k] = replacement
|
42
|
+
else:
|
43
|
+
# Otherwise keep walking
|
44
|
+
result[k] = scrub_fields(v, field_names, replacement)
|
45
|
+
return result
|
46
|
+
|
47
|
+
if isinstance(obj, list):
|
48
|
+
return [scrub_fields(item, field_names, replacement) for item in obj]
|
49
|
+
|
50
|
+
if isinstance(obj, tuple):
|
51
|
+
return tuple(scrub_fields(item, field_names, replacement) for item in obj)
|
52
|
+
|
53
|
+
if isinstance(obj, set):
|
54
|
+
# Note: a set cannot contain mutable/unhashable items like dicts,
|
55
|
+
# so we assume its members are hashable after scrubbing.
|
56
|
+
return {scrub_fields(item, field_names, replacement) for item in obj}
|
57
|
+
|
58
|
+
return obj
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|