2019-02-13 20:21:14 +00:00
|
|
|
"""Support for Dialogflow webhook."""
|
2017-01-31 15:54:54 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
import voluptuous as vol
|
2018-10-28 18:25:43 +00:00
|
|
|
from aiohttp import web
|
2017-01-31 15:54:54 +00:00
|
|
|
|
2018-10-28 18:25:43 +00:00
|
|
|
from homeassistant.const import CONF_WEBHOOK_ID
|
2018-01-07 00:39:32 +00:00
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2018-10-28 18:25:43 +00:00
|
|
|
from homeassistant.helpers import intent, template, config_entry_flow
|
2019-02-13 20:21:14 +00:00
|
|
|
|
2019-05-13 08:16:55 +00:00
|
|
|
from .const import DOMAIN
|
|
|
|
|
2017-01-31 15:54:54 +00:00
|
|
|
|
2019-05-13 08:16:55 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2017-10-21 20:12:23 +00:00
|
|
|
|
2017-11-03 14:43:30 +00:00
|
|
|
SOURCE = "Home Assistant Dialogflow"
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
|
2017-01-31 15:54:54 +00:00
|
|
|
|
2019-08-18 04:19:13 +00:00
|
|
|
V1 = 1
|
|
|
|
V2 = 2
|
|
|
|
|
2017-01-31 15:54:54 +00:00
|
|
|
|
2018-01-07 00:39:32 +00:00
|
|
|
class DialogFlowError(HomeAssistantError):
|
|
|
|
"""Raised when a DialogFlow error happens."""
|
|
|
|
|
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def async_setup(hass, config):
|
2019-02-13 20:21:14 +00:00
|
|
|
"""Set up the Dialogflow component."""
|
2017-01-31 15:54:54 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-10-28 18:25:43 +00:00
|
|
|
async def handle_webhook(hass, webhook_id, request):
|
|
|
|
"""Handle incoming webhook with Dialogflow requests."""
|
|
|
|
message = await request.json()
|
|
|
|
|
|
|
|
_LOGGER.debug("Received Dialogflow request: %s", message)
|
|
|
|
|
|
|
|
try:
|
|
|
|
response = await async_handle_message(hass, message)
|
2019-07-31 19:25:30 +00:00
|
|
|
return b"" if response is None else web.json_response(response)
|
2018-10-28 18:25:43 +00:00
|
|
|
|
|
|
|
except DialogFlowError as err:
|
|
|
|
_LOGGER.warning(str(err))
|
2019-02-13 20:21:14 +00:00
|
|
|
return web.json_response(dialogflow_error_response(message, str(err)))
|
2018-10-28 18:25:43 +00:00
|
|
|
|
|
|
|
except intent.UnknownIntent as err:
|
|
|
|
_LOGGER.warning(str(err))
|
|
|
|
return web.json_response(
|
|
|
|
dialogflow_error_response(
|
2019-07-31 19:25:30 +00:00
|
|
|
message, "This intent is not yet configured within Home Assistant."
|
2018-10-28 18:25:43 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
except intent.InvalidSlotInfo as err:
|
|
|
|
_LOGGER.warning(str(err))
|
|
|
|
return web.json_response(
|
|
|
|
dialogflow_error_response(
|
2019-07-31 19:25:30 +00:00
|
|
|
message, "Invalid slot information received for this intent."
|
2018-10-28 18:25:43 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
except intent.IntentError as err:
|
|
|
|
_LOGGER.warning(str(err))
|
|
|
|
return web.json_response(
|
2019-07-31 19:25:30 +00:00
|
|
|
dialogflow_error_response(message, "Error handling intent.")
|
|
|
|
)
|
2018-10-28 18:25:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_entry(hass, entry):
|
|
|
|
"""Configure based on config entry."""
|
|
|
|
hass.components.webhook.async_register(
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN, "DialogFlow", entry.data[CONF_WEBHOOK_ID], handle_webhook
|
|
|
|
)
|
2018-10-28 18:25:43 +00:00
|
|
|
return True
|
2017-01-31 15:54:54 +00:00
|
|
|
|
|
|
|
|
2018-10-28 18:25:43 +00:00
|
|
|
async def async_unload_entry(hass, entry):
|
|
|
|
"""Unload a config entry."""
|
|
|
|
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
|
|
|
|
return True
|
2017-01-31 15:54:54 +00:00
|
|
|
|
2019-04-01 12:07:12 +00:00
|
|
|
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
async_remove_entry = config_entry_flow.webhook_async_remove_entry
|
|
|
|
|
|
|
|
|
2018-10-28 18:25:43 +00:00
|
|
|
def dialogflow_error_response(message, error):
|
2018-01-07 00:39:32 +00:00
|
|
|
"""Return a response saying the error message."""
|
2019-08-18 04:19:13 +00:00
|
|
|
api_version = get_api_version(message)
|
|
|
|
if api_version is V1:
|
|
|
|
parameters = message["result"]["parameters"]
|
|
|
|
elif api_version is V2:
|
|
|
|
parameters = message["queryResult"]["parameters"]
|
|
|
|
dialogflow_response = DialogflowResponse(parameters, api_version)
|
2018-01-07 00:39:32 +00:00
|
|
|
dialogflow_response.add_speech(error)
|
|
|
|
return dialogflow_response.as_dict()
|
2017-07-22 04:38:53 +00:00
|
|
|
|
|
|
|
|
2019-08-18 04:19:13 +00:00
|
|
|
def get_api_version(message):
|
|
|
|
"""Get API version of Dialogflow message."""
|
|
|
|
if message.get("id") is not None:
|
|
|
|
return V1
|
|
|
|
if message.get("responseId") is not None:
|
|
|
|
return V2
|
|
|
|
|
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def async_handle_message(hass, message):
|
2018-01-07 00:39:32 +00:00
|
|
|
"""Handle a DialogFlow message."""
|
2019-08-18 04:19:13 +00:00
|
|
|
_api_version = get_api_version(message)
|
|
|
|
if _api_version is V1:
|
|
|
|
_LOGGER.warning(
|
|
|
|
"Dialogflow V1 API will be removed on October 23, 2019. Please change your DialogFlow settings to use the V2 api"
|
|
|
|
)
|
|
|
|
req = message.get("result")
|
|
|
|
action_incomplete = req.get("actionIncomplete", True)
|
|
|
|
if action_incomplete:
|
|
|
|
return
|
2018-01-07 00:39:32 +00:00
|
|
|
|
2019-08-18 04:19:13 +00:00
|
|
|
elif _api_version is V2:
|
|
|
|
req = message.get("queryResult")
|
|
|
|
if req.get("allRequiredParamsPresent", False) is False:
|
|
|
|
return
|
2018-01-07 00:39:32 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
action = req.get("action", "")
|
|
|
|
parameters = req.get("parameters").copy()
|
2018-07-07 09:10:43 +00:00
|
|
|
parameters["dialogflow_query"] = message
|
2019-08-18 04:19:13 +00:00
|
|
|
dialogflow_response = DialogflowResponse(parameters, _api_version)
|
2018-01-07 00:39:32 +00:00
|
|
|
|
|
|
|
if action == "":
|
|
|
|
raise DialogFlowError(
|
2019-07-31 19:25:30 +00:00
|
|
|
"You have not defined an action in your Dialogflow intent."
|
|
|
|
)
|
2018-01-07 00:39:32 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
intent_response = await intent.async_handle(
|
2019-07-31 19:25:30 +00:00
|
|
|
hass,
|
|
|
|
DOMAIN,
|
|
|
|
action,
|
|
|
|
{key: {"value": value} for key, value in parameters.items()},
|
|
|
|
)
|
2018-01-07 00:39:32 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if "plain" in intent_response.speech:
|
|
|
|
dialogflow_response.add_speech(intent_response.speech["plain"]["speech"])
|
2018-01-07 00:39:32 +00:00
|
|
|
|
|
|
|
return dialogflow_response.as_dict()
|
2017-01-31 15:54:54 +00:00
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class DialogflowResponse:
|
2017-10-21 20:12:23 +00:00
|
|
|
"""Help generating the response for Dialogflow."""
|
2017-01-31 15:54:54 +00:00
|
|
|
|
2019-08-18 04:19:13 +00:00
|
|
|
def __init__(self, parameters, api_version):
|
2017-10-21 20:12:23 +00:00
|
|
|
"""Initialize the Dialogflow response."""
|
2017-01-31 15:54:54 +00:00
|
|
|
self.speech = None
|
|
|
|
self.parameters = {}
|
2019-08-18 04:19:13 +00:00
|
|
|
self.api_version = api_version
|
2017-01-31 15:54:54 +00:00
|
|
|
# Parameter names replace '.' and '-' for '_'
|
|
|
|
for key, value in parameters.items():
|
2019-07-31 19:25:30 +00:00
|
|
|
underscored_key = key.replace(".", "_").replace("-", "_")
|
2017-01-31 15:54:54 +00:00
|
|
|
self.parameters[underscored_key] = value
|
|
|
|
|
|
|
|
def add_speech(self, text):
|
|
|
|
"""Add speech to the response."""
|
|
|
|
assert self.speech is None
|
|
|
|
|
|
|
|
if isinstance(text, template.Template):
|
|
|
|
text = text.async_render(self.parameters)
|
|
|
|
|
|
|
|
self.speech = text
|
|
|
|
|
|
|
|
def as_dict(self):
|
2017-10-21 20:12:23 +00:00
|
|
|
"""Return response in a Dialogflow valid dictionary."""
|
2019-08-18 04:19:13 +00:00
|
|
|
if self.api_version is V1:
|
|
|
|
return {"speech": self.speech, "displayText": self.speech, "source": SOURCE}
|
|
|
|
|
|
|
|
if self.api_version is V2:
|
|
|
|
return {"fulfillmentText": self.speech, "source": SOURCE}
|