Invoking an OF script via URL (macOS)

Is it possible to activate a script inside OF via URL on macOS, passing arguments to it?

There is a workaround available:

  • Add an INBOX item in a specific format via omnifocus:///add
  • Make the script running all the time, polling INBOX, and reacting to it, or maybe activate it via AppleScript

But this is a very roundabout way to achieve the result, and it creates useless trasient INBOX items that, given a bad timing, may also get synced to other devices.

1 Like

Installed plug-ins can be called externally. For example, here is the function for calling a specific action by name:

function performPlugInAction(PlugInID, actionName){
	var aPlugin = PlugIn.find(PlugInID)
	if (aPlugin == null){throw new Error("Plugin is not installed.")}
	var actionNames = aPlugin.actions.map(function(action){
		return action.name
	})
	if(actionNames.indexOf(actionName) == -1){
		throw new Error("Action “" + actionName + "” is not in the PlugIn.")
	} else {
		if(aPlugin.action(actionName).validate()){
			aPlugin.action(actionName).perform()
		} else {
			throw new Error("The action “" + actionName + "” is not valid to run.")
		}
	}
}
performPlugInAction("com.omni-automation.plugin-id", "nameOfAction")

This function and call can be encoded into a script URL that can be called from other apps or placed in a webpage:

omnifocus://localhost/omnijs-run?script=function%20performPlugInAction%28PlugInID%2C%20actionName%29%7B%0A%09var%20aPlugin%20%3D%20PlugIn%2Efind%28PlugInID%29%0A%09if%20%28aPlugin%20%3D%3D%20null%29%7Bthrow%20new%20Error%28%22Plugin%20is%20not%20installed%2E%22%29%7D%0A%09var%20actionNames%20%3D%20aPlugin%2Eactions%2Emap%28function%28action%29%7B%0A%09%09return%20action%2Ename%0A%09%7D%29%0A%09if%28actionNames%2EindexOf%28actionName%29%20%3D%3D%20%2D1%29%7B%0A%09%09throw%20new%20Error%28%22Action%20%E2%80%9C%22%20%2B%20actionName%20%2B%20%22%E2%80%9D%20is%20not%20in%20the%20PlugIn%2E%22%29%0A%09%7D%20else%20%7B%0A%09%09if%28aPlugin%2Eaction%28actionName%29%2Evalidate%28%29%29%7B%0A%09%09%09aPlugin%2Eaction%28actionName%29%2Eperform%28%29%0A%09%09%7D%20else%20%7B%0A%09%09%09throw%20new%20Error%28%22The%20action%20%E2%80%9C%22%20%2B%20actionName%20%2B%20%22%E2%80%9D%20is%20not%20valid%20to%20run%2E%22%29%0A%09%09%7D%0A%09%7D%0A%7D%0AperformPlugInAction%28%22com%2Eomni%2Dautomation%2Eplugin%2Did%22%2C%20%22nameOfAction%22%29
2 Likes

IMPORTANT NOTE:
If a plug-in action is designed to process the currently selected items in the app, then it must be adapted to check for the passed selection to be “undefined” and generate the selection object itself. The following example is for OmniOutliner:

	var action = new PlugIn.Action(function(selection){
		// if called externally (from script) generate selection array
		if (typeof selection == 'undefined'){
			// convert nodes into items
			nodes = document.editors[0].selectedNodes
			selectedItems = nodes.map((node) => {return node.object})
		} else {
			selectedItems = selection.items
		}
		// action code …
	});

Thank you, exactly what I need.

Sal, perhaps a little unkind to users to suggest this degree of inconsistency and wordiness ?

Perhaps worth sticking to a consistent pattern and keeping it simple ? How about just:

.map(action => action.name)

?

In your second example, you show a quite different but still oddly wordy pattern:

How about just:

selectedItems = nodes.map(node => node.object)

?

In modelling these things, I think there are probably arguments in favour of:

  • consistency
  • simplicity

( Inconsistent and redundant ink is a cognitive cost – a randomly blotted copy book :-)

PS the other thing which seems a bit unneededly techie/hieratic for casual scripters and beginners is:

if(actionNames.indexOf(actionName) == -1)

perhaps simpler to go to the validate() branch if:

actionNames.includes(actionName)

and to the error if not ?

.includes seems easier to understand (and type) than the more convoluted and mysterious business of looking for index values of ‘-1’ …

Perhaps worth standardizing on that in sample code ?

(Would make people’s lives a bit easier and less puzzled, I think)

I need to confess that I have tried this numerous times with my placeholder template script and it always fails. As in, OmniFocus crashes. I’m guessing it has something to do with opening the FilePicker.

Is it not possible to simply pass the name of the script? Something like this:

omnifocus://localhost/omnijs-run?script=add-template

And then have that run it? It seems like encoding and passing the whole bank of javascript is really messy.

1 Like