Skip to content

Commit

Permalink
Sync
Browse files Browse the repository at this point in the history
  • Loading branch information
tronikos committed May 12, 2024
1 parent 0908b6f commit 13da925
Show file tree
Hide file tree
Showing 9 changed files with 52 additions and 65 deletions.
13 changes: 8 additions & 5 deletions custom_components/google_assistant_sdk_custom/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
"""Support for Google Assistant SDK."""

from __future__ import annotations

import dataclasses

import aiohttp
from gassist_text import TextAssistant
from google.oauth2.credentials import Credentials
import voluptuous as vol

from homeassistant.components import conversation
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import CONF_NAME, Platform
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME, Platform
from homeassistant.core import (
HomeAssistant,
ServiceCall,
Expand All @@ -28,7 +30,6 @@
from .helpers import (
GoogleAssistantSDKAudioView,
InMemoryStorage,
async_create_credentials,
async_send_text_commands,
parse_response,
)
Expand Down Expand Up @@ -98,7 +99,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
for service_name in hass.services.async_services()[DOMAIN]:
for service_name in hass.services.async_services_for_domain(DOMAIN):
hass.services.async_remove(DOMAIN, service_name)

conversation.async_unset_agent(hass, entry)
Expand Down Expand Up @@ -165,11 +166,13 @@ async def async_process(
await session.async_ensure_token_valid()
self.assistant = None
if not self.assistant or user_input.language != self.language:
credentials = await async_create_credentials(self.hass, self.entry)
credentials = Credentials(session.token[CONF_ACCESS_TOKEN])
self.language = user_input.language
self.assistant = TextAssistant(credentials, self.language, display=True)

resp = self.assistant.assist(user_input.text)
resp = await self.hass.async_add_executor_job(
self.assistant.assist, user_input.text
)
text_response = parse_response(self.hass, user_input.text, resp)

intent_response = intent.IntentResponse(language=user_input.language)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""application_credentials platform for Google Assistant SDK."""

from homeassistant.components.application_credentials import AuthorizationServer
from homeassistant.core import HomeAssistant

Expand Down
23 changes: 12 additions & 11 deletions custom_components/google_assistant_sdk_custom/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Config flow for Google Assistant SDK integration."""

from __future__ import annotations

from collections.abc import Mapping
Expand All @@ -7,10 +8,8 @@

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult, OptionsFlow
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_entry_oauth2_flow

from .const import CONF_LANGUAGE_CODE, DEFAULT_NAME, DOMAIN, SUPPORTED_LANGUAGE_CODES
Expand Down Expand Up @@ -43,7 +42,9 @@ def extra_authorize_data(self) -> dict[str, Any]:
"prompt": "consent",
}

async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an API authentication error."""
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
Expand All @@ -52,13 +53,13 @@ async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:

async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
) -> ConfigFlowResult:
"""Confirm reauth dialog."""
if user_input is None:
return self.async_show_form(step_id="reauth_confirm")
return await self.async_step_user()

async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult:
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
"""Create an entry for the flow, or update existing entry."""
if self.reauth_entry:
self.hass.config_entries.async_update_entry(self.reauth_entry, data=data)
Expand All @@ -80,22 +81,22 @@ async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult:
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
config_entry: ConfigEntry,
) -> OptionsFlow:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)


class OptionsFlowHandler(config_entries.OptionsFlow):
class OptionsFlowHandler(OptionsFlow):
"""Google Assistant SDK options flow."""

def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
) -> ConfigFlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/google_assistant_sdk_custom/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Constants for Google Assistant SDK integration."""

from typing import Final

DOMAIN: Final = "google_assistant_sdk_custom"
Expand All @@ -7,7 +8,6 @@

CONF_LANGUAGE_CODE: Final = "language_code"

DATA_CREDENTIALS: Final = "credentials"
DATA_MEM_STORAGE: Final = "mem_storage"
DATA_SESSION: Final = "session"

Expand Down
51 changes: 12 additions & 39 deletions custom_components/google_assistant_sdk_custom/helpers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
"""Helper classes for Google Assistant SDK integration."""

from __future__ import annotations

from dataclasses import dataclass
from http import HTTPStatus
import json
import logging
import os
from typing import Any
import uuid

Expand All @@ -32,7 +31,6 @@

from .const import (
CONF_LANGUAGE_CODE,
DATA_CREDENTIALS,
DATA_MEM_STORAGE,
DATA_SESSION,
DOMAIN,
Expand Down Expand Up @@ -60,54 +58,29 @@ class CommandResponse:
text: str


async def async_create_credentials(
hass: HomeAssistant, entry: ConfigEntry
) -> Credentials:
"""Create credentials to pass to TextAssistant."""
# Credentials already exist in memory, return that.
if DATA_CREDENTIALS in hass.data[DOMAIN][entry.entry_id]:
return hass.data[DOMAIN][entry.entry_id][DATA_CREDENTIALS]

# Check if there is a json file created with google-oauthlib-tool with application type of Desktop app.
# This is needed for personal results to work.
credentials_json_filename = hass.config.path(
"google_assistant_sdk_credentials.json"
)
if os.path.isfile(credentials_json_filename):
with open(credentials_json_filename, encoding="utf-8") as credentials_json_file:
credentials = Credentials(token=None, **json.load(credentials_json_file))
# Store credentials in memory to avoid reading the file every time.
hass.data[DOMAIN][entry.entry_id][DATA_CREDENTIALS] = credentials
return credentials

# Create credentials using only the access token, application type of Web application,
# using the LocalOAuth2Implementation.
# Personal results don't work with this.
session: OAuth2Session = hass.data[DOMAIN][entry.entry_id][DATA_SESSION]
try:
await session.async_ensure_token_valid()
except aiohttp.ClientResponseError as err:
if 400 <= err.status < 500:
entry.async_start_reauth(hass)
raise err
return Credentials(session.token[CONF_ACCESS_TOKEN])


async def async_send_text_commands(
hass: HomeAssistant, commands: list[str], media_players: list[str] | None = None
) -> list[CommandResponse]:
"""Send text commands to Google Assistant Service."""
# There can only be 1 entry (config_flow has single_instance_allowed)
entry: ConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]

credentials = await async_create_credentials(hass, entry)
session: OAuth2Session = hass.data[DOMAIN][entry.entry_id][DATA_SESSION]
try:
await session.async_ensure_token_valid()
except aiohttp.ClientResponseError as err:
if 400 <= err.status < 500:
entry.async_start_reauth(hass)
raise

credentials = Credentials(session.token[CONF_ACCESS_TOKEN])
language_code = entry.options.get(CONF_LANGUAGE_CODE, default_language_code(hass))
with TextAssistant(
credentials, language_code, audio_out=bool(media_players), display=True
) as assistant:
command_response_list = []
for command in commands:
resp = assistant.assist(command)
resp = await hass.async_add_executor_job(assistant.assist, command)
text_response = parse_response(hass, command, resp)
_LOGGER.debug("command: %s\nresponse: %s", command, text_response)
audio_response = resp[2]
Expand Down Expand Up @@ -152,7 +125,7 @@ def parse_response(hass: HomeAssistant, command: str, resp):
return response


def default_language_code(hass: HomeAssistant):
def default_language_code(hass: HomeAssistant) -> str:
"""Get default language code based on Home Assistant config."""
language_code = f"{hass.config.language}-{hass.config.country}"
if language_code in SUPPORTED_LANGUAGE_CODES:
Expand Down
5 changes: 5 additions & 0 deletions custom_components/google_assistant_sdk_custom/icons.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"services": {
"send_text_command": "mdi:comment-text-outline"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/tronikos/google_assistant_sdk_custom/issues",
"requirements": ["beautifulsoup4", "gassist-text==0.0.10"],
"requirements": ["beautifulsoup4", "gassist-text==0.0.11"],
"version": "0.0.0"
}
15 changes: 8 additions & 7 deletions custom_components/google_assistant_sdk_custom/notify.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Support for Google Assistant SDK broadcast notifications."""

from __future__ import annotations

from typing import Any
Expand All @@ -24,13 +25,13 @@
}


def broadcast_commands(language_code: str):
def broadcast_commands(language_code: str) -> tuple[str, str]:
"""Get the commands for broadcasting a message for the given language code.
Return type is a tuple where [0] is for broadcasting to your entire home,
while [1] is for broadcasting to a specific target.
"""
return LANG_TO_BROADCAST_COMMAND.get(language_code.split("-", maxsplit=1)[0])
return LANG_TO_BROADCAST_COMMAND[language_code.split("-", maxsplit=1)[0]]


async def async_get_service(
Expand Down Expand Up @@ -60,13 +61,13 @@ async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
CONF_LANGUAGE_CODE, default_language_code(self.hass)
)

commands = []
commands: list[str] = []
targets = kwargs.get(ATTR_TARGET)
if not targets:
commands.append(broadcast_commands(language_code)[0].format(message))
else:
for target in targets:
commands.append(
broadcast_commands(language_code)[1].format(message, target)
)
commands.extend(
broadcast_commands(language_code)[1].format(message, target)
for target in targets
)
await async_send_text_commands(self.hass, commands)
5 changes: 4 additions & 1 deletion custom_components/google_assistant_sdk_custom/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
"unknown": "[%key:common::config_flow::error::unknown%]",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
Expand Down

0 comments on commit 13da925

Please sign in to comment.