From 4efe86327d7c0948b3cb68128d163bcfb448c295 Mon Sep 17 00:00:00 2001 From: Marcel Hoppe <hobbypunk90@users.noreply.github.com> Date: Wed, 12 Sep 2018 13:27:21 +0200 Subject: [PATCH] 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 --- .coveragerc | 1 + homeassistant/components/hangouts/__init__.py | 26 ++++++++-- homeassistant/components/hangouts/const.py | 3 ++ .../components/hangouts/hangouts_bot.py | 51 ++++++++++++++----- homeassistant/components/hangouts/intents.py | 33 ++++++++++++ 5 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/hangouts/intents.py diff --git a/.coveragerc b/.coveragerc index ca36f4a8dbb..bd08f5c38df 100644 --- a/.coveragerc +++ b/.coveragerc @@ -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 diff --git a/homeassistant/components/hangouts/__init__.py b/homeassistant/components/hangouts/__init__.py index ebadff57be3..8480ae09549 100644 --- a/homeassistant/components/hangouts/__init__.py +++ b/homeassistant/components/hangouts/__init__.py @@ -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 diff --git a/homeassistant/components/hangouts/const.py b/homeassistant/components/hangouts/const.py index 3b96edf93a2..caae0de169b 100644 --- a/homeassistant/components/hangouts/const.py +++ b/homeassistant/components/hangouts/const.py @@ -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' diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index 15f4156d374..7edc8898c8c 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -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) diff --git a/homeassistant/components/hangouts/intents.py b/homeassistant/components/hangouts/intents.py new file mode 100644 index 00000000000..be52f059139 --- /dev/null +++ b/homeassistant/components/hangouts/intents.py @@ -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 -- GitLab