aioamazondevices 1.4.2__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.
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.0}/PKG-INFO +3 -2
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.0}/README.md +2 -1
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.0}/pyproject.toml +1 -1
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.0}/src/aioamazondevices/__init__.py +1 -1
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.0}/src/aioamazondevices/api.py +113 -41
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.0}/LICENSE +0 -0
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.0}/src/aioamazondevices/const.py +0 -0
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.0}/src/aioamazondevices/exceptions.py +0 -0
- {aioamazondevices-1.4.2 → aioamazondevices-1.5.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.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
|
-
"
|
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
|
-
"
|
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
|
}
|
@@ -8,6 +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 Morsel
|
11
12
|
from pathlib import Path
|
12
13
|
from typing import Any, cast
|
13
14
|
from urllib.parse import parse_qs, urlencode
|
@@ -53,6 +54,7 @@ class AmazonDevice:
|
|
53
54
|
device_family: str
|
54
55
|
device_type: str
|
55
56
|
device_owner_customer_id: str
|
57
|
+
device_cluster_members: list[str]
|
56
58
|
online: bool
|
57
59
|
serial_number: str
|
58
60
|
software_version: str
|
@@ -95,6 +97,7 @@ class AmazonEchoApi:
|
|
95
97
|
self._save_raw_data = save_raw_data
|
96
98
|
self._login_stored_data = login_data
|
97
99
|
self._serial = self._serial_number()
|
100
|
+
self._list_for_clusters: dict[str, str] = {}
|
98
101
|
|
99
102
|
self.session: ClientSession
|
100
103
|
|
@@ -254,7 +257,6 @@ class AmazonEchoApi:
|
|
254
257
|
data=input_data if not json_data else orjson.dumps(input_data),
|
255
258
|
cookies=self._load_website_cookies(),
|
256
259
|
headers=headers,
|
257
|
-
allow_redirects=True,
|
258
260
|
)
|
259
261
|
content_type: str = resp.headers.get("Content-Type", "")
|
260
262
|
_LOGGER.debug(
|
@@ -520,7 +522,8 @@ class AmazonEchoApi:
|
|
520
522
|
_LOGGER.debug("Response data: |%s|", response_data)
|
521
523
|
|
522
524
|
if not self._csrf_cookie:
|
523
|
-
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)
|
524
527
|
|
525
528
|
json_data = {} if len(response_data) == 0 else await raw_resp.json()
|
526
529
|
|
@@ -535,29 +538,44 @@ class AmazonEchoApi:
|
|
535
538
|
|
536
539
|
final_devices_list: dict[str, AmazonDevice] = {}
|
537
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]
|
538
545
|
# Remove stale, orphaned and virtual devices
|
539
546
|
if (
|
540
547
|
NODE_DEVICES not in device
|
541
|
-
or
|
548
|
+
or devices_node.get("deviceType") == AMAZON_DEVICE_TYPE
|
542
549
|
):
|
543
550
|
continue
|
544
551
|
|
545
|
-
serial_number: str =
|
546
|
-
preferences = device.get(NODE_PREFERENCES)
|
552
|
+
serial_number: str = devices_node["serialNumber"]
|
547
553
|
final_devices_list[serial_number] = AmazonDevice(
|
548
|
-
account_name=
|
549
|
-
capabilities=
|
550
|
-
device_family=
|
551
|
-
device_type=
|
552
|
-
device_owner_customer_id=
|
553
|
-
|
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"],
|
554
563
|
serial_number=serial_number,
|
555
|
-
software_version=
|
556
|
-
do_not_disturb=
|
557
|
-
response_style=
|
558
|
-
|
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"],
|
559
570
|
)
|
560
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
|
+
|
561
579
|
return final_devices_list
|
562
580
|
|
563
581
|
async def auth_check_status(self) -> bool:
|
@@ -582,18 +600,71 @@ class AmazonEchoApi:
|
|
582
600
|
_LOGGER.debug("Session authenticated: %s", authenticated)
|
583
601
|
return bool(authenticated)
|
584
602
|
|
585
|
-
async def
|
586
|
-
self,
|
587
|
-
|
588
|
-
|
589
|
-
) -> dict[str, Any]:
|
590
|
-
"""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."""
|
591
607
|
locale_data = Locale.parse(f"und_{self._login_country_code}")
|
592
608
|
locale = f"{locale_data.language}-{locale_data.language}"
|
593
609
|
|
594
610
|
if not self._login_stored_data:
|
595
611
|
_LOGGER.warning("Trying to send message before login")
|
596
|
-
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
|
+
}
|
597
668
|
|
598
669
|
sequence = {
|
599
670
|
"@type": "com.amazon.alexa.behaviors.model.Sequence",
|
@@ -602,28 +673,13 @@ class AmazonEchoApi:
|
|
602
673
|
"nodesToExecute": [
|
603
674
|
{
|
604
675
|
"@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode", # noqa: E501
|
605
|
-
"type":
|
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
|
-
},
|
676
|
+
"type": message_type,
|
677
|
+
"operationPayload": payload,
|
623
678
|
},
|
624
679
|
],
|
625
680
|
},
|
626
681
|
}
|
682
|
+
|
627
683
|
node_data = {
|
628
684
|
"behaviorId": "PREVIEW",
|
629
685
|
"sequenceJson": orjson.dumps(sequence).decode("utf-8"),
|
@@ -638,4 +694,20 @@ class AmazonEchoApi:
|
|
638
694
|
json_data=True,
|
639
695
|
)
|
640
696
|
|
641
|
-
return
|
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)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|