69: Joe Buhlig and Automating OmniFocus

1 Like

As someone who is somewhat obsessed with automating OmniFocus, I naturally enjoyed this episode!

A couple of small points Iā€™ve picked up on the Omni Slack channel that I thought might be useful to share:


1. Reducing items in share menu
@MacSparky mentioned the issue of an overpopulated ā€˜shareā€™ menu in creating his template script. One potential solution to this is to update the validation function so that it is not available when a task or project is selected, something like this:

action.validate = (selection, sender) => {
    return selection.tasks.length === 0 && selection.projects.length === 0
  }

This way it doesnā€™t show on the share sheet, but can still be run from the Automation menu or via keyboard shortcut. The trade-off with this is that, obviously, if something is selected, it wonā€™t be available! I usually get around this by just clicking somewhere else, but it might be a dealbreaker for some.


2. Running actions automatically (and in the background) using Keyboard Maestro
There was also some discussion about running actions automatically, or on a schedule. Previously I had been doing this using a Keyboard Maestro action that called a URL (as I think was discussed on the show) but the downside to this was that it drew focus to the OmniFocus each time. Just in the last couple of days Ken Case pointed out in the Slack channel that a script can be run via AppleScript, one benefit of which is that this can happen in the background which is much nicer. So now my Keyboard Maestro macros are just a single ā€˜Execute AppleScriptā€™ action that looks something like:

tell application "OmniFocus"
	evaluate javascript "PlugIn.find('com.KaitlinSalzke.Scheduling').action('newDay').perform()"
end tell

(As an aside, I was amused to hear the comment about how everyone had their own Template script. I also have one in progress :woman_facepalming: )

2 Likes

This is actually the original method behind my Auto-Parser script. It was all AppleScript so it could be run with Hazel or a handful of other automation tools. The trick is mimicking that feature on iOS. :thinking:

This is the problem with templating. Everyone wants it in a different way. :joy:

1 Like

Iā€™m in bed waiting for the baby to wake up and decide to feed, so I havenā€™t tested, butā€¦in the absence of a better option, I wonder whether an ā€œOn App Openā€ trigger in Shortcuts that runs the script via a URL might work somewhat effectively for the parsing script? (It might jump around a bit, though, which might be annoying. Iā€™m not sure.)

Enjoyed the episode. Any ideas on best way to create a shortcut to run a omnifocus plug-in that i can keep in a widget on my home screen.

Great episode, have really enjoyed playing with the JS automation in OmniFocus. For anyone who missed it I wrote a plugin that starts task timers in Toggl, it shows how you can use this to interact with other services out there to get the perfect setup your heart desires. (and avoid doing real work :grinning_face_with_smiling_eyes: )

1 Like

In the episode all three folks discussed using templates to automate creation of Projects in OmniFocus. Iā€™ve been doing this with Drafts and would like to move it to OmniFocus Automation.

My question is how do I point the automation at a specific Taskpaper file rather than use a FilePicker object?

It would. Thatā€™s the trouble. Iā€™ve not found any scripts on iOS that truly run in the background.

For the sake of keeping everything together:

Iā€™m pretty sure you can bypass the FilePicker, but itā€™ll be messy and platform dependent. The file system differences will change where it lives.

For example, using Omni JS on my Mac, a FilePicker url looks like this:
file:///Users/joebuhlig/Documents/omnifocus/templates/article.txt

That same file, but on iOS:
file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Documents/omnifocus/templates/article.txt

Regardless, you should be able to change line 20.

Original: var fileURL = urlsArray[0]

Replace with: URL.fromString("file:///Users/joebuhlig/Documents/omnifocus/templates/article.txt");

Iā€™m pretty sure you can bypass the FilePicker, but itā€™ll be messy and platform dependent. The file system differences will change where it lives.

For example, using Omni JS on my Mac, a FilePicker url looks like this:
file:///Users/joebuhlig/Documents/omnifocus/templates/article.txt

That same file, but on iOS:
file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Documents/omnifocus/templates/article.txt

Regardless, you should be able to change line 20.

Original: var fileURL = urlsArray[0]

Replace with: URL.fromString("file:///Users/joebuhlig/Documents/omnifocus/templates/article.txt");

Thanks, @joebuhlig !

Here is my code:

/*{
    "author": "Dean Pribetic",
    "targets": ["omnifocus"],
    "type": "action",
    "identifier": "co.findmethod.of-new--engagement",
    "version": "0.1",
    "description": "A plug-in that...",
    "label": "New Engagement",
    "mediumLabel": "of-new--engagement",
    "paletteLabel": "of-new--engagement",
}*/
(() => {
    var action = new PlugIn.Action(function(selection) {
    	var templateURL = URL.fromString("file:///Users/dean/Downloads/testFile.txt");
    	templateURL.fetch(data => {
			console.log("Run!")
			/*
    		var TaskPaperText = data.toString();
    		TaskPaperText = encodeURIcomponent(TaskPaperText);
    		var ofURL = "omnifocus:///paste?content=" + TaskPaperText;
    		URL.fromString(ofURL).open();
            */    
        });
    });

    // If needed, uncomment, and add a function that returns true if the current selection is appropriate for the action.
    
    action.validate = function(selection, sender){
		return true;
    };
    
        
    return action;
})();

When I trigger the automation it never gets to the console.log("Run!") commandā€¦which suggests itā€™s not fetching the data.

In this case, you need to act on the Promise that is returned. Like so:

/*{
    "author": "Dean Pribetic",
    "targets": ["omnifocus"],
    "type": "action",
    "identifier": "co.findmethod.of-new--engagement",
    "version": "0.1",
    "description": "A plug-in that...",
    "label": "New Engagement",
    "mediumLabel": "of-new--engagement",
    "paletteLabel": "of-new--engagement",
}*/
(() => {
    var action = new PlugIn.Action(function(selection) {
      var request = URL.FetchRequest.fromString("file:///Users/joebuhlig/Documents/omnifocus/templates/article.txt");
      var templateFetch = request.fetch().then(function(data){
      console.log("Run!")
      /*
        var TaskPaperText = data.toString();
        TaskPaperText = encodeURIcomponent(TaskPaperText);
        var ofURL = "omnifocus:///paste?content=" + TaskPaperText;
        URL.fromString(ofURL).open();
            */
        });
      templateFetch.catch(error => {
        console.log(error);
      })
    });

    // If needed, uncomment, and add a function that returns true if the current selection is appropriate for the action.

    action.validate = function(selection, sender){
    return true;
    };


    return action;
})();

The trouble here becomes a permissions issueā€¦ I think. It gives me an error kCFErrorDomainCFNetwork error 1 Havenā€™t found the answer to that, but it seems to be grabbing the file correctly. Just not able to open it.

Hey @joebuhlig ā€“ thanks for that. Iā€™m getting the same thing with the errorā€¦but it is definitely progress! Iā€™ve looked up that error and most of the solutions about Safari and local storage.

The next thing Iā€™m going to try is to put the templates into a Plug-In file and see if that gets me anywhere (it would also solve the iOS vs macOS issue).

Hi,
Iā€™m trying to add in another line to this wonderful javascript plugin ā€œComplete and Await Replyā€ that will add a tag to the task ā€œwaitingā€. I canā€™t seem to figure out how to do thisā€¦ sigh.

Can someone help?

Hereā€™s the code of he plugin

/*{
	"author": "Rosemary Orchard",
	"targets": ["omnifocus"],
	"type": "action",
	"identifier": "com.rosemaryorchard.omnifocus.complete-and-await-reply",
	"version": "1.0",
	"description": "Mark the currently selected task as complete and add a new task to await the reply.",
	"label": "Complete and Await Reply",
	"mediumLabel": "Complete and Await Reply",
	"paletteLabel": "Complete and Await Reply",
}*/
(() => {
	let action = new PlugIn.Action(function(selection) {
		let duplicatedTasks = new Array()
		selection.tasks.forEach(function(task){
			insertionLocation = task.containingProject
			if(insertionLocation === null){insertionLocation = inbox.ending}
			dupTasks = duplicateTasks([task], insertionLocation)
			dupTasks[0].name = "Waiting on reply: " + task.name;
			duplicatedTasks.push(dupTasks[0].id.primaryKey);
			task.markComplete();
		});
		idStr = duplicatedTasks.join(",")
		URL.fromString("omnifocus:///task/" + idStr).open()
    });

    
	action.validate = function(selection){
		return (selection.tasks.length >= 1)
	};
        
	return action;
})();

As per the install page:

This script does not add a waiting tag to the new task, but that could be added after line 19 with dupTasks[0].addTag(tagNamed('Waiting On')); (this assumes your tag is called ā€œWaiting Onā€ placed at the top-level of tags).

1 Like

Thanks very much! I had installed it a while ago and hadnā€™t seen that helpful note. So thanks both for the note and the helpful pointer!

Hey @joebuhlig. A quick update. I went down the plug-in path and it was the right choice. I can get the URL of the file via something like this (from Omni Automation)

var plugin = PlugIn.find("com.omni-automation.og.example-plug-in")
var resourceName = "fileName.taskpaper"
var aURL = plugin.resourceNamed(resourceName)

ā€¦and then use the callback aUrl.fetch(function(data) {}); construct.

Thanks for all your help with this.

1 Like