Add image support to hangouts notifications (#16560)
* add image to services.yaml * add image support Add image support to hangouts notification. * fix indents * fix line length * Add data schema forgot schema * fix linelength * use is_allowed_path and better file error handling * elif not else if * fix logger error * fixes * fix travis errors/warnings * remove trailing whitespace * fix travis pylint naming * new async style * removed unused async_timeout * change to image_file/url (#3) * change to image_file/url * removed whitespace * forgot to remove unused urlparse import * image_file/url in service help
This commit is contained in:
parent
a1c914dfeb
commit
bc8d323bdd
3 changed files with 62 additions and 9 deletions
|
@ -3,7 +3,8 @@ import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.notify import ATTR_MESSAGE, ATTR_TARGET
|
from homeassistant.components.notify \
|
||||||
|
import ATTR_MESSAGE, ATTR_TARGET, ATTR_DATA
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger('homeassistant.components.hangouts')
|
_LOGGER = logging.getLogger('homeassistant.components.hangouts')
|
||||||
|
@ -56,10 +57,15 @@ MESSAGE_SEGMENT_SCHEMA = vol.Schema({
|
||||||
vol.Optional('parse_str'): cv.boolean,
|
vol.Optional('parse_str'): cv.boolean,
|
||||||
vol.Optional('link_target'): cv.string
|
vol.Optional('link_target'): cv.string
|
||||||
})
|
})
|
||||||
|
MESSAGE_DATA_SCHEMA = vol.Schema({
|
||||||
|
vol.Optional('image_file'): cv.string,
|
||||||
|
vol.Optional('image_url'): cv.string
|
||||||
|
})
|
||||||
|
|
||||||
MESSAGE_SCHEMA = vol.Schema({
|
MESSAGE_SCHEMA = vol.Schema({
|
||||||
vol.Required(ATTR_TARGET): [TARGETS_SCHEMA],
|
vol.Required(ATTR_TARGET): [TARGETS_SCHEMA],
|
||||||
vol.Required(ATTR_MESSAGE): [MESSAGE_SEGMENT_SCHEMA]
|
vol.Required(ATTR_MESSAGE): [MESSAGE_SEGMENT_SCHEMA],
|
||||||
|
vol.Optional(ATTR_DATA): MESSAGE_DATA_SCHEMA
|
||||||
})
|
})
|
||||||
|
|
||||||
INTENT_SCHEMA = vol.All(
|
INTENT_SCHEMA = vol.All(
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
"""The Hangouts Bot."""
|
"""The Hangouts Bot."""
|
||||||
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
import asyncio
|
||||||
|
import aiohttp
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers import dispatcher, intent
|
from homeassistant.helpers import dispatcher, intent
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_MESSAGE, ATTR_TARGET, CONF_CONVERSATIONS, DOMAIN,
|
ATTR_MESSAGE, ATTR_TARGET, ATTR_DATA, CONF_CONVERSATIONS, DOMAIN,
|
||||||
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
||||||
EVENT_HANGOUTS_DISCONNECTED, EVENT_HANGOUTS_MESSAGE_RECEIVED,
|
EVENT_HANGOUTS_DISCONNECTED, EVENT_HANGOUTS_MESSAGE_RECEIVED,
|
||||||
CONF_MATCHERS, CONF_CONVERSATION_ID,
|
CONF_MATCHERS, CONF_CONVERSATION_ID,
|
||||||
|
@ -146,7 +149,8 @@ class HangoutsBot:
|
||||||
is_error and conv_id in self._error_suppressed_conv_ids):
|
is_error and conv_id in self._error_suppressed_conv_ids):
|
||||||
await self._async_send_message(
|
await self._async_send_message(
|
||||||
[{'text': message, 'parse_str': True}],
|
[{'text': message, 'parse_str': True}],
|
||||||
[{CONF_CONVERSATION_ID: conv_id}])
|
[{CONF_CONVERSATION_ID: conv_id}],
|
||||||
|
None)
|
||||||
|
|
||||||
async def _async_process(self, intents, text, conv_id):
|
async def _async_process(self, intents, text, conv_id):
|
||||||
"""Detect a matching intent."""
|
"""Detect a matching intent."""
|
||||||
|
@ -203,7 +207,7 @@ class HangoutsBot:
|
||||||
"""Run once when Home Assistant stops."""
|
"""Run once when Home Assistant stops."""
|
||||||
await self.async_disconnect()
|
await self.async_disconnect()
|
||||||
|
|
||||||
async def _async_send_message(self, message, targets):
|
async def _async_send_message(self, message, targets, data):
|
||||||
conversations = []
|
conversations = []
|
||||||
for target in targets:
|
for target in targets:
|
||||||
conversation = None
|
conversation = None
|
||||||
|
@ -233,10 +237,48 @@ class HangoutsBot:
|
||||||
del segment['parse_str']
|
del segment['parse_str']
|
||||||
messages.append(ChatMessageSegment(**segment))
|
messages.append(ChatMessageSegment(**segment))
|
||||||
|
|
||||||
|
image_file = None
|
||||||
|
if data:
|
||||||
|
if data.get('image_url'):
|
||||||
|
uri = data.get('image_url')
|
||||||
|
try:
|
||||||
|
websession = async_get_clientsession(self.hass)
|
||||||
|
async with websession.get(uri, timeout=5) as response:
|
||||||
|
if response.status != 200:
|
||||||
|
_LOGGER.error(
|
||||||
|
'Fetch image failed, %s, %s',
|
||||||
|
response.status,
|
||||||
|
response
|
||||||
|
)
|
||||||
|
image_file = None
|
||||||
|
else:
|
||||||
|
image_data = await response.read()
|
||||||
|
image_file = io.BytesIO(image_data)
|
||||||
|
image_file.name = "image.png"
|
||||||
|
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
|
||||||
|
_LOGGER.error(
|
||||||
|
'Failed to fetch image, %s',
|
||||||
|
type(error)
|
||||||
|
)
|
||||||
|
image_file = None
|
||||||
|
elif data.get('image_file'):
|
||||||
|
uri = data.get('image_file')
|
||||||
|
if self.hass.config.is_allowed_path(uri):
|
||||||
|
try:
|
||||||
|
image_file = open(uri, 'rb')
|
||||||
|
except IOError as error:
|
||||||
|
_LOGGER.error(
|
||||||
|
'Image file I/O error(%s): %s',
|
||||||
|
error.errno,
|
||||||
|
error.strerror
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.error('Path "%s" not allowed', uri)
|
||||||
|
|
||||||
if not messages:
|
if not messages:
|
||||||
return False
|
return False
|
||||||
for conv in conversations:
|
for conv in conversations:
|
||||||
await conv.send_message(messages)
|
await conv.send_message(messages, image_file)
|
||||||
|
|
||||||
async def _async_list_conversations(self):
|
async def _async_list_conversations(self):
|
||||||
import hangups
|
import hangups
|
||||||
|
@ -261,7 +303,8 @@ class HangoutsBot:
|
||||||
async def async_handle_send_message(self, service):
|
async def async_handle_send_message(self, service):
|
||||||
"""Handle the send_message service."""
|
"""Handle the send_message service."""
|
||||||
await self._async_send_message(service.data[ATTR_MESSAGE],
|
await self._async_send_message(service.data[ATTR_MESSAGE],
|
||||||
service.data[ATTR_TARGET])
|
service.data[ATTR_TARGET],
|
||||||
|
service.data[ATTR_DATA])
|
||||||
|
|
||||||
async def async_handle_update_users_and_conversations(self, _=None):
|
async def async_handle_update_users_and_conversations(self, _=None):
|
||||||
"""Handle the update_users_and_conversations service."""
|
"""Handle the update_users_and_conversations service."""
|
||||||
|
|
|
@ -9,4 +9,8 @@ send_message:
|
||||||
example: '[{"id": "UgxrXzVrARmjx_C6AZx4AaABAagBo-6UCw"}, {"name": "Test Conversation"}]'
|
example: '[{"id": "UgxrXzVrARmjx_C6AZx4AaABAagBo-6UCw"}, {"name": "Test Conversation"}]'
|
||||||
message:
|
message:
|
||||||
description: List of message segments, only the "text" field is required in every segment. [Required]
|
description: List of message segments, only the "text" field is required in every segment. [Required]
|
||||||
example: '[{"text":"test", "is_bold": false, "is_italic": false, "is_strikethrough": false, "is_underline": false, "parse_str": false, "link_target": "http://google.com"}, ...]'
|
example: '[{"text":"test", "is_bold": false, "is_italic": false, "is_strikethrough": false, "is_underline": false, "parse_str": false, "link_target": "http://google.com"}, ...]'
|
||||||
|
data:
|
||||||
|
description: Other options ['image_file' / 'image_url']
|
||||||
|
example: '{ "image_file": "file" }' or '{ "image_url": "url" }'
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue