"""Slack platform for notify component.""" import logging import requests from requests.auth import HTTPBasicAuth, HTTPDigestAuth import slacker from slacker import Slacker 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 import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) CONF_CHANNEL = "default_channel" CONF_TIMEOUT = 15 # Top level attributes in 'data' ATTR_ATTACHMENTS = "attachments" ATTR_FILE = "file" # Attributes contained in file ATTR_FILE_URL = "url" ATTR_FILE_PATH = "path" ATTR_FILE_USERNAME = "username" ATTR_FILE_PASSWORD = "password" ATTR_FILE_AUTH = "auth" # Any other value or absence of 'auth' lead to basic authentication being used ATTR_FILE_AUTH_DIGEST = "digest" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_CHANNEL): cv.string, vol.Optional(CONF_ICON): cv.string, vol.Optional(CONF_USERNAME): cv.string, } ) def get_service(hass, config, discovery_info=None): """Get the Slack notification service.""" channel = config.get(CONF_CHANNEL) api_key = config.get(CONF_API_KEY) username = config.get(CONF_USERNAME) icon = config.get(CONF_ICON) try: return SlackNotificationService( channel, api_key, username, icon, hass.config.is_allowed_path ) except slacker.Error: _LOGGER.exception("Authentication failed") return None class SlackNotificationService(BaseNotificationService): """Implement the notification service for Slack.""" def __init__(self, default_channel, api_token, username, icon, is_allowed_path): """Initialize the service.""" self._default_channel = default_channel self._api_token = api_token self._username = username self._icon = icon if self._username or self._icon: self._as_user = False else: self._as_user = True self.is_allowed_path = is_allowed_path self.slack = Slacker(self._api_token) self.slack.auth.test() def send_message(self, message="", **kwargs): """Send a message to a user.""" if kwargs.get(ATTR_TARGET) is None: targets = [self._default_channel] else: targets = kwargs.get(ATTR_TARGET) data = kwargs.get(ATTR_DATA) attachments = data.get(ATTR_ATTACHMENTS) if data else None file = data.get(ATTR_FILE) if data else None title = kwargs.get(ATTR_TITLE) for target in targets: try: if file is not None: # Load from file or URL file_as_bytes = self.load_file( url=file.get(ATTR_FILE_URL), local_path=file.get(ATTR_FILE_PATH), username=file.get(ATTR_FILE_USERNAME), password=file.get(ATTR_FILE_PASSWORD), auth=file.get(ATTR_FILE_AUTH), ) # Choose filename if file.get(ATTR_FILE_URL): filename = file.get(ATTR_FILE_URL) else: filename = file.get(ATTR_FILE_PATH) # Prepare structure for Slack API data = { "content": None, "filetype": None, "filename": filename, # If optional title is none use the filename "title": title if title else filename, "initial_comment": message, "channels": target, } # Post to slack self.slack.files.post( "files.upload", data=data, files={"file": file_as_bytes} ) else: self.slack.chat.post_message( target, message, as_user=self._as_user, username=self._username, icon_emoji=self._icon, attachments=attachments, link_names=True, ) except slacker.Error as err: _LOGGER.error("Could not send notification. Error: %s", err) def load_file( self, url=None, local_path=None, username=None, password=None, auth=None ): """Load image/document/etc from a local path or URL.""" try: if url: # Check whether authentication parameters are provided if username: # Use digest or basic authentication if ATTR_FILE_AUTH_DIGEST == auth: auth_ = HTTPDigestAuth(username, password) else: auth_ = HTTPBasicAuth(username, password) # Load file from URL with authentication req = requests.get(url, auth=auth_, timeout=CONF_TIMEOUT) else: # Load file from URL without authentication req = requests.get(url, timeout=CONF_TIMEOUT) return req.content if local_path: # Check whether path is whitelisted in configuration.yaml if self.is_allowed_path(local_path): return open(local_path, "rb") _LOGGER.warning("'%s' is not secure to load data from!", local_path) else: _LOGGER.warning("Neither URL nor local path found in params!") except OSError as error: _LOGGER.error("Can't load from URL or local path: %s", error) return None