Coverage for custom_components/supernotify/methods/email.py: 88%
88 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-28 14:21 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-28 14:21 +0000
1import logging
2import re
3from typing import TYPE_CHECKING, Any
5from homeassistant.components.notify.const import ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, ATTR_TITLE
6from homeassistant.const import CONF_EMAIL
7from homeassistant.core import HomeAssistant
8from jinja2 import Environment, FileSystemLoader
10from custom_components.supernotify import CONF_TEMPLATE, METHOD_EMAIL
11from custom_components.supernotify.configuration import SupernotificationConfiguration
12from custom_components.supernotify.delivery_method import DeliveryMethod
13from custom_components.supernotify.envelope import Envelope
15if TYPE_CHECKING:
16 from pathlib import Path
18RE_VALID_EMAIL = (
19 r"^[a-zA-Z0-9.+/=?^_-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$"
20)
22_LOGGER = logging.getLogger(__name__)
25class EmailDeliveryMethod(DeliveryMethod):
26 method = METHOD_EMAIL
28 def __init__(self, hass: HomeAssistant, context: SupernotificationConfiguration, deliveries: dict | None = None) -> None:
29 super().__init__(hass, context, deliveries)
30 self.template_path: Path | None = None
31 if self.context.template_path:
32 self.template_path = self.context.template_path / "email"
33 if not self.template_path.exists():
34 _LOGGER.warning("SUPERNOTIFY Email templates not available at %s", self.template_path)
35 self.template_path = None
36 if self.template_path is None:
37 _LOGGER.warning("SUPERNOTIFY Email templates not available")
38 else:
39 _LOGGER.debug("SUPERNOTIFY Loading email templates from %s", self.template_path)
41 def select_target(self, target: str) -> bool:
42 return re.fullmatch(RE_VALID_EMAIL, target) is not None
44 def recipient_target(self, recipient: dict) -> list[str]:
45 email = recipient.get(CONF_EMAIL)
46 return [email] if email else []
48 async def deliver(self, envelope: Envelope) -> bool:
49 _LOGGER.info("SUPERNOTIFY notify_email: %s %s", envelope.delivery_name, envelope.targets)
51 data: dict[str, Any] = envelope.data or {}
52 config = self.delivery_config(envelope.delivery_name)
53 html: str | None = data.get("html")
54 template: str = data.get(CONF_TEMPLATE, config.get(CONF_TEMPLATE))
55 addresses: list[str] = envelope.targets or []
56 snapshot_url: str | None = data.get("snapshot_url")
57 # TODO: centralize in config
58 footer_template = data.get("footer")
59 footer = footer_template.format(e=envelope) if footer_template else None
61 action_data: dict[str, Any] = envelope.core_action_data()
63 if len(addresses) > 0:
64 action_data[ATTR_TARGET] = addresses
65 # default to SMTP platform default recipients if no explicit addresses
67 if data and data.get("data"):
68 action_data[ATTR_DATA] = data.get("data")
70 if not template or not self.template_path:
71 if footer and action_data.get(ATTR_MESSAGE):
72 action_data[ATTR_MESSAGE] = f"{action_data[ATTR_MESSAGE]}\n\n{footer}"
74 image_path: Path | None = await envelope.grab_image()
75 if image_path:
76 action_data.setdefault("data", {})
77 action_data["data"]["images"] = [str(image_path)]
78 if envelope.message_html:
79 action_data.setdefault("data", {})
80 html = envelope.message_html
81 if image_path:
82 image_name = image_path.name
83 if html and "cid:%s" not in html and not html.endswith("</html"):
84 if snapshot_url:
85 html += f'<div><p><a href="{snapshot_url}">'
86 html += f'<img src="cid:{image_name}"/></a>'
87 html += "</p></div>"
88 else:
89 html += f'<div><p><img src="cid:{image_name}"></p></div>'
91 action_data["data"]["html"] = html
92 else:
93 html = self.render_template(template, envelope, action_data, snapshot_url, envelope.message_html)
94 if html:
95 action_data.setdefault("data", {})
96 action_data["data"]["html"] = html
97 return await self.call_action(envelope, action_data=action_data)
99 def render_template(
100 self,
101 template: str,
102 envelope: Envelope,
103 action_data: dict[str, Any],
104 snapshot_url: str | None,
105 preformatted_html: str | None,
106 ) -> str | None:
107 alert = {}
108 try:
109 alert = {
110 "message": action_data.get(ATTR_MESSAGE),
111 "title": action_data.get(ATTR_TITLE),
112 "envelope": envelope,
113 "subheading": "Home Assistant Notification",
114 "configuration": self.context,
115 "preformatted_html": preformatted_html,
116 "img": None,
117 }
118 if snapshot_url:
119 alert["img"] = {"text": "Snapshot Image", "url": snapshot_url}
120 env = Environment(loader=FileSystemLoader(self.template_path or ""), autoescape=True)
121 template_obj = env.get_template(template)
122 html = template_obj.render(alert=alert)
123 if not html:
124 _LOGGER.error("Empty result from template %s", template)
125 else:
126 return html
127 except Exception as e:
128 _LOGGER.error("SUPERNOTIFY Failed to generate html mail: %s", e)
129 _LOGGER.debug("SUPERNOTIFY Template failure: %s", alert, exc_info=True)
130 return None