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.pypull/16474/merge
parent
ff78a5b04b
commit
4efe86327d
|
@ -123,6 +123,7 @@ omit =
|
||||||
homeassistant/components/hangouts/const.py
|
homeassistant/components/hangouts/const.py
|
||||||
homeassistant/components/hangouts/hangouts_bot.py
|
homeassistant/components/hangouts/hangouts_bot.py
|
||||||
homeassistant/components/hangouts/hangups_utils.py
|
homeassistant/components/hangouts/hangups_utils.py
|
||||||
|
homeassistant/components/hangouts/intents.py
|
||||||
homeassistant/components/*/hangouts.py
|
homeassistant/components/*/hangouts.py
|
||||||
|
|
||||||
homeassistant/components/hdmi_cec.py
|
homeassistant/components/hdmi_cec.py
|
||||||
|
|
|
@ -9,7 +9,9 @@ import logging
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.hangouts.intents import HelpIntent
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
from homeassistant.helpers import intent
|
||||||
from homeassistant.helpers import dispatcher
|
from homeassistant.helpers import dispatcher
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
@ -18,12 +20,13 @@ from .const import (
|
||||||
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
||||||
MESSAGE_SCHEMA, SERVICE_SEND_MESSAGE,
|
MESSAGE_SCHEMA, SERVICE_SEND_MESSAGE,
|
||||||
SERVICE_UPDATE, CONF_SENTENCES, CONF_MATCHERS,
|
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.
|
# We need an import from .config_flow, without it .config_flow is never loaded.
|
||||||
from .config_flow import HangoutsFlowHandler # noqa: F401
|
from .config_flow import HangoutsFlowHandler # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
REQUIREMENTS = ['hangups==0.4.5']
|
REQUIREMENTS = ['hangups==0.4.5']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -33,6 +36,8 @@ CONFIG_SCHEMA = vol.Schema({
|
||||||
vol.Optional(CONF_INTENTS, default={}): vol.Schema({
|
vol.Optional(CONF_INTENTS, default={}): vol.Schema({
|
||||||
cv.string: INTENT_SCHEMA
|
cv.string: INTENT_SCHEMA
|
||||||
}),
|
}),
|
||||||
|
vol.Optional(CONF_DEFAULT_CONVERSATIONS, default=[]):
|
||||||
|
[TARGETS_SCHEMA],
|
||||||
vol.Optional(CONF_ERROR_SUPPRESSED_CONVERSATIONS, default=[]):
|
vol.Optional(CONF_ERROR_SUPPRESSED_CONVERSATIONS, default=[]):
|
||||||
[TARGETS_SCHEMA]
|
[TARGETS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@ -47,16 +52,23 @@ async def async_setup(hass, config):
|
||||||
if config is None:
|
if config is None:
|
||||||
hass.data[DOMAIN] = {
|
hass.data[DOMAIN] = {
|
||||||
CONF_INTENTS: {},
|
CONF_INTENTS: {},
|
||||||
|
CONF_DEFAULT_CONVERSATIONS: [],
|
||||||
CONF_ERROR_SUPPRESSED_CONVERSATIONS: [],
|
CONF_ERROR_SUPPRESSED_CONVERSATIONS: [],
|
||||||
}
|
}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
hass.data[DOMAIN] = {
|
hass.data[DOMAIN] = {
|
||||||
CONF_INTENTS: config[CONF_INTENTS],
|
CONF_INTENTS: config[CONF_INTENTS],
|
||||||
|
CONF_DEFAULT_CONVERSATIONS: config[CONF_DEFAULT_CONVERSATIONS],
|
||||||
CONF_ERROR_SUPPRESSED_CONVERSATIONS:
|
CONF_ERROR_SUPPRESSED_CONVERSATIONS:
|
||||||
config[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():
|
for data in hass.data[DOMAIN][CONF_INTENTS].values():
|
||||||
matchers = []
|
matchers = []
|
||||||
for sentence in data[CONF_SENTENCES]:
|
for sentence in data[CONF_SENTENCES]:
|
||||||
|
@ -82,6 +94,7 @@ async def async_setup_entry(hass, config):
|
||||||
hass,
|
hass,
|
||||||
config.data.get(CONF_REFRESH_TOKEN),
|
config.data.get(CONF_REFRESH_TOKEN),
|
||||||
hass.data[DOMAIN][CONF_INTENTS],
|
hass.data[DOMAIN][CONF_INTENTS],
|
||||||
|
hass.data[DOMAIN][CONF_DEFAULT_CONVERSATIONS],
|
||||||
hass.data[DOMAIN][CONF_ERROR_SUPPRESSED_CONVERSATIONS])
|
hass.data[DOMAIN][CONF_ERROR_SUPPRESSED_CONVERSATIONS])
|
||||||
hass.data[DOMAIN][CONF_BOT] = bot
|
hass.data[DOMAIN][CONF_BOT] = bot
|
||||||
except GoogleAuthError as exception:
|
except GoogleAuthError as exception:
|
||||||
|
@ -96,11 +109,12 @@ async def async_setup_entry(hass, config):
|
||||||
dispatcher.async_dispatcher_connect(
|
dispatcher.async_dispatcher_connect(
|
||||||
hass,
|
hass,
|
||||||
EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
||||||
bot.async_update_conversation_commands)
|
bot.async_resolve_conversations)
|
||||||
|
|
||||||
dispatcher.async_dispatcher_connect(
|
dispatcher.async_dispatcher_connect(
|
||||||
hass,
|
hass,
|
||||||
EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
EVENT_HANGOUTS_CONVERSATIONS_RESOLVED,
|
||||||
bot.async_handle_update_error_suppressed_conversations)
|
bot.async_update_conversation_commands)
|
||||||
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||||
bot.async_handle_hass_stop)
|
bot.async_handle_hass_stop)
|
||||||
|
@ -116,6 +130,8 @@ async def async_setup_entry(hass, config):
|
||||||
async_handle_update_users_and_conversations,
|
async_handle_update_users_and_conversations,
|
||||||
schema=vol.Schema({}))
|
schema=vol.Schema({}))
|
||||||
|
|
||||||
|
intent.async_register(hass, HelpIntent(hass))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,13 @@ CONF_INTENT_TYPE = 'intent_type'
|
||||||
CONF_SENTENCES = 'sentences'
|
CONF_SENTENCES = 'sentences'
|
||||||
CONF_MATCHERS = 'matchers'
|
CONF_MATCHERS = 'matchers'
|
||||||
|
|
||||||
|
INTENT_HELP = 'HangoutsHelp'
|
||||||
|
|
||||||
EVENT_HANGOUTS_CONNECTED = 'hangouts_connected'
|
EVENT_HANGOUTS_CONNECTED = 'hangouts_connected'
|
||||||
EVENT_HANGOUTS_DISCONNECTED = 'hangouts_disconnected'
|
EVENT_HANGOUTS_DISCONNECTED = 'hangouts_disconnected'
|
||||||
EVENT_HANGOUTS_USERS_CHANGED = 'hangouts_users_changed'
|
EVENT_HANGOUTS_USERS_CHANGED = 'hangouts_users_changed'
|
||||||
EVENT_HANGOUTS_CONVERSATIONS_CHANGED = 'hangouts_conversations_changed'
|
EVENT_HANGOUTS_CONVERSATIONS_CHANGED = 'hangouts_conversations_changed'
|
||||||
|
EVENT_HANGOUTS_CONVERSATIONS_RESOLVED = 'hangouts_conversations_resolved'
|
||||||
EVENT_HANGOUTS_MESSAGE_RECEIVED = 'hangouts_message_received'
|
EVENT_HANGOUTS_MESSAGE_RECEIVED = 'hangouts_message_received'
|
||||||
|
|
||||||
CONF_CONVERSATION_ID = 'id'
|
CONF_CONVERSATION_ID = 'id'
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .const import (
|
||||||
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,
|
||||||
CONF_CONVERSATION_NAME)
|
CONF_CONVERSATION_NAME, EVENT_HANGOUTS_CONVERSATIONS_RESOLVED, INTENT_HELP)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
class HangoutsBot:
|
class HangoutsBot:
|
||||||
"""The Hangouts Bot."""
|
"""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."""
|
"""Set up the client."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
@ -29,6 +30,8 @@ class HangoutsBot:
|
||||||
self._client = None
|
self._client = None
|
||||||
self._user_list = None
|
self._user_list = None
|
||||||
self._conversation_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_convs = error_suppressed_convs
|
||||||
self._error_suppressed_conv_ids = None
|
self._error_suppressed_conv_ids = None
|
||||||
|
|
||||||
|
@ -51,7 +54,7 @@ class HangoutsBot:
|
||||||
return conv
|
return conv
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def async_update_conversation_commands(self, _):
|
def async_update_conversation_commands(self):
|
||||||
"""Refresh the commands for every conversation."""
|
"""Refresh the commands for every conversation."""
|
||||||
self._conversation_intents = {}
|
self._conversation_intents = {}
|
||||||
|
|
||||||
|
@ -63,6 +66,8 @@ class HangoutsBot:
|
||||||
if conv_id is not None:
|
if conv_id is not None:
|
||||||
conversations.append(conv_id)
|
conversations.append(conv_id)
|
||||||
data['_' + CONF_CONVERSATIONS] = conversations
|
data['_' + CONF_CONVERSATIONS] = conversations
|
||||||
|
elif self._default_conv_ids:
|
||||||
|
data['_' + CONF_CONVERSATIONS] = self._default_conv_ids
|
||||||
else:
|
else:
|
||||||
data['_' + CONF_CONVERSATIONS] = \
|
data['_' + CONF_CONVERSATIONS] = \
|
||||||
[conv.id_ for conv in self._conversation_list.get_all()]
|
[conv.id_ for conv in self._conversation_list.get_all()]
|
||||||
|
@ -81,13 +86,22 @@ class HangoutsBot:
|
||||||
self._conversation_list.on_event.add_observer(
|
self._conversation_list.on_event.add_observer(
|
||||||
self._async_handle_conversation_event)
|
self._async_handle_conversation_event)
|
||||||
|
|
||||||
def async_handle_update_error_suppressed_conversations(self, _):
|
def async_resolve_conversations(self, _):
|
||||||
"""Resolve the list of error suppressed conversations."""
|
"""Resolve the list of default and error suppressed conversations."""
|
||||||
|
self._default_conv_ids = []
|
||||||
self._error_suppressed_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:
|
for conversation in self._error_suppressed_convs:
|
||||||
conv_id = self._resolve_conversation_id(conversation)
|
conv_id = self._resolve_conversation_id(conversation)
|
||||||
if conv_id is not None:
|
if conv_id is not None:
|
||||||
self._error_suppressed_conv_ids.append(conv_id)
|
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):
|
async def _async_handle_conversation_event(self, event):
|
||||||
from hangups import ChatMessageEvent
|
from hangups import ChatMessageEvent
|
||||||
|
@ -112,7 +126,8 @@ class HangoutsBot:
|
||||||
if intents is not None:
|
if intents is not None:
|
||||||
is_error = False
|
is_error = False
|
||||||
try:
|
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:
|
except (intent.UnknownIntent, intent.IntentHandleError) as err:
|
||||||
is_error = True
|
is_error = True
|
||||||
intent_result = intent.IntentResponse()
|
intent_result = intent.IntentResponse()
|
||||||
|
@ -133,7 +148,7 @@ class HangoutsBot:
|
||||||
[{'text': message, 'parse_str': True}],
|
[{'text': message, 'parse_str': True}],
|
||||||
[{CONF_CONVERSATION_ID: conv_id}])
|
[{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."""
|
"""Detect a matching intent."""
|
||||||
for intent_type, data in intents.items():
|
for intent_type, data in intents.items():
|
||||||
for matcher in data.get(CONF_MATCHERS, []):
|
for matcher in data.get(CONF_MATCHERS, []):
|
||||||
|
@ -141,12 +156,15 @@ class HangoutsBot:
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
continue
|
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,
|
DOMAIN, intent_type,
|
||||||
{key: {'value': value} for key, value
|
{key: {'value': value}
|
||||||
in match.groupdict().items()}, text)
|
for key, value in match.groupdict().items()}, text)
|
||||||
return response
|
|
||||||
|
|
||||||
async def async_connect(self):
|
async def async_connect(self):
|
||||||
"""Login to the Google Hangouts."""
|
"""Login to the Google Hangouts."""
|
||||||
|
@ -204,15 +222,16 @@ class HangoutsBot:
|
||||||
from hangups import ChatMessageSegment, hangouts_pb2
|
from hangups import ChatMessageSegment, hangouts_pb2
|
||||||
messages = []
|
messages = []
|
||||||
for segment in message:
|
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']:
|
if 'parse_str' in segment and segment['parse_str']:
|
||||||
messages.extend(ChatMessageSegment.from_str(segment['text']))
|
messages.extend(ChatMessageSegment.from_str(segment['text']))
|
||||||
else:
|
else:
|
||||||
if 'parse_str' in segment:
|
if 'parse_str' in segment:
|
||||||
del segment['parse_str']
|
del segment['parse_str']
|
||||||
messages.append(ChatMessageSegment(**segment))
|
messages.append(ChatMessageSegment(**segment))
|
||||||
messages.append(ChatMessageSegment('',
|
|
||||||
segment_type=hangouts_pb2.
|
|
||||||
SEGMENT_TYPE_LINE_BREAK))
|
|
||||||
|
|
||||||
if not messages:
|
if not messages:
|
||||||
return False
|
return False
|
||||||
|
@ -247,3 +266,7 @@ class HangoutsBot:
|
||||||
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."""
|
||||||
await self._async_list_conversations()
|
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)
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue