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