1 """The imap integration."""
3 from __future__
import annotations
8 from aioimaplib
import IMAP4_SSL, AioImapException, Response
9 import voluptuous
as vol
21 ConfigEntryAuthFailed,
24 ServiceValidationError,
29 from .const
import CONF_ENABLE_PUSH, DOMAIN
30 from .coordinator
import (
31 ImapDataUpdateCoordinator,
33 ImapPollingDataUpdateCoordinator,
34 ImapPushDataUpdateCoordinator,
37 from .errors
import InvalidAuth, InvalidFolder
39 PLATFORMS: list[Platform] = [Platform.SENSOR]
44 CONF_TARGET_FOLDER =
"target_folder"
46 _LOGGER = logging.getLogger(__name__)
49 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
51 _SERVICE_UID_SCHEMA = vol.Schema(
53 vol.Required(CONF_ENTRY): cv.string,
54 vol.Required(CONF_UID): cv.string,
58 SERVICE_SEEN_SCHEMA = _SERVICE_UID_SCHEMA
59 SERVICE_MOVE_SCHEMA = _SERVICE_UID_SCHEMA.extend(
61 vol.Optional(CONF_SEEN): cv.boolean,
62 vol.Required(CONF_TARGET_FOLDER): cv.string,
65 SERVICE_DELETE_SCHEMA = _SERVICE_UID_SCHEMA
66 SERVICE_FETCH_TEXT_SCHEMA = _SERVICE_UID_SCHEMA
68 type ImapConfigEntry = ConfigEntry[ImapDataUpdateCoordinator]
72 """Get IMAP client and connect."""
73 if (entry := hass.config_entries.async_get_entry(entry_id))
is None or (
74 entry.state
is not ConfigEntryState.LOADED
77 translation_domain=DOMAIN,
78 translation_key=
"invalid_entry",
82 except InvalidAuth
as exc:
84 translation_domain=DOMAIN, translation_key=
"invalid_auth"
86 except InvalidFolder
as exc:
88 translation_domain=DOMAIN, translation_key=
"invalid_folder"
90 except (TimeoutError, AioImapException)
as exc:
92 translation_domain=DOMAIN,
93 translation_key=
"imap_server_fail",
94 translation_placeholders={
"error":
str(exc)},
101 """Get error message from response."""
102 if response.result !=
"OK":
103 error: str = response.lines[0].decode(
"utf-8")
105 translation_domain=DOMAIN,
106 translation_key=translation_key,
107 translation_placeholders={
"error": error},
111 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
112 """Set up imap services."""
114 async
def async_seen(call: ServiceCall) ->
None:
115 """Process mark as seen service call."""
116 entry_id: str = call.data[CONF_ENTRY]
117 uid: str = call.data[CONF_UID]
119 "Mark message %s as seen. Entry: %s",
125 response = await client.store(uid,
"+FLAGS (\\Seen)")
126 except (TimeoutError, AioImapException)
as exc:
128 translation_domain=DOMAIN,
129 translation_key=
"imap_server_fail",
130 translation_placeholders={
"error":
str(exc)},
135 hass.services.async_register(DOMAIN,
"seen", async_seen, SERVICE_SEEN_SCHEMA)
137 async
def async_move(call: ServiceCall) ->
None:
138 """Process move email service call."""
139 entry_id: str = call.data[CONF_ENTRY]
140 uid: str = call.data[CONF_UID]
141 seen = bool(call.data.get(CONF_SEEN))
142 target_folder: str = call.data[CONF_TARGET_FOLDER]
144 "Move message %s to folder %s. Mark as seen: %s. Entry: %s",
153 response = await client.store(uid,
"+FLAGS (\\Seen)")
155 response = await client.copy(uid, target_folder)
157 response = await client.store(uid,
"+FLAGS (\\Deleted)")
159 response = await asyncio.wait_for(
160 client.protocol.expunge(uid, by_uid=
True), client.timeout
163 except (TimeoutError, AioImapException)
as exc:
165 translation_domain=DOMAIN,
166 translation_key=
"imap_server_fail",
167 translation_placeholders={
"error":
str(exc)},
171 hass.services.async_register(DOMAIN,
"move", async_move, SERVICE_MOVE_SCHEMA)
173 async
def async_delete(call: ServiceCall) ->
None:
174 """Process deleting email service call."""
175 entry_id: str = call.data[CONF_ENTRY]
176 uid: str = call.data[CONF_UID]
178 "Delete message %s. Entry: %s",
184 response = await client.store(uid,
"+FLAGS (\\Deleted)")
186 response = await asyncio.wait_for(
187 client.protocol.expunge(uid, by_uid=
True), client.timeout
190 except (TimeoutError, AioImapException)
as exc:
192 translation_domain=DOMAIN,
193 translation_key=
"imap_server_fail",
194 translation_placeholders={
"error":
str(exc)},
198 hass.services.async_register(DOMAIN,
"delete", async_delete, SERVICE_DELETE_SCHEMA)
200 async
def async_fetch(call: ServiceCall) -> ServiceResponse:
201 """Process fetch email service and return content."""
202 entry_id: str = call.data[CONF_ENTRY]
203 uid: str = call.data[CONF_UID]
205 "Fetch text for message %s. Entry: %s",
211 response = await client.fetch(uid,
"BODY.PEEK[]")
212 except (TimeoutError, AioImapException)
as exc:
214 translation_domain=DOMAIN,
215 translation_key=
"imap_server_fail",
216 translation_placeholders={
"error":
str(exc)},
222 "text": message.text,
223 "sender": message.sender,
224 "subject": message.subject,
228 hass.services.async_register(
232 SERVICE_FETCH_TEXT_SCHEMA,
233 supports_response=SupportsResponse.ONLY,
240 """Set up imap from a config entry."""
243 except InvalidAuth
as err:
244 raise ConfigEntryAuthFailed
from err
245 except InvalidFolder
as err:
247 except (TimeoutError, AioImapException)
as err:
248 raise ConfigEntryNotReady
from err
250 coordinator_class: type[
251 ImapPushDataUpdateCoordinator | ImapPollingDataUpdateCoordinator
253 enable_push: bool = entry.data.get(CONF_ENABLE_PUSH,
True)
254 if enable_push
and imap_client.has_capability(
"IDLE"):
255 coordinator_class = ImapPushDataUpdateCoordinator
257 coordinator_class = ImapPollingDataUpdateCoordinator
259 coordinator: ImapDataUpdateCoordinator = coordinator_class(hass, imap_client, entry)
260 await coordinator.async_config_entry_first_refresh()
262 entry.runtime_data = coordinator
264 entry.async_on_unload(
265 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.shutdown)
268 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
274 """Unload a config entry."""
275 if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
276 coordinator = entry.runtime_data
277 await coordinator.shutdown()
IMAP4_SSL connect_to_server(Mapping[str, Any] data)
IMAP4_SSL async_get_imap_client(HomeAssistant hass, str entry_id)
bool async_setup(HomeAssistant hass, ConfigType config)
None raise_on_error(Response response, str translation_key)
bool async_setup_entry(HomeAssistant hass, ImapConfigEntry entry)
bool async_unload_entry(HomeAssistant hass, ImapConfigEntry entry)