150 lines
4.7 KiB
Python
150 lines
4.7 KiB
Python
"""Slack platform for notify component."""
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
from urllib.parse import urlparse
|
|
|
|
from slack import WebClient
|
|
from slack.errors import SlackApiError
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.notify import (
|
|
ATTR_DATA,
|
|
ATTR_TARGET,
|
|
ATTR_TITLE,
|
|
PLATFORM_SCHEMA,
|
|
BaseNotificationService,
|
|
)
|
|
from homeassistant.const import CONF_API_KEY, CONF_ICON, CONF_USERNAME
|
|
from homeassistant.core import callback
|
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
ATTR_ATTACHMENTS = "attachments"
|
|
ATTR_BLOCKS = "blocks"
|
|
ATTR_FILE = "file"
|
|
|
|
CONF_DEFAULT_CHANNEL = "default_channel"
|
|
|
|
DEFAULT_TIMEOUT_SECONDS = 15
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
{
|
|
vol.Required(CONF_API_KEY): cv.string,
|
|
vol.Required(CONF_DEFAULT_CHANNEL): cv.string,
|
|
vol.Optional(CONF_ICON): cv.string,
|
|
vol.Optional(CONF_USERNAME): cv.string,
|
|
}
|
|
)
|
|
|
|
|
|
async def async_get_service(hass, config, discovery_info=None):
|
|
"""Set up the Slack notification service."""
|
|
session = aiohttp_client.async_get_clientsession(hass)
|
|
client = WebClient(token=config[CONF_API_KEY], run_async=True, session=session)
|
|
|
|
try:
|
|
await client.auth_test()
|
|
except SlackApiError as err:
|
|
_LOGGER.error("Error while setting up integration: %s", err)
|
|
return
|
|
|
|
return SlackNotificationService(
|
|
hass,
|
|
client,
|
|
config[CONF_DEFAULT_CHANNEL],
|
|
username=config.get(CONF_USERNAME),
|
|
icon=config.get(CONF_ICON),
|
|
)
|
|
|
|
|
|
@callback
|
|
def _async_sanitize_channel_names(channel_list):
|
|
"""Remove any # symbols from a channel list."""
|
|
return [channel.lstrip("#") for channel in channel_list]
|
|
|
|
|
|
class SlackNotificationService(BaseNotificationService):
|
|
"""Define the Slack notification logic."""
|
|
|
|
def __init__(self, hass, client, default_channel, username, icon):
|
|
"""Initialize."""
|
|
self._client = client
|
|
self._default_channel = default_channel
|
|
self._hass = hass
|
|
self._icon = icon
|
|
self._username = username
|
|
|
|
async def _async_send_local_file_message(self, path, targets, message, title):
|
|
"""Upload a local file (with message) to Slack."""
|
|
if not self._hass.config.is_allowed_path(path):
|
|
_LOGGER.error("Path does not exist or is not allowed: %s", path)
|
|
return
|
|
|
|
parsed_url = urlparse(path)
|
|
filename = os.path.basename(parsed_url.path)
|
|
|
|
try:
|
|
await self._client.files_upload(
|
|
channels=",".join(targets),
|
|
file=path,
|
|
filename=filename,
|
|
initial_comment=message,
|
|
title=title or filename,
|
|
)
|
|
except SlackApiError as err:
|
|
_LOGGER.error("Error while uploading file-based message: %s", err)
|
|
|
|
async def _async_send_text_only_message(
|
|
self, targets, message, title, attachments, blocks
|
|
):
|
|
"""Send a text-only message."""
|
|
tasks = {
|
|
target: self._client.chat_postMessage(
|
|
channel=target,
|
|
text=message,
|
|
attachments=attachments,
|
|
blocks=blocks,
|
|
icon_emoji=self._icon,
|
|
link_names=True,
|
|
username=self._username,
|
|
)
|
|
for target in targets
|
|
}
|
|
|
|
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
|
|
for target, result in zip(tasks, results):
|
|
if isinstance(result, SlackApiError):
|
|
_LOGGER.error(
|
|
"There was a Slack API error while sending to %s: %s",
|
|
target,
|
|
result,
|
|
)
|
|
|
|
async def async_send_message(self, message, **kwargs):
|
|
"""Send a message to Slack."""
|
|
data = kwargs[ATTR_DATA] or {}
|
|
title = kwargs.get(ATTR_TITLE)
|
|
targets = _async_sanitize_channel_names(
|
|
kwargs.get(ATTR_TARGET, [self._default_channel])
|
|
)
|
|
|
|
if ATTR_FILE in data:
|
|
return await self._async_send_local_file_message(
|
|
data[ATTR_FILE], targets, message, title
|
|
)
|
|
|
|
attachments = data.get(ATTR_ATTACHMENTS, {})
|
|
if attachments:
|
|
_LOGGER.warning(
|
|
"Attachments are deprecated and part of Slack's legacy API; support "
|
|
"for them will be dropped in 0.114.0. In most cases, Blocks should be "
|
|
"used instead: https://www.home-assistant.io/integrations/slack/"
|
|
)
|
|
blocks = data.get(ATTR_BLOCKS, {})
|
|
|
|
return await self._async_send_text_only_message(
|
|
targets, message, title, attachments, blocks
|
|
)
|