Hangouts help "page" and little bugfix (#16464)

* add 'default_conversations' parameter

* remove the empty message segment at the end of every message

* add 'HangoutsHelp' intent

* add hangouts/intents.py
pull/16474/merge
Marcel Hoppe 2018-09-12 13:27:21 +02:00 committed by Paulus Schoutsen
parent ff78a5b04b
commit 4efe86327d
5 changed files with 95 additions and 19 deletions

View File

@ -123,6 +123,7 @@ omit =
homeassistant/components/hangouts/const.py
homeassistant/components/hangouts/hangouts_bot.py
homeassistant/components/hangouts/hangups_utils.py
homeassistant/components/hangouts/intents.py
homeassistant/components/*/hangouts.py
homeassistant/components/hdmi_cec.py

View File

@ -9,7 +9,9 @@ import logging
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.hangouts.intents import HelpIntent
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import intent
from homeassistant.helpers import dispatcher
import homeassistant.helpers.config_validation as cv
@ -18,12 +20,13 @@ from .const import (
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
MESSAGE_SCHEMA, SERVICE_SEND_MESSAGE,
SERVICE_UPDATE, CONF_SENTENCES, CONF_MATCHERS,
CONF_ERROR_SUPPRESSED_CONVERSATIONS, INTENT_SCHEMA, TARGETS_SCHEMA)
CONF_ERROR_SUPPRESSED_CONVERSATIONS, INTENT_SCHEMA, TARGETS_SCHEMA,
CONF_DEFAULT_CONVERSATIONS, EVENT_HANGOUTS_CONVERSATIONS_RESOLVED,
INTENT_HELP)
# We need an import from .config_flow, without it .config_flow is never loaded.
from .config_flow import HangoutsFlowHandler # noqa: F401
REQUIREMENTS = ['hangups==0.4.5']
_LOGGER = logging.getLogger(__name__)
@ -33,6 +36,8 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_INTENTS, default={}): vol.Schema({
cv.string: INTENT_SCHEMA
}),
vol.Optional(CONF_DEFAULT_CONVERSATIONS, default=[]):
[TARGETS_SCHEMA],
vol.Optional(CONF_ERROR_SUPPRESSED_CONVERSATIONS, default=[]):
[TARGETS_SCHEMA]
})
@ -47,16 +52,23 @@ async def async_setup(hass, config):
if config is None:
hass.data[DOMAIN] = {
CONF_INTENTS: {},
CONF_DEFAULT_CONVERSATIONS: [],
CONF_ERROR_SUPPRESSED_CONVERSATIONS: [],
}
return True
hass.data[DOMAIN] = {
CONF_INTENTS: config[CONF_INTENTS],
CONF_DEFAULT_CONVERSATIONS: config[CONF_DEFAULT_CONVERSATIONS],
CONF_ERROR_SUPPRESSED_CONVERSATIONS:
config[CONF_ERROR_SUPPRESSED_CONVERSATIONS],
}
if (hass.data[DOMAIN][CONF_INTENTS] and
INTENT_HELP not in hass.data[DOMAIN][CONF_INTENTS]):
hass.data[DOMAIN][CONF_INTENTS][INTENT_HELP] = {
CONF_SENTENCES: ['HELP']}
for data in hass.data[DOMAIN][CONF_INTENTS].values():
matchers = []
for sentence in data[CONF_SENTENCES]:
@ -82,6 +94,7 @@ async def async_setup_entry(hass, config):
hass,
config.data.get(CONF_REFRESH_TOKEN),
hass.data[DOMAIN][CONF_INTENTS],
hass.data[DOMAIN][CONF_DEFAULT_CONVERSATIONS],
hass.data[DOMAIN][CONF_ERROR_SUPPRESSED_CONVERSATIONS])
hass.data[DOMAIN][CONF_BOT] = bot
except GoogleAuthError as exception:
@ -96,11 +109,12 @@ async def async_setup_entry(hass, config):
dispatcher.async_dispatcher_connect(
hass,
EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
bot.async_update_conversation_commands)
bot.async_resolve_conversations)
dispatcher.async_dispatcher_connect(
hass,
EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
bot.async_handle_update_error_suppressed_conversations)
EVENT_HANGOUTS_CONVERSATIONS_RESOLVED,
bot.async_update_conversation_commands)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
bot.async_handle_hass_stop)
@ -116,6 +130,8 @@ async def async_setup_entry(hass, config):
async_handle_update_users_and_conversations,
schema=vol.Schema({}))
intent.async_register(hass, HelpIntent(hass))
return True

View File

@ -24,10 +24,13 @@ CONF_INTENT_TYPE = 'intent_type'
CONF_SENTENCES = 'sentences'
CONF_MATCHERS = 'matchers'
INTENT_HELP = 'HangoutsHelp'
EVENT_HANGOUTS_CONNECTED = 'hangouts_connected'
EVENT_HANGOUTS_DISCONNECTED = 'hangouts_disconnected'
EVENT_HANGOUTS_USERS_CHANGED = 'hangouts_users_changed'
EVENT_HANGOUTS_CONVERSATIONS_CHANGED = 'hangouts_conversations_changed'
EVENT_HANGOUTS_CONVERSATIONS_RESOLVED = 'hangouts_conversations_resolved'
EVENT_HANGOUTS_MESSAGE_RECEIVED = 'hangouts_message_received'
CONF_CONVERSATION_ID = 'id'

View File

@ -8,7 +8,7 @@ from .const import (
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
EVENT_HANGOUTS_DISCONNECTED, EVENT_HANGOUTS_MESSAGE_RECEIVED,
CONF_MATCHERS, CONF_CONVERSATION_ID,
CONF_CONVERSATION_NAME)
CONF_CONVERSATION_NAME, EVENT_HANGOUTS_CONVERSATIONS_RESOLVED, INTENT_HELP)
_LOGGER = logging.getLogger(__name__)
@ -16,7 +16,8 @@ _LOGGER = logging.getLogger(__name__)
class HangoutsBot:
"""The Hangouts Bot."""
def __init__(self, hass, refresh_token, intents, error_suppressed_convs):
def __init__(self, hass, refresh_token, intents,
default_convs, error_suppressed_convs):
"""Set up the client."""
self.hass = hass
self._connected = False
@ -29,6 +30,8 @@ class HangoutsBot:
self._client = None
self._user_list = None
self._conversation_list = None
self._default_convs = default_convs
self._default_conv_ids = None
self._error_suppressed_convs = error_suppressed_convs
self._error_suppressed_conv_ids = None
@ -51,7 +54,7 @@ class HangoutsBot:
return conv
return None
def async_update_conversation_commands(self, _):
def async_update_conversation_commands(self):
"""Refresh the commands for every conversation."""
self._conversation_intents = {}
@ -63,6 +66,8 @@ class HangoutsBot:
if conv_id is not None:
conversations.append(conv_id)
data['_' + CONF_CONVERSATIONS] = conversations
elif self._default_conv_ids:
data['_' + CONF_CONVERSATIONS] = self._default_conv_ids
else:
data['_' + CONF_CONVERSATIONS] = \
[conv.id_ for conv in self._conversation_list.get_all()]
@ -81,13 +86,22 @@ class HangoutsBot:
self._conversation_list.on_event.add_observer(
self._async_handle_conversation_event)
def async_handle_update_error_suppressed_conversations(self, _):
"""Resolve the list of error suppressed conversations."""
def async_resolve_conversations(self, _):
"""Resolve the list of default and error suppressed conversations."""
self._default_conv_ids = []
self._error_suppressed_conv_ids = []
for conversation in self._default_convs:
conv_id = self._resolve_conversation_id(conversation)
if conv_id is not None:
self._default_conv_ids.append(conv_id)
for conversation in self._error_suppressed_convs:
conv_id = self._resolve_conversation_id(conversation)
if conv_id is not None:
self._error_suppressed_conv_ids.append(conv_id)
dispatcher.async_dispatcher_send(self.hass,
EVENT_HANGOUTS_CONVERSATIONS_RESOLVED)
async def _async_handle_conversation_event(self, event):
from hangups import ChatMessageEvent
@ -112,7 +126,8 @@ class HangoutsBot:
if intents is not None:
is_error = False
try:
intent_result = await self._async_process(intents, message)
intent_result = await self._async_process(intents, message,
conv_id)
except (intent.UnknownIntent, intent.IntentHandleError) as err:
is_error = True
intent_result = intent.IntentResponse()
@ -133,7 +148,7 @@ class HangoutsBot:
[{'text': message, 'parse_str': True}],
[{CONF_CONVERSATION_ID: conv_id}])
async def _async_process(self, intents, text):
async def _async_process(self, intents, text, conv_id):
"""Detect a matching intent."""
for intent_type, data in intents.items():
for matcher in data.get(CONF_MATCHERS, []):
@ -141,12 +156,15 @@ class HangoutsBot:
if not match:
continue
if intent_type == INTENT_HELP:
return await self.hass.helpers.intent.async_handle(
DOMAIN, intent_type,
{'conv_id': {'value': conv_id}}, text)
response = await self.hass.helpers.intent.async_handle(
return await self.hass.helpers.intent.async_handle(
DOMAIN, intent_type,
{key: {'value': value} for key, value
in match.groupdict().items()}, text)
return response
{key: {'value': value}
for key, value in match.groupdict().items()}, text)
async def async_connect(self):
"""Login to the Google Hangouts."""
@ -204,15 +222,16 @@ class HangoutsBot:
from hangups import ChatMessageSegment, hangouts_pb2
messages = []
for segment in message:
if messages:
messages.append(ChatMessageSegment('',
segment_type=hangouts_pb2.
SEGMENT_TYPE_LINE_BREAK))
if 'parse_str' in segment and segment['parse_str']:
messages.extend(ChatMessageSegment.from_str(segment['text']))
else:
if 'parse_str' in segment:
del segment['parse_str']
messages.append(ChatMessageSegment(**segment))
messages.append(ChatMessageSegment('',
segment_type=hangouts_pb2.
SEGMENT_TYPE_LINE_BREAK))
if not messages:
return False
@ -247,3 +266,7 @@ class HangoutsBot:
async def async_handle_update_users_and_conversations(self, _=None):
"""Handle the update_users_and_conversations service."""
await self._async_list_conversations()
def get_intents(self, conv_id):
"""Return the intents for a specific conversation."""
return self._conversation_intents.get(conv_id)

View File

@ -0,0 +1,33 @@
"""Intents for the hangouts component."""
from homeassistant.helpers import intent
import homeassistant.helpers.config_validation as cv
from .const import INTENT_HELP, DOMAIN, CONF_BOT
class HelpIntent(intent.IntentHandler):
"""Handle Help intents."""
intent_type = INTENT_HELP
slot_schema = {
'conv_id': cv.string
}
def __init__(self, hass):
"""Set up the intent."""
self.hass = hass
async def async_handle(self, intent_obj):
"""Handle the intent."""
slots = self.async_validate_slots(intent_obj.slots)
conv_id = slots['conv_id']['value']
intents = self.hass.data[DOMAIN][CONF_BOT].get_intents(conv_id)
response = intent_obj.create_response()
help_text = "I understand the following sentences:"
for intent_data in intents.values():
for sentence in intent_data['sentences']:
help_text += "\n'{}'".format(sentence)
response.async_set_speech(help_text)
return response