aioamazondevices 4.0.1__tar.gz → 5.0.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: 4.0.1
3
+ Version: 5.0.0
4
4
  Summary: Python library to control Amazon devices
5
5
  License: Apache-2.0
6
6
  Author: Simone Chemelli
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aioamazondevices"
3
- version = "4.0.1"
3
+ version = "5.0.0"
4
4
  requires-python = ">=3.12"
5
5
  description = "Python library to control Amazon devices"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  """aioamazondevices library."""
2
2
 
3
- __version__ = "4.0.1"
3
+ __version__ = "5.0.0"
4
4
 
5
5
 
6
6
  from .api import AmazonDevice, AmazonEchoApi
@@ -16,7 +16,11 @@ from typing import Any, cast
16
16
  from urllib.parse import parse_qs, urlencode
17
17
 
18
18
  import orjson
19
- from aiohttp import ClientConnectorError, ClientResponse, ClientSession
19
+ from aiohttp import (
20
+ ClientConnectorError,
21
+ ClientResponse,
22
+ ClientSession,
23
+ )
20
24
  from bs4 import BeautifulSoup, Tag
21
25
  from langcodes import Language
22
26
  from multidict import MultiDictProxy
@@ -37,9 +41,9 @@ from .const import (
37
41
  DEFAULT_AGENT,
38
42
  DEFAULT_ASSOC_HANDLE,
39
43
  DEFAULT_HEADERS,
44
+ DEFAULT_SITE,
40
45
  DEVICE_TO_IGNORE,
41
46
  DEVICE_TYPE_TO_MODEL,
42
- DOMAIN_BY_ISO3166_COUNTRY,
43
47
  HTML_EXTENSION,
44
48
  HTTP_ERROR_199,
45
49
  HTTP_ERROR_299,
@@ -49,6 +53,8 @@ from .const import (
49
53
  NODE_DO_NOT_DISTURB,
50
54
  NODE_IDENTIFIER,
51
55
  NODE_PREFERENCES,
56
+ REFRESH_ACCESS_TOKEN,
57
+ REFRESH_AUTH_COOKIES,
52
58
  SAVE_PATH,
53
59
  SENSORS,
54
60
  URI_IDS,
@@ -61,7 +67,6 @@ from .exceptions import (
61
67
  CannotConnect,
62
68
  CannotRegisterDevice,
63
69
  CannotRetrieveData,
64
- WrongCountry,
65
70
  WrongMethod,
66
71
  )
67
72
  from .utils import obfuscate_email, scrub_fields
@@ -122,32 +127,22 @@ class AmazonEchoApi:
122
127
  def __init__(
123
128
  self,
124
129
  client_session: ClientSession,
125
- login_country_code: str,
126
130
  login_email: str,
127
131
  login_password: str,
128
132
  login_data: dict[str, Any] | None = None,
129
133
  ) -> None:
130
134
  """Initialize the scanner."""
131
- # Force country digits as lower case
132
- country_code = login_country_code.lower()
133
-
134
- locale: dict = DOMAIN_BY_ISO3166_COUNTRY.get(country_code, {})
135
- domain: str = locale.get("domain", country_code)
136
- market: list[str] = locale.get("market", [f"https://www.amazon.{domain}"])
137
- assoc_handle: str = locale.get(
138
- "openid.assoc_handle", f"{DEFAULT_ASSOC_HANDLE}_{country_code}"
139
- )
135
+ # Check if there is a previous login, otherwise use default (US)
136
+ site = login_data.get("site", DEFAULT_SITE) if login_data else DEFAULT_SITE
137
+ _LOGGER.debug("Using site: %s", site)
138
+ self._country_specific_data(site)
140
139
 
141
- self._assoc_handle = assoc_handle
142
140
  self._login_email = login_email
143
141
  self._login_password = login_password
144
- self._login_country_code = country_code
145
- self._domain = domain
146
- self._market = market
142
+
147
143
  self._cookies = self._build_init_cookies()
148
- self._csrf_cookie: str | None = None
149
144
  self._save_raw_data = False
150
- self._login_stored_data = login_data
145
+ self._login_stored_data = login_data or {}
151
146
  self._serial = self._serial_number()
152
147
  self._list_for_clusters: dict[str, str] = {}
153
148
 
@@ -155,23 +150,40 @@ class AmazonEchoApi:
155
150
  self._devices: dict[str, Any] = {}
156
151
  self._sensors_available: bool = True
157
152
 
158
- lang_object = Language.make(territory=self._login_country_code.upper())
153
+ _LOGGER.debug("Initialize library v%s", __version__)
154
+
155
+ @property
156
+ def domain(self) -> str:
157
+ """Return current Amazon domain."""
158
+ return self._domain
159
+
160
+ def save_raw_data(self) -> None:
161
+ """Save raw data to disk."""
162
+ self._save_raw_data = True
163
+ _LOGGER.debug("Saving raw data to disk")
164
+
165
+ def _country_specific_data(self, domain: str) -> None:
166
+ """Set country specific data."""
167
+ # Force lower case
168
+ domain = domain.replace("https://www.amazon.", "").lower()
169
+ country_code = domain.split(".")[-1] if domain != "com" else "us"
170
+
171
+ lang_object = Language.make(territory=country_code.upper())
159
172
  lang_maximized = lang_object.maximize()
173
+
174
+ self._domain: str = domain
160
175
  self._language = f"{lang_maximized.language}-{lang_maximized.region}"
161
176
 
177
+ # Reset CSRF cookie when changing country
178
+ self._csrf_cookie: str | None = None
179
+
162
180
  _LOGGER.debug(
163
- "Initialize library v%s: domain <amazon.%s>, language <%s>, market: <%s>",
164
- __version__,
181
+ "Initialize country <%s>: domain <amazon.%s>, language <%s>",
182
+ country_code.upper(),
165
183
  self._domain,
166
184
  self._language,
167
- self._market,
168
185
  )
169
186
 
170
- def save_raw_data(self) -> None:
171
- """Save raw data to disk."""
172
- self._save_raw_data = True
173
- _LOGGER.debug("Saving raw data to disk")
174
-
175
187
  def _load_website_cookies(self) -> dict[str, str]:
176
188
  """Get website cookies, if avaliables."""
177
189
  if not self._login_stored_data:
@@ -246,11 +258,11 @@ class AmazonEchoApi:
246
258
  code_challenge = self._create_s256_code_challenge(code_verifier)
247
259
 
248
260
  oauth_params = {
249
- "openid.return_to": f"https://www.amazon.{self._domain}/ap/maplanding",
261
+ "openid.return_to": "https://www.amazon.com/ap/maplanding",
250
262
  "openid.oa2.code_challenge_method": "S256",
251
- "openid.assoc_handle": self._assoc_handle,
263
+ "openid.assoc_handle": DEFAULT_ASSOC_HANDLE,
252
264
  "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
253
- "pageId": self._assoc_handle,
265
+ "pageId": DEFAULT_ASSOC_HANDLE,
254
266
  "accountStatusPolicy": "P1",
255
267
  "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select",
256
268
  "openid.mode": "checkid_setup",
@@ -265,9 +277,7 @@ class AmazonEchoApi:
265
277
  "openid.oa2.response_type": "code",
266
278
  }
267
279
 
268
- return (
269
- f"https://www.amazon.{self._domain}{URI_SIGNIN}?{urlencode(oauth_params)}"
270
- )
280
+ return f"https://www.amazon.com{URI_SIGNIN}?{urlencode(oauth_params)}"
271
281
 
272
282
  def _get_inputs_from_soup(self, soup: BeautifulSoup) -> dict[str, str]:
273
283
  """Extract hidden form input fields from a Amazon login page."""
@@ -352,7 +362,7 @@ class AmazonEchoApi:
352
362
  json_data,
353
363
  )
354
364
 
355
- headers = DEFAULT_HEADERS
365
+ headers = DEFAULT_HEADERS.copy()
356
366
  headers.update({"Accept-Language": self._language})
357
367
  if not amazon_user_agent:
358
368
  _LOGGER.debug("Changing User-Agent to %s", DEFAULT_AGENT)
@@ -370,7 +380,15 @@ class AmazonEchoApi:
370
380
  _cookies = (
371
381
  self._load_website_cookies() if self._login_stored_data else self._cookies
372
382
  )
373
- self._session.cookie_jar.update_cookies(_cookies)
383
+ self._session.cookie_jar.update_cookies(_cookies, URL(f"amazon.{self._domain}"))
384
+
385
+ if url.endswith("/auth/token"):
386
+ headers.update(
387
+ {
388
+ "Content-Type": "application/x-www-form-urlencoded",
389
+ "x-amzn-identity-auth-domain": "api.amazon.com",
390
+ }
391
+ )
374
392
 
375
393
  resp: ClientResponse | None = None
376
394
  for delay in [0, 1, 2, 5, 8, 12, 21]:
@@ -405,9 +423,11 @@ class AmazonEchoApi:
405
423
  _LOGGER.error("No response received from %s", url)
406
424
  raise CannotConnect(f"No response received from {url}")
407
425
 
408
- if not self._csrf_cookie:
409
- self._csrf_cookie = resp.cookies.get(CSRF_COOKIE, Morsel()).value
410
- _LOGGER.debug("CSRF cookie value: <%s>", self._csrf_cookie)
426
+ if not self._csrf_cookie and (
427
+ csrf := resp.cookies.get(CSRF_COOKIE, Morsel()).value
428
+ ):
429
+ self._csrf_cookie = csrf
430
+ _LOGGER.debug("CSRF cookie value: <%s> [%s]", self._csrf_cookie, url)
411
431
 
412
432
  content_type: str = resp.headers.get("Content-Type", "")
413
433
  _LOGGER.debug(
@@ -508,6 +528,7 @@ class AmazonEchoApi:
508
528
  "software_version": AMAZON_DEVICE_SOFTWARE_VERSION,
509
529
  },
510
530
  "auth_data": {
531
+ "use_global_authentication": "true",
511
532
  "client_id": self._build_client_id(),
512
533
  "authorization_code": authorization_code,
513
534
  "code_verifier": code_verifier.decode(),
@@ -523,7 +544,7 @@ class AmazonEchoApi:
523
544
  ],
524
545
  }
525
546
 
526
- register_url = f"https://api.amazon.{self._domain}/auth/register"
547
+ register_url = "https://api.amazon.com/auth/register"
527
548
  _, resp = await self._session_request(
528
549
  method=HTTPMethod.POST,
529
550
  url=register_url,
@@ -573,31 +594,9 @@ class AmazonEchoApi:
573
594
  "device_info": device_info,
574
595
  "customer_info": customer_info,
575
596
  }
576
- await self._save_to_file(login_data, "login_data", JSON_EXTENSION)
597
+ _LOGGER.info("Register device: %s", scrub_fields(login_data))
577
598
  return login_data
578
599
 
579
- async def _check_country(self) -> None:
580
- """Check if user selected country matches Amazon account country."""
581
- url = f"https://alexa.amazon.{self._domain}/api/users/me"
582
- _, resp_me = await self._session_request(HTTPMethod.GET, url)
583
-
584
- if resp_me.status != HTTPStatus.OK:
585
- raise CannotAuthenticate
586
-
587
- resp_me_json = await resp_me.json()
588
- amazon_market = resp_me_json["marketPlaceDomainName"]
589
-
590
- if amazon_market not in self._market:
591
- _LOGGER.warning(
592
- "Selected country <%s> doesn't match Amazon API reply:\n%s\n vs \n%s",
593
- self._login_country_code.upper(),
594
- {"input ": self._market},
595
- {"amazon": amazon_market},
596
- )
597
- raise WrongCountry
598
-
599
- _LOGGER.debug("User selected country matches Amazon API one")
600
-
601
600
  async def _get_devices_ids(self) -> list[dict[str, str]]:
602
601
  """Retrieve devices entityId and applianceId."""
603
602
  _, raw_resp = await self._session_request(
@@ -728,6 +727,22 @@ class AmazonEchoApi:
728
727
  bool(otp_code),
729
728
  )
730
729
 
730
+ device_login_data = await self._login_mode_interactive_oauth(otp_code)
731
+
732
+ login_data = await self._register_device(device_login_data)
733
+ self._login_stored_data = login_data
734
+
735
+ await self._domain_refresh_auth_cookies()
736
+
737
+ self._login_stored_data.update({"site": f"https://www.amazon.{self._domain}"})
738
+ await self._save_to_file(self._login_stored_data, "login_data", JSON_EXTENSION)
739
+
740
+ return self._login_stored_data
741
+
742
+ async def _login_mode_interactive_oauth(
743
+ self, otp_code: str
744
+ ) -> dict[str, str | bytes]:
745
+ """Login interactive via oauth URL."""
731
746
  code_verifier = self._create_code_verifier()
732
747
  client_id = self._build_client_id()
733
748
 
@@ -772,21 +787,12 @@ class AmazonEchoApi:
772
787
  authcode = self._extract_code_from_url(login_resp.url)
773
788
  _LOGGER.debug("Login extracted authcode: %s", authcode)
774
789
 
775
- device_login_data = {
790
+ return {
776
791
  "authorization_code": authcode,
777
792
  "code_verifier": code_verifier,
778
793
  "domain": self._domain,
779
794
  }
780
795
 
781
- register_device = await self._register_device(device_login_data)
782
- self._login_stored_data = register_device
783
-
784
- _LOGGER.info("Register device: %s", scrub_fields(register_device))
785
-
786
- await self._check_country()
787
-
788
- return register_device
789
-
790
796
  async def login_mode_stored_data(self) -> dict[str, Any]:
791
797
  """Login to Amazon using previously stored data."""
792
798
  if not self._login_stored_data:
@@ -801,10 +807,51 @@ class AmazonEchoApi:
801
807
  obfuscate_email(self._login_email),
802
808
  )
803
809
 
804
- await self._check_country()
805
-
806
810
  return self._login_stored_data
807
811
 
812
+ async def _get_alexa_domain(self) -> str:
813
+ """Get the Alexa domain."""
814
+ _LOGGER.debug("Retrieve Alexa domain")
815
+ _, raw_resp = await self._session_request(
816
+ method=HTTPMethod.GET,
817
+ url=f"https://alexa.amazon.{self._domain}/api/welcome",
818
+ )
819
+ json_data = await raw_resp.json()
820
+ return cast(
821
+ "str", json_data.get("alexaHostName", f"alexa.amazon.{self._domain}")
822
+ )
823
+
824
+ async def _refresh_auth_cookies(self) -> None:
825
+ """Refresh cookies after domain swap."""
826
+ _, json_token_resp = await self._refresh_data(REFRESH_AUTH_COOKIES)
827
+
828
+ # Need to take cookies from response and create them as cookies
829
+ website_cookies = self._login_stored_data["website_cookies"] = {}
830
+ self._session.cookie_jar.clear()
831
+
832
+ cookie_json = json_token_resp["response"]["tokens"]["cookies"]
833
+ for cookie_domain in cookie_json:
834
+ for cookie in cookie_json[cookie_domain]:
835
+ new_cookie_value = cookie["Value"].replace(r'"', r"")
836
+ new_cookie = {cookie["Name"]: new_cookie_value}
837
+ self._session.cookie_jar.update_cookies(new_cookie, URL(cookie_domain))
838
+ website_cookies.update(new_cookie)
839
+ if cookie["Name"] == "session-token":
840
+ self._login_stored_data["store_authentication_cookie"] = {
841
+ "cookie": new_cookie_value
842
+ }
843
+
844
+ async def _domain_refresh_auth_cookies(self) -> None:
845
+ """Refresh cookies after domain swap."""
846
+ _LOGGER.debug("Refreshing auth cookies after domain change")
847
+
848
+ # Get the new Alexa domain
849
+ user_domain = (await self._get_alexa_domain()).replace("alexa", "https://www")
850
+ if user_domain != DEFAULT_SITE:
851
+ _LOGGER.debug("User domain changed to %s", user_domain)
852
+ self._country_specific_data(user_domain)
853
+ await self._refresh_auth_cookies()
854
+
808
855
  async def get_devices_data(
809
856
  self,
810
857
  ) -> dict[str, AmazonDevice]:
@@ -1122,3 +1169,58 @@ class AmazonEchoApi:
1122
1169
  await self._session_request(
1123
1170
  method="PUT", url=url, input_data=payload, json_data=True
1124
1171
  )
1172
+
1173
+ async def _refresh_data(self, data_type: str) -> tuple[bool, dict]:
1174
+ """Refresh data."""
1175
+ if not self._login_stored_data:
1176
+ _LOGGER.debug("No login data available, cannot refresh")
1177
+ return False, {}
1178
+
1179
+ data = {
1180
+ "app_name": AMAZON_APP_NAME,
1181
+ "app_version": AMAZON_APP_VERSION,
1182
+ "di.sdk.version": "6.12.4",
1183
+ "source_token": self._login_stored_data["refresh_token"],
1184
+ "package_name": AMAZON_APP_BUNDLE_ID,
1185
+ "di.hw.version": "iPhone",
1186
+ "platform": "iOS",
1187
+ "requested_token_type": data_type,
1188
+ "source_token_type": "refresh_token",
1189
+ "di.os.name": "iOS",
1190
+ "di.os.version": AMAZON_CLIENT_OS,
1191
+ "current_version": "6.12.4",
1192
+ "previous_version": "6.12.4",
1193
+ "domain": f"www.amazon.{self._domain}",
1194
+ }
1195
+
1196
+ response = await self._session.post(
1197
+ "https://api.amazon.com/auth/token",
1198
+ data=data,
1199
+ )
1200
+ _LOGGER.debug(
1201
+ "Refresh data response %s with payload %s",
1202
+ response.status,
1203
+ orjson.dumps(data),
1204
+ )
1205
+
1206
+ if response.status != HTTPStatus.OK:
1207
+ _LOGGER.debug("Failed to refresh data")
1208
+ return False, {}
1209
+
1210
+ json_response = await response.json()
1211
+ _LOGGER.debug("Refresh data json:\n%s ", json_response)
1212
+
1213
+ if data_type == REFRESH_ACCESS_TOKEN and (
1214
+ new_token := json_response.get(REFRESH_ACCESS_TOKEN)
1215
+ ):
1216
+ self._login_stored_data[REFRESH_ACCESS_TOKEN] = new_token
1217
+ self.expires_in = datetime.now(tz=UTC).timestamp() + int(
1218
+ json_response.get("expires_in")
1219
+ )
1220
+ return True, json_response
1221
+
1222
+ if data_type == REFRESH_AUTH_COOKIES:
1223
+ return True, json_response
1224
+
1225
+ _LOGGER.debug("Unexpected refresh data response")
1226
+ return False, {}
@@ -1,7 +1,6 @@
1
1
  """Constants for Amazon devices."""
2
2
 
3
3
  import logging
4
- from typing import Any
5
4
 
6
5
  _LOGGER = logging.getLogger(__package__)
7
6
 
@@ -31,56 +30,6 @@ TO_REDACT = {
31
30
  "user_id",
32
31
  }
33
32
 
34
- AMAZON_DE_OVERRIDE: dict[str, str] = {
35
- "domain": "de",
36
- "openid.assoc_handle": f"{DEFAULT_ASSOC_HANDLE}_de",
37
- }
38
- AMAZON_US_OVERRIDE: dict[str, str] = {
39
- "domain": "com",
40
- "openid.assoc_handle": DEFAULT_ASSOC_HANDLE,
41
- }
42
-
43
- DOMAIN_BY_ISO3166_COUNTRY: dict[str, dict[str, Any]] = {
44
- "ar": AMAZON_US_OVERRIDE,
45
- "at": AMAZON_DE_OVERRIDE,
46
- "au": {
47
- "domain": "com.au",
48
- "openid.assoc_handle": f"{DEFAULT_ASSOC_HANDLE}_au",
49
- },
50
- "be": {
51
- "domain": "com.be",
52
- },
53
- "br": AMAZON_US_OVERRIDE | {"market": "https://www.amazon.com.br"},
54
- "gb": {
55
- "domain": "co.uk",
56
- "openid.assoc_handle": f"{DEFAULT_ASSOC_HANDLE}_uk",
57
- },
58
- "il": AMAZON_US_OVERRIDE,
59
- "jp": {
60
- "domain": "co.jp",
61
- },
62
- "mx": {
63
- "domain": "com.mx",
64
- },
65
- "nl": {
66
- "domain": "nl",
67
- "market": "https://www.amazon.co.uk",
68
- },
69
- "no": AMAZON_DE_OVERRIDE,
70
- "nz": {
71
- "domain": "com.au",
72
- "openid.assoc_handle": f"{DEFAULT_ASSOC_HANDLE}_au",
73
- },
74
- "pl": AMAZON_US_OVERRIDE,
75
- "tr": {
76
- "domain": "com.tr",
77
- },
78
- "us": AMAZON_US_OVERRIDE,
79
- "za": {
80
- "domain": "co.za",
81
- },
82
- }
83
-
84
33
  # Amazon APP info
85
34
  AMAZON_APP_BUNDLE_ID = "com.amazon.echo"
86
35
  AMAZON_APP_ID = "MAPiOSLib/6.0/ToHideRetailLink"
@@ -90,6 +39,7 @@ AMAZON_DEVICE_SOFTWARE_VERSION = "35602678"
90
39
  AMAZON_DEVICE_TYPE = "A2IVLV5VM2W81"
91
40
  AMAZON_CLIENT_OS = "18.5"
92
41
 
42
+ DEFAULT_SITE = "https://www.amazon.com"
93
43
  DEFAULT_HEADERS = {
94
44
  "User-Agent": (
95
45
  f"AmazonWebView/AmazonAlexa/{AMAZON_APP_VERSION}/iOS/{AMAZON_CLIENT_OS}/iPhone"
@@ -101,6 +51,9 @@ DEFAULT_HEADERS = {
101
51
  DEFAULT_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0" # noqa: E501
102
52
  CSRF_COOKIE = "csrf"
103
53
 
54
+ REFRESH_ACCESS_TOKEN = "access_token" # noqa: S105
55
+ REFRESH_AUTH_COOKIES = "auth_cookies"
56
+
104
57
  NODE_DEVICES = "devices"
105
58
  NODE_DO_NOT_DISTURB = "doNotDisturbDeviceStatusList"
106
59
  NODE_PREFERENCES = "devicePreferences"
@@ -160,6 +113,10 @@ DEVICE_TO_IGNORE: list[str] = [
160
113
  "A1L4KDRIILU6N9", # Sony headset WH-CH700N - issue #345
161
114
  "A2IJJ9QXVOSYK0", # JBL TUNE770NC - issue #391
162
115
  "AKOAGQTKAS9YB", # Amazon Echo Connect - issue #406
116
+ "A3PAHYZLPKL73D", # EERO 6 Wifi AP - issue #426
117
+ "A3KOTUS4DKHU1W", # Samsung Fridge - issue #429
118
+ "AN630UQPG2CA4", # Insignia TV - issue #430
119
+ "A3SSG6GR8UU7SN", # Amazon Echo Sub - issue #437
163
120
  ]
164
121
 
165
122
  DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
@@ -244,13 +201,17 @@ DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
244
201
  "model": "Echo Dot Clock",
245
202
  "hw_version": "Gen5",
246
203
  },
204
+ "A2E0SNTXJVT7WK ": {
205
+ "model": "Fire TV Stick",
206
+ "hw_version": "Gen2",
207
+ },
247
208
  "A2F7IJUT32OLN4": {
248
209
  "manufacturer": "Samsung Electronics Co., Ltd.",
249
210
  "model": "Soundbar Q990D",
250
211
  "hw_version": None,
251
212
  },
252
213
  "A2GFL5ZMWNE0PX": {
253
- "model": "Fire TV",
214
+ "model": "Fire TV Stick",
254
215
  "hw_version": "Gen3",
255
216
  },
256
217
  "A2H4LV5GIZ1JFT": {
@@ -458,6 +419,10 @@ DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
458
419
  "model": "Fire Tablet HD 8 Plus",
459
420
  "hw_version": "Gen10",
460
421
  },
422
+ "AWZZ5CVHX2CD": {
423
+ "model": "Echo Show",
424
+ "hw_version": "Gen2",
425
+ },
461
426
  "G2A0V704840708AP": {
462
427
  "model": "Echo Plus",
463
428
  "hw_version": "Gen2",
@@ -25,7 +25,3 @@ class CannotRegisterDevice(AmazonError):
25
25
 
26
26
  class WrongMethod(AmazonError):
27
27
  """Exception raised when the wrong login metho is used."""
28
-
29
-
30
- class WrongCountry(AmazonError):
31
- """Exceptio nraised when Amazon country."""