52 files
This commit is contained in:
@@ -19,7 +19,7 @@ from homeassistant.const import (
|
||||
CONF_RESOURCES,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_entry_oauth2_flow, selector
|
||||
|
||||
from .const import (
|
||||
@@ -358,8 +358,17 @@ async def _get_mailboxes(
|
||||
oauth_token=oauth_token,
|
||||
)
|
||||
|
||||
except (TimeoutError, AioImapException, ConnectionRefusedError) as err:
|
||||
_LOGGER.error("Unable to connect: %s", err)
|
||||
except (TimeoutError, AioImapException, ConnectionRefusedError, InvalidAuth) as err:
|
||||
_LOGGER.error(
|
||||
"Unable to connect: %s (IMAP server %s:%s as %s, security: %s, oauth: %s, class: %s)",
|
||||
err if str(err) else "Authentication failed or timed out",
|
||||
host,
|
||||
port,
|
||||
user,
|
||||
security,
|
||||
oauth_token is not None,
|
||||
type(err).__name__,
|
||||
)
|
||||
return []
|
||||
|
||||
_LOGGER.debug("Attempting to get mailbox list...")
|
||||
@@ -493,6 +502,7 @@ async def _get_schema_step_2(
|
||||
user_input: list,
|
||||
default_dict: list,
|
||||
hass: HomeAssistant,
|
||||
entry: config_entries.ConfigEntry | None = None,
|
||||
) -> Any:
|
||||
"""Get a schema using the default_dict as a backup."""
|
||||
if user_input is None:
|
||||
@@ -502,6 +512,31 @@ async def _get_schema_step_2(
|
||||
"""Get default value for key."""
|
||||
return user_input.get(key, default_dict.get(key, fallback_default))
|
||||
|
||||
oauth_token = None
|
||||
if entry:
|
||||
try:
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass,
|
||||
entry,
|
||||
)
|
||||
)
|
||||
session = config_entry_oauth2_flow.OAuth2Session(
|
||||
hass,
|
||||
entry,
|
||||
implementation,
|
||||
)
|
||||
await session.async_ensure_token_valid()
|
||||
oauth_token = session.token.get("access_token")
|
||||
except Exception as err: # noqa: BLE001
|
||||
_LOGGER.error("Error refreshing OAuth token: %s", err)
|
||||
|
||||
if not oauth_token:
|
||||
if "token" in data and isinstance(data["token"], dict):
|
||||
oauth_token = data["token"].get("access_token")
|
||||
else:
|
||||
oauth_token = data.get("access_token")
|
||||
|
||||
mailboxes = await _get_mailboxes(
|
||||
hass,
|
||||
data[CONF_HOST],
|
||||
@@ -510,7 +545,7 @@ async def _get_schema_step_2(
|
||||
data.get(CONF_PASSWORD, ""),
|
||||
data[CONF_IMAP_SECURITY],
|
||||
data[CONF_VERIFY_SSL],
|
||||
data.get("token", {}).get("access_token"),
|
||||
oauth_token,
|
||||
)
|
||||
|
||||
default_folder = _get_default(CONF_FOLDER)
|
||||
@@ -817,7 +852,15 @@ async def _validate_login(
|
||||
except ssl.SSLError:
|
||||
errors["base"] = "ssl_error"
|
||||
except (TimeoutError, AioImapException, ConnectionRefusedError) as err:
|
||||
_LOGGER.error("Unable to connect: %s", err)
|
||||
_LOGGER.error(
|
||||
"Unable to connect: %s (IMAP server %s:%s as %s, security: %s, oauth: False, class: %s)",
|
||||
err if str(err) else "Authentication failed or timed out",
|
||||
user_input[CONF_HOST],
|
||||
user_input[CONF_PORT],
|
||||
user_input[CONF_USERNAME],
|
||||
user_input[CONF_IMAP_SECURITY],
|
||||
type(err).__name__,
|
||||
)
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
if result != "OK":
|
||||
@@ -840,6 +883,14 @@ class MailAndPackagesFlowHandler(
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
DOMAIN = DOMAIN
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: config_entries.ConfigEntry,
|
||||
) -> config_entries.OptionsFlow:
|
||||
"""Get the options flow for this handler."""
|
||||
return MailAndPackagesOptionsFlow(config_entry)
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
"""Return logger."""
|
||||
@@ -858,6 +909,12 @@ class MailAndPackagesFlowHandler(
|
||||
"prompt": "consent",
|
||||
}
|
||||
)
|
||||
elif auth_type == AUTH_TYPE_OAUTH_MICROSOFT:
|
||||
data.update(
|
||||
{
|
||||
"prompt": "select_account",
|
||||
}
|
||||
)
|
||||
return data
|
||||
|
||||
def __init__(self):
|
||||
@@ -951,12 +1008,16 @@ class MailAndPackagesFlowHandler(
|
||||
"""Handle OAuth2 completion — store token and continue to step 2."""
|
||||
self._data.update(data)
|
||||
if self._entry:
|
||||
if self.source == config_entries.SOURCE_REAUTH:
|
||||
return self.async_update_reload_and_abort(
|
||||
self._entry,
|
||||
data=self._data,
|
||||
)
|
||||
return await self.async_step_reconfig_2()
|
||||
# Reconfigure or Reauth flow: update entry and reload
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._entry,
|
||||
data=self._data,
|
||||
)
|
||||
await self.hass.config_entries.async_reload(self._entry.entry_id)
|
||||
if self.context.get("source") == config_entries.SOURCE_REAUTH:
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
_LOGGER.debug("%s reconfigured.", DOMAIN)
|
||||
return self.async_abort(reason="reconfigure_successful")
|
||||
return await self.async_step_config_2()
|
||||
|
||||
async def _show_auth_form(self, user_input):
|
||||
@@ -1053,6 +1114,7 @@ class MailAndPackagesFlowHandler(
|
||||
user_input,
|
||||
defaults,
|
||||
self.hass,
|
||||
getattr(self, "_entry", None),
|
||||
),
|
||||
errors=self._errors,
|
||||
)
|
||||
@@ -1212,6 +1274,22 @@ class MailAndPackagesFlowHandler(
|
||||
auth_type = self._data.get(CONF_AUTH_TYPE, AUTH_TYPE_PASSWORD)
|
||||
|
||||
if auth_type != AUTH_TYPE_PASSWORD:
|
||||
if (
|
||||
self._entry
|
||||
and self._entry.data.get(CONF_AUTH_TYPE) == auth_type
|
||||
and self._entry.data.get(CONF_HOST) == self._data.get(CONF_HOST)
|
||||
and self._entry.data.get(CONF_USERNAME)
|
||||
== self._data.get(CONF_USERNAME)
|
||||
and "token" in self._data
|
||||
):
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._entry,
|
||||
data=self._data,
|
||||
)
|
||||
await self.hass.config_entries.async_reload(self._entry.entry_id)
|
||||
_LOGGER.debug("%s reconfigured (oauth skip).", DOMAIN)
|
||||
return self.async_abort(reason="reconfigure_successful")
|
||||
|
||||
self._data[CONF_VERIFY_SSL] = True
|
||||
self.hass.data.setdefault(DOMAIN, {})
|
||||
self.hass.data[DOMAIN]["oauth_provider"] = auth_type
|
||||
@@ -1219,7 +1297,13 @@ class MailAndPackagesFlowHandler(
|
||||
|
||||
self._errors = await _validate_login(self.hass, self._data)
|
||||
if self._errors == {}:
|
||||
return await self.async_step_reconfig_2()
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._entry,
|
||||
data=self._data,
|
||||
)
|
||||
await self.hass.config_entries.async_reload(self._entry.entry_id)
|
||||
_LOGGER.debug("%s reconfigured.", DOMAIN)
|
||||
return self.async_abort(reason="reconfigure_successful")
|
||||
|
||||
return await self._show_reconfig_imap_form(user_input)
|
||||
|
||||
@@ -1265,117 +1349,104 @@ class MailAndPackagesFlowHandler(
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
async def async_step_reconfig_2(self, user_input=None):
|
||||
"""Configure form step 2."""
|
||||
|
||||
class MailAndPackagesOptionsFlow(config_entries.OptionsFlow):
|
||||
"""Options flow for Mail and Packages."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
self._entry = config_entry
|
||||
self._data = {**config_entry.data, **config_entry.options}
|
||||
self._errors = {}
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> config_entries.ConfigFlowResult:
|
||||
"""Manage the options."""
|
||||
self._errors = {}
|
||||
_LOGGER.debug("Loading step 2...")
|
||||
if user_input is not None:
|
||||
self._data.update(user_input)
|
||||
self._errors, user_input = await _validate_user_input(user_input, self.hass)
|
||||
if self._data.get("generate_mp4", False):
|
||||
if not await _check_ffmpeg():
|
||||
self._errors["generate_mp4"] = "ffmpeg_not_found"
|
||||
if len(self._errors) == 0:
|
||||
if self._data.get(CONF_ALLOW_FORWARDED_EMAILS, False):
|
||||
return await self.async_step_reconfig_forwarded_emails()
|
||||
|
||||
return await self.async_step_options_forwarded_emails()
|
||||
if any(
|
||||
sensor in self._data.get(CONF_RESOURCES, [])
|
||||
for sensor in AMAZON_SENSORS
|
||||
sensor in self._data[CONF_RESOURCES] for sensor in AMAZON_SENSORS
|
||||
):
|
||||
return await self.async_step_reconfig_amazon()
|
||||
has_custom_image = (
|
||||
self._data.get(CONF_CUSTOM_IMG)
|
||||
or self._data.get(CONF_AMAZON_CUSTOM_IMG)
|
||||
or self._data.get(CONF_UPS_CUSTOM_IMG)
|
||||
or self._data.get(CONF_WALMART_CUSTOM_IMG)
|
||||
or self._data.get(CONF_FEDEX_CUSTOM_IMG)
|
||||
or self._data.get(CONF_GENERIC_CUSTOM_IMG)
|
||||
or self._data.get(CONF_POST_DE_CUSTOM_IMG)
|
||||
)
|
||||
if has_custom_image:
|
||||
return await self.async_step_reconfig_3()
|
||||
return await self.async_step_options_amazon()
|
||||
if self._data.get(CONF_CUSTOM_IMG, False):
|
||||
return await self.async_step_options_3()
|
||||
return await self.async_step_options_storage()
|
||||
|
||||
return await self.async_step_reconfig_storage()
|
||||
return await self._show_options_2(user_input)
|
||||
|
||||
return await self._show_reconfig_2(user_input)
|
||||
return await self._show_options_2(user_input)
|
||||
|
||||
return await self._show_reconfig_2(user_input)
|
||||
async def _show_options_2(self, user_input):
|
||||
"""Step 2 of options."""
|
||||
if self._data.get(CONF_AMAZON_FWDS) == []:
|
||||
self._data[CONF_AMAZON_FWDS] = "(none)"
|
||||
|
||||
async def _show_reconfig_2(self, user_input):
|
||||
"""Step 2 setup."""
|
||||
return self.async_show_form(
|
||||
step_id="reconfig_2",
|
||||
step_id="init",
|
||||
data_schema=await _get_schema_step_2(
|
||||
self._data,
|
||||
user_input,
|
||||
self._data,
|
||||
self.hass,
|
||||
self._data, user_input, self._data, self.hass, self._entry
|
||||
),
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
async def async_step_reconfig_3(self, user_input=None):
|
||||
"""Configure form step 2."""
|
||||
async def async_step_options_forwarded_emails(self, user_input=None):
|
||||
"""Configure forwarded emails."""
|
||||
self._errors = {}
|
||||
if user_input is not None:
|
||||
if user_input.get(CONF_FORWARDED_EMAILS) == "(none)":
|
||||
user_input[CONF_FORWARDED_EMAILS] = []
|
||||
self._data.update(user_input)
|
||||
self._errors, user_input = await _validate_user_input(self._data, self.hass)
|
||||
if len(self._errors) == 0:
|
||||
return await self.async_step_reconfig_storage()
|
||||
if any(
|
||||
sensor in self._data[CONF_RESOURCES] for sensor in AMAZON_SENSORS
|
||||
):
|
||||
return await self.async_step_options_amazon()
|
||||
if self._data.get(CONF_CUSTOM_IMG, False):
|
||||
return await self.async_step_options_3()
|
||||
return await self.async_step_options_storage()
|
||||
return await self._show_options_forwarded_emails(user_input)
|
||||
|
||||
return await self._show_reconfig_3(user_input)
|
||||
|
||||
return await self._show_reconfig_3(user_input)
|
||||
|
||||
async def _show_reconfig_3(self, user_input=None): # pylint: disable=unused-argument
|
||||
"""Step 3 setup."""
|
||||
# Defaults
|
||||
defaults = {
|
||||
CONF_CUSTOM_IMG_FILE: DEFAULT_CUSTOM_IMG_FILE,
|
||||
CONF_AMAZON_CUSTOM_IMG_FILE: DEFAULT_AMAZON_CUSTOM_IMG_FILE,
|
||||
CONF_UPS_CUSTOM_IMG_FILE: DEFAULT_UPS_CUSTOM_IMG_FILE,
|
||||
CONF_WALMART_CUSTOM_IMG_FILE: DEFAULT_WALMART_CUSTOM_IMG_FILE,
|
||||
CONF_FEDEX_CUSTOM_IMG_FILE: DEFAULT_FEDEX_CUSTOM_IMG_FILE,
|
||||
CONF_GENERIC_CUSTOM_IMG_FILE: DEFAULT_GENERIC_CUSTOM_IMG_FILE,
|
||||
CONF_POST_DE_CUSTOM_IMG_FILE: DEFAULT_POST_DE_CUSTOM_IMG_FILE,
|
||||
}
|
||||
async def _show_options_forwarded_emails(self, user_input=None):
|
||||
"""Step forwarded emails."""
|
||||
if self._data.get(CONF_FORWARDED_EMAILS, []) == []:
|
||||
self._data[CONF_FORWARDED_EMAILS] = "(none)"
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfig_3",
|
||||
data_schema=_get_schema_step_3(self._data, defaults),
|
||||
step_id="options_forwarded_emails",
|
||||
data_schema=_get_schema_step_forwarded_emails(user_input, self._data),
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
async def async_step_reconfig_amazon(self, user_input=None):
|
||||
"""Configure form step amazon."""
|
||||
async def async_step_options_amazon(self, user_input=None):
|
||||
"""Configure amazon options."""
|
||||
self._errors = {}
|
||||
if user_input is not None:
|
||||
if user_input.get(CONF_AMAZON_FWDS) == "(none)":
|
||||
user_input[CONF_AMAZON_FWDS] = []
|
||||
self._data.update(user_input)
|
||||
self._errors, user_input = await _validate_user_input(self._data, self.hass)
|
||||
if len(self._errors) == 0:
|
||||
has_custom_image = (
|
||||
self._data.get(CONF_CUSTOM_IMG)
|
||||
or self._data.get(CONF_AMAZON_CUSTOM_IMG)
|
||||
or self._data.get(CONF_UPS_CUSTOM_IMG)
|
||||
or self._data.get(CONF_WALMART_CUSTOM_IMG)
|
||||
or self._data.get(CONF_FEDEX_CUSTOM_IMG)
|
||||
or self._data.get(CONF_GENERIC_CUSTOM_IMG)
|
||||
or self._data.get(CONF_POST_DE_CUSTOM_IMG)
|
||||
)
|
||||
if has_custom_image:
|
||||
return await self.async_step_reconfig_3()
|
||||
if self._data.get(CONF_CUSTOM_IMG, False):
|
||||
return await self.async_step_options_3()
|
||||
return await self.async_step_options_storage()
|
||||
return await self._show_options_amazon(user_input)
|
||||
return await self._show_options_amazon(user_input)
|
||||
|
||||
return await self.async_step_reconfig_storage()
|
||||
|
||||
return await self._show_reconfig_amazon(user_input)
|
||||
|
||||
return await self._show_reconfig_amazon(user_input)
|
||||
|
||||
async def _show_reconfig_amazon(self, user_input):
|
||||
"""Step 3 setup."""
|
||||
if self._data.get(CONF_AMAZON_FWDS, []) == []:
|
||||
async def _show_options_amazon(self, user_input):
|
||||
"""Step Amazon setup."""
|
||||
if self._data.get(CONF_AMAZON_FWDS) == []:
|
||||
self._data[CONF_AMAZON_FWDS] = "(none)"
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfig_amazon",
|
||||
step_id="options_amazon",
|
||||
data_schema=_get_schema_step_amazon(
|
||||
user_input,
|
||||
self._data,
|
||||
@@ -1384,61 +1455,83 @@ class MailAndPackagesFlowHandler(
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
async def async_step_reconfig_forwarded_emails(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
):
|
||||
"""Configure form step forwarded emails."""
|
||||
async def async_step_options_3(self, user_input=None):
|
||||
"""Configure custom image files."""
|
||||
self._errors = {}
|
||||
if user_input is not None:
|
||||
self._data.update(user_input)
|
||||
self._errors, user_input = await _validate_user_input(self._data, self.hass)
|
||||
if len(self._errors) == 0:
|
||||
if any(
|
||||
sensor in self._data.get(CONF_RESOURCES, [])
|
||||
for sensor in AMAZON_SENSORS
|
||||
):
|
||||
return await self.async_step_reconfig_amazon()
|
||||
if self._data.get(CONF_CUSTOM_IMG, False):
|
||||
return await self.async_step_reconfig_3()
|
||||
return await self.async_step_reconfig_storage()
|
||||
return await self._show_reconfig_forwarded_emails(user_input)
|
||||
return await self._show_reconfig_forwarded_emails(user_input)
|
||||
|
||||
async def _show_reconfig_forwarded_emails(self, user_input=None):
|
||||
"""Step forwarded emails."""
|
||||
if self._data.get(CONF_FORWARDED_EMAILS, []) == []:
|
||||
self._data[CONF_FORWARDED_EMAILS] = "(none)"
|
||||
return await self.async_step_options_storage()
|
||||
return await self._show_options_3(user_input)
|
||||
return await self._show_options_3(user_input)
|
||||
|
||||
async def _show_options_3(self, user_input):
|
||||
"""Step 3 setup."""
|
||||
return self.async_show_form(
|
||||
step_id="reconfig_forwarded_emails",
|
||||
data_schema=_get_schema_step_forwarded_emails(user_input, self._data),
|
||||
step_id="options_3",
|
||||
data_schema=_get_schema_step_3(self._data, self._data),
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
async def async_step_reconfig_storage(self, user_input=None):
|
||||
"""Configure form step storage."""
|
||||
async def async_step_options_storage(self, user_input=None):
|
||||
"""Configure storage options."""
|
||||
self._errors = {}
|
||||
if user_input is not None:
|
||||
self._data.update(user_input)
|
||||
self._errors, user_input = await _validate_user_input(self._data, self.hass)
|
||||
if len(self._errors) == 0:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._entry,
|
||||
data=self._data,
|
||||
)
|
||||
await self.hass.config_entries.async_reload(self._entry.entry_id)
|
||||
_LOGGER.debug("%s reconfigured.", DOMAIN)
|
||||
return self.async_abort(reason="reconfigure_successful")
|
||||
# Remove config flow only fields to not pollute options
|
||||
options_keys = {
|
||||
CONF_FOLDER,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_RESOURCES,
|
||||
CONF_CUSTOM_IMG,
|
||||
CONF_AMAZON_CUSTOM_IMG,
|
||||
CONF_UPS_CUSTOM_IMG,
|
||||
CONF_WALMART_CUSTOM_IMG,
|
||||
CONF_FEDEX_CUSTOM_IMG,
|
||||
CONF_GENERIC_CUSTOM_IMG,
|
||||
CONF_POST_DE_CUSTOM_IMG,
|
||||
CONF_CUSTOM_IMG_FILE,
|
||||
CONF_AMAZON_CUSTOM_IMG_FILE,
|
||||
CONF_UPS_CUSTOM_IMG_FILE,
|
||||
CONF_WALMART_CUSTOM_IMG_FILE,
|
||||
CONF_FEDEX_CUSTOM_IMG_FILE,
|
||||
CONF_GENERIC_CUSTOM_IMG_FILE,
|
||||
CONF_POST_DE_CUSTOM_IMG_FILE,
|
||||
CONF_ALLOW_FORWARDED_EMAILS,
|
||||
CONF_FORWARDED_EMAILS,
|
||||
CONF_FORWARDING_HEADER,
|
||||
CONF_AMAZON_FWDS,
|
||||
CONF_AMAZON_DOMAIN,
|
||||
CONF_AMAZON_DAYS,
|
||||
CONF_STORAGE,
|
||||
"generate_mp4",
|
||||
"generate_grid",
|
||||
"gif_duration",
|
||||
"custom_days",
|
||||
"allow_external",
|
||||
"image_security",
|
||||
"imap_timeout",
|
||||
"image_name",
|
||||
"image_path",
|
||||
"usps_placeholder",
|
||||
}
|
||||
for key in list(self._data.keys()):
|
||||
if key not in options_keys:
|
||||
self._data.pop(key, None)
|
||||
|
||||
return await self._show_reconfig_storage(user_input)
|
||||
return self.async_create_entry(title="", data=self._data)
|
||||
|
||||
return await self._show_reconfig_storage(user_input)
|
||||
return await self._show_options_storage(user_input)
|
||||
|
||||
async def _show_reconfig_storage(self, user_input):
|
||||
"""Step 3 setup."""
|
||||
return await self._show_options_storage(user_input)
|
||||
|
||||
async def _show_options_storage(self, user_input):
|
||||
"""Step storage setup."""
|
||||
return self.async_show_form(
|
||||
step_id="reconfig_storage",
|
||||
step_id="options_storage",
|
||||
data_schema=_get_schema_step_storage(user_input, self._data),
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user