A new Bookends –> DT script with a group per reference

Update: script updated below to fix issues/add features. It now updates existing items in-place, calls on Bookends to apply formats less frequently, and is a bit more robust in other ways.

Update 2: script updated further: it now looks for any updated tags in the DT item and sends them back to Bookends, and doesn’t use any Bookends formats so it’s more self-contained. (It still doesn’t unindex any attachments which have been removed from Bookends or renamed, or e.g. update the group name if the Bookends reference details change. I’m not sure I can be bothered doing either of those as the payoff is low.)

Github gist: https://gist.github.com/lyndondrake/8983fa2c493f0f0911c6810c3b2ff64d

This is a work-in-progress, building on ideas from several others, to pull across info from Bookends into DEVONthink. Each Bookends reference corresponds to a DT group. Within the group, a summary Markdown file is created from a template with a little bit of info in it. Any Bookends attachments for the reference are indexed into the group. I set the Bookends URL to the DT item so that I can click. I migrate any URLs in the URL fields into DT bookmarks (and store the URLS in user20 in Bookends). Updates in Bookends are reflected in DT.

The main reason for creating a group per reference is that I have several thousand annotations in varying formats. I want to have a straightforward way of gathering them under the correct reference. I also want to pull these across into Tinderbox at some point, and having this structure seems like one way to do so. A final benefit is that I set the Alias for the group to the BibTeX citekey, which lets me use WikiLinks in DT on the citekey to switch to the group for the reference in question. Because I also set the URL for the DT group and its items to the Bookends reference, and I have a “Launch URL” button in my toolbar, I can click on a WikiLink for a reference, and then hit the toolbar button to jump to that reference in Bookends.

I also wanted a group in place for Bookends references which don’t have an attachment. In my field (theology) many books are not available in electronic form, and I still want to put notes into DT for those references, and I didn’t want a separate group or structure for non-PDF references.

Still to do:

  • if an attachment is removed from Bookends, or even renamed, I don’t unindex it in DT
  • similarly with URLs
  • and similarly with references

The main question I have is whether there’s a way in Applescript to replace a templated import in-situ (i.e. without disrupting the DT UUID)?

I create the initial summary Markdown file like so:

set theSummaryRecord to import theTemplateFile placeholders theSummaryPlaceholders to theGroup

but of course this creates duplicates. I could delete the earlier DT item and create a new one, but then I’d have a new UUID, and I’d like to preserve these whenever possible. Any suggestions?

to convertDate(textDate)
	if length of textDate is 4 then
		set textDate to "1/1/" & textDate
	end if
	set resultDate to my date textDate
	return resultDate
end convertDate

to surname(textName)
	set r to textName
	set otid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to ","
	set s to text items of textName
	if s is not missing value then
		set r to (item 1 of s) as string
	end if
	set AppleScript's text item delimiters to otid
	return r
end surname

to authorlist(authors)
	set otid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to "

"
	set r to authors as text
	set AppleScript's text item delimiters to otid
	return r
end authorlist

set theTemplateFile to "/Users/lyndon/Library/Application Support/DEVONthink 3/Templates.noindex/Education/Reference LD.md"

tell application id "DNtp"
	set theDatabase to open database "/Users/lyndon/DevonThink/Research.dtBase2"
	set theLocation to create location "/Library"
end tell


tell application "Bookends"
	tell front library window
		set theRefs to selected publication items
		set itemCount to count of theRefs
		if itemCount > 0 then
			set toProcess to display dialog "You have " & itemCount & " items selected. Would you like to process the selected items or the entire library?" buttons {"Cancel", "Selection", "Library"} default button 3
		end if
		if the button returned of toProcess is "Library" then
			set theRefs to publication items of group item "DT"
		else if the button returned of toProcess is "Cancel" then
			return
		end if
		
		repeat with theRef in theRefs
			set {theID, theKey, thePaths, dtUUID, origURLs, modDate, theAuthor, theEditor, theDateText, theTitle, theShortTitle, theAbstract, theDOI, theKeywords, theURL} to {id, citekey, path of attachment items, user15, user20, date modified, authors, editors, publication date string, title, short title, abstract, doi, keyword names, url} of theRef
			if theAuthor = "" then set theAuthor to theEditor
			if theShortTitle = "" then set theShortTitle to theTitle
			set theDate to my convertDate(theDateText)
			set theYear to the year of theDate as string
			set theBookendsURL to ("bookends://sonnysoftware.com/" & theID) as text
			
			repeat 1 times -- dummy loop to allow skipping to next item if not recently modified in Bookends
				tell application id "DNtp"
					set theRecord to get record with uuid dtUUID in theDatabase
					if theRecord is missing value then
						set theFilename to theYear & " " & theShortTitle
						set theAuthor to paragraphs of theAuthor
						if (count of theAuthor) is 1 then
							set s1 to my surname(item 1 of theAuthor)
							set theFilename to s1 & " " & theFilename
						else if (count of theAuthor) is 2 then
							set s1 to my surname(item 1 of theAuthor)
							set s2 to my surname(item 2 of theAuthor)
							set theFilename to s1 & " and " & s2 & " " & theFilename
						else if (count of theAuthor) > 2 then
							set s1 to my surname(item 1 of theAuthor)
							set theFilename to s1 & " et al " & theFilename
						end if
						set theGroup to create location "/Library/" & theFilename
						set origURLs to theURL
					else
						if (modification date of theRecord) > (modDate + (1 * minutes)) then
							set theTags to the tags of theRecord
							set otid to AppleScript's text item delimiters
							set AppleScript's text item delimiters to "
"
							set theTags to theTags as text
							set AppleScript's text item delimiters to otid
							tell application "Bookends" to set the keywords of theRef to theTags
						end if
						if modDate ≤ modification date of theRecord then
							exit repeat -- jump out of dummy loop to next item
						end if
						set theGroup to theRecord
					end if
					
					-- set group metadata and grab reference details
					tell theGroup
						set URL to theBookendsURL
						set aliases to theKey
						set tags to theKeywords
						set custom meta data to {|date|:theDate, doi:theDOI, abstract:theAbstract, citekey:theKey, bookendsid:theID}
						set dtNewUUID to uuid
						set dtLink to reference URL & "?reveal=1"
					end tell
					
					-- update Bookends record
					tell application "Bookends"
						tell theRef
							set user15 to dtNewUUID
							set user20 to origURLs
							set url to dtLink
						end tell
					end tell
					
					-- create summary Markdown file
					set theSummaryName to ("___" & theKey & ".md") as text
					set theAuthorList to my authorlist(theAuthor)
					set theSummaryPlaceholders to {|%reference%|:theTitle, |%authors%|:theAuthorList, |%date%|:theYear, |%citation%|:theKey, |%doi%|:theDOI, |%abstract%|:theAbstract}
					set theTempRecord to import theTemplateFile placeholders theSummaryPlaceholders to theGroup
					set theSummaryRecord to get record at (the location of theTempRecord) & theSummaryName
					if theSummaryRecord is missing value then
						set the name of theTempRecord to theSummaryName
						set theSummaryRecord to theTempRecord
					else
						set theTempContent to the plain text of theTempRecord
						set the plain text of theSummaryRecord to theTempContent
						delete record theTempRecord
					end if
					set URL of theSummaryRecord to theBookendsURL
					set custom meta data of theSummaryRecord to {|date|:theDate, doi:theDOI, abstract:theAbstract, citekey:theKey, bookendsid:theID}
					set tags of theSummaryRecord to theKeywords
					
					
					-- create bookmarks to URLs
					set origURLs to paragraphs of origURLs
					repeat with theOrigURL in origURLs
						set theBookmarkRecord to lookup records with URL theOrigURL
						if theBookmarkRecord is missing value or (count of theBookmarkRecord) is less than 1 then
							create record with {name:theOrigURL, type:bookmark, URL:theOrigURL} in theGroup
						end if
					end repeat
					
					-- index the attachments
					repeat with thePath in thePaths
						set theAttachmentRecord to lookup records with path thePath
						if theAttachmentRecord is missing value or (count of theAttachmentRecord) is less than 1 then
							set theAttachmentRecord to indicate thePath to theGroup
						else
							set theAttachmentRecord to item 1 of theAttachmentRecord
						end if
						set URL of theAttachmentRecord to theBookendsURL
						set custom meta data of theAttachmentRecord to {|date|:theDate, doi:theDOI, abstract:theAbstract, citekey:theKey, bookendsid:theID}
						set tags of theAttachmentRecord to theKeywords
					end repeat
					
				end tell
			end repeat
		end repeat
	end tell
end tell
4 Likes

Credits:

A lot based on this: Autolinking Bookends & DEVONthink records

And then based on zverhope’s script here: How to avoid creating duplicates in a script (& any general tidy up tips)

2 Likes

The main question I have is whether there’s a way in Applescript to replace a templated import in-situ (i.e. without disrupting the DT UUID)?

No. Creating or importing a new file will always generate a new UUID.

I could overwrite the contents easily enough, but I can’t see a way to use a template to do so - it looks like templates are a one-shot item creation thing? In this case the template’s simple enough that I could just do it all in code to create the appropriate Markdown text

Yes, templates are made for creating often-used items in DEVONthink. Modification of files beyond that is the jurisdiction of scripting.

You mean that you created the template with this written in it? This is not clear to me.
Could you perhaps share the template?

Here’s the template:

### %reference%

**Reference:**

* Citekey: %citation%
* [DOI: %doi%](http://dx.doi.org/%doi%)

### Abstract

%abstract%

It would be simple enough to include in code - but cleaner to use the template. I suppose one way I could do it is create a new, temporary item using import command, and then grab the contents of the temporary item and replace the contents of the in-place item in the reference’s group.

1 Like

Thanks! I managed to get it to work and it should be very interesting to me.

Another question, if you don’t mind. Where are you running this script from?
I am getting seemingly random errors from the date conversion process. The way I found to make it work was to remove it, along with all references to theDate. Apparently, it is picking up random bits of text from Bookends when the error gets displayed.

The error seems to happens in this line:

set the year of resultDate to ( text 1 thru 4 of textDate)

I’ve been running it inside Script Debugger. Actually, I might not need that date function any more. I’ll give it a go, and will test it as a standalone script too.

The other thing that has occurred to me is that I might well want to do my tagging inside DT, and then have those changes synced back to Bookends. I don’t think that will be too hard to add, although I don’t want to get too far down the rabbit hole of 2-way syncing. I think it’s close to what I need now.

2 Likes

Watching this with great interest - hoping you get an easy to apply script figured out, as I’m a neophyte in terms of error checking such things :wink:

A question about set custom meta data: I can write:

add custom meta data theDate for "date" to theSummaryRecord

which adds a date to the field on the “Custom”. But there’s no equivalent for set custom meta data. I can try to use the array syntax:

set custom meta data to {date:theDate, doi:theDOI, abstract:theAbstract, referenceyear:theDate, citekey:theKey, bookendsid:theID}

but that throws an error:

DEVONthink 3 got an error: AppleEvent handler failed.

I can set a separate custom meta data field up but I was hoping to use the standard one from the DT preferences. Any suggestions?

Jim @BLUEFROG, could you perhaps rescue us with your applescript wizardry?
Apologies for being a pain in the ass :upside_down_face:

This should work:

set custom meta data of theSummaryRecord to {date:theDate, doi:theDOI, abstract:theAbstract, referenceyear:theDate, citekey:theKey, bookendsid:theID}

I should have said: I already had it in a tell block. I’ve even tried what you suggest outside the tell block but to no avail - same error message. I notice that Script Debugger highlights date which I guess means it has significance other than as a metadata identifier?

You’re right, custom meta data keys which are identical to AppleScript commands/properties have to be escaped, therefore |date|:theDate should work.

Wonderful, that works! Apologies for my ignorance of AppleScript - learning as I go :slight_smile:

Perhaps you could update the original post so that anyone reading this would find the latest version of your script :wink:

2 Likes

Good idea. :slight_smile: Right now, I’m totally lost and, being a user of DT3 + Bookends, I’d love to try the script.

1 Like

So I’ve updated the script in the original post at the top of the thread.

I see that people tend to post things like this in a Github “gist”. Any pointers to how to create such a thing? I have a Github account.

I still want to alter the script to merge DT tag changes back to Bookends, which I can steal from another script so should be straightforward.

At the moment, I call Bookends with a couple of custom formats to get strings to use. My guess is that I can speed things up, as well as removing dependencies on my custom formats, by doing the string processing in AppleScript.

2 Likes

Thanks for updating the script! I’ll test it now.

It’s pretty straightforward. Just log in, click on the plus sign on the upper-right corner and post the code.

1 Like