Sync your Alexa/Todoist shopping list to the Home Assistant shopping list!

Offering what advice I can…

  • Not sure, but there may be a security issue related to adding config/pyscript to the sys.path. You have to manually create config/pyscript_modules
  • I didn’t add anything relating to this script to a services.yaml
  • Make sure you’ve rebooted your full system since you installed pyscript

You’ll need to call pyscript.reload after you get it to see write_file.py, then the new pyscript.sync_shopping_list service should appear.

If it still doesn’t work, you’ll need to check your logs for an error that we can diagnose.

I was using @fenty17’s aiohttp version of this script and it started giving me an error code today (script was functioning, just throwing an error code; maybe related to the aiohttp bump in 2023.7.3?)

ERROR (MainThread) [custom_components.pyscript.file.shopping_list_sync.update_shopping_list] Exception in <file.shopping_list_sync.update_shopping_list> line 97:
hass.data["shopping_list"].async_load()
^
TypeError: '<function>' is not callable (got None)

Switched to the other version (updated for api v2) and I’m no longer getting errors.

After reviewing everything again, I was making a fools mistake. I placed the script on config/custom_components/pyscript, instead of config/pyscript.

But even after that, I had issues running @fenty17’s version. I ended up using @alexismadd version and it’s working correctly.

From what I could understand, the script is able to sync HASS Shop-List with Alexa/Todoist when an item is added or deleted from HASS. But, when I add something through Todoist or Alexa, it doesn’t sync with HASS. This is easily fixed using a automation to call the service, though.

Aside from that, I was also wondering if it was possible to add the API Token and ID on secrets.yaml and call it from there. Just for security reasons…

Anyway, thanks for this awesome script!

…when I add something through Todoist or Alexa, it doesn’t sync with HASS.

For this to work, you need to have your HA available on the Internet with SSL enabled.

You need to setup a webhook. Cheat code: Nabu Casa, the built in paid cloud service, will establish a secure connection to your home instance and allow you to use webhooks

I’ve got the script set up and running (tick occurs when script run), but the shopping list doesn’t update. I get an error in the logs:

2023-09-06 21:13:41.675 ERROR (MainThread) [custom_components.pyscript.file.shopping_list_sync.sync_shopping_list] Exception in <file.shopping_list_sync.sync_shopping_list> line 44:
            await f.write(j)
                          ^
TypeError: object int can't be used in 'await' expression

Does anyone know what’s going on here?
I’m using fenty17’s code posted above.

I’m also getting this error on occasion and can’t work out why. I only updated a few api calls in the script a while back but am not skilled enough to do much more! There was an update to the code more recently that was also pulled to @alexismadd ’s version I think. Merge pull request #1 from rhapgood/main · fenty17/ha-shopping-list-sync@0037cc3 · GitHub

My list sync is currently working, but did get out of sync which I think caused the error. Hopefully someone will be able to at least understand the cause of the error.

I was having issues with the version that used aiohttp a few weeks ago as well, switched back to the original with some modifications. This code has been working for me for the last few weeks without error (replace the two REPLACEME variable values):

import requests
import sys
import time
from importlib import reload
if "/config/pyscript_modules" not in sys.path:
    sys.path.append("/config/pyscript_modules")

import write_file

write_file = reload(write_file)

TODOIST_TOKEN = "REPLACME"
TODOIST_PROJECT_ID = REPLACEME

def get_tasks():
    get_tasks_url =f"https://api.todoist.com/rest/v2/tasks?project_id={TODOIST_PROJECT_ID}"
    headers = {"Authorization" : f"Bearer {TODOIST_TOKEN}"}

    status_code = 500

    while status_code is not 200:
        response = task.executor(requests.get, get_tasks_url, headers = headers)
        status_code = response.status_code
        task.sleep(1)
    json = response.json()
    return json

def add_task(item):
    url = "https://api.todoist.com/rest/v2/tasks"
    headers = {"Authorization" : f"Bearer {TODOIST_TOKEN}", "Content-Type" : "application/json"}
    body = {"content" : item, "project_id" : TODOIST_PROJECT_ID}
    status_code = 500

    while status_code is not 200:
        response = task.executor(requests.post, url, headers = headers, json=body)
        status_code = response.status_code
        task.sleep(1)
    if status_code == 200:
        return True
    else:
        return False

def update_task(id, content):
    url = f"https://api.todoist.com/rest/v2/tasks/{id}"
    headers = {"Authorization" : f"Bearer {TODOIST_TOKEN}", "Content-Type" : "application/json"}
    body = {"content" : content}
    status_code = 500

    counter = 0
    while status_code is not 204 and counter < 10:
        response = task.executor(requests.post, url, headers = headers, json=body)
        status_code = response.status_code
        task.sleep(1)
        counter += 1
    if status_code == 204:
        return True
    else:
        return False

def complete_task(id):
    url = f"https://api.todoist.com/rest/v2/tasks/{id}/close"
    headers = {"Authorization" : f"Bearer {TODOIST_TOKEN}"}
    status_code = 500

    counter = 0
    while status_code is not 204 and counter < 10:
        response = task.executor(requests.post, url, headers = headers)
        status_code = response.status_code
        task.sleep(1)
        counter += 1
    if status_code == 204:
        return True
    else:
        return False

@service
def sync_shopping_list():
    tasks = []
    json = get_tasks()
    for item in json:
        tasks.append({"name" : item["content"], "id" : str(item["id"]), "complete" : item["is_completed"]})

    task.executor(write_file.write_json, filename = "/config/.shopping_list.json", content=tasks)
    hass.data["shopping_list"].async_load()

@event_trigger('shopping_list_updated')
def update_shopping_list(action=None, item=None):
    if action == "add":
        add_task(item["name"])
        sync_shopping_list()
    if action == "update" and item["complete"] == False:
        update_task(item["id"],item["name"])
        sync_shopping_list()
    if action == "update" and item["complete"] == True:
        complete_task(item["id"])
        sync_shopping_list()

Shopping list reverted to a one-way sync sometime around upgrade to HA 2023.09. Sync from HA to Todoist still works, but the sync from Todoist to HA does not. Error: Exception in <file.shopping_list_sync.update_shopping_list> line 44: await f.write(j) ^ TypeError: object int can't be used in 'await' expression

I’ve tried reverting to “original with some modifications” from @xiport but no dice. Flying blind with pyscript so hoping someone can bail me out…

Everything working fine for me. I just added an item to my shopping list through an Alexa voice command. Then I checked it off on Home Assistant. Both changes propagated through to todoist and the HA frontend.

Maybe try restarting your system? Or check your settings on todoist calling the HA hook.

Following up on this… The shopping list is working after uninstalling and reinstalling the Shopping List integration.

Additional detail for posterity: I had an entity in home assistant that counts the items in the list and it was reflecting the number of items in Todoist/Alexa and it didn’t jive with the items shown in my HA Shopping List. I opened the shopping_list.json and could see the items from Todoist/Alexa but they weren’t showing up in the HA shopping list card despite being complete:false. That weird behavior is what led me to uninstall and reinstall the Shopping List integration which ultimately solved the problem.

I don’t get the Todoist webhook to work since I updated latest 2023.9.2.
I have created the Webhook including step 6
It also shows the hook is active
But nothing is triggered in HA. If I trigger the update in HA with postman it’s works fine.
Also the checkmark “only via the local web” was removed, and I tested it.

I have read the following in the Todoist instructions:
"To activate webhooks for personal use, you need to complete the OAuth process with your account. You can do this without code by manually executing the OAuth flow in two steps.“
But here I am a little overwhelmed by what has to be done in connection with HA? (I don’t believe this is the problem here, as no one else has had a problem with this so far.)

What I have also already tried:

  • Reinstalled everything (HA Webhook, Todoist App)
  • Shoppinglist integration in HA reinstalled
  • restart HA

Same here, have you got a solution?

I’m not having any issues with my setup (2023.10.1).

Make sure you are using that test token from step 6 as your TODOIST_TOKEN (as opposed to the regular API token). No OAuth needed.

Just came here to say thanks as I’d been racking my brain trying to get this to work and couldn’t, for the life of me, work out what was wrong. Really appreciate this @fenty17 .

1 Like

I updated to 2023.10 and it seems to work again.
I tried both tokens API and the Test token. I could trigger the python script manually with both tokens and it worked but no webhook was triggered by todoist automatically.
Thanks for your help.

Hi All

Brilliant idea to make this! So useful! I have installed this through HACs and set the config. Had a few errors and not getting anything back when I call the function. So I did get a few errors when I first created the shopping list python script… Here is what I had to mod…

First error:

TODOIST_PROJECT_ID = - Complained of invalid Syntax so I had to put quotes at each end ""

Second Error:

No “config/pyscript_modules” folder created. I could not see a way to create a root level folder called “pyscript_modules”… Workaround, create it inside the main pyscripts folder and update the sys.path in the code…

This got the file to start without error but loads with no data…

Pretty sure my API key or ProjectID is correct so I don’t think that is the problem.

My gut feeling is the plugin hasn’t loaded properly hence the “Modules” folder not being there?

Has anyone else had these issues before?

this is all I get back

1 Like

Did you managed to fix it?

@HVPereira

I did… Thanks for asking… I realised that I needed to create the pyscripts_modules folder manually. I did that and moved the writefile.py script into the correct place and it worked…

I do have a small issue now with notifications not triggering when using the shopping list card…

I know what the issue is.

The shopping list addon has a event that fires when something is updated. It carrys the payload of the item you added to the list. You can use this even to create an automation to tell someone when something is added to the list…

The issue is that the Pyscript function bypasses the shopping list add-on and writes to the Shopping list JSON file directly, so it never calls the function.

I am trying to see if there is a way around this. If not I will see if I can Mod the Pyscript to call the Shopping list function instead of writing the JSON direct. That would then trigger any automation and native function of the addon

Has anyone managed to find a way to get notifications working if someone adds an item via Alexa? Looking through the Python code we basically bypass the shopping list addon functions and write directly to the JSON. That is not a problem but it does mean that the functions that are in the shopping list add on that fire an event if updated won’t fire.

Not sure if there is another way around this but I am thinking of seeing if I can change this one line of code to call the shopping list service and pass it the new value. I think that would then trigger the event…

task.executor(write_file.write_json, filename = "/config/.shopping_list.json", content=tasks)

I think the combination of these two docs would let me do that?

Python Scripts - Home Assistant.

if anyone knows another way to send a notification when the shopping list is updated via alexa that is less complicated would be great.

Couldn’t you just do that as an additional action in the automation that is triggered by the webhook from Todoist?