"""Support for Dialogflow webhook.""" import logging import voluptuous as vol from aiohttp import web from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import intent, template, config_entry_flow from .const import DOMAIN _LOGGER = logging.getLogger(__name__) SOURCE = "Home Assistant Dialogflow" CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA) class DialogFlowError(HomeAssistantError): """Raised when a DialogFlow error happens.""" async def async_setup(hass, config): """Set up the Dialogflow component.""" return True 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) return b"" if response is None else web.json_response(response) except DialogFlowError as err: _LOGGER.warning(str(err)) return web.json_response(dialogflow_error_response(message, str(err))) except intent.UnknownIntent as err: _LOGGER.warning(str(err)) return web.json_response( dialogflow_error_response( message, "This intent is not yet configured within Home Assistant." ) ) except intent.InvalidSlotInfo as err: _LOGGER.warning(str(err)) return web.json_response( dialogflow_error_response( message, "Invalid slot information received for this intent." ) ) except intent.IntentError as err: _LOGGER.warning(str(err)) return web.json_response( dialogflow_error_response(message, "Error handling intent.") ) async def async_setup_entry(hass, entry): """Configure based on config entry.""" hass.components.webhook.async_register( DOMAIN, "DialogFlow", entry.data[CONF_WEBHOOK_ID], handle_webhook ) return True async def async_unload_entry(hass, entry): """Unload a config entry.""" hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) return True # pylint: disable=invalid-name async_remove_entry = config_entry_flow.webhook_async_remove_entry def dialogflow_error_response(message, error): """Return a response saying the error message.""" dialogflow_response = DialogflowResponse(message["result"]["parameters"]) dialogflow_response.add_speech(error) return dialogflow_response.as_dict() async def async_handle_message(hass, message): """Handle a DialogFlow message.""" req = message.get("result") action_incomplete = req["actionIncomplete"] if action_incomplete: return None action = req.get("action", "") parameters = req.get("parameters").copy() parameters["dialogflow_query"] = message dialogflow_response = DialogflowResponse(parameters) if action == "": raise DialogFlowError( "You have not defined an action in your Dialogflow intent." ) intent_response = await intent.async_handle( hass, DOMAIN, action, {key: {"value": value} for key, value in parameters.items()}, ) if "plain" in intent_response.speech: dialogflow_response.add_speech(intent_response.speech["plain"]["speech"]) return dialogflow_response.as_dict() class DialogflowResponse: """Help generating the response for Dialogflow.""" def __init__(self, parameters): """Initialize the Dialogflow response.""" self.speech = None self.parameters = {} # Parameter names replace '.' and '-' for '_' for key, value in parameters.items(): underscored_key = key.replace(".", "_").replace("-", "_") 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): """Return response in a Dialogflow valid dictionary.""" return {"speech": self.speech, "displayText": self.speech, "source": SOURCE}