Learning to Code: Siri, meet Todoist

So I have not exactly been good about checking in here with my New Year’s Resolution, and now the year is nearly a quarter over. But this does not mean I have been ignoring my promise to learn to code this year. In fact, I have my first functional piece of software, an AppleScript based app that takes reminders – like the ones you get when you use Siri – and transfers them into Todoist, my to do app of choice these days.

I started out with the basics using my Lynda.com subscription. I took three courses:

  1. Foundations of Programming: Fundamentals

  2. Foundations of Programming: Object-Oriented Design

  3. Up and Running with AppleScript

I have had limited experience with all of these concepts in the past, but I figured this was a good time to sit down and start over with the basics. I have always been able to copy code from others and make minor adjustments, but the goal now is to know how all of it works, not just the small section I am tweaking. The most difficult thing with these courses was not getting caught up in the specifics. They are broad overview courses. It’s about learning the mechanics, not specific syntax.

For my first usable app I decided to go with something that fills an actual need. I have been using Todoist for a while now and have really liked it, except for one thing. It does not work with Siri. No third party app works with Siri, but some other to do apps have bridged the divide by pulling in information from Reminders. Todoist does not do this. Yes, I could use IFTTT, but the problem with that is it merely duplicates the data rather than transfer it.

This was a great use case for a simple AppleScript app. It does not need a UI, and Reminders is AppleScript friendly. Todoist is not, but they do have a very open API.

With all that information in hand, here is the code I wrote, in AppleScript, to take Reminders, add them to Todoist with all data intact, and then delete them from Reminders.

-- Copy todos from Reminders to Todoist
-- Set the script to stay open.
on idle
    tell application "Reminders"
        -- Set Reminders to loop through until the count is 0
        repeat while ((count of reminders) in list "Reminders") is greater than 0
            -- Get the name of the reminder
            set eventName to name of ((first reminder) in list "Reminders")
            -- Get the due date if one is present
            if due date of ((first reminder) in list "Reminders") is not missing value then
                (* Break apart the due date and set the month and day as integers.
                Format as MM/DD. This is required syntax for Todoist *)
                set dueDate to due date of first reminder
                set dueMonth to month of dueDate
                set dueMonthInt to dueMonth as integer
                set dueDay to day of dueDate
                set dueDayInt to dueDay as integer
                -- Set time in the hh:mm format
                set dueHour to hours of dueDate as string
                if minutes of dueDate is less than 10 then
                    set dueMin to "0" & minutes of dueDate as string
                    set dueMin to minutes of dueDate as string
                end if
                -- Set the variable for the due date we will actually pass to Todoist
                set dueOn to dueMonthInt & "/" & dueDayInt & " @ " & dueHour & ":" & dueMin as string
            end if
            -- Execute shell script to Todoist API with no date
            if due date of ((first reminder) in list "Reminders") is missing value then
                do shell script "curl -X POST -d 'content=" & {eventName} & "' -d priority=1 -d 'token=YOURTOKENHERE' https://todoist.com/API/additem"
                -- Execute shell script to Todoist with date
                do shell script "curl -X POST -d 'content=" & {eventName} & "' -d priority=1 -d 'date_string=" & {dueOn} & "' -d 'token=YOURTOKENHERE' https://todoist.com/API/additem"
            end if
            -- Delete the reminder. The loop ends when all reminders are processed.
            delete ((first reminder) in list "Reminders")
        end repeat
    end tell
    -- Set the script to run again every 30 seconds.
    return 30
end idle

I am posting this code here and making it freely available. Feel free to use it yourself and alter it as you see fit (I would love attribution for anything you derive from it though).

The app is designed to run continuously in the background. It gets the reminders in a list I have called “Reminders”. I only want to pull from this list as I do have others that I do not want to script to touch. I get the name and due date if there is one. The due date proved the be the trickiest part of this. Todoist expects a very specifically formatted date string and the format AppleScript was returning was not compatible.

I know there is likely a better way to get the date into the format I need (MM/DD hh:mm) but this was how I was able to get it to work for me. Not the most elegant code I know, but it worked, and given this is my first functional program, I wanted to show it here as it was when it first ran successfully.

If there is no due date, the script simply passes the event name to Todoist’s API. Otherwise name and date are included. Note that if you want to use this you need to replace YOURTOKENHERE with your API token that you can get from the Todoist settings. Otherwise the script won’t know which Todoist account to add to.

The script will delete each todo from Reminders after processing it. Once there are no reminders left, it ends, and waits 30 seconds before running again when you export it as an application using the “stay open after run handler” option.

In my case I loaded the app onto my OS X server, enabled iCloud reminders sync to that machine, and that was that. Everything now works. Within 30 seconds of me using Siri to add a new reminder, it will be transferred from Reminders into Todoist. If you don’t have an OS X server, it will still work on any OS X machine so long as the computer is running.

Yes this is a very simple app using a scripting language. It won’t be in the App Store any time soon. But it feels extremely good to have a real, functional piece of software that exists and fills a need. For the first time rather than relying on someone else solving this problem, I did it myself. I have done a few other AppleScript apps since this one, and now feel much more comfortable with at least the basics of programming. So now it is time to move on and dip my toe into the Objective-C pool. That will be the next post on this topic.

Learning to Code: Siri, meet Todoist was last updated March 19th, 2015 by Michael Truskowski