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

add event for new list items #12

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
136 changes: 136 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,142 @@ use the built-in services to add, remove and update items from your synchronized
- `todo.remove_item`
- `todo.update_item`

## Events

Google recently removed third party integrations from Google Assistant. Now you can re-create these integrations.

When a new item is added to a synced list via Google Assistant or from Google Keep, a `google_keep_sync_new_item` event will be triggered. This allows Home Assistant to pick up new items from Google Keep and sync them with third party systems such as Trello, Bring, Anylist etc.

Note: Only new items that are not completed at the time of syncing will trigger the event.

Below are some examples of how to do this, click to expand.

<details>
<summary>Sync Google Todo List with Trello via email</summary>

1. Install and configure this plugin

1. Install and configure the Anylist plugin

1. Create an email notify service in Home assistant

1. Create a new [app password](https://myaccount.google.com/apppasswords>) in your Gmail.

1. Setup [creating cards by email]([https://support.atlassian.com/trello/docs/creating-cards-by-email/) in Trello

1. Add the following to your config.yaml file.

```yaml
notify:
- name: "Email to Trello Todo"
platform: smtp
server: "smtp.gmail.com"
port: 587
timeout: 15
encryption: starttls
sender: "[email protected]"
username: "[email protected]"
password: "app password"
sender_name: "your name"
recipient: "[email protected]"
```

1. Create an Automation in Home Assistant:

```yaml
alias: Google Todo List
description: Send new items added to Google's Todo List to Trello
trigger:
- platform: event
event_type: google_keep_sync_new_item
variables:
item_name: "{{ trigger.event.data.item_name }}"
item_id: "{{ trigger.event.data.item_id }}"
item_checked: "{{ trigger.event.data.item_checked }}"
list_name: "{{ trigger.event.data.list_name }}"
list_id: "{{ trigger.event.data.list_id }}"
condition:
# Update this to the name of your shopping list in Home Assistant.
- condition: template
value_template: "{{ list_name == 'Google To Do' }}"
action:
# Optional: Send a notification of new item in HA.
- service: notify.persistent_notification
data:
message: "'{{item_name}}' was just added to the '{{list_name}}' list."
# Call Home Assistant Notify service to send item to Trello Board.
- service: notify.email_to_trello_todo
data:
title: "{{item_name}}"
message:
# Complete item from google shopping list. Can also call todo.remove_item to delete it from the list.
# Update entity_id to the id of your google list in Home Assistant.
- service: todo.update_item
target:
entity_id: todo.google_keep_to_do
data:
status: completed
item: "{{item_name}}"
""
mode: single
```

</details>

<details>
<summary>Sync Google Shopping List with Anylist</summary>

The same process works for Bring Shopping list or any other integrated list to Home Assistant.

1. Install and configure this plugin

1. Install and configure the Anylist plugin

1. Create an Automation in Home Assistant:

```yaml
alias: Google Shopping List
description: Sync Google Shopping List with Anylist
trigger:
- platform: event
event_type: google_keep_sync_new_item
variables:
item_name: "{{ trigger.event.data.item_name }}"
item_id: "{{ trigger.event.data.item_id }}"
item_checked: "{{ trigger.event.data.item_checked }}"
list_name: "{{ trigger.event.data.list_name }}"
list_id: "{{ trigger.event.data.list_id }}"
condition:
# Update this to the name of your shopping list in Home Assistant.
- condition: template
value_template: "{{ list_name == 'Google Shopping list' }}"
action:
# Optional: Send a notification of new item in Home Assistant.
- service: notify.persistent_notification
data:
message: "'{{item_name}}' was just added to the '{{list_name}}' list."
# Add new item to your Anylist list
# Update the entity_id list name to your list in Home Assistant.
- service: todo.add_item
data:
item: "{{item_name}}"
target:
entity_id: todo.anylist_alexa_shopping_list
enabled: true
# Complete item from google shopping list. Can also call todo.remove_item to delete it from the list
# Update entity_id to the id of your google list in Home Assistant.
- service: todo.update_item
target:
entity_id: todo.google_keep_shopping_list
data:
status: completed
item: "{{item_name}}"
mode: single

```

</details>

## Limitations

- **Polling Interval**: While changes made in Home Assistant are instantly reflected in Google Keep, changes made in Google Keep are not instantly reflected in Home Assistant. The integration polls Google Keep for updates every 15 minutes. Therefore, any changes made directly in Google Keep will be visible in Home Assistant after the next polling interval.
Expand Down
65 changes: 63 additions & 2 deletions custom_components/google_keep_sync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry( # noqa: C901
alexschwantes marked this conversation as resolved.
Show resolved Hide resolved
hass: HomeAssistant, entry: ConfigEntry
) -> bool:
"""Set up Google Keep Sync from a config entry."""
# Create API instance
api = GoogleKeepAPI(hass, entry.data["username"], entry.data["password"])
Expand All @@ -28,13 +30,72 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.error("Failed to authenticate Google Keep API")
return False # Exit early if authentication fails

async def _parse_gkeep_data_dict() -> dict[str, dict[str, dict[str, str]]]:
alexschwantes marked this conversation as resolved.
Show resolved Hide resolved
"""Parse unchecked gkeep api data to a dictionary."""
todo_lists = {}

# for each list
alexschwantes marked this conversation as resolved.
Show resolved Hide resolved
for glist in coordinator.data or []:
# get all the unchecked items only
items = {
item.id: {"summary": item.text, "checked": item.checked}
for item in glist.items
if not item.checked
}
todo_lists[glist.id] = {"name": glist.title, "items": items}
return todo_lists

async def _check_gkeep_lists_changes(original_lists, updated_lists) -> None:
alexschwantes marked this conversation as resolved.
Show resolved Hide resolved
"""Compare original_lists and updated_lists lists.

Report on any new TodoItem's that have been added to any
lists in updated_lists that are not in original_lists.
"""
# for each list
for upldated_list_id, upldated_list in updated_lists.items():
alexschwantes marked this conversation as resolved.
Show resolved Hide resolved
if upldated_list_id not in original_lists:
_LOGGER.debug(
"Found new list not in original: %s", upldated_list["name"]
)
continue

# for each todo item in the list
for upldated_list_item_id, upldated_list_item in upldated_list[
"items"
].items():
# if todo is not in original list, then it is new
if (
upldated_list_item_id
not in original_lists[upldated_list_id]["items"]
):
list_prefix = entry.data.get("list_prefix", "")
data = {
"item_name": upldated_list_item["summary"],
"item_id": upldated_list_item_id,
"item_checked": upldated_list_item["checked"],
"list_name": (f"{list_prefix} " if list_prefix else "")
+ upldated_list["name"],
"list_id": upldated_list_id,
}

_LOGGER.debug("Found new TodoItem: %s", data)
hass.bus.async_fire("google_keep_sync_new_item", data)
watkins-matt marked this conversation as resolved.
Show resolved Hide resolved

# Define the update method for the coordinator
async def async_update_data():
"""Fetch data from API."""
try:
# save lists prior to syncing
original_lists = await _parse_gkeep_data_dict()
# Directly call the async_sync_data method
lists_to_sync = entry.data.get("lists_to_sync", [])
return await api.async_sync_data(lists_to_sync)
result = await api.async_sync_data(lists_to_sync)
# save lists after syncing
updated_lists = await _parse_gkeep_data_dict()
# compare both list for changes
await _check_gkeep_lists_changes(original_lists, updated_lists)

return result
except Exception as error:
raise UpdateFailed(f"Error communicating with API: {error}") from error

Expand Down
Loading