diff --git a/homeassistant/components/notify/nfandroidtv.py b/homeassistant/components/notify/nfandroidtv.py index 044a037cc2978b69653456820b3f7277d3e38484..faf5e90e0166c740eed93ba47dae160d3dc89af3 100644 --- a/homeassistant/components/notify/nfandroidtv.py +++ b/homeassistant/components/notify/nfandroidtv.py @@ -9,6 +9,8 @@ import io import base64 import requests +from requests.auth import HTTPBasicAuth +from requests.auth import HTTPDigestAuth import voluptuous as vol from homeassistant.components.notify import ( @@ -21,12 +23,14 @@ _LOGGER = logging.getLogger(__name__) CONF_IP = 'host' CONF_DURATION = 'duration' +CONF_FONTSIZE = 'fontsize' CONF_POSITION = 'position' CONF_TRANSPARENCY = 'transparency' CONF_COLOR = 'color' CONF_INTERRUPT = 'interrupt' DEFAULT_DURATION = 5 +DEFAULT_FONTSIZE = 'medium' DEFAULT_POSITION = 'bottom-right' DEFAULT_TRANSPARENCY = 'default' DEFAULT_COLOR = 'grey' @@ -37,11 +41,29 @@ DEFAULT_ICON = ( 'cMXEAAAAASUVORK5CYII=') ATTR_DURATION = 'duration' +ATTR_FONTSIZE = 'fontsize' ATTR_POSITION = 'position' ATTR_TRANSPARENCY = 'transparency' ATTR_COLOR = 'color' ATTR_BKGCOLOR = 'bkgcolor' ATTR_INTERRUPT = 'interrupt' +ATTR_IMAGE = 'filename2' +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' + +FONTSIZES = { + 'small': 1, + 'medium': 0, + 'large': 2, + 'max': 3 +} POSITIONS = { 'bottom-right': 0, @@ -75,6 +97,8 @@ COLORS = { PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_IP): cv.string, vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): vol.Coerce(int), + vol.Optional(CONF_FONTSIZE, default=DEFAULT_FONTSIZE): + vol.In(FONTSIZES.keys()), vol.Optional(CONF_POSITION, default=DEFAULT_POSITION): vol.In(POSITIONS.keys()), vol.Optional(CONF_TRANSPARENCY, default=DEFAULT_TRANSPARENCY): @@ -90,6 +114,7 @@ def get_service(hass, config, discovery_info=None): """Get the Notifications for Android TV notification service.""" remoteip = config.get(CONF_IP) duration = config.get(CONF_DURATION) + fontsize = config.get(CONF_FONTSIZE) position = config.get(CONF_POSITION) transparency = config.get(CONF_TRANSPARENCY) color = config.get(CONF_COLOR) @@ -97,23 +122,26 @@ def get_service(hass, config, discovery_info=None): timeout = config.get(CONF_TIMEOUT) return NFAndroidTVNotificationService( - remoteip, duration, position, transparency, color, interrupt, timeout) + remoteip, duration, fontsize, position, + transparency, color, interrupt, timeout, hass.config.is_allowed_path) class NFAndroidTVNotificationService(BaseNotificationService): """Notification service for Notifications for Android TV.""" - def __init__(self, remoteip, duration, position, transparency, color, - interrupt, timeout): + def __init__(self, remoteip, duration, fontsize, position, transparency, + color, interrupt, timeout, is_allowed_path): """Initialize the service.""" self._target = 'http://{}:7676'.format(remoteip) self._default_duration = duration + self._default_fontsize = fontsize self._default_position = position self._default_transparency = transparency self._default_color = color self._default_interrupt = interrupt self._timeout = timeout self._icon_file = io.BytesIO(base64.b64decode(DEFAULT_ICON)) + self.is_allowed_path = is_allowed_path def send_message(self, message="", **kwargs): """Send a message to a Android TV device.""" @@ -123,7 +151,8 @@ class NFAndroidTVNotificationService(BaseNotificationService): 'application/octet-stream', {'Expires': '0'}), type='0', title=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), - msg=message, duration="%i" % self._default_duration, + msg=message, duration='%i' % self._default_duration, + fontsize='%i' % FONTSIZES.get(self._default_fontsize), position='%i' % POSITIONS.get(self._default_position), bkgcolor='%s' % COLORS.get(self._default_color), transparency='%i' % TRANSPARENCIES.get( @@ -140,6 +169,13 @@ class NFAndroidTVNotificationService(BaseNotificationService): except ValueError: _LOGGER.warning("Invalid duration-value: %s", str(duration)) + if ATTR_FONTSIZE in data: + fontsize = data.get(ATTR_FONTSIZE) + if fontsize in FONTSIZES: + payload[ATTR_FONTSIZE] = '%i' % FONTSIZES.get(fontsize) + else: + _LOGGER.warning("Invalid fontsize-value: %s", + str(fontsize)) if ATTR_POSITION in data: position = data.get(ATTR_POSITION) if position in POSITIONS: @@ -168,6 +204,19 @@ class NFAndroidTVNotificationService(BaseNotificationService): except vol.Invalid: _LOGGER.warning("Invalid interrupt-value: %s", str(interrupt)) + filedata = data.get(ATTR_FILE) if data else None + if filedata is not None: + # Load from file or URL + file_as_bytes = self.load_file( + url=filedata.get(ATTR_FILE_URL), + local_path=filedata.get(ATTR_FILE_PATH), + username=filedata.get(ATTR_FILE_USERNAME), + password=filedata.get(ATTR_FILE_PASSWORD), + auth=filedata.get(ATTR_FILE_AUTH)) + if file_as_bytes: + payload[ATTR_IMAGE] = ( + 'image', file_as_bytes, + 'application/octet-stream', {'Expires': '0'}) try: _LOGGER.debug("Payload: %s", str(payload)) @@ -178,3 +227,37 @@ class NFAndroidTVNotificationService(BaseNotificationService): except requests.exceptions.ConnectionError as err: _LOGGER.error("Error communicating with %s: %s", self._target, str(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 is not None: + # Check whether authentication parameters are provided + if username is not None and password is not None: + # 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=DEFAULT_TIMEOUT) + else: + # Load file from URL without authentication + req = requests.get(url, timeout=DEFAULT_TIMEOUT) + return req.content + + elif local_path is not None: + # 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