Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Dialogflow webhook."""
2 
3 import logging
4 
5 from aiohttp import web
6 import voluptuous as vol
7 
8 from homeassistant.components import webhook
9 from homeassistant.config_entries import ConfigEntry
10 from homeassistant.const import CONF_WEBHOOK_ID
11 from homeassistant.core import HomeAssistant
12 from homeassistant.exceptions import HomeAssistantError
13 from homeassistant.helpers import config_entry_flow, intent, template
14 
15 from .const import DOMAIN
16 
17 _LOGGER = logging.getLogger(__name__)
18 
19 SOURCE = "Home Assistant Dialogflow"
20 
21 CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
22 
23 V1 = 1
24 V2 = 2
25 
26 
28  """Raised when a DialogFlow error happens."""
29 
30 
31 async def handle_webhook(
32  hass: HomeAssistant, webhook_id: str, request: web.Request
33 ) -> web.Response | None:
34  """Handle incoming webhook with Dialogflow requests."""
35  message = await request.json()
36 
37  _LOGGER.debug("Received Dialogflow request: %s", message)
38 
39  try:
40  response = await async_handle_message(hass, message)
41  return None if response is None else web.json_response(response)
42 
43  except DialogFlowError as err:
44  _LOGGER.warning(str(err))
45  return web.json_response(dialogflow_error_response(message, str(err)))
46 
47  except intent.UnknownIntent as err:
48  _LOGGER.warning(str(err))
49  return web.json_response(
51  message, "This intent is not yet configured within Home Assistant."
52  )
53  )
54 
55  except intent.InvalidSlotInfo as err:
56  _LOGGER.warning(str(err))
57  return web.json_response(
59  message, "Invalid slot information received for this intent."
60  )
61  )
62 
63  except intent.IntentError as err:
64  _LOGGER.warning(str(err))
65  return web.json_response(
66  dialogflow_error_response(message, "Error handling intent.")
67  )
68 
69 
70 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
71  """Configure based on config entry."""
72  webhook.async_register(
73  hass, DOMAIN, "DialogFlow", entry.data[CONF_WEBHOOK_ID], handle_webhook
74  )
75  return True
76 
77 
78 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
79  """Unload a config entry."""
80  webhook.async_unregister(hass, entry.data[CONF_WEBHOOK_ID])
81  return True
82 
83 
84 async_remove_entry = config_entry_flow.webhook_async_remove_entry
85 
86 
87 def dialogflow_error_response(message, error):
88  """Return a response saying the error message."""
89  api_version = get_api_version(message)
90  if api_version is V1:
91  parameters = message["result"]["parameters"]
92  elif api_version is V2:
93  parameters = message["queryResult"]["parameters"]
94  dialogflow_response = DialogflowResponse(parameters, api_version)
95  dialogflow_response.add_speech(error)
96  return dialogflow_response.as_dict()
97 
98 
99 def get_api_version(message):
100  """Get API version of Dialogflow message."""
101  if message.get("id") is not None:
102  return V1
103  if message.get("responseId") is not None:
104  return V2
105 
106  raise ValueError(f"Unable to extract API version from message: {message}")
107 
108 
109 async def async_handle_message(hass, message):
110  """Handle a DialogFlow message."""
111  _api_version = get_api_version(message)
112  if _api_version is V1:
113  _LOGGER.warning(
114  "Dialogflow V1 API will be removed on October 23, 2019. Please change your"
115  " DialogFlow settings to use the V2 api"
116  )
117  req = message.get("result")
118  if req.get("actionIncomplete", True):
119  return None
120 
121  elif _api_version is V2:
122  req = message.get("queryResult")
123  if req.get("allRequiredParamsPresent", False) is False:
124  return None
125 
126  action = req.get("action", "")
127  parameters = req.get("parameters").copy()
128  parameters["dialogflow_query"] = message
129  dialogflow_response = DialogflowResponse(parameters, _api_version)
130 
131  if action == "":
132  raise DialogFlowError(
133  "You have not defined an action in your Dialogflow intent."
134  )
135 
136  intent_response = await intent.async_handle(
137  hass,
138  DOMAIN,
139  action,
140  {key: {"value": value} for key, value in parameters.items()},
141  )
142 
143  if "plain" in intent_response.speech:
144  dialogflow_response.add_speech(intent_response.speech["plain"]["speech"])
145 
146  return dialogflow_response.as_dict()
147 
148 
150  """Help generating the response for Dialogflow."""
151 
152  def __init__(self, parameters, api_version):
153  """Initialize the Dialogflow response."""
154  self.speechspeech = None
155  self.parametersparameters = {}
156  self.api_versionapi_version = api_version
157  # Parameter names replace '.' and '-' for '_'
158  for key, value in parameters.items():
159  underscored_key = key.replace(".", "_").replace("-", "_")
160  self.parametersparameters[underscored_key] = value
161 
162  def add_speech(self, text):
163  """Add speech to the response."""
164  assert self.speechspeech is None
165 
166  if isinstance(text, template.Template):
167  text = text.async_render(self.parametersparameters, parse_result=False)
168 
169  self.speechspeech = text
170 
171  def as_dict(self):
172  """Return response in a Dialogflow valid dictionary."""
173  if self.api_versionapi_version is V1:
174  return {"speech": self.speechspeech, "displayText": self.speechspeech, "source": SOURCE}
175 
176  if self.api_versionapi_version is V2:
177  return {"fulfillmentText": self.speechspeech, "source": SOURCE}
178 
179  raise ValueError(f"Invalid API version: {self.api_version}")
def __init__(self, parameters, api_version)
Definition: __init__.py:152
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:78
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:70
def dialogflow_error_response(message, error)
Definition: __init__.py:87
def async_handle_message(hass, message)
Definition: __init__.py:109
web.Response|None handle_webhook(HomeAssistant hass, str webhook_id, web.Request request)
Definition: __init__.py:33