Recursive Actions with Launch Center Pro & Pythonista

There's no shortage of great examples about using Drafts and recursive actions to accomplish a myriad of tasks. While incredibly useful, creating and understanding these workflows can be a daunting task given the complexities of x-callback-url, url-encoding and nesting (Phillip does a great job explaining it though).

There's another downside often overlooked – the constant hopping back and forth between apps. Negligible on faster devices but a real pain on older ones. Fortunately, there's a quick fix for this, especially now that Launch Center Pro2 made its debut on the iPad.

The Idea

The core apps behind this workflow are Launch Center Pro and Pythonista. I'll also use Fantastical 2 and GoodTask3, although this trick can be tweaked to accommodate just about any app that supports x-callback-url with x-success.

The idea is as simple as the execution: Use x-success to trigger subsequent actions without leaving the app. Using Launch Center Pro's list prompt, we send all the text to Pythonista, which in turn processes the text, prepares the x-callback-url and then triggers it. The url-encoding is handled by Pythonista, taking into account the various nesting levels of course.

The Execution

Now that I've got your attention, let's take a quick look at the code that makes this possible along with a few examples. If you recall from a previous post, I eschewed Due in favor of Reminders for my time sensitive tasks. For that very reason, the two examples I'll present pertain to adding tasks to reminders.

Using Goodtask

GoodTask is a universal Reminders app with some innovative features and a versatile x-callback-url4. To get things started, create a new LCP action with the following url (or import here):

pythonista://list2goodtask?action=run&argv=[prompt-list]

This will prompt you for a list of tasks. Unfortunately GoodTask doesn't have Natural Language Parsing (NLP) and so we need to supply a little more detail to get things right. Since LCP splits each item with a comma, we need a different separator. I opted with a forward slash (/). Therefore, the following list item Buy milk/03-14 15:30/Personal, when parsed by our python code, creates the task Buy milk on March 14 at 3:30pm in our Personal list.

Essentially we're using a simple construct: Task / Date time / list name. If, for instance, you wanted to create a simple task in your Work list with no date/time, you'd use: Call boss about secret project//Work. Notice that even though I defined no date or time, I still need to account for it, therefore the presence of the two forward slashes. This is important to .

Now let's take a look at the python code that performs all the magic. Create a new empty script in Pythonista (or import using this gist) and add the following code:

#coding: utf-8
import urllib2
import webbrowser
import sys

events = sys.argv[1]    # The list of items received from LCP
SEP = '/'   # Change to any seperator you choose to use
url_str = ''    # Initialize the x-callback-url

# Split the list by commas and iterate over each resulting item
for event in events.split(','):

    # Split each item by the separator SEP 
    # and get tasks, dates and listname
    try:
        task = event.split(SEP)[0].strip()
    except IndexError:
        task = ''

    try:
        date = event.split(SEP)[1].strip()
    except IndexError:
        date = ''

    try:
        listname = event.split(SEP)[2].strip()
    except IndexError:
        listname = ''


    # Create the task url
    task_str = 'goodtask://add?text=%s&due=%s&list=%s' %(urllib2.quote(task,''), date, listname)


    '''Check to see if there is already an item in the x-callback-url, 
    if not, create it, if there is then add task_str + &x-success followed by
    a the URL-encoded url_str. This respects the needed encoding of nested
    x-success entries.'''
    if url_str == '':
        url_str = task_str

    else:
        url_str = task_str+'&x-success='+urllib2.quote(url_str,'')

# Check to see if GoodTask is installed and open the URL if it is.
if webbrowser.can_open("goodtask://"):
    webbrowser.open(url_str)
else:
    print "GoodTask not installed"

I would like to draw your attention to the most important block, namely the loop that creates the x-callback-url. As you're well aware, after successfully completing an action with x-callback-url, an app triggers a x-success which has a layer of url-encoding. The deeper the recursion the more complex this becomes and therefore we usually return to Drafts because it simplifies the process of creating these complex loops. Fortunately this small block of code handles that for us now.

if url_str == '':
    url_str = task_str

else:
    url_str = task_str+'&x-success='+urllib2.quote(url_str,'')

This little block of code ensures that your nested x-success queries are properly encoded ensuring things work smoothly.

Now that you have your LCP action and Pythonista script, you can quickly create any number of tasks in one fell swoop, such as:

Using Fantastical 2

Thanks to Fantastical's great Natural Language Parsing, our python script, as well as our data entry, can be simplified, as we don't have to create separate entries for date and list name. Furthermore, we're not limited to adding tasks but can also add events to the calendar, all in one go.

As before, create a new action in LCP with the following url (or import here):

pythonista://list2fantastical?action=run&argv=[prompt-list]

Next create a new empty script in pythonista (or import this gist):

#coding: utf-8
import urllib2
import webbrowser
import sys

tasks = sys.argv[1]     # The list of items received from LCP
url_str = ''            # Initialize the x-callback-url

# Split the list by commas and iterate over each resulting item
for task in tasks.split(','):

    '''Check to see if there is already an item in the x-callback-url, 
    if not, create it, if there is then add task_str + &x-success followed by
    a the URL-encoded url_str. This respects the needed encoding of nested'''

    if url_str == '':
        url_str += 'fantastical2://x-callback-url/parse?sentence='+urllib2.quote(task,'')

    else:
        url_str = 'fantastical2://x-callback-url/parse?sentence='+urllib2.quote(task,'')+'&x-success='+urllib2.quote(url_str,'')

# Check to see if Fantastical is installed and open the URL if it is.
if webbrowser.can_open("fantastical2://"):
    webbrowser.open(url_str)
else:
    print "Fantastical not installed"

As you can see, this code is much simpler since we don't need to further split each list item. All you need is to write as you normally would. Now you can launch LCP, trigger the action and create tasks or events quickly and easily such as:

Hopefully by now you'll also have grasped just how easy it can be to extend this code to achieve recursive actions without having to worry about the intricacies of properly encoding the various nested levels.


  1. iPhone | iPad 

  2. iPhone | iPad 

  3. Previously known as This Week.  

  4. Extensively documented in the app's settings screen.