Compatibility with Hook Productivity on Mac OS

No, it’s not done. There are workarounds – the Show in Finder trick and then use Hook from there – but Obsidian would be so enriched by having the full Hook functionality. For example, there is a feature in Hook where you can be on a web page, for example, and then use something called “Hook to New.” That creates a new note which is automatically deep-linked to that website on your computer. Currently, we can Hook to New in apps like DevonThink or BBEdit, and having Hook to New in Obsidian would be amazing.

3 Likes

I am not talking about the show in finder.

We have custom URL, such as:
obsidian://open?vault=Obsidian%20Help&file=Start%20here.md

We also have an API now, so you can write a plugin and access the custom URL automatically.

I am gonna move this to plugin ideas.

4 Likes

Thank you. I will work with the developers at Hook to see if they can build the plug-in and will let you know if there is anything else needed from the Obsidian side.

1 Like

The steps to build a script in Hook for compatability are over at: Creating and Modifying Integration Scripts – Hook. The four parts are:

  • Get Address
  • Get Name (which is now optional if the name is returned as part of the address),
  • Open Item
  • New Item

The recent support for custom URLs in Obsidian should mean not having to do a custom Open Item. I think I saw support for a new item too, but would have to look around more.

The hard one is Get Address (and Get Name). My understanding is that Hook compatibility is done through Applescript, so Hook would need a way to ask Obsidian for the address and name of the currently viewed. I’m not sure if that’s possible for a plugin yet, or would have to happen to some degree in Obsidian itself.

2 Likes

This code could be used from a plugin to copy the current document (it would need some error handling if the editor doesn’t have the focus):

app.commands.executeCommandById('workspace:copy-url')

I still haven’t found a way to call that from Applescript.

I had a breakthrough! And it doesn’t involve any extra code in Obsidian so far!

  • open ~/Library/Application Support/obsidian/obsidian.json
  • look at last_open to see what vault was last viewed, and make note of it
  • look at vaults to find that vault
  • make note of the vault’s path
  • open path/to/vault/.obsidian/workspace
  • look at the first item in lastOpenFiles to get the relative path
  • generate a obsidian with the vault and file path: obsidian://open?vault=<vault id>&file=<relative path>

It doesn’t look like the obsidian URI supports creating new files yet. It could be done by using a similar approach, and then creating a file in the vault’s path.


That said, I got it working :scream: Here are Hook scripts for Obsidian :confetti_ball:

Open Hook -> Preferences -> Scripts -> “+” and then browse select Obsidian

For “Get Name”:

//JavaScript
(() => {
    'use strict';
	
	const main = () => {
		const app = Application.currentApplication()
		app.includeStandardAdditions = true
		
		const homeDirectory = app.pathTo("home folder").toString()

		const obsidianJSONPath = `${homeDirectory}/Library/Application Support/obsidian/obsidian.json`
		const obsidianData = JSON.parse(app.read(Path(obsidianJSONPath)))


		const vaultId = obsidianData["last_open"]
		const vault = obsidianData["vaults"][vaultId]
		const vaultPath = vault["path"]

		const workspaceJSONPath = `${vaultPath}/.obsidian/workspace`
		const workspaceData = JSON.parse(app.read(Path(workspaceJSONPath)))

		const currentDocument = workspaceData["lastOpenFiles"][0]		
		const slashParts = currentDocument.split("/")

		
		const basename = slashParts[slashParts.length - 1]
		if (basename) {
			const title = basename.replace(/\.md$/, '')
			return title
		}
	}
	
	return main();
})()

For “Get Address”:

//JavaScript
(() => {
    'use strict';
	
	const main = () => {
		const app = Application.currentApplication()
		app.includeStandardAdditions = true
		
		const homeDirectory = app.pathTo("home folder").toString()

		const obsidianJSONPath = `${homeDirectory}/Library/Application Support/obsidian/obsidian.json`
		const obsidianData = JSON.parse(app.read(Path(obsidianJSONPath)))


		const vaultId = obsidianData["last_open"]
		const vault = obsidianData["vaults"][vaultId]
		const vaultPath = vault["path"]

		const workspaceJSONPath = `${vaultPath}/.obsidian/workspace`
		const workspaceData = JSON.parse(app.read(Path(workspaceJSONPath)))

		const currentDocument = workspaceData["lastOpenFiles"][0]

		const uri = `obsidian://open?vault=${vaultId}&file=${encodeURIComponent(currentDocument)}`
		return uri;
	}
	
	return main();

})()
8 Likes

This is great and really ties Obsidian into the hook ecosystem nicely. Thanks so much

2 Likes

Excellent! Thanks for this @technicalpickles!

2 Likes

That is so great, @technicalpickles!

I’ve set a Keyboard Maestro Macro to expand the string ;olink into a link in whatever app I’m currently using it.

I was previously using System Events/AppleScript to do so, but it required an Obsidian App activation in order to complete its action.

Your JavaScript runs faster and without the need for activating Obsidian app. Many thanks!

Here’s the screenshot should anybody want to implement this as well

3 Likes

Actually, Hook is agnostic about the language. It can use any language/API that fits the bill.

1 Like

Thanks for passing this on. A dev of ours had been studying the plugin. I’m sure she’ll find the JavaScript very useful.

2 Likes

Thank you for sharing this. May workaround was painful. This is just great. Kudos.

2 Likes

Hey @technicalpickles, once again thanking you for this script.

Also, I think I found a bug: your get name script is struggling to get accented characters (such as ã, á, â…) correctly. See attached image bellow for an example:

Captura de Tela 2020-12-01 às 15.06.46

It should read 2020.2. Supervisões de…

Perhaps this is something easily fixable, perhaps it’s not… I don’t know JavaScript enough to judge it.

Anyway, this is still the best way to extract links to Obsidian notes via Automation

I don’t know how folks usually share and track their Hook scripts, but I pushed mine up to GitHub: technicalpickles/obsidian-hook-scripts

@ldebritto I don’t have any accented characters in notes yet, but I can take a look. Is that screenshot from Hook when you go to link? Does it display correctly in Obsidian?

3 Likes

Actually both scripts (Name and URL) fail on accented characters.

The screenshot from hook is this:
Captura de Tela 2020-12-01 às 16.42.43

On Obsidian, it fails to retrieve the correct note:
Captura de Tela 2020-12-01 às 16.42.28

That also will happen with Emoji on note titles.

I’ll mark your Git repo as watched!

Looked into it a bit, and I think this might be an Obsidian bug? I’m pulling things from .obsidian/workspace (which is a JSON file), and the filename in lastOpenFiles has the bad characters instead of the accents.

It looks like like the metadata cache in ~/Library/Application Support/obsidian/ObsidianCache/<vault id>.json has the correct characters though. I’m not sure if there’s any way to work around it, since that is keyed by the filename, and the last file we know the name on is wrong :thinking:

It looks ok on my lastOpenFiles list. Have a look into this screenshot from the file on BBEdit:

I wonder if there’s something going wrong on the on the parsing to URL format section of the script.

Using AppleScript and two handlers (one for JSON Parsing and other for URL encoding, both from stackoverflow), I’ve managed to make a version that will work following the same @technicalpickles specifications for getting the URL.

As a known but very likely workable limitation, I couldn’t make the script work properly with multiple vaults, and thus there’s a segment still in progress. Somebody with better AppleScript knowledge could possibly make a better job here.

This is the Get URL script:

# GOAL
-- Fetches an Obsidian URI link trough AppleScript on the background

-- Known limitations: currently works only with one vault

# WORKFLOW
-- Step 0: replace selected variables with your own data
-- Step 1A: gets last vault from Obsidian JSON
-- Step 1B: gets last file relative path via parsing of JSON
-- Step 2: encodes the relative path to URL format
-- Step 3: generates obsidian link

# HANDLERS
-- Uses a JSONtoRecord handler from user CJK posted at Stackoverflow (https://stackoverflow.com/a/51623993/13936032)
use framework "Foundation"
use scripting additions
--------------------------------------------------------------------------------
property ca : a reference to current application
property NSData : a reference to ca's NSData
property NSDictionary : a reference to ca's NSDictionary
property NSJSONSerialization : a reference to ca's NSJSONSerialization
property NSString : a reference to ca's NSString
property NSUTF8StringEncoding : a reference to 4
--------------------------------------------------------------------------------
on JSONtoRecord from fp
	local fp
	
	set JSONdata to NSData's dataWithContentsOfFile:fp
	
	set [x, E] to (NSJSONSerialization's ¬
		JSONObjectWithData:JSONdata ¬
			options:0 ¬
			|error|:(reference))
	
	if E ≠ missing value then error E
	
	tell x to if its isKindOfClass:NSDictionary then ¬
		return it as record
	
	x as list
end JSONtoRecord
--------------------------------------------------------------------------------

-- This second handler encodes the URL format without errors
on urlEncode(input)
	tell current application's NSString to set rawUrl to stringWithString_(input)
	set theEncodedURL to rawUrl's stringByAddingPercentEscapesUsingEncoding:4 -- 4 is NSUTF8StringEncoding
	return theEncodedURL as Unicode text
end urlEncode

# START OF THE STEPS
(*
# IN PROGRESS - STEP 1A - Gets lastVault Name and Path data from obsidian.json at home folder
-- Gets the last vault JSON info
set home to the path to home folder
set v to the POSIX path of home & "Library/Application Support/obsidian/obsidian.json"
set JSONVaultRecord to JSONtoRecord from v
set lastVaultID to last_open of JSONVaultRecord
set lastVault to lastVaultID of vaults of JSONVaultRecord

set vaultPath to |path| of lastVault
*)

# STEP 1B - Gets user inputted vault Name and Path
set vaultName to "Obsidian"
set vaultPOSIXPath to "/Users/xxx/Documents/Obsdian/"


# STEP 2 - Gets relative path to lastOpenFile
-- Gets the workspace JSON file
set f to vaultPOSIXPath & ".obsidian/workspace"
set JSONFilesRecord to JSONtoRecord from f
set lastOpenFile to item 1 of lastOpenFiles of JSONFilesRecord


# STEP 3 - Encodes the relative path to a URL-friendly format
set encodedRelativePath to urlEncode(lastOpenFile)


# STEP 4 - Mounts the URI scheme link from variables vaultName and encodedRelativePath
set obsidianURL to "obsidian://open?vault=" & vaultName & "&file=" & encodedRelativePath

return obsidianURL

This is the Get Name script:

# GOAL
-- Fetches an Obsidian URI link trough AppleScript on the background

-- Known limitations: currently works only with one vault

# WORKFLOW
-- Step 0: replace selected variables with your own data
-- Step 1A: gets last vault from Obsidian JSON
-- Step 1B: gets last file relative path via parsing of JSON
-- Step 2: encodes the relative path to URL format
-- Step 3: generates obsidian link

# HANDLERS
-- Uses a JSONtoRecord handler from user CJK posted at Stackoverflow (https://stackoverflow.com/a/51623993/13936032)
use framework "Foundation"
use scripting additions
--------------------------------------------------------------------------------
property ca : a reference to current application
property NSData : a reference to ca's NSData
property NSDictionary : a reference to ca's NSDictionary
property NSJSONSerialization : a reference to ca's NSJSONSerialization
property NSString : a reference to ca's NSString
property NSUTF8StringEncoding : a reference to 4
--------------------------------------------------------------------------------
on JSONtoRecord from fp
	local fp
	
	set JSONdata to NSData's dataWithContentsOfFile:fp
	
	set [x, E] to (NSJSONSerialization's ¬
		JSONObjectWithData:JSONdata ¬
			options:0 ¬
			|error|:(reference))
	
	if E ≠ missing value then error E
	
	tell x to if its isKindOfClass:NSDictionary then ¬
		return it as record
	
	x as list
end JSONtoRecord
--------------------------------------------------------------------------------

-- This second handler encodes the URL format without errors
on urlEncode(input)
	tell current application's NSString to set rawUrl to stringWithString_(input)
	set theEncodedURL to rawUrl's stringByAddingPercentEscapesUsingEncoding:4 -- 4 is NSUTF8StringEncoding
	return theEncodedURL as Unicode text
end urlEncode

(*
# IN PROGRESS STEP 1A - Gets lastVault Name and Path data from obsidian.json at home folder
-- Gets the last vault JSON info
set home to the path to home folder
set v to the POSIX path of home & "Library/Application Support/obsidian/obsidian.json"
set JSONVaultRecord to JSONtoRecord from v
set lastVaultID to last_open of JSONVaultRecord
set lastVault to lastVaultID of vaults of JSONVaultRecord

set vaultPath to |path| of lastVault
*)

# STEP 1B - Gets user inputted vault Name and Path
set vaultName to "Obsidian"
set vaultPOSIXPath to "/Users/xxx/Documents/Obsdian/"


# STEP 2 - Gets relative path to lastOpenFile
-- Gets the workspace JSON file
set f to vaultPOSIXPath & ".obsidian/workspace"
set JSONFilesRecord to JSONtoRecord from f
set lastOpenFile to item 1 of lastOpenFiles of JSONFilesRecord


# STEP 3 - Encodes the relative path to a URL-friendly format
set encodedRelativePath to urlEncode(lastOpenFile)


# STEP 4 - Mounts the URI scheme link from variables vaultName and encodedRelativePath
set obsidianURL to "obsidian://open?vault=" & vaultName & "&file=" & encodedRelativePath

set fileName to name of (info for (vaultPOSIXPath & lastOpenFile))
return fileName```

It’s great to see all this integration happening. We’ve been looking at this at CogSci Apps and looking to incorporate support based on this.

I noticed something about Obsidian’s URL handling (nothing to do with the integration scripts). If I

  1. copy a URL of a file using Obsidian itself.
  2. rename the file in Obsidian itself ,i.e using the Obsidian UI, (not even via Finder),
  3. attempt to use the URL from step 1

result:

Obsidian cannot resolve the URLs.

Looking at the obsidian URL, it does not contain an ID.

Am I missing something?

hook://file/ URLs are robust under file renames, so we will likely, in addition to these scripts, provide alternative scripts that traffic in hook://file/ URLs.

Also, I’ve asked our devs at CogSci Apps to ensure Reveal File in Finder works via Hook in the context of Obsidian.

5 Likes

FYI Hook integration scripts v. 139 now available — Initial support for Obsidian! - Releases - Hook Productivity Forum With thanks to @technicalpickles for the JS and to @lsieverts , @Haberjr and everyone else who’s contributed to the discussion. That’s available via Hook’s in app software update mechanism.

Hook 2.1 ( hopefully coming later today or tomorrow) will also have this integration built in too, along with support for Apple Notes etc.

We’ve also created a dedicated page re Using Hook with Obsidian.

You’ll notice in those docs that we intend to update the integration (e.g., providing Reveal File in Finder , supporting Hook to New > Obsidian document , and giving users the option to use hook://file/ URLs, to the extent the Obsidian API allows.) We look forward to further development of the Obsidian API to automate linking from Obsidian to anything.

8 Likes