Obtaining `x-devonthink-item` from `path`

I need to produce a macOS script that retrieves an imported item’s x-devonthink-item:// link given its filesystem path. My search for a solution in the DEVONthink scripting dictionary was fruitless, but I may have looked too cursorily. Is a solution available?

Background: Every book or article in my biblatex (bibliography) database contains the filesystem path of the respective PDF. These paths point to locations within the DEVONthink database “package” and thus only work on macOS—they are entirely meaningless on the iPad. This is why I would like to replace them with x-devonthink-item:// links.

A JavaScript script could look like this:

const app = Application("DEVONthink") // use "DEVONthink 3" for DT3
const dbName = "MyDatabase"; // change to your database
const db = app.databases[dbName];
const basePath = `${db.path()}/Files.noindex/`;

function getUUIDfromPath(path) {
  const fixedPath = path.startsWith('.') ? basePath + path.replace(/^\.\//,'') : path;
  const records = db.contents.whose({path: fixedPath})();
  return records && records.length >= 1 ? records[0].uuid() : undefined;
}

const uuid = getUUIDfromPath('./pdf/xx/yyy.pdf');
console.log(uuid);

The important part are the lines before function getUUIDfromPath and this function itself, ending with the }.

The function expects a path of the form ./pdf/xx/yyy.pdf or /Users/home/folder…/MyDatabase.dtBase2/files.noindex/pdf.xx/yyy.pdf, and returns the record’s UUID, if it finds the path. Otherwise, it returns undefined.

You must set the value of databaseName to the name of the database containing your PDFs.

1 Like

Here is a quick AppleScript snippet…

tell application id "DNtp"
	if (selected records) is {} then return
	set sel to (selected record 1)
	set theLink to ((ASCII character 92) & "href{" & reference URL of sel & "}{" & (name without extension of sel) & "}" as string)
	set the clipboard to theLink
end tell
--> \href{x-devonthink-item://73847502-CE21-4EF1-A4D9-208645DC9C24}{Screenshot Background Blue}
1 Like

Fantastic! — Grateful for both replies.

1 Like

According to the scripting dictionary, records have a path property:

record n [inh. item] : A database record

  • path (text) : The POSIX file path of a record. Only the path of external records can be changed.

Here is a third approach:

set thePath to -- However you get the path

tell application id "DNtp"
    set DB to current database
    set theRecord to first item of (contents of DB whose path = thePath)
    set theLink to reference URL of theRecord
end tell
1 Like

For any future reference, here is a script that performs the BibDesk-DEVONthink automation described above. Thanks to all contributors to this thread for their tips.

In brief, the script accepts selected BibDesk items as input. For every DEVONthink-imported or -indexed attachment in this collection of items, it adds the attachment’s x-devonthink-item URI in a Devonthink field (or DevonthinkN, if N>1) of the respective BibDesk item.

The script enables me to open these attachments directly from my BibLaTeX bibliography manager, both on the Mac (BibDesk) and the iPad (References app).

Any suggestions for improving the script would be warmly welcome. (It is noticeably slow when numerous bibliography items and/or attachments are involved.)

-- Declare global variable for logging
global logFilePath

-- Set log file path
set logFilePath to POSIX path of (path to desktop folder) & "BibDesk-DEVONthink-log.txt"

-- Function to log messages using shell with timestamp
on logMessage(theText)
	set timeStamp to do shell script "date '+%Y-%m-%d %H:%M:%S'"
	do shell script "echo " & quoted form of ("[" & timeStamp & "] " & theText) & " >> " & quoted form of logFilePath
end logMessage

-- Activate BibDesk
tell application "BibDesk" to activate
delay 0.3

-- Initialize counters
set totalUpdates to 0
set summaryText to ""

-- Access selection from BibDesk
tell application "BibDesk"
	if not (exists document 1) then
		display dialog "No BibDesk document is open." buttons {"OK"} default button "OK"
		return
	end if
	
	set theDoc to document 1
	
	try
		set selectedPubs to selection of theDoc
		if selectedPubs is {} then
			display dialog "No references selected in BibDesk." buttons {"OK"} default button "OK"
			return
		end if
	on error
		display dialog "Could not retrieve selection. Please make sure the main BibDesk window is frontmost and items are selected." buttons {"OK"} default button "OK"
		return
	end try
	
	repeat with thisPub in selectedPubs
		set pubTitle to title of thisPub
		set theAttachments to linked files of thisPub
		set addedCount to 0
		
		repeat with thisFile in theAttachments
			set filePath to POSIX path of thisFile
			set refURL to ""
			set foundInDevon to false
			
			with timeout of 600 seconds
				tell application id "DNtp"
					try
						if current database is missing value then
							my logMessage("❌ No DEVONthink database is open.")
							display dialog "No DEVONthink database is open." buttons {"OK"} default button "OK"
							return
						end if
						
						set theDatabase to current database
						set theMatches to (contents of theDatabase whose path = filePath)
						
						if (count of theMatches) > 0 then
							set theRecord to first item of theMatches
							set refURL to reference URL of theRecord
							set foundInDevon to true
						else
							my logMessage("NOT FOUND: " & filePath & " (BibDesk: " & pubTitle & ")")
						end if
						
					on error errMsg
						my logMessage("ERROR: " & errMsg & " for file " & filePath & " (BibDesk: " & pubTitle & ")")
					end try
				end tell
			end timeout
			
			if foundInDevon then
				tell thisPub
					-- Check if refURL already exists
					set refAlreadyExists to false
					set devonIndex to 1
					
					repeat while devonIndex ≤ 50
						if devonIndex = 1 then
							set fieldName to "Devonthink"
						else
							set fieldName to "Devonthink" & devonIndex
						end if
						
						if exists field fieldName then
							set existingVal to value of field fieldName
							if existingVal is equal to refURL then
								set refAlreadyExists to true
								exit repeat
							end if
						else
							exit repeat
						end if
						
						set devonIndex to devonIndex + 1
					end repeat
					
					-- If not already present, add to first empty field
					if refAlreadyExists is false then
						set devonIndex to 1
						repeat while devonIndex ≤ 50
							if devonIndex = 1 then
								set fieldName to "Devonthink"
							else
								set fieldName to "Devonthink" & devonIndex
							end if
							
							if not (exists field fieldName) then
								make new field with properties {name:fieldName}
								set value of field fieldName to refURL
								set addedCount to addedCount + 1
								exit repeat
							else
								set existingVal to value of field fieldName
								if existingVal is missing value or existingVal is "" then
									set value of field fieldName to refURL
									set addedCount to addedCount + 1
									exit repeat
								end if
							end if
							
							set devonIndex to devonIndex + 1
						end repeat
					end if
				end tell
			end if
		end repeat
		
		if addedCount > 0 then
			set totalUpdates to totalUpdates + addedCount
			set summaryText to summaryText & "• “" & pubTitle & "”: added " & addedCount & " link(s)" & linefeed
		end if
	end repeat
end tell

-- Show summary
if totalUpdates > 0 then
	display dialog "✅ Added " & totalUpdates & " DEVONthink link(s)." buttons {"OK"} default button "OK"
else
	display dialog "No new DEVONthink links were added." buttons {"OK"} default button "OK"
end if