Files
HomeAssistantVS/custom_components/mail_and_packages/__init__.py
T
2026-07-14 23:57:03 -04:00

371 lines
12 KiB
Python

"""Mail and Packages Integration."""
import asyncio
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_RESOURCES
from homeassistant.core import HomeAssistant
from homeassistant.helpers import (
config_validation as cv,
)
from homeassistant.helpers import (
device_registry as dr,
)
from . import const
from .const import (
ATTR_IMAGE_NAME,
ATTR_IMAGE_PATH,
AUTH_TYPE_PASSWORD,
CONF_AMAZON_CUSTOM_IMG,
CONF_AMAZON_CUSTOM_IMG_FILE,
CONF_AMAZON_DAYS,
CONF_AMAZON_DOMAIN,
CONF_AMAZON_FWDS,
CONF_AUTH_TYPE,
CONF_FEDEX_CUSTOM_IMG,
CONF_FEDEX_CUSTOM_IMG_FILE,
CONF_FOLDER,
CONF_FORWARDED_EMAILS,
CONF_GENERIC_CUSTOM_IMG,
CONF_GENERIC_CUSTOM_IMG_FILE,
CONF_IMAGE_SECURITY,
CONF_IMAP_SECURITY,
CONF_IMAP_TIMEOUT,
CONF_PATH,
CONF_SCAN_INTERVAL,
CONF_STORAGE,
CONF_UPS_CUSTOM_IMG,
CONF_UPS_CUSTOM_IMG_FILE,
CONF_VERIFY_SSL,
CONF_WALMART_CUSTOM_IMG,
CONF_WALMART_CUSTOM_IMG_FILE,
CONFIG_VER,
DEFAULT_AMAZON_CUSTOM_IMG_FILE,
DEFAULT_AMAZON_DAYS,
DEFAULT_FEDEX_CUSTOM_IMG_FILE,
DEFAULT_GENERIC_CUSTOM_IMG_FILE,
DEFAULT_UPS_CUSTOM_IMG_FILE,
DEFAULT_WALMART_CUSTOM_IMG_FILE,
DOMAIN,
ISSUE_URL,
PLATFORMS,
VERSION,
)
from .coordinator import (
MailAndPackagesConfigEntry,
MailAndPackagesData,
MailDataUpdateCoordinator,
)
from .utils.image import default_image_path, hash_file
__all__ = [
"ATTR_IMAGE_NAME",
"ATTR_IMAGE_PATH",
"AUTH_TYPE_PASSWORD",
"CONFIG_VER",
"CONF_AMAZON_CUSTOM_IMG",
"CONF_AMAZON_CUSTOM_IMG_FILE",
"CONF_AMAZON_DAYS",
"CONF_AMAZON_DOMAIN",
"CONF_AMAZON_FWDS",
"CONF_AUTH_TYPE",
"CONF_FEDEX_CUSTOM_IMG",
"CONF_FEDEX_CUSTOM_IMG_FILE",
"CONF_GENERIC_CUSTOM_IMG",
"CONF_GENERIC_CUSTOM_IMG_FILE",
"CONF_IMAGE_SECURITY",
"CONF_IMAP_SECURITY",
"CONF_IMAP_TIMEOUT",
"CONF_PATH",
"CONF_SCAN_INTERVAL",
"CONF_STORAGE",
"CONF_UPS_CUSTOM_IMG",
"CONF_UPS_CUSTOM_IMG_FILE",
"CONF_VERIFY_SSL",
"CONF_WALMART_CUSTOM_IMG",
"CONF_WALMART_CUSTOM_IMG_FILE",
"DEFAULT_AMAZON_CUSTOM_IMG_FILE",
"DEFAULT_AMAZON_DAYS",
"DEFAULT_FEDEX_CUSTOM_IMG_FILE",
"DEFAULT_GENERIC_CUSTOM_IMG_FILE",
"DEFAULT_UPS_CUSTOM_IMG_FILE",
"DEFAULT_WALMART_CUSTOM_IMG_FILE",
"DOMAIN",
"ISSUE_URL",
"PLATFORMS",
"VERSION",
"MailAndPackagesConfigEntry",
"MailAndPackagesData",
"MailDataUpdateCoordinator",
"const",
"default_image_path",
"hash_file",
]
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config_entry: MailAndPackagesConfigEntry): # pylint: disable=unused-argument
"""Disallow configuration via YAML."""
return True
async def async_setup_entry(
hass: HomeAssistant,
config_entry: MailAndPackagesConfigEntry,
) -> bool:
"""Load the saved entities."""
_LOGGER.info(
"Version %s is starting, if you have any issues please report them here: %s",
VERSION,
ISSUE_URL,
)
hass.data.setdefault(DOMAIN, {})
updated_config = config_entry.data.copy()
# Sort the resources
updated_config[CONF_RESOURCES] = sorted(updated_config[CONF_RESOURCES])
if updated_config != config_entry.data:
hass.config_entries.async_update_entry(config_entry, data=updated_config)
# Variables for data coordinator
config = config_entry.data
# Setup the data coordinator
coordinator = MailDataUpdateCoordinator(hass, config, config_entry)
config_entry.runtime_data = MailAndPackagesData(coordinator=coordinator, cameras=[])
# Fetch initial data in the background so setup doesn't block
hass.async_create_task(coordinator.async_refresh())
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_remove_config_entry_device( # pylint: disable-next=unused-argument
hass: HomeAssistant,
config_entry: ConfigEntry,
device_entry: dr.DeviceEntry,
) -> bool:
"""Remove config entry from a device if its no longer present."""
return not any(
identifier
for identifier in device_entry.identifiers
if identifier[0] == DOMAIN
and config_entry.runtime_data.get_device(identifier[1])
)
async def async_unload_entry(
hass: HomeAssistant,
config_entry: MailAndPackagesConfigEntry,
) -> bool:
"""Handle removal of an entry."""
_LOGGER.debug("Attempting to unload sensors from the %s integration", DOMAIN)
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(config_entry, platform)
for platform in PLATFORMS
],
),
)
if unload_ok:
_LOGGER.debug("Successfully removed sensors from the %s integration", DOMAIN)
return unload_ok
async def async_migrate_entry(hass, config_entry):
"""Migrate an old config entry."""
version = config_entry.version
new_version = CONFIG_VER
_LOGGER.debug("Migrating from version %s", version)
updated_config = {**config_entry.data}
_migrate_legacy_versions(updated_config, version, config_entry)
_apply_default_config(updated_config)
if updated_config != config_entry.data:
hass.config_entries.async_update_entry(
config_entry,
data=updated_config,
version=new_version,
)
_LOGGER.debug("Migration complete to version %s", new_version)
return True
def _migrate_legacy_versions(updated_config, version, config_entry):
"""Handle migration of legacy versions."""
_migrate_versions_1_to_3(updated_config, version, config_entry)
_migrate_versions_4_to_16(updated_config, version)
_migrate_version_17(updated_config, version)
_migrate_version_18(updated_config, version)
def _migrate_versions_1_to_3(updated_config, version, config_entry):
"""Handle migration for versions 1 to 3."""
if version == 1:
if CONF_AMAZON_FWDS in updated_config:
if not isinstance(updated_config[CONF_AMAZON_FWDS], list):
updated_config[CONF_AMAZON_FWDS] = [
x.strip() for x in updated_config[CONF_AMAZON_FWDS].split(",")
]
else:
updated_config[CONF_AMAZON_FWDS] = []
else:
_LOGGER.warning("Missing configuration data: %s", CONF_AMAZON_FWDS)
# Force path change
updated_config[CONF_PATH] = "custom_components/mail_and_packages/images/"
# Always on image security
if not config_entry.data.get(CONF_IMAGE_SECURITY, False):
updated_config[CONF_IMAGE_SECURITY] = True
# Add default Amazon Days configuration
updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS
# 2 -> 4
if version <= 2:
# Force path change
updated_config[CONF_PATH] = "custom_components/mail_and_packages/images/"
# Always on image security
if not config_entry.data.get(CONF_IMAGE_SECURITY, False):
updated_config[CONF_IMAGE_SECURITY] = True
# Add default Amazon Days configuration
updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS
if version <= 3:
# Add default Amazon Days configuration
updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS
def _migrate_versions_4_to_16(updated_config, version):
"""Handle migration for versions 4 to 16."""
_migrate_versions_4_to_7(updated_config, version)
_migrate_versions_15_to_16(updated_config, version)
def _migrate_versions_4_to_7(updated_config, version):
"""Handle migration for versions 4 to 7."""
if version <= 4:
if CONF_AMAZON_FWDS in updated_config and updated_config[CONF_AMAZON_FWDS] == [
'""',
]:
updated_config[CONF_AMAZON_FWDS] = []
if version <= 5:
if CONF_VERIFY_SSL not in updated_config:
updated_config[CONF_VERIFY_SSL] = True
if version <= 6:
if CONF_IMAP_SECURITY not in updated_config:
updated_config[CONF_IMAP_SECURITY] = "SSL"
if version <= 7:
if CONF_AMAZON_DOMAIN not in updated_config:
updated_config[CONF_AMAZON_DOMAIN] = "amazon.com"
def _migrate_versions_15_to_16(updated_config, version):
"""Handle migration for versions 15 to 16."""
if version <= 15:
if updated_config.get(CONF_IMAP_SECURITY) == "startTLS":
updated_config[CONF_IMAP_SECURITY] = "SSL"
if CONF_AUTH_TYPE not in updated_config:
updated_config[CONF_AUTH_TYPE] = AUTH_TYPE_PASSWORD
if version <= 16:
if "auth" in updated_config:
auth_data = updated_config.pop("auth")
updated_config.update(auth_data)
def _migrate_version_17(updated_config, version):
"""Handle migration for version 17."""
if version <= 17:
fwds = updated_config.get(CONF_FORWARDED_EMAILS)
if isinstance(fwds, str):
if fwds and fwds != "(none)":
updated_config[CONF_FORWARDED_EMAILS] = [
e.strip() for e in fwds.split(",") if e.strip()
]
else:
updated_config.pop(CONF_FORWARDED_EMAILS, None)
def _migrate_version_18(updated_config, version):
"""Handle migration for version 18."""
if version <= 18:
if CONF_FOLDER in updated_config:
folder = updated_config[CONF_FOLDER]
if isinstance(folder, str):
updated_config[CONF_FOLDER] = folder.strip('"')
elif isinstance(folder, (list, tuple, set)):
updated_config[CONF_FOLDER] = [
f.strip('"') for f in folder if isinstance(f, str)
]
def _apply_default_config(updated_config):
"""Ensure default configurations are present."""
# Require configs on all migration paths
if CONF_PATH not in updated_config:
updated_config[CONF_PATH] = "custom_components/mail_and_packages/images/"
if CONF_RESOURCES not in updated_config:
updated_config[CONF_RESOURCES] = []
# Add default for image storage config
if CONF_STORAGE not in updated_config:
updated_config[CONF_STORAGE] = "custom_components/mail_and_packages/images/"
_apply_courier_image_defaults(updated_config)
_apply_walmart_generic_fedex_defaults(updated_config)
def _apply_courier_image_defaults(updated_config):
"""Apply default Amazon and UPS custom image configurations."""
if CONF_AMAZON_CUSTOM_IMG not in updated_config:
updated_config[CONF_AMAZON_CUSTOM_IMG] = False
if CONF_AMAZON_CUSTOM_IMG_FILE not in updated_config:
updated_config[CONF_AMAZON_CUSTOM_IMG_FILE] = DEFAULT_AMAZON_CUSTOM_IMG_FILE
if CONF_UPS_CUSTOM_IMG not in updated_config:
updated_config[CONF_UPS_CUSTOM_IMG] = False
if CONF_UPS_CUSTOM_IMG_FILE not in updated_config:
updated_config[CONF_UPS_CUSTOM_IMG_FILE] = DEFAULT_UPS_CUSTOM_IMG_FILE
def _apply_walmart_generic_fedex_defaults(updated_config):
"""Apply default Walmart, Generic and FedEx custom image configurations."""
if CONF_WALMART_CUSTOM_IMG not in updated_config:
updated_config[CONF_WALMART_CUSTOM_IMG] = False
if CONF_WALMART_CUSTOM_IMG_FILE not in updated_config:
updated_config[CONF_WALMART_CUSTOM_IMG_FILE] = DEFAULT_WALMART_CUSTOM_IMG_FILE
if CONF_GENERIC_CUSTOM_IMG not in updated_config:
updated_config[CONF_GENERIC_CUSTOM_IMG] = False
if CONF_GENERIC_CUSTOM_IMG_FILE not in updated_config:
updated_config[CONF_GENERIC_CUSTOM_IMG_FILE] = DEFAULT_GENERIC_CUSTOM_IMG_FILE
if CONF_FEDEX_CUSTOM_IMG not in updated_config:
updated_config[CONF_FEDEX_CUSTOM_IMG] = False
if CONF_FEDEX_CUSTOM_IMG_FILE not in updated_config:
updated_config[CONF_FEDEX_CUSTOM_IMG_FILE] = DEFAULT_FEDEX_CUSTOM_IMG_FILE