Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for multiple Google Accounts and an optional master token login #5

Merged
merged 3 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,44 @@ This is meant to be used with Google Assistant to easily add items to Home Assis
5. Restart Home Assistant.
6. In the Home Assistant Configuration, add the new integration by searching for its name.

When creating a new service you will be presented with 3 fields to fill in:
- username (requred)
- password (optional)
- master token (optional)

You will need to fill out either password or master token as a way of authenticating to your google account via the keep api. App passwords are highly recommended, but if you are seeing authentication errors see the Authentication Options section for other methods.

## Usage

The integration adds a service call that can be used on any automation to synchronize Google Keep List with Home Assistant Shopping List.
The integration adds one service per service entry in the integration page that can be called by any automation or script to synchronize Google Keep List with Home Assistant Shopping List.

The service goes through the following steps:
Each service will go through the following steps:

1. Reads all unchecked items from the specified Google Keep list. Checked items are ignored.
1. Read all unchecked items from the specified Google Keep list. Checked items are ignored.
2. Adds each item to Home Assistant Shopping List integration.
3. Delete the item from Google Keep list. This prevent double adding an item if the service is called again.


## Authentication Options
It is recommended to login with an app password as this can be easily revoked if it were to become compromised. That being said, some accounts seem to have issues logging in with passwords (both the regular password and app passwords) depending on which device you are attempting to get a token from. A workaround that seems to work for these "problematic" accounts is to create a docker container and run a script to get the tokens which we can then pass into the keep api. Although this is the least secure by far (master tokens never expire) it should allow for any google account to use this integration

Steps to acquire a master token: (**only do this if username and password are NOT working**)
1. Install docker container if you do not already have it
2. In your preferred cli run the following commands

```
$ docker pull breph/ha-google-home_get-token:latest
$ docker run -it -d breph/ha-google-home_get-token
# use the returned container id inside the next command
$ docker exec -it <id> bash

# inside container
root@<id>:/# python3 get_tokens.py`
```

3. From the printed output you should get the master token for the account you entered, it will start with: aas_et/. Copy the whole token without any additional spaces into the master token field when setting up a new service in the integration ui.


## Shoutouts/Credits
[@fcastilloec](https://github.com/fcastilloec) For creating this project and publishing it for us to use
[@lhy](https://github.com/LeeHanYeong) For the docker container to reliably get a master token over on [ha-google-home](https://github.com/leikoilja/ha-google-home/issues/599#issuecomment-1760800334)
12 changes: 7 additions & 5 deletions custom_components/gkeep_list_sync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
CONF_LIST_ID,
SHOPPING_LIST_DOMAIN,
MISSING_LIST,
SERVICE,
SERVICE_NAME_BASE,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -83,15 +83,17 @@ async def handle_sync_list(call) -> None: # pylint: disable=unused-argument
# Sync again to delete already added items
await hass.async_add_executor_job(keep.sync)

# Register the service
hass.services.async_register(DOMAIN, SERVICE, handle_sync_list)
# Register the service - Allow for as many services as we have usernames
hass.services.async_register(DOMAIN, get_service_name(config_entry), handle_sync_list)

return True


async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: # pylint: disable=unused-argument
"""Unload a config entry."""
_LOGGER.debug("Unload integration")
hass.services.async_remove(DOMAIN, SERVICE)
del hass.data[DOMAIN]
hass.services.async_remove(DOMAIN, get_service_name(config_entry))
return True

def get_service_name(config_entry: ConfigEntry) -> str:
return SERVICE_NAME_BASE + "_" + config_entry.data.get(CONF_USERNAME).partition("@")[0]
36 changes: 20 additions & 16 deletions custom_components/gkeep_list_sync/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
CONF_LIST_ID,
DEFAULT_LIST_TITLE,
MISSING_LIST,
MASTER_TOKEN
)

_LOGGER = logging.getLogger(__name__)
Expand All @@ -53,6 +54,16 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
config_entry.data.get(CONF_USERNAME),
config_entry.data.get(CONF_ACCESS_TOKEN),
)
elif data.get(MASTER_TOKEN) is not None:
config[CONF_USERNAME] = data[CONF_USERNAME]
await hass.async_add_executor_job(
keep.resume,
data[CONF_USERNAME],
data[MASTER_TOKEN]
)
elif data.get(MASTER_TOKEN) is None and data.get(CONF_PASSWORD) is None:
_LOGGER.error("A password or master token is needed to setup a new service for gkeep-list-sync")
raise(InvalidConfig())
else:
config[CONF_USERNAME] = data[CONF_USERNAME]
await hass.async_add_executor_job(
Expand Down Expand Up @@ -86,7 +97,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Google Keep List Sync."""

VERSION = 1
VERSION = 2

def __init__(self) -> None:
"""Initialize the Google Keep config flow."""
Expand All @@ -99,36 +110,27 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Flo
"""Handle the initial step."""
errors = {}

config_entry: config_entries.ConfigEntry = None

# Check that the dependency is loaded
if not self.hass.data.get(SHOPPING_LIST_DOMAIN):
_LOGGER.error("Shopping List integration needs to be setup")
return self.async_abort(reason="dependency_not_found")

# Get any current configuration entries
if self._async_current_entries():
config_entry = self._async_current_entries()[0]
else:
config_entry = None

# Check that single instance of the config is allowed
if config_entry and not self.reauth:
return self.async_abort(reason="single_instance_allowed")

# Schema for initial setup of loging re-auth
all_schema = vol.Schema(
{
vol.Required(
CONF_USERNAME,
default=self.username,
): str,
vol.Required(
CONF_PASSWORD,
): str,
vol.Optional(CONF_PASSWORD): str,
vol.Optional(MASTER_TOKEN): str,
vol.Required(
CONF_LIST_TITLE,
default=self.list_title,
): str,
}
},
)

# Schema for List name re-auth
Expand Down Expand Up @@ -179,6 +181,8 @@ async def async_step_reauth(self, data: Mapping[str, Any]) -> FlowResult:
self.missing_list = self.hass.data[DOMAIN][MISSING_LIST]
return await self.async_step_user()


class CannotLogin(HomeAssistantError):
"""Error to indicate we cannot login."""

class InvalidConfig(HomeAssistantError):
"""Error to indicate the user entered invalid config"""
3 changes: 2 additions & 1 deletion custom_components/gkeep_list_sync/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
DEFAULT_LIST_TITLE = "Groceries"
MISSING_LIST = "missing_list"
SHOPPING_LIST_DOMAIN = "shopping_list"
SERVICE = "sync_list"
SERVICE_NAME_BASE = "sync_list"
MASTER_TOKEN = "master_token"
2 changes: 1 addition & 1 deletion custom_components/gkeep_list_sync/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"config_flow": true,
"documentation": "https://github.com/fcastilloec/gkeep-list-sync/blob/main/README.md",
"issue_tracker": "https://github.com/fcastilloec/gkeep-list-sync/issues",
"requirements": ["gkeepapi==0.14.2", "urllib3<2", "gpsoauth==1.0.2"],
"requirements": ["gkeepapi==0.15.1", "urllib3<2", "gpsoauth==1.0.2"],
"dependencies": ["shopping_list"],
"codeowners": ["@fcastilloec"],
"integration_type": "service"
Expand Down
3 changes: 2 additions & 1 deletion custom_components/gkeep_list_sync/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]",
"list_title": "List title"
"list_title": "List title",
"master_token": "Master Token"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion custom_components/gkeep_list_sync/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"data": {
"list_title": "List title",
"password": "Password",
"username": "Username"
"username": "Username",
"master_token": "Master Token"
}
}
}
Expand Down