Integrating Omnifocus 2 with Devonthink Pro (Rob Trew scripts)

I am so grateful for your work, @unlocked2412. With the help of Keyboard Maestro, I am back to the functionality I enjoyed with Rob’s original scripts using OmniFocus 1.

For posterity and future development, here’s the final script I use, the Keyboard Maestro macros that connect it all, and a (very homespun) video that shows how I use them all together.

  1. AppleScript bundle improved by unlocked2412 with minor tweaks for my system.
  2. Keyboard Maestro macros shown in the video.
  3. Video of how I use the scripts and macros on my system with OmniFocus Pro v2, DEVONthink Pro Office v2, and OmniOutliner Pro v5.
3 Likes

You’re welcome, @TheWart!
You explain really well, so it’s easy to understand what you are trying to do.
One suggestion to your excellent workflow: would you like to open both links with separate shortcuts via Applescript? If you move the window, the KM macro cannot locate the links, I think.
P.S.: I’m intrigued: what the purpose of Script: interactive Inbox?

You’re kind, but things were easy to understand because you made the program do exactly what was needed!

I’d like to learn how to get OF to open either link with AppleScript, and the script would reach a wider audience than a Keyboard Maestro macro. Would it be possible to activate an AppleScript that, when activated from within the tasks of a project, throws a pop-up that allows me to choose between hyperlinks in the host project’s note field?

I’ve tested the KM macro in different window locations because the first mouse click is not dependent upon position of the OF window, but actually looks for the image of hyperlinked text on a white background. This is KM’s secret sauce for me: “Click at found image” does what no other shortcut program can.

You’re right. It’s a clever solution but it is little faster and more reliable with Applescript.
I used “Click at found image” many times. In some cases, it’s the only option.
Yes, it’s possible. This is a starting point. To open DT link:

  • Store in a variable
    • The selected project or
  • The project of the selected task
  • Store in a variable the value of the attribute “link” of the style of the note of the project
  • Open variable contents via system events

Code:

--unlocked2412

tell application "OmniFocus"
	tell front document
		tell front document window
			set lst_values to (value of selected trees of content) whose (class of value = task) or (class of value = project)
			if (count of lst_values) = 0 then
				set lst_values to (value of selected trees of sidebar) whose (class of value = task) or (class of value = project)
				if (count of lst_values) = 0 then
					display notification "Select a project or task"
					return
				end if
			end if
			set the_value to item 1 of lst_values
			if class of the_value = task then
				set the_project to containing project of the_value
			else
				set the_project to the_value
			end if
		end tell
		set note_ref to a reference to (note of the_project)
		tell note_ref
			set dt_link to value of attribute "link" of style of paragraph 1
		end tell
	end tell
end tell

tell application "System Events"
	open location dt_link
end tell

If you need to open OmniOutliner link, you must get de value of attribute “link” of style of paragraph 3.
I leave the code for reference:

Code
tell application "OmniFocus"
	tell front document
		tell front document window
			set lst_values to (value of selected trees of content) whose (class of value = task) or (class of value = project)
			if (count of lst_values) = 0 then
				set lst_values to (value of selected trees of sidebar) whose (class of value = task) or (class of value = project)
				if (count of lst_values) = 0 then
					display notification "Select a project or task"
					return
				end if
			end if
			set the_value to item 1 of lst_values
			if class of the_value = task then
				set the_project to containing project of the_value
			else
				set the_project to the_value
			end if
		end tell
		set note_ref to a reference to (note of the_project)
		tell note_ref
			set oo_link to value of attribute "link" of style of paragraph 3
		end tell
	end tell
end tell

tell application "System Events"
	open location oo_link
end tell

We can do that with a Standard Additions command (Applescript): choose from list.

Or, trigger a Keyboard Maestro palette. What do you prefer?
Tell me if you need some help, @TheWart.

That is very helpful. I see how changing paragraph link to 1 allows the script to open the associated DTPO group. I’ve made two copies of the AppleScript and placed them in my OF toolbar. I’ll load both scripts into my existing Keyboard Maestro palette instead of my Rube Goldberg macros.

Thank you very much!

P.S. You asked earlier about the “Interactive Inbox” script in my toolbar. It reads my OF inbox and prompts for a project and a context to assign each inbox task. It was taken from the work of someone else in the forum. I’ll look for it and link it back here.

use AppleScript version "2.4" -- Yosemite (10.10) or later
    use scripting additions
    use O : script "omnifocus"
    property assign_method : 3

    if assign_method is 1 then
    	assign_project()
    else if assign_method is 2 then
    	assign_context()
    else if assign_method is 3 then
    	assign_project()
    	assign_context()
    end if


    on assign_project()
    	tell application "OmniFocus"
    		tell default document
    			repeat with _task in ((every inbox task) whose (completed) is false)
    				try
    					set nofolder_Projects to (name of (flattened projects where its folder is missing value and its status is active))
    					set folder_Projects to (name of (flattened projects where hidden of its folder is false and its status is active))
    					set projectNames to nofolder_Projects & folder_Projects
    					set _project to O's findProject(choose from list projectNames with title "Interactive Inbox" with prompt "Assign project to: " & name of _task without empty selection allowed)
    					set assigned container of _task to _project
    				on error
    					display notification "No project selected for " & name of _task & "."
    				end try
    			end repeat
    			
    			compact
    			
    			if (count of (inbox tasks whose (completed) is false)) > 1 then
    				display notification (((count of (inbox tasks whose (completed) is false)) as text) & " tasks remain in the inbox.")
    			else if (count of inbox tasks) > 0 then
    				display notification (((count of (inbox tasks whose (completed) is false)) as text) & " task remains in the inbox.")
    			end if
    		end tell
    	end tell
    end assign_project

    on assign_context()
    	tell application "OmniFocus"
    		tell default document
    			repeat with _task in ((every inbox task) whose (completed) is false)
    				try
    					set contextNames to (name of every flattened context whose hidden is false)
    					set _context to O's findContext(choose from list contextNames with title "Interactive Inbox" with prompt "Assign context to: " & name of _task without empty selection allowed)
    					set context of _task to _context
    				on error
    					display notification "No context selected for " & name of _task & "."
    				end try
    			end repeat
    			
    			compact
    			
    			if (count of (inbox tasks whose (completed) is false)) > 1 then
    				display notification (((count of (inbox tasks whose (completed) is false)) as text) & " tasks remain in the inbox.")
    			else if (count of inbox tasks) > 0 then
    				display notification (((count of (inbox tasks whose (completed) is false)) as text) & " task remains in the inbox.")
    			end if
    		end tell
    	end tell
    end assign_context
2 Likes

This is very promising and it would be great if I can make this working on my machine, however I get the following error message:

error “OmniFocus kreeg een fout: Error: near “Support”: syntax error” number 1

It is on this command line probably:

set strNoteXML to do shell script "sqlite3 " & pstrDBPath & space & quoted form of strQuery

I use this DBPath:

property pstrDBPath : "~/Library/Containers/com.omnigroup.OmniFocus2.MacAppStore/Data/Library/Application Support/Omnifocus/OmniFocus.ofocus"

Could you give me a hint where to look for a solution!

I’ll try to give you a hint. Did you bought OmniFocus in the Mac App Store?
If so, try with this line:
property pstrDBPath : "~/Library/Containers/com.omnigroup.OmniFocus2.MacAppStore/Data/Library/Caches/com.omnigroup.OmniFocus2.MacAppStore/OmniFocusDatabase2"

If not:

property pstrDBPath : "$HOME/Library/Containers/com.omnigroup.OmniFocus2/Data/Library/Caches/com.omnigroup.OmniFocus2/OmniFocusDatabase2"

I hope that helps. Let me know if it works.

2 Likes

Thank you very much for your quick reply and answer. It is great to have such supportive people, willing to help!

Your suggestions helped and everything is working now!

2 Likes

That’s fantastic! You’re welcome. I’m glad I could help.

I’m working through adding this to my system, but I’m having trouble trying to get KM to recognise the area of the OF note to activate a switch to either DevonThink or OmniOutliner.

It seems that KM just can’t ‘see’ the text image to be able to activate the click action.

I’ve tried taking my own screenshot and replacing the built-in as well as mucking around with fuzziness, but it’s not working.

Any ideas?

To respond to my own thread to help others; I discovered that I had to recreate the screenshots of the text in KM - I think my font must have been different.

I also went into System Preferences and toggled the reduce transparency setting to prevent colour bleed - although not sure this has any impact, to be honest.

Now, though, with my own screenshot, the text is recognised consistently and the script works.

I used the Rob Trew versions back in OF1 days, and it’s nice to have them back again!

If you like, I am leaving here two scripts I wrote in JXA to open DT link and OO link

Usage:

  • Select a project
  • Run script (or use a KM Execute Javascript Action)

P.S.: In one of the above posts, there is an AppleScript version.

(() => {
	// Data List JS ----------------------------------------------------------

	// bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
	const bindMay = (mb, mf) =>
		mb.nothing ? mb : mf(mb.just);

	// just :: a -> Just a
	const just = x => ({
		nothing: false,
		just: x
	});

	// nothing :: () -> Nothing
	const nothing = (optionalMsg) => ({
		nothing: true,
		msg: optionalMsg
	});

	// OF Functions -----------------------------------
	
	// projectMay :: Window -> Maybe Project
	const projectMay = win => {
		const seln = win.content.selectedTrees.value();
		return seln.length > 0 ?
			(Object.keys(seln[0].properties()).includes('reviewInterval')) ?
			just(seln[0]) : nothing('No projects selected') :
			nothing('No selection')
	}
	
	// linkMay :: OF Project -> URL String
	const linkMay = x => {
		try {
			return {
				nothing: false,
				just: x.note.paragraphs[0].style.attributes['link'].value()
			};
		} catch (e) {
			return {
				nothing: true,
				msg: 'No link in note'
			};
		}
	};

	// groupMay :: URL String -> maybe DT record
	const groupMay = x => {
		const strUUID = x.split('//')[1]
		return dt.getRecordWithUuid(strUUID) == null ?
			nothing('') :
			just(x)
	};

	// MAIN
	const
		ca = Application.currentApplication(),
		sa = (ca.includeStandardAdditions = true, ca),
		dt = Application('DEVONthink Pro'),
		of = Application('OmniFocus'),
		oDoc = of.defaultDocument,
		oWin = oDoc.documentWindows[0];
		
	const mb = bindMay(bindMay(projectMay(oWin), linkMay), groupMay)
	return (mb.nothing) ? mb.msg : ca.openLocation(mb.just)
})();
Open OO Link Code
(() => {
    // Data List JS ----------------------------------------------------------

    // bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    const bindMay = (mb, mf) =>
        mb.nothing ? mb : mf(mb.just);

    // Data List JS ----------------------------------------------------------

    // just :: a -> Just a
    const just = x => ({
        nothing: false,
        just: x
    });

    // nothing :: () -> Nothing
    const nothing = (optionalMsg) => ({
        nothing: true,
        msg: optionalMsg
    });
    // OF Functions -----------------------------------
    
    // projectMay :: Window -> Maybe Project
    const projectMay = win => {
        const seln = win.content.selectedTrees.value();
        return seln.length > 0 ?
            (Object.keys(seln[0].properties()).includes('reviewInterval')) ?
            just(seln[0]) : nothing('No projects selected') :
            nothing('No selection')
    }
    
    // linkMay :: OF Project -> URL String
    const linkMay = x => {
        try {
            return {
                nothing: false,
                just: x.note.paragraphs[2].style.attributes['link'].value()
            };
        } catch (e) {
            return {
                nothing: true,
                msg: 'No link in note'
            };
        }
    };

    // groupMay :: URL String -> maybe DT record
    const groupMay = x => {
        const strUUID = x.split('//')[1]
        return dt.getRecordWithUuid(strUUID) == null ?
            nothing('') :
            just(x)
    };

    // MAIN
    const
        ca = Application.currentApplication(),
        sa = (ca.includeStandardAdditions = true, ca),
        dt = Application('DEVONthink Pro'),
        of = Application('OmniFocus'),
        oDoc = of.defaultDocument,
        oWin = oDoc.documentWindows[0];
        
    const mb = bindMay(bindMay(projectMay(oWin), linkMay), groupMay)
    return (mb.nothing) ? mb.msg : ca.openLocation(mb.just)
})();
2 Likes

I’ve tried using this Interactive Inbox script, but I get an error upon compile

Can't get script "omnifocus"

This is triggered by line 3 of the script.

Can you suggest what I need to do to fix it?

In looking for an answer, I dug some more and found that the Interactive Inbox script is the work of Brandon Pittman. I forgot that he had a “library” script that needed to be placed in an OF directory for the inbox script to work. You can find directions at the link above.

1 Like

Thank you so much; that did the trick!

The script broke down, I do not know why. I get the following error:

/Users/bertkruisdijk/Library/Application Scripts/com.omnigroup.OmniFocus3.MacAppStore/DTPO.scptd: execution error: OmniFocus kreeg een fout: Error: near “Support”: syntax error (1)\

I have tried to find answers/ solutions but not succeeded. Can somebody give me a hint?

Your path contains a space (near “Support”), and what happens is explained here:

Mac Automation Scripting Guide

Quoting Strings
The shell uses space characters to separate parameters and gives special meaning to certain punctuation marks, such as $ , ( , ) , and * . To ensure that strings are treated as expected—for example, spaces aren’t seen as delimiters—it’s best to wrap strings in quotes. This process is known as quoting . If your string contains quotes, they must also be escaped (preceded by a / character) so they are interpreted as part of the string.

set thePath to "/Library/Application Support/"
do shell script "ls " & thePath
--> Result: error "ls: /Library/Application: No such file or directory\rls: Support: No such file or directory" number 1

It’s necessary to properly quote the string in order to run a shell script.

Change this line:

set strNoteXML to do shell script "sqlite3 " & pstrDBPath & space & quoted form of strQuery

to this line of code…

set strNoteXML to do shell script "sqlite3 " & quoted form of pstrDBPath & space & quoted form of strQuery

Thank you very much for this. It solved the problem I was facing. Again thanks for your effort!!

I am now encountering a new problem:

I am using this path:
property pstrDBPath : “~/Library/Containers/com.omnigroup.OmniFocus3.MacAppStore/Data/Library/Application Support/OmniFocus/OmniFocus Caches⁨/OmniFocusDatabase”

Is this not working anymore because the database is encrypted?

You are welcome.

The problem is your path contains a tilde ~ and it does not expand automatically. Use full path, instead.

Tell me if you have any remaining issues.