2017-08-29 20:40:08 +00:00
|
|
|
"""The HTTP api to control the cloud integration."""
|
|
|
|
import asyncio
|
2017-09-12 16:47:04 +00:00
|
|
|
from functools import wraps
|
2017-08-29 20:40:08 +00:00
|
|
|
import logging
|
|
|
|
|
2018-11-26 13:10:18 +00:00
|
|
|
import aiohttp
|
2017-08-29 20:40:08 +00:00
|
|
|
import async_timeout
|
2018-01-21 06:35:38 +00:00
|
|
|
import voluptuous as vol
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2018-09-20 12:53:13 +00:00
|
|
|
from homeassistant.core import callback
|
2018-02-14 20:06:03 +00:00
|
|
|
from homeassistant.components.http import HomeAssistantView
|
|
|
|
from homeassistant.components.http.data_validator import (
|
|
|
|
RequestDataValidator)
|
2018-09-20 12:53:13 +00:00
|
|
|
from homeassistant.components import websocket_api
|
2018-11-05 12:21:03 +00:00
|
|
|
from homeassistant.components.alexa import smart_home as alexa_sh
|
|
|
|
from homeassistant.components.google_assistant import smart_home as google_sh
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
from . import auth_api
|
2018-11-20 22:23:07 +00:00
|
|
|
from .const import (
|
|
|
|
DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
|
|
|
|
PREF_GOOGLE_ALLOW_UNLOCK)
|
2018-10-21 10:16:24 +00:00
|
|
|
from .iot import STATE_DISCONNECTED, STATE_CONNECTED
|
2017-08-29 20:40:08 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2018-09-20 12:53:13 +00:00
|
|
|
WS_TYPE_STATUS = 'cloud/status'
|
|
|
|
SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_STATUS,
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2018-09-20 21:46:51 +00:00
|
|
|
WS_TYPE_UPDATE_PREFS = 'cloud/update_prefs'
|
|
|
|
SCHEMA_WS_UPDATE_PREFS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_UPDATE_PREFS,
|
2018-11-20 22:23:07 +00:00
|
|
|
vol.Optional(PREF_ENABLE_GOOGLE): bool,
|
|
|
|
vol.Optional(PREF_ENABLE_ALEXA): bool,
|
|
|
|
vol.Optional(PREF_GOOGLE_ALLOW_UNLOCK): bool,
|
2018-09-20 21:46:51 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
|
2018-09-20 12:53:13 +00:00
|
|
|
WS_TYPE_SUBSCRIPTION = 'cloud/subscription'
|
|
|
|
SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_SUBSCRIPTION,
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2018-11-26 13:10:18 +00:00
|
|
|
WS_TYPE_HOOK_CREATE = 'cloud/cloudhook/create'
|
|
|
|
SCHEMA_WS_HOOK_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_HOOK_CREATE,
|
|
|
|
vol.Required('webhook_id'): str
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
WS_TYPE_HOOK_DELETE = 'cloud/cloudhook/delete'
|
|
|
|
SCHEMA_WS_HOOK_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_HOOK_DELETE,
|
|
|
|
vol.Required('webhook_id'): str
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2018-03-23 19:13:52 +00:00
|
|
|
async def async_setup(hass):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Initialize the HTTP API."""
|
2018-09-20 12:53:13 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_STATUS, websocket_cloud_status,
|
|
|
|
SCHEMA_WS_STATUS
|
|
|
|
)
|
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_SUBSCRIPTION, websocket_subscription,
|
|
|
|
SCHEMA_WS_SUBSCRIPTION
|
|
|
|
)
|
2018-09-20 21:46:51 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_UPDATE_PREFS, websocket_update_prefs,
|
|
|
|
SCHEMA_WS_UPDATE_PREFS
|
|
|
|
)
|
2018-11-26 13:10:18 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_HOOK_CREATE, websocket_hook_create,
|
|
|
|
SCHEMA_WS_HOOK_CREATE
|
|
|
|
)
|
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_HOOK_DELETE, websocket_hook_delete,
|
|
|
|
SCHEMA_WS_HOOK_DELETE
|
|
|
|
)
|
2018-03-23 19:13:52 +00:00
|
|
|
hass.http.register_view(GoogleActionsSyncView)
|
2017-08-29 20:40:08 +00:00
|
|
|
hass.http.register_view(CloudLoginView)
|
|
|
|
hass.http.register_view(CloudLogoutView)
|
2017-09-12 16:47:04 +00:00
|
|
|
hass.http.register_view(CloudRegisterView)
|
2017-12-29 13:46:10 +00:00
|
|
|
hass.http.register_view(CloudResendConfirmView)
|
2017-09-12 16:47:04 +00:00
|
|
|
hass.http.register_view(CloudForgotPasswordView)
|
2017-08-29 20:40:08 +00:00
|
|
|
|
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
_CLOUD_ERRORS = {
|
|
|
|
auth_api.UserNotFound: (400, "User does not exist."),
|
|
|
|
auth_api.UserNotConfirmed: (400, 'Email not confirmed.'),
|
|
|
|
auth_api.Unauthenticated: (401, 'Authentication failed.'),
|
|
|
|
auth_api.PasswordChangeRequired: (400, 'Password change required.'),
|
|
|
|
asyncio.TimeoutError: (502, 'Unable to reach the Home Assistant cloud.')
|
|
|
|
}
|
2017-08-29 20:40:08 +00:00
|
|
|
|
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
def _handle_cloud_errors(handler):
|
2018-11-26 13:10:18 +00:00
|
|
|
"""Webview decorator to handle auth errors."""
|
2017-09-12 16:47:04 +00:00
|
|
|
@wraps(handler)
|
2018-03-23 19:13:52 +00:00
|
|
|
async def error_handler(view, request, *args, **kwargs):
|
2017-09-12 16:47:04 +00:00
|
|
|
"""Handle exceptions that raise from the wrapped request handler."""
|
2017-08-29 20:40:08 +00:00
|
|
|
try:
|
2018-03-23 19:13:52 +00:00
|
|
|
result = await handler(view, request, *args, **kwargs)
|
2017-09-12 16:47:04 +00:00
|
|
|
return result
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2019-02-04 09:14:30 +00:00
|
|
|
except Exception as err: # pylint: disable=broad-except
|
2017-09-12 16:47:04 +00:00
|
|
|
err_info = _CLOUD_ERRORS.get(err.__class__)
|
|
|
|
if err_info is None:
|
2019-02-04 09:14:30 +00:00
|
|
|
_LOGGER.exception(
|
|
|
|
"Unexpected error processing request for %s", request.path)
|
2017-09-12 16:47:04 +00:00
|
|
|
err_info = (502, 'Unexpected error: {}'.format(err))
|
|
|
|
status, msg = err_info
|
2019-02-04 09:14:30 +00:00
|
|
|
return view.json_message(
|
|
|
|
msg, status_code=status,
|
|
|
|
message_code=err.__class__.__name__.lower())
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
return error_handler
|
2017-08-29 20:40:08 +00:00
|
|
|
|
|
|
|
|
2018-03-23 19:13:52 +00:00
|
|
|
class GoogleActionsSyncView(HomeAssistantView):
|
|
|
|
"""Trigger a Google Actions Smart Home Sync."""
|
|
|
|
|
|
|
|
url = '/api/cloud/google_actions/sync'
|
|
|
|
name = 'api:cloud:google_actions/sync'
|
|
|
|
|
|
|
|
@_handle_cloud_errors
|
|
|
|
async def post(self, request):
|
|
|
|
"""Trigger a Google Actions sync."""
|
|
|
|
hass = request.app['hass']
|
|
|
|
cloud = hass.data[DOMAIN]
|
|
|
|
websession = hass.helpers.aiohttp_client.async_get_clientsession()
|
|
|
|
|
|
|
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
|
|
|
await hass.async_add_job(auth_api.check_token, cloud)
|
|
|
|
|
|
|
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
|
|
|
req = await websession.post(
|
|
|
|
cloud.google_actions_sync_url, headers={
|
|
|
|
'authorization': cloud.id_token
|
|
|
|
})
|
|
|
|
|
|
|
|
return self.json({}, status_code=req.status)
|
|
|
|
|
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
class CloudLoginView(HomeAssistantView):
|
|
|
|
"""Login to Home Assistant cloud."""
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
url = '/api/cloud/login'
|
|
|
|
name = 'api:cloud:login'
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
@_handle_cloud_errors
|
|
|
|
@RequestDataValidator(vol.Schema({
|
|
|
|
vol.Required('email'): str,
|
|
|
|
vol.Required('password'): str,
|
|
|
|
}))
|
2018-03-23 19:13:52 +00:00
|
|
|
async def post(self, request, data):
|
2017-09-12 16:47:04 +00:00
|
|
|
"""Handle login request."""
|
|
|
|
hass = request.app['hass']
|
2017-10-15 02:43:14 +00:00
|
|
|
cloud = hass.data[DOMAIN]
|
2017-09-12 16:47:04 +00:00
|
|
|
|
|
|
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
2018-03-23 19:13:52 +00:00
|
|
|
await hass.async_add_job(auth_api.login, cloud, data['email'],
|
|
|
|
data['password'])
|
2017-09-12 16:47:04 +00:00
|
|
|
|
2017-11-15 07:16:19 +00:00
|
|
|
hass.async_add_job(cloud.iot.connect)
|
2018-09-20 12:53:13 +00:00
|
|
|
return self.json({'success': True})
|
2017-08-29 20:40:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class CloudLogoutView(HomeAssistantView):
|
|
|
|
"""Log out of the Home Assistant cloud."""
|
|
|
|
|
|
|
|
url = '/api/cloud/logout'
|
|
|
|
name = 'api:cloud:logout'
|
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
@_handle_cloud_errors
|
2018-03-23 19:13:52 +00:00
|
|
|
async def post(self, request):
|
2017-09-12 16:47:04 +00:00
|
|
|
"""Handle logout request."""
|
2017-08-29 20:40:08 +00:00
|
|
|
hass = request.app['hass']
|
2017-10-15 02:43:14 +00:00
|
|
|
cloud = hass.data[DOMAIN]
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
2018-03-23 19:13:52 +00:00
|
|
|
await cloud.logout()
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
return self.json_message('ok')
|
2017-08-29 20:40:08 +00:00
|
|
|
|
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
class CloudRegisterView(HomeAssistantView):
|
|
|
|
"""Register on the Home Assistant cloud."""
|
|
|
|
|
|
|
|
url = '/api/cloud/register'
|
|
|
|
name = 'api:cloud:register'
|
|
|
|
|
|
|
|
@_handle_cloud_errors
|
|
|
|
@RequestDataValidator(vol.Schema({
|
|
|
|
vol.Required('email'): str,
|
|
|
|
vol.Required('password'): vol.All(str, vol.Length(min=6)),
|
|
|
|
}))
|
2018-03-23 19:13:52 +00:00
|
|
|
async def post(self, request, data):
|
2017-09-12 16:47:04 +00:00
|
|
|
"""Handle registration request."""
|
|
|
|
hass = request.app['hass']
|
2017-10-15 02:43:14 +00:00
|
|
|
cloud = hass.data[DOMAIN]
|
2017-09-12 16:47:04 +00:00
|
|
|
|
|
|
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
2018-03-23 19:13:52 +00:00
|
|
|
await hass.async_add_job(
|
2017-10-15 02:43:14 +00:00
|
|
|
auth_api.register, cloud, data['email'], data['password'])
|
2017-09-12 16:47:04 +00:00
|
|
|
|
|
|
|
return self.json_message('ok')
|
|
|
|
|
|
|
|
|
2017-12-29 13:46:10 +00:00
|
|
|
class CloudResendConfirmView(HomeAssistantView):
|
|
|
|
"""Resend email confirmation code."""
|
|
|
|
|
|
|
|
url = '/api/cloud/resend_confirm'
|
|
|
|
name = 'api:cloud:resend_confirm'
|
|
|
|
|
|
|
|
@_handle_cloud_errors
|
|
|
|
@RequestDataValidator(vol.Schema({
|
|
|
|
vol.Required('email'): str,
|
|
|
|
}))
|
2018-03-23 19:13:52 +00:00
|
|
|
async def post(self, request, data):
|
2017-12-29 13:46:10 +00:00
|
|
|
"""Handle resending confirm email code request."""
|
|
|
|
hass = request.app['hass']
|
|
|
|
cloud = hass.data[DOMAIN]
|
|
|
|
|
|
|
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
2018-03-23 19:13:52 +00:00
|
|
|
await hass.async_add_job(
|
2017-12-29 13:46:10 +00:00
|
|
|
auth_api.resend_email_confirm, cloud, data['email'])
|
|
|
|
|
|
|
|
return self.json_message('ok')
|
|
|
|
|
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
class CloudForgotPasswordView(HomeAssistantView):
|
|
|
|
"""View to start Forgot Password flow.."""
|
|
|
|
|
|
|
|
url = '/api/cloud/forgot_password'
|
|
|
|
name = 'api:cloud:forgot_password'
|
|
|
|
|
|
|
|
@_handle_cloud_errors
|
|
|
|
@RequestDataValidator(vol.Schema({
|
|
|
|
vol.Required('email'): str,
|
|
|
|
}))
|
2018-03-23 19:13:52 +00:00
|
|
|
async def post(self, request, data):
|
2017-09-12 16:47:04 +00:00
|
|
|
"""Handle forgot password request."""
|
|
|
|
hass = request.app['hass']
|
2017-10-15 02:43:14 +00:00
|
|
|
cloud = hass.data[DOMAIN]
|
2017-09-12 16:47:04 +00:00
|
|
|
|
|
|
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
2018-03-23 19:13:52 +00:00
|
|
|
await hass.async_add_job(
|
2017-10-15 02:43:14 +00:00
|
|
|
auth_api.forgot_password, cloud, data['email'])
|
2017-09-12 16:47:04 +00:00
|
|
|
|
|
|
|
return self.json_message('ok')
|
|
|
|
|
|
|
|
|
2018-09-20 12:53:13 +00:00
|
|
|
@callback
|
|
|
|
def websocket_cloud_status(hass, connection, msg):
|
|
|
|
"""Handle request for account info.
|
|
|
|
|
|
|
|
Async friendly.
|
|
|
|
"""
|
|
|
|
cloud = hass.data[DOMAIN]
|
2018-10-01 14:09:31 +00:00
|
|
|
connection.send_message(
|
2018-09-20 12:53:13 +00:00
|
|
|
websocket_api.result_message(msg['id'], _account_data(cloud)))
|
|
|
|
|
|
|
|
|
2018-11-26 13:10:18 +00:00
|
|
|
def _require_cloud_login(handler):
|
|
|
|
"""Websocket decorator that requires cloud to be logged in."""
|
|
|
|
@wraps(handler)
|
|
|
|
def with_cloud_auth(hass, connection, msg):
|
|
|
|
"""Require to be logged into the cloud."""
|
|
|
|
cloud = hass.data[DOMAIN]
|
|
|
|
if not cloud.is_logged_in:
|
|
|
|
connection.send_message(websocket_api.error_message(
|
|
|
|
msg['id'], 'not_logged_in',
|
|
|
|
'You need to be logged in to the cloud.'))
|
|
|
|
return
|
|
|
|
|
|
|
|
handler(hass, connection, msg)
|
|
|
|
|
|
|
|
return with_cloud_auth
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_aiohttp_errors(handler):
|
|
|
|
"""Websocket decorator that handlers aiohttp errors.
|
|
|
|
|
|
|
|
Can only wrap async handlers.
|
|
|
|
"""
|
|
|
|
@wraps(handler)
|
|
|
|
async def with_error_handling(hass, connection, msg):
|
|
|
|
"""Handle aiohttp errors."""
|
|
|
|
try:
|
|
|
|
await handler(hass, connection, msg)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
connection.send_message(websocket_api.error_message(
|
|
|
|
msg['id'], 'timeout', 'Command timed out.'))
|
|
|
|
except aiohttp.ClientError:
|
|
|
|
connection.send_message(websocket_api.error_message(
|
|
|
|
msg['id'], 'unknown', 'Error making request.'))
|
|
|
|
|
|
|
|
return with_error_handling
|
|
|
|
|
|
|
|
|
|
|
|
@_require_cloud_login
|
2018-09-20 12:53:13 +00:00
|
|
|
@websocket_api.async_response
|
|
|
|
async def websocket_subscription(hass, connection, msg):
|
|
|
|
"""Handle request for account info."""
|
|
|
|
cloud = hass.data[DOMAIN]
|
|
|
|
|
|
|
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
|
|
|
response = await cloud.fetch_subscription_info()
|
|
|
|
|
2018-10-21 10:16:24 +00:00
|
|
|
if response.status != 200:
|
2018-10-01 14:09:31 +00:00
|
|
|
connection.send_message(websocket_api.error_message(
|
2018-09-20 12:53:13 +00:00
|
|
|
msg['id'], 'request_failed', 'Failed to request subscription'))
|
2018-10-21 10:16:24 +00:00
|
|
|
|
|
|
|
data = await response.json()
|
|
|
|
|
|
|
|
# Check if a user is subscribed but local info is outdated
|
|
|
|
# In that case, let's refresh and reconnect
|
|
|
|
if data.get('provider') and cloud.iot.state != STATE_CONNECTED:
|
|
|
|
_LOGGER.debug(
|
|
|
|
"Found disconnected account with valid subscriotion, connecting")
|
|
|
|
await hass.async_add_executor_job(
|
|
|
|
auth_api.renew_access_token, cloud)
|
|
|
|
|
|
|
|
# Cancel reconnect in progress
|
|
|
|
if cloud.iot.state != STATE_DISCONNECTED:
|
|
|
|
await cloud.iot.disconnect()
|
|
|
|
|
|
|
|
hass.async_create_task(cloud.iot.connect())
|
|
|
|
|
|
|
|
connection.send_message(websocket_api.result_message(msg['id'], data))
|
2018-09-20 12:53:13 +00:00
|
|
|
|
|
|
|
|
2018-11-26 13:10:18 +00:00
|
|
|
@_require_cloud_login
|
2018-09-20 21:46:51 +00:00
|
|
|
@websocket_api.async_response
|
|
|
|
async def websocket_update_prefs(hass, connection, msg):
|
|
|
|
"""Handle request for account info."""
|
|
|
|
cloud = hass.data[DOMAIN]
|
|
|
|
|
|
|
|
changes = dict(msg)
|
|
|
|
changes.pop('id')
|
|
|
|
changes.pop('type')
|
2018-11-20 22:23:07 +00:00
|
|
|
await cloud.prefs.async_update(**changes)
|
2018-09-20 21:46:51 +00:00
|
|
|
|
2018-11-26 13:10:18 +00:00
|
|
|
connection.send_message(websocket_api.result_message(msg['id']))
|
|
|
|
|
|
|
|
|
|
|
|
@_require_cloud_login
|
|
|
|
@websocket_api.async_response
|
|
|
|
@_handle_aiohttp_errors
|
|
|
|
async def websocket_hook_create(hass, connection, msg):
|
|
|
|
"""Handle request for account info."""
|
|
|
|
cloud = hass.data[DOMAIN]
|
|
|
|
hook = await cloud.cloudhooks.async_create(msg['webhook_id'])
|
|
|
|
connection.send_message(websocket_api.result_message(msg['id'], hook))
|
|
|
|
|
|
|
|
|
|
|
|
@_require_cloud_login
|
|
|
|
@websocket_api.async_response
|
|
|
|
async def websocket_hook_delete(hass, connection, msg):
|
|
|
|
"""Handle request for account info."""
|
|
|
|
cloud = hass.data[DOMAIN]
|
|
|
|
await cloud.cloudhooks.async_delete(msg['webhook_id'])
|
|
|
|
connection.send_message(websocket_api.result_message(msg['id']))
|
2018-09-20 21:46:51 +00:00
|
|
|
|
|
|
|
|
2017-10-15 02:43:14 +00:00
|
|
|
def _account_data(cloud):
|
2017-09-12 16:47:04 +00:00
|
|
|
"""Generate the auth data JSON response."""
|
2018-09-20 12:53:13 +00:00
|
|
|
if not cloud.is_logged_in:
|
|
|
|
return {
|
|
|
|
'logged_in': False,
|
|
|
|
'cloud': STATE_DISCONNECTED,
|
|
|
|
}
|
|
|
|
|
2017-11-15 07:16:19 +00:00
|
|
|
claims = cloud.claims
|
|
|
|
|
2017-09-12 16:47:04 +00:00
|
|
|
return {
|
2018-09-20 12:53:13 +00:00
|
|
|
'logged_in': True,
|
2017-11-15 07:16:19 +00:00
|
|
|
'email': claims['email'],
|
|
|
|
'cloud': cloud.iot.state,
|
2018-11-20 22:23:07 +00:00
|
|
|
'prefs': cloud.prefs.as_dict(),
|
2018-11-05 12:21:03 +00:00
|
|
|
'google_entities': cloud.google_actions_user_conf['filter'].config,
|
|
|
|
'google_domains': list(google_sh.DOMAIN_TO_GOOGLE_TYPES),
|
|
|
|
'alexa_entities': cloud.alexa_config.should_expose.config,
|
|
|
|
'alexa_domains': list(alexa_sh.ENTITY_ADAPTERS),
|
2017-09-12 16:47:04 +00:00
|
|
|
}
|