Evernoteでノート間リンクを貼るApplescript

[2011.01.21追記] リンクを開こうとするとFinderがときどき[エラー-43]を吐く…何のエラーだろう?

ノート間リンクがないのはEvernoteの大きな欠点の1つだと思う。
ということでEvernoteのノート同士をリンクさせたように見せかける方法 - めざせ!へなちょこ脱出を参考に、擬似的にノート間リンクを実現するApplescriptを組んでみた。

まだあまり使っていないので不具合があるかもしれないけれど公開してみる。Butlerなどでショートカットキーで起動できるように設定しておくと良い。

注意点として、Evernoteのノートをダブルクリックで開いておく必要がある。メインウィンドウでもノートを選択して読むことができるが、その場合にはノートの選択状態がApplescriptで正しく認識されないみたい。
[追記]GUI scriptingを使っている関係で、テキストエリアをフォーカスしておく必要があるかも。つまり、ノート内のPDFとかにフォーカスが奪われていると正しく機能しない。

ランダム文字列の代わりにノートのlocal idを本文先頭に貼るようにした。多分このIDは不変だと思うけど、もし違ったらまともに機能しないことになるので教えて頂けると助かります。リンクを開くときは、デフォルトで~/Library/Caches/Metadata/com.evernote.Evernote/にあるp****.evernoteというファイルをFinderから開いている。リンクには方向性を付けて、開くときに選べるようにしてみた。

以下ソース

  • リンクを貼るApplescript
    • 2つのノートウィンドウを開いておく必要がある
-- Internote Link書式:
-- ***Evernote Link Header***>>>{refTo list}***<<<{refFrom list}***
on run
	tell application "Evernote"
		activate
		if 3 > (count of windows) then
			display dialog "最低2つのノートウィンドウが必要です。" buttons {"OK"} default button 1
		end if
		set w1 to name of window 1
		set note1 to note 1 of window w1
		set lid1 to local id of note1
		set w2 to name of window 2
		set note2 to note 1 of window w2
		set lid2 to local id of note2
		set askDir to display dialog w1 & linefeed & linefeed & w2 buttons {"/\\", "\\/"} default button 2
		set ltor to "\\/" is button returned of askDir
	end tell
	-- idを抽出
	set id1 to last item of split(lid1, "/p")
	set id2 to last item of split(lid2, "/p")
	
	set linkDeclaration to "***Evernote Link Header***"
	
	set str1 to getStr(w1, linkDeclaration)
	set notDeclared1 to "" is str1
	if notDeclared1 then
		set refTo1 to {}
		set refFrom1 to {}
	else
		set refTo1 to getRefTo(str1)
		set refFrom1 to getRefFrom(str1)
	end if
	set str2 to getStr(w2, linkDeclaration)
	set notDeclared2 to "" is str2
	if notDeclared2 then
		set refTo2 to {}
		set refFrom2 to {}
	else
		set refTo2 to getRefTo(str2)
		set refFrom2 to getRefFrom(str2)
	end if
	-- link headerを作成
	if ltor then
		if refTo1 contains id2 then set id2 to {}
		if refFrom2 contains id1 then set id1 to {}
		setHeader(w1, refTo1 & id2, refFrom1, notDeclared1, linkDeclaration)
		setHeader(w2, refTo2, refFrom2 & id1, notDeclared1, linkDeclaration)
	else
		if refFrom1 contains id2 then set id2 to {}
		if refTo2 contains id1 then set id1 to {}
		setHeader(w1, refTo1, refFrom1 & id2, notDeclared2, linkDeclaration)
		setHeader(w2, refTo2 & id1, refFrom2, notDeclared2, linkDeclaration)
	end if
end run

on setHeader(wName, ref1, ref2, notDeclared, declaration)
	set the clipboard to ""
	set str1 to concatp(ref1)
	set str2 to concatp(ref2)
	delay 0.2
	tell application "System Events" to tell process "Evernote"
		tell menu item wName of menu 1 of menu bar item "Window" of menu bar 1
			click
		end tell
		delay 0.2
		tell UI element 1 of scroll area 1 of window wName
			if notDeclared then
				set str to declaration & ">>>" & str1 & "***" & "<<<" & str2 & "***" & (ASCII character 13) & (ASCII character 13)
				set the clipboard to str
				delay 0.2
				keystroke "v" using command down
			else
				set str to declaration & ">>>" & str1 & "***" & "<<<" & str2 & "***"
				set the clipboard to str
				delay 0.2
				keystroke "v" using command down
			end if
		end tell
		delay 0.2
	end tell
end setHeader
on getStr(wName, linkDeclaration)
	set the clipboard to ""
	delay 0.2
	tell application "System Events" to tell process "Evernote"
		tell menu item wName of menu 1 of menu bar item "Window" of menu bar 1
			click
		end tell
		delay 0.2
		tell UI element 1 of scroll area 1 of window wName
			key code 126 using command down -- command-up: 先頭にカーソル移動
			delay 0.2
			key code 124 using {command down, shift down} -- 一行選択
			delay 0.2
			keystroke "c" using command down -- コビー
			delay 0.2
			set theLine to the clipboard
			if theLine contains linkDeclaration then
				-- 先頭行がheaderなら(リンクが定義済みなら)さらに3行選択し、カット
				return theLine
			else
				-- 始めてリンクを設定する場合は、カーソルを先頭へ
				key code 126 using command down
				return ""
			end if
		end tell
	end tell
end getStr

on getRefTo(str)
	set flat to item 3 of split(str, "***")
	if ">>>" is flat then
		return {}
	else
		return rest of split(characters 4 thru end of flat as text, "p")
	end if
end getRefTo
on getRefFrom(str)
	set flat to item 4 of split(str, "***")
	if "<<<" is flat then
		return {}
	else
		return rest of split(characters 4 thru end of flat as text, "p")
	end if
end getRefFrom

on concatp(lst)
	set str to concat(lst, "p")
	if "" is str then
		return ""
	else
		return "p" & str
	end if
end concatp

on split(str, delim)
	--	try
	considering case
		set lastDelim to AppleScript's text item delimiters
		set AppleScript's text item delimiters to delim
		set spl to every text item of str
		set AppleScript's text item delimiters to lastDelim
	end considering
	return spl
	--	on error
	--		return ""
	--	end try
end split
on concat(lst, delim)
	set lastDelim to AppleScript's text item delimiters
	set AppleScript's text item delimiters to delim
	set conc to lst as string
	set AppleScript's text item delimiters to lastDelim
	return conc
end concat
on replace(str, repFrom, repTo)
	set spl to split(str, repFrom)
	if spl is "" then
		return str
	else
		return concat(spl, repTo)
	end if
end replace
-- Internote Link書式:
-- ***Evernote Link Header***>>>{refTo list}***<<<{refFrom list}***
on run
	tell application "Finder"
		set enDir to folder "com.evernote.Evernote" of folder "Metadata" of folder "Caches" of folder "Library" of (path to home folder)
	end tell
	tell application "Evernote"
		activate
		if 2 > (count of windows) then
			display dialog "最低1つのノートウィンドウが必要です。" buttons {"OK"} default button 1
			return
		end if
		set wName to name of window 1
		set askDir to display dialog "どの向きのリンクを開きますか?" buttons {"全部", ">>>", "<<<"}
		set dir to button returned of askDir
		
	end tell
	
	set linkDeclaration to "***Evernote Link Header***"
	
	set str to getLink(wName, linkDeclaration)
	if "" is str then
		display dialog "リンクはありません" buttons {"OK"} default button 1 giving up after 1
	end if
	set refTo to getRefTo(str)
	set refFrom to getRefFrom(str)
	-- open link
	if dir is ">>>" then
		openLink(refTo, enDir)
	else if dir is "<<<" then
		openLink(refFrom, enDir)
	else if dir is "全部" then
		openLink(refTo, enDir)
		openLink(refFrom, enDir)
	end if
end run

on openLink(refList, enDir)
	repeat with id in refList
		tell application "Finder"
			open file ("p" & id & ".evernote") of enDir
		end tell
	end repeat
end openLink

on getLink(wName, linkDeclaration)
	set the clipboard to ""
	delay 0.2
	tell application "System Events" to tell process "Evernote"
		tell menu item wName of menu 1 of menu bar item "Window" of menu bar 1
			click
		end tell
		delay 0.2
		tell UI element 1 of scroll area 1 of window wName
			key code 126 using command down -- command-up: 先頭にカーソル移動
			delay 0.2
			key code 124 using {command down, shift down} -- 一行選択
			delay 0.2
			keystroke "c" using command down -- コビー
			delay 0.2
			set theLine to the clipboard
			if theLine contains linkDeclaration then
				-- 先頭行がheaderなら(リンクが定義済みなら)さらに3行選択し、カット
				return theLine
			else
				-- リンク未設定
				key code 126 using command down
				return ""
			end if
		end tell
	end tell
end getLink

on getRefTo(str)
	set flat to item 3 of split(str, "***")
	if ">>>" is flat then
		return {}
	else
		return rest of split(characters 4 thru end of flat as text, "p")
	end if
end getRefTo
on getRefFrom(str)
	set flat to item 4 of split(str, "***")
	if "<<<" is flat then
		return {}
	else
		return rest of split(characters 4 thru end of flat as text, "p")
	end if
end getRefFrom

on split(str, delim)
	--	try
	considering case
		set lastDelim to AppleScript's text item delimiters
		set AppleScript's text item delimiters to delim
		set spl to every text item of str
		set AppleScript's text item delimiters to lastDelim
	end considering
	return spl
	--	on error
	--		return ""
	--	end try
end split
on concat(lst, delim)
	set lastDelim to AppleScript's text item delimiters
	set AppleScript's text item delimiters to delim
	set conc to lst as string
	set AppleScript's text item delimiters to lastDelim
	return conc
end concat
on replace(str, repFrom, repTo)
	set spl to split(str, repFrom)
	if spl is "" then
		return str
	else
		return concat(spl, repTo)
	end if
end replace