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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: aioamazondevices
3
- Version: 1.2.0
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.2.0"
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 = "*"
@@ -1,6 +1,6 @@
1
1
  """aioamazondevices library."""
2
2
 
3
- __version__ = "1.2.0"
3
+ __version__ = "1.3.0"
4
4
 
5
5
 
6
6
  from .api import AmazonDevice, AmazonEchoApi
@@ -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._website_cookies,
240
- headers={"Content-Type": "application/json"} if json_data else None,
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 == HTML_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: %s", 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)",