diff --git a/custom_components/alexa_media/.translations/ar.json b/custom_components/alexa_media/.translations/ar.json
new file mode 100644
index 00000000..216f8f32
--- /dev/null
+++ b/custom_components/alexa_media/.translations/ar.json
@@ -0,0 +1,57 @@
+{
+ "config": {
+ "abort": {
+ "forgot_password": "The Forgot Password page was detected. This normally is the result of too may failed logins. Amazon may require action before a relogin can be attempted.",
+ "login_failed": "Alexa Media Player failed to login.",
+ "reauth_successful": "Alexa Media Player successfully reauthenticated."
+ },
+ "error": {
+ "2fa_key_invalid": "Invalid Built-In 2FA key",
+ "connection_error": "Error connecting; check network and retry",
+ "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the External Url under Configuration -> General",
+ "identifier_exists": "Email for Alexa URL already registered",
+ "invalid_credentials": "Invalid credentials",
+ "unknown_error": "Unknown error, please enable advanced debugging and report log info"
+ },
+ "step": {
+ "captcha": {
+ "data": {
+ "securitycode": "2FA Code (recommended to avoid login issues)"
+ }
+ },
+ "totp_register": {
+ "data": {
+ "registered": "OTP from the Built-in 2FA App Key confirmed successfully."
+ },
+ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
+ "title": "Alexa Media Player - OTP Confirmation"
+ },
+ "user": {
+ "data": {
+ "debug": "Advanced debugging",
+ "email": "Email Address",
+ "exclude_devices": "Excluded device (comma separated)",
+ "hass_url": "Url to access Home Assistant",
+ "include_devices": "Included device (comma separated)",
+ "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
+ "password": "Password",
+ "scan_interval": "Seconds between scans",
+ "securitycode": "2FA Code (recommended to avoid login issues)",
+ "url": "Amazon region domain (e.g., amazon.co.uk)"
+ },
+ "description": "Please confirm the information below.",
+ "title": "Alexa Media Player - Configuration"
+ }
+ }
+ },
+ "options": {
+ "step": {
+ "init": {
+ "data": {
+ "extended_entity_discovery": "Include devices connected via Echo",
+ "queue_delay": "Seconds to wait to queue commands together"
+ }
+ }
+ }
+ }
+}
diff --git a/custom_components/alexa_media/.translations/de.json b/custom_components/alexa_media/.translations/de.json
index 15a60736..39e2fba1 100644
--- a/custom_components/alexa_media/.translations/de.json
+++ b/custom_components/alexa_media/.translations/de.json
@@ -14,38 +14,10 @@
"unknown_error": "Unbekannter Fehler, bitte Log-Info melden"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon sendet eine Push-Benachrichtigung je folgender Nachricht. Bitte antworte sie vollständig, bevor Du fortfährst. \n{message}",
- "title": "Alexa Media Player - Aktion erforderlich"
- },
- "authselect": {
- "data": {
- "authselectoption": "Einmal Pin Methode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Einmal Pin Passwort"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Passwort",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Verifizierungs Methode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nBitte Verifizierungs Methode auswählen. (z.B., `0` oder `1`) \n{message}",
- "title": "Alexa Media Player - Verifizierungs Methode"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA Code"
- },
- "description": "**{email} - alexa.{url}** \nDen Zwei Faktor Pin eingeben. \n{message}",
- "title": "Alexa Media Player - Zwei Faktor Authentifizierung"
- },
"user": {
"data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
"debug": "Erweitertes debugging",
"email": "Email Adresse",
"exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Eingebundene Geräte (komma getrennnt)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Passwort",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Sekunden zwischen den Scans",
"securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)",
"url": "Amazon Region (z.B., amazon.de)"
},
"description": "Bitte geben Sie ihre Informationen ein.",
"title": "Alexa Media Player - Konfiguration"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "Erweitertes debugging",
- "email": "Email Adresse",
- "exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)",
- "include_devices": "Eingebundene Geräte (komma getrennnt)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Passwort",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Sekunden zwischen den Scans",
- "securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)",
- "url": "Amazon Region (z.B., amazon.de)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/.translations/en.json b/custom_components/alexa_media/.translations/en.json
index f52044a0..216f8f32 100644
--- a/custom_components/alexa_media/.translations/en.json
+++ b/custom_components/alexa_media/.translations/en.json
@@ -14,38 +14,10 @@
"unknown_error": "Unknown error, please enable advanced debugging and report log info"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}",
- "title": "Alexa Media Player - Action Required"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP method",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - One Time Password"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Password",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA Code (recommended to avoid login issues)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Verification method",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}",
- "title": "Alexa Media Player - Verification Method"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA Code"
- },
- "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)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Included device (comma separated)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Password",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Seconds between scans",
"securitycode": "2FA Code (recommended to avoid login issues)",
"url": "Amazon region domain (e.g., amazon.co.uk)"
},
- "description": "Please confirm the information below. For legacy configuration, disable `Use Login Proxy method` option.",
+ "description": "Please confirm the information below.",
"title": "Alexa Media Player - Configuration"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Cookies.txt data",
- "debug": "Advanced debugging",
- "email": "Email Address",
- "exclude_devices": "Excluded device (comma separated)",
- "include_devices": "Included device (comma separated)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Password",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Seconds between scans",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "url": "Amazon region domain (e.g., amazon.co.uk)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/.translations/es.json b/custom_components/alexa_media/.translations/es.json
index d1ddc7a7..644bc547 100644
--- a/custom_components/alexa_media/.translations/es.json
+++ b/custom_components/alexa_media/.translations/es.json
@@ -11,41 +11,13 @@
"hass_url_invalid": "No se puede conectar a la url de Home Assistant. Compruebe la dirección URL externa en Configuración -> General",
"identifier_exists": "Correo electrónico para la URL de Alexa ya registrado",
"invalid_credentials": "Credenciales no válidas",
- "unknown_error": "Error desconocido, por favor revisa los registros en Home Assistant y reporta el error si es necesario."
+ "unknown_error": "Error desconocido, habilite la depuración avanzada e informe la información de registro"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon enviará una notificación a tu dispositivo vinculado. Completa los pasos descritos antes de continuar.\n{message}",
- "title": "Alexa Media Player - Acción requerida"
- },
- "authselect": {
- "data": {
- "authselectoption": "Clave OTP",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Contraseña de un solo uso"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Contraseña",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Método de verificación",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nSeleccione el método de verificación por número. (e.g., `0` or `1`) \n{message}",
- "title": "Alexa Media Player - Método de verificación"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA Code"
- },
- "description": "**{email} - alexa.{url}** \nIngrese la contraseña de un solo uso (OTP). \n{message}",
- "title": "Alexa Media Player - Autenticación de dos factores"
- },
"user": {
"data": {
- "cookies_txt": "Datos de Cookies.txt",
"debug": "Depuración avanzada",
"email": "Dirección de correo electrónico",
"exclude_devices": "Dispositivo excluido (separado por comas)",
- "hass_url": "Url to access Home Assistant",
+ "hass_url": "Url para acceder a Home Assistant",
"include_devices": "Dispositivo incluido (separado por comas)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Contraseña",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Segundos entre escaneos",
"securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)",
"url": "Región del dominio de Amazon (por ejemplo, amazon.es)"
},
- "description": "Confirme la siguiente información. Para la configuración heredada, desactive la opción `Usar método de proxy de inicio de sesión`.",
+ "description": "Confirme la siguiente información.",
"title": "Alexa Media Player - Configuración"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Datos de Cookies.txt",
- "debug": "Depuración avanzada",
- "email": "Dirección de correo electrónico",
- "exclude_devices": "Dispositivo excluido (separado por comas)",
- "include_devices": "Dispositivo incluido (separado por comas)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Contraseña",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Segundos entre escaneos",
- "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)",
- "url": "Región del dominio de Amazon (por ejemplo, amazon.es)"
- },
- "description": "Por favor introduce tu [información](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **El método más rápido es [Importar cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import).** \n**ADVERTENCIA: Amazon informará 'Introduce un correo electrónico o número de teléfono válido' si tu cuenta utiliza [códigos 2FA - Segundo Factor de Autenticación](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}",
- "title": "Alexa Media Player - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/.translations/fr.json b/custom_components/alexa_media/.translations/fr.json
index 6d73c4e9..f466b335 100644
--- a/custom_components/alexa_media/.translations/fr.json
+++ b/custom_components/alexa_media/.translations/fr.json
@@ -14,38 +14,10 @@
"unknown_error": "Erreur inconnue, veuillez signaler les informations du journal"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)"
- },
- "description": "** {email} - alexa. {url} ** \n Amazon enverra une notification push conformément au message ci-dessous. Veuillez répondre complètement avant de continuer. \n {message}",
- "title": "Alexa Media Player - Action requise"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP méthode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Mot de passe unique"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Mot de passe",
- "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)",
"securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Méthode de vérification",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nVeuillez sélectionner la méthode de vérification par numéro. (exemple., `0` ou `1`) \n{message}",
- "title": "Alexa Media Player - Méthode de vérification"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "** {email} - alexa. {url} **\n Avez-vous confirmé avec succès un OTP à partir de la clé d'application 2FA intégrée avec Amazon?\n > Code OTP {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)",
- "securitycode": "2FA Code"
- },
- "description": "**{email} - alexa.{url}** \nEntrez le mot de passe unique (OTP). \n{message}",
- "title": "Alexa Media Player - Authentification à deux facteurs"
- },
"user": {
"data": {
- "cookies_txt": "Données cookies.txt",
"debug": "Débogage avancé",
"email": "Adresse Email",
"exclude_devices": "Appareil exclu (séparé par des virgules)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Appareil inclus (séparé par des virgules)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)",
"password": "Mot de passe",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Secondes entre les analyses",
"securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)",
"url": "Domaine de la région Amazon (exemple, amazon.fr)"
},
- "description": "Veuillez confirmer les informations ci-dessous. Pour la configuration héritée, désactivez l'option `Utiliser la méthode proxy de connexion`.",
+ "description": "Veuillez confirmer les informations ci-dessous.",
"title": "Alexa Media Player - Configuration"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Données cookies.txt",
- "debug": "Débogage avancé",
- "email": "Adresse Email",
- "exclude_devices": "Appareil exclu (séparé par des virgules)",
- "include_devices": "Appareil inclus (séparé par des virgules)",
- "oauth_login": "Activer la méthode d'application oauth-token",
- "otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)",
- "password": "Mot de passe",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Secondes entre les analyses",
- "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)",
- "url": "Domaine de la région Amazon (exemple, amazon.fr)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/.translations/it.json b/custom_components/alexa_media/.translations/it.json
index f48012a9..318c7b5a 100644
--- a/custom_components/alexa_media/.translations/it.json
+++ b/custom_components/alexa_media/.translations/it.json
@@ -14,38 +14,10 @@
"unknown_error": "Errore sconosciuto, si prega di abilitare il debug avanzato e riportare i log informativi"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)"
- },
- "description": "**{email} - alexa. {url}**\n Amazon invierà una notifica push per il seguente messaggio. Si prega di rispondere completamente prima di continuare.\n {message}",
- "title": "Alexa Media Player - Azione Richiesta"
- },
- "authselect": {
- "data": {
- "authselectoption": "Metodo password usa e getta (OTP)",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)"
- },
- "description": "**{email} - alexa.{url}**\n{message}",
- "title": "Alexa Media Player - Password Usa e Getta (One Time Password)"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Password",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)",
"securitycode": "Codice 2FA (raccomandato per evitare problemi di login)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Metodo di verifica",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)"
- },
- "description": "**{email} - alexa.{url}**\nSelezionare un metodo di verifica con un numero. (ad es., `0` o `1`)\n{message}",
- "title": "Alexa Media Player - Metodo di verifica"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}**\nHai confermato con successo una chiave usa e getta (OTP) dall'applicazione 2FA integrata con Amazon?\n>Codice OTP {message}",
"title": "Alexa Media Player - Conferma OTP"
},
- "twofactor": {
- "data": {
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)",
- "securitycode": "Codice autenticazione a due fattori (2FA)"
- },
- "description": "**{email} - alexa.{url}** \nInserisci la password usa e getta (OTP). \n{message}",
- "title": "Alexa Media Player - Autenticazione a Due Fattori"
- },
"user": {
"data": {
- "cookies_txt": "Dati cookies.txt",
"debug": "Debug avanzato",
"email": "Indirizzo email",
"exclude_devices": "Dispositivi da escludere (separati da virgola)",
"hass_url": "URL per accedere a Home Assistant",
"include_devices": "Dispositivi da includere (separati da virgola)",
- "oauth_login": "Abilita metodo app oauth-token",
"otp_secret": "Chiave dell'app 2FA integrata (generazione automatica di codici 2FA)",
"password": "Password",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)",
"scan_interval": "Tempo in secondi fra le scansioni",
"securitycode": "Codice 2FA (raccomandato per evitare problemi di login)",
"url": "Regione del dominio Amazon (ad es., amazon.it)"
},
- "description": "Confermare le informazioni sottostanti. Per le vecchie configurazioni, disabilitare l'opzione `Usa Proxy come metodo di login`.",
+ "description": "Confermare le informazioni sottostanti.",
"title": "Alexa Media Player - Configurazione"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Dati cookies.txt",
- "debug": "Debug avanzato",
- "email": "Indirizzo email",
- "exclude_devices": "Dispositivi da escludere (separati da virgola)",
- "include_devices": "Dispositivi da includere (separati da virgola)",
- "oauth_login": "Abilitare il metodo oauth-token app",
- "otp_secret": "Chiave 2FA integrata (genera automaticamente i codici 2FA)",
- "password": "Password",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)",
- "scan_interval": "Tempo in secondi fra le scansioni",
- "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)",
- "url": "Regione del dominio Amazon (ad es., amazon.it)"
- },
- "description": "Prego inserisci le tue [informazioni](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) potrebbe essere più semplice!** \n**ATTENZIONE: Amazon risponde incorrettamente 'Inserire una mail valida o un numero di telefono' quando è richiesto il codice 2FA](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}",
- "title": "Alexa Media Player - Vecchia configurazione"
}
}
},
diff --git a/custom_components/alexa_media/.translations/nb.json b/custom_components/alexa_media/.translations/nb.json
index 333797c9..87f5b062 100644
--- a/custom_components/alexa_media/.translations/nb.json
+++ b/custom_components/alexa_media/.translations/nb.json
@@ -14,38 +14,10 @@
"unknown_error": "Ukjent feil, vennligst rapporter logginfo"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon vil sende et push-varsel i henhold til meldingen nedenfor. Fullfør svaret før du fortsetter. \n{message}",
- "title": "Alexa Media Player - Handling påkrevd"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP-metode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Engangspassord"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Passord",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)"
- },
- "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Bekreftelsesmetode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nVelg bekreftelsesmetode etter nummer. (f.eks. \"0\" eller \"1\") \n{message}",
- "title": "Alexa Media Player - Bekreftelsesmetode"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA Kode"
- },
- "description": "**{email} - alexa.{url}** \nSkriv inn engangspassordet (OTP). \n>{message}",
- "title": "Alexa Media Player - Tofaktorautentisering"
- },
"user": {
"data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
"debug": "Avansert feilsøking",
"email": "Epostadresse",
"exclude_devices": "Ekskludert enhet (kommaseparert)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Inkluder enhet (kommaseparert)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Passord",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Sekunder mellom skanninger",
"securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)",
"url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)"
},
- "description": "Bekreft informasjonen nedenfor. For eldre konfigurasjon, deaktiver alternativet \"Bruk innlogging proxy-metode\".",
+ "description": "Bekreft informasjonen nedenfor.",
"title": "Alexa Media Player - Konfigurasjon"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "Avansert feilsøking",
- "email": "Epostadresse",
- "exclude_devices": "Ekskludert enhet (kommaseparert)",
- "include_devices": "Inkluder enhet (kommaseparert)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Passord",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Sekunder mellom skanninger",
- "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)",
- "url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/.translations/nl.json b/custom_components/alexa_media/.translations/nl.json
index 9a4afdbe..77558398 100644
--- a/custom_components/alexa_media/.translations/nl.json
+++ b/custom_components/alexa_media/.translations/nl.json
@@ -14,38 +14,10 @@
"unknown_error": "Onbekende fout, meld de loggegevens"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}",
- "title": "Alexa Media Player - Action Required"
- },
- "authselect": {
- "data": {
- "authselectoption": "Eenmalige Pincode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Eenmalige Pincode"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Paswoord",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "config::step::captcha::data::securitycode"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Verificatiemethode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nSelecteer de verificatiemethode (bv.`0` of `1`) \n{message}",
- "title": "Alexa Media Player - Verificatiemethode"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "Verificatiecode"
- },
- "description": "**{email} - alexa.{url}** \nGeef de verificatiecode in. \n{message}",
- "title": "Alexa Media Player - Tweestapsverificatie"
- },
"user": {
"data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
"debug": "Geavanceerd debuggen",
"email": "E-mailadres",
"exclude_devices": "Apparaten uitsluiten (Scheiding: komma)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Apparaten toevoegen (Scheiding: komma)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Paswoord",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Aantal seconden tussen scans",
"securitycode": "2FA Code (recommended to avoid login issues)",
"url": "Domeinnaam van Amazon regio (bv.amazon.co.uk)"
},
"description": "Vul je gegevens in a.u.b.",
"title": "Alexa Media Player - Configuratie"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "Geavanceerd debuggen",
- "email": "E-mailadres",
- "exclude_devices": "Apparaten uitsluiten (Scheiding: komma)",
- "include_devices": "Apparaten toevoegen (Scheiding: komma)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Paswoord",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Aantal seconden tussen scans",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "url": "Domeinnaam van Amazon regio (bv.amazon.co.uk)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/.translations/pl.json b/custom_components/alexa_media/.translations/pl.json
index e0121412..fcb2c462 100644
--- a/custom_components/alexa_media/.translations/pl.json
+++ b/custom_components/alexa_media/.translations/pl.json
@@ -14,38 +14,10 @@
"unknown_error": "Nieznany błąd, włącz zaawansowane debugowanie i zgłoś log z tego zdarzenia"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon wyśle powiadomienie push zgodnie z poniższą wiadomością. Przed kontynuowaniem odpowiedz na wiadomość. \n{message}",
- "title": "Alexa Media Player — wymagane działanie"
- },
- "authselect": {
- "data": {
- "authselectoption": "Metoda OTP",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player — hasło jednorazowe"
- },
"captcha": {
"data": {
- "captcha": "Kod Captcha",
- "password": "Hasło",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)",
"securitycode": "Kod uwierzytelniania dwuskładnikowego"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player — kod Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Metoda weryfikacji",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)"
- },
- "description": "**{email} - alexa.{url}** \nWybierz metodę weryfikacji. (np., `0` lub `1`) \n{message}",
- "title": "Alexa Media Player — metoda weryfikacji"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nCzy pomyślnie potwierdziłeś hasło jednorazowe z wbudowanej aplikacji uwierzytelniania dwuskładnikowego z Amazonu? \n >Kod hasła jednorazowego {message}",
"title": "Alexa Media Player - Potwierdzanie hasła jednorazowego"
},
- "twofactor": {
- "data": {
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)",
- "securitycode": "Kod uwierzytelniania dwuskładnikowego"
- },
- "description": "**{email} - alexa.{url}** \nWprowadź hasło jednorazowe (OTP). \n{message}",
- "title": "Alexa Media Player — uwierzytelnianie dwuskładnikowe"
- },
"user": {
"data": {
- "cookies_txt": "zawartość pliku cookies.txt",
"debug": "Zaawansowane debugowanie",
"email": "Adres email",
"exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)",
"hass_url": "URL dostępu do Home Assistanta",
"include_devices": "Dodawane urządzenia (oddzielone przecinkami)",
- "oauth_login": "Włącz metodę tokena OAuth aplikacji",
"otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)",
"password": "Hasło",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)",
"scan_interval": "Interwał skanowania (sekundy)",
"securitycode": "Kod uwierzytelniania dwuskładnikowego (zalecany w celu uniknięcia problemów z logowaniem)",
"url": "Region/domena Amazon (np. amazon.co.uk)"
},
- "description": "Potwierdź poniższe informacje. W przypadku starszych konfiguracji wyłącz opcję 'Użyj metody logowania proxy'.",
+ "description": "Potwierdź poniższe informacje.",
"title": "Alexa Media Player — konfiguracja"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "zawartość pliku cookies.txt",
- "debug": "Zaawansowane debugowanie",
- "email": "Adres e-mail",
- "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)",
- "include_devices": "Dodawane urządzenia (oddzielone przecinkami)",
- "oauth_login": "Włącz metodę tokena OAuth aplikacji",
- "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)",
- "password": "Hasło",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)",
- "scan_interval": "Interwał skanowania (sekundy)",
- "securitycode": "Kod uwierzytelniania dwuskładnikowego",
- "url": "Region/domena Amazon (np. amazon.co.uk)"
- },
- "description": "Wprowadź [dane](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Import pliku Cookie](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) może być najłatwiejszą metodą!** \n**OSTRZEŻENIE: Amazon nieprawidłowo zgłasza 'Wprowadź prawidłowy adres e-mail lub numer telefonu komórkowego', gdy wymagany jest [kod uwierzytelniania dwuskładnikowego](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}",
- "title": "Alexa Media Player — starsza konfiguracja"
}
}
},
diff --git a/custom_components/alexa_media/.translations/pt-BR.json b/custom_components/alexa_media/.translations/pt-BR.json
index 1f3897e2..6d4b4804 100644
--- a/custom_components/alexa_media/.translations/pt-BR.json
+++ b/custom_components/alexa_media/.translations/pt-BR.json
@@ -14,38 +14,10 @@
"unknown_error": "Erro desconhecido, favor habilitar depuração avançada e informações de registro de relatório"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Usar método Login Proxy (Não requer 2FA)"
- },
- "description": "** {email} - alexa. {url} **\n A Amazon enviará uma notificação por push de acordo com a mensagem abaixo. Por favor, responda completamente antes de continuar.\n {message}",
- "title": "Alexa Media Player - Ação necessária"
- },
- "authselect": {
- "data": {
- "authselectoption": "Método OTP",
- "proxy": "Usar método Login Proxy (Não requer 2FA)"
- },
- "description": "**{email} - alexa.{url}**\n{message}",
- "title": "Alexa Media Player - Senha de uso único"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Senha",
- "proxy": "Usar método Login Proxy (Não requer 2FA)",
"securitycode": "Código 2FA (recomendado para evitar problemas de login)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Método de verificação",
- "proxy": "Usar método Login Proxy (Não requer 2FA)"
- },
- "description": "** {email} - alexa. {url} **\n Selecione o método de verificação por número. (por exemplo, `0` ou `1`)\n {message}",
- "title": "Alexa Media Player - Método de verificação"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "** {email} - alexa. {url} **\n Você confirmou com sucesso um OTP da chave de aplicativo 2FA integrada com a Amazon?\n > Código OTP {message}",
"title": "Alexa Media Player - Confirmação OTP"
},
- "twofactor": {
- "data": {
- "proxy": "Usar método Login Proxy (Não requer 2FA)",
- "securitycode": "Código 2FA"
- },
- "description": "** {email} - alexa. {url} **\n Digite a senha de uso único (OTP).\n {message}",
- "title": "Alexa Media Player - Autenticação de dois fatores"
- },
"user": {
"data": {
- "cookies_txt": "Cookies.txt data",
"debug": "Depuração avançada",
"email": "Endereço de e-mail",
"exclude_devices": "Dispositivos excluídos (separado por vírgula)",
"hass_url": "Url para acesso ao Home Assistant",
"include_devices": "Dispositivos incluídos (separado por vírgula)",
- "oauth_login": "Habilitar o aplicativo para método auth-token",
"otp_secret": "Chave de aplicativo 2FA integrada (gerar automaticamente códigos 2FA)",
"password": "Senha",
- "proxy": "Usar método Login Proxy (Não requer 2FA)",
"scan_interval": "Segundos entre varreduras",
"securitycode": "Código 2FA (recomendado para evitar problemas de login)",
"url": "Domínio regional da Amazon (ex: amazon.co.uk)"
},
- "description": "Por favor, confirme as informações abaixo. Para configuração legada, desative a opção `Usar método de proxy de login`.",
+ "description": "Por favor, confirme as informações abaixo.",
"title": "Alexa Media Player - Configurações"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Cookies.txt data",
- "debug": "Depuração avançada",
- "email": "Endereço de e-mail",
- "exclude_devices": "Dispositivos excluídos (separado por vírgula)",
- "include_devices": "Dispositivos incluídos (separado por vírgula)",
- "oauth_login": "Habilitar o aplicativo para método auth-token",
- "otp_secret": "Chave de aplicativo 2FA integrada (gerar automaticamente códigos 2FA)",
- "password": "Senha",
- "proxy": "Usar método Login Proxy (Não requer 2FA)",
- "scan_interval": "Segundos entre escaneamentos",
- "securitycode": "Código 2FA (recomendado para evitar problemas de login)",
- "url": "Domínio regional da Amazon (ex: amazon.co.uk)"
- },
- "description": "Insira suas [informações](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Importação de cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) pode ser mais fácil!**\n **AVISO: A Amazon informa incorretamente 'Digite um e-mail ou número de celular válido' quando [o código 2FA é necessário](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication- para-sua-conta-amazon).**\n > {message}",
- "title": "Alexa Media Player - Configurações legado"
}
}
},
diff --git a/custom_components/alexa_media/.translations/pt.json b/custom_components/alexa_media/.translations/pt.json
index 5cf76c7d..0ea685b6 100644
--- a/custom_components/alexa_media/.translations/pt.json
+++ b/custom_components/alexa_media/.translations/pt.json
@@ -14,38 +14,10 @@
"unknown_error": "Erro desconhecido, por favor habilite depuração avançada e informações de log de relatório"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Usar método de proxy de login (2FA não é necessário)"
- },
- "description": "**{email} - alexa.{url}** \nA Amazon enviará uma notificação push de acordo com a mensagem abaixo. Por favor, responda completamente antes de continuar. \n{message}",
- "title": "Alexa Media Player - Ação Necessária"
- },
- "authselect": {
- "data": {
- "authselectoption": "Método OTP (senha de uso único)",
- "proxy": "Usar método de proxy de login (2FA não é necessário)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Senha de uso único"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Senha",
- "proxy": "Usar método de proxy de login (2FA não é necessário)",
"securitycode": "Código 2FA (recomendado para evitar problemas de login)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Método de verificação",
- "proxy": "Usar método de proxy de login (2FA não é necessário)"
- },
- "description": "**{email} - alexa.{url}** \nPor favor, selecione o método de verificação por número. (ex. '0' ou '1') \n{message}",
- "title": "Alexa Media Player - Método de verificação"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "** {email} - alexa. {url} **\n Você confirmou com sucesso uma senha de uso único na aplicação 2FA integrada com a Amazon?\n > Código OTP {message}",
"title": "Alexa Media Player - Confirmação OTP"
},
- "twofactor": {
- "data": {
- "proxy": "Usar método de proxy de login (2FA não é necessário)",
- "securitycode": "Código 2FA"
- },
- "description": "**{email} - alexa.{url}** \nDigite a senha de uso único (OTP). \n{message}",
- "title": "Alexa Media Player - autenticação de dois fatores"
- },
"user": {
"data": {
- "cookies_txt": "Dados de cookies.txt",
"debug": "Depuração avançada",
"email": "Endereço de e-mail",
"exclude_devices": "Dispositivo excluído (separado por vírgula)",
"hass_url": "URL para aceder o Home Assistant",
"include_devices": "Dispositivo incluído (separado por vírgula)",
- "oauth_login": "Habilitar método de aplicativo oauth-token",
"otp_secret": "Chave de aplicativo 2FA integrada (gerar códigos 2FA automaticamente)",
"password": "Senha",
- "proxy": "Usar método de proxy de login (2FA não é necessário)",
"scan_interval": "Segundos entre análises",
"securitycode": "Código 2FA (recomendado para evitar problemas de login)",
"url": "Região do domínio Amazon (ex. amazon.com.br)"
},
- "description": "Por favor, confirme as informações abaixo. Para configuração de compatibilidade, desative a opção 'Usar método de proxy de login'.",
+ "description": "Por favor, confirme as informações abaixo.",
"title": "Alexa Media Player - Configuração"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Dados de cookies.txt",
- "debug": "Depuração avançada",
- "email": "Endereço de e-mail",
- "exclude_devices": "Dispositivo excluído (separado por vírgula)",
- "include_devices": "Dispositivo incluído (separado por vírgula)",
- "oauth_login": "Habilitar método de aplicativo oauth-token",
- "otp_secret": "Chave de aplicativo 2FA integrada (gerar códigos 2FA automaticamente)",
- "password": "Senha",
- "proxy": "Usar método de proxy de login (2FA não é necessário)",
- "scan_interval": "Segundos entre análises",
- "securitycode": "Código 2FA (recomendado para evitar problemas de login)",
- "url": "Região do domínio Amazon (ex. amazon.com.br)"
- },
- "description": "Por favor, introduza a sua [informação](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) pode ser mais fácil!** \n**Aviso: a Amazon informa incorretamente 'Insira um e-mail ou número de celular válido' quando [2FA Code é necessário](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}",
- "title": "Alexa Media Player - Configuração de Compatibilidade"
}
}
},
diff --git a/custom_components/alexa_media/.translations/pt_BR.json b/custom_components/alexa_media/.translations/pt_BR.json
index 2696cc29..2e8d00b0 100644
--- a/custom_components/alexa_media/.translations/pt_BR.json
+++ b/custom_components/alexa_media/.translations/pt_BR.json
@@ -8,7 +8,7 @@
"error": {
"2fa_key_invalid": "Chave integrada 2FA inválida",
"connection_error": "Erro de conexão; Verifique a sua conexão e tente novamente",
- "hass_url_invalid": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL externa em Configuração -> Geral",
+ "unable_to_connect_hass_url": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL externa em Configuração -> Geral",
"identifier_exists": "Email para URL Alexa já registrado",
"invalid_credentials": "Credenciais inválidas",
"unknown_error": "Erro desconhecido, favor habilitar a depuração avançada e reporte as informações de registro"
diff --git a/custom_components/alexa_media/.translations/pt_PT.json b/custom_components/alexa_media/.translations/pt_PT.json
index 5cf76c7d..2d7b4382 100644
--- a/custom_components/alexa_media/.translations/pt_PT.json
+++ b/custom_components/alexa_media/.translations/pt_PT.json
@@ -8,7 +8,7 @@
"error": {
"2fa_key_invalid": "Chave 2FA integrada inválida",
"connection_error": "Erro ao conectar; verifique a rede e tente novamente",
- "hass_url_invalid": "Não foi possível conectar ao URL do Home Assistant. Verifique o URL externo em Configuração - > Geral",
+ "unable_to_connect_hass_url": "Não foi possível conectar ao URL do Home Assistant. Verifique o URL externo em Configuração - > Geral",
"identifier_exists": "E-mail para URL Alexa já registado",
"invalid_credentials": "Credenciais inválidas",
"unknown_error": "Erro desconhecido, por favor habilite depuração avançada e informações de log de relatório"
diff --git a/custom_components/alexa_media/.translations/ru.json b/custom_components/alexa_media/.translations/ru.json
index ee10f828..e6aa3c69 100644
--- a/custom_components/alexa_media/.translations/ru.json
+++ b/custom_components/alexa_media/.translations/ru.json
@@ -14,38 +14,10 @@
"unknown_error": "Неизвестная ошибка, пожалуйста, сообщите информацию журнала"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa. {url}**\nAmazon отправит push-уведомление в соответствии с нижеприведенным сообщением. Пожалуйста, полностью ответьте, прежде чем продолжить.\n{message}",
- "title": "Alexa Media Player - Требуемое действие"
- },
- "authselect": {
- "data": {
- "authselectoption": "Одноразовый пароль",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Одноразовый Пароль"
- },
"captcha": {
"data": {
- "captcha": "Капча",
- "password": "Пароль",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA Code (recommended to avoid login issues)"
- },
- "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}",
- "title": "Alexa Media Player - Капча"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Метод проверки",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}**\nПожалуйста, выберите способ проверки по номеру. (например, \" 0 \" или \" 1`)\n{message}",
- "title": "Alexa Media Player - Метод проверки"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2-факторная авторизация"
- },
- "description": "**{email} - alexa.{url}**\nВведите одноразовый пароль.\n{message}",
- "title": "Alexa Media Player - Двух факторная идентификация"
- },
"user": {
"data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
"debug": "Расширенные возможности отладки",
"email": "Адрес электронной почты",
"exclude_devices": "Исключенные устройства (через запятую)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Включенные устройства (разделенное запятыми)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Пароль",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Секунды между сканированиями",
"securitycode": "2FA Code (recommended to avoid login issues)",
"url": "Домен региона Amazon (например, amazon.co.uk)"
},
- "description": "Пожалуйста, подтвердите информацию ниже. Для старой конфигурации отключите опцию `Use Login Proxy method`.",
+ "description": "Пожалуйста, подтвердите информацию ниже.",
"title": "Alexa Media Player - Конфигурация"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "Расширенные возможности отладки",
- "email": "Адрес электронной почты",
- "exclude_devices": "Исключенные устройства (через запятую)",
- "include_devices": "Включенные устройства (разделенное запятыми)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Пароль",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Секунды между сканированиями",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "url": "Домен региона Amazon (например, amazon.co.uk)"
- },
- "description": "Пожалуйста, введите свои данные.{message}",
- "title": "Alexa Media Player - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/.translations/zh-Hans.json b/custom_components/alexa_media/.translations/zh-Hans.json
index 4af369e3..2658d0e6 100644
--- a/custom_components/alexa_media/.translations/zh-Hans.json
+++ b/custom_components/alexa_media/.translations/zh-Hans.json
@@ -14,38 +14,10 @@
"unknown_error": "Unknown error, please report log info"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}",
- "title": "Alexa Media Player - Action Required"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP方式",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}**\n{message}",
- "title": "Alexa Media Player - 一次性密码"
- },
"captcha": {
"data": {
- "captcha": "验证码",
- "password": "密码",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA Code (recommended to avoid login issues)"
- },
- "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}",
- "title": "Alexa Media Player-验证码"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "验证方式",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}",
- "title": "Alexa Media Player - 验证方法"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA代码"
- },
- "description": "**{email} - Alexa.{url} ** \n输入一次性密码(OTP)。 \n {message}",
- "title": "Alexa Media Player - Two Factor Authentication"
- },
"user": {
"data": {
- "cookies_txt": "Cookie.txt数据",
"debug": "高级调试",
"email": "电子邮件地址",
"exclude_devices": "Excluded device (comma separated)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Included device (comma separated)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "密码",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Seconds between scans",
"securitycode": "2FA Code (recommended to avoid login issues)",
"url": "Amazon region domain (e.g., amazon.co.uk)"
},
- "description": "请确认以下信息。对于旧版配置,请禁用“使用登录代理方法”选项。",
+ "description": "请确认以下信息。",
"title": "Alexa Media Player-配置"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "高级调试",
- "email": "电子邮件地址",
- "exclude_devices": "Excluded device (comma separated)",
- "include_devices": "Included device (comma separated)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "密码",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Seconds between scans",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "url": "Amazon region domain (e.g., amazon.co.uk)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/__init__.py b/custom_components/alexa_media/__init__.py
index 3f604e3f..a02027ea 100644
--- a/custom_components/alexa_media/__init__.py
+++ b/custom_components/alexa_media/__init__.py
@@ -148,8 +148,12 @@ async def async_setup(hass, config, discovery_info=None):
CONF_SCAN_INTERVAL: account[
CONF_SCAN_INTERVAL
].total_seconds(),
- CONF_OAUTH: account.get(CONF_OAUTH, {}),
- CONF_OTPSECRET: account.get(CONF_OTPSECRET, ""),
+ CONF_OAUTH: account.get(
+ CONF_OAUTH, entry.data.get(CONF_OAUTH, {})
+ ),
+ CONF_OTPSECRET: account.get(
+ CONF_OTPSECRET, entry.data.get(CONF_OTPSECRET, "")
+ ),
},
)
entry_found = True
@@ -642,6 +646,7 @@ async def async_update_data() -> Optional[AlexaEntityData]:
"access_token": login_obj.access_token,
"refresh_token": login_obj.refresh_token,
"expires_in": login_obj.expires_in,
+ "mac_dms": login_obj.mac_dms,
},
},
)
@@ -652,7 +657,7 @@ async def process_notifications(login_obj, raw_notifications=None):
"""Process raw notifications json."""
if not raw_notifications:
raw_notifications = await AlexaAPI.get_notifications(login_obj)
- email: Text = login_obj.email
+ email: str = login_obj.email
previous = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get(
"notifications", {}
)
@@ -1054,7 +1059,7 @@ async def ws_handler(message_obj):
async def ws_open_handler():
"""Handle websocket open."""
- email: Text = login_obj.email
+ email: str = login_obj.email
_LOGGER.debug("%s: Websocket successfully connected", hide_email(email))
hass.data[DATA_ALEXAMEDIA]["accounts"][email][
"websocketerror"
@@ -1069,7 +1074,7 @@ async def ws_close_handler():
This should attempt to reconnect up to 5 times
"""
- email: Text = login_obj.email
+ email: str = login_obj.email
if login_obj.close_requested:
_LOGGER.debug(
"%s: Close requested; will not reconnect websocket", hide_email(email)
@@ -1081,7 +1086,7 @@ async def ws_close_handler():
)
return
errors: int = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"]
- delay: int = 5 * 2 ** errors
+ delay: int = 5 * 2**errors
last_attempt = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
"websocket_lastattempt"
]
@@ -1107,7 +1112,7 @@ async def ws_close_handler():
errors = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"] = (
hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"] + 1
)
- delay = 5 * 2 ** errors
+ delay = 5 * 2**errors
errors = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"]
await asyncio.sleep(delay)
if not websocket_enabled:
@@ -1128,7 +1133,7 @@ async def ws_error_handler(message):
the websocket and determine if a reconnect should be done. By
specification, websockets will issue a close after every error.
"""
- email: Text = login_obj.email
+ email: str = login_obj.email
errors = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"]
_LOGGER.debug(
"%s: Received websocket error #%i %s: type %s",
@@ -1245,7 +1250,7 @@ async def async_unload_entry(hass, entry) -> bool:
return True
-async def close_connections(hass, email: Text) -> None:
+async def close_connections(hass, email: str) -> None:
"""Clear open aiohttp connections for email."""
if (
email not in hass.data[DATA_ALEXAMEDIA]["accounts"]
@@ -1293,7 +1298,7 @@ async def test_login_status(hass, config_entry, login) -> bool:
account = config_entry.data
_LOGGER.debug("Logging in: %s %s", obfuscate(account), in_progess_instances(hass))
_LOGGER.debug("Login stats: %s", login.stats)
- message: Text = f"Reauthenticate {login.email} on the [Integrations](/config/integrations) page. "
+ message: str = f"Reauthenticate {login.email} on the [Integrations](/config/integrations) page. "
if login.stats.get("login_timestamp") != datetime(1, 1, 1):
elaspsed_time: str = str(datetime.now() - login.stats.get("login_timestamp"))
api_calls: int = login.stats.get("api_calls")
diff --git a/custom_components/alexa_media/config_flow.py b/custom_components/alexa_media/config_flow.py
index d94281de..8bba88f3 100644
--- a/custom_components/alexa_media/config_flow.py
+++ b/custom_components/alexa_media/config_flow.py
@@ -14,7 +14,7 @@
import logging
from typing import Any, Dict, List, Optional, Text
-from aiohttp import ClientConnectionError, ClientSession, web, web_response
+from aiohttp import ClientConnectionError, ClientSession, InvalidURL, web, web_response
from aiohttp.web_exceptions import HTTPBadRequest
from alexapy import (
AlexaLogin,
@@ -50,6 +50,7 @@
CONF_INCLUDE_DEVICES,
CONF_OAUTH,
CONF_OTPSECRET,
+ CONF_PROXY_WARNING,
CONF_QUEUE_DELAY,
CONF_SECURITYCODE,
CONF_TOTP_REGISTER,
@@ -103,7 +104,7 @@ def __init__(self):
_LOGGER.info(STARTUP)
_LOGGER.info("Loaded alexapy==%s", alexapy_version)
self.login = None
- self.securitycode: Optional[Text] = None
+ self.securitycode: Optional[str] = None
self.automatic_steps: int = 0
self.config = OrderedDict()
self.proxy_schema = None
@@ -123,6 +124,9 @@ def __init__(self):
self.totp_register = OrderedDict(
[(vol.Optional(CONF_TOTP_REGISTER, default=False), bool)]
)
+ self.proxy_warning = OrderedDict(
+ [(vol.Optional(CONF_PROXY_WARNING, default=False), bool)]
+ )
async def async_step_import(self, import_config):
"""Import a config entry from configuration.yaml."""
@@ -132,7 +136,7 @@ async def async_step_user(self, user_input=None):
"""Provide a proxy for login."""
self._save_user_input_to_config(user_input=user_input)
try:
- hass_url: Text = get_url(self.hass, prefer_external=True)
+ hass_url: str = get_url(self.hass, prefer_external=True)
except NoURLAvailableError:
hass_url = ""
self.proxy_schema = OrderedDict(
@@ -222,6 +226,7 @@ async def async_step_user(self, user_input=None):
outputpath=self.hass.config.path,
debug=self.config[CONF_DEBUG],
otp_secret=self.config.get(CONF_OTPSECRET, ""),
+ oauth=self.config.get(CONF_OAUTH, {}),
uuid=uuid,
oauth_login=True,
)
@@ -239,35 +244,39 @@ async def async_step_user(self, user_input=None):
errors={"base": "2fa_key_invalid"},
description_placeholders={"message": ""},
)
- hass_url: Text = user_input.get(CONF_HASS_URL)
+ hass_url: str = user_input.get(CONF_HASS_URL)
hass_url_valid: bool = False
+ hass_url_error: str = ""
async with ClientSession() as session:
try:
async with session.get(hass_url) as resp:
hass_url_valid = resp.status == 200
- except ClientConnectionError:
+ except (ClientConnectionError) as err:
+ hass_url_valid = False
+ hass_url_error = str(err)
+ except (InvalidURL) as err:
hass_url_valid = False
+ hass_url_error = str(err.__cause__)
if not hass_url_valid:
_LOGGER.debug(
"Unable to connect to provided Home Assistant url: %s", hass_url
)
return self.async_show_form(
- step_id="user",
- errors={"base": "hass_url_invalid"},
- description_placeholders={"message": ""},
- )
- if not self.proxy:
- self.proxy = AlexaProxy(
- self.login, str(URL(hass_url).with_path(AUTH_PROXY_PATH))
- )
- # Swap the login object
- self.proxy.change_login(self.login)
+ step_id="proxy_warning",
+ data_schema=vol.Schema(self.proxy_warning),
+ errors={},
+ description_placeholders={
+ "email": self.login.email,
+ "hass_url": hass_url,
+ "error": hass_url_error
+ },
+ )
if (
user_input
and user_input.get(CONF_OTPSECRET)
and user_input.get(CONF_OTPSECRET).replace(" ", "")
):
- otp: Text = self.login.get_totp_token()
+ otp: str = self.login.get_totp_token()
if otp:
_LOGGER.debug("Generating OTP from %s", otp)
return self.async_show_form(
@@ -289,6 +298,19 @@ async def async_step_start_proxy(self, user_input=None):
hide_email(self.login.email),
self.login.url,
)
+ if not self.proxy:
+ try:
+ self.proxy = AlexaProxy(
+ self.login, str(URL(self.config.get(CONF_HASS_URL)).with_path(AUTH_PROXY_PATH))
+ )
+ except ValueError as ex:
+ return self.async_show_form(
+ step_id="user",
+ errors={"base": "invalid_url"},
+ description_placeholders={"message": str(ex)},
+ )
+ # Swap the login object
+ self.proxy.change_login(self.login)
if not self.proxy_view:
self.proxy_view = AlexaMediaAuthorizationProxyView(self.proxy.all_handler)
else:
@@ -297,7 +319,7 @@ async def async_step_start_proxy(self, user_input=None):
self.hass.http.register_view(AlexaMediaAuthorizationCallbackView())
self.hass.http.register_view(self.proxy_view)
callback_url = (
- URL(self.config["hass_url"])
+ URL(self.config[CONF_HASS_URL])
.with_path(AUTH_CALLBACK_PATH)
.with_query({"flow_id": self.flow_id})
)
@@ -387,7 +409,7 @@ async def async_step_user_legacy(self, user_input=None):
and user_input.get(CONF_OTPSECRET)
and user_input.get(CONF_OTPSECRET).replace(" ", "")
):
- otp: Text = self.login.get_totp_token()
+ otp: str = self.login.get_totp_token()
if otp:
_LOGGER.debug("Generating OTP from %s", otp)
return self.async_show_form(
@@ -435,15 +457,28 @@ async def async_step_user_legacy(self, user_input=None):
return self.async_show_form(
step_id="user_legacy",
errors={"base": "unknown_error"},
+ description_placeholders={"message": str(ex)},
+ )
+
+ async def async_step_proxy_warning(self, user_input=None):
+ """Handle the proxy_warning for the config flow."""
+ self._save_user_input_to_config(user_input=user_input)
+ if user_input and user_input.get(CONF_PROXY_WARNING) is False:
+ _LOGGER.debug("User is not accepting warning, go back")
+ return self.async_show_form(
+ step_id="user",
+ data_schema=vol.Schema(self.proxy_schema),
description_placeholders={"message": ""},
)
+ _LOGGER.debug("User is ignoring proxy warning; starting proxy anyway")
+ return await self.async_step_start_proxy(user_input)
async def async_step_totp_register(self, user_input=None):
"""Handle the input processing of the config flow."""
self._save_user_input_to_config(user_input=user_input)
- if user_input and user_input.get("registered") is False:
+ if user_input and user_input.get(CONF_TOTP_REGISTER) is False:
_LOGGER.debug("Not registered, regenerating")
- otp: Text = self.login.get_totp_token()
+ otp: str = self.login.get_totp_token()
if otp:
_LOGGER.debug("Generating OTP from %s", otp)
return self.async_show_form(
@@ -523,6 +558,7 @@ async def _test_login(self):
"access_token": login.access_token,
"refresh_token": login.refresh_token,
"expires_in": login.expires_in,
+ "mac_dms": login.mac_dms
}
self.hass.data.setdefault(
DATA_ALEXAMEDIA,
@@ -567,7 +603,7 @@ async def _test_login(self):
"Creating config_flow to request 2FA. Saved security code %s",
self.securitycode,
)
- generated_securitycode: Text = login.get_totp_token()
+ generated_securitycode: str = login.get_totp_token()
if (
self.securitycode or generated_securitycode
) and self.automatic_steps < 2:
@@ -783,12 +819,12 @@ async def get(self, request: web.Request):
class AlexaMediaAuthorizationProxyView(HomeAssistantView):
"""Handle proxy connections."""
- url: Text = AUTH_PROXY_PATH
- extra_urls: List[Text] = [f"{AUTH_PROXY_PATH}/{{tail:.*}}"]
- name: Text = AUTH_PROXY_NAME
+ url: str = AUTH_PROXY_PATH
+ extra_urls: List[str] = [f"{AUTH_PROXY_PATH}/{{tail:.*}}"]
+ name: str = AUTH_PROXY_NAME
requires_auth: bool = False
handler: web.RequestHandler = None
- known_ips: Dict[Text, datetime.datetime] = {}
+ known_ips: Dict[str, datetime.datetime] = {}
auth_seconds: int = 300
def __init__(self, handler: web.RequestHandler):
diff --git a/custom_components/alexa_media/const.py b/custom_components/alexa_media/const.py
index 72ec35c9..ba627e2e 100644
--- a/custom_components/alexa_media/const.py
+++ b/custom_components/alexa_media/const.py
@@ -8,7 +8,7 @@
"""
from datetime import timedelta
-__version__ = "4.0.1"
+__version__ = "4.1.0"
PROJECT_URL = "https://github.com/custom-components/alexa_media_player/"
ISSUE_URL = f"{PROJECT_URL}issues"
@@ -42,6 +42,7 @@
CONF_SECURITYCODE = "securitycode"
CONF_OTPSECRET = "otp_secret"
CONF_PROXY = "proxy"
+CONF_PROXY_WARNING = "proxy_warning"
CONF_TOTP_REGISTER = "registered"
CONF_OAUTH = "oauth"
DATA_LISTENER = "listener"
@@ -69,6 +70,15 @@
"XXXX-WXX-7": "Every Sunday",
}
+RECURRING_DAY = {
+ "MO": 1,
+ "TU": 2,
+ "WE": 3,
+ "TH": 4,
+ "FR": 5,
+ "SA": 6,
+ "SU": 7,
+}
RECURRING_PATTERN_ISO_SET = {
None: {},
"P1D": {1, 2, 3, 4, 5, 6, 7},
diff --git a/custom_components/alexa_media/manifest.json b/custom_components/alexa_media/manifest.json
index aa953dfc..4dbc698f 100644
--- a/custom_components/alexa_media/manifest.json
+++ b/custom_components/alexa_media/manifest.json
@@ -1,12 +1,12 @@
{
"domain": "alexa_media",
"name": "Alexa Media Player",
- "version": "4.0.1",
+ "version": "4.1.0",
"config_flow": true,
"documentation": "https://github.com/custom-components/alexa_media_player/wiki",
"issue_tracker": "https://github.com/custom-components/alexa_media_player/issues",
"dependencies": ["persistent_notification", "http"],
"codeowners": ["@alandtse", "@keatontaylor"],
- "requirements": ["alexapy==1.25.6", "packaging>=20.3", "wrapt>=1.12.1"],
+ "requirements": ["alexapy==1.26.1", "packaging>=20.3", "wrapt>=1.12.1"],
"iot_class": "cloud_polling"
}
diff --git a/custom_components/alexa_media/notify.py b/custom_components/alexa_media/notify.py
index 51b02a34..c8e76ef5 100644
--- a/custom_components/alexa_media/notify.py
+++ b/custom_components/alexa_media/notify.py
@@ -146,22 +146,44 @@ def targets(self):
for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
if "entities" not in account_dict:
return devices
+ last_called_entity = None
for _, entity in account_dict["entities"]["media_player"].items():
entity_name = (entity.entity_id).split(".")[1]
devices[entity_name] = entity.unique_id
if self.last_called and entity.extra_state_attributes.get(
"last_called"
):
- entity_name_last_called = (
- f"last_called{'_'+ email if entity_name[-1:].isdigit() else ''}"
- )
- _LOGGER.debug(
- "%s: Creating last_called target %s using %s",
- hide_email(email),
- entity_name_last_called,
- entity,
- )
- devices[entity_name_last_called] = entity.unique_id
+ if last_called_entity is None:
+ _LOGGER.debug(
+ "%s: Found last_called %s called at %s",
+ hide_email(email),
+ entity,
+ entity.extra_state_attributes.get("last_called_timestamp"),
+ )
+ last_called_entity = entity
+ elif (last_called_entity.extra_state_attributes.get("last_called_timestamp")
+ < entity.extra_state_attributes.get("last_called_timestamp")
+ ):
+ _LOGGER.debug(
+ "%s: Found newer last_called %s called at %s",
+ hide_email(email),
+ entity,
+ entity.extra_state_attributes.get("last_called_timestamp"),
+ )
+ last_called_entity = entity
+ if last_called_entity is not None:
+ entity_name = (last_called_entity.entity_id).split(".")[1]
+ entity_name_last_called = (
+ f"last_called{'_'+ email if entity_name[-1:].isdigit() else ''}"
+ )
+ _LOGGER.debug(
+ "%s: Creating last_called target %s using %s called at %s",
+ hide_email(email),
+ entity_name_last_called,
+ last_called_entity,
+ last_called_entity.extra_state_attributes.get("last_called_timestamp"),
+ )
+ devices[entity_name_last_called] = last_called_entity.unique_id
return devices
@property
diff --git a/custom_components/alexa_media/sensor.py b/custom_components/alexa_media/sensor.py
index a9990695..5d05ccc3 100644
--- a/custom_components/alexa_media/sensor.py
+++ b/custom_components/alexa_media/sensor.py
@@ -10,6 +10,7 @@
import logging
from typing import Callable, List, Optional, Text # noqa pylint: disable=unused-import
+from homeassistant.components.sensor import SensorEntity
from homeassistant.const import (
DEVICE_CLASS_TIMESTAMP,
STATE_UNAVAILABLE,
@@ -18,7 +19,6 @@
)
from homeassistant.exceptions import ConfigEntryNotReady, NoEntitySpecifiedError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
-from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt
@@ -37,6 +37,7 @@
from .alexa_entity import parse_temperature_from_coordinator
from .const import (
CONF_EXTENDED_ENTITY_DISCOVERY,
+ RECURRING_DAY,
RECURRING_PATTERN,
RECURRING_PATTERN_ISO_SET,
)
@@ -181,7 +182,7 @@ def lookup_device_info(account_dict, device_serial):
return None
-class TemperatureSensor(CoordinatorEntity):
+class TemperatureSensor(SensorEntity, CoordinatorEntity):
"""A temperature sensor reported by an Echo."""
def __init__(self, coordinator, entity_id, name, media_player_device_id):
@@ -221,14 +222,14 @@ def unique_id(self):
return self.alexa_entity_id + "_temperature"
-class AlexaMediaNotificationSensor(Entity):
+class AlexaMediaNotificationSensor(SensorEntity):
"""Representation of Alexa Media sensors."""
def __init__(
self,
client,
n_dict,
- sensor_property: Text,
+ sensor_property: str,
account,
name="Next Notification",
icon=None,
@@ -252,9 +253,9 @@ def __init__(
self._tracker: Optional[Callable] = None
self._state: Optional[datetime.datetime] = None
self._dismissed: Optional[datetime.datetime] = None
- self._status: Optional[Text] = None
- self._amz_id: Optional[Text] = None
- self._version: Optional[Text] = None
+ self._status: Optional[str] = None
+ self._amz_id: Optional[str] = None
+ self._version: Optional[str] = None
def _process_raw_notifications(self):
self._all = (
@@ -356,35 +357,47 @@ def _fix_alarm_date_time(self, value):
def _update_recurring_alarm(self, value):
_LOGGER.debug("Sensor value %s", value)
- alarm = value[1][self._sensor_property]
+ next_item = value[1]
+ alarm = next_item[self._sensor_property]
reminder = None
- if isinstance(value[1][self._sensor_property], (int, float)):
+ recurrence = []
+ if isinstance(next_item[self._sensor_property], (int, float)):
reminder = True
alarm = dt.as_local(
self._round_time(
datetime.datetime.fromtimestamp(alarm / 1000, tz=LOCAL_TIMEZONE)
)
)
- alarm_on = value[1]["status"] == "ON"
- recurring_pattern = value[1].get("recurringPattern")
+ alarm_on = next_item["status"] == "ON"
+ r_rule_data = next_item.get("rRuleData")
+ if r_rule_data: # the new recurrence pattern; https://github.com/custom-components/alexa_media_player/issues/1608
+ next_trigger_times = r_rule_data.get("nextTriggerTimes")
+ weekdays = r_rule_data.get("byWeekDays")
+ if next_trigger_times:
+ alarm = next_trigger_times[0]
+ elif weekdays:
+ for day in weekdays:
+ recurrence.append(RECURRING_DAY[day])
+ else:
+ recurring_pattern = next_item.get("recurringPattern")
+ recurrence = RECURRING_PATTERN_ISO_SET.get(recurring_pattern)
while (
alarm_on
- and recurring_pattern
- and RECURRING_PATTERN_ISO_SET.get(recurring_pattern)
- and alarm.isoweekday not in RECURRING_PATTERN_ISO_SET[recurring_pattern]
+ and recurrence
+ and alarm.isoweekday not in recurrence
and alarm < dt.now()
):
alarm += datetime.timedelta(days=1)
if reminder:
alarm = dt.as_timestamp(alarm) * 1000
- if alarm != value[1][self._sensor_property]:
+ if alarm != next_item[self._sensor_property]:
_LOGGER.debug(
"%s with recurrence %s set to %s",
- value[1]["type"],
- RECURRING_PATTERN.get(recurring_pattern),
+ next_item["type"],
+ recurrence,
alarm,
)
- value[1][self._sensor_property] = alarm
+ next_item[self._sensor_property] = alarm
return value
@staticmethod
diff --git a/custom_components/alexa_media/strings.json b/custom_components/alexa_media/strings.json
index 0844ad31..55e880b0 100644
--- a/custom_components/alexa_media/strings.json
+++ b/custom_components/alexa_media/strings.json
@@ -4,9 +4,10 @@
"connection_error": "Error connecting; check network and retry",
"identifier_exists": "Email for Alexa URL already registered",
"invalid_credentials": "Invalid credentials",
+ "invalid_url": "URL is invalid: {message}",
"2fa_key_invalid": "Invalid Built-In 2FA key",
- "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General",
- "unknown_error": "Unknown error, please enable advanced debugging and report log info"
+ "unable_to_connect_hass_url": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General",
+ "unknown_error": "Unknown error: {message}"
},
"step": {
"user": {
@@ -15,7 +16,7 @@
"email": "Email Address",
"url": "Amazon region domain (e.g., amazon.co.uk)",
"hass_url": "Url to access Home Assistant",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
+ "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes). This not six digits long.",
"include_devices": "Included device (comma separated)",
"exclude_devices": "Excluded device (comma separated)",
"debug": "Advanced debugging",
@@ -26,33 +27,12 @@
"description": "Please confirm the information below. For legacy configuration, disable `Use Login Proxy method` option.",
"title": "Alexa Media Player - Configuration"
},
- "user_legacy": {
+ "proxy_warning": {
"data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "password": "Password",
- "email": "Email Address",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "url": "Amazon region domain (e.g., amazon.co.uk)",
- "include_devices": "Included device (comma separated)",
- "exclude_devices": "Excluded device (comma separated)",
- "debug": "Advanced debugging",
- "scan_interval": "Seconds between scans",
- "cookies_txt": "Cookies.txt data",
- "oauth_login": "Enable oauth-token app method"
- },
- "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 - Legacy Configuration"
- },
- "captcha": {
- "data": {
- "password": "Password",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "captcha": "Captcha",
- "proxy": "Use Login Proxy method (2FA not required)"
+ "proxy_warning": "Ignore and Continue - I understand that no support for login issues are provided for bypassing this warning."
},
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
+ "description": "The HA server cannot connect to the URL provided: {hass_url}.\n> {error}\n\nTo fix this, please confirm your **HA server** can reach {hass_url}. This field is from the External Url under Configuration -> General but you can try your internal url.\n\nIf you are **certain** your client can reach this url, you can bypass this warning.",
+ "title": "Alexa Media Player - Unable to Connect to HA URL"
},
"totp_register": {
"data": {
@@ -60,43 +40,12 @@
},
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
- },
- "twofactor": {
- "data": {
- "securitycode": "2FA Code",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nEnter the One Time Password (OTP). \n{message}",
- "title": "Alexa Media Player - Two Factor Authentication"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP method",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - One Time Password"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Verification method",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}",
- "title": "Alexa Media Player - Verification Method"
- },
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}",
- "title": "Alexa Media Player - Action Required"
}
},
"abort": {
"forgot_password": "The Forgot Password page was detected. This normally is the result of too may failed logins. Amazon may require action before a relogin can be attempted.",
"login_failed": "Alexa Media Player failed to login.",
- "reauth_successful": "Alexa Media Player successfully reauthenticated."
+ "reauth_successful": "Alexa Media Player successfully reauthenticated. Please ignore the \"Aborted\" message from HA."
}
},
"options": {
diff --git a/custom_components/alexa_media/translations/ar.json b/custom_components/alexa_media/translations/ar.json
new file mode 100644
index 00000000..216f8f32
--- /dev/null
+++ b/custom_components/alexa_media/translations/ar.json
@@ -0,0 +1,57 @@
+{
+ "config": {
+ "abort": {
+ "forgot_password": "The Forgot Password page was detected. This normally is the result of too may failed logins. Amazon may require action before a relogin can be attempted.",
+ "login_failed": "Alexa Media Player failed to login.",
+ "reauth_successful": "Alexa Media Player successfully reauthenticated."
+ },
+ "error": {
+ "2fa_key_invalid": "Invalid Built-In 2FA key",
+ "connection_error": "Error connecting; check network and retry",
+ "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the External Url under Configuration -> General",
+ "identifier_exists": "Email for Alexa URL already registered",
+ "invalid_credentials": "Invalid credentials",
+ "unknown_error": "Unknown error, please enable advanced debugging and report log info"
+ },
+ "step": {
+ "captcha": {
+ "data": {
+ "securitycode": "2FA Code (recommended to avoid login issues)"
+ }
+ },
+ "totp_register": {
+ "data": {
+ "registered": "OTP from the Built-in 2FA App Key confirmed successfully."
+ },
+ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
+ "title": "Alexa Media Player - OTP Confirmation"
+ },
+ "user": {
+ "data": {
+ "debug": "Advanced debugging",
+ "email": "Email Address",
+ "exclude_devices": "Excluded device (comma separated)",
+ "hass_url": "Url to access Home Assistant",
+ "include_devices": "Included device (comma separated)",
+ "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
+ "password": "Password",
+ "scan_interval": "Seconds between scans",
+ "securitycode": "2FA Code (recommended to avoid login issues)",
+ "url": "Amazon region domain (e.g., amazon.co.uk)"
+ },
+ "description": "Please confirm the information below.",
+ "title": "Alexa Media Player - Configuration"
+ }
+ }
+ },
+ "options": {
+ "step": {
+ "init": {
+ "data": {
+ "extended_entity_discovery": "Include devices connected via Echo",
+ "queue_delay": "Seconds to wait to queue commands together"
+ }
+ }
+ }
+ }
+}
diff --git a/custom_components/alexa_media/translations/de.json b/custom_components/alexa_media/translations/de.json
index 15a60736..39e2fba1 100644
--- a/custom_components/alexa_media/translations/de.json
+++ b/custom_components/alexa_media/translations/de.json
@@ -14,38 +14,10 @@
"unknown_error": "Unbekannter Fehler, bitte Log-Info melden"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon sendet eine Push-Benachrichtigung je folgender Nachricht. Bitte antworte sie vollständig, bevor Du fortfährst. \n{message}",
- "title": "Alexa Media Player - Aktion erforderlich"
- },
- "authselect": {
- "data": {
- "authselectoption": "Einmal Pin Methode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Einmal Pin Passwort"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Passwort",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Verifizierungs Methode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nBitte Verifizierungs Methode auswählen. (z.B., `0` oder `1`) \n{message}",
- "title": "Alexa Media Player - Verifizierungs Methode"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA Code"
- },
- "description": "**{email} - alexa.{url}** \nDen Zwei Faktor Pin eingeben. \n{message}",
- "title": "Alexa Media Player - Zwei Faktor Authentifizierung"
- },
"user": {
"data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
"debug": "Erweitertes debugging",
"email": "Email Adresse",
"exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Eingebundene Geräte (komma getrennnt)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Passwort",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Sekunden zwischen den Scans",
"securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)",
"url": "Amazon Region (z.B., amazon.de)"
},
"description": "Bitte geben Sie ihre Informationen ein.",
"title": "Alexa Media Player - Konfiguration"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "Erweitertes debugging",
- "email": "Email Adresse",
- "exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)",
- "include_devices": "Eingebundene Geräte (komma getrennnt)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Passwort",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Sekunden zwischen den Scans",
- "securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)",
- "url": "Amazon Region (z.B., amazon.de)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/translations/en.json b/custom_components/alexa_media/translations/en.json
index f52044a0..216f8f32 100644
--- a/custom_components/alexa_media/translations/en.json
+++ b/custom_components/alexa_media/translations/en.json
@@ -14,38 +14,10 @@
"unknown_error": "Unknown error, please enable advanced debugging and report log info"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}",
- "title": "Alexa Media Player - Action Required"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP method",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - One Time Password"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Password",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA Code (recommended to avoid login issues)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Verification method",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}",
- "title": "Alexa Media Player - Verification Method"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA Code"
- },
- "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)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Included device (comma separated)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Password",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Seconds between scans",
"securitycode": "2FA Code (recommended to avoid login issues)",
"url": "Amazon region domain (e.g., amazon.co.uk)"
},
- "description": "Please confirm the information below. For legacy configuration, disable `Use Login Proxy method` option.",
+ "description": "Please confirm the information below.",
"title": "Alexa Media Player - Configuration"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Cookies.txt data",
- "debug": "Advanced debugging",
- "email": "Email Address",
- "exclude_devices": "Excluded device (comma separated)",
- "include_devices": "Included device (comma separated)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Password",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Seconds between scans",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "url": "Amazon region domain (e.g., amazon.co.uk)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/translations/es.json b/custom_components/alexa_media/translations/es.json
index d1ddc7a7..644bc547 100644
--- a/custom_components/alexa_media/translations/es.json
+++ b/custom_components/alexa_media/translations/es.json
@@ -11,41 +11,13 @@
"hass_url_invalid": "No se puede conectar a la url de Home Assistant. Compruebe la dirección URL externa en Configuración -> General",
"identifier_exists": "Correo electrónico para la URL de Alexa ya registrado",
"invalid_credentials": "Credenciales no válidas",
- "unknown_error": "Error desconocido, por favor revisa los registros en Home Assistant y reporta el error si es necesario."
+ "unknown_error": "Error desconocido, habilite la depuración avanzada e informe la información de registro"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon enviará una notificación a tu dispositivo vinculado. Completa los pasos descritos antes de continuar.\n{message}",
- "title": "Alexa Media Player - Acción requerida"
- },
- "authselect": {
- "data": {
- "authselectoption": "Clave OTP",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Contraseña de un solo uso"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Contraseña",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Método de verificación",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nSeleccione el método de verificación por número. (e.g., `0` or `1`) \n{message}",
- "title": "Alexa Media Player - Método de verificación"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA Code"
- },
- "description": "**{email} - alexa.{url}** \nIngrese la contraseña de un solo uso (OTP). \n{message}",
- "title": "Alexa Media Player - Autenticación de dos factores"
- },
"user": {
"data": {
- "cookies_txt": "Datos de Cookies.txt",
"debug": "Depuración avanzada",
"email": "Dirección de correo electrónico",
"exclude_devices": "Dispositivo excluido (separado por comas)",
- "hass_url": "Url to access Home Assistant",
+ "hass_url": "Url para acceder a Home Assistant",
"include_devices": "Dispositivo incluido (separado por comas)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Contraseña",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Segundos entre escaneos",
"securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)",
"url": "Región del dominio de Amazon (por ejemplo, amazon.es)"
},
- "description": "Confirme la siguiente información. Para la configuración heredada, desactive la opción `Usar método de proxy de inicio de sesión`.",
+ "description": "Confirme la siguiente información.",
"title": "Alexa Media Player - Configuración"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Datos de Cookies.txt",
- "debug": "Depuración avanzada",
- "email": "Dirección de correo electrónico",
- "exclude_devices": "Dispositivo excluido (separado por comas)",
- "include_devices": "Dispositivo incluido (separado por comas)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Contraseña",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Segundos entre escaneos",
- "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)",
- "url": "Región del dominio de Amazon (por ejemplo, amazon.es)"
- },
- "description": "Por favor introduce tu [información](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **El método más rápido es [Importar cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import).** \n**ADVERTENCIA: Amazon informará 'Introduce un correo electrónico o número de teléfono válido' si tu cuenta utiliza [códigos 2FA - Segundo Factor de Autenticación](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}",
- "title": "Alexa Media Player - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/translations/fr.json b/custom_components/alexa_media/translations/fr.json
index 6d73c4e9..f466b335 100644
--- a/custom_components/alexa_media/translations/fr.json
+++ b/custom_components/alexa_media/translations/fr.json
@@ -14,38 +14,10 @@
"unknown_error": "Erreur inconnue, veuillez signaler les informations du journal"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)"
- },
- "description": "** {email} - alexa. {url} ** \n Amazon enverra une notification push conformément au message ci-dessous. Veuillez répondre complètement avant de continuer. \n {message}",
- "title": "Alexa Media Player - Action requise"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP méthode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Mot de passe unique"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Mot de passe",
- "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)",
"securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Méthode de vérification",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nVeuillez sélectionner la méthode de vérification par numéro. (exemple., `0` ou `1`) \n{message}",
- "title": "Alexa Media Player - Méthode de vérification"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "** {email} - alexa. {url} **\n Avez-vous confirmé avec succès un OTP à partir de la clé d'application 2FA intégrée avec Amazon?\n > Code OTP {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)",
- "securitycode": "2FA Code"
- },
- "description": "**{email} - alexa.{url}** \nEntrez le mot de passe unique (OTP). \n{message}",
- "title": "Alexa Media Player - Authentification à deux facteurs"
- },
"user": {
"data": {
- "cookies_txt": "Données cookies.txt",
"debug": "Débogage avancé",
"email": "Adresse Email",
"exclude_devices": "Appareil exclu (séparé par des virgules)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Appareil inclus (séparé par des virgules)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)",
"password": "Mot de passe",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Secondes entre les analyses",
"securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)",
"url": "Domaine de la région Amazon (exemple, amazon.fr)"
},
- "description": "Veuillez confirmer les informations ci-dessous. Pour la configuration héritée, désactivez l'option `Utiliser la méthode proxy de connexion`.",
+ "description": "Veuillez confirmer les informations ci-dessous.",
"title": "Alexa Media Player - Configuration"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Données cookies.txt",
- "debug": "Débogage avancé",
- "email": "Adresse Email",
- "exclude_devices": "Appareil exclu (séparé par des virgules)",
- "include_devices": "Appareil inclus (séparé par des virgules)",
- "oauth_login": "Activer la méthode d'application oauth-token",
- "otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)",
- "password": "Mot de passe",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Secondes entre les analyses",
- "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)",
- "url": "Domaine de la région Amazon (exemple, amazon.fr)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/translations/it.json b/custom_components/alexa_media/translations/it.json
index f48012a9..318c7b5a 100644
--- a/custom_components/alexa_media/translations/it.json
+++ b/custom_components/alexa_media/translations/it.json
@@ -14,38 +14,10 @@
"unknown_error": "Errore sconosciuto, si prega di abilitare il debug avanzato e riportare i log informativi"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)"
- },
- "description": "**{email} - alexa. {url}**\n Amazon invierà una notifica push per il seguente messaggio. Si prega di rispondere completamente prima di continuare.\n {message}",
- "title": "Alexa Media Player - Azione Richiesta"
- },
- "authselect": {
- "data": {
- "authselectoption": "Metodo password usa e getta (OTP)",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)"
- },
- "description": "**{email} - alexa.{url}**\n{message}",
- "title": "Alexa Media Player - Password Usa e Getta (One Time Password)"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Password",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)",
"securitycode": "Codice 2FA (raccomandato per evitare problemi di login)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Metodo di verifica",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)"
- },
- "description": "**{email} - alexa.{url}**\nSelezionare un metodo di verifica con un numero. (ad es., `0` o `1`)\n{message}",
- "title": "Alexa Media Player - Metodo di verifica"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}**\nHai confermato con successo una chiave usa e getta (OTP) dall'applicazione 2FA integrata con Amazon?\n>Codice OTP {message}",
"title": "Alexa Media Player - Conferma OTP"
},
- "twofactor": {
- "data": {
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)",
- "securitycode": "Codice autenticazione a due fattori (2FA)"
- },
- "description": "**{email} - alexa.{url}** \nInserisci la password usa e getta (OTP). \n{message}",
- "title": "Alexa Media Player - Autenticazione a Due Fattori"
- },
"user": {
"data": {
- "cookies_txt": "Dati cookies.txt",
"debug": "Debug avanzato",
"email": "Indirizzo email",
"exclude_devices": "Dispositivi da escludere (separati da virgola)",
"hass_url": "URL per accedere a Home Assistant",
"include_devices": "Dispositivi da includere (separati da virgola)",
- "oauth_login": "Abilita metodo app oauth-token",
"otp_secret": "Chiave dell'app 2FA integrata (generazione automatica di codici 2FA)",
"password": "Password",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)",
"scan_interval": "Tempo in secondi fra le scansioni",
"securitycode": "Codice 2FA (raccomandato per evitare problemi di login)",
"url": "Regione del dominio Amazon (ad es., amazon.it)"
},
- "description": "Confermare le informazioni sottostanti. Per le vecchie configurazioni, disabilitare l'opzione `Usa Proxy come metodo di login`.",
+ "description": "Confermare le informazioni sottostanti.",
"title": "Alexa Media Player - Configurazione"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Dati cookies.txt",
- "debug": "Debug avanzato",
- "email": "Indirizzo email",
- "exclude_devices": "Dispositivi da escludere (separati da virgola)",
- "include_devices": "Dispositivi da includere (separati da virgola)",
- "oauth_login": "Abilitare il metodo oauth-token app",
- "otp_secret": "Chiave 2FA integrata (genera automaticamente i codici 2FA)",
- "password": "Password",
- "proxy": "Usa metodo Proxy per il login (2FA non richiesto)",
- "scan_interval": "Tempo in secondi fra le scansioni",
- "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)",
- "url": "Regione del dominio Amazon (ad es., amazon.it)"
- },
- "description": "Prego inserisci le tue [informazioni](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) potrebbe essere più semplice!** \n**ATTENZIONE: Amazon risponde incorrettamente 'Inserire una mail valida o un numero di telefono' quando è richiesto il codice 2FA](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}",
- "title": "Alexa Media Player - Vecchia configurazione"
}
}
},
diff --git a/custom_components/alexa_media/translations/nb.json b/custom_components/alexa_media/translations/nb.json
index 333797c9..87f5b062 100644
--- a/custom_components/alexa_media/translations/nb.json
+++ b/custom_components/alexa_media/translations/nb.json
@@ -14,38 +14,10 @@
"unknown_error": "Ukjent feil, vennligst rapporter logginfo"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon vil sende et push-varsel i henhold til meldingen nedenfor. Fullfør svaret før du fortsetter. \n{message}",
- "title": "Alexa Media Player - Handling påkrevd"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP-metode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Engangspassord"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Passord",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)"
- },
- "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Bekreftelsesmetode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nVelg bekreftelsesmetode etter nummer. (f.eks. \"0\" eller \"1\") \n{message}",
- "title": "Alexa Media Player - Bekreftelsesmetode"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA Kode"
- },
- "description": "**{email} - alexa.{url}** \nSkriv inn engangspassordet (OTP). \n>{message}",
- "title": "Alexa Media Player - Tofaktorautentisering"
- },
"user": {
"data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
"debug": "Avansert feilsøking",
"email": "Epostadresse",
"exclude_devices": "Ekskludert enhet (kommaseparert)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Inkluder enhet (kommaseparert)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Passord",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Sekunder mellom skanninger",
"securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)",
"url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)"
},
- "description": "Bekreft informasjonen nedenfor. For eldre konfigurasjon, deaktiver alternativet \"Bruk innlogging proxy-metode\".",
+ "description": "Bekreft informasjonen nedenfor.",
"title": "Alexa Media Player - Konfigurasjon"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "Avansert feilsøking",
- "email": "Epostadresse",
- "exclude_devices": "Ekskludert enhet (kommaseparert)",
- "include_devices": "Inkluder enhet (kommaseparert)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Passord",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Sekunder mellom skanninger",
- "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)",
- "url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/translations/nl.json b/custom_components/alexa_media/translations/nl.json
index 9a4afdbe..77558398 100644
--- a/custom_components/alexa_media/translations/nl.json
+++ b/custom_components/alexa_media/translations/nl.json
@@ -14,38 +14,10 @@
"unknown_error": "Onbekende fout, meld de loggegevens"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}",
- "title": "Alexa Media Player - Action Required"
- },
- "authselect": {
- "data": {
- "authselectoption": "Eenmalige Pincode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Eenmalige Pincode"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Paswoord",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "config::step::captcha::data::securitycode"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Verificatiemethode",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nSelecteer de verificatiemethode (bv.`0` of `1`) \n{message}",
- "title": "Alexa Media Player - Verificatiemethode"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "Verificatiecode"
- },
- "description": "**{email} - alexa.{url}** \nGeef de verificatiecode in. \n{message}",
- "title": "Alexa Media Player - Tweestapsverificatie"
- },
"user": {
"data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
"debug": "Geavanceerd debuggen",
"email": "E-mailadres",
"exclude_devices": "Apparaten uitsluiten (Scheiding: komma)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Apparaten toevoegen (Scheiding: komma)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Paswoord",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Aantal seconden tussen scans",
"securitycode": "2FA Code (recommended to avoid login issues)",
"url": "Domeinnaam van Amazon regio (bv.amazon.co.uk)"
},
"description": "Vul je gegevens in a.u.b.",
"title": "Alexa Media Player - Configuratie"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "Geavanceerd debuggen",
- "email": "E-mailadres",
- "exclude_devices": "Apparaten uitsluiten (Scheiding: komma)",
- "include_devices": "Apparaten toevoegen (Scheiding: komma)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Paswoord",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Aantal seconden tussen scans",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "url": "Domeinnaam van Amazon regio (bv.amazon.co.uk)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/translations/pl.json b/custom_components/alexa_media/translations/pl.json
index e0121412..fcb2c462 100644
--- a/custom_components/alexa_media/translations/pl.json
+++ b/custom_components/alexa_media/translations/pl.json
@@ -14,38 +14,10 @@
"unknown_error": "Nieznany błąd, włącz zaawansowane debugowanie i zgłoś log z tego zdarzenia"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon wyśle powiadomienie push zgodnie z poniższą wiadomością. Przed kontynuowaniem odpowiedz na wiadomość. \n{message}",
- "title": "Alexa Media Player — wymagane działanie"
- },
- "authselect": {
- "data": {
- "authselectoption": "Metoda OTP",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player — hasło jednorazowe"
- },
"captcha": {
"data": {
- "captcha": "Kod Captcha",
- "password": "Hasło",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)",
"securitycode": "Kod uwierzytelniania dwuskładnikowego"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player — kod Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Metoda weryfikacji",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)"
- },
- "description": "**{email} - alexa.{url}** \nWybierz metodę weryfikacji. (np., `0` lub `1`) \n{message}",
- "title": "Alexa Media Player — metoda weryfikacji"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nCzy pomyślnie potwierdziłeś hasło jednorazowe z wbudowanej aplikacji uwierzytelniania dwuskładnikowego z Amazonu? \n >Kod hasła jednorazowego {message}",
"title": "Alexa Media Player - Potwierdzanie hasła jednorazowego"
},
- "twofactor": {
- "data": {
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)",
- "securitycode": "Kod uwierzytelniania dwuskładnikowego"
- },
- "description": "**{email} - alexa.{url}** \nWprowadź hasło jednorazowe (OTP). \n{message}",
- "title": "Alexa Media Player — uwierzytelnianie dwuskładnikowe"
- },
"user": {
"data": {
- "cookies_txt": "zawartość pliku cookies.txt",
"debug": "Zaawansowane debugowanie",
"email": "Adres email",
"exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)",
"hass_url": "URL dostępu do Home Assistanta",
"include_devices": "Dodawane urządzenia (oddzielone przecinkami)",
- "oauth_login": "Włącz metodę tokena OAuth aplikacji",
"otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)",
"password": "Hasło",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)",
"scan_interval": "Interwał skanowania (sekundy)",
"securitycode": "Kod uwierzytelniania dwuskładnikowego (zalecany w celu uniknięcia problemów z logowaniem)",
"url": "Region/domena Amazon (np. amazon.co.uk)"
},
- "description": "Potwierdź poniższe informacje. W przypadku starszych konfiguracji wyłącz opcję 'Użyj metody logowania proxy'.",
+ "description": "Potwierdź poniższe informacje.",
"title": "Alexa Media Player — konfiguracja"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "zawartość pliku cookies.txt",
- "debug": "Zaawansowane debugowanie",
- "email": "Adres e-mail",
- "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)",
- "include_devices": "Dodawane urządzenia (oddzielone przecinkami)",
- "oauth_login": "Włącz metodę tokena OAuth aplikacji",
- "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)",
- "password": "Hasło",
- "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)",
- "scan_interval": "Interwał skanowania (sekundy)",
- "securitycode": "Kod uwierzytelniania dwuskładnikowego",
- "url": "Region/domena Amazon (np. amazon.co.uk)"
- },
- "description": "Wprowadź [dane](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Import pliku Cookie](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) może być najłatwiejszą metodą!** \n**OSTRZEŻENIE: Amazon nieprawidłowo zgłasza 'Wprowadź prawidłowy adres e-mail lub numer telefonu komórkowego', gdy wymagany jest [kod uwierzytelniania dwuskładnikowego](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}",
- "title": "Alexa Media Player — starsza konfiguracja"
}
}
},
diff --git a/custom_components/alexa_media/translations/pt-BR.json b/custom_components/alexa_media/translations/pt-BR.json
index 1f3897e2..6d4b4804 100644
--- a/custom_components/alexa_media/translations/pt-BR.json
+++ b/custom_components/alexa_media/translations/pt-BR.json
@@ -14,38 +14,10 @@
"unknown_error": "Erro desconhecido, favor habilitar depuração avançada e informações de registro de relatório"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Usar método Login Proxy (Não requer 2FA)"
- },
- "description": "** {email} - alexa. {url} **\n A Amazon enviará uma notificação por push de acordo com a mensagem abaixo. Por favor, responda completamente antes de continuar.\n {message}",
- "title": "Alexa Media Player - Ação necessária"
- },
- "authselect": {
- "data": {
- "authselectoption": "Método OTP",
- "proxy": "Usar método Login Proxy (Não requer 2FA)"
- },
- "description": "**{email} - alexa.{url}**\n{message}",
- "title": "Alexa Media Player - Senha de uso único"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Senha",
- "proxy": "Usar método Login Proxy (Não requer 2FA)",
"securitycode": "Código 2FA (recomendado para evitar problemas de login)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Método de verificação",
- "proxy": "Usar método Login Proxy (Não requer 2FA)"
- },
- "description": "** {email} - alexa. {url} **\n Selecione o método de verificação por número. (por exemplo, `0` ou `1`)\n {message}",
- "title": "Alexa Media Player - Método de verificação"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "** {email} - alexa. {url} **\n Você confirmou com sucesso um OTP da chave de aplicativo 2FA integrada com a Amazon?\n > Código OTP {message}",
"title": "Alexa Media Player - Confirmação OTP"
},
- "twofactor": {
- "data": {
- "proxy": "Usar método Login Proxy (Não requer 2FA)",
- "securitycode": "Código 2FA"
- },
- "description": "** {email} - alexa. {url} **\n Digite a senha de uso único (OTP).\n {message}",
- "title": "Alexa Media Player - Autenticação de dois fatores"
- },
"user": {
"data": {
- "cookies_txt": "Cookies.txt data",
"debug": "Depuração avançada",
"email": "Endereço de e-mail",
"exclude_devices": "Dispositivos excluídos (separado por vírgula)",
"hass_url": "Url para acesso ao Home Assistant",
"include_devices": "Dispositivos incluídos (separado por vírgula)",
- "oauth_login": "Habilitar o aplicativo para método auth-token",
"otp_secret": "Chave de aplicativo 2FA integrada (gerar automaticamente códigos 2FA)",
"password": "Senha",
- "proxy": "Usar método Login Proxy (Não requer 2FA)",
"scan_interval": "Segundos entre varreduras",
"securitycode": "Código 2FA (recomendado para evitar problemas de login)",
"url": "Domínio regional da Amazon (ex: amazon.co.uk)"
},
- "description": "Por favor, confirme as informações abaixo. Para configuração legada, desative a opção `Usar método de proxy de login`.",
+ "description": "Por favor, confirme as informações abaixo.",
"title": "Alexa Media Player - Configurações"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Cookies.txt data",
- "debug": "Depuração avançada",
- "email": "Endereço de e-mail",
- "exclude_devices": "Dispositivos excluídos (separado por vírgula)",
- "include_devices": "Dispositivos incluídos (separado por vírgula)",
- "oauth_login": "Habilitar o aplicativo para método auth-token",
- "otp_secret": "Chave de aplicativo 2FA integrada (gerar automaticamente códigos 2FA)",
- "password": "Senha",
- "proxy": "Usar método Login Proxy (Não requer 2FA)",
- "scan_interval": "Segundos entre escaneamentos",
- "securitycode": "Código 2FA (recomendado para evitar problemas de login)",
- "url": "Domínio regional da Amazon (ex: amazon.co.uk)"
- },
- "description": "Insira suas [informações](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Importação de cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) pode ser mais fácil!**\n **AVISO: A Amazon informa incorretamente 'Digite um e-mail ou número de celular válido' quando [o código 2FA é necessário](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication- para-sua-conta-amazon).**\n > {message}",
- "title": "Alexa Media Player - Configurações legado"
}
}
},
diff --git a/custom_components/alexa_media/translations/pt.json b/custom_components/alexa_media/translations/pt.json
index 5cf76c7d..0ea685b6 100644
--- a/custom_components/alexa_media/translations/pt.json
+++ b/custom_components/alexa_media/translations/pt.json
@@ -14,38 +14,10 @@
"unknown_error": "Erro desconhecido, por favor habilite depuração avançada e informações de log de relatório"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Usar método de proxy de login (2FA não é necessário)"
- },
- "description": "**{email} - alexa.{url}** \nA Amazon enviará uma notificação push de acordo com a mensagem abaixo. Por favor, responda completamente antes de continuar. \n{message}",
- "title": "Alexa Media Player - Ação Necessária"
- },
- "authselect": {
- "data": {
- "authselectoption": "Método OTP (senha de uso único)",
- "proxy": "Usar método de proxy de login (2FA não é necessário)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Senha de uso único"
- },
"captcha": {
"data": {
- "captcha": "Captcha",
- "password": "Senha",
- "proxy": "Usar método de proxy de login (2FA não é necessário)",
"securitycode": "Código 2FA (recomendado para evitar problemas de login)"
- },
- "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}",
- "title": "Alexa Media Player - Captcha"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Método de verificação",
- "proxy": "Usar método de proxy de login (2FA não é necessário)"
- },
- "description": "**{email} - alexa.{url}** \nPor favor, selecione o método de verificação por número. (ex. '0' ou '1') \n{message}",
- "title": "Alexa Media Player - Método de verificação"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "** {email} - alexa. {url} **\n Você confirmou com sucesso uma senha de uso único na aplicação 2FA integrada com a Amazon?\n > Código OTP {message}",
"title": "Alexa Media Player - Confirmação OTP"
},
- "twofactor": {
- "data": {
- "proxy": "Usar método de proxy de login (2FA não é necessário)",
- "securitycode": "Código 2FA"
- },
- "description": "**{email} - alexa.{url}** \nDigite a senha de uso único (OTP). \n{message}",
- "title": "Alexa Media Player - autenticação de dois fatores"
- },
"user": {
"data": {
- "cookies_txt": "Dados de cookies.txt",
"debug": "Depuração avançada",
"email": "Endereço de e-mail",
"exclude_devices": "Dispositivo excluído (separado por vírgula)",
"hass_url": "URL para aceder o Home Assistant",
"include_devices": "Dispositivo incluído (separado por vírgula)",
- "oauth_login": "Habilitar método de aplicativo oauth-token",
"otp_secret": "Chave de aplicativo 2FA integrada (gerar códigos 2FA automaticamente)",
"password": "Senha",
- "proxy": "Usar método de proxy de login (2FA não é necessário)",
"scan_interval": "Segundos entre análises",
"securitycode": "Código 2FA (recomendado para evitar problemas de login)",
"url": "Região do domínio Amazon (ex. amazon.com.br)"
},
- "description": "Por favor, confirme as informações abaixo. Para configuração de compatibilidade, desative a opção 'Usar método de proxy de login'.",
+ "description": "Por favor, confirme as informações abaixo.",
"title": "Alexa Media Player - Configuração"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "Dados de cookies.txt",
- "debug": "Depuração avançada",
- "email": "Endereço de e-mail",
- "exclude_devices": "Dispositivo excluído (separado por vírgula)",
- "include_devices": "Dispositivo incluído (separado por vírgula)",
- "oauth_login": "Habilitar método de aplicativo oauth-token",
- "otp_secret": "Chave de aplicativo 2FA integrada (gerar códigos 2FA automaticamente)",
- "password": "Senha",
- "proxy": "Usar método de proxy de login (2FA não é necessário)",
- "scan_interval": "Segundos entre análises",
- "securitycode": "Código 2FA (recomendado para evitar problemas de login)",
- "url": "Região do domínio Amazon (ex. amazon.com.br)"
- },
- "description": "Por favor, introduza a sua [informação](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) pode ser mais fácil!** \n**Aviso: a Amazon informa incorretamente 'Insira um e-mail ou número de celular válido' quando [2FA Code é necessário](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}",
- "title": "Alexa Media Player - Configuração de Compatibilidade"
}
}
},
diff --git a/custom_components/alexa_media/translations/pt_BR.json b/custom_components/alexa_media/translations/pt_BR.json
index 2696cc29..2e8d00b0 100644
--- a/custom_components/alexa_media/translations/pt_BR.json
+++ b/custom_components/alexa_media/translations/pt_BR.json
@@ -8,7 +8,7 @@
"error": {
"2fa_key_invalid": "Chave integrada 2FA inválida",
"connection_error": "Erro de conexão; Verifique a sua conexão e tente novamente",
- "hass_url_invalid": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL externa em Configuração -> Geral",
+ "unable_to_connect_hass_url": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL externa em Configuração -> Geral",
"identifier_exists": "Email para URL Alexa já registrado",
"invalid_credentials": "Credenciais inválidas",
"unknown_error": "Erro desconhecido, favor habilitar a depuração avançada e reporte as informações de registro"
diff --git a/custom_components/alexa_media/translations/pt_PT.json b/custom_components/alexa_media/translations/pt_PT.json
index 5cf76c7d..2d7b4382 100644
--- a/custom_components/alexa_media/translations/pt_PT.json
+++ b/custom_components/alexa_media/translations/pt_PT.json
@@ -8,7 +8,7 @@
"error": {
"2fa_key_invalid": "Chave 2FA integrada inválida",
"connection_error": "Erro ao conectar; verifique a rede e tente novamente",
- "hass_url_invalid": "Não foi possível conectar ao URL do Home Assistant. Verifique o URL externo em Configuração - > Geral",
+ "unable_to_connect_hass_url": "Não foi possível conectar ao URL do Home Assistant. Verifique o URL externo em Configuração - > Geral",
"identifier_exists": "E-mail para URL Alexa já registado",
"invalid_credentials": "Credenciais inválidas",
"unknown_error": "Erro desconhecido, por favor habilite depuração avançada e informações de log de relatório"
diff --git a/custom_components/alexa_media/translations/ru.json b/custom_components/alexa_media/translations/ru.json
index ee10f828..e6aa3c69 100644
--- a/custom_components/alexa_media/translations/ru.json
+++ b/custom_components/alexa_media/translations/ru.json
@@ -14,38 +14,10 @@
"unknown_error": "Неизвестная ошибка, пожалуйста, сообщите информацию журнала"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa. {url}**\nAmazon отправит push-уведомление в соответствии с нижеприведенным сообщением. Пожалуйста, полностью ответьте, прежде чем продолжить.\n{message}",
- "title": "Alexa Media Player - Требуемое действие"
- },
- "authselect": {
- "data": {
- "authselectoption": "Одноразовый пароль",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \n{message}",
- "title": "Alexa Media Player - Одноразовый Пароль"
- },
"captcha": {
"data": {
- "captcha": "Капча",
- "password": "Пароль",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA Code (recommended to avoid login issues)"
- },
- "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}",
- "title": "Alexa Media Player - Капча"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "Метод проверки",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}**\nПожалуйста, выберите способ проверки по номеру. (например, \" 0 \" или \" 1`)\n{message}",
- "title": "Alexa Media Player - Метод проверки"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2-факторная авторизация"
- },
- "description": "**{email} - alexa.{url}**\nВведите одноразовый пароль.\n{message}",
- "title": "Alexa Media Player - Двух факторная идентификация"
- },
"user": {
"data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
"debug": "Расширенные возможности отладки",
"email": "Адрес электронной почты",
"exclude_devices": "Исключенные устройства (через запятую)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Включенные устройства (разделенное запятыми)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "Пароль",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Секунды между сканированиями",
"securitycode": "2FA Code (recommended to avoid login issues)",
"url": "Домен региона Amazon (например, amazon.co.uk)"
},
- "description": "Пожалуйста, подтвердите информацию ниже. Для старой конфигурации отключите опцию `Use Login Proxy method`.",
+ "description": "Пожалуйста, подтвердите информацию ниже.",
"title": "Alexa Media Player - Конфигурация"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "Расширенные возможности отладки",
- "email": "Адрес электронной почты",
- "exclude_devices": "Исключенные устройства (через запятую)",
- "include_devices": "Включенные устройства (разделенное запятыми)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "Пароль",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Секунды между сканированиями",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "url": "Домен региона Amazon (например, amazon.co.uk)"
- },
- "description": "Пожалуйста, введите свои данные.{message}",
- "title": "Alexa Media Player - Legacy Configuration"
}
}
},
diff --git a/custom_components/alexa_media/translations/zh-Hans.json b/custom_components/alexa_media/translations/zh-Hans.json
index 4af369e3..2658d0e6 100644
--- a/custom_components/alexa_media/translations/zh-Hans.json
+++ b/custom_components/alexa_media/translations/zh-Hans.json
@@ -14,38 +14,10 @@
"unknown_error": "Unknown error, please report log info"
},
"step": {
- "action_required": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}",
- "title": "Alexa Media Player - Action Required"
- },
- "authselect": {
- "data": {
- "authselectoption": "OTP方式",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}**\n{message}",
- "title": "Alexa Media Player - 一次性密码"
- },
"captcha": {
"data": {
- "captcha": "验证码",
- "password": "密码",
- "proxy": "Use Login Proxy method (2FA not required)",
"securitycode": "2FA Code (recommended to avoid login issues)"
- },
- "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}",
- "title": "Alexa Media Player-验证码"
- },
- "claimspicker": {
- "data": {
- "authselectoption": "验证方式",
- "proxy": "Use Login Proxy method (2FA not required)"
- },
- "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}",
- "title": "Alexa Media Player - 验证方法"
+ }
},
"totp_register": {
"data": {
@@ -54,50 +26,21 @@
"description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}",
"title": "Alexa Media Player - OTP Confirmation"
},
- "twofactor": {
- "data": {
- "proxy": "Use Login Proxy method (2FA not required)",
- "securitycode": "2FA代码"
- },
- "description": "**{email} - Alexa.{url} ** \n输入一次性密码(OTP)。 \n {message}",
- "title": "Alexa Media Player - Two Factor Authentication"
- },
"user": {
"data": {
- "cookies_txt": "Cookie.txt数据",
"debug": "高级调试",
"email": "电子邮件地址",
"exclude_devices": "Excluded device (comma separated)",
"hass_url": "Url to access Home Assistant",
"include_devices": "Included device (comma separated)",
- "oauth_login": "Enable oauth-token app method",
"otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
"password": "密码",
- "proxy": "Use Login Proxy method (2FA not required)",
"scan_interval": "Seconds between scans",
"securitycode": "2FA Code (recommended to avoid login issues)",
"url": "Amazon region domain (e.g., amazon.co.uk)"
},
- "description": "请确认以下信息。对于旧版配置,请禁用“使用登录代理方法”选项。",
+ "description": "请确认以下信息。",
"title": "Alexa Media Player-配置"
- },
- "user_legacy": {
- "data": {
- "cookies_txt": "config::step::user::data::cookies_txt",
- "debug": "高级调试",
- "email": "电子邮件地址",
- "exclude_devices": "Excluded device (comma separated)",
- "include_devices": "Included device (comma separated)",
- "oauth_login": "Enable oauth-token app method",
- "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)",
- "password": "密码",
- "proxy": "Use Login Proxy method (2FA not required)",
- "scan_interval": "Seconds between scans",
- "securitycode": "2FA Code (recommended to avoid login issues)",
- "url": "Amazon region domain (e.g., amazon.co.uk)"
- },
- "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 - Legacy Configuration"
}
}
},
diff --git a/custom_components/garbage_collection/__init__.py b/custom_components/garbage_collection/__init__.py
index 238475ae..239054a8 100644
--- a/custom_components/garbage_collection/__init__.py
+++ b/custom_components/garbage_collection/__init__.py
@@ -5,24 +5,18 @@
import logging
from datetime import timedelta
from types import MappingProxyType
-from typing import Any
+from typing import Any, Dict
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
import voluptuous as vol
from dateutil.relativedelta import relativedelta
-from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
-from homeassistant.const import (
- ATTR_HIDDEN,
- CONF_ENTITIES,
- CONF_ENTITY_ID,
- CONF_NAME,
- WEEKDAYS,
-)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import ATTR_HIDDEN, CONF_ENTITIES, CONF_ENTITY_ID, WEEKDAYS
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.typing import ConfigType
-from . import const, helpers, sensor
+from . import const, helpers
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
@@ -52,7 +46,7 @@
cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(min=1, max=5))]
),
vol.Optional(const.CONF_PERIOD): vol.All(
- vol.Coerce(int), vol.Range(min=1, max=365)
+ vol.Coerce(int), vol.Range(min=1, max=1000)
),
vol.Optional(const.CONF_FIRST_WEEK): vol.All(
vol.Coerce(int), vol.Range(min=1, max=52)
@@ -104,17 +98,13 @@
)
-async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
- """Set up this component using YAML."""
+async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
+ """Set up platform - register services, inicialize data structure."""
async def handle_add_date(call: ServiceCall) -> None:
"""Handle the add_date service call."""
- if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])):
- _LOGGER.error("add_date - missing Entity ID.")
- return
- if (collection_date := call.data.get(const.CONF_DATE)) is None:
- _LOGGER.error("add_date - missing collection date.")
- return
+ entity_ids = call.data.get(CONF_ENTITY_ID, [])
+ collection_date = call.data.get(const.CONF_DATE)
for entity_id in entity_ids:
_LOGGER.debug("called add_date %s from %s", collection_date, entity_id)
try:
@@ -122,17 +112,16 @@ async def handle_add_date(call: ServiceCall) -> None:
await entity.add_date(collection_date)
except KeyError as err:
_LOGGER.error(
- "Failed adding date %s to %s (%s)", collection_date, entity_id, err
+ "Failed adding date %s to %s (%s)",
+ collection_date,
+ entity_id,
+ err,
)
async def handle_remove_date(call: ServiceCall) -> None:
"""Handle the remove_date service call."""
- if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])):
- _LOGGER.error("remove_date - missing Entity ID.")
- return
- if (collection_date := call.data.get(const.CONF_DATE)) is None:
- _LOGGER.error("remove_date - missing collection date.")
- return
+ entity_ids = call.data.get(CONF_ENTITY_ID, [])
+ collection_date = call.data.get(const.CONF_DATE)
for entity_id in entity_ids:
_LOGGER.debug("called remove_date %s from %s", collection_date, entity_id)
try:
@@ -140,9 +129,7 @@ async def handle_remove_date(call: ServiceCall) -> None:
await entity.remove_date(collection_date)
except KeyError as err:
_LOGGER.error(
- "Failed removing date %s from %s. Most likely, "
- "it was removed by competing automation runing in parallel. "
- "(%s)",
+ "Failed removing date %s from %s (%s)",
collection_date,
entity_id,
err,
@@ -150,15 +137,9 @@ async def handle_remove_date(call: ServiceCall) -> None:
async def handle_offset_date(call: ServiceCall) -> None:
"""Handle the offset_date service call."""
- if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])):
- _LOGGER.error("offset_date - missing Entity ID.")
- return
- if (offset := call.data.get(const.CONF_OFFSET)) is None:
- _LOGGER.error("offset_date - missing offset.")
- return
- if (collection_date := call.data.get(const.CONF_DATE)) is None:
- _LOGGER.error("offset_date - missing collection date.")
- return
+ entity_ids = call.data.get(CONF_ENTITY_ID, [])
+ offset = call.data.get(const.CONF_OFFSET)
+ collection_date = call.data.get(const.CONF_DATE)
for entity_id in entity_ids:
_LOGGER.debug(
"called offset_date %s by %d days for %s",
@@ -167,7 +148,9 @@ async def handle_offset_date(call: ServiceCall) -> None:
entity_id,
)
try:
- new_date = collection_date + relativedelta(days=offset)
+ new_date = collection_date + relativedelta(
+ days=offset
+ ) # pyright: reportOptionalOperand=false
entity = hass.data[const.DOMAIN][const.SENSOR_PLATFORM][entity_id]
await asyncio.gather(
entity.remove_date(collection_date), entity.add_date(new_date)
@@ -178,9 +161,7 @@ async def handle_offset_date(call: ServiceCall) -> None:
async def handle_update_state(call: ServiceCall) -> None:
"""Handle the update_state service call."""
- if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])):
- _LOGGER.error("update_state - missing Entity ID.")
- return
+ entity_ids = call.data.get(CONF_ENTITY_ID, [])
for entity_id in entity_ids:
_LOGGER.debug("called update_state for %s", entity_id)
try:
@@ -191,10 +172,8 @@ async def handle_update_state(call: ServiceCall) -> None:
async def handle_collect_garbage(call: ServiceCall) -> None:
"""Handle the collect_garbage service call."""
- if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])):
- _LOGGER.error("collect_garbage - missing Entity ID.")
- return
- last_collection = call.data.get(const.ATTR_LAST_COLLECTION, sensor.now())
+ entity_ids = call.data.get(CONF_ENTITY_ID, [])
+ last_collection = call.data.get(const.ATTR_LAST_COLLECTION, helpers.now())
for entity_id in entity_ids:
_LOGGER.debug("called collect_garbage for %s", entity_id)
try:
@@ -206,63 +185,32 @@ async def handle_collect_garbage(call: ServiceCall) -> None:
"Failed setting last collection for %s - %s", entity_id, err
)
- if const.DOMAIN not in hass.services.async_services():
- hass.services.async_register(
- const.DOMAIN,
- "collect_garbage",
- handle_collect_garbage,
- schema=COLLECT_NOW_SCHEMA,
- )
- hass.services.async_register(
- const.DOMAIN,
- "update_state",
- handle_update_state,
- schema=UPDATE_STATE_SCHEMA,
- )
- hass.services.async_register(
- const.DOMAIN, "add_date", handle_add_date, schema=ADD_REMOVE_DATE_SCHEMA
- )
- hass.services.async_register(
- const.DOMAIN,
- "remove_date",
- handle_remove_date,
- schema=ADD_REMOVE_DATE_SCHEMA,
- )
- hass.services.async_register(
- const.DOMAIN, "offset_date", handle_offset_date, schema=OFFSET_DATE_SCHEMA
- )
- else:
- _LOGGER.debug("Services already registered")
-
- if config.get(const.DOMAIN) is None:
- # We get here if the integration is set up using config flow
- return True
-
- # If platform is not enabled, skip.
- if not (platform_config := config[const.DOMAIN].get(const.CONF_SENSORS, {})):
- return False
-
- for entry in hass.config_entries.async_entries(const.DOMAIN):
- if entry.source == SOURCE_IMPORT:
- _LOGGER.error(
- "garbage_collection already imported. "
- "Remove it from configuration.yaml now!"
- )
- return True
- for entry in platform_config:
- _LOGGER.debug(
- "Importing %s(%s) from YAML configuration",
- entry[CONF_NAME],
- entry[const.CONF_FREQUENCY],
- )
- # Import YAML to ConfigFlow
- hass.async_create_task(
- hass.config_entries.flow.async_init(
- const.DOMAIN,
- context={"source": SOURCE_IMPORT},
- data=entry,
- )
- )
+ hass.data.setdefault(const.DOMAIN, {})
+ hass.data[const.DOMAIN].setdefault(const.SENSOR_PLATFORM, {})
+ hass.services.async_register(
+ const.DOMAIN,
+ "collect_garbage",
+ handle_collect_garbage,
+ schema=COLLECT_NOW_SCHEMA,
+ )
+ hass.services.async_register(
+ const.DOMAIN,
+ "update_state",
+ handle_update_state,
+ schema=UPDATE_STATE_SCHEMA,
+ )
+ hass.services.async_register(
+ const.DOMAIN, "add_date", handle_add_date, schema=ADD_REMOVE_DATE_SCHEMA
+ )
+ hass.services.async_register(
+ const.DOMAIN,
+ "remove_date",
+ handle_remove_date,
+ schema=ADD_REMOVE_DATE_SCHEMA,
+ )
+ hass.services.async_register(
+ const.DOMAIN, "offset_date", handle_offset_date, schema=OFFSET_DATE_SCHEMA
+ )
return True
@@ -271,10 +219,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
_LOGGER.debug(
"Setting %s (%s) from ConfigFlow",
config_entry.title,
- config_entry.data[const.CONF_FREQUENCY],
+ config_entry.options[const.CONF_FREQUENCY],
)
- # Backward compatibility - clean-up (can be removed later?)
- config_entry.options = MappingProxyType({})
config_entry.add_update_listener(update_listener)
# Add sensor
hass.async_create_task(
@@ -298,15 +244,15 @@ async def async_remove_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
pass
-async def async_migrate_entry(_, config_entry: ConfigEntry) -> bool:
+async def async_migrate_entry(_: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.info(
"Migrating %s from version %s", config_entry.title, config_entry.version
)
- new_data = {**config_entry.data}
- new_options = {**config_entry.options}
- removed_data: dict[str, Any] = {}
- removed_options: dict[str, Any] = {}
+ new_data: Dict[str, Any] = {**config_entry.data}
+ new_options: Dict[str, Any] = {**config_entry.options}
+ removed_data: Dict[str, Any] = {}
+ removed_options: Dict[str, Any] = {}
_LOGGER.debug("new_data %s", new_data)
_LOGGER.debug("new_options %s", new_options)
if config_entry.version == 1:
@@ -358,6 +304,40 @@ async def async_migrate_entry(_, config_entry: ConfigEntry) -> bool:
new_options[const.CONF_WEEKDAY_ORDER_NUMBER] = list(
map(str, new_options[const.CONF_WEEKDAY_ORDER_NUMBER])
)
+ if config_entry.version <= 5:
+ for conf in [
+ const.CONF_FREQUENCY,
+ const.CONF_ICON_NORMAL,
+ const.CONF_ICON_TODAY,
+ const.CONF_ICON_TOMORROW,
+ const.CONF_MANUAL,
+ const.CONF_OFFSET,
+ const.CONF_EXPIRE_AFTER,
+ const.CONF_VERBOSE_STATE,
+ const.CONF_FIRST_MONTH,
+ const.CONF_LAST_MONTH,
+ const.CONF_COLLECTION_DAYS,
+ const.CONF_WEEKDAY_ORDER_NUMBER,
+ const.CONF_FORCE_WEEK_NUMBERS,
+ const.CONF_WEEK_ORDER_NUMBER,
+ const.CONF_DATE,
+ const.CONF_PERIOD,
+ const.CONF_FIRST_WEEK,
+ const.CONF_FIRST_DATE,
+ const.CONF_SENSORS,
+ const.CONF_VERBOSE_FORMAT,
+ const.CONF_DATE_FORMAT,
+ ]:
+ if conf in new_data:
+ new_options[conf] = new_data.get(conf)
+ del new_data[conf]
+ if (
+ const.CONF_EXPIRE_AFTER in new_options
+ and len(new_options[const.CONF_EXPIRE_AFTER]) == 5
+ ):
+ new_options[const.CONF_EXPIRE_AFTER] = (
+ new_options[const.CONF_EXPIRE_AFTER] + ":00"
+ )
config_entry.version = const.CONFIG_VERSION
config_entry.data = MappingProxyType({**new_data})
config_entry.options = MappingProxyType({**new_options})
@@ -381,13 +361,8 @@ async def async_migrate_entry(_, config_entry: ConfigEntry) -> bool:
return True
-async def update_listener(hass, entry):
- """Update listener."""
- # The OptionsFlow saves data to options.
- # Move them back to data and clean options (dirty, but not sure how else to do that)
- if len(entry.options) > 0:
- entry.data = entry.options
- entry.options = {}
+async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
+ """Update listener - to re-create device after options update."""
await hass.config_entries.async_forward_entry_unload(entry, const.SENSOR_PLATFORM)
hass.async_add_job(
hass.config_entries.async_forward_entry_setup(entry, const.SENSOR_PLATFORM)
diff --git a/custom_components/garbage_collection/calendar.py b/custom_components/garbage_collection/calendar.py
index 29934396..591df505 100644
--- a/custom_components/garbage_collection/calendar.py
+++ b/custom_components/garbage_collection/calendar.py
@@ -4,7 +4,9 @@
from datetime import datetime, timedelta
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
+from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import Throttle
from .const import CALENDAR_NAME, CALENDAR_PLATFORM, DOMAIN, SENSOR_PLATFORM
@@ -12,14 +14,12 @@
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
-async def async_setup_platform(
- hass, config, async_add_entities, discovery_info=None
+async def async_setup_entry(
+ _: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
- """Add calendar entities to HA, of there are calendar instances."""
# pylint: disable=unused-argument
- # Only single instance allowed
- if not GarbageCollectionCalendar.instances:
- async_add_entities([GarbageCollectionCalendar()], True)
+ """Add calendar entity to HA."""
+ async_add_entities([GarbageCollectionCalendar()], True)
class GarbageCollectionCalendar(CalendarEntity):
@@ -30,7 +30,7 @@ class GarbageCollectionCalendar(CalendarEntity):
def __init__(self) -> None:
"""Create empty calendar."""
self._cal_data: dict = {}
- self._name = CALENDAR_NAME
+ self._attr_name = CALENDAR_NAME
GarbageCollectionCalendar.instances = True
@property
@@ -39,9 +39,9 @@ def event(self) -> CalendarEvent | None:
return self.hass.data[DOMAIN][CALENDAR_PLATFORM].event
@property
- def name(self) -> str:
+ def name(self) -> str | None:
"""Return the name of the entity."""
- return self._name
+ return self._attr_name
async def async_update(self) -> None:
"""Update all calendars."""
@@ -102,20 +102,25 @@ async def async_get_events(
continue
garbage_collection = hass.data[DOMAIN][SENSOR_PLATFORM][entity]
start = garbage_collection.get_next_date(start_date, True)
- while start is not None and start >= start_date and start <= end_date:
+ while start is not None and start_date <= start <= end_date:
try:
end = start + timedelta(days=1)
except TypeError:
end = start
+ name = (
+ garbage_collection.name
+ if garbage_collection.name is not None
+ else "Unknown"
+ )
if garbage_collection.expire_after is None:
event = CalendarEvent(
- summary=garbage_collection.name,
+ summary=name,
start=start,
end=end,
)
else:
event = CalendarEvent(
- summary=garbage_collection.name,
+ summary=name,
start=datetime.combine(start, datetime.min.time()),
end=datetime.combine(start, garbage_collection.expire_after),
)
diff --git a/custom_components/garbage_collection/config_flow.py b/custom_components/garbage_collection/config_flow.py
index 2919adb4..d9054a1c 100644
--- a/custom_components/garbage_collection/config_flow.py
+++ b/custom_components/garbage_collection/config_flow.py
@@ -2,356 +2,199 @@
from __future__ import annotations
import logging
-import uuid
-from collections import OrderedDict
-from typing import Any
+
+# import uuid
+from collections.abc import Mapping
+from typing import Any, Dict, cast
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
-from homeassistant import config_entries
from homeassistant.const import ATTR_HIDDEN, CONF_ENTITIES, CONF_NAME, WEEKDAYS
-from homeassistant.core import HomeAssistant, callback
+from homeassistant.core import callback
+from homeassistant.helpers import selector
+from homeassistant.helpers.schema_config_entry_flow import (
+ SchemaConfigFlowHandler,
+ SchemaFlowError,
+ SchemaFlowFormStep,
+ SchemaFlowMenuStep,
+ SchemaOptionsFlowHandler,
+)
from . import const, helpers
_LOGGER = logging.getLogger(__name__)
-class GarbageCollectionShared:
- """Store configuration for both YAML and config_flow."""
-
- def __init__(self, data):
- """Create class attributes and set initial values."""
- self._data = data.copy()
- self.hass: HomeAssistant | None = None
- self.name: str
- self.errors: dict = {}
- self.data_schema: OrderedDict = OrderedDict()
- self._defaults = {
- const.CONF_FREQUENCY: const.DEFAULT_FREQUENCY,
- const.CONF_ICON_NORMAL: const.DEFAULT_ICON_NORMAL,
- const.CONF_ICON_TODAY: const.DEFAULT_ICON_TODAY,
- const.CONF_ICON_TOMORROW: const.DEFAULT_ICON_TOMORROW,
- const.CONF_VERBOSE_STATE: const.DEFAULT_VERBOSE_STATE,
- ATTR_HIDDEN: False,
- const.CONF_MANUAL: False,
- const.CONF_FIRST_MONTH: const.DEFAULT_FIRST_MONTH,
- const.CONF_LAST_MONTH: const.DEFAULT_LAST_MONTH,
- const.CONF_PERIOD: const.DEFAULT_PERIOD,
- const.CONF_FIRST_WEEK: const.DEFAULT_FIRST_WEEK,
- const.CONF_VERBOSE_FORMAT: const.DEFAULT_VERBOSE_FORMAT,
- const.CONF_DATE_FORMAT: const.DEFAULT_DATE_FORMAT,
- }
-
- def update_data(self, user_input: dict) -> None:
- """Remove empty fields, and fields that should not be stored in the config."""
- self._data.update(user_input)
- for key, value in user_input.items():
- if value == "":
- del self._data[key]
- if CONF_NAME in self._data:
- self.name = self._data[CONF_NAME]
- del self._data[CONF_NAME]
-
- def required(self, key: str, options: dict | None) -> vol.Required:
- """Return vol.Required."""
- if isinstance(options, dict) and key in options:
- suggested_value = options[key]
- elif key in self._data:
- suggested_value = self._data[key]
- elif key in self._defaults:
- suggested_value = self._defaults[key]
- else:
- return vol.Required(key)
- return vol.Required(key, description={"suggested_value": suggested_value})
-
- def optional(self, key: str, options: dict | None) -> vol.Optional:
- """Return vol.Optional."""
- if isinstance(options, dict) and key in options:
- suggested_value = options[key]
- elif key in self._data:
- suggested_value = self._data[key]
- elif key in self._defaults:
- suggested_value = self._defaults[key]
- else:
- return vol.Optional(key)
- return vol.Optional(key, description={"suggested_value": suggested_value})
-
- def step1_frequency(self, user_input: dict | None, options: bool = False) -> bool:
- """Step 1 - choose frequency and common parameters."""
- self.errors.clear()
- if user_input is not None:
- try:
- cv.icon(
- user_input.get(const.CONF_ICON_NORMAL, const.DEFAULT_ICON_NORMAL)
- )
- cv.icon(user_input.get(const.CONF_ICON_TODAY, const.DEFAULT_ICON_TODAY))
- cv.icon(
- user_input.get(
- const.CONF_ICON_TOMORROW, const.DEFAULT_ICON_TOMORROW
- )
- )
- except vol.Invalid:
- self.errors["base"] = "icon"
- try:
- helpers.time_text(user_input.get(const.CONF_EXPIRE_AFTER))
- except vol.Invalid:
- self.errors["base"] = "time"
- if not self.errors:
- self.update_data(user_input)
- return True
- self.data_schema.clear()
- # Do not show name for Options_Flow. The name cannot be changed here
- if not options:
- self.data_schema[self.required(CONF_NAME, user_input)] = str
- self.data_schema[self.required(const.CONF_FREQUENCY, user_input)] = vol.In(
- const.FREQUENCY_OPTIONS
- )
- self.data_schema[self.optional(const.CONF_ICON_NORMAL, user_input)] = str
- self.data_schema[self.optional(const.CONF_ICON_TODAY, user_input)] = str
- self.data_schema[self.optional(const.CONF_ICON_TOMORROW, user_input)] = str
- self.data_schema[self.optional(const.CONF_EXPIRE_AFTER, user_input)] = str
- self.data_schema[self.optional(const.CONF_VERBOSE_STATE, user_input)] = bool
- self.data_schema[self.optional(ATTR_HIDDEN, user_input)] = bool
- self.data_schema[self.optional(const.CONF_MANUAL, user_input)] = bool
- return False
-
- def step2_detail(self, user_input: dict) -> bool:
- """Step 2 - enter detail that depend on frequency."""
- self.errors.clear()
- # Skip second step on blank frequency
- if self._data[
- const.CONF_FREQUENCY
- ] in const.BLANK_FREQUENCY and not self._data.get(
- const.CONF_VERBOSE_STATE, False
- ):
- return True
- if user_input is not None and user_input:
- # Validation
- if user_input.get(const.CONF_FREQUENCY) in const.ANNUAL_FREQUENCY:
- # "annual"
- try:
- helpers.month_day_text(user_input.get(const.CONF_DATE, ""))
- except vol.Invalid:
- self.errors["base"] = "month_day"
- if user_input.get(const.CONF_FREQUENCY) in const.DAILY_FREQUENCY:
- # "every-n-days"
- try:
- cv.date(user_input.get(const.CONF_FIRST_DATE, ""))
- except vol.Invalid:
- self.errors["base"] = "date"
- if not self.errors:
- self.update_data(user_input)
- return True
- self.data_schema.clear()
- # Build form
- if self._data[const.CONF_FREQUENCY] in const.ANNUAL_FREQUENCY:
- # "annual"
- self.data_schema[self.required(const.CONF_DATE, user_input)] = str
- elif self._data[const.CONF_FREQUENCY] in const.GROUP_FREQUENCY:
- # "group"
- if self.hass is None:
- _LOGGER.error(
- "Cannot get list of garbage_collection entities for the group."
- )
- else:
- entities = self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM]
- entity_ids = {
- entity: entity
- for entity in entities
- if entities[entity].unique_id != self._data["unique_id"]
- }
- self.data_schema[
- self.required(CONF_ENTITIES, user_input)
- ] = cv.multi_select(entity_ids)
- elif self._data[const.CONF_FREQUENCY] not in const.BLANK_FREQUENCY:
- # everything else except "blank" and every-n-days
- if self._data[const.CONF_FREQUENCY] not in const.DAILY_FREQUENCY:
- weekdays_dict = {weekday: weekday for weekday in WEEKDAYS}
- self.data_schema[
- self.required(const.CONF_COLLECTION_DAYS, user_input)
- ] = cv.multi_select(weekdays_dict)
- # everything else except "blank"
- self.data_schema[
- self.optional(const.CONF_FIRST_MONTH, user_input)
- ] = vol.In(const.MONTH_OPTIONS)
- self.data_schema[self.optional(const.CONF_LAST_MONTH, user_input)] = vol.In(
- const.MONTH_OPTIONS
- )
- if self._data[const.CONF_FREQUENCY] in const.MONTHLY_FREQUENCY:
- # "monthly"
- self.data_schema[
- self.optional(const.CONF_WEEKDAY_ORDER_NUMBER, user_input)
- ] = vol.All(
- cv.multi_select(
- {"1": "1st", "2": "2nd", "3": "3rd", "4": "4th", "5": "5th"}
- ),
- )
- self.data_schema[
- self.optional(const.CONF_FORCE_WEEK_NUMBERS, user_input)
- ] = bool
- if self._data[const.CONF_FREQUENCY] in const.WEEKLY_DAILY_MONTHLY:
- # "every-n-weeks", "every-n-days", "monthly"
- self.data_schema[
- self.required(const.CONF_PERIOD, user_input)
- ] = vol.All(vol.Coerce(int), vol.Range(min=1, max=365))
- if self._data[const.CONF_FREQUENCY] in const.WEEKLY_FREQUENCY_X:
- # every-n-weeks
- self.data_schema[
- self.required(const.CONF_FIRST_WEEK, user_input)
- ] = vol.All(vol.Coerce(int), vol.Range(min=1, max=52))
- if self._data[const.CONF_FREQUENCY] in const.DAILY_FREQUENCY:
- # every-n-days
- self.data_schema[self.required(const.CONF_FIRST_DATE, user_input)] = str
- if self._data.get(const.CONF_VERBOSE_STATE, False):
- # "verbose_state"
- self.data_schema[
- self.required(const.CONF_VERBOSE_FORMAT, user_input)
- ] = cv.string
- self.data_schema[
- self.required(const.CONF_DATE_FORMAT, user_input)
- ] = cv.string
- return False
-
- @property
- def frequency(self):
- """Return the collection frequency."""
+def _validate_config(data: Any) -> Any:
+ """Validate config."""
+ if const.CONF_DATE in data:
try:
- return self._data[const.CONF_FREQUENCY]
- except KeyError:
- return None
-
- @property
- def data(self):
- """Return whole data store."""
- return self._data
-
-
-@config_entries.HANDLERS.register(const.DOMAIN)
-class GarbageCollectionFlowHandler(config_entries.ConfigFlow):
- """Config flow for garbage_collection."""
-
- VERSION = const.CONFIG_VERSION
- CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
+ helpers.month_day_text(data[const.CONF_DATE])
+ except vol.Invalid as exc:
+ raise SchemaFlowError("month_day") from exc
+ return data
+
+
+def required(
+ key: str, options: Dict[str, Any], default: Any | None = None
+) -> vol.Required:
+ """Return vol.Required."""
+ if isinstance(options, dict) and key in options:
+ suggested_value = options[key]
+ elif default is not None:
+ suggested_value = default
+ else:
+ return vol.Required(key)
+ return vol.Required(key, description={"suggested_value": suggested_value})
+
+
+def optional(
+ key: str, options: Dict[str, Any], default: Any | None = None
+) -> vol.Optional:
+ """Return vol.Optional."""
+ if isinstance(options, dict) and key in options:
+ suggested_value = options[key]
+ elif default is not None:
+ suggested_value = default
+ else:
+ return vol.Optional(key)
+ return vol.Optional(key, description={"suggested_value": suggested_value})
+
+
+def general_options_schema(
+ _: SchemaConfigFlowHandler | SchemaOptionsFlowHandler,
+ options: Dict[str, Any],
+) -> vol.Schema:
+ """Generate options schema."""
+ return vol.Schema(
+ {
+ required(const.CONF_FREQUENCY, options, const.DEFAULT_FREQUENCY): vol.In(
+ const.FREQUENCY_OPTIONS
+ ),
+ optional(
+ const.CONF_ICON_NORMAL, options, const.DEFAULT_ICON_NORMAL
+ ): selector.IconSelector(),
+ optional(
+ const.CONF_ICON_TODAY, options, const.DEFAULT_ICON_TODAY
+ ): selector.IconSelector(),
+ optional(
+ const.CONF_ICON_TOMORROW, options, const.DEFAULT_ICON_TOMORROW
+ ): selector.IconSelector(),
+ optional(const.CONF_EXPIRE_AFTER, options): selector.TimeSelector(),
+ optional(
+ const.CONF_VERBOSE_STATE, options, const.DEFAULT_VERBOSE_STATE
+ ): bool,
+ optional(ATTR_HIDDEN, options, False): bool,
+ optional(const.CONF_MANUAL, options, False): bool,
+ }
+ )
- def __init__(self):
- """Initialize."""
- self.shared_class = GarbageCollectionShared({"unique_id": str(uuid.uuid4())})
- async def async_step_user(
- self, user_input: dict = {}
- ): # pylint: disable=dangerous-default-value
- """Step 1 - set general parameters."""
- next_step = self.shared_class.step1_frequency(user_input)
- if next_step:
- return await self.async_step_detail()
- return self.async_show_form(
- step_id="user",
- data_schema=vol.Schema(
- self.shared_class.data_schema, extra=vol.ALLOW_EXTRA
+def general_config_schema(
+ handler: SchemaConfigFlowHandler | SchemaOptionsFlowHandler,
+ options: Dict[str, Any],
+) -> vol.Schema:
+ """Generate config schema."""
+ return vol.Schema(
+ {
+ optional(CONF_NAME, options): selector.TextSelector(),
+ }
+ ).extend(general_options_schema(handler, options).schema)
+
+
+def detail_config_schema(
+ _,
+ options: Dict[str, Any],
+) -> vol.Schema:
+ """Generate options schema."""
+ options_schema: Dict[vol.Optional | vol.Required, Any] = {}
+ if options[const.CONF_FREQUENCY] in const.ANNUAL_FREQUENCY:
+ # "annual"
+ options_schema[required(const.CONF_DATE, options)] = str
+ elif options[const.CONF_FREQUENCY] in const.GROUP_FREQUENCY:
+ # "group"
+ options_schema[required(CONF_ENTITIES, options)] = selector.EntitySelector(
+ selector.EntitySelectorConfig(
+ domain="sensor", integration=const.DOMAIN, multiple=True
),
- errors=self.shared_class.errors,
)
-
- async def async_step_detail(
- self, user_input: dict = {}
- ): # pylint: disable=dangerous-default-value
- """Step 2 - enter detail depending on frequency."""
- self.shared_class.hass = self.hass
- next_step = self.shared_class.step2_detail(user_input)
- if next_step:
- return self.async_create_entry(
- title=self.shared_class.name, data=self.shared_class.data
+ elif options[const.CONF_FREQUENCY] not in const.BLANK_FREQUENCY:
+ # everything else except "blank" and every-n-days
+ if options[const.CONF_FREQUENCY] not in const.DAILY_FREQUENCY:
+ weekdays_dict = {weekday: weekday for weekday in WEEKDAYS}
+ options_schema[
+ required(const.CONF_COLLECTION_DAYS, options)
+ ] = cv.multi_select(weekdays_dict)
+ # everything else except "blank"
+ options_schema[
+ optional(const.CONF_FIRST_MONTH, options, const.DEFAULT_FIRST_MONTH)
+ ] = vol.In(const.MONTH_OPTIONS)
+ options_schema[
+ optional(const.CONF_LAST_MONTH, options, const.DEFAULT_LAST_MONTH)
+ ] = vol.In(const.MONTH_OPTIONS)
+ if options[const.CONF_FREQUENCY] in const.MONTHLY_FREQUENCY:
+ # "monthly"
+ options_schema[
+ optional(const.CONF_WEEKDAY_ORDER_NUMBER, options)
+ ] = vol.All(
+ cv.multi_select(
+ {"1": "1st", "2": "2nd", "3": "3rd", "4": "4th", "5": "5th"}
+ ),
)
- return self.async_show_form(
- step_id="detail",
- data_schema=vol.Schema(
- self.shared_class.data_schema, extra=vol.ALLOW_EXTRA
- ),
- errors=self.shared_class.errors,
- )
-
- async def async_step_import(
- self, user_input: dict
- ): # pylint: disable=unused-argument
- """Import config from configuration.yaml."""
- _LOGGER.info("Importing config for %s", user_input)
- to_remove = [
- "offset",
- "move_country_holidays",
- "holiday_in_week_move",
- "holiday_pop_named",
- "holiday_move_offset",
- "prov",
- "state",
- "observed",
- "exclude_dates",
- "include_dates",
- ]
- removed_data: dict[str, Any] = {}
- for remove in to_remove:
- if remove in user_input:
- removed_data[remove] = user_input[remove]
- del user_input[remove]
- if user_input.get(const.CONF_FREQUENCY) in const.MONTHLY_FREQUENCY:
- if const.CONF_WEEK_ORDER_NUMBER in user_input:
- user_input[const.CONF_WEEKDAY_ORDER_NUMBER] = list(
- map(str, user_input[const.CONF_WEEK_ORDER_NUMBER])
- )
- user_input[const.CONF_FORCE_WEEK_NUMBERS] = True
- del user_input[const.CONF_WEEK_ORDER_NUMBER]
- _LOGGER.debug("Updated data config for week_order_number")
- else:
- user_input[const.CONF_WEEKDAY_ORDER_NUMBER] = list(
- map(str, user_input[const.CONF_WEEKDAY_ORDER_NUMBER])
- )
- user_input[const.CONF_FORCE_WEEK_NUMBERS] = False
- if removed_data:
- _LOGGER.error(
- "Removed obsolete config values: %s. "
- "Please check the documentation how to configure the functionality.",
- removed_data,
+ options_schema[optional(const.CONF_FORCE_WEEK_NUMBERS, options)] = bool
+ if options[const.CONF_FREQUENCY] in const.WEEKLY_DAILY_MONTHLY:
+ # "every-n-weeks", "every-n-days", "monthly"
+ options_schema[required(const.CONF_PERIOD, options)] = vol.All(
+ vol.Coerce(int), vol.Range(min=1, max=1000)
)
- self.shared_class.update_data(user_input)
- return self.async_create_entry(
- title=self.shared_class.name, data=self.shared_class.data
- )
+ if options[const.CONF_FREQUENCY] in const.WEEKLY_FREQUENCY_X:
+ # every-n-weeks
+ options_schema[
+ required(const.CONF_FIRST_WEEK, options, const.DEFAULT_FIRST_WEEK)
+ ] = vol.All(vol.Coerce(int), vol.Range(min=1, max=52))
+ if options[const.CONF_FREQUENCY] in const.DAILY_FREQUENCY:
+ # every-n-days
+ options_schema[
+ required(const.CONF_FIRST_DATE, options)
+ ] = selector.DateSelector()
+ if options.get(const.CONF_VERBOSE_STATE, False):
+ # "verbose_state"
+ options_schema[
+ required(const.CONF_VERBOSE_FORMAT, options, const.DEFAULT_VERBOSE_FORMAT)
+ ] = cv.string
+ options_schema[
+ required(const.CONF_DATE_FORMAT, options, const.DEFAULT_DATE_FORMAT)
+ ] = cv.string
+ return vol.Schema(options_schema)
+
+
+CONFIG_FLOW: Dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
+ "user": SchemaFlowFormStep(general_config_schema, next_step=lambda x: "detail"),
+ "detail": SchemaFlowFormStep(
+ detail_config_schema, validate_user_input=_validate_config
+ ),
+}
+OPTIONS_FLOW: Dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
+ "init": SchemaFlowFormStep(general_options_schema, next_step=lambda x: "detail"),
+ "detail": SchemaFlowFormStep(
+ detail_config_schema, validate_user_input=_validate_config
+ ),
+}
+
+
+# mypy: ignore-errors
+class GarbageCollectionConfigFlowHandler(SchemaConfigFlowHandler, domain=const.DOMAIN):
+ """Handle a config or options flow for GarbageCollection."""
+
+ config_flow = CONFIG_FLOW
+ options_flow = OPTIONS_FLOW
+ VERSION = const.CONFIG_VERSION
- @staticmethod
@callback
- def async_get_options_flow(config_entry):
- """Return options flow handler, or empty options flow if no unique_id."""
- return OptionsFlowHandler(config_entry)
-
-
-class OptionsFlowHandler(config_entries.OptionsFlow):
- """Options flow handler."""
-
- def __init__(self, config_entry):
- """Create and initualize class variables."""
- self.shared_class = GarbageCollectionShared(config_entry.data)
+ def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
+ """Return config entry title.
- async def async_step_init(self, user_input: dict | None = None):
- """Set genral parameters."""
- next_step = self.shared_class.step1_frequency(user_input, options=True)
- if next_step:
- return await self.async_step_detail()
- return self.async_show_form(
- step_id="init",
- data_schema=vol.Schema(self.shared_class.data_schema),
- errors=self.shared_class.errors,
- )
-
- async def async_step_detail(
- self, user_input: dict = {}
- ): # pylint: disable=dangerous-default-value
- """Step 2 - annual or group (no week days)."""
- self.shared_class.hass = self.hass
- next_step = self.shared_class.step2_detail(user_input)
- if next_step:
- return self.async_create_entry(title="", data=self.shared_class.data)
- return self.async_show_form(
- step_id="detail",
- data_schema=vol.Schema(self.shared_class.data_schema),
- errors=self.shared_class.errors,
- )
+ The options parameter contains config entry options, which is the union of user
+ input from the config flow steps.
+ """
+ return cast(str, options["name"]) if "name" in options else ""
diff --git a/custom_components/garbage_collection/const.py b/custom_components/garbage_collection/const.py
index 0a770371..9f565d2e 100644
--- a/custom_components/garbage_collection/const.py
+++ b/custom_components/garbage_collection/const.py
@@ -8,7 +8,7 @@
SENSOR_PLATFORM = "sensor"
CALENDAR_PLATFORM = "calendar"
ATTRIBUTION = "Data from this is provided by garbage_collection."
-CONFIG_VERSION = 5
+CONFIG_VERSION = 6
ATTR_NEXT_DATE = "next_date"
ATTR_DAYS = "days"
diff --git a/custom_components/garbage_collection/diagnostics.py b/custom_components/garbage_collection/diagnostics.py
index 3bfa6c56..738fc684 100644
--- a/custom_components/garbage_collection/diagnostics.py
+++ b/custom_components/garbage_collection/diagnostics.py
@@ -1,7 +1,7 @@
"""Diagnostics support for Garbage Collection."""
from __future__ import annotations
-from typing import Any
+from typing import Any, Dict
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@@ -12,7 +12,7 @@
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: ConfigEntry,
-) -> dict[str, Any]:
+) -> Dict[str, Any]:
"""Return diagnostics for a config entry."""
entities = hass.data[const.DOMAIN][const.SENSOR_PLATFORM]
entity_data = [
diff --git a/custom_components/garbage_collection/helpers.py b/custom_components/garbage_collection/helpers.py
index 7ed2d5c3..1bbd6b17 100644
--- a/custom_components/garbage_collection/helpers.py
+++ b/custom_components/garbage_collection/helpers.py
@@ -4,10 +4,16 @@
from datetime import date, datetime
from typing import Any
+import homeassistant.util.dt as dt_util
import voluptuous as vol
from dateutil.parser import ParserError, parse
+def now() -> datetime:
+ """Return current date and time. Needed for testing."""
+ return dt_util.now()
+
+
def to_date(day: Any) -> date:
"""Convert datetime or text to date, if not already datetime.
@@ -41,16 +47,6 @@ def dates_to_texts(dates: list[date]) -> list[str]:
return converted
-def date_text(value: Any) -> str:
- """Have to store date as text - datetime is not JSON serialisable."""
- if value is None or value == "":
- return ""
- try:
- return datetime.strptime(value, "%Y-%m-%d").date().strftime("%Y-%m-%d")
- except ValueError as error:
- raise vol.Invalid(f"Invalid date: {value}") from error
-
-
def time_text(value: Any) -> str:
"""Have to store time as text - datetime is not JSON serialisable."""
if value is None or value == "":
@@ -61,15 +57,6 @@ def time_text(value: Any) -> str:
raise vol.Invalid(f"Invalid date: {value}") from error
-def string_to_list(string) -> list:
- """Convert comma separated text to list."""
- if isinstance(string, list):
- return string # Already list
- if string is None or string == "":
- return []
- return list(map(lambda x: x.strip("'\" "), string.split(",")))
-
-
def month_day_text(value: Any) -> str:
"""Validate format month/day."""
if value is None or value == "":
@@ -78,34 +65,3 @@ def month_day_text(value: Any) -> str:
return datetime.strptime(value, "%m/%d").date().strftime("%m/%d")
except ValueError as error:
raise vol.Invalid(f"Invalid date: {value}") from error
-
-
-def is_date(record: str) -> bool:
- """Validate yyyy-mm-dd format."""
- if date == "":
- return True
- try:
- datetime.strptime(record, "%Y-%m-%d")
- return True
- except ValueError:
- return False
-
-
-def is_dates(dates: list) -> bool:
- """Validate list of dates (yyyy-mm-dd, yyyy-mm-dd)."""
- if not dates:
- return True
- check = True
- for record in dates:
- if not is_date(record):
- check = False
- return check
-
-
-def is_month_day(record) -> bool:
- """Validate mm/dd format."""
- try:
- datetime.strptime(record, "%m/%d")
- return True
- except ValueError:
- return False
diff --git a/custom_components/garbage_collection/manifest.json b/custom_components/garbage_collection/manifest.json
index 6076cb87..59e36407 100644
--- a/custom_components/garbage_collection/manifest.json
+++ b/custom_components/garbage_collection/manifest.json
@@ -1,10 +1,11 @@
{
"domain": "garbage_collection",
"name": "Garbage Collection",
- "version": "4.7.8",
+ "version": "4.8.3",
"documentation": "https://github.com/bruxy70/Garbage-Collection/",
"issue_tracker": "https://github.com/bruxy70/Garbage-Collection/issues",
"iot_class": "calculated",
+ "integration_type": "helper",
"dependencies": [],
"config_flow": true,
"codeowners": [
diff --git a/custom_components/garbage_collection/sensor.py b/custom_components/garbage_collection/sensor.py
index 7793e7a0..dccc0afc 100644
--- a/custom_components/garbage_collection/sensor.py
+++ b/custom_components/garbage_collection/sensor.py
@@ -1,11 +1,10 @@
"""Sensor platform for garbage_collection."""
from __future__ import annotations
-import asyncio
import logging
from datetime import date, datetime, time, timedelta
+from typing import Any, Dict, Generator
-import homeassistant.util.dt as dt_util
from dateutil.relativedelta import relativedelta
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@@ -15,8 +14,9 @@
CONF_NAME,
WEEKDAYS,
)
+from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
-from homeassistant.helpers.discovery import async_load_platform
+from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
@@ -29,16 +29,11 @@
THROTTLE_INTERVAL = timedelta(seconds=60)
-def now() -> datetime:
- """Return current date and time. Needed for testing."""
- return dt_util.now()
-
-
async def async_setup_entry(
- _, config_entry: ConfigEntry, async_add_devices: AddEntitiesCallback
+ _: HomeAssistant, config_entry: ConfigEntry, async_add_devices: AddEntitiesCallback
) -> None:
"""Create garbage collection entities defined in config_flow and add them to HA."""
- frequency = config_entry.data.get(const.CONF_FREQUENCY)
+ frequency = config_entry.options.get(const.CONF_FREQUENCY)
name = (
config_entry.title
if config_entry.title is not None
@@ -65,6 +60,9 @@ class GarbageCollection(RestoreEntity):
"""GarbageCollection Sensor class."""
__slots__ = (
+ "_attr_icon",
+ "_attr_name",
+ "_attr_state",
"_collection_dates",
"_date_format",
"_days",
@@ -73,13 +71,10 @@ class GarbageCollection(RestoreEntity):
"_icon_normal",
"_icon_today",
"_icon_tomorrow",
- "_icon",
"_last_month",
"_last_updated",
"_manual",
- "_name",
"_next_date",
- "_state",
"_verbose_format",
"_verbose_state",
"config_entry",
@@ -89,22 +84,22 @@ class GarbageCollection(RestoreEntity):
def __init__(self, config_entry: ConfigEntry) -> None:
"""Read configuration and initialise class variables."""
- config = config_entry.data
+ config = config_entry.options
self.config_entry = config_entry
- self._name = (
+ self._attr_name = (
config_entry.title
if config_entry.title is not None
else config.get(CONF_NAME)
)
self._hidden = config.get(ATTR_HIDDEN, False)
self._manual = config.get(const.CONF_MANUAL)
- first_month = config.get(const.CONF_FIRST_MONTH)
+ first_month = config.get(const.CONF_FIRST_MONTH, const.DEFAULT_FIRST_MONTH)
self._first_month: int = (
const.MONTH_OPTIONS.index(first_month) + 1
if first_month in const.MONTH_OPTIONS
else 1
)
- last_month = config.get(const.CONF_LAST_MONTH)
+ last_month = config.get(const.CONF_LAST_MONTH, const.DEFAULT_LAST_MONTH)
self._last_month: int = (
const.MONTH_OPTIONS.index(last_month) + 1
if last_month in const.MONTH_OPTIONS
@@ -115,9 +110,13 @@ def __init__(self, config_entry: ConfigEntry) -> None:
self._icon_today = config.get(const.CONF_ICON_TODAY)
self._icon_tomorrow = config.get(const.CONF_ICON_TOMORROW)
exp = config.get(const.CONF_EXPIRE_AFTER)
- self.expire_after: time | None
- self.expire_after = (
- None if exp is None else datetime.strptime(exp, "%H:%M").time()
+ self.expire_after: time | None = (
+ None
+ if (
+ exp is None
+ or datetime.strptime(exp, "%H:%M:%S").time() == time(0, 0, 0)
+ )
+ else datetime.strptime(exp, "%H:%M:%S").time()
)
self._date_format = config.get(
const.CONF_DATE_FORMAT, const.DEFAULT_DATE_FORMAT
@@ -130,51 +129,50 @@ def __init__(self, config_entry: ConfigEntry) -> None:
self._last_updated: datetime | None = None
self.last_collection: datetime | None = None
self._days: int | None = None
- self._state = "" if bool(self._verbose_state) else 2
- self._icon = self._icon_normal
+ self._attr_state = "" if bool(self._verbose_state) else 2
+ self._attr_icon = self._icon_normal
- async def async_added_to_hass(self):
+ async def async_added_to_hass(self) -> None:
"""When sensor is added to hassio, add it to calendar."""
await super().async_added_to_hass()
- if const.DOMAIN not in self.hass.data:
- self.hass.data[const.DOMAIN] = {}
- if const.SENSOR_PLATFORM not in self.hass.data[const.DOMAIN]:
- self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM] = {}
self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM][self.entity_id] = self
+ # Restore stored state
if (state := await self.async_get_last_state()) is not None:
+ self._last_updated = None # Unblock update - after options change
+ self._attr_state = state.state
+ self._days = state.attributes[const.ATTR_DAYS]
+ next_date = helpers.parse_datetime(state.attributes[const.ATTR_NEXT_DATE])
+ self._next_date = None if next_date is None else next_date.date()
self.last_collection = helpers.parse_datetime(
- state.attributes.get(const.ATTR_LAST_COLLECTION)
+ state.attributes[const.ATTR_LAST_COLLECTION]
)
+ # Create device
device_registry = dr.async_get(self.hass)
device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id,
identifiers={(const.DOMAIN, self.unique_id)},
- name=self.name,
+ name=self._attr_name,
manufacturer="bruxy70",
)
+ # Create or add to calendar
if not self.hidden:
if const.CALENDAR_PLATFORM not in self.hass.data[const.DOMAIN]:
self.hass.data[const.DOMAIN][
const.CALENDAR_PLATFORM
] = EntitiesCalendarData(self.hass)
_LOGGER.debug("Creating garbage_collection calendar")
- self.hass.async_create_task(
- async_load_platform(
- self.hass,
- const.CALENDAR_PLATFORM,
- const.DOMAIN,
- {"name": const.CALENDAR_NAME},
- {"name": const.CALENDAR_NAME},
- )
+ await self.hass.config_entries.async_forward_entry_setup(
+ self.config_entry, const.CALENDAR_PLATFORM
)
+
self.hass.data[const.DOMAIN][const.CALENDAR_PLATFORM].add_entity(
self.entity_id
)
- async def async_will_remove_from_hass(self):
+ async def async_will_remove_from_hass(self) -> None:
"""When sensor is added to hassio, remove it."""
await super().async_will_remove_from_hass()
del self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM][self.entity_id]
@@ -183,12 +181,14 @@ async def async_will_remove_from_hass(self):
)
@property
- def unique_id(self):
+ def unique_id(self) -> str:
"""Return a unique ID to use for this sensor."""
- return self.config_entry.data.get("unique_id", None)
+ if "unique_id" in self.config_entry.data: # From legacy config
+ return self.config_entry.data["unique_id"]
+ return self.config_entry.entry_id
@property
- def device_info(self):
+ def device_info(self) -> DeviceInfo | None:
"""Return device info."""
return {
"identifiers": {(const.DOMAIN, self.unique_id)},
@@ -197,62 +197,72 @@ def device_info(self):
}
@property
- def name(self):
+ def name(self) -> str | None:
"""Return the name of the sensor."""
- return self._name
+ return self._attr_name
@property
- def next_date(self):
+ def next_date(self) -> date | None:
"""Return next date attribute."""
return self._next_date
@property
- def hidden(self):
+ def hidden(self) -> bool:
"""Return the hidden attribute."""
return self._hidden
@property
- def state(self):
+ def native_unit_of_measurement(self) -> str | None:
+ """Return unit of measurement - None for numerical value."""
+ return None
+
+ @property
+ def native_value(self) -> object:
"""Return the state of the sensor."""
- return self._state
+ return self._attr_state
+
+ @property
+ def last_updated(self) -> datetime | None:
+ """Return when the sensor was last updated."""
+ return self._last_updated
@property
- def icon(self):
+ def icon(self) -> str:
"""Return the entity icon."""
- return self._icon
+ return self._attr_icon
@property
- def extra_state_attributes(self):
+ def extra_state_attributes(self) -> Dict[str, Any]:
"""Return the state attributes."""
- res = {}
- if self._next_date is None:
- res[const.ATTR_NEXT_DATE] = None
- else:
- res[const.ATTR_NEXT_DATE] = datetime(
+ state_attr = {
+ const.ATTR_DAYS: self._days,
+ const.ATTR_LAST_COLLECTION: self.last_collection,
+ const.ATTR_LAST_UPDATED: self._last_updated,
+ const.ATTR_NEXT_DATE: None
+ if self._next_date is None
+ else datetime(
self._next_date.year, self._next_date.month, self._next_date.day
- ).astimezone()
- res[const.ATTR_DAYS] = self._days
- res[const.ATTR_LAST_COLLECTION] = self.last_collection
- res[const.ATTR_LAST_UPDATED] = self._last_updated
- # Needed for translations to work
- res[ATTR_DEVICE_CLASS] = self.DEVICE_CLASS
- return res
+ ).astimezone(),
+ # Needed for translations to work
+ ATTR_DEVICE_CLASS: self.DEVICE_CLASS,
+ }
+ return state_attr
@property
- def DEVICE_CLASS(self): # pylint: disable=C0103
+ def DEVICE_CLASS(self) -> str: # pylint: disable=C0103
"""Return the class of the sensor."""
return const.DEVICE_CLASS
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return main sensor parameters."""
return (
- f"{self.__class__.__name__}(name={self._name}, "
+ f"{self.__class__.__name__}(name={self._attr_name}, "
f"entity_id={self.entity_id}, "
- f"state={self.state}\n"
+ f"state={self.state}, "
f"attributes={self.extra_state_attributes})"
)
- async def _async_find_candidate_date(self, day1: date) -> date | None:
+ def _find_candidate_date(self, day1: date) -> date | None:
"""Find the next possible date starting from day1.
Only based on calendar, not looking at include/exclude days.
@@ -266,12 +276,12 @@ async def _async_ready_for_update(self) -> bool:
Skip the update if the sensor was updated today
Except for the sensors with with next date today and after the expiration time
"""
- current_date_time = now()
+ current_date_time = helpers.now()
today = current_date_time.date()
try:
ready_for_update = bool(self._last_updated.date() != today) # type: ignore
except AttributeError:
- ready_for_update = True
+ return True
try:
if self._next_date == today and (
(
@@ -283,7 +293,7 @@ async def _async_ready_for_update(self) -> bool:
and self.last_collection.date() == today
)
):
- ready_for_update = True
+ return True
except (AttributeError, TypeError):
pass
return ready_for_update
@@ -292,75 +302,58 @@ def date_inside(self, dat: date) -> bool:
"""Check if the date is inside first and last date."""
month = dat.month
if self._first_month <= self._last_month:
- return bool(month >= self._first_month and month <= self._last_month)
- return bool(month <= self._last_month or month >= self._first_month)
+ return bool(self._first_month <= month <= self._last_month)
+ return bool(self._first_month <= month or month <= self._last_month)
def move_to_range(self, day: date) -> date:
"""If the date is not in range, move to the range."""
if not self.date_inside(day):
year = day.year
month = day.month
- if self._first_month <= self._last_month and month > self._last_month:
+ if self._first_month <= self._last_month < month:
_LOGGER.debug(
"(%s) %s outside the range, lookig from %s next year",
- self._name,
+ self._attr_name,
day,
const.MONTH_OPTIONS[self._first_month - 1],
)
return date(year + 1, self._first_month, 1)
- else:
- _LOGGER.debug(
- "(%s) %s outside the range, searching from %s",
- self._name,
- day,
- const.MONTH_OPTIONS[self._first_month - 1],
- )
- return date(year, self._first_month, 1)
+ _LOGGER.debug(
+ "(%s) %s outside the range, searching from %s",
+ self._attr_name,
+ day,
+ const.MONTH_OPTIONS[self._first_month - 1],
+ )
+ return date(year, self._first_month, 1)
return day
- async def _async_find_next_date(self, first_date: date) -> date | None:
- """Get date within configured date range."""
- # Today's collection can be triggered by past collection with offset
- # Move starting date if today is out of range
- day1 = self.move_to_range(first_date)
- next_date = None
- while next_date is None:
+ def collection_schedule(
+ self, date1: date | None = None, date2: date | None = None
+ ) -> Generator[date, None, None]:
+ """Get dates within configured date range."""
+ today = helpers.now().date()
+ first_date: date = date(today.year - 1, 1, 1) if date1 is None else date1
+ last_date: date = date(today.year + 1, 12, 31) if date2 is None else date2
+ first_date = self.move_to_range(first_date)
+ while True:
try:
- next_date = await self._async_find_candidate_date(day1)
+ next_date = self._find_candidate_date(first_date)
except (TypeError, ValueError):
- return None
- if next_date is None:
- return None
+ return
+ if next_date is None or next_date > last_date:
+ return
if (new_date := self.move_to_range(next_date)) != next_date:
- day1 = new_date # continue from next year
- next_date = None
+ first_date = new_date # continue from next year
else:
- # Date is before starting date
- if next_date < first_date:
- next_date = None
- day1 += relativedelta(days=1) # look from the next day
- return next_date
+ yield next_date
+ first_date = next_date + relativedelta(days=1) # look from the next day
async def _async_load_collection_dates(self) -> None:
"""Fill the collection dates list."""
self._collection_dates.clear()
- today = now().date()
- start_date = end_date = date(today.year - 1, 1, 1)
- end_date = date(today.year + 1, 12, 31)
- try:
- next_date = await self._async_find_next_date(start_date)
- except asyncio.TimeoutError:
- _LOGGER.error("(%s) Timeout loading collection dates", self._name)
- return
-
- while (
- next_date is not None and next_date >= start_date and next_date <= end_date
- ):
- self._collection_dates.append(next_date)
- next_date = await self._async_find_next_date(
- next_date + relativedelta(days=1)
- )
- self._collection_dates.sort()
+ for collection_date in self.collection_schedule():
+ self._collection_dates.append(collection_date)
+ # self._collection_dates.sort()
async def add_date(self, collection_date: date) -> None:
"""Add date to _collection_dates."""
@@ -368,7 +361,7 @@ async def add_date(self, collection_date: date) -> None:
self._collection_dates.append(collection_date)
self._collection_dates.sort()
else:
- _LOGGER.error(
+ _LOGGER.warning(
"%s not added to %s - already on the collection schedule",
collection_date,
self.name,
@@ -379,7 +372,7 @@ async def remove_date(self, collection_date: date) -> None:
try:
self._collection_dates.remove(collection_date)
except ValueError:
- _LOGGER.error(
+ _LOGGER.warning(
"%s not removed from %s - not in the collection schedule",
collection_date,
self.name,
@@ -387,7 +380,7 @@ async def remove_date(self, collection_date: date) -> None:
def get_next_date(self, first_date: date, ignore_today=False) -> date | None:
"""Get next date from self._collection_dates."""
- current_date_time = now()
+ current_date_time = helpers.now()
for d in self._collection_dates: # pylint: disable=invalid-name
if d < first_date:
continue
@@ -411,10 +404,11 @@ async def async_update(self) -> None:
if not await self._async_ready_for_update() or not self.hass.is_running:
return
- _LOGGER.debug("(%s) Calling update", self._name)
+ _LOGGER.debug("(%s) Calling update", self._attr_name)
await self._async_load_collection_dates()
_LOGGER.debug(
- "(%s) Dates loaded, firing a garbage_collection_loaded event", self._name
+ "(%s) Dates loaded, firing a garbage_collection_loaded event",
+ self._attr_name,
)
event_data = {
"entity_id": self.entity_id,
@@ -426,44 +420,47 @@ async def async_update(self) -> None:
def update_state(self) -> None:
"""Pick the first event from collection dates, update attributes."""
- _LOGGER.debug("(%s) Looking for next collection", self._name)
- self._last_updated = now()
+ _LOGGER.debug("(%s) Looking for next collection", self._attr_name)
+ self._last_updated = helpers.now()
today = self._last_updated.date()
self._next_date = self.get_next_date(today)
if self._next_date is not None:
_LOGGER.debug(
- "(%s) next_date (%s), today (%s)", self._name, self._next_date, today
+ "(%s) next_date (%s), today (%s)",
+ self._attr_name,
+ self._next_date,
+ today,
)
self._days = (self._next_date - today).days
next_date_txt = self._next_date.strftime(self._date_format)
_LOGGER.debug(
"(%s) Found next collection date: %s, that is in %d days",
- self._name,
+ self._attr_name,
next_date_txt,
self._days,
)
if self._days > 1:
if bool(self._verbose_state):
- self._state = self._verbose_format.format(
+ self._attr_state = self._verbose_format.format(
date=next_date_txt, days=self._days
)
- # self._state = "on_date"
+ # self._attr_state = "on_date"
else:
- self._state = 2
- self._icon = self._icon_normal
+ self._attr_state = 2
+ self._attr_icon = self._icon_normal
else:
if self._days == 0:
if bool(self._verbose_state):
- self._state = const.STATE_TODAY
+ self._attr_state = const.STATE_TODAY
else:
- self._state = self._days
- self._icon = self._icon_today
+ self._attr_state = self._days
+ self._attr_icon = self._icon_today
elif self._days == 1:
if bool(self._verbose_state):
- self._state = const.STATE_TOMORROW
+ self._attr_state = const.STATE_TOMORROW
else:
- self._state = self._days
- self._icon = self._icon_tomorrow
+ self._attr_state = self._days
+ self._attr_icon = self._icon_tomorrow
else:
self._days = None
@@ -476,7 +473,7 @@ class WeeklyCollection(GarbageCollection):
def __init__(self, config_entry: ConfigEntry) -> None:
"""Read parameters specific for Weekly Collection Frequency."""
super().__init__(config_entry)
- config = config_entry.data
+ config = config_entry.options
self._collection_days = config.get(const.CONF_COLLECTION_DAYS, [])
self._period: int
self._first_week: int
@@ -494,7 +491,7 @@ def __init__(self, config_entry: ConfigEntry) -> None:
self._period = config.get(const.CONF_PERIOD, 1)
self._first_week = config.get(const.CONF_FIRST_WEEK, 1)
- async def _async_find_candidate_date(self, day1: date) -> date | None:
+ def _find_candidate_date(self, day1: date) -> date | None:
"""Calculate possible date, for weekly frequency."""
week = day1.isocalendar()[1]
weekday = day1.weekday()
@@ -524,7 +521,7 @@ class DailyCollection(GarbageCollection):
def __init__(self, config_entry: ConfigEntry) -> None:
"""Read parameters specific for Daily Collection Frequency."""
super().__init__(config_entry)
- config = config_entry.data
+ config = config_entry.options
self._period = config.get(const.CONF_PERIOD)
self._first_date: date | None
try:
@@ -532,7 +529,7 @@ def __init__(self, config_entry: ConfigEntry) -> None:
except ValueError:
self._first_date = None
- async def _async_find_candidate_date(self, day1: date) -> date | None:
+ def _find_candidate_date(self, day1: date) -> date | None:
"""Calculate possible date, for every-n-days frequency."""
try:
if (day1 - self._first_date).days % self._period == 0: # type: ignore
@@ -542,7 +539,7 @@ async def _async_find_candidate_date(self, day1: date) -> date | None:
)
except TypeError as error:
raise ValueError(
- f"({self._name}) Please configure first_date and period "
+ f"({self._attr_name}) Please configure first_date and period "
"for every-n-days collection frequency."
) from error
return day1 + relativedelta(days=offset)
@@ -562,7 +559,7 @@ class MonthlyCollection(GarbageCollection):
def __init__(self, config_entry: ConfigEntry) -> None:
"""Read parameters specific for Monthly Collection Frequency."""
super().__init__(config_entry)
- config = config_entry.data
+ config = config_entry.options
self._collection_days = config.get(const.CONF_COLLECTION_DAYS, [])
self._monthly_force_week_numbers = config.get(
const.CONF_FORCE_WEEK_NUMBERS, False
@@ -571,7 +568,7 @@ def __init__(self, config_entry: ConfigEntry) -> None:
self._week_order_numbers: list
order_numbers: list = []
if const.CONF_WEEKDAY_ORDER_NUMBER in config:
- order_numbers = list(map(int, config.get(const.CONF_WEEKDAY_ORDER_NUMBER)))
+ order_numbers = list(map(int, config[const.CONF_WEEKDAY_ORDER_NUMBER]))
if self._monthly_force_week_numbers:
self._weekday_order_numbers = []
self._week_order_numbers = order_numbers
@@ -611,7 +608,7 @@ def nth_weekday_date(
+ (weekday_number - 1) * 7
)
- async def _async_monthly_candidate(self, day1: date) -> date:
+ def _monthly_candidate(self, day1: date) -> date:
"""Calculate possible date, for monthly frequency."""
if self._monthly_force_week_numbers:
for week_order_number in self._week_order_numbers:
@@ -647,13 +644,13 @@ async def _async_monthly_candidate(self, day1: date) -> date:
WEEKDAYS.index(self._collection_days[0]),
)
- async def _async_find_candidate_date(self, day1: date) -> date | None:
+ def _find_candidate_date(self, day1: date) -> date | None:
if self._period is None or self._period == 1:
- return await self._async_monthly_candidate(day1)
+ return self._monthly_candidate(day1)
else:
- candidate_date = await self._async_monthly_candidate(day1)
+ candidate_date = self._monthly_candidate(day1)
while (candidate_date.month - self._first_month) % self._period != 0:
- candidate_date = await self._async_monthly_candidate(
+ candidate_date = self._monthly_candidate(
candidate_date + relativedelta(days=1)
)
return candidate_date
@@ -667,17 +664,17 @@ class AnnualCollection(GarbageCollection):
def __init__(self, config_entry: ConfigEntry) -> None:
"""Read parameters specific for Annual Collection Frequency."""
super().__init__(config_entry)
- config = config_entry.data
+ config = config_entry.options
self._date = config.get(const.CONF_DATE)
- async def _async_find_candidate_date(self, day1: date) -> date | None:
+ def _find_candidate_date(self, day1: date) -> date | None:
"""Calculate possible date, for annual frequency."""
year = day1.year
try:
conf_date = datetime.strptime(self._date, "%m/%d").date()
except TypeError as error:
raise ValueError(
- f"({self._name}) Please configure the date "
+ f"({self._attr_name}) Please configure the date "
"for annual collection frequency."
) from error
if (candidate_date := date(year, conf_date.month, conf_date.day)) < day1:
@@ -693,15 +690,17 @@ class GroupCollection(GarbageCollection):
def __init__(self, config_entry: ConfigEntry) -> None:
"""Read parameters specific for Group Collection Frequency."""
super().__init__(config_entry)
- config = config_entry.data
+ config = config_entry.options
self._entities = config.get(CONF_ENTITIES, [])
- async def _async_find_candidate_date(self, day1: date) -> date | None:
+ def _find_candidate_date(self, day1: date) -> date | None:
"""Calculate possible date, for group frequency."""
candidate_date = None
try:
for entity_id in self._entities:
- entity = self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM][entity_id]
+ entity: GarbageCollection = self.hass.data[const.DOMAIN][
+ const.SENSOR_PLATFORM
+ ][entity_id]
next_date = entity.get_next_date(day1)
if next_date is not None and (
candidate_date is None or next_date < candidate_date
@@ -710,7 +709,7 @@ async def _async_find_candidate_date(self, day1: date) -> date | None:
except KeyError as error:
raise ValueError from error
except TypeError as error:
- _LOGGER.error("(%s) Please add entities for the group.", self._name)
+ _LOGGER.error("(%s) Please add entities for the group.", self._attr_name)
raise ValueError from error
return candidate_date
@@ -719,7 +718,7 @@ async def _async_ready_for_update(self) -> bool:
For group sensors wait for update of the sensors in the group
"""
- current_date_time = now()
+ current_date_time = helpers.now()
today = current_date_time.date()
try:
ready_for_update = bool(self._last_updated.date() != today) # type: ignore
@@ -728,17 +727,14 @@ async def _async_ready_for_update(self) -> bool:
members_ready = True
for entity_id in self._entities:
try:
- entity = self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM][entity_id]
+ entity: GarbageCollection = self.hass.data[const.DOMAIN][
+ const.SENSOR_PLATFORM
+ ][entity_id]
await entity.async_update()
except KeyError:
- pass
- state_object = self.hass.states.get(entity_id)
- if state_object is None:
members_ready = False
break
- if (
- last_updated := state_object.attributes.get(const.ATTR_LAST_UPDATED)
- ) is None:
+ if (last_updated := entity.last_updated) is None:
ready_for_update = True
continue
# Wait for all members to get updated
@@ -756,15 +752,10 @@ async def _async_ready_for_update(self) -> bool:
class BlankCollection(GarbageCollection):
"""No collection - for mnual update."""
- async def _async_find_candidate_date(self, day1: date) -> date | None:
+ def _find_candidate_date(self, day1: date) -> date | None:
"""Do not return any date for blank frequency."""
return None
- async def _async_find_next_date(self, first_date: date) -> date | None:
- """Get date within configured date range."""
- # Blank frequency always returns None.
- return None
-
async def _async_load_collection_dates(self) -> None:
"""Clear collection dates (filled in by the blueprint)."""
self._collection_dates.clear()
@@ -775,10 +766,11 @@ async def async_update(self) -> None:
if not await self._async_ready_for_update() or not self.hass.is_running:
return
- _LOGGER.debug("(%s) Calling update", self._name)
+ _LOGGER.debug("(%s) Calling update", self._attr_name)
await self._async_load_collection_dates()
_LOGGER.debug(
- "(%s) Dates loaded, firing a garbage_collection_loaded event", self._name
+ "(%s) Dates loaded, firing a garbage_collection_loaded event",
+ self._attr_name,
)
event_data = {
"entity_id": self.entity_id,
diff --git a/custom_components/garbage_collection/translations/cs.json b/custom_components/garbage_collection/translations/cs.json
index 33224994..014c23bb 100644
--- a/custom_components/garbage_collection/translations/cs.json
+++ b/custom_components/garbage_collection/translations/cs.json
@@ -25,7 +25,7 @@
"collection_days": "Dny svozu",
"first_month": "První měsíc svozu",
"last_month": "Poslední měsíc svozu",
- "period": "Perioda (svoz každých n týdnů/dnů): (1-365)",
+ "period": "Perioda (svoz každých n týdnů/dnů): (1-1000)",
"first_week": "První týden svozu (1-52)",
"first_date": "První datum",
"weekday_order_number": "Pořadí dne v měsící (např. první středa v měsíci)",
@@ -44,7 +44,7 @@
"time": "Špatný formát času!",
"weekday_order_number": "Vyber jeden nebo více dní!",
"week_order_number": "Vyber jeden nebo více týdnů!",
- "period": "Perioda musí být číslo mezi 1 a 365",
+ "period": "Perioda musí být číslo mezi 1 a 1000",
"first_week": "První týden musí být číslo mezi 1 a 52",
"date": "Špatný formát data!"
},
@@ -77,7 +77,7 @@
"collection_days": "Dny svozu",
"first_month": "První měsíc svozu",
"last_month": "Poslední měsíc scozu",
- "period": "Perioda (svoz každých n týdnů-dnů): (1-365)",
+ "period": "Perioda (svoz každých n týdnů-dnů): (1-1000)",
"first_week": "První týden svozu (1-52)",
"first_date": "První datum",
"weekday_order_number": "Pořadí dne v měsící (např. první středa v měsíci)",
@@ -96,7 +96,7 @@
"time": "Špatný formát času!",
"weekday_order_number": "Vyber jeden nebo více dní!",
"week_order_number": "Vyber jeden nebo více týdnů!",
- "period": "Perioda musí být číslo mezi 1 a 365",
+ "period": "Perioda musí být číslo mezi 1 a 1000",
"first_week": "První týden musí být číslo mezi 1 a 52",
"date": "Špatný formát data!"
}
diff --git a/custom_components/garbage_collection/translations/da.json b/custom_components/garbage_collection/translations/da.json
new file mode 100644
index 00000000..774f0267
--- /dev/null
+++ b/custom_components/garbage_collection/translations/da.json
@@ -0,0 +1,105 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "title": "Garbage Collection - Afhentningsfrekvens (1/2)",
+ "description": "Angiv navnet på sensoren og konfigurer sensorparametre. For mere info se: https://github.com/bruxy70/Garbage-Collection",
+ "data": {
+ "name": "Visningsnavn",
+ "hidden": "Skjul i kalender",
+ "frequency": "Frekvens",
+ "manual_update": "Manuel opdatering - sensortilstand opdateres manuelt af en service (blueprint)",
+ "icon_normal": "Ikon (mdi:trash-can) - ikke påkrævet",
+ "icon_tomorrow": "Ikon ved afhentning i morgen (mdi:delete-restore) - ikke påkrævet",
+ "icon_today": "Ikon ved afhentning i dag (mdi:delete-circle) - ikke påkrævet",
+ "expire_after": "Forældes efter (HH:MM) - ikke påkrævet",
+ "verbose_state": "Verbos tilstand (tekst i stedet for tal)"
+ }
+ },
+ "detail": {
+ "title": "Garbage Collection - Yderligere parametre (2/2)",
+ "description": "For mere info se: https://github.com/bruxy70/Garbage-Collection",
+ "data": {
+ "date": "Dato (mm/dd)",
+ "entities": "Liste af enheder (kommasepareret)",
+ "collection_days": "Afhentningsdage",
+ "first_month": "Første afhentningsmåned",
+ "last_month": "Sidste afhentningsmåned",
+ "period": "Afhentning sker hver n uger/dage: (1-1000)",
+ "first_week": "Første afhentningsuge (1-52)",
+ "first_date": "Første dato",
+ "weekday_order_number": "Specifikke ugedage i måneden (fx første onsdag i måneden)",
+ "force_week_order_numbers": "Brug specifik uge i måneden i stedet for specifik ugedag (fx onsdag i første uge i måneden)",
+ "verbose_format": "Verbost format (anvend `date` og `days` variablerne (i tuborgklammer))",
+ "date_format": "Datoformat (se http://strftime.org/)"
+ }
+ }
+ },
+ "error": {
+ "value": "Ugyldig værdi. Check dit input!",
+ "icon": "Ikoner skal angives i formen 'præfiks:navn'.",
+ "days": "Vælg en eller flere dage!",
+ "entities": "Enheden findes ikke!",
+ "month_day": "Ugyldigt dato format!",
+ "time": "Ugyldigt tids format!",
+ "weekday_order_number": "Vælg en eller flere dage",
+ "week_order_number": "Vælg en eller flere uger",
+ "period": "Afhentningsperioden skal være et tal mellem 1 og 1000",
+ "first_week": "Første afhentningsuge skal være et tal mellem 1 and 52",
+ "date": "Ugyldigt dato format!"
+ },
+ "abort": {
+ "single_instance_allowed": "Det er kun tilladt at have én konfiguration af Garbage Collection."
+ }
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Garbage Collection - Afhentningsfrekvens (1/2)",
+ "description": "Ændr sensorparametre. More info on https://github.com/bruxy70/Garbage-Collection",
+ "data": {
+ "hidden": "Skjul i kalender",
+ "frequency": "Frekvens",
+ "manual_update": "Manuel opdatering - sensortilstand opdateres manuelt af en service (blueprint)",
+ "icon_normal": "Ikon (mdi:trash-can) - ikke påkrævet",
+ "icon_tomorrow": "Ikon ved afhentning i morgen (mdi:delete-restore) - ikke påkrævet",
+ "icon_today": "Ikon ved afhentning i dag (mdi:delete-circle) - ikke påkrævet",
+ "expire_after": "Forældes efter (HH:MM) - ikke påkrævet",
+ "verbose_state": "Verbos tilstand (tekst i stedet for tal)"
+ }
+ },
+ "detail": {
+ "title": "Garbage Collection - Yderligere parametre (2/2)",
+ "description": "For mere info se: https://github.com/bruxy70/Garbage-Collection",
+ "data": {
+ "date": "Dato (mm/dd)",
+ "entities": "Liste af enheder (kommasepareret)",
+ "collection_days": "Afhentningsdage",
+ "first_month": "Første afhentningsmåned",
+ "last_month": "Sidste afhentningsmåned",
+ "period": "Afhentning sker hver n uger/dage: (1-1000)",
+ "first_week": "Første afhentningsuge (1-52)",
+ "first_date": "Første dato",
+ "weekday_order_number": "Specifikke ugedage i måneden (fx første onsdag i måneden)",
+ "force_week_order_numbers": "Brug specifik uge i måneden i stedet for specifik ugedag (fx onsdag i første uge i måneden)",
+ "verbose_format": "Verbost format (anvend `date` og `days` variablerne (i tuborgklammer))",
+ "date_format": "Datoformat (se http://strftime.org/)"
+ }
+ }
+ },
+ "error": {
+ "value": "Ugyldig værdi. Check dit input!",
+ "icon": "Ikoner skal angives i formen 'præfiks:navn'.",
+ "days": "Vælg en eller flere dage!",
+ "entities": "Enheden findes ikke!",
+ "month_day": "Ugyldigt dato format!",
+ "time": "Ugyldigt tids format!",
+ "weekday_order_number": "Vælg en eller flere dage",
+ "week_order_number": "Vælg en eller flere uger",
+ "period": "Afhentningsperioden skal være et tal mellem 1 og 1000",
+ "first_week": "Første afhentningsuge skal være et tal mellem 1 and 52",
+ "date": "Ugyldigt dato format!"
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/custom_components/garbage_collection/translations/en.json b/custom_components/garbage_collection/translations/en.json
index 619d4138..c1f18da3 100644
--- a/custom_components/garbage_collection/translations/en.json
+++ b/custom_components/garbage_collection/translations/en.json
@@ -25,7 +25,7 @@
"collection_days": "Collection days",
"first_month": "First collection month",
"last_month": "Last collection month",
- "period": "Collection every n weeks/days: (1-365)",
+ "period": "Collection every n weeks/days: (1-1000)",
"first_week": "First collection week (1-52)",
"first_date": "First date",
"weekday_order_number": "Order of the weekday in the month (e.g. first Wednesday of the month)",
@@ -44,7 +44,7 @@
"time": "Invalid time format!",
"weekday_order_number": "Select 1 or more days",
"week_order_number": "Select 1 or more weeks",
- "period": "Period must be a number between 1 and 365",
+ "period": "Period must be a number between 1 and 1000",
"first_week": "First week must be a number between 1 and 52",
"date": "Invalid date format!"
},
@@ -77,7 +77,7 @@
"collection_days": "Collection days",
"first_month": "First collection month",
"last_month": "Last collection month",
- "period": "Collection every n weeks/days: (1-365)",
+ "period": "Collection every n weeks/days: (1-1000)",
"first_week": "First collection week (1-52)",
"first_date": "First date",
"weekday_order_number": "Order of the weekday in the month (e.g. first Wednesday of the month)",
@@ -96,7 +96,7 @@
"time": "Invalid time format!",
"weekday_order_number": "Select 1 or more days",
"week_order_number": "Select 1 or more weeks",
- "period": "Period must be a number between 1 and 365",
+ "period": "Period must be a number between 1 and 1000",
"first_week": "First week must be a number between 1 and 52",
"date": "Invalid date format!"
}
diff --git a/custom_components/garbage_collection/translations/es.json b/custom_components/garbage_collection/translations/es.json
index 496c4eda..6d65d3de 100644
--- a/custom_components/garbage_collection/translations/es.json
+++ b/custom_components/garbage_collection/translations/es.json
@@ -25,7 +25,7 @@
"collection_days": "Días de recogida",
"first_month": "Primer mes de recogida",
"last_month": "Último mes de recogida",
- "period": "Recolección cada n semanas / días: (1-365)",
+ "period": "Recolección cada n semanas / días: (1-1000)",
"first_week": "Primera semana de recolección (1-52)",
"first_date": "Primera fecha",
"weekday_order_number": "Orden del día de la semana en el mes (por ejemplo, primer miércoles del mes)",
@@ -44,7 +44,7 @@
"time": "Formato de hora inválido!",
"weekday_order_number": "Seleccione 1 o más días",
"week_order_number": "Seleccione 1 o más semanas",
- "period": "El período debe ser un número entre 1 y 365",
+ "period": "El período debe ser un número entre 1 y 1000",
"first_week": "La primera semana debe ser un número entre 1 y 52",
"date": "Formato de fecha inválido!"
},
@@ -77,7 +77,7 @@
"collection_days": "Días de recogida",
"first_month": "Primer mes de recogida",
"last_month": "Último mes de recogida",
- "period": "Recolección cada n semanas / días: (1-365)",
+ "period": "Recolección cada n semanas / días: (1-1000)",
"first_week": "Primera semana de recolección (1-52)",
"first_date": "Primera fecha",
"weekday_order_number": "Orden del día de la semana en el mes (por ejemplo, primer miércoles del mes)",
@@ -96,7 +96,7 @@
"time": "Formato de hora inválido!",
"weekday_order_number": "Seleccione 1 o más días",
"week_order_number": "Seleccione 1 o más semanas",
- "period": "El período debe ser un número entre 1 y 365",
+ "period": "El período debe ser un número entre 1 y 1000",
"first_week": "La primera semana debe ser un número entre 1 y 52",
"date": "Formato de fecha inválido!"
}
diff --git a/custom_components/garbage_collection/translations/et.json b/custom_components/garbage_collection/translations/et.json
index 5cf720b8..bd68ee31 100644
--- a/custom_components/garbage_collection/translations/et.json
+++ b/custom_components/garbage_collection/translations/et.json
@@ -25,7 +25,7 @@
"collection_days": "Tühjendamise nädalapäevad",
"first_month": "Esimene prügiveo kuu",
"last_month": "Viimane prügiveo kuu",
- "period": "Prügivedu iga n päeva/nädala tagant: (1-365)",
+ "period": "Prügivedu iga n päeva/nädala tagant: (1-1000)",
"first_week": "Esimese prügiveo nädal (1-52)",
"first_date": "Esimese prügiveo kuupäev",
"weekday_order_number": "Kuu nädalapäevade järjekord (nt kuu esimene kolmapäev)",
@@ -44,7 +44,7 @@
"time": "Vigane kellaaja formaat!",
"weekday_order_number": "Valige üks või rohkem päevi",
"week_order_number": "Valige üks või rohkem nädalat",
- "period": "Välp peab olema number 1 ja 365 vahel",
+ "period": "Välp peab olema number 1 ja 1000 vahel",
"first_week": "Esimene nädal peab olema number 1 ja 52 vahel",
"date": "Vigane kuupäeva formaat!!"
},
@@ -77,7 +77,7 @@
"collection_days": "Tühjendamise nädalapäevad",
"first_month": "Esimene prügiveo kuu",
"last_month": "Viimane prügiveo kuu",
- "period": "Prügivedu iga n päeva/nädala tagant: (1-365)",
+ "period": "Prügivedu iga n päeva/nädala tagant: (1-1000)",
"first_week": "Esimese prügiveo nädal (1-52)",
"first_date": "Esimese prügiveo kuupäev",
"weekday_order_number": "Kuu nädalapäevade järjekord (nt kuu esimene kolmapäev)",
@@ -96,7 +96,7 @@
"time": "Vigane kellaaja formaat!",
"weekday_order_number": "Valige üks või rohkem päevi",
"week_order_number": "Valige üks või rohkem nädalat",
- "period": "Välp peab olema number 1 ja 365 vahel",
+ "period": "Välp peab olema number 1 ja 1000 vahel",
"first_week": "Esimene nädal peab olema number 1 ja 52 vahel",
"date": "Vigane kuupäeva formaat!!"
}
diff --git a/custom_components/garbage_collection/translations/fr.json b/custom_components/garbage_collection/translations/fr.json
index 96101a14..16618d15 100644
--- a/custom_components/garbage_collection/translations/fr.json
+++ b/custom_components/garbage_collection/translations/fr.json
@@ -26,7 +26,7 @@
"collection_days": "Jours de collecte",
"first_month": "Premier mois de la collecte",
"last_month": "Dernier mois de la collecte",
- "period": "Collecte toutes les n semaines/jours: (1-365)",
+ "period": "Collecte toutes les n semaines/jours: (1-1000)",
"first_week": "Première semaine de la collecte (1-52)",
"first_date": "Première date",
"weekday_order_number": "Ordre du jour de la semaine dans le mois (par exemple, premier mercredi du mois)",
@@ -45,7 +45,7 @@
"time": "Format d'heure invalide !",
"weekday_order_number": "Choisir un ou plusieurs jours",
"week_order_number": "Choisir une ou plusieurs semaines",
- "period": "La semaine doit être un nombre entre 1 et 365",
+ "period": "La semaine doit être un nombre entre 1 et 1000",
"first_week": "La première semaine doit être un nombre entre 1 et 52",
"date": "Format de date invalide !"
},
@@ -79,7 +79,7 @@
"collection_days": "Jours de collecte",
"first_month": "Premier mois de la collecte",
"last_month": "Dernier mois de la collecte",
- "period": "Collecte toutes les n semaines/jours: (1-365)",
+ "period": "Collecte toutes les n semaines/jours: (1-1000)",
"first_week": "Première semaine de la collecte (1-52)",
"first_date": "Première date",
"weekday_order_number": "Ordre du jour de la semaine dans le mois (par exemple, premier mercredi du mois)",
@@ -98,7 +98,7 @@
"time": "Format d'heure invalide !",
"weekday_order_number": "Choisir un ou plusieurs jours",
"week_order_number": "Choisir une ou plusieurs semaines",
- "period": "La semaine doit être un nombre entre 1 et 365",
+ "period": "La semaine doit être un nombre entre 1 et 1000",
"first_week": "La première semaine doit être un nombre entre 1 et 52",
"date": "Format de date invalide !"
}
diff --git a/custom_components/garbage_collection/translations/it.json b/custom_components/garbage_collection/translations/it.json
index 8bcb5858..418c020e 100644
--- a/custom_components/garbage_collection/translations/it.json
+++ b/custom_components/garbage_collection/translations/it.json
@@ -25,7 +25,7 @@
"collection_days": "Giorni di raccolta",
"first_month": "Prima raccolta del mese",
"last_month": "Ultima raccolta del mese",
- "period": "Raccolta ogni n settimane/giorno: (1-365)",
+ "period": "Raccolta ogni n settimane/giorno: (1-1000)",
"first_week": "Prima raccolta della settimana (1-52)",
"first_date": "Prima date",
"weekday_order_number": "Ordine del giorno della settimana nel mese (ad esempio il primo mercoledì del mese)",
@@ -44,7 +44,7 @@
"time": "Formato ora non valido!",
"weekday_order_number": "Seleziona uno o più giorni",
"week_order_number": "Seleziona uno o più settimani",
- "period": "Il periodo deve essere un numero compreso tra 1 e 365",
+ "period": "Il periodo deve essere un numero compreso tra 1 e 1000",
"first_week": "La prima settimana deve essere un numero tra 1 e 52",
"date": "Formato data non valido!"
},
@@ -77,7 +77,7 @@
"collection_days": "Giorni di raccolta",
"first_month": "Prima raccolta del mese",
"last_month": "Ultima raccolta del mese",
- "period": "Raccolta ogni n settimane/giorno: (1-365)",
+ "period": "Raccolta ogni n settimane/giorno: (1-1000)",
"first_week": "Prima raccolta della settimana (1-52)",
"first_date": "Prima date",
"weekday_order_number": "Ordine del giorno della settimana nel mese (ad esempio il primo mercoledì del mese)",
@@ -96,7 +96,7 @@
"time": "Formato ora non valido!",
"weekday_order_number": "Seleziona uno o più giorni",
"week_order_number": "Seleziona uno o più settimani",
- "period": "Il periodo deve essere un numero compreso tra 1 e 365",
+ "period": "Il periodo deve essere un numero compreso tra 1 e 1000",
"first_week": "La prima settimana deve essere un numero tra 1 e 52",
"date": "Formato data non valido!"
}
diff --git a/custom_components/garbage_collection/translations/pl.json b/custom_components/garbage_collection/translations/pl.json
index ab6488f9..175d24ba 100644
--- a/custom_components/garbage_collection/translations/pl.json
+++ b/custom_components/garbage_collection/translations/pl.json
@@ -25,7 +25,7 @@
"collection_days": "Dni wywozu",
"first_month": "Miesiąc pierwszego wywozu",
"last_month": "Miesiąc ostatniego wywozu",
- "period": "Wywóz co n dni/tygodni: (1-365)",
+ "period": "Wywóz co n dni/tygodni: (1-1000)",
"first_week": "Tydzień pierwszego wywozu: (1-52)",
"first_date": "Pierwsza data",
"weekday_order_number": "Kolejność dni tygodnia w miesiącu (np. pierwsza środa miesiąca)",
@@ -44,7 +44,7 @@
"time": "Nieprawidłowy format czasu!",
"weekday_order_number": "Wybierz 1 lub więcej dni!",
"week_order_number": "Wybierz 1 lub więcej tydzieńi!",
- "period": "Okres musi być liczbą od 1 do 365.",
+ "period": "Okres musi być liczbą od 1 do 1000.",
"first_week": "Pierwszy tydzień musi być liczbą między 1 a 52.",
"date": "Nieprawidłowy format daty!"
},
@@ -77,7 +77,7 @@
"collection_days": "Dni wywozu",
"first_month": "Miesiąc pierwszego wywozu",
"last_month": "Miesiąc ostatniego wywozu",
- "period": "Wywóz co n dni/tygodni: (1-365)",
+ "period": "Wywóz co n dni/tygodni: (1-1000)",
"first_week": "Tydzień pierwszego wywozu: (1-52)",
"first_date": "Pierwsza data",
"weekday_order_number": "Kolejność dni tygodnia w miesiącu (np. pierwsza środa miesiąca)",
@@ -96,7 +96,7 @@
"time": "Nieprawidłowy format czasu!",
"weekday_order_number": "Wybierz 1 lub więcej dni!",
"week_order_number": "Wybierz 1 lub więcej tydzieńi!",
- "period": "Okres musi być liczbą od 1 do 365.",
+ "period": "Okres musi być liczbą od 1 do 1000.",
"first_week": "Pierwszy tydzień musi być liczbą między 1 a 52.",
"date": "Nieprawidłowy format daty!"
}
diff --git a/custom_components/garbage_collection/translations/pt-BR.json b/custom_components/garbage_collection/translations/pt-BR.json
index 2c7c1a85..8475e88e 100644
--- a/custom_components/garbage_collection/translations/pt-BR.json
+++ b/custom_components/garbage_collection/translations/pt-BR.json
@@ -25,7 +25,7 @@
"collection_days": "Dias de coleta",
"first_month": "Primeiro mês de coletah",
"last_month": "Último mês de coleta",
- "period": "Coleta a cada n semanas/dias: (1-365)",
+ "period": "Coleta a cada n semanas/dias: (1-1000)",
"first_week": "Primeira semana de coleta (1-52)",
"first_date": "Primeira data",
"weekday_order_number": "Ordem do dia da semana no mês (por exemplo, primeira quarta-feira do mês)",
@@ -44,7 +44,7 @@
"time": "Formato de hora inválido!",
"weekday_order_number": "Selecione 1 ou mais dias",
"week_order_number": "Selecione 1 ou mais semanas",
- "period": "O período deve ser um número entre 1 e 365",
+ "period": "O período deve ser um número entre 1 e 1000",
"first_week": "A primeira semana deve ser um número entre 1 e 52",
"date": "Formato de data inválido!"
},
@@ -77,7 +77,7 @@
"collection_days": "Dias de coleta",
"first_month": "Primeiro mês de coleta",
"last_month": "Último mês de coleta",
- "period": "Coleta a cada n semanas/dias: (1-365)",
+ "period": "Coleta a cada n semanas/dias: (1-1000)",
"first_week": "Primeira semana de coleta (1-52)",
"first_date": "Primeira data",
"weekday_order_number": "Ordem do dia da semana no mês (por exemplo, primeira quarta-feira do mês)",
@@ -96,7 +96,7 @@
"time": "Formato de hora inválido!",
"weekday_order_number": "Selecione 1 ou mais dias",
"week_order_number": "Selecione 1 ou mais semanas",
- "period": "O período deve ser um número entre 1 e 365",
+ "period": "O período deve ser um número entre 1 e 1000",
"first_week": "A primeira semana deve ser um número entre 1 e 52",
"date": "Formato de data inválido!"
}
diff --git a/custom_components/garbage_collection/translations/sensor.dk.json b/custom_components/garbage_collection/translations/sensor.da.json
similarity index 53%
rename from custom_components/garbage_collection/translations/sensor.dk.json
rename to custom_components/garbage_collection/translations/sensor.da.json
index 4dffc6c6..2141243a 100644
--- a/custom_components/garbage_collection/translations/sensor.dk.json
+++ b/custom_components/garbage_collection/translations/sensor.da.json
@@ -1,8 +1,8 @@
{
"state": {
"garbage_collection__schedule": {
- "today": "i dag",
- "tomorrow": "i morgen"
+ "today": "I dag",
+ "tomorrow": "I morgen"
}
}
}
\ No newline at end of file
diff --git a/custom_components/garbage_collection/translations/sk.json b/custom_components/garbage_collection/translations/sk.json
index c698d26b..c4a03029 100644
--- a/custom_components/garbage_collection/translations/sk.json
+++ b/custom_components/garbage_collection/translations/sk.json
@@ -48,7 +48,7 @@
"data": {
"first_month": "Prvý mesiac zvozu",
"last_month": "Posledný mesiac zvozu",
- "period": "Perióda (zvoz každých n týždňov/dní): (1-365)",
+ "period": "Perióda (zvoz každých n týždňov/dní): (1-1000)",
"first_week": "Prvý týždeň zvozu (1-52)",
"first_date": "Prvý dátum",
"weekday_order_number_1": "Prvý deň v mesiaci",
@@ -81,7 +81,7 @@
"time": "Zlý formát času!",
"weekday_order_number": "Vyber jeden nebo viacej dní!",
"week_order_number": "Vyber jeden nebo viacej týždňov!",
- "period": "Perióda musí být číslo medzi 1 a 365",
+ "period": "Perióda musí být číslo medzi 1 a 1000",
"first_week": "Prvý týždeň musí být číslo medzi 1 a 52",
"date": "Zlý formát dátumu!"
},
@@ -138,7 +138,7 @@
"data": {
"first_month": "Prvý mesiac zvozu",
"last_month": "Posledný mesiac zvozu",
- "period": "Perióda (zvoz každých n týždňov/dní): (1-365)",
+ "period": "Perióda (zvoz každých n týždňov/dní): (1-1000)",
"first_week": "Prvý týždeň zvozu (1-52)",
"first_date": "Prvý dátum",
"weekday_order_number_1": "Prvý deň v mesiaci",
@@ -170,7 +170,7 @@
"time": "Zlý formát času!",
"weekday_order_number": "Vyber jeden nebo viacej dní!",
"week_order_number": "Vyber jeden nebo viacej týždňov!",
- "period": "Perióda musí být číslo medzi 1 a 365",
+ "period": "Perióda musí být číslo medzi 1 a 1000",
"first_week": "Prvý týždeň musí být číslo medzi 1 a 52",
"date": "Zlý formát dátumu!"
}
diff --git a/custom_components/garbage_collection/translations/sl.json b/custom_components/garbage_collection/translations/sl.json
index 44c63387..592e71d1 100644
--- a/custom_components/garbage_collection/translations/sl.json
+++ b/custom_components/garbage_collection/translations/sl.json
@@ -49,7 +49,7 @@
"data": {
"first_month": "Prvi mesec odvoza",
"last_month": "Zadnji mesec odvoza",
- "period": "Odvoz vsakih v tednov/dni: (1-365)",
+ "period": "Odvoz vsakih v tednov/dni: (1-1000)",
"first_week": "Prvi teden odvoza (1-52)",
"first_date": "Prvi datum",
"weekday_order_number_1": "1. dan tedna v mesecu",
@@ -81,7 +81,7 @@
"time": "Neveljaven format časa!",
"weekday_order_number": "Izberite enega ali več dni",
"week_order_number": "Izberite enega ali več tednov",
- "period": "Interval mora biti številka med 1 in 365",
+ "period": "Interval mora biti številka med 1 in 1000",
"first_week": "Prvi teden mora biti številka med 1 in 52",
"date": "Neveljaven format datuma!"
},
@@ -138,7 +138,7 @@
"data": {
"first_month": "Prvi mesec odvoza",
"last_month": "Zdnji mesec odvoza",
- "period": "Odvoz vsakih n tednov/dni: (1-365)",
+ "period": "Odvoz vsakih n tednov/dni: (1-1000)",
"first_week": "Prvi teden odvoza (1-52)",
"first_date": "Prvi datum",
"weekday_order_number_1": "1. dan tedna v mesecu",
@@ -170,7 +170,7 @@
"time": "Neveljaven format časa!",
"weekday_order_number": "Izberite enega ali več dni",
"week_order_number": "Izberite enega ali več tednov",
- "period": "Interval mora biti številka med 1 in 365",
+ "period": "Interval mora biti številka med 1 in 1000",
"first_week": "Prvi teden mora biti številka med 1 in 52",
"date": "Neveljaven format datuma!"
}
diff --git a/custom_components/grocy/__init__.py b/custom_components/grocy/__init__.py
index 7790f816..2f16a702 100644
--- a/custom_components/grocy/__init__.py
+++ b/custom_components/grocy/__init__.py
@@ -4,17 +4,17 @@
For more details about this integration, please refer to
https://github.com/custom-components/grocy
"""
-import asyncio
import logging
from datetime import timedelta
-from typing import List
+from typing import Any, List
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Config, HomeAssistant
-from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from pygrocy import Grocy
+from .helpers import extract_base_url_and_path
+
from .const import (
CONF_API_KEY,
CONF_PORT,
@@ -33,11 +33,6 @@
_LOGGER = logging.getLogger(__name__)
-async def async_setup(_hass: HomeAssistant, _config: Config):
- """Set up this integration using YAML is not supported."""
- return True
-
-
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Set up this integration using UI."""
hass.data.setdefault(DOMAIN, {})
@@ -51,10 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
config_entry.data[CONF_VERIFY_SSL],
)
- await coordinator.async_refresh()
-
- if not coordinator.last_update_success:
- raise ConfigEntryNotReady
+ await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN] = coordinator
@@ -68,7 +60,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
# Setup http endpoint for proxying images from grocy
await async_setup_image_api(hass, config_entry.data)
- config_entry.add_update_listener(async_reload_entry)
return True
@@ -78,7 +69,10 @@ class GrocyDataUpdateCoordinator(DataUpdateCoordinator):
def __init__(self, hass, url, api_key, port_number, verify_ssl):
"""Initialize."""
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
- self.api = Grocy(url, api_key, port_number, verify_ssl)
+ (base_url, path) = extract_base_url_and_path(url)
+ self.api = Grocy(
+ base_url, api_key, path=path, port=port_number, verify_ssl=verify_ssl
+ )
self.entities = []
self.data = {}
@@ -86,82 +80,75 @@ async def _async_update_data(self):
"""Update data via library."""
grocy_data = GrocyData(self.hass, self.api)
data = {}
- features = []
- try:
- features = await async_supported_features(grocy_data)
- if not features:
- raise UpdateFailed("No features enabled")
- except Exception as exception:
- raise UpdateFailed(exception)
+ features = await async_supported_features(grocy_data)
+ if not features:
+ raise UpdateFailed("No features enabled")
for entity in self.entities:
- if entity.enabled and entity.entity_type in features:
- try:
- data[entity.entity_type] = await grocy_data.async_update_data(
- entity.entity_type
- )
- except Exception as exception:
- _LOGGER.error(
- f"Update of {entity.entity_type} failed with {exception}"
- )
- elif entity.entity_type not in features:
- _LOGGER.warning(
- f"You have enabled the entity for {entity.name}, but this feature is not enabled in Grocy",
+ if not entity.enabled:
+ continue
+ if not entity.entity_type in features:
+ _LOGGER.debug(
+ "You have enabled the entity for '%s', but this feature is not enabled in Grocy",
+ entity.name,
+ )
+ continue
+
+ try:
+ data[entity.entity_type] = await grocy_data.async_update_data(
+ entity.entity_type
+ )
+ except Exception as exception: # pylint: disable=broad-except
+ _LOGGER.error(
+ "Update of %s failed with %s",
+ entity.entity_type,
+ exception,
)
return data
-async def async_supported_features(grocy_data) -> List[str]:
+async def async_supported_features(grocy_data: GrocyData) -> List[str]:
"""Return a list of supported features."""
features = []
config = await grocy_data.async_get_config()
if config:
- if config["FEATURE_FLAG_STOCK"] != "0":
+ if is_enabled_grocy_feature(config, "FEATURE_FLAG_STOCK"):
features.append(GrocyEntityType.STOCK)
features.append(GrocyEntityType.PRODUCTS)
features.append(GrocyEntityType.MISSING_PRODUCTS)
features.append(GrocyEntityType.EXPIRED_PRODUCTS)
features.append(GrocyEntityType.EXPIRING_PRODUCTS)
- if config["FEATURE_FLAG_SHOPPINGLIST"] != "0":
+ if is_enabled_grocy_feature(config, "FEATURE_FLAG_SHOPPINGLIST"):
features.append(GrocyEntityType.SHOPPING_LIST)
- if config["FEATURE_FLAG_TASKS"] != "0":
+ if is_enabled_grocy_feature(config, "FEATURE_FLAG_TASKS"):
features.append(GrocyEntityType.TASKS)
features.append(GrocyEntityType.OVERDUE_TASKS)
- if config["FEATURE_FLAG_CHORES"] != "0":
+ if is_enabled_grocy_feature(config, "FEATURE_FLAG_CHORES"):
features.append(GrocyEntityType.CHORES)
features.append(GrocyEntityType.OVERDUE_CHORES)
- if config["FEATURE_FLAG_RECIPES"] != "0":
+ if is_enabled_grocy_feature(config, "FEATURE_FLAG_RECIPES"):
features.append(GrocyEntityType.MEAL_PLAN)
return features
-async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
- """Handle removal of an entry."""
- _LOGGER.debug("Unloading with state %s", entry.state)
- if entry.state == "loaded":
- unloaded = all(
- await asyncio.gather(
- *[
- hass.config_entries.async_forward_entry_unload(entry, platform)
- for platform in PLATFORMS
- ]
- )
- )
- _LOGGER.debug("Unloaded? %s", unloaded)
- del hass.data[DOMAIN]
- return unloaded
- return False
+def is_enabled_grocy_feature(grocy_config: Any, feature_setting_key: str) -> bool:
+ """
+ Return whether the Grocy feature is enabled or not, default is enabled.
+ Setting value received from Grocy can be a str or bool.
+ """
+ feature_setting_value = grocy_config[feature_setting_key]
+ return feature_setting_value not in (False, "0")
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+ """Unload a config entry."""
+ await async_unload_services(hass)
+ if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
+ del hass.data[DOMAIN]
-async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry):
- """Reload config entry."""
- unloaded = await async_unload_entry(hass, entry)
- _LOGGER.error("Unloaded successfully: %s", unloaded)
- if unloaded:
- await async_setup_entry(hass, entry)
- await async_unload_services(hass)
+ return unloaded
diff --git a/custom_components/grocy/config_flow.py b/custom_components/grocy/config_flow.py
index 36d31839..f0ad7994 100644
--- a/custom_components/grocy/config_flow.py
+++ b/custom_components/grocy/config_flow.py
@@ -6,6 +6,8 @@
from homeassistant import config_entries
from pygrocy import Grocy
+from .helpers import extract_base_url_and_path
+
from .const import (
CONF_API_KEY,
CONF_PORT, # pylint: disable=unused-import
@@ -59,25 +61,35 @@ async def _show_config_form(self, user_input): # pylint: disable=unused-argumen
"""Show the configuration form to edit the data."""
data_schema = OrderedDict()
data_schema[vol.Required(CONF_URL, default="")] = str
- data_schema[vol.Required(CONF_API_KEY, default="",)] = str
+ data_schema[
+ vol.Required(
+ CONF_API_KEY,
+ default="",
+ )
+ ] = str
data_schema[vol.Optional(CONF_PORT, default=DEFAULT_PORT)] = int
data_schema[vol.Optional(CONF_VERIFY_SSL, default=False)] = bool
_LOGGER.debug("config form")
return self.async_show_form(
- step_id="user", data_schema=vol.Schema(data_schema), errors=self._errors,
+ step_id="user",
+ data_schema=vol.Schema(data_schema),
+ errors=self._errors,
)
async def _test_credentials(self, url, api_key, port, verify_ssl):
"""Return true if credentials is valid."""
try:
- client = Grocy(url, api_key, port, verify_ssl)
+ (base_url, path) = extract_base_url_and_path(url)
+ client = Grocy(
+ base_url, api_key, port=port, path=path, verify_ssl=verify_ssl
+ )
_LOGGER.debug("Testing credentials")
def system_info():
"""Get system information from Grocy."""
- client._api_client._do_get_request("/api/system/info")
+ client._api_client._do_get_request("system/info")
await self.hass.async_add_executor_job(system_info)
return True
diff --git a/custom_components/grocy/const.py b/custom_components/grocy/const.py
index 4abdf53c..da482250 100644
--- a/custom_components/grocy/const.py
+++ b/custom_components/grocy/const.py
@@ -4,7 +4,7 @@
# Base component constants
NAME = "Grocy"
DOMAIN = "grocy"
-VERSION = "v4.2.2b"
+VERSION = "4.3.4"
ISSUE_URL = "https://github.com/custom-components/grocy/issues"
@@ -73,4 +73,3 @@ class GrocyEntityIcon(str, Enum):
SHOPPING_LIST = "mdi:cart-outline"
STOCK = "mdi:fridge-outline"
TASKS = "mdi:checkbox-marked-circle-outline"
-
diff --git a/custom_components/grocy/entity.py b/custom_components/grocy/entity.py
index adc15075..f5c0fe0a 100644
--- a/custom_components/grocy/entity.py
+++ b/custom_components/grocy/entity.py
@@ -1,9 +1,8 @@
"""GrocyEntity class"""
import json
-from homeassistant.const import MAJOR_VERSION, MINOR_VERSION
-from homeassistant.helpers import entity
-from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
+from homeassistant.helpers.device_registry import DeviceEntryType
+from homeassistant.helpers.update_coordinator import CoordinatorEntity
# pylint: disable=relative-beyond-top-level
from .const import (
@@ -17,50 +16,11 @@
from .json_encode import GrocyJSONEncoder
-class GrocyCoordinatorEntity(entity.Entity):
- """
- CoordinatorEntity was added to HA in 0.115, this is a copy of the
- class CoordinatorEntity from homeassistant.helpers.update_coordinator
+class GrocyEntity(CoordinatorEntity):
+ """Base class for Grocy entities."""
- Remove this class and use CoordinatorEntity instead when grocy require min version 0.115
- """
-
- def __init__(self, coordinator: DataUpdateCoordinator) -> None:
- """Create the entity with a DataUpdateCoordinator."""
- self.coordinator = coordinator
-
- @property
- def should_poll(self) -> bool:
- """No need to poll. Coordinator notifies entity of updates."""
- return False
-
- @property
- def available(self) -> bool:
- """Return if entity is available."""
- return self.coordinator.last_update_success
-
- async def async_added_to_hass(self) -> None:
- """When entity is added to hass."""
- await super().async_added_to_hass()
- self.async_on_remove(
- self.coordinator.async_add_listener(self.async_write_ha_state)
- )
-
- async def async_update(self) -> None:
- """Update the entity.
-
- Only used by the generic entity update service.
- """
-
- # Ignore manual update requests if the entity is disabled
- if not self.enabled:
- return
-
- await self.coordinator.async_request_refresh()
-
-
-class GrocyEntity(GrocyCoordinatorEntity):
def __init__(self, coordinator, config_entry, entity_type):
+ """Initialize generic Grocy entity."""
super().__init__(coordinator)
self.config_entry = config_entry
self.entity_type = entity_type
@@ -103,22 +63,13 @@ def icon(self):
@property
def device_info(self):
- info = {
- # "identifiers": {(DOMAIN, self.unique_id)},
+ return {
"identifiers": {(DOMAIN, self.config_entry.entry_id)},
"name": NAME,
"model": VERSION,
"manufacturer": NAME,
+ "entry_type": DeviceEntryType.SERVICE,
}
- # LEGACY can be removed when min HA version is 2021.12
- if (MAJOR_VERSION > 2021) or (MAJOR_VERSION == 2021 and MINOR_VERSION >= 12):
- # pylint: disable=import-outside-toplevel
- from homeassistant.helpers.device_registry import DeviceEntryType
-
- info["entry_type"] = DeviceEntryType.SERVICE
- else:
- info["entry_type"] = "service"
- return info
@property
def extra_state_attributes(self):
diff --git a/custom_components/grocy/grocy_data.py b/custom_components/grocy/grocy_data.py
index 7dc7e38c..de274a65 100644
--- a/custom_components/grocy/grocy_data.py
+++ b/custom_components/grocy/grocy_data.py
@@ -1,7 +1,6 @@
from aiohttp import hdrs, web
-from datetime import timedelta, datetime
+from datetime import datetime
import logging
-import pytz
from homeassistant.components.http import HomeAssistantView
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -12,13 +11,10 @@
CONF_PORT,
GrocyEntityType,
)
-from .helpers import MealPlanItem
+from .helpers import MealPlanItem, extract_base_url_and_path
-MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
_LOGGER = logging.getLogger(__name__)
-utc = pytz.UTC
-
class GrocyData:
"""This class handle communication and stores the data."""
@@ -87,8 +83,8 @@ def wrapper():
overdue_chores = []
for chore in chores:
if chore.next_estimated_execution_time:
- now = datetime.now().replace(tzinfo=utc)
- due = chore.next_estimated_execution_time.replace(tzinfo=utc)
+ now = datetime.now()
+ due = chore.next_estimated_execution_time
if due < now:
overdue_chores.append(chore)
return overdue_chores
@@ -97,7 +93,7 @@ async def async_get_config(self):
"""Get the configuration from Grocy."""
def wrapper():
- return self.client._api_client._do_get_request("/api/system/config")
+ return self.client._api_client._do_get_request("system/config")
return await self.hass.async_add_executor_job(wrapper)
@@ -132,7 +128,7 @@ async def async_update_expiring_products(self):
"""Update data."""
# This is where the main logic to update platform data goes.
def wrapper():
- return self.client.expiring_products(True)
+ return self.client.due_products(True)
return await self.hass.async_add_executor_job(wrapper)
@@ -165,13 +161,20 @@ def wrapper():
async def async_setup_image_api(hass, config):
+ """Setup and register the image api for grocy images with HA."""
session = async_get_clientsession(hass)
url = config.get(CONF_URL)
+ (grocy_base_url, grocy_path) = extract_base_url_and_path(url)
api_key = config.get(CONF_API_KEY)
port_number = config.get(CONF_PORT)
- base_url = f"{url}:{port_number}"
- hass.http.register_view(GrocyPictureView(session, base_url, api_key))
+ if grocy_path:
+ grocy_full_url = f"{grocy_base_url}:{port_number}/{grocy_path}"
+ else:
+ grocy_full_url = f"{grocy_base_url}:{port_number}"
+
+ _LOGGER.debug("Generated image api url to grocy: '%s'", grocy_full_url)
+ hass.http.register_view(GrocyPictureView(session, grocy_full_url, api_key))
class GrocyPictureView(HomeAssistantView):
diff --git a/custom_components/grocy/helpers.py b/custom_components/grocy/helpers.py
index 168e21e5..100c98e9 100644
--- a/custom_components/grocy/helpers.py
+++ b/custom_components/grocy/helpers.py
@@ -1,4 +1,12 @@
import base64
+from urllib.parse import urlparse
+from typing import Tuple
+
+
+def extract_base_url_and_path(url: str) -> Tuple[str, str]:
+ """Extract the base url and path from a given URL."""
+ parsed_url = urlparse(url)
+ return (f"{parsed_url.scheme}://{parsed_url.netloc}", parsed_url.path.strip("/"))
class MealPlanItem(object):
diff --git a/custom_components/grocy/manifest.json b/custom_components/grocy/manifest.json
index 9b0a6785..03a6048c 100644
--- a/custom_components/grocy/manifest.json
+++ b/custom_components/grocy/manifest.json
@@ -12,10 +12,8 @@
"@isabellaalstrom"
],
"requirements": [
- "pygrocy==1.0.0",
- "iso8601==0.1.12",
- "integrationhelper==0.2.2"
+ "pygrocy==1.3.0"
],
- "version": "v3.0.1",
+ "version": "4.3.4",
"iot_class": "local_polling"
}
diff --git a/custom_components/grocy/services.py b/custom_components/grocy/services.py
index 946b9bad..de18f0de 100644
--- a/custom_components/grocy/services.py
+++ b/custom_components/grocy/services.py
@@ -1,23 +1,22 @@
"""Grocy services."""
+from __future__ import annotations
+
import asyncio
import voluptuous as vol
-import iso8601
-from homeassistant.helpers import config_validation as cv
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import entity_component
-from pygrocy import TransactionType
-from pygrocy import EntityType
-from datetime import datetime
+from pygrocy import TransactionType, EntityType
# pylint: disable=relative-beyond-top-level
from .const import DOMAIN
-GROCY_SERVICES = "grocy_services"
-
SERVICE_PRODUCT_ID = "product_id"
SERVICE_AMOUNT = "amount"
SERVICE_PRICE = "price"
SERVICE_SPOILED = "spoiled"
+SERVICE_SUBPRODUCT_SUBSTITUTION = "allow_subproduct_substitution"
SERVICE_TRANSACTION_TYPE = "transaction_type"
SERVICE_CHORE_ID = "chore_id"
SERVICE_DONE_BY = "done_by"
@@ -47,6 +46,7 @@
vol.Required(SERVICE_PRODUCT_ID): vol.Coerce(int),
vol.Required(SERVICE_AMOUNT): vol.Coerce(float),
vol.Optional(SERVICE_SPOILED): bool,
+ vol.Optional(SERVICE_SUBPRODUCT_SUBSTITUTION): bool,
vol.Optional(SERVICE_TRANSACTION_TYPE): str,
}
)
@@ -78,16 +78,22 @@
)
)
+SERVICES_WITH_ACCOMPANYING_SCHEMA: list[tuple[str, vol.Schema]] = [
+ (SERVICE_ADD_PRODUCT, SERVICE_ADD_PRODUCT_SCHEMA),
+ (SERVICE_CONSUME_PRODUCT, SERVICE_CONSUME_PRODUCT_SCHEMA),
+ (SERVICE_EXECUTE_CHORE, SERVICE_EXECUTE_CHORE_SCHEMA),
+ (SERVICE_COMPLETE_TASK, SERVICE_COMPLETE_TASK_SCHEMA),
+ (SERVICE_ADD_GENERIC, SERVICE_ADD_GENERIC_SCHEMA),
+]
-async def async_setup_services(hass, entry):
+
+async def async_setup_services(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Set up services for Grocy integration."""
coordinator = hass.data[DOMAIN]
- if hass.data.get(GROCY_SERVICES, False):
+ if hass.services.async_services().get(DOMAIN):
return
- hass.data[GROCY_SERVICES] = True
-
- async def async_call_grocy_service(service_call):
+ async def async_call_grocy_service(service_call: ServiceCall) -> None:
"""Call correct Grocy service."""
service = service_call.service
service_data = service_call.data
@@ -107,53 +113,17 @@ async def async_call_grocy_service(service_call):
elif service == SERVICE_ADD_GENERIC:
await async_add_generic_service(hass, coordinator, service_data)
- hass.services.async_register(
- DOMAIN,
- SERVICE_ADD_PRODUCT,
- async_call_grocy_service,
- schema=SERVICE_ADD_PRODUCT_SCHEMA,
- )
-
- hass.services.async_register(
- DOMAIN,
- SERVICE_CONSUME_PRODUCT,
- async_call_grocy_service,
- schema=SERVICE_CONSUME_PRODUCT_SCHEMA,
- )
-
- hass.services.async_register(
- DOMAIN,
- SERVICE_EXECUTE_CHORE,
- async_call_grocy_service,
- schema=SERVICE_EXECUTE_CHORE_SCHEMA,
- )
+ for service, schema in SERVICES_WITH_ACCOMPANYING_SCHEMA:
+ hass.services.async_register(DOMAIN, service, async_call_grocy_service, schema)
- hass.services.async_register(
- DOMAIN,
- SERVICE_COMPLETE_TASK,
- async_call_grocy_service,
- schema=SERVICE_COMPLETE_TASK_SCHEMA,
- )
- hass.services.async_register(
- DOMAIN,
- SERVICE_ADD_GENERIC,
- async_call_grocy_service,
- schema=SERVICE_ADD_GENERIC_SCHEMA,
- )
-
-
-async def async_unload_services(hass):
+async def async_unload_services(hass: HomeAssistant) -> None:
"""Unload Grocy services."""
- if not hass.data.get(GROCY_SERVICES):
+ if not hass.services.async_services().get(DOMAIN):
return
- hass.data[GROCY_SERVICES] = False
-
- hass.services.async_remove(DOMAIN, SERVICE_ADD_PRODUCT)
- hass.services.async_remove(DOMAIN, SERVICE_CONSUME_PRODUCT)
- hass.services.async_remove(DOMAIN, SERVICE_EXECUTE_CHORE)
- hass.services.async_remove(DOMAIN, SERVICE_COMPLETE_TASK)
+ for service, _ in SERVICES_WITH_ACCOMPANYING_SCHEMA:
+ hass.services.async_remove(DOMAIN, service)
async def async_add_product_service(hass, coordinator, data):
@@ -173,6 +143,7 @@ async def async_consume_product_service(hass, coordinator, data):
product_id = data[SERVICE_PRODUCT_ID]
amount = data[SERVICE_AMOUNT]
spoiled = data.get(SERVICE_SPOILED, False)
+ allow_subproduct_substitution = data.get(SERVICE_SUBPRODUCT_SUBSTITUTION, False)
transaction_type_raw = data.get(SERVICE_TRANSACTION_TYPE, None)
transaction_type = TransactionType.CONSUME
@@ -182,7 +153,11 @@ async def async_consume_product_service(hass, coordinator, data):
def wrapper():
coordinator.api.consume_product(
- product_id, amount, spoiled=spoiled, transaction_type=transaction_type
+ product_id,
+ amount,
+ spoiled=spoiled,
+ transaction_type=transaction_type,
+ allow_subproduct_substitution=allow_subproduct_substitution,
)
await hass.async_add_executor_job(wrapper)
diff --git a/custom_components/grocy/services.yaml b/custom_components/grocy/services.yaml
index 9bfd8dbb..72c44f90 100644
--- a/custom_components/grocy/services.yaml
+++ b/custom_components/grocy/services.yaml
@@ -54,6 +54,13 @@ consume_product_from_stock:
default: false
selector:
boolean:
+ allow_subproduct_substitution:
+ name: Subproduct substitution
+ description: If subproduct substitution is allowed
+ example: false
+ default: false
+ selector:
+ boolean:
transaction_type:
name: Transaction Type
description: The type of the transaction.
diff --git a/custom_components/hacs/__init__.py b/custom_components/hacs/__init__.py
index c5f625e0..770a56ff 100644
--- a/custom_components/hacs/__init__.py
+++ b/custom_components/hacs/__init__.py
@@ -23,13 +23,13 @@
from homeassistant.loader import async_get_integration
import voluptuous as vol
-from custom_components.hacs.frontend import async_register_frontend
-
from .base import HacsBase
from .const import DOMAIN, MINIMUM_HA_VERSION, STARTUP
from .enums import ConfigurationType, HacsDisabledReason, HacsStage, LovelaceMode
+from .frontend import async_register_frontend
from .utils.configuration_schema import hacs_config_combined
from .utils.data import HacsData
+from .utils.platform_setup import async_setup_entity_platforms
from .utils.queue_manager import QueueManager
from .utils.version import version_left_higher_or_equal_then_right
from .websocket import async_register_websocket_commands
@@ -169,14 +169,14 @@ async def async_startup():
hacs.log.info("Update entities are only supported when using UI configuration")
else:
- if hacs.configuration.experimental:
- hass.config_entries.async_setup_platforms(
- hacs.configuration.config_entry, [Platform.SENSOR, Platform.UPDATE]
- )
- else:
- hass.config_entries.async_setup_platforms(
- hacs.configuration.config_entry, [Platform.SENSOR]
- )
+ await async_setup_entity_platforms(
+ hacs,
+ hass,
+ config_entry,
+ [Platform.SENSOR, Platform.UPDATE]
+ if hacs.configuration.experimental
+ else [Platform.SENSOR],
+ )
hacs.set_stage(HacsStage.SETUP)
if hacs.system.disabled:
diff --git a/custom_components/hacs/base.py b/custom_components/hacs/base.py
index 7c32e35a..ebb0356b 100644
--- a/custom_components/hacs/base.py
+++ b/custom_components/hacs/base.py
@@ -5,7 +5,6 @@
from dataclasses import asdict, dataclass, field
from datetime import timedelta
import gzip
-import json
import logging
import math
import os
@@ -53,7 +52,9 @@
)
from .repositories import RERPOSITORY_CLASSES
from .utils.decode import decode_content
-from .utils.logger import get_hacs_logger
+from .utils.json import json_loads
+from .utils.logger import LOGGER
+from .utils.platform_setup import async_setup_entity_platforms
from .utils.queue_manager import QueueManager
from .utils.store import async_load_from_store, async_save_to_store
@@ -163,8 +164,6 @@ class HacsStatus:
startup: bool = True
new: bool = False
- reloading_data: bool = False
- upgrading_all: bool = False
@dataclass
@@ -347,7 +346,7 @@ class HacsBase:
githubapi: GitHubAPI | None = None
hass: HomeAssistant | None = None
integration: Integration | None = None
- log: logging.Logger = get_hacs_logger()
+ log: logging.Logger = LOGGER
queue: QueueManager | None = None
recuring_tasks = []
repositories: HacsRepositories = HacsRepositories()
@@ -473,7 +472,7 @@ async def async_github_get_hacs_default_file(self, filename: str) -> list:
if response is None:
return []
- return json.loads(decode_content(response.data.content))
+ return json_loads(decode_content(response.data.content))
async def async_github_api_method(
self,
@@ -732,7 +731,9 @@ async def async_recreate_entities(self) -> None:
platforms=platforms,
)
- self.hass.config_entries.async_setup_platforms(self.configuration.config_entry, platforms)
+ await async_setup_entity_platforms(
+ self, self.hass, self.configuration.config_entry, platforms
+ )
@callback
def async_dispatch(self, signal: HacsDispatchEvent, data: dict | None = None) -> None:
diff --git a/custom_components/hacs/config_flow.py b/custom_components/hacs/config_flow.py
index 3529af3b..1227e2ab 100644
--- a/custom_components/hacs/config_flow.py
+++ b/custom_components/hacs/config_flow.py
@@ -14,7 +14,7 @@
from .const import CLIENT_ID, DOMAIN, MINIMUM_HA_VERSION
from .enums import ConfigurationType
from .utils.configuration_schema import RELEASE_LIMIT, hacs_config_option_schema
-from .utils.logger import get_hacs_logger
+from .utils.logger import LOGGER
class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@@ -28,7 +28,7 @@ def __init__(self):
self._errors = {}
self.device = None
self.activation = None
- self.log = get_hacs_logger()
+ self.log = LOGGER
self._progress_task = None
self._login_device = None
self._reauth = False
diff --git a/custom_components/hacs/hacs_frontend/c.01f18260.js b/custom_components/hacs/hacs_frontend/c.01f18260.js
new file mode 100644
index 00000000..a583ec91
--- /dev/null
+++ b/custom_components/hacs/hacs_frontend/c.01f18260.js
@@ -0,0 +1,147 @@
+import{u as e,v as t,G as i,M as c,_ as o,i as r,e as n,t as a,B as d,$ as s,o as p,I as l,y as m,p as h,q as u,r as f,n as b,a as g,h as k,J as x,K as _,g as y,w as v,R as T,j as w,A as E}from"./main-7bc9a818.js";import{c as O,o as I}from"./c.5d9598b2.js";import{o as C}from"./c.8e28b461.js";var A,R,S={ANCHOR:"mdc-menu-surface--anchor",ANIMATING_CLOSED:"mdc-menu-surface--animating-closed",ANIMATING_OPEN:"mdc-menu-surface--animating-open",FIXED:"mdc-menu-surface--fixed",IS_OPEN_BELOW:"mdc-menu-surface--is-open-below",OPEN:"mdc-menu-surface--open",ROOT:"mdc-menu-surface"},F={CLOSED_EVENT:"MDCMenuSurface:closed",CLOSING_EVENT:"MDCMenuSurface:closing",OPENED_EVENT:"MDCMenuSurface:opened",FOCUSABLE_ELEMENTS:["button:not(:disabled)",'[href]:not([aria-disabled="true"])',"input:not(:disabled)","select:not(:disabled)","textarea:not(:disabled)",'[tabindex]:not([tabindex="-1"]):not([aria-disabled="true"])'].join(", ")},B={TRANSITION_OPEN_DURATION:120,TRANSITION_CLOSE_DURATION:75,MARGIN_TO_EDGE:32,ANCHOR_TO_MENU_SURFACE_WIDTH_RATIO:.67,TOUCH_EVENT_WAIT_MS:30};!function(e){e[e.BOTTOM=1]="BOTTOM",e[e.CENTER=2]="CENTER",e[e.RIGHT=4]="RIGHT",e[e.FLIP_RTL=8]="FLIP_RTL"}(A||(A={})),function(e){e[e.TOP_LEFT=0]="TOP_LEFT",e[e.TOP_RIGHT=4]="TOP_RIGHT",e[e.BOTTOM_LEFT=1]="BOTTOM_LEFT",e[e.BOTTOM_RIGHT=5]="BOTTOM_RIGHT",e[e.TOP_START=8]="TOP_START",e[e.TOP_END=12]="TOP_END",e[e.BOTTOM_START=9]="BOTTOM_START",e[e.BOTTOM_END=13]="BOTTOM_END"}(R||(R={}));var M=function(c){function o(e){var i=c.call(this,t(t({},o.defaultAdapter),e))||this;return i.isSurfaceOpen=!1,i.isQuickOpen=!1,i.isHoistedElement=!1,i.isFixedPosition=!1,i.isHorizontallyCenteredOnViewport=!1,i.maxHeight=0,i.openBottomBias=0,i.openAnimationEndTimerId=0,i.closeAnimationEndTimerId=0,i.animationRequestId=0,i.anchorCorner=R.TOP_START,i.originCorner=R.TOP_START,i.anchorMargin={top:0,right:0,bottom:0,left:0},i.position={x:0,y:0},i}return e(o,c),Object.defineProperty(o,"cssClasses",{get:function(){return S},enumerable:!1,configurable:!0}),Object.defineProperty(o,"strings",{get:function(){return F},enumerable:!1,configurable:!0}),Object.defineProperty(o,"numbers",{get:function(){return B},enumerable:!1,configurable:!0}),Object.defineProperty(o,"Corner",{get:function(){return R},enumerable:!1,configurable:!0}),Object.defineProperty(o,"defaultAdapter",{get:function(){return{addClass:function(){},removeClass:function(){},hasClass:function(){return!1},hasAnchor:function(){return!1},isElementInContainer:function(){return!1},isFocused:function(){return!1},isRtl:function(){return!1},getInnerDimensions:function(){return{height:0,width:0}},getAnchorDimensions:function(){return null},getWindowDimensions:function(){return{height:0,width:0}},getBodyDimensions:function(){return{height:0,width:0}},getWindowScroll:function(){return{x:0,y:0}},setPosition:function(){},setMaxHeight:function(){},setTransformOrigin:function(){},saveFocus:function(){},restoreFocus:function(){},notifyClose:function(){},notifyOpen:function(){},notifyClosing:function(){}}},enumerable:!1,configurable:!0}),o.prototype.init=function(){var e=o.cssClasses,t=e.ROOT,i=e.OPEN;if(!this.adapter.hasClass(t))throw new Error(t+" class required in root element.");this.adapter.hasClass(i)&&(this.isSurfaceOpen=!0)},o.prototype.destroy=function(){clearTimeout(this.openAnimationEndTimerId),clearTimeout(this.closeAnimationEndTimerId),cancelAnimationFrame(this.animationRequestId)},o.prototype.setAnchorCorner=function(e){this.anchorCorner=e},o.prototype.flipCornerHorizontally=function(){this.originCorner=this.originCorner^A.RIGHT},o.prototype.setAnchorMargin=function(e){this.anchorMargin.top=e.top||0,this.anchorMargin.right=e.right||0,this.anchorMargin.bottom=e.bottom||0,this.anchorMargin.left=e.left||0},o.prototype.setIsHoisted=function(e){this.isHoistedElement=e},o.prototype.setFixedPosition=function(e){this.isFixedPosition=e},o.prototype.isFixed=function(){return this.isFixedPosition},o.prototype.setAbsolutePosition=function(e,t){this.position.x=this.isFinite(e)?e:0,this.position.y=this.isFinite(t)?t:0},o.prototype.setIsHorizontallyCenteredOnViewport=function(e){this.isHorizontallyCenteredOnViewport=e},o.prototype.setQuickOpen=function(e){this.isQuickOpen=e},o.prototype.setMaxHeight=function(e){this.maxHeight=e},o.prototype.setOpenBottomBias=function(e){this.openBottomBias=e},o.prototype.isOpen=function(){return this.isSurfaceOpen},o.prototype.open=function(){var e=this;this.isSurfaceOpen||(this.adapter.saveFocus(),this.isQuickOpen?(this.isSurfaceOpen=!0,this.adapter.addClass(o.cssClasses.OPEN),this.dimensions=this.adapter.getInnerDimensions(),this.autoposition(),this.adapter.notifyOpen()):(this.adapter.addClass(o.cssClasses.ANIMATING_OPEN),this.animationRequestId=requestAnimationFrame((function(){e.dimensions=e.adapter.getInnerDimensions(),e.autoposition(),e.adapter.addClass(o.cssClasses.OPEN),e.openAnimationEndTimerId=setTimeout((function(){e.openAnimationEndTimerId=0,e.adapter.removeClass(o.cssClasses.ANIMATING_OPEN),e.adapter.notifyOpen()}),B.TRANSITION_OPEN_DURATION)})),this.isSurfaceOpen=!0))},o.prototype.close=function(e){var t=this;if(void 0===e&&(e=!1),this.isSurfaceOpen){if(this.adapter.notifyClosing(),this.isQuickOpen)return this.isSurfaceOpen=!1,e||this.maybeRestoreFocus(),this.adapter.removeClass(o.cssClasses.OPEN),this.adapter.removeClass(o.cssClasses.IS_OPEN_BELOW),void this.adapter.notifyClose();this.adapter.addClass(o.cssClasses.ANIMATING_CLOSED),requestAnimationFrame((function(){t.adapter.removeClass(o.cssClasses.OPEN),t.adapter.removeClass(o.cssClasses.IS_OPEN_BELOW),t.closeAnimationEndTimerId=setTimeout((function(){t.closeAnimationEndTimerId=0,t.adapter.removeClass(o.cssClasses.ANIMATING_CLOSED),t.adapter.notifyClose()}),B.TRANSITION_CLOSE_DURATION)})),this.isSurfaceOpen=!1,e||this.maybeRestoreFocus()}},o.prototype.handleBodyClick=function(e){var t=e.target;this.adapter.isElementInContainer(t)||this.close()},o.prototype.handleKeydown=function(e){var t=e.keyCode;("Escape"===e.key||27===t)&&this.close()},o.prototype.autoposition=function(){var e;this.measurements=this.getAutoLayoutmeasurements();var t=this.getoriginCorner(),i=this.getMenuSurfaceMaxHeight(t),c=this.hasBit(t,A.BOTTOM)?"bottom":"top",r=this.hasBit(t,A.RIGHT)?"right":"left",n=this.getHorizontalOriginOffset(t),a=this.getVerticalOriginOffset(t),d=this.measurements,s=d.anchorSize,p=d.surfaceSize,l=((e={})[r]=n,e[c]=a,e);s.width/p.width>B.ANCHOR_TO_MENU_SURFACE_WIDTH_RATIO&&(r="center"),(this.isHoistedElement||this.isFixedPosition)&&this.adjustPositionForHoistedElement(l),this.adapter.setTransformOrigin(r+" "+c),this.adapter.setPosition(l),this.adapter.setMaxHeight(i?i+"px":""),this.hasBit(t,A.BOTTOM)||this.adapter.addClass(o.cssClasses.IS_OPEN_BELOW)},o.prototype.getAutoLayoutmeasurements=function(){var e=this.adapter.getAnchorDimensions(),t=this.adapter.getBodyDimensions(),i=this.adapter.getWindowDimensions(),c=this.adapter.getWindowScroll();return e||(e={top:this.position.y,right:this.position.x,bottom:this.position.y,left:this.position.x,width:0,height:0}),{anchorSize:e,bodySize:t,surfaceSize:this.dimensions,viewportDistance:{top:e.top,right:i.width-e.right,bottom:i.height-e.bottom,left:e.left},viewportSize:i,windowScroll:c}},o.prototype.getoriginCorner=function(){var e,t,i=this.originCorner,c=this.measurements,r=c.viewportDistance,n=c.anchorSize,a=c.surfaceSize,d=o.numbers.MARGIN_TO_EDGE;this.hasBit(this.anchorCorner,A.BOTTOM)?(e=r.top-d+this.anchorMargin.bottom,t=r.bottom-d-this.anchorMargin.bottom):(e=r.top-d+this.anchorMargin.top,t=r.bottom-d+n.height-this.anchorMargin.top),!(t-a.height>0)&&e>t+this.openBottomBias&&(i=this.setBit(i,A.BOTTOM));var s,p,l=this.adapter.isRtl(),m=this.hasBit(this.anchorCorner,A.FLIP_RTL),h=this.hasBit(this.anchorCorner,A.RIGHT)||this.hasBit(i,A.RIGHT),u=!1;(u=l&&m?!h:h)?(s=r.left+n.width+this.anchorMargin.right,p=r.right-this.anchorMargin.right):(s=r.left+this.anchorMargin.left,p=r.right+n.width-this.anchorMargin.left);var f=s-a.width>0,b=p-a.width>0,g=this.hasBit(i,A.FLIP_RTL)&&this.hasBit(i,A.RIGHT);return b&&g&&l||!f&&g?i=this.unsetBit(i,A.RIGHT):(f&&u&&l||f&&!u&&h||!b&&s>=p)&&(i=this.setBit(i,A.RIGHT)),i},o.prototype.getMenuSurfaceMaxHeight=function(e){if(this.maxHeight>0)return this.maxHeight;var t=this.measurements.viewportDistance,i=0,c=this.hasBit(e,A.BOTTOM),r=this.hasBit(this.anchorCorner,A.BOTTOM),n=o.numbers.MARGIN_TO_EDGE;return c?(i=t.top+this.anchorMargin.top-n,r||(i+=this.measurements.anchorSize.height)):(i=t.bottom-this.anchorMargin.bottom+this.measurements.anchorSize.height-n,r&&(i-=this.measurements.anchorSize.height)),i},o.prototype.getHorizontalOriginOffset=function(e){var t=this.measurements.anchorSize,i=this.hasBit(e,A.RIGHT),c=this.hasBit(this.anchorCorner,A.RIGHT);if(i){var o=c?t.width-this.anchorMargin.left:this.anchorMargin.right;return this.isHoistedElement||this.isFixedPosition?o-(this.measurements.viewportSize.width-this.measurements.bodySize.width):o}return c?t.width-this.anchorMargin.right:this.anchorMargin.left},o.prototype.getVerticalOriginOffset=function(e){var t=this.measurements.anchorSize,i=this.hasBit(e,A.BOTTOM),c=this.hasBit(this.anchorCorner,A.BOTTOM);return i?c?t.height-this.anchorMargin.top:-this.anchorMargin.bottom:c?t.height+this.anchorMargin.bottom:this.anchorMargin.top},o.prototype.adjustPositionForHoistedElement=function(e){var t,c,o=this.measurements,r=o.windowScroll,n=o.viewportDistance,a=o.surfaceSize,d=o.viewportSize,s=Object.keys(e);try{for(var p=i(s),l=p.next();!l.done;l=p.next()){var m=l.value,h=e[m]||0;!this.isHorizontallyCenteredOnViewport||"left"!==m&&"right"!==m?(h+=n[m],this.isFixedPosition||("top"===m?h+=r.y:"bottom"===m?h-=r.y:"left"===m?h+=r.x:h-=r.x),e[m]=h):e[m]=(d.width-a.width)/2}}catch(e){t={error:e}}finally{try{l&&!l.done&&(c=p.return)&&c.call(p)}finally{if(t)throw t.error}}},o.prototype.maybeRestoreFocus=function(){var e=this,t=this.adapter.isFocused(),i=document.activeElement&&this.adapter.isElementInContainer(document.activeElement);(t||i)&&setTimeout((function(){e.adapter.restoreFocus()}),B.TOUCH_EVENT_WAIT_MS)},o.prototype.hasBit=function(e,t){return Boolean(e&t)},o.prototype.setBit=function(e,t){return e|t},o.prototype.unsetBit=function(e,t){return e^t},o.prototype.isFinite=function(e){return"number"==typeof e&&isFinite(e)},o}(c),z=M;const L={TOP_LEFT:R.TOP_LEFT,TOP_RIGHT:R.TOP_RIGHT,BOTTOM_LEFT:R.BOTTOM_LEFT,BOTTOM_RIGHT:R.BOTTOM_RIGHT,TOP_START:R.TOP_START,TOP_END:R.TOP_END,BOTTOM_START:R.BOTTOM_START,BOTTOM_END:R.BOTTOM_END};class N extends d{constructor(){super(...arguments),this.mdcFoundationClass=z,this.absolute=!1,this.fullwidth=!1,this.fixed=!1,this.x=null,this.y=null,this.quick=!1,this.open=!1,this.stayOpenOnBodyClick=!1,this.bitwiseCorner=R.TOP_START,this.previousMenuCorner=null,this.menuCorner="START",this.corner="TOP_START",this.styleTop="",this.styleLeft="",this.styleRight="",this.styleBottom="",this.styleMaxHeight="",this.styleTransformOrigin="",this.anchor=null,this.previouslyFocused=null,this.previousAnchor=null,this.onBodyClickBound=()=>{}}render(){const e={"mdc-menu-surface--fixed":this.fixed,"mdc-menu-surface--fullwidth":this.fullwidth},t={top:this.styleTop,left:this.styleLeft,right:this.styleRight,bottom:this.styleBottom,"max-height":this.styleMaxHeight,"transform-origin":this.styleTransformOrigin};return s`
+
`}createAdapter(){return Object.assign(Object.assign({},m(this.mdcRoot)),{hasAnchor:()=>!!this.anchor,notifyClose:()=>{const e=new CustomEvent("closed",{bubbles:!0,composed:!0});this.open=!1,this.mdcRoot.dispatchEvent(e)},notifyClosing:()=>{const e=new CustomEvent("closing",{bubbles:!0,composed:!0});this.mdcRoot.dispatchEvent(e)},notifyOpen:()=>{const e=new CustomEvent("opened",{bubbles:!0,composed:!0});this.open=!0,this.mdcRoot.dispatchEvent(e)},isElementInContainer:()=>!1,isRtl:()=>!!this.mdcRoot&&"rtl"===getComputedStyle(this.mdcRoot).direction,setTransformOrigin:e=>{this.mdcRoot&&(this.styleTransformOrigin=e)},isFocused:()=>h(this),saveFocus:()=>{const e=u(),t=e.length;t||(this.previouslyFocused=null),this.previouslyFocused=e[t-1]},restoreFocus:()=>{this.previouslyFocused&&"focus"in this.previouslyFocused&&this.previouslyFocused.focus()},getInnerDimensions:()=>{const e=this.mdcRoot;return e?{width:e.offsetWidth,height:e.offsetHeight}:{width:0,height:0}},getAnchorDimensions:()=>{const e=this.anchor;return e?e.getBoundingClientRect():null},getBodyDimensions:()=>({width:document.body.clientWidth,height:document.body.clientHeight}),getWindowDimensions:()=>({width:window.innerWidth,height:window.innerHeight}),getWindowScroll:()=>({x:window.pageXOffset,y:window.pageYOffset}),setPosition:e=>{this.mdcRoot&&(this.styleLeft="left"in e?`${e.left}px`:"",this.styleRight="right"in e?`${e.right}px`:"",this.styleTop="top"in e?`${e.top}px`:"",this.styleBottom="bottom"in e?`${e.bottom}px`:"")},setMaxHeight:async e=>{this.mdcRoot&&(this.styleMaxHeight=e,await this.updateComplete,this.styleMaxHeight=`var(--mdc-menu-max-height, ${e})`)}})}onKeydown(e){this.mdcFoundation&&this.mdcFoundation.handleKeydown(e)}onBodyClick(e){if(this.stayOpenOnBodyClick)return;-1===e.composedPath().indexOf(this)&&this.close()}registerBodyClick(){this.onBodyClickBound=this.onBodyClick.bind(this),document.body.addEventListener("click",this.onBodyClickBound,{passive:!0,capture:!0})}deregisterBodyClick(){document.body.removeEventListener("click",this.onBodyClickBound,{capture:!0})}close(){this.open=!1}show(){this.open=!0}}o([r(".mdc-menu-surface")],N.prototype,"mdcRoot",void 0),o([r("slot")],N.prototype,"slotElement",void 0),o([n({type:Boolean}),C((function(e){this.mdcFoundation&&!this.fixed&&this.mdcFoundation.setIsHoisted(e)}))],N.prototype,"absolute",void 0),o([n({type:Boolean})],N.prototype,"fullwidth",void 0),o([n({type:Boolean}),C((function(e){this.mdcFoundation&&!this.absolute&&this.mdcFoundation.setFixedPosition(e)}))],N.prototype,"fixed",void 0),o([n({type:Number}),C((function(e){this.mdcFoundation&&null!==this.y&&null!==e&&(this.mdcFoundation.setAbsolutePosition(e,this.y),this.mdcFoundation.setAnchorMargin({left:e,top:this.y,right:-e,bottom:this.y}))}))],N.prototype,"x",void 0),o([n({type:Number}),C((function(e){this.mdcFoundation&&null!==this.x&&null!==e&&(this.mdcFoundation.setAbsolutePosition(this.x,e),this.mdcFoundation.setAnchorMargin({left:this.x,top:e,right:-this.x,bottom:e}))}))],N.prototype,"y",void 0),o([n({type:Boolean}),C((function(e){this.mdcFoundation&&this.mdcFoundation.setQuickOpen(e)}))],N.prototype,"quick",void 0),o([n({type:Boolean,reflect:!0}),C((function(e,t){this.mdcFoundation&&(e?this.mdcFoundation.open():void 0!==t&&this.mdcFoundation.close())}))],N.prototype,"open",void 0),o([n({type:Boolean})],N.prototype,"stayOpenOnBodyClick",void 0),o([a(),C((function(e){this.mdcFoundation&&this.mdcFoundation.setAnchorCorner(e)}))],N.prototype,"bitwiseCorner",void 0),o([n({type:String}),C((function(e){if(this.mdcFoundation){const t="START"===e||"END"===e,i=null===this.previousMenuCorner,c=!i&&e!==this.previousMenuCorner,o=i&&"END"===e;t&&(c||o)&&(this.bitwiseCorner=this.bitwiseCorner^A.RIGHT,this.mdcFoundation.flipCornerHorizontally(),this.previousMenuCorner=e)}}))],N.prototype,"menuCorner",void 0),o([n({type:String}),C((function(e){if(this.mdcFoundation&&e){let t=L[e];"END"===this.menuCorner&&(t^=A.RIGHT),this.bitwiseCorner=t}}))],N.prototype,"corner",void 0),o([a()],N.prototype,"styleTop",void 0),o([a()],N.prototype,"styleLeft",void 0),o([a()],N.prototype,"styleRight",void 0),o([a()],N.prototype,"styleBottom",void 0),o([a()],N.prototype,"styleMaxHeight",void 0),o([a()],N.prototype,"styleTransformOrigin",void 0);const D=f`.mdc-menu-surface{display:none;position:absolute;box-sizing:border-box;max-width:calc(100vw - 32px);max-width:var(--mdc-menu-max-width, calc(100vw - 32px));max-height:calc(100vh - 32px);max-height:var(--mdc-menu-max-height, calc(100vh - 32px));margin:0;padding:0;transform:scale(1);transform-origin:top left;opacity:0;overflow:auto;will-change:transform,opacity;z-index:8;transition:opacity .03s linear,transform .12s cubic-bezier(0, 0, 0.2, 1),height 250ms cubic-bezier(0, 0, 0.2, 1);box-shadow:0px 5px 5px -3px rgba(0, 0, 0, 0.2),0px 8px 10px 1px rgba(0, 0, 0, 0.14),0px 3px 14px 2px rgba(0,0,0,.12);background-color:#fff;background-color:var(--mdc-theme-surface, #fff);color:#000;color:var(--mdc-theme-on-surface, #000);border-radius:4px;border-radius:var(--mdc-shape-medium, 4px);transform-origin-left:top left;transform-origin-right:top right}.mdc-menu-surface:focus{outline:none}.mdc-menu-surface--animating-open{display:inline-block;transform:scale(0.8);opacity:0}.mdc-menu-surface--open{display:inline-block;transform:scale(1);opacity:1}.mdc-menu-surface--animating-closed{display:inline-block;opacity:0;transition:opacity .075s linear}[dir=rtl] .mdc-menu-surface,.mdc-menu-surface[dir=rtl]{transform-origin-left:top right;transform-origin-right:top left}.mdc-menu-surface--anchor{position:relative;overflow:visible}.mdc-menu-surface--fixed{position:fixed}.mdc-menu-surface--fullwidth{width:100%}:host(:not([open])){display:none}.mdc-menu-surface{z-index:8;z-index:var(--mdc-menu-z-index, 8);min-width:112px;min-width:var(--mdc-menu-min-width, 112px)}`;let H=class extends N{};H.styles=[D],H=o([b("mwc-menu-surface")],H);var P,$={MENU_SELECTED_LIST_ITEM:"mdc-menu-item--selected",MENU_SELECTION_GROUP:"mdc-menu__selection-group",ROOT:"mdc-menu"},G={ARIA_CHECKED_ATTR:"aria-checked",ARIA_DISABLED_ATTR:"aria-disabled",CHECKBOX_SELECTOR:'input[type="checkbox"]',LIST_SELECTOR:".mdc-list,.mdc-deprecated-list",SELECTED_EVENT:"MDCMenu:selected",SKIP_RESTORE_FOCUS:"data-menu-item-skip-restore-focus"},U={FOCUS_ROOT_INDEX:-1};!function(e){e[e.NONE=0]="NONE",e[e.LIST_ROOT=1]="LIST_ROOT",e[e.FIRST_ITEM=2]="FIRST_ITEM",e[e.LAST_ITEM=3]="LAST_ITEM"}(P||(P={}));var j=function(i){function c(e){var o=i.call(this,t(t({},c.defaultAdapter),e))||this;return o.closeAnimationEndTimerId=0,o.defaultFocusState=P.LIST_ROOT,o.selectedIndex=-1,o}return e(c,i),Object.defineProperty(c,"cssClasses",{get:function(){return $},enumerable:!1,configurable:!0}),Object.defineProperty(c,"strings",{get:function(){return G},enumerable:!1,configurable:!0}),Object.defineProperty(c,"numbers",{get:function(){return U},enumerable:!1,configurable:!0}),Object.defineProperty(c,"defaultAdapter",{get:function(){return{addClassToElementAtIndex:function(){},removeClassFromElementAtIndex:function(){},addAttributeToElementAtIndex:function(){},removeAttributeFromElementAtIndex:function(){},getAttributeFromElementAtIndex:function(){return null},elementContainsClass:function(){return!1},closeSurface:function(){},getElementIndex:function(){return-1},notifySelected:function(){},getMenuItemCount:function(){return 0},focusItemAtIndex:function(){},focusListRoot:function(){},getSelectedSiblingOfItemAtIndex:function(){return-1},isSelectableItemAtIndex:function(){return!1}}},enumerable:!1,configurable:!0}),c.prototype.destroy=function(){this.closeAnimationEndTimerId&&clearTimeout(this.closeAnimationEndTimerId),this.adapter.closeSurface()},c.prototype.handleKeydown=function(e){var t=e.key,i=e.keyCode;("Tab"===t||9===i)&&this.adapter.closeSurface(!0)},c.prototype.handleItemAction=function(e){var t=this,i=this.adapter.getElementIndex(e);if(!(i<0)){this.adapter.notifySelected({index:i});var c="true"===this.adapter.getAttributeFromElementAtIndex(i,G.SKIP_RESTORE_FOCUS);this.adapter.closeSurface(c),this.closeAnimationEndTimerId=setTimeout((function(){var i=t.adapter.getElementIndex(e);i>=0&&t.adapter.isSelectableItemAtIndex(i)&&t.setSelectedIndex(i)}),M.numbers.TRANSITION_CLOSE_DURATION)}},c.prototype.handleMenuSurfaceOpened=function(){switch(this.defaultFocusState){case P.FIRST_ITEM:this.adapter.focusItemAtIndex(0);break;case P.LAST_ITEM:this.adapter.focusItemAtIndex(this.adapter.getMenuItemCount()-1);break;case P.NONE:break;default:this.adapter.focusListRoot()}},c.prototype.setDefaultFocusState=function(e){this.defaultFocusState=e},c.prototype.getSelectedIndex=function(){return this.selectedIndex},c.prototype.setSelectedIndex=function(e){if(this.validatedIndex(e),!this.adapter.isSelectableItemAtIndex(e))throw new Error("MDCMenuFoundation: No selection group at specified index.");var t=this.adapter.getSelectedSiblingOfItemAtIndex(e);t>=0&&(this.adapter.removeAttributeFromElementAtIndex(t,G.ARIA_CHECKED_ATTR),this.adapter.removeClassFromElementAtIndex(t,$.MENU_SELECTED_LIST_ITEM)),this.adapter.addClassToElementAtIndex(e,$.MENU_SELECTED_LIST_ITEM),this.adapter.addAttributeToElementAtIndex(e,G.ARIA_CHECKED_ATTR,"true"),this.selectedIndex=e},c.prototype.setEnabled=function(e,t){this.validatedIndex(e),t?(this.adapter.removeClassFromElementAtIndex(e,O.LIST_ITEM_DISABLED_CLASS),this.adapter.addAttributeToElementAtIndex(e,G.ARIA_DISABLED_ATTR,"false")):(this.adapter.addClassToElementAtIndex(e,O.LIST_ITEM_DISABLED_CLASS),this.adapter.addAttributeToElementAtIndex(e,G.ARIA_DISABLED_ATTR,"true"))},c.prototype.validatedIndex=function(e){var t=this.adapter.getMenuItemCount();if(!(e>=0&&e