2019-02-13 20:21:14 +00:00
|
|
|
"""Webhooks for Home Assistant."""
|
2018-09-30 12:45:48 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
from aiohttp.web import Response
|
2018-11-05 08:23:58 +00:00
|
|
|
import voluptuous as vol
|
2018-09-30 12:45:48 +00:00
|
|
|
|
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.loader import bind_hass
|
|
|
|
from homeassistant.auth.util import generate_secret
|
2018-11-05 08:23:58 +00:00
|
|
|
from homeassistant.components import websocket_api
|
2018-09-30 12:45:48 +00:00
|
|
|
from homeassistant.components.http.view import HomeAssistantView
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN = "webhook"
|
2018-09-30 12:45:48 +00:00
|
|
|
|
2019-01-21 19:50:41 +00:00
|
|
|
URL_WEBHOOK_PATH = "/api/webhook/{webhook_id}"
|
2019-02-13 20:21:14 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
WS_TYPE_LIST = "webhook/list"
|
2019-02-13 20:21:14 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
|
|
|
|
{vol.Required("type"): WS_TYPE_LIST}
|
|
|
|
)
|
2018-11-05 08:23:58 +00:00
|
|
|
|
|
|
|
|
2018-09-30 12:45:48 +00:00
|
|
|
@callback
|
|
|
|
@bind_hass
|
2018-11-05 08:23:58 +00:00
|
|
|
def async_register(hass, domain, name, webhook_id, handler):
|
2018-09-30 12:45:48 +00:00
|
|
|
"""Register a webhook."""
|
|
|
|
handlers = hass.data.setdefault(DOMAIN, {})
|
|
|
|
|
|
|
|
if webhook_id in handlers:
|
2019-07-31 19:25:30 +00:00
|
|
|
raise ValueError("Handler is already defined!")
|
2018-09-30 12:45:48 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
handlers[webhook_id] = {"domain": domain, "name": name, "handler": handler}
|
2018-09-30 12:45:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
@bind_hass
|
|
|
|
def async_unregister(hass, webhook_id):
|
|
|
|
"""Remove a webhook."""
|
|
|
|
handlers = hass.data.setdefault(DOMAIN, {})
|
|
|
|
handlers.pop(webhook_id, None)
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_generate_id():
|
|
|
|
"""Generate a webhook_id."""
|
|
|
|
return generate_secret(entropy=32)
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
@bind_hass
|
|
|
|
def async_generate_url(hass, webhook_id):
|
2019-01-21 19:50:41 +00:00
|
|
|
"""Generate the full URL for a webhook_id."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "{}{}".format(hass.config.api.base_url, async_generate_path(webhook_id))
|
2019-01-21 19:50:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_generate_path(webhook_id):
|
|
|
|
"""Generate the path component for a webhook_id."""
|
|
|
|
return URL_WEBHOOK_PATH.format(webhook_id=webhook_id)
|
2018-09-30 12:45:48 +00:00
|
|
|
|
|
|
|
|
2018-11-26 13:10:18 +00:00
|
|
|
@bind_hass
|
|
|
|
async def async_handle_webhook(hass, webhook_id, request):
|
|
|
|
"""Handle a webhook."""
|
|
|
|
handlers = hass.data.setdefault(DOMAIN, {})
|
|
|
|
webhook = handlers.get(webhook_id)
|
|
|
|
|
|
|
|
# Always respond successfully to not give away if a hook exists or not.
|
|
|
|
if webhook is None:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.warning("Received message for unregistered webhook %s", webhook_id)
|
2018-11-26 13:10:18 +00:00
|
|
|
return Response(status=200)
|
|
|
|
|
|
|
|
try:
|
2019-07-31 19:25:30 +00:00
|
|
|
response = await webhook["handler"](hass, webhook_id, request)
|
2018-11-26 13:10:18 +00:00
|
|
|
if response is None:
|
|
|
|
response = Response(status=200)
|
|
|
|
return response
|
|
|
|
except Exception: # pylint: disable=broad-except
|
|
|
|
_LOGGER.exception("Error processing webhook %s", webhook_id)
|
|
|
|
return Response(status=200)
|
|
|
|
|
|
|
|
|
2018-09-30 12:45:48 +00:00
|
|
|
async def async_setup(hass, config):
|
|
|
|
"""Initialize the webhook component."""
|
|
|
|
hass.http.register_view(WebhookView)
|
2018-11-05 08:23:58 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
2019-07-31 19:25:30 +00:00
|
|
|
WS_TYPE_LIST, websocket_list, SCHEMA_WS_LIST
|
2018-11-05 08:23:58 +00:00
|
|
|
)
|
2018-09-30 12:45:48 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class WebhookView(HomeAssistantView):
|
|
|
|
"""Handle incoming webhook requests."""
|
|
|
|
|
2019-01-21 19:50:41 +00:00
|
|
|
url = URL_WEBHOOK_PATH
|
2018-09-30 12:45:48 +00:00
|
|
|
name = "api:webhook"
|
|
|
|
requires_auth = False
|
|
|
|
|
|
|
|
async def post(self, request, webhook_id):
|
|
|
|
"""Handle webhook call."""
|
2019-07-31 19:25:30 +00:00
|
|
|
hass = request.app["hass"]
|
2018-11-26 13:10:18 +00:00
|
|
|
return await async_handle_webhook(hass, webhook_id, request)
|
2018-11-05 08:23:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def websocket_list(hass, connection, msg):
|
|
|
|
"""Return a list of webhooks."""
|
|
|
|
handlers = hass.data.setdefault(DOMAIN, {})
|
2019-07-31 19:25:30 +00:00
|
|
|
result = [
|
|
|
|
{"webhook_id": webhook_id, "domain": info["domain"], "name": info["name"]}
|
|
|
|
for webhook_id, info in handlers.items()
|
|
|
|
]
|
|
|
|
|
|
|
|
connection.send_message(websocket_api.result_message(msg["id"], result))
|