aioamazondevices 1.4.1__tar.gz → 1.5.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.4.1
3
+ Version: 1.5.0
4
4
  Summary: Python library to control Amazon devices
5
5
  License: Apache-2.0
6
6
  Author: Simone Chemelli
@@ -82,7 +82,8 @@ The script accept command line arguments or a library_test.json config file:
82
82
  "country": "IT",
83
83
  "email": "<my_address@gmail.com>",
84
84
  "password": "<my_password>",
85
- "device_name": "Echo Dot Livingroom",
85
+ "device_name_speak": "Echo Dot Livingroom",
86
+ "device_name_announcement": "Everywhere",
86
87
  "login_data_file": "out/login_data.json",
87
88
  "save_raw_data": "True"
88
89
  }
@@ -54,7 +54,8 @@ 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
+ "device_name_speak": "Echo Dot Livingroom",
58
+ "device_name_announcement": "Everywhere",
58
59
  "login_data_file": "out/login_data.json",
59
60
  "save_raw_data": "True"
60
61
  }
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "aioamazondevices"
3
- version = "1.4.1"
3
+ version = "1.5.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"
@@ -1,6 +1,6 @@
1
1
  """aioamazondevices library."""
2
2
 
3
- __version__ = "1.4.1"
3
+ __version__ = "1.5.0"
4
4
 
5
5
 
6
6
  from .api import AmazonDevice, AmazonEchoApi
@@ -8,7 +8,7 @@ import uuid
8
8
  from dataclasses import dataclass
9
9
  from datetime import UTC, datetime, timedelta
10
10
  from http import HTTPStatus
11
- from http.cookies import SimpleCookie
11
+ from http.cookies import Morsel
12
12
  from pathlib import Path
13
13
  from typing import Any, cast
14
14
  from urllib.parse import parse_qs, urlencode
@@ -54,6 +54,7 @@ class AmazonDevice:
54
54
  device_family: str
55
55
  device_type: str
56
56
  device_owner_customer_id: str
57
+ device_cluster_members: list[str]
57
58
  online: bool
58
59
  serial_number: str
59
60
  software_version: str
@@ -96,15 +97,16 @@ class AmazonEchoApi:
96
97
  self._save_raw_data = save_raw_data
97
98
  self._login_stored_data = login_data
98
99
  self._serial = self._serial_number()
100
+ self._list_for_clusters: dict[str, str] = {}
99
101
 
100
102
  self.session: ClientSession
101
103
 
102
- def _load_website_cookies(self) -> list[SimpleCookie]:
104
+ def _load_website_cookies(self) -> dict[str, str]:
103
105
  """Get website cookies, if avaliables."""
104
106
  if not self._login_stored_data:
105
- return []
107
+ return {}
106
108
 
107
- return cast("list", self._login_stored_data["website_cookies"])
109
+ return cast("dict", self._login_stored_data["website_cookies"])
108
110
 
109
111
  def _serial_number(self) -> str:
110
112
  """Get or calculate device serial number."""
@@ -255,7 +257,6 @@ class AmazonEchoApi:
255
257
  data=input_data if not json_data else orjson.dumps(input_data),
256
258
  cookies=self._load_website_cookies(),
257
259
  headers=headers,
258
- allow_redirects=True,
259
260
  )
260
261
  content_type: str = resp.headers.get("Content-Type", "")
261
262
  _LOGGER.debug(
@@ -363,7 +364,7 @@ class AmazonEchoApi:
363
364
  input_data=body,
364
365
  json_data=True,
365
366
  )
366
- resp_json = resp.json()
367
+ resp_json = await resp.json()
367
368
 
368
369
  if resp.status != HTTPStatus.OK:
369
370
  _LOGGER.error(
@@ -374,7 +375,7 @@ class AmazonEchoApi:
374
375
  raise CannotRegisterDevice(resp_json)
375
376
 
376
377
  await self._save_to_file(
377
- resp.text,
378
+ await resp.text(),
378
379
  url=register_url,
379
380
  extension=JSON_EXTENSION,
380
381
  )
@@ -521,7 +522,8 @@ class AmazonEchoApi:
521
522
  _LOGGER.debug("Response data: |%s|", response_data)
522
523
 
523
524
  if not self._csrf_cookie:
524
- self._csrf_cookie = raw_resp.cookies.get(CSRF_COOKIE).value
525
+ self._csrf_cookie = raw_resp.cookies.get(CSRF_COOKIE, Morsel()).value
526
+ _LOGGER.error("CSRF cookie value: <%s>", self._csrf_cookie)
525
527
 
526
528
  json_data = {} if len(response_data) == 0 else await raw_resp.json()
527
529
 
@@ -536,29 +538,44 @@ class AmazonEchoApi:
536
538
 
537
539
  final_devices_list: dict[str, AmazonDevice] = {}
538
540
  for device in devices.values():
541
+ devices_node = device[NODE_DEVICES]
542
+ preferences_node = device.get(NODE_PREFERENCES)
543
+ do_not_disturb_node = device[NODE_DO_NOT_DISTURB]
544
+ bluetooth_node = device[NODE_BLUETOOTH]
539
545
  # Remove stale, orphaned and virtual devices
540
546
  if (
541
547
  NODE_DEVICES not in device
542
- or device[NODE_DEVICES].get("deviceType") == AMAZON_DEVICE_TYPE
548
+ or devices_node.get("deviceType") == AMAZON_DEVICE_TYPE
543
549
  ):
544
550
  continue
545
551
 
546
- serial_number: str = device[NODE_DEVICES]["serialNumber"]
547
- preferences = device.get(NODE_PREFERENCES)
552
+ serial_number: str = devices_node["serialNumber"]
548
553
  final_devices_list[serial_number] = AmazonDevice(
549
- account_name=device[NODE_DEVICES]["accountName"],
550
- capabilities=device[NODE_DEVICES]["capabilities"],
551
- device_family=device[NODE_DEVICES]["deviceFamily"],
552
- device_type=device[NODE_DEVICES]["deviceType"],
553
- device_owner_customer_id=device[NODE_DEVICES]["deviceOwnerCustomerId"],
554
- online=device[NODE_DEVICES]["online"],
554
+ account_name=devices_node["accountName"],
555
+ capabilities=devices_node["capabilities"],
556
+ device_family=devices_node["deviceFamily"],
557
+ device_type=devices_node["deviceType"],
558
+ device_owner_customer_id=devices_node["deviceOwnerCustomerId"],
559
+ device_cluster_members=(
560
+ devices_node["clusterMembers"] or [serial_number]
561
+ ),
562
+ online=devices_node["online"],
555
563
  serial_number=serial_number,
556
- software_version=device[NODE_DEVICES]["softwareVersion"],
557
- do_not_disturb=device[NODE_DO_NOT_DISTURB]["enabled"],
558
- response_style=preferences["responseStyle"] if preferences else None,
559
- bluetooth_state=device[NODE_BLUETOOTH]["online"],
564
+ software_version=devices_node["softwareVersion"],
565
+ do_not_disturb=do_not_disturb_node["enabled"],
566
+ response_style=(
567
+ preferences_node["responseStyle"] if preferences_node else None
568
+ ),
569
+ bluetooth_state=bluetooth_node["online"],
560
570
  )
561
571
 
572
+ self._list_for_clusters.update(
573
+ {
574
+ device.serial_number: device.device_type
575
+ for device in final_devices_list.values()
576
+ }
577
+ )
578
+
562
579
  return final_devices_list
563
580
 
564
581
  async def auth_check_status(self) -> bool:
@@ -583,18 +600,71 @@ class AmazonEchoApi:
583
600
  _LOGGER.debug("Session authenticated: %s", authenticated)
584
601
  return bool(authenticated)
585
602
 
586
- async def call_alexa_speak(
587
- self,
588
- device: AmazonDevice,
589
- message_body: str,
590
- ) -> dict[str, Any]:
591
- """Call Alexa.Speak to send a message."""
603
+ async def _send_message(
604
+ self, device: AmazonDevice, message_type: str, message_body: str
605
+ ) -> None:
606
+ """Send message to specific device."""
592
607
  locale_data = Locale.parse(f"und_{self._login_country_code}")
593
608
  locale = f"{locale_data.language}-{locale_data.language}"
594
609
 
595
610
  if not self._login_stored_data:
596
611
  _LOGGER.warning("Trying to send message before login")
597
- return {}
612
+ return
613
+
614
+ payload: dict[str, Any]
615
+ if message_type == "Alexa.Speak":
616
+ payload = {
617
+ "deviceType": device.device_type,
618
+ "deviceSerialNumber": device.serial_number,
619
+ "locale": locale,
620
+ "customerId": device.device_owner_customer_id,
621
+ "textToSpeak": message_body,
622
+ "target": {
623
+ "customerId": device.device_owner_customer_id,
624
+ "devices": [
625
+ {
626
+ "deviceSerialNumber": device.serial_number,
627
+ "deviceTypeId": device.device_type,
628
+ },
629
+ ],
630
+ },
631
+ "skillId": "amzn1.ask.1p.saysomething",
632
+ }
633
+ else:
634
+ playback_devices: list[dict[str, str]] = [
635
+ {
636
+ "deviceSerialNumber": serial,
637
+ "deviceTypeId": self._list_for_clusters[serial],
638
+ }
639
+ for serial in device.device_cluster_members
640
+ if serial in self._list_for_clusters
641
+ ]
642
+
643
+ payload = {
644
+ "deviceType": device.device_type,
645
+ "deviceSerialNumber": device.serial_number,
646
+ "locale": locale,
647
+ "customerId": device.device_owner_customer_id,
648
+ "expireAfter": "PT5S",
649
+ "content": [
650
+ {
651
+ "locale": locale,
652
+ "display": {
653
+ "title": "Home Assistant",
654
+ "body": message_body,
655
+ },
656
+ "speak": {
657
+ "type": "text",
658
+ "value": message_body,
659
+ },
660
+ }
661
+ ],
662
+ "target": {
663
+ "customerId": device.device_owner_customer_id,
664
+ "devices": playback_devices,
665
+ },
666
+ "skillId": "amzn1.ask.1p.routines.messaging",
667
+ }
598
668
 
599
669
  sequence = {
600
670
  "@type": "com.amazon.alexa.behaviors.model.Sequence",
@@ -603,28 +673,13 @@ class AmazonEchoApi:
603
673
  "nodesToExecute": [
604
674
  {
605
675
  "@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode", # noqa: E501
606
- "type": "Alexa.Speak",
607
- "operationPayload": {
608
- "deviceType": device.device_type,
609
- "deviceSerialNumber": device.serial_number,
610
- "locale": locale,
611
- "customerId": device.device_owner_customer_id,
612
- "textToSpeak": message_body,
613
- "target": {
614
- "customerId": device.device_owner_customer_id,
615
- "devices": [
616
- {
617
- "deviceSerialNumber": device.serial_number,
618
- "deviceTypeId": device.device_type,
619
- },
620
- ],
621
- },
622
- "skillId": "amzn1.ask.1p.saysomething",
623
- },
676
+ "type": message_type,
677
+ "operationPayload": payload,
624
678
  },
625
679
  ],
626
680
  },
627
681
  }
682
+
628
683
  node_data = {
629
684
  "behaviorId": "PREVIEW",
630
685
  "sequenceJson": orjson.dumps(sequence).decode("utf-8"),
@@ -639,4 +694,20 @@ class AmazonEchoApi:
639
694
  json_data=True,
640
695
  )
641
696
 
642
- return node_data
697
+ return
698
+
699
+ async def call_alexa_speak(
700
+ self,
701
+ device: AmazonDevice,
702
+ message_body: str,
703
+ ) -> None:
704
+ """Call Alexa.Speak to send a message."""
705
+ return await self._send_message(device, "Alexa.Speak", message_body)
706
+
707
+ async def call_alexa_announcement(
708
+ self,
709
+ device: AmazonDevice,
710
+ message_body: str,
711
+ ) -> None:
712
+ """Call AlexaAnnouncement to send a message."""
713
+ return await self._send_message(device, "AlexaAnnouncement", message_body)