aioamazondevices 6.5.1__py3-none-any.whl → 11.0.2__py3-none-any.whl

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.
@@ -0,0 +1,439 @@
1
+ """Support for Amazon login."""
2
+
3
+ import asyncio
4
+ import base64
5
+ import hashlib
6
+ import secrets
7
+ import uuid
8
+ from datetime import UTC, datetime, timedelta
9
+ from http import HTTPMethod, HTTPStatus
10
+ from typing import Any, cast
11
+ from urllib.parse import parse_qs, urlencode
12
+
13
+ from bs4 import BeautifulSoup, Tag
14
+ from multidict import MultiDictProxy
15
+ from yarl import URL
16
+
17
+ from .const.http import (
18
+ AMAZON_APP_NAME,
19
+ AMAZON_APP_VERSION,
20
+ AMAZON_CLIENT_OS,
21
+ AMAZON_DEVICE_SOFTWARE_VERSION,
22
+ AMAZON_DEVICE_TYPE,
23
+ DEFAULT_SITE,
24
+ REFRESH_AUTH_COOKIES,
25
+ URI_DEVICES,
26
+ URI_SIGNIN,
27
+ )
28
+ from .const.metadata import MAX_CUSTOMER_ACCOUNT_RETRIES
29
+ from .exceptions import (
30
+ CannotAuthenticate,
31
+ CannotRegisterDevice,
32
+ CannotRetrieveData,
33
+ WrongMethod,
34
+ )
35
+ from .http_wrapper import AmazonHttpWrapper, AmazonSessionStateData
36
+ from .utils import _LOGGER, obfuscate_email, scrub_fields
37
+
38
+
39
+ class AmazonLogin:
40
+ """Amazon login for Echo devices."""
41
+
42
+ def __init__(
43
+ self,
44
+ http_wrapper: AmazonHttpWrapper,
45
+ session_state_data: AmazonSessionStateData,
46
+ ) -> None:
47
+ """Login to Amazon."""
48
+ self._session_state_data = session_state_data
49
+ self._http_wrapper = http_wrapper
50
+
51
+ self._serial = self._serial_number()
52
+
53
+ def _serial_number(self) -> str:
54
+ """Get or calculate device serial number."""
55
+ if not self._session_state_data.login_stored_data:
56
+ # Create a new serial number
57
+ _LOGGER.debug("Cannot find previous login data, creating new serial number")
58
+ return uuid.uuid4().hex.upper()
59
+
60
+ _LOGGER.debug("Found previous login data, loading serial number")
61
+ return cast(
62
+ "str",
63
+ self._session_state_data.login_stored_data["device_info"][
64
+ "device_serial_number"
65
+ ],
66
+ )
67
+
68
+ def _create_code_verifier(self, length: int = 32) -> bytes:
69
+ """Create code verifier."""
70
+ verifier = secrets.token_bytes(length)
71
+ return base64.urlsafe_b64encode(verifier).rstrip(b"=")
72
+
73
+ def _create_s256_code_challenge(self, verifier: bytes) -> bytes:
74
+ """Create S256 code challenge."""
75
+ m = hashlib.sha256(verifier)
76
+ return base64.urlsafe_b64encode(m.digest()).rstrip(b"=")
77
+
78
+ def _build_client_id(self) -> str:
79
+ """Build client ID."""
80
+ client_id = self._serial.encode() + b"#" + AMAZON_DEVICE_TYPE.encode("utf-8")
81
+ return client_id.hex()
82
+
83
+ def _build_oauth_url(
84
+ self,
85
+ code_verifier: bytes,
86
+ client_id: str,
87
+ ) -> str:
88
+ """Build the url to login to Amazon as a Mobile device."""
89
+ code_challenge = self._create_s256_code_challenge(code_verifier)
90
+
91
+ oauth_params = {
92
+ "openid.return_to": "https://www.amazon.com/ap/maplanding",
93
+ "openid.oa2.code_challenge_method": "S256",
94
+ "openid.assoc_handle": "amzn_dp_project_dee_ios",
95
+ "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
96
+ "pageId": "amzn_dp_project_dee_ios",
97
+ "accountStatusPolicy": "P1",
98
+ "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select",
99
+ "openid.mode": "checkid_setup",
100
+ "openid.ns.oa2": "http://www.amazon.com/ap/ext/oauth/2",
101
+ "openid.oa2.client_id": f"device:{client_id}",
102
+ "language": self._session_state_data.language.replace("-", "_"),
103
+ "openid.ns.pape": "http://specs.openid.net/extensions/pape/1.0",
104
+ "openid.oa2.code_challenge": code_challenge,
105
+ "openid.oa2.scope": "device_auth_access",
106
+ "openid.ns": "http://specs.openid.net/auth/2.0",
107
+ "openid.pape.max_auth_age": "0",
108
+ "openid.oa2.response_type": "code",
109
+ }
110
+
111
+ return f"https://www.amazon.com{URI_SIGNIN}?{urlencode(oauth_params)}"
112
+
113
+ def _get_inputs_from_soup(self, soup: BeautifulSoup) -> dict[str, str]:
114
+ """Extract hidden form input fields from a Amazon login page."""
115
+ form = soup.find("form", {"name": "signIn"}) or soup.find("form")
116
+
117
+ if not isinstance(form, Tag):
118
+ raise CannotAuthenticate("Unable to find form in login response")
119
+
120
+ inputs = {}
121
+ for field in form.find_all("input"):
122
+ if isinstance(field, Tag) and field.get("type", "") == "hidden":
123
+ inputs[field["name"]] = field.get("value", "")
124
+
125
+ return inputs
126
+
127
+ def _get_request_from_soup(self, soup: BeautifulSoup) -> tuple[str, str]:
128
+ """Extract URL and method for the next request."""
129
+ _LOGGER.debug("Get request data from HTML source")
130
+ form = soup.find("form", {"name": "signIn"}) or soup.find("form")
131
+ if isinstance(form, Tag):
132
+ method = form.get("method")
133
+ url = form.get("action")
134
+ if isinstance(method, str) and isinstance(url, str):
135
+ return method, url
136
+ raise CannotAuthenticate("Unable to extract form data from response")
137
+
138
+ def _extract_code_from_url(self, url: URL) -> str:
139
+ """Extract the access token from url query after login."""
140
+ parsed_url: dict[str, list[str]] = {}
141
+ if isinstance(url.query, bytes):
142
+ parsed_url = parse_qs(url.query.decode())
143
+ elif isinstance(url.query, MultiDictProxy):
144
+ for key, value in url.query.items():
145
+ parsed_url[key] = [value]
146
+ else:
147
+ raise CannotAuthenticate(
148
+ f"Unable to extract authorization code from url: {url}"
149
+ )
150
+ return parsed_url["openid.oa2.authorization_code"][0]
151
+
152
+ async def _register_device(
153
+ self,
154
+ data: dict[str, Any],
155
+ ) -> dict[str, Any]:
156
+ """Register a dummy Alexa device."""
157
+ authorization_code: str = data["authorization_code"]
158
+ code_verifier: bytes = data["code_verifier"]
159
+
160
+ body = {
161
+ "requested_extensions": ["device_info", "customer_info"],
162
+ "cookies": {
163
+ "website_cookies": [],
164
+ "domain": f".amazon.{self._session_state_data.domain}",
165
+ },
166
+ "registration_data": {
167
+ "domain": "Device",
168
+ "app_version": AMAZON_APP_VERSION,
169
+ "device_type": AMAZON_DEVICE_TYPE,
170
+ "device_name": (
171
+ f"%FIRST_NAME%\u0027s%DUPE_STRATEGY_1ST%{AMAZON_APP_NAME}"
172
+ ),
173
+ "os_version": AMAZON_CLIENT_OS,
174
+ "device_serial": self._serial,
175
+ "device_model": "iPhone",
176
+ "app_name": AMAZON_APP_NAME,
177
+ "software_version": AMAZON_DEVICE_SOFTWARE_VERSION,
178
+ },
179
+ "auth_data": {
180
+ "use_global_authentication": "true",
181
+ "client_id": self._build_client_id(),
182
+ "authorization_code": authorization_code,
183
+ "code_verifier": code_verifier.decode(),
184
+ "code_algorithm": "SHA-256",
185
+ "client_domain": "DeviceLegacy",
186
+ },
187
+ "user_context_map": {"frc": self._http_wrapper.cookies["frc"]},
188
+ "requested_token_type": [
189
+ "bearer",
190
+ "mac_dms",
191
+ "website_cookies",
192
+ "store_authentication_cookie",
193
+ ],
194
+ }
195
+
196
+ register_url = "https://api.amazon.com/auth/register"
197
+ _, raw_resp = await self._http_wrapper.session_request(
198
+ method=HTTPMethod.POST,
199
+ url=register_url,
200
+ input_data=body,
201
+ json_data=True,
202
+ )
203
+ resp_json = await self._http_wrapper.response_to_json(raw_resp)
204
+
205
+ if raw_resp.status != HTTPStatus.OK:
206
+ msg = resp_json["response"]["error"]["message"]
207
+ _LOGGER.error(
208
+ "Cannot register device for %s: %s",
209
+ obfuscate_email(self._session_state_data.login_email),
210
+ msg,
211
+ )
212
+ raise CannotRegisterDevice(
213
+ f"{await self._http_wrapper.http_phrase_error(raw_resp.status)}: {msg}"
214
+ )
215
+
216
+ success_response = resp_json["response"]["success"]
217
+
218
+ tokens = success_response["tokens"]
219
+ adp_token = tokens["mac_dms"]["adp_token"]
220
+ device_private_key = tokens["mac_dms"]["device_private_key"]
221
+ store_authentication_cookie = tokens["store_authentication_cookie"]
222
+ access_token = tokens["bearer"]["access_token"]
223
+ refresh_token = tokens["bearer"]["refresh_token"]
224
+ expires_s = int(tokens["bearer"]["expires_in"])
225
+ expires = (datetime.now(UTC) + timedelta(seconds=expires_s)).timestamp()
226
+
227
+ extensions = success_response["extensions"]
228
+ device_info = extensions["device_info"]
229
+ customer_info = extensions["customer_info"]
230
+
231
+ website_cookies = {}
232
+ for cookie in tokens["website_cookies"]:
233
+ website_cookies[cookie["Name"]] = cookie["Value"].replace(r'"', r"")
234
+
235
+ login_data = {
236
+ "adp_token": adp_token,
237
+ "device_private_key": device_private_key,
238
+ "access_token": access_token,
239
+ "refresh_token": refresh_token,
240
+ "expires": expires,
241
+ "website_cookies": website_cookies,
242
+ "store_authentication_cookie": store_authentication_cookie,
243
+ "device_info": device_info,
244
+ "customer_info": customer_info,
245
+ }
246
+ _LOGGER.info("Register device: %s", scrub_fields(login_data))
247
+ return login_data
248
+
249
+ async def login_mode_interactive(self, otp_code: str) -> dict[str, Any]:
250
+ """Login to Amazon interactively via OTP."""
251
+ _LOGGER.debug(
252
+ "Logging-in for %s [otp code: %s]",
253
+ obfuscate_email(self._session_state_data.login_email),
254
+ bool(otp_code),
255
+ )
256
+
257
+ device_login_data = await self._login_mode_interactive_oauth(otp_code)
258
+
259
+ login_data = await self._register_device(device_login_data)
260
+ self._session_state_data.login_stored_data = login_data
261
+
262
+ await self._domain_refresh_auth_cookies()
263
+
264
+ await self.obtain_account_customer_id()
265
+
266
+ self._session_state_data.login_stored_data.update(
267
+ {"site": f"https://www.amazon.{self._session_state_data.domain}"}
268
+ )
269
+
270
+ return self._session_state_data.login_stored_data
271
+
272
+ async def _login_mode_interactive_oauth(
273
+ self, otp_code: str
274
+ ) -> dict[str, str | bytes]:
275
+ """Login interactive via oauth URL."""
276
+ code_verifier = self._create_code_verifier()
277
+ client_id = self._build_client_id()
278
+
279
+ _LOGGER.debug("Build oauth URL")
280
+ login_url = self._build_oauth_url(code_verifier, client_id)
281
+
282
+ login_soup, _ = await self._http_wrapper.session_request(
283
+ method=HTTPMethod.GET,
284
+ url=login_url,
285
+ )
286
+ login_method, login_url = self._get_request_from_soup(login_soup)
287
+ login_inputs = self._get_inputs_from_soup(login_soup)
288
+ login_inputs["email"] = self._session_state_data.login_email
289
+ login_inputs["password"] = self._session_state_data.login_password
290
+
291
+ _LOGGER.debug("Register at %s", login_url)
292
+ login_soup, _ = await self._http_wrapper.session_request(
293
+ method=login_method,
294
+ url=login_url,
295
+ input_data=login_inputs,
296
+ )
297
+
298
+ if not login_soup.find("input", id="auth-mfa-otpcode"):
299
+ _LOGGER.debug(
300
+ 'Cannot find "auth-mfa-otpcode" in html source [%s]', login_url
301
+ )
302
+ raise CannotAuthenticate("MFA OTP code not found on login page")
303
+
304
+ login_method, login_url = self._get_request_from_soup(login_soup)
305
+
306
+ login_inputs = self._get_inputs_from_soup(login_soup)
307
+ login_inputs["otpCode"] = otp_code
308
+ login_inputs["mfaSubmit"] = "Submit"
309
+ login_inputs["rememberDevice"] = "false"
310
+
311
+ login_soup, login_resp = await self._http_wrapper.session_request(
312
+ method=login_method,
313
+ url=login_url,
314
+ input_data=login_inputs,
315
+ )
316
+ _LOGGER.debug("Login response url:%s", login_resp.url)
317
+
318
+ authcode = self._extract_code_from_url(login_resp.url)
319
+ _LOGGER.debug("Login extracted authcode: %s", authcode)
320
+
321
+ return {
322
+ "authorization_code": authcode,
323
+ "code_verifier": code_verifier,
324
+ "domain": self._session_state_data.domain,
325
+ }
326
+
327
+ async def login_mode_stored_data(self) -> dict[str, Any]:
328
+ """Login to Amazon using previously stored data."""
329
+ if not self._session_state_data.login_stored_data:
330
+ _LOGGER.debug(
331
+ "Cannot find previous login data,\
332
+ use login_mode_interactive() method instead",
333
+ )
334
+ raise WrongMethod
335
+
336
+ _LOGGER.debug(
337
+ "Logging-in for %s with stored data",
338
+ obfuscate_email(self._session_state_data.login_email),
339
+ )
340
+
341
+ await self.obtain_account_customer_id()
342
+
343
+ return self._session_state_data.login_stored_data
344
+
345
+ async def _get_alexa_domain(self) -> str:
346
+ """Get the Alexa domain."""
347
+ _LOGGER.debug("Retrieve Alexa domain")
348
+ _, raw_resp = await self._http_wrapper.session_request(
349
+ method=HTTPMethod.GET,
350
+ url=f"https://alexa.amazon.{self._session_state_data.domain}/api/welcome",
351
+ )
352
+ json_data = await self._http_wrapper.response_to_json(raw_resp)
353
+ return cast(
354
+ "str",
355
+ json_data.get(
356
+ "alexaHostName", f"alexa.amazon.{self._session_state_data.domain}"
357
+ ),
358
+ )
359
+
360
+ async def _refresh_auth_cookies(self) -> None:
361
+ """Refresh cookies after domain swap."""
362
+ _, json_token_resp = await self._http_wrapper.refresh_data(REFRESH_AUTH_COOKIES)
363
+
364
+ # Need to take cookies from response and create them as cookies
365
+ website_cookies = self._session_state_data.login_stored_data[
366
+ "website_cookies"
367
+ ] = {}
368
+ await self._http_wrapper.clear_cookies()
369
+
370
+ cookie_json = json_token_resp["response"]["tokens"]["cookies"]
371
+ for cookie_domain in cookie_json:
372
+ for cookie in cookie_json[cookie_domain]:
373
+ new_cookie_value = cookie["Value"].replace(r'"', r"")
374
+ new_cookie = {cookie["Name"]: new_cookie_value}
375
+ await self._http_wrapper.set_cookies(new_cookie, URL(cookie_domain))
376
+ website_cookies.update(new_cookie)
377
+ if cookie["Name"] == "session-token":
378
+ self._session_state_data.login_stored_data[
379
+ "store_authentication_cookie"
380
+ ] = {"cookie": new_cookie_value}
381
+
382
+ async def _domain_refresh_auth_cookies(self) -> None:
383
+ """Refresh cookies after domain swap."""
384
+ _LOGGER.debug("Refreshing auth cookies after domain change")
385
+
386
+ # Get the new Alexa domain
387
+ user_domain = (await self._get_alexa_domain()).replace("alexa", "https://www")
388
+ if user_domain != DEFAULT_SITE:
389
+ _LOGGER.debug("User domain changed to %s", user_domain)
390
+ self._session_state_data.country_specific_data(user_domain)
391
+ await self._http_wrapper.clear_csrf_cookie()
392
+ await self._refresh_auth_cookies()
393
+
394
+ async def obtain_account_customer_id(self) -> None:
395
+ """Find account customer id."""
396
+ for retry_count in range(MAX_CUSTOMER_ACCOUNT_RETRIES):
397
+ if not self._session_state_data.account_customer_id:
398
+ await asyncio.sleep(2) # allow time for device to be registered
399
+
400
+ _LOGGER.debug(
401
+ "Lookup customer account ID (attempt %d/%d)",
402
+ retry_count + 1,
403
+ MAX_CUSTOMER_ACCOUNT_RETRIES,
404
+ )
405
+ _, raw_resp = await self._http_wrapper.session_request(
406
+ method=HTTPMethod.GET,
407
+ url=f"https://alexa.amazon.{self._session_state_data.domain}{URI_DEVICES}",
408
+ )
409
+
410
+ json_data = await self._http_wrapper.response_to_json(raw_resp, "devices")
411
+
412
+ for device in json_data.get("devices", []):
413
+ dev_serial = device.get("serialNumber")
414
+ if not dev_serial:
415
+ _LOGGER.warning(
416
+ "Skipping device without serial number: %s",
417
+ device["accountName"],
418
+ )
419
+ continue
420
+ if device["deviceType"] != AMAZON_DEVICE_TYPE:
421
+ continue
422
+
423
+ this_device_serial = self._session_state_data.login_stored_data[
424
+ "device_info"
425
+ ]["device_serial_number"]
426
+
427
+ for subdevice in device["appDeviceList"]:
428
+ if subdevice["serialNumber"] == this_device_serial:
429
+ account_owner_customer_id = device["deviceOwnerCustomerId"]
430
+ _LOGGER.debug(
431
+ "Setting account owner: %s",
432
+ account_owner_customer_id,
433
+ )
434
+ self._session_state_data.account_customer_id = (
435
+ account_owner_customer_id
436
+ )
437
+ return
438
+ if not self._session_state_data.account_customer_id:
439
+ raise CannotRetrieveData("Cannot find account owner customer ID")
@@ -0,0 +1,65 @@
1
+ """aioamazondevices structures module."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from enum import StrEnum
6
+
7
+
8
+ @dataclass
9
+ class AmazonDeviceSensor:
10
+ """Amazon device sensor class."""
11
+
12
+ name: str
13
+ value: str | int | float
14
+ error: bool
15
+ error_type: str | None
16
+ error_msg: str | None
17
+ scale: str | None
18
+
19
+
20
+ @dataclass
21
+ class AmazonSchedule:
22
+ """Amazon schedule class."""
23
+
24
+ type: str # alarm, reminder, timer
25
+ status: str
26
+ label: str
27
+ next_occurrence: datetime | None
28
+
29
+
30
+ @dataclass
31
+ class AmazonDevice:
32
+ """Amazon device class."""
33
+
34
+ account_name: str
35
+ capabilities: list[str]
36
+ device_family: str
37
+ device_type: str
38
+ device_owner_customer_id: str
39
+ household_device: bool
40
+ device_cluster_members: dict[str, str | None]
41
+ online: bool
42
+ serial_number: str
43
+ software_version: str
44
+ entity_id: str | None
45
+ endpoint_id: str | None
46
+ sensors: dict[str, AmazonDeviceSensor]
47
+ notifications: dict[str, AmazonSchedule]
48
+
49
+
50
+ class AmazonSequenceType(StrEnum):
51
+ """Amazon sequence types."""
52
+
53
+ Announcement = "AlexaAnnouncement"
54
+ Speak = "Alexa.Speak"
55
+ Sound = "Alexa.Sound"
56
+ Music = "Alexa.Music.PlaySearchPhrase"
57
+ TextCommand = "Alexa.TextCommand"
58
+ LaunchSkill = "Alexa.Operation.SkillConnections.Launch"
59
+
60
+
61
+ class AmazonMusicSource(StrEnum):
62
+ """Amazon music sources."""
63
+
64
+ Radio = "TUNEIN"
65
+ AmazonMusic = "AMAZON_MUSIC"
aioamazondevices/utils.py CHANGED
@@ -1,9 +1,31 @@
1
1
  """Utils module for Amazon devices."""
2
2
 
3
+ import logging
3
4
  from collections.abc import Collection
4
5
  from typing import Any
5
6
 
6
- from .const import TO_REDACT
7
+ _LOGGER = logging.getLogger(__package__)
8
+
9
+ TO_REDACT = {
10
+ "address",
11
+ "address1",
12
+ "address2",
13
+ "address3",
14
+ "city",
15
+ "county",
16
+ "customerId",
17
+ "deviceAccountId",
18
+ "deviceAddress",
19
+ "deviceOwnerCustomerId",
20
+ "given_name",
21
+ "name",
22
+ "password",
23
+ "postalCode",
24
+ "searchCustomerId",
25
+ "state",
26
+ "street",
27
+ "user_id",
28
+ }
7
29
 
8
30
 
9
31
  def obfuscate_email(email: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aioamazondevices
3
- Version: 6.5.1
3
+ Version: 11.0.2
4
4
  Summary: Python library to control Amazon devices
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -81,7 +81,6 @@ The script accept command line arguments or a library_test.json config file:
81
81
  "single_device_name": "Echo Dot Livingroom",
82
82
  "cluster_device_name": "Everywhere",
83
83
  "login_data_file": "out/login_data.json",
84
- "save_raw_data": true,
85
84
  "test": true
86
85
  }
87
86
  ```
@@ -135,12 +134,28 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
135
134
  <sub><b>Ivan F. Martinez</b></sub>
136
135
  </a>
137
136
  </td>
137
+ <td align="center">
138
+ <a href="https://github.com/eyadkobatte">
139
+ <img src="https://avatars.githubusercontent.com/u/16541074?v=4" width="100;" alt="eyadkobatte"/>
140
+ <br />
141
+ <sub><b>Eyad Kobatte</b></sub>
142
+ </a>
143
+ </td>
138
144
  <td align="center">
139
145
  <a href="https://github.com/AzonInc">
140
146
  <img src="https://avatars.githubusercontent.com/u/11911587?v=4" width="100;" alt="AzonInc"/>
141
147
  <br />
142
148
  <sub><b>Flo</b></sub>
143
149
  </a>
150
+ </td>
151
+ </tr>
152
+ <tr>
153
+ <td align="center">
154
+ <a href="https://github.com/francescolf">
155
+ <img src="https://avatars.githubusercontent.com/u/14892143?v=4" width="100;" alt="francescolf"/>
156
+ <br />
157
+ <sub><b>Francesco Lo Faro</b></sub>
158
+ </a>
144
159
  </td>
145
160
  <td align="center">
146
161
  <a href="https://github.com/lchavezcuu">
@@ -149,8 +164,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
149
164
  <sub><b>Luis Chavez</b></sub>
150
165
  </a>
151
166
  </td>
152
- </tr>
153
- <tr>
154
167
  <td align="center">
155
168
  <a href="https://github.com/maxmati">
156
169
  <img src="https://avatars.githubusercontent.com/u/509560?v=4" width="100;" alt="maxmati"/>
@@ -0,0 +1,23 @@
1
+ aioamazondevices/__init__.py,sha256=RH-8Gu8O2U2mizx4VXkLW5aTuvHUP0LZ9GAU0b3dzdc,277
2
+ aioamazondevices/api.py,sha256=e5RcQpRWM52IxwUBXLmgGLYqkawKBu474Qit3ijD6cM,19252
3
+ aioamazondevices/const/__init__.py,sha256=xQt8Smq2Ojjo30KKdev_gxDkcpY9PJlArTUXKel8oqs,38
4
+ aioamazondevices/const/devices.py,sha256=-W4yBSeACIsG3z_8MC_jrQoFJN1NZMe4c62XPee6q1k,11285
5
+ aioamazondevices/const/http.py,sha256=tgsRJWfx_SFRBk6T7TiClP02qIsPN4wShfMqRbZcQio,1204
6
+ aioamazondevices/const/metadata.py,sha256=SV8sVVevB0ap7ZLNcAg9L-CSoSe885jlmg15ls99shA,1100
7
+ aioamazondevices/const/queries.py,sha256=weCYmUJedNyx1P8z_tG_6cHGMjICUVo6KOckkl4P_-w,1900
8
+ aioamazondevices/const/schedules.py,sha256=GohhoXSGxXEq_fEycSBsjaDJdIxuMtvspLVqaCmJjOU,1378
9
+ aioamazondevices/const/sounds.py,sha256=hdrXYKEuSOCw7fDdylvqLcDqD5poHzhgXEnUwq_TwDE,1923
10
+ aioamazondevices/exceptions.py,sha256=gRYrxNAJnrV6uRuMx5e76VMvtNKyceXd09q84pDBBrI,638
11
+ aioamazondevices/http_wrapper.py,sha256=MrPiWisER2piJy02yiP5eAP6SDuq-i10lIp5DN_epYc,14467
12
+ aioamazondevices/implementation/__init__.py,sha256=HU18CNdQaDYFksyHi8BL0a0FK0_knqo60ueOxH3zvgo,47
13
+ aioamazondevices/implementation/dnd.py,sha256=UrYSavcaddpYY7JA3aGXEjw7TCNaVERnEcunNcvheac,2065
14
+ aioamazondevices/implementation/notification.py,sha256=b9pvajZvdn-ykISuYFb9-BW4bVczUQGnnnuctJ9_kZY,8674
15
+ aioamazondevices/implementation/sequence.py,sha256=xDOzQouGrsPAAPjBfHkDIAuC0fTEB5uqi8QR4VmNHYs,5650
16
+ aioamazondevices/login.py,sha256=NNbZjaSC32uOGsKNkSx-URA4DIEGpCZSLp2Ey5xFv9U,17375
17
+ aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ aioamazondevices/structures.py,sha256=2duKj9kiknFQHVGOysh0pWTQagTT-IDStCskayrwYSE,1425
19
+ aioamazondevices/utils.py,sha256=V6b5_CNJ5LtVBl9KSitr14nNle4mNDkZVojGhKfy60A,2373
20
+ aioamazondevices-11.0.2.dist-info/METADATA,sha256=GxyYwHb_ebJzaPjCEpIVCvYnH-a4LnLqrU_Ah2eRLVk,8308
21
+ aioamazondevices-11.0.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
22
+ aioamazondevices-11.0.2.dist-info/licenses/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
23
+ aioamazondevices-11.0.2.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- aioamazondevices/__init__.py,sha256=F9PVHDhnnAwbsRZGVQ6jRtVcKcvPsK2zm7IZVRX_p64,276
2
- aioamazondevices/api.py,sha256=4ut_LNMO4nHD1Fw92XP7hXzTdcOnnYeQC5GynqvSbqk,58409
3
- aioamazondevices/const.py,sha256=EUoGr9gqqwoSz01gk56Nhw_-1Tdmv0Yjyh9cJb3ofbs,13556
4
- aioamazondevices/exceptions.py,sha256=gRYrxNAJnrV6uRuMx5e76VMvtNKyceXd09q84pDBBrI,638
5
- aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- aioamazondevices/query.py,sha256=xVgXF1PIiH7uu33Q_iVfCtdH9hqLOAGdCNnAxTZ76UE,1883
7
- aioamazondevices/sounds.py,sha256=CXMDk-KoKVFxBdVAw3MeOClqgpzcVDxvQhFOJp7qX-Y,1896
8
- aioamazondevices/utils.py,sha256=RzuKRhnq_8ymCoJMoQJ2vBYyuew06RSWpqQWmqdNczE,2019
9
- aioamazondevices-6.5.1.dist-info/METADATA,sha256=2XGsEjNlJpCKx4ffTMoEO_WjRnEuuU693ST-pRE0y1Q,7679
10
- aioamazondevices-6.5.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
11
- aioamazondevices-6.5.1.dist-info/licenses/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
12
- aioamazondevices-6.5.1.dist-info/RECORD,,