Interoperability: Omni Automation and Alfred
DISCLAIMER: Mention of third-party websites and products is for informational purposes only and constitutes neither an endorsement nor a recommendation. OMNI-AUTOMATION.COM assumes no responsibility with regard to the selection, performance or use of information or products found at third-party websites. OMNI-AUTOMATION.COM provides this only as a convenience to our users. OMNI-AUTOMATION.COM has not tested the information found on these sites and makes no representations regarding its accuracy or reliability. There are risks inherent in the use of any information or products found on the Internet, and OMNI-AUTOMATION.COM assumes no responsibility in this regard. Please understand that a third-party site is independent from OMNI-AUTOMATION.COM and that OMNI-AUTOMATION.COM has no control over the content on that website. Please contact the vendor for additional information.
This section concerns the third automation scenario listed on the main page:
- Alfred is used to launch an Omni Automation script that requires no user input to perform a procedure in OmniFocus whose linked results are then displayed in the Alfred interface.
The following documentation details the construction of the third Alfred action in the OmniFocus Collection Alfred Workflow. This action is used to generate and display in the Alfred interface a linked list of the OmniFocus tasks that are due today.
Scenario 3: Displaying the Results an Omni Automation Script
This scenario begins with the execution of the self-contained Omni Automation script that requires no input from the user.
After performing a filter procedure to identify any tasks that are due today, the script generates a JSON object containing specific information for each of the matched tasks. For example, here is the JSON for a single matched tasks:
Response JSON
{
"items":[
{
"title": "Sunday Dinner",
"subtitle": "DUE: 17:00:00 GMT-0700 (Pacific Time)",
"arg": "dvpyM40skEI"
}
]
}
NOTE: The highlighted object keys (line 39-53 below) items title subtitle arg are some the available JSON keys supported by Alfred’s Script Filter step which is used in the custom action to parse and display results.
The corresponding values for the keys are generated by the script dynamically for each matched task: task name (title), task due time (subtitle), and the task’s unique ID string (arg) which will be used by the Alfred response action to construct a link to the matched task in OmniFocus.
The generated JSON object is converted into an encoded string (line 56-59 below) and added to the Alfred response URL (line 69 below). When executed, the response URL will target a specific Alfred action designed to receive parse, and display the passed JSON object.
Tasks Due Today
(async () => {
try {
function createUtterance(textToSpeak){
langCode = Speech.Voice.currentLanguageCode
voiceObj = Speech.Voice.withLanguage(langCode)
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = Speech.Utterance.defaultSpeechRate
return utterance
}
var synthesizer = new Speech.Synthesizer()
// AVAILABLE TASKS DUE TODAY
fmatrStyle = Formatter.Date.Style.Short
fmatr = Formatter.Date.withStyle(fmatrStyle)
rangeStart = fmatr.dateFromString('today')
rangeEnd = fmatr.dateFromString('tomorrow')
tasksToProcess = flattenedTasks.filter(task => {
return (
task.effectiveDueDate >= rangeStart &&
task.effectiveDueDate < rangeEnd &&
task.taskStatus === Task.Status.DueSoon
)
})
if(tasksToProcess.length === 0){
throw {
name: "No Items Matched",
message: "No tasks are due today."
}
}
matchCount = tasksToProcess.length
// CONSTRUCT ARRAY OF OBJECTS FOR TASKS
itemsArray = new Array()
for (i = 0; i < tasksToProcess.length; i++){
aTask = tasksToProcess[i]
// CONSTRUCT OBJECT FOR TASK
itemObj = new Object()
itemObj.title = aTask.name
itemObj.subtitle = "DUE: " + aTask.dueDate.toTimeString()
itemObj.arg = aTask.id.primaryKey
itemsArray.push(itemObj)
}
// CONSTRUCT JSON OBJECT CONTAINING ITEM ARRAY
jsonObj = new Object()
jsonObj.items = itemsArray
// CONVERT TO STRING FOR INCLUSION IN URL
jsonStr = JSON.stringify(jsonObj)
// ENCODE FOR URL
encodedJSON = encodeURIComponent(jsonStr)
// ALFRED WORKFLOW ID
alfredWorkflowID = "com.omni-automation.of.alfred-collection"
// ALFRED ACTION TRIGGER ID
alfredTriggerVar = "displayResults"
// CONSTRUCT URL STRING
urlStr = `alfred://runtrigger/${alfredWorkflowID}/`
urlStr += `${alfredTriggerVar}/?argument=${encodedJSON}`
// CONVERT TO URL OBJECT AND EXECUTE
URL.fromString(urlStr).open()
// CONSTRUCT/SPEAK RESONSE WHILE ALFRED DISPLAYS RESULTS
taskOrTasks = (matchCount === 1)? "task":"tasks"
response = `${matchCount} ${taskOrTasks} due today.`
utterance = createUtterance(response)
synthesizer.speakUtterance(utterance)
}
catch(err){
utterance = createUtterance(err.message)
synthesizer.speakUtterance(utterance)
//new Alert(err.name, err.message).show()
}
})();
Using the Omni Automation Script URL Constructor provided on this website, convert the “Tasks Due Today” script into a script URL.
Encoded Script URL
omnifocus://localhost/omnijs-run?script=%28async%20%28%29%20%3D%3E%20%7B%0A%09try%20%7B%0A%09%09function%20createUtterance%28textToSpeak%29%7B%0A%09%09%09langCode%20%3D%20Speech%2EVoice%2EcurrentLanguageCode%0A%09%09%09voiceObj%20%3D%20Speech%2EVoice%2EwithLanguage%28langCode%29%0A%09%09%09utterance%20%3D%20new%20Speech%2EUtterance%28textToSpeak%29%0A%09%09%09utterance%2Evoice%20%3D%20voiceObj%0A%09%09%09utterance%2Erate%20%3D%20Speech%2EUtterance%2EdefaultSpeechRate%0A%09%09%09return%20utterance%0A%09%09%7D%0A%09%09var%20synthesizer%20%3D%20new%20Speech%2ESynthesizer%28%29%0A%0A%09%09%2F%2F%20AVAILABLE%20TASKS%20DUE%20TODAY%0A%09%09fmatrStyle%20%3D%20Formatter%2EDate%2EStyle%2EShort%0A%09%09fmatr%20%3D%20Formatter%2EDate%2EwithStyle%28fmatrStyle%29%0A%09%09rangeStart%20%3D%20fmatr%2EdateFromString%28%27today%27%29%0A%09%09rangeEnd%20%3D%20fmatr%2EdateFromString%28%27tomorrow%27%29%0A%09%09tasksToProcess%20%3D%20flattenedTasks%2Efilter%28task%20%3D%3E%20%7B%0A%09%09%09return%20%28%0A%09%09%09%09task%2EeffectiveDueDate%20%3E%3D%20rangeStart%20%26%26%20%0A%09%09%09%09task%2EeffectiveDueDate%20%3C%20rangeEnd%20%26%26%0A%09%09%09%09task%2EtaskStatus%20%3D%3D%3D%20Task%2EStatus%2EDueSoon%0A%09%09%09%29%0A%09%09%7D%29%0A%09%09%0A%09%09if%28tasksToProcess%2Elength%20%3D%3D%3D%200%29%7B%0A%09%09%09throw%20%7B%0A%09%09%09%09name%3A%20%22No%20Items%20Matched%22%2C%0A%09%09%09%09message%3A%20%22No%20tasks%20are%20due%20today%2E%22%0A%09%09%09%7D%0A%09%09%7D%0A%09%09matchCount%20%3D%20tasksToProcess%2Elength%0A%09%09%0A%09%09%2F%2F%20CONSTRUCT%20ARRAY%20OF%20OBJECTS%20FOR%20TASKS%0A%09%09itemsArray%20%3D%20new%20Array%28%29%0A%09%09for%20%28i%20%3D%200%3B%20i%20%3C%20tasksToProcess%2Elength%3B%20i%2B%2B%29%7B%20%0A%09%09%09aTask%20%3D%20tasksToProcess%5Bi%5D%0A%09%09%09%2F%2F%20CONSTRUCT%20OBJECT%20FOR%20TASK%0A%09%09%09itemObj%20%3D%20new%20Object%28%29%0A%09%09%09itemObj%2Etitle%20%3D%20aTask%2Ename%0A%09%09%09itemObj%2Esubtitle%20%3D%20%22DUE%3A%20%22%20%2B%20aTask%2EdueDate%2EtoTimeString%28%29%0A%09%09%09itemObj%2Earg%20%3D%20aTask%2Eid%2EprimaryKey%0A%09%09%09itemsArray%2Epush%28itemObj%29%0A%09%09%7D%0A%09%09%0A%09%09%2F%2F%20CONSTRUCT%20JSON%20OBJECT%20CONTAINING%20ITEM%20ARRAY%0A%09%09jsonObj%20%3D%20new%20Object%28%29%0A%09%09jsonObj%2Eitems%20%3D%20itemsArray%0A%09%09%0A%09%09%2F%2F%20CONVERT%20TO%20STRING%20FOR%20INCLUSION%20IN%20URL%0A%09%09jsonStr%20%3D%20JSON%2Estringify%28jsonObj%29%0A%09%09%0A%09%09%2F%2F%20ENCODE%20FOR%20URL%09%09%0A%09%09encodedJSON%20%3D%20encodeURIComponent%28jsonStr%29%0A%09%09%0A%09%09%2F%2F%20ALFRED%20WORKFLOW%20ID%0A%09%09alfredWorkflowID%20%3D%20%22com%2Eomni%2Dautomation%2Eof%2Ealfred%2Dcollection%22%0A%09%09%0A%09%09%2F%2F%20ALFRED%20ACTION%20TRIGGER%20ID%0A%09%09alfredTriggerVar%20%3D%20%22displayResults%22%0A%09%09%0A%09%09%2F%2F%20CONSTRUCT%20URL%20STRING%0A%09%09urlStr%20%3D%20%60alfred%3A%2F%2Fruntrigger%2F%24%7BalfredWorkflowID%7D%2F%60%0A%09%09urlStr%20%2B%3D%20%60%24%7BalfredTriggerVar%7D%2F%3Fargument%3D%24%7BencodedJSON%7D%60%0A%09%09%0A%09%09%2F%2F%20CONVERT%20TO%20URL%20OBJECT%20AND%20EXECUTE%0A%09%09URL%2EfromString%28urlStr%29%2Eopen%28%29%0A%09%09%0A%09%09%2F%2F%20CONSTRUCT%2FSPEAK%20RESONSE%20WHILE%20ALFRED%20DISPLAYS%20RESULTS%0A%09%09taskOrTasks%20%3D%20%28matchCount%20%3D%3D%3D%201%29%3F%20%22task%22%3A%22tasks%22%0A%09%09response%20%3D%20%60%24%7BmatchCount%7D%20%24%7BtaskOrTasks%7D%20due%20today%2E%60%0A%09%09utterance%20%3D%20createUtterance%28response%29%0A%09%09synthesizer%2EspeakUtterance%28utterance%29%0A%09%7D%0A%09catch%28err%29%7B%0A%09%09utterance%20%3D%20createUtterance%28err%2Emessage%29%0A%09%09synthesizer%2EspeakUtterance%28utterance%29%0A%09%09%2F%2Fnew%20Alert%28err%2Ename%2C%20err%2Emessage%29%2Eshow%28%29%0A%09%7D%0A%7D%29%28%29%3B
Constructing the “Tasks Due Today” Alfred Action
The “Tasks Due Today” action will display a linked list of the available OmniFocus tasks that are due today. It works by executing an Omni Automation script, not requiring user-input, that will filter all available OmniFocus tasks to determine those with a due date of today, and then call a second Alfred action to display the results as a linked list in Alfred.
Optionally, this Alfred action can be called using a Voice Control command: “What is due today?”
NOTE: Both triggers 1—3 are connected to the same “Open URL” step 2 so that either may begin the action.
Keyword Trigger Preferences
Double-click the “Keyword Trigger” step to summon its preferences window:
Open URL Preferences
Double-click the “Open URL” step to summon its preferences window:
Constructing the “Display Results” Alfred Action
Once a script URL has executed and has performed its processing, it will need to have Alfred display any matching items. To do so, the script will call to an action in the Alfred workflow that is designed to be triggered via an external URL that contains an encoded JSON object containing the items to display in the Alfred interface. See this page for an example of the JSON data.
As shown in lines 62, 65, 68, and 69 of the main script, the response URL is constructed like the following with the highlighted placeholders replaced with the relevant information:
alfred://runtrigger/ALFREDWORKFLOWID/ALFREDTRIGGERID/?argument=ENCODEDJSON
Here is an illustration of the Alfred action for displaying results of an OmniFocus query:
Double-click the External Trigger step to summon its preferences window:
alfred://runtrigger/ALFREDWORKFLOWID/ALFREDTRIGGERID/?argument=ENCODEDJSON
Double-click the “Variable Constructor” step to summon its preferences window:
Double-click the “Script Filter” step to summon its preferences window:
Double-click the “Open URL” step to summon its preferences window:
Running the Alfred Command
To use the Tasks Due Today action, summon Alfred and enter the keyword you entered during the workflow setup. The default is: tsk
A list of tasks due will be presented. Navigate the list and press the return key to choose the task to have displayed in OmniFocus for you.
The Voice Control Command
Because of Alfred’s ability to have actions triggered externally via URLs, it works well with Apple’s built-in Voice Control technology.
DOWNLOAD an example voice commands file that you can easily import and use.
In the Accessibility system preference pane 1 select the Voice Control 2 option. From the “Advanced Options” menu 3 select the option to “Import Custom Commands…” menu option and choose the provided voice commands file.
A new voice command will be added:
alfred://runtrigger/com.omni-automation.of.alfred-collection/tasksDue
Next Example
In the NEXT EXAMPLE, Alfred is used to launch an Omni Automation script that requires user input within Alfred in order to perform a procedure in OmniFocus that returns a list of linked objects to Alfred for display.