Skip to content
32 changes: 26 additions & 6 deletions homeassistant/components/imap/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_USERNAME
from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow, FlowResult
from homeassistant.helpers import config_validation as cv
Expand All @@ -25,7 +25,7 @@
from .coordinator import connect_to_server
from .errors import InvalidAuth, InvalidFolder

STEP_USER_DATA_SCHEMA = vol.Schema(
CONFIG_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
Expand Down Expand Up @@ -77,14 +77,34 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
_reauth_entry: config_entries.ConfigEntry | None

async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
"""Handle the import from imap_email_content integration."""
data = CONFIG_SCHEMA(
{
CONF_SERVER: user_input[CONF_SERVER],
CONF_PORT: user_input[CONF_PORT],
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_FOLDER: user_input[CONF_FOLDER],
}
)
self._async_abort_entries_match(
{
key: data[key]
for key in (CONF_USERNAME, CONF_SERVER, CONF_FOLDER, CONF_SEARCH)
}
)
title = user_input[CONF_NAME]
if await validate_input(data):
raise AbortFlow("cannot_connect")
return self.async_create_entry(title=title, data=data)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)
return self.async_show_form(step_id="user", data_schema=CONFIG_SCHEMA)

self._async_abort_entries_match(
{
Expand All @@ -98,7 +118,7 @@ async def async_step_user(

return self.async_create_entry(title=title, data=user_input)

schema = self.add_suggested_values_to_schema(STEP_USER_DATA_SCHEMA, user_input)
schema = self.add_suggested_values_to_schema(CONFIG_SCHEMA, user_input)
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)

async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/imap_email_content/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
"""The imap_email_content component."""

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType

PLATFORMS = [Platform.SENSOR]


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up imap_email_content."""
return True
13 changes: 13 additions & 0 deletions homeassistant/components/imap_email_content/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Constants for the imap email content integration."""

DOMAIN = "imap_email_content"

CONF_SERVER = "server"
CONF_SENDERS = "senders"
CONF_FOLDER = "folder"

ATTR_FROM = "from"
ATTR_BODY = "body"
ATTR_SUBJECT = "subject"

DEFAULT_PORT = 993
1 change: 1 addition & 0 deletions homeassistant/components/imap_email_content/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"domain": "imap_email_content",
"name": "IMAP Email Content",
"codeowners": [],
"dependencies": ["imap"],
"documentation": "https://www.home-assistant.io/integrations/imap_email_content",
"iot_class": "cloud_push"
}
173 changes: 173 additions & 0 deletions homeassistant/components/imap_email_content/repairs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"""Repair flow for imap email content integration."""

from typing import Any

import voluptuous as vol
import yaml

from homeassistant import data_entry_flow
from homeassistant.components.imap import DOMAIN as IMAP_DOMAIN
from homeassistant.components.repairs import RepairsFlow
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
CONF_VALUE_TEMPLATE,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.typing import ConfigType

from .const import CONF_FOLDER, CONF_SENDERS, CONF_SERVER, DOMAIN


async def async_process_issue(hass: HomeAssistant, config: ConfigType) -> None:
"""Register an issue and suggest new config."""

name: str = config.get(CONF_NAME) or config[CONF_USERNAME]

issue_id = (
f"{name}_{config[CONF_USERNAME]}_{config[CONF_SERVER]}_{config[CONF_FOLDER]}"
)

if CONF_VALUE_TEMPLATE in config:
template: str = config[CONF_VALUE_TEMPLATE].template
template = template.replace("subject", 'trigger.event.data["subject"]')
template = template.replace("from", 'trigger.event.data["sender"]')
template = template.replace("date", 'trigger.event.data["date"]')
template = template.replace("body", 'trigger.event.data["text"]')
else:
template = '{{ trigger.event.data["subject"] }}'

template_sensor_config: ConfigType = {
"template": [
{
"trigger": [
{
"id": "custom_event",
"platform": "event",
"event_type": "imap_content",
"event_data": {"sender": config[CONF_SENDERS][0]},
}
],
"sensor": [
{
"state": template,
"name": name,
}
],
}
]
}

data = {
CONF_SERVER: config[CONF_SERVER],
CONF_PORT: config[CONF_PORT],
CONF_USERNAME: config[CONF_USERNAME],
CONF_PASSWORD: config[CONF_PASSWORD],
CONF_FOLDER: config[CONF_FOLDER],
}
data[CONF_VALUE_TEMPLATE] = template
data[CONF_NAME] = name
placeholders = {"yaml_example": yaml.dump(template_sensor_config)}
placeholders.update(data)

ir.async_create_issue(
hass,
DOMAIN,
issue_id,
breaks_in_ha_version="2023.10.0",
is_fixable=True,
severity=ir.IssueSeverity.WARNING,
translation_key="migration",
translation_placeholders=placeholders,
data=data,
)


class DeprecationRepairFlow(RepairsFlow):
"""Handler for an issue fixing flow."""

def __init__(self, issue_id: str, config: ConfigType) -> None:
"""Create flow."""
self._name: str = config[CONF_NAME]
self._config: dict[str, Any] = config
self._issue_id = issue_id
super().__init__()

async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""
return await self.async_step_start()

@callback
def _async_get_placeholders(self) -> dict[str, str] | None:
issue_registry = ir.async_get(self.hass)
description_placeholders = None
if issue := issue_registry.async_get_issue(self.handler, self.issue_id):
description_placeholders = issue.translation_placeholders

return description_placeholders

async def async_step_start(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Wait for the user to start the config migration."""
placeholders = self._async_get_placeholders()
if user_input is None:
return self.async_show_form(
step_id="start",
data_schema=vol.Schema({}),
description_placeholders=placeholders,
)

return await self.async_step_confirm()

async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the confirm step of a fix flow."""
placeholders = self._async_get_placeholders()
if user_input is not None:
user_input[CONF_NAME] = self._name
result = await self.hass.config_entries.flow.async_init(
IMAP_DOMAIN, context={"source": SOURCE_IMPORT}, data=self._config
)
if result["type"] == FlowResultType.ABORT:
ir.async_delete_issue(self.hass, DOMAIN, self._issue_id)
ir.async_create_issue(
self.hass,
DOMAIN,
self._issue_id,
breaks_in_ha_version="2023.10.0",
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecation",
translation_placeholders=placeholders,
data=self._config,
learn_more_url="https://www.home-assistant.io/integrations/imap/#using-events",
)
return self.async_abort(reason=result["reason"])
return self.async_create_entry(
title="",
data={},
)

return self.async_show_form(
step_id="confirm",
data_schema=vol.Schema({}),
description_placeholders=placeholders,
)


async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
data: dict[str, str | int | float | None],
) -> RepairsFlow:
"""Create flow."""
return DeprecationRepairFlow(issue_id, data)
23 changes: 13 additions & 10 deletions homeassistant/components/imap_email_content/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.ssl import client_context

_LOGGER = logging.getLogger(__name__)

CONF_SERVER = "server"
CONF_SENDERS = "senders"
CONF_FOLDER = "folder"

ATTR_FROM = "from"
ATTR_BODY = "body"
ATTR_SUBJECT = "subject"
from .const import (
ATTR_BODY,
ATTR_FROM,
ATTR_SUBJECT,
CONF_FOLDER,
CONF_SENDERS,
CONF_SERVER,
DEFAULT_PORT,
)
from .repairs import async_process_issue

DEFAULT_PORT = 993
_LOGGER = logging.getLogger(__name__)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
Expand Down Expand Up @@ -79,6 +80,8 @@ def setup_platform(
value_template,
)

hass.add_job(async_process_issue, hass, config)

if sensor.connected:
add_entities([sensor], True)

Expand Down
27 changes: 27 additions & 0 deletions homeassistant/components/imap_email_content/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"issues": {
"deprecation": {
"title": "The IMAP email content integration is deprecated",
"description": "The IMAP email content integration is deprecated. Your IMAP server configuration was already migrated to to the [imap integration](https://my.home-assistant.io/redirect/config_flow_start?domain=imap). To set up a sensor for the IMAP email content, set up a template sensor with the config:\n\n```yaml\n{yaml_example}```\n\nPlease remove the deprecated `imap_email_plaform` sensor configuration from your `configuration.yaml`.\n\nNote that the event filter only filters on the first of the configured allowed senders, customize the filter if needed.\n\nYou can skip this part if you have already set up a template sensor."
},
"migration": {
"title": "The IMAP email content integration needs attention",
"fix_flow": {
"step": {
"start": {
"title": "Migrate your IMAP email configuration",
"description": "The IMAP email content integration is deprecated. Your IMAP server configuration can be migrated automatically to the [imap integration](https://my.home-assistant.io/redirect/config_flow_start?domain=imap), this will enable using a custom `imap` event trigger. To set up a sensor that has an IMAP content state, a template sensor can be used. Remove the `imap_email_plaform` sensor configuration from your `configuration.yaml` after migration.\n\nSubmit to start migration of your IMAP server configuration to the `imap` integration."
},
"confirm": {
"title": "Your IMAP server settings will be migrated",
"description": "In this step an `imap` config entry will be set up with the following configuration:\n\n```text\nServer\t{server}\nPort\t{port}\nUsername\t{username}\nPassword\t*****\nFolder\t{folder}\n```\n\nSee also: (https://www.home-assistant.io/integrations/imap/)\n\nFitering configuration on allowed `sender` is part of the template sensor config that can copied and placed in your `configuration.yaml.\n\nNote that the event filter only filters on the first of the configured allowed senders, customize the filter if needed.\n\n```yaml\n{yaml_example}```\nDo not forget to cleanup the your `configuration.yaml` after migration.\n\nSubmit to migrate your IMAP server configuration to an `imap` configuration entry."
}
},
"abort": {
"already_configured": "The IMAP server config was already migrated to the imap integration. Remove the `imap_email_plaform` sensor configuration from your `configuration.yaml`.",
"cannot_connect": "Migration failed. Failed to connect to the IMAP server. Perform a manual migration."
}
}
}
}
}
Loading