diff --git a/custom_components/alexa_media/.translations/de.json b/custom_components/alexa_media/.translations/de.json index f962a5de..625c4e08 100644 --- a/custom_components/alexa_media/.translations/de.json +++ b/custom_components/alexa_media/.translations/de.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Erweitertes debugging", "email": "Email Adresse", "exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)", @@ -60,8 +61,7 @@ "description": "Bitte geben Sie ihre Informationen ein.", "title": "Alexa Media Player - Konfiguration" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/en.json b/custom_components/alexa_media/.translations/en.json index aefa1b70..e6c873bd 100644 --- a/custom_components/alexa_media/.translations/en.json +++ b/custom_components/alexa_media/.translations/en.json @@ -29,7 +29,7 @@ "password": "Password", "securitycode": "2FA Code (recommended to avoid login issues)" }, - "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}", + "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", "title": "Alexa Media Player - Captcha" }, "claimspicker": { @@ -43,11 +43,12 @@ "data": { "securitycode": "2FA Code" }, - "description": "**{email} - alexa.{url}** \nEnter the One Time Password (OTP). \n>{message}", + "description": "**{email} - alexa.{url}** \nEnter the One Time Password (OTP). \n{message}", "title": "Alexa Media Player - Two Factor Authentication" }, "user": { "data": { + "cookies_txt": "Cookies.txt data", "debug": "Advanced debugging", "email": "Email Address", "exclude_devices": "Excluded device (comma separated)", @@ -57,11 +58,10 @@ "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, - "description": "Please enter your information. \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when 2FA Code is required.** \n>{message}", + "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", "title": "Alexa Media Player - Configuration" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/es.json b/custom_components/alexa_media/.translations/es.json index e0325f6f..3ba4ca32 100644 --- a/custom_components/alexa_media/.translations/es.json +++ b/custom_components/alexa_media/.translations/es.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Depuración avanzada", "email": "Dirección de correo electrónico", "exclude_devices": "Dispositivo excluido (separado por comas)", @@ -60,8 +61,7 @@ "description": "Por favor, introduzca su información.", "title": "Alexa Media Player - Configuracion" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/fr.json b/custom_components/alexa_media/.translations/fr.json index 4b0ed929..a8db6bc7 100644 --- a/custom_components/alexa_media/.translations/fr.json +++ b/custom_components/alexa_media/.translations/fr.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Débogage avancé", "email": "Adresse Email", "exclude_devices": "Appareil exclu (séparé par des virgules)", @@ -60,8 +61,7 @@ "description": "Veuillez saisir vos informations.", "title": "Alexa Media Player - Configuration" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/it.json b/custom_components/alexa_media/.translations/it.json index 622fd120..25050593 100644 --- a/custom_components/alexa_media/.translations/it.json +++ b/custom_components/alexa_media/.translations/it.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Debug avanzato", "email": "Indirizzo email", "exclude_devices": "Dispositivi da escludere (separati da virgola)", @@ -60,8 +61,7 @@ "description": "Prego inserisci le tue informazioni.", "title": "Alexa Media Player - Configurazione" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/nb.json b/custom_components/alexa_media/.translations/nb.json index 795aef7d..ca9c5ea6 100644 --- a/custom_components/alexa_media/.translations/nb.json +++ b/custom_components/alexa_media/.translations/nb.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Avansert feilsøking", "email": "Epostadresse", "exclude_devices": "Ekskludert enhet (kommaseparert)", @@ -60,8 +61,7 @@ "description": "Vennligst skriv inn informasjonen din. \n**WARNING: Amazon rapporterer feilaktig 'Angi en gyldig e-postadresse eller et mobilnummer' når 2FA-kode kreves.** \n>{message}", "title": "Alexa Media Player - Konfigurasjon" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/nl.json b/custom_components/alexa_media/.translations/nl.json index 9438d061..43517b9a 100644 --- a/custom_components/alexa_media/.translations/nl.json +++ b/custom_components/alexa_media/.translations/nl.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Geavanceerd debuggen", "email": "E-mailadres", "exclude_devices": "Apparaten uitsluiten (Scheiding: komma)", @@ -60,8 +61,7 @@ "description": "Vul je gegevens in a.u.b.", "title": "Alexa Media Player - Configuratie" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/pl.json b/custom_components/alexa_media/.translations/pl.json index 24c5191b..a3c31811 100644 --- a/custom_components/alexa_media/.translations/pl.json +++ b/custom_components/alexa_media/.translations/pl.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Zaawansowane debugowanie", "email": "Adres email", "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", @@ -60,8 +61,7 @@ "description": "Wprowadź dane", "title": "Alexa Media Player - konfiguracja" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/ru.json b/custom_components/alexa_media/.translations/ru.json index 185bf7cf..dbd0f4aa 100644 --- a/custom_components/alexa_media/.translations/ru.json +++ b/custom_components/alexa_media/.translations/ru.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Расширенные возможности отладки", "email": "Адрес электронной почты", "exclude_devices": "Исключенные устройства (через запятую)", @@ -60,8 +61,7 @@ "description": "Пожалуйста, введите свои данные.", "title": "Alexa Media Player - Конфигурация" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/.translations/zh-Hans.json b/custom_components/alexa_media/.translations/zh-Hans.json index a15a22d7..54a746b2 100644 --- a/custom_components/alexa_media/.translations/zh-Hans.json +++ b/custom_components/alexa_media/.translations/zh-Hans.json @@ -48,6 +48,7 @@ }, "user": { "data": { + "cookies_txt": "config::step::user::data::cookies_txt", "debug": "高级调试", "email": "电子邮件地址", "exclude_devices": "Excluded device (comma separated)", @@ -60,8 +61,7 @@ "description": "请输入您的信息。", "title": "Alexa Media Player-配置" } - }, - "title": "Alexa Media Player" + } }, "options": { "step": { diff --git a/custom_components/alexa_media/__init__.py b/custom_components/alexa_media/__init__.py index 6e303a95..e50f2f84 100644 --- a/custom_components/alexa_media/__init__.py +++ b/custom_components/alexa_media/__init__.py @@ -56,7 +56,7 @@ SCAN_INTERVAL, STARTUP, ) -from .helpers import _existing_serials +from .helpers import _existing_serials, _catch_login_errors from .notify import async_unload_entry as notify_async_unload_entry from .services import AlexaMediaServices @@ -446,6 +446,7 @@ async def async_update_data(): hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False + @_catch_login_errors async def process_notifications(login_obj, raw_notifications=None): """Process raw notifications json.""" if not raw_notifications: @@ -488,6 +489,7 @@ async def process_notifications(login_obj, raw_notifications=None): ), ) + @_catch_login_errors async def update_last_called(login_obj, last_called=None): """Update the last called device for the login_obj. @@ -518,6 +520,7 @@ async def update_last_called(login_obj, last_called=None): ) hass.data[DATA_ALEXAMEDIA]["accounts"][email]["last_called"] = last_called + @_catch_login_errors async def update_bluetooth_state(login_obj, device_serial): """Update the bluetooth state on ws bluetooth event.""" bluetooth = await AlexaAPI.get_bluetooth(login_obj) diff --git a/custom_components/alexa_media/config_flow.py b/custom_components/alexa_media/config_flow.py index 2ed07ff9..496c156b 100644 --- a/custom_components/alexa_media/config_flow.py +++ b/custom_components/alexa_media/config_flow.py @@ -13,6 +13,7 @@ from functools import reduce import logging from typing import Any, Optional, Text +import re from alexapy import AlexaLogin, AlexapyConnectionError, hide_email, obfuscate from homeassistant import config_entries @@ -29,10 +30,12 @@ import voluptuous as vol from .const import ( + CONF_COOKIES_TXT, CONF_DEBUG, CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES, CONF_QUEUE_DELAY, + CONF_SECURITYCODE, DATA_ALEXAMEDIA, DEFAULT_QUEUE_DELAY, DOMAIN, @@ -81,12 +84,13 @@ def __init__(self): [ (vol.Required(CONF_EMAIL), str), (vol.Required(CONF_PASSWORD), str), - (vol.Optional("securitycode"), str), + (vol.Optional(CONF_SECURITYCODE), str), (vol.Required(CONF_URL, default="amazon.com"), str), (vol.Optional(CONF_DEBUG, default=False), bool), (vol.Optional(CONF_INCLUDE_DEVICES, default=""), str), (vol.Optional(CONF_EXCLUDE_DEVICES, default=""), str), (vol.Optional(CONF_SCAN_INTERVAL, default=60), int), + (vol.Optional(CONF_COOKIES_TXT, default=""), str), ] ) self.captcha_schema = OrderedDict( @@ -94,7 +98,7 @@ def __init__(self): (vol.Required(CONF_PASSWORD), str), ( vol.Optional( - "securitycode", + CONF_SECURITYCODE, default=self.securitycode if self.securitycode else "", ), str, @@ -106,7 +110,7 @@ def __init__(self): [ ( vol.Required( - "securitycode", + CONF_SECURITYCODE, default=self.securitycode if self.securitycode else "", ), str, @@ -179,14 +183,14 @@ async def async_step_user(self, user_input=None): self.hass.config.path, self.config[CONF_DEBUG], ) - await self.login.login( - cookies=await self.login.load_cookie(), data=self.config - ) else: _LOGGER.debug("Using existing login") - await self.login.login( - cookies=await self.login.load_cookie(), data=self.config - ) + await self.login.login( + cookies=await self.login.load_cookie( + cookies_txt=self.config[CONF_COOKIES_TXT] + ), + data=self.config, + ) return await self._test_login() except AlexapyConnectionError: self.automatic_steps = 0 @@ -275,6 +279,7 @@ async def _test_login(self): _LOGGER.debug("Testing login status: %s", login.status) if login.status and login.status.get("login_successful"): existing_entry = await self.async_set_unique_id(f"{email} - {login.url}") + self.config.pop(CONF_COOKIES_TXT) if existing_entry: self.hass.config_entries.async_update_entry( existing_entry, data=self.config @@ -309,7 +314,7 @@ async def _test_login(self): CONF_PASSWORD, default=self.config[CONF_PASSWORD] ): str, vol.Optional( - "securitycode", + CONF_SECURITYCODE, default=self.securitycode if self.securitycode else "", ): str, }, @@ -339,11 +344,13 @@ async def _test_login(self): "Automatically submitting securitycode %s", self.securitycode ) self.automatic_steps += 1 - sleep(1) + await sleep(1) return await self.async_step_twofactor( - user_input={"securitycode": self.securitycode} + user_input={CONF_SECURITYCODE: self.securitycode} ) - self.twofactor_schema = OrderedDict([(vol.Required("securitycode",), str,)]) + self.twofactor_schema = OrderedDict( + [(vol.Required(CONF_SECURITYCODE,), str,)] + ) self.automatic_steps = 0 return self.async_show_form( step_id="twofactor", @@ -424,7 +431,7 @@ async def _test_login(self): "Trying automatic resubmission for error_message 'valid email'" ) self.automatic_steps += 1 - sleep(1) + await sleep(1) return await self.async_step_user(user_input=self.config) self.automatic_steps = 0 return self.async_show_form( @@ -452,11 +459,11 @@ def _save_user_input_to_config(self, user_input=None) -> None: """ if user_input is None: return - self.securitycode = user_input.get("securitycode") + self.securitycode = user_input.get(CONF_SECURITYCODE) if self.securitycode is not None: - self.config["securitycode"] = self.securitycode - elif "securitycode" in self.config: - self.config.pop("securitycode") + self.config[CONF_SECURITYCODE] = self.securitycode + elif CONF_SECURITYCODE in self.config: + self.config.pop(CONF_SECURITYCODE) if CONF_EMAIL in user_input: self.config[CONF_EMAIL] = user_input[CONF_EMAIL] if CONF_PASSWORD in user_input: @@ -489,6 +496,21 @@ def _save_user_input_to_config(self, user_input=None) -> None: ) else: self.config[CONF_EXCLUDE_DEVICES] = user_input[CONF_EXCLUDE_DEVICES] + if CONF_COOKIES_TXT in user_input: + fixed_cookies_txt = "# HTTP Cookie File\n" + re.sub( + r" ", + r"\n", + re.sub( + r"#.*\n", + r"", + re.sub( + r"# ((?:.(?!# ))+)$", + r"\1", + re.sub(r" #", r"\n#", user_input[CONF_COOKIES_TXT]), + ), + ), + ) + self.config[CONF_COOKIES_TXT] = fixed_cookies_txt def _update_schema_defaults(self) -> Any: new_schema = self._update_ord_dict( @@ -499,7 +521,7 @@ def _update_schema_defaults(self) -> Any: CONF_PASSWORD, default=self.config.get(CONF_PASSWORD, "") ): str, vol.Optional( - "securitycode", + CONF_SECURITYCODE, default=self.securitycode if self.securitycode else "", ): str, vol.Required( diff --git a/custom_components/alexa_media/const.py b/custom_components/alexa_media/const.py index 6aa097b5..040fbde7 100644 --- a/custom_components/alexa_media/const.py +++ b/custom_components/alexa_media/const.py @@ -27,11 +27,12 @@ DEPENDENT_ALEXA_COMPONENTS = ["notify", "switch", "sensor", "alarm_control_panel"] CONF_ACCOUNTS = "accounts" +CONF_COOKIES_TXT = "cookies_txt" CONF_DEBUG = "debug" CONF_INCLUDE_DEVICES = "include_devices" CONF_EXCLUDE_DEVICES = "exclude_devices" CONF_QUEUE_DELAY = "queue_delay" - +CONF_SECURITYCODE = "securitycode" DATA_LISTENER = "listener" EXCEPTION_TEMPLATE = "An exception of type {0} occurred. Arguments:\n{1!r}" diff --git a/custom_components/alexa_media/manifest.json b/custom_components/alexa_media/manifest.json index 778f834f..d6e0f5bc 100644 --- a/custom_components/alexa_media/manifest.json +++ b/custom_components/alexa_media/manifest.json @@ -6,5 +6,5 @@ "issue_tracker": "https://github.com/custom-components/alexa_media_player/issues", "dependencies": ["persistent_notification"], "codeowners": ["@keatontaylor", "@alandtse"], - "requirements": ["alexapy==1.14.1", "packaging~=20.3", "wrapt~=1.12.1"] + "requirements": ["alexapy==1.15.1", "packaging~=20.3", "wrapt~=1.12.1"] } diff --git a/custom_components/alexa_media/strings.json b/custom_components/alexa_media/strings.json index 804065f3..bb6dc0d1 100644 --- a/custom_components/alexa_media/strings.json +++ b/custom_components/alexa_media/strings.json @@ -16,9 +16,10 @@ "include_devices": "Included device (comma separated)", "exclude_devices": "Excluded device (comma separated)", "debug": "Advanced debugging", - "scan_interval": "Seconds between scans" + "scan_interval": "Seconds between scans", + "cookies_txt": "Cookies.txt data" }, - "description": "Please enter your information. \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when 2FA Code is required.** \n>{message}", + "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", "title": "Alexa Media Player - Configuration" }, "captcha": { diff --git a/custom_components/alexa_media/switch.py b/custom_components/alexa_media/switch.py index ad8c768a..4f477ee1 100644 --- a/custom_components/alexa_media/switch.py +++ b/custom_components/alexa_media/switch.py @@ -188,13 +188,11 @@ async def _set_switch(self, state, **kwargs): except AttributeError: pass success = await self._switch_function(state) - # if function returns success, make immediate state change + # if function returns success, make immediate state change if success: setattr(self._client, self._switch_property, state) _LOGGER.debug( - "Switch set to %s based on %s", - getattr(self._client, self._switch_property), - state, + "%s set to %s", self.name, getattr(self._client, self._switch_property), ) self.async_schedule_update_ha_state() elif self.should_poll: