Skip to content

Commit

Permalink
maint: Consolidate sensor code to one place
Browse files Browse the repository at this point in the history
  • Loading branch information
RogerSelwyn committed Jul 13, 2024
1 parent 80eb85d commit 08dc34c
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 272 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
SENSOR_AUTO_REPLY,
SENSOR_EMAIL,
)
from .mailsensor import build_mail_query
from .sensor_integration import build_mail_query

_LOGGER = logging.getLogger(__name__)

Expand Down
190 changes: 190 additions & 0 deletions custom_components/ms365_mail/integration/notify_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
"""Notification processing."""

import logging
import os
import zipfile
from pathlib import Path

from homeassistant.components.notify import (
ATTR_DATA,
ATTR_TARGET,
ATTR_TITLE,
BaseNotificationService,
)

from ..const import CONF_ENTITY_NAME
from ..helpers.config_entry import MS365ConfigEntry
from .const_integration import (
ATTR_ATTACHMENTS,
ATTR_IMPORTANCE,
ATTR_MESSAGE_IS_HTML,
ATTR_PHOTOS,
ATTR_SENDER,
ATTR_ZIP_ATTACHMENTS,
ATTR_ZIP_NAME,
CONF_ENTRY,
PERM_MAIL_SEND,
)
from .schema_integration import NOTIFY_SERVICE_BASE_SCHEMA

_LOGGER = logging.getLogger(__name__)


async def async_integration_get_service(hass, config, discovery_info=None): # pylint: disable=unused-argument
"""Get the service."""
if discovery_info is None or not hasattr(
discovery_info[CONF_ENTRY], "runtime_data"
):
return

entry: MS365ConfigEntry = discovery_info[CONF_ENTRY]
account = entry.runtime_data.account
if (
account.is_authenticated
and entry.runtime_data.permissions.validate_authorization(PERM_MAIL_SEND)
):
return MS365EmailService(account, hass, entry)

return


class MS365EmailService(BaseNotificationService):
"""Implement the notification service for MS365."""

def __init__(self, account, hass, entry: MS365ConfigEntry):
"""Initialize the service."""
self.account = account
self._entry = entry
self._cleanup_files = []
self._hass = hass

@property
def targets(self):
"""Targets property."""
return {f"_{self._entry.data.get(CONF_ENTITY_NAME)}": ""}

def send_message(self, message="", **kwargs):
"""Send a message to a user."""
_LOGGER.warning("Non async send_message unsupported")

async def async_send_message(self, message="", **kwargs):
"""Send an async message to a user."""
if not self._entry.runtime_data.permissions.validate_authorization(
PERM_MAIL_SEND
):
_LOGGER.error(
"Not authorisied to send mail - requires permission: %s", PERM_MAIL_SEND
)
return

self._cleanup_files = []
data = kwargs.get(ATTR_DATA)
if data is None:
kwargs.pop(ATTR_DATA)

NOTIFY_SERVICE_BASE_SCHEMA(kwargs)

title = kwargs.get(ATTR_TITLE, "Notification from Home Assistant")

if data and data.get(ATTR_TARGET, None):
target = data.get(ATTR_TARGET)
else:
resp = await self.hass.async_add_executor_job(self.account.get_current_user)
target = resp.mail

new_message = self.account.new_message()
message = self._build_message(data, message, new_message.attachments)
self._build_attachments(data, new_message.attachments)
new_message.to.add(target)
if data:
if data.get(ATTR_SENDER, None):
new_message.sender = data.get(ATTR_SENDER)
if data.get(ATTR_IMPORTANCE, None):
new_message.importance = data.get(ATTR_IMPORTANCE)
new_message.subject = title
new_message.body = message
await self.hass.async_add_executor_job(new_message.send)

self._cleanup()

def _build_message(self, data, message, new_message_attachments):
is_html = False
photos = []
if data:
is_html = data.get(ATTR_MESSAGE_IS_HTML, False)
photos = data.get(ATTR_PHOTOS, [])
if is_html or photos:
message = f"""
<html>
<body>
{message}"""
message += self._build_photo_content(photos, new_message_attachments)
message += "</body></html>"

return message

def _build_photo_content(self, photos, new_message_attachments):
if isinstance(photos, str):
photos = [photos]

photos_content = ""
for i, photo in enumerate(photos, start=1):
if photo.startswith("http"):
photos_content += f'<br><img src="{photo}">'
else:
photo = self._get_ha_filepath(photo)
new_message_attachments.add(photo)
att = new_message_attachments[-1]
att.is_inline = True
att.content_id = str(i)
photos_content += f'<br><img src="cid:{att.content_id}">'

return photos_content

def _build_attachments(self, data, new_message_attachments):
attachments = []
zip_attachments = False
zip_name = None
if data:
attachments = data.get(ATTR_ATTACHMENTS, [])
zip_attachments = data.get(ATTR_ZIP_ATTACHMENTS, False)
zip_name = data.get(ATTR_ZIP_NAME, None)

attachments = [self._get_ha_filepath(x) for x in attachments]
if attachments and zip_attachments:
z_file = zip_files(attachments, zip_name)
new_message_attachments.add(z_file)
self._cleanup_files.append(z_file)

else:
for attachment in attachments:
new_message_attachments.add(attachment)

def _cleanup(self):
for filename in self._cleanup_files:
os.remove(filename)

def _get_ha_filepath(self, filepath):
"""Get the file path."""
_filepath = Path(filepath)
if _filepath.parts[0] == "/" and _filepath.parts[1] == "config":
_filepath = os.path.join(self._hass.config.config_dir, *_filepath.parts[2:])

if not os.path.isfile(_filepath):
if not os.path.isfile(filepath):
raise ValueError(f"Could not access file {filepath} at {_filepath}")
return filepath
return _filepath


def zip_files(filespaths, zip_name):
"""Zip the files."""
if not zip_name:
zip_name = "archive.zip"
if Path(zip_name).suffix != ".zip":
zip_name += ".zip"

with zipfile.ZipFile(zip_name, mode="w") as zip_file:
for file_path in filespaths:
zip_file.write(file_path, os.path.basename(file_path))
return zip_name
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
"""MS365 mail sensors."""

import datetime
import logging
from operator import itemgetter

from homeassistant.components.sensor import SensorEntity
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from O365 import mailbox # pylint: disable=no-name-in-module

from ..classes.entity import MS365Entity
from ..const import ATTR_DATA, DATETIME_FORMAT
from ..const import (
ATTR_DATA,
CONF_ENABLE_UPDATE,
CONF_ENTITY_KEY,
CONF_ENTITY_TYPE,
DATETIME_FORMAT,
)
from ..helpers.config_entry import MS365ConfigEntry
from ..helpers.utils import clean_html
from .const_integration import (
Expand All @@ -20,6 +31,7 @@
ATTR_STATE,
CONF_BODY_CONTAINS,
CONF_DOWNLOAD_ATTACHMENTS,
CONF_ENABLE_AUTOREPLY,
CONF_HAS_ATTACHMENT,
CONF_HTML_BODY,
CONF_IMPORTANCE,
Expand All @@ -29,11 +41,96 @@
CONF_SUBJECT_CONTAINS,
CONF_SUBJECT_IS,
PERM_MAILBOX_SETTINGS,
SENSOR_AUTO_REPLY,
SENSOR_EMAIL,
Attachment,
Unread,
)
from .schema_integration import (
AUTO_REPLY_SERVICE_DISABLE_SCHEMA,
AUTO_REPLY_SERVICE_ENABLE_SCHEMA,
)
from .utils_integration import get_email_attributes

_LOGGER = logging.getLogger(__name__)


async def async_integration_setup_entry(
hass: HomeAssistant, # pylint: disable=unused-argument
entry: MS365ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the MS365 platform."""

account = entry.runtime_data.account

is_authenticated = account.is_authenticated
if not is_authenticated:
return False

sensor_entities = _sensor_entities(entry)
email_entities = _email_entities(entry)
entities = sensor_entities + email_entities

async_add_entities(entities, False)
await _async_setup_register_services(entry)

return True


def _sensor_entities(entry):
return [
MS365AutoReplySensor(
entry.runtime_data.coordinator,
entry,
key[CONF_NAME],
key[CONF_ENTITY_KEY],
key[CONF_UNIQUE_ID],
)
for key in entry.runtime_data.sensors
if key[CONF_ENTITY_TYPE] == SENSOR_AUTO_REPLY
]


def _email_entities(entry):
return [
MS365MailSensor(
entry.runtime_data.coordinator,
entry,
key[CONF_NAME],
key[CONF_ENTITY_KEY],
key[CONF_UNIQUE_ID],
)
for key in entry.runtime_data.sensors
if key[CONF_ENTITY_TYPE] == SENSOR_EMAIL
]


async def _async_setup_register_services(entry):
perms = entry.runtime_data.permissions
await _async_setup_mailbox_services(entry, perms)


async def _async_setup_mailbox_services(entry, perms):
if not entry.data.get(CONF_ENABLE_UPDATE):
return

if not entry.data.get(CONF_ENABLE_AUTOREPLY):
return

platform = entity_platform.async_get_current_platform()
if perms.validate_authorization(PERM_MAILBOX_SETTINGS):
platform.async_register_entity_service(
"auto_reply_enable",
AUTO_REPLY_SERVICE_ENABLE_SCHEMA,
"auto_reply_enable",
)
platform.async_register_entity_service(
"auto_reply_disable",
AUTO_REPLY_SERVICE_DISABLE_SCHEMA,
"auto_reply_disable",
)


class MS365MailSensor(MS365Entity, SensorEntity):
"""MS365 generic Mail Sensor class."""
Expand Down
Loading

0 comments on commit 08dc34c

Please sign in to comment.