Connect MarginNote3 to Tinderbox with AppleScript

Just played with the current beta of MarginNote3. I do like MN3 but it has one major drawback: no useful way to extract the data to be used in other apps like Tinderbox or DevonThink. The current beta version of MN3 introduced AppleScript support and I played around with it to get the annotations out of MN3. It’s not finished and I don’t like AppleScript so much (I miss an associative list type and other stuff) but it seems to be possible solution to get the data out of MN3.

# just a sample to extract the annotations of a single notebook within MarginNote3
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property dictKey : "title"
property dictKey2 : "id"

set allBooks to getAllNotesBooks(0)
set allBTitles to getAllTitlesFromBooks(allBooks)
set dialogList to {}

repeat with n in allBTitles
	set dialogList to dialogList & the title of n
end repeat

set theChosenBook to choose from list dialogList with prompt "Select the notebook:"
set allNotes to getAllNotesFromBookWithName(theChosenBook)
set test to true

on getAllTitlesFromBooks(theBooks)
	set titleList to {}
	if the length of theBooks > 0 then
		repeat with n in theBooks
			set singleList to {}
			tell application "MarginNote 3"
				if title of n ≠ missing value then
					set curTitle to title of n
					set curID to id of n
					
					set scr to "on run argList
					    return {|" & dictKey & "|: (item 1 of argList), " & dictKey2 & ": (item 2 of argList)}
					    end run"
					
					set singleList to (run script scr with parameters {curTitle, curID})
					
					copy singleList to the end of the titleList
				end if
			end tell
		end repeat
	end if
	return titleList
end getAllTitlesFromBooks

on getAllNotesFromBookWithName(aNoteBook)
	# get the notes of the first matching notebook
	tell application "MarginNote 3" to activate
	tell application "MarginNote 3"
		set nbk to item 1 of (search notebook aNoteBook)
		set nLst to note ids of nbk
		set nLst to fetch dictionaries nLst
	end tell
	return nLst
end getAllNotesFromBookWithName

on getAllNotesBooks(theType)
	# get all notebooks MN3 knows of
	tell application "MarginNote 3" to activate
	tell application "MarginNote 3"
		#set nbks to (search notebook "" with type DocumentNotebook)
		if theType = 1 then
			#DocumentNotebook
			set nbks to search notebook "" type DocumentNotebook
		else if theType = 2 then
			# CardDeck
			set nbks to search notebook "" type CardDeck
		else if theType = 3 then
			# MindMapNotebook
			set nbks to search notebook "" type MindMapNotebook
		else
			set nbks to search notebook ""
		end if
	end tell
	return nbks
end getAllNotesBooks

The output:
{{title:"Wozu ein Titel?", id:"47B580BC-F1D1-49C2-8DAB-0E7A78E1DF03", notebook:"088EED5B-D2FE-46A6-B4E2-6388C027395A", excerpt text:"Modul G2: Geschichte der Schriftkultur", is mindmap node:false, start page:1, end page:1, start pos:"136.965902,558.762416", end pos:"393.620713,555.655129", color index:0, fill index:-1, comment dictionaries:missing value}, {title:"", id:"68325CEA-B310-42D4-BB3E-E5AFF8DE143B", notebook:"088EED5B-D2FE-46A6-B4E2-6388C027395A", excerpt text:"Gelangt eine entwickelte Schrift von außen in eine orale Gesellschaft, ist sie zuerst nur in begrenzten Bereichen und mit begrenzter Wirkung zu finden. In dieser Übergangszeit werden Schrift und Schreiben oftmals als „Werkzeug von geheimer und magischer Kraft“ gesehen.", is mindmap node:false, start page:3, end page:3, start pos:"73.965705,380.231816", end pos:"242.973411,312.216952", color index:1, fill index:-1, comment dictionaries:missing value},...}

3 Likes

Ya, this is a bit of a mess. You could write a regex stamp to parse this, but I suspect it is a clean/standardized format.

Wow, thanks. I think a few lurkers will be happy! I’d agree that AppleScript isn’t everyone’s cup of tea. But a decent bridge between apps is better than no bridge!

1 Like

It’s just a first step - I was struggling with the data types in AppleScript - but it should be possible to create a much cleaner JSON output. I don’t know at the moment if MN3 registered an URL schema so that it would be possible to open an annotation from Tinderbox in MN3 - that’s the next thing I will try.

For Tinderbox CSV or Tab-delimited is what you need. IIRC, there is no drag/drop JSON parser.

There could be, if it’s needed, provided it doesn’t require evaluation (which proper JSON should not).

You could use this handler

-- Format JSON

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property usePrettyPrint : true
property useSortKeys : true

set theRecord to {{title:"Wozu ein Titel?", |id|:"47B580BC-F1D1-49C2-8DAB-0E7A78E1DF03", notebook:"088EED5B-D2FE-46A6-B4E2-6388C027395A", |excerpt text|:"Modul G2: Geschichte der Schriftkultur", |is mindmap node|:false, |start page|:1, |end page|:1, |start pos|:"136.965902,558.762416", |end pos|:"393.620713,555.655129", |color index|:0, |fill index|:-1, |comment dictionaries|:missing value}, {title:"", |id|:"68325CEA-B310-42D4-BB3E-E5AFF8DE143B", notebook:"088EED5B-D2FE-46A6-B4E2-6388C027395A", |excerpt text|:"Gelangt eine entwickelte Schrift von außen in eine orale Gesellschaft, ist sie zuerst nur in begrenzten Bereichen und mit begrenzter Wirkung zu finden. In dieser Übergangszeit werden Schrift und Schreiben oftmals als „Werkzeug von geheimer und magischer Kraft“ gesehen.", |is mindmap node|:false, |start page|:3, |end page|:3, |start pos|:"73.965705,380.231816", |end pos|:"242.973411,312.216952", |color index|:1, |fill index|:-1, |comment dictionaries|:missing value}}

set theJSON to my formatJSON(theRecord)

on formatJSON(theRecord)
	try
		if not (current application's NSJSONSerialization's isValidJSONObject:theRecord) then error "No valid JSON Object"
		set theJSONOptions to 0
		if usePrettyPrint then set theJSONOptions to theJSONOptions + ((current application's NSJSONWritingPrettyPrinted) as integer)
		if useSortKeys then set theJSONOptions to theJSONOptions + ((current application's NSJSONWritingSortedKeys) as integer)
		set {theJSONData, theError} to (current application's NSJSONSerialization's dataWithJSONObject:theRecord options:theJSONOptions |error|:(reference))
		if theError ≠ missing value then error (theError's localizedDescription() as string)
		set theJSONString to (current application's NSString's alloc()'s initWithData:theJSONData encoding:(current application's NSUTF8StringEncoding))
		return theJSONString as string
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"formatJSON\"" message error_message as warning
		error number -128
	end try
end formatJSON

Output with property usePrettyPrint and useSortKeys set to true:

[
  {
    "color index" : 0,
    "comment dictionaries" : null,
    "end page" : 1,
    "end pos" : "393.620713,555.655129",
    "excerpt text" : "Modul G2: Geschichte der Schriftkultur",
    "fill index" : -1,
    "id" : "47B580BC-F1D1-49C2-8DAB-0E7A78E1DF03",
    "is mindmap node" : false,
    "notebook" : "088EED5B-D2FE-46A6-B4E2-6388C027395A",
    "start page" : 1,
    "start pos" : "136.965902,558.762416",
    "title" : "Wozu ein Titel?"
  },
  {
    "color index" : 1,
    "comment dictionaries" : null,
    "end page" : 3,
    "end pos" : "242.973411,312.216952",
    "excerpt text" : "Gelangt eine entwickelte Schrift von außen in eine orale Gesellschaft, ist sie zuerst nur in begrenzten Bereichen und mit begrenzter Wirkung zu finden. In dieser Übergangszeit werden Schrift und Schreiben oftmals als „Werkzeug von geheimer und magischer Kraft“ gesehen.",
    "fill index" : -1,
    "id" : "68325CEA-B310-42D4-BB3E-E5AFF8DE143B",
    "is mindmap node" : false,
    "notebook" : "088EED5B-D2FE-46A6-B4E2-6388C027395A",
    "start page" : 3,
    "start pos" : "73.965705,380.231816",
    "title" : ""
  }
]

Make sure to include the use statement at the top of your script:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions


@eastgate could you please add AppleScript syntax highlighing to the Discourse software setup?

Seems that currenty the best option (for users) is to overwrite the default by using

```text

at the start of a code block.

In the DEVONthink forum it’s possible to overwrite the default highlighting (which is AppleScript), e.g.

```javascript

I guess it’s only a minor change necessary.

I think a formatted output, CSV or JSON or…, is no big hurdle - the nice sample from @Pete shows a perfect sample. The JSON formatter will not work with the original output since the pipes around the labels are missing. Anyhow - MN3 has an option for the method fetch dictionaries:

set nLst to fetch dictionaries nLst with json

returning this:

{“{"color index":0,"end page":1,"end pos":"393.620713,555.655129","excerpt text":"Modul G2: Geschichte der Schriftkultur","fill index":-1,"id":"47B580BC-F1D1-49C2-8DAB-0E7A78E1DF03","is mindmap node":false,"notebook id":"088EED5B-D2FE-46A6-B4E2-6388C027395A","start page":1,"start pos":"136.965902,558.762416","title":"Wozu ein Titel?"}”,

Don’t know why MN3 add quotation marks around the label…?!

For me it’s important to have a link to an annotation in Tinderbox. The id of the note and the id of the notebook should be sufficient - but I need an URL schema…

If MN3 allows to open an annotation via AppleScript you could probably create an AppleScript app with a custom URL scheme.

See AppleScript: Launching Scripts From Links

1 Like

played around with the AppleScript app - but didn’t figure out how to open a note in MN3 - I can read the data, but found no way to show the selected annotation in MN3 with AppleScript.

But this is the URL schema for MN3:
marginnote3app://note/89705AAE-450B-4FA6-88BD-E81DAA244969

the last part is the ID of the note

You can open it e.g. via

do shell script "open " & quoted form of theURL
1 Like

I just found an interesting plugin which allows direct export from MarginNote to Obsidian. The last step would be to simply copy-paste export results to Tinderbox.

1 Like

yes - another option: export the notes from MN3 to DevonThink and reimport them from there into TBX. AppleScript will do the job too.