Javascript: Get Custom Metadata to rename file

Hi together,

How can I get information out of a custom metadata field by using JavaScript? I need that data to rename my file. Renaming is no problem, but I don’t know how to get the needed custom metadata and I search a lot. Is there any API description or similar about JavaScript and devonthink?

regards,
Denis

Welcome @denis90

You can check out the dictionary in Script Editor’s File > Open Dictionary, selecting DEVONthink, and setting the dictionary to JavaScript.

I would search for posts by @chrillek as he exclusively uses JavaScript with DEVONthink. He also has a really nice Intro to JavaScript for Automation he posted outside the forums.

2 Likes

Hi @BLUEFROG

thanks for your prompt answer. I took directly a look into the Dictionary, thanks for that hint and on some posts of @chrillek and on the linked page. But I don’t get it running.

I tried various formulations and I always get errors. Maybe you have an additional hint for me?

function performsmartrule(records) {
	var app = Application("DEVONthink 3");
	app.includeStandardAdditions = true;

	records.forEach (r => {
	//r.name = app.getCustomMetaData({for:"purpose", from:r}).plainText;
	r.name = r.customMetaData['purpose'].plainText;
	})
}

I always get "Error: Error: Can’t convert types.)

You’re welcome!

First, I would suggest running the scipt without the smart rule function first.
Work it out in Script Editor then add it as a smart rule when it’s working as expected.

Try this…

var app = Application("DEVONthink 3");
app.includeStandardAdditions = true;
	records = app.selectedRecords();
		records.forEach (r => {
			n = app.getCustomMetaData({for:"decimal", from:r});
			app.displayDialog(n);
 	}
 )

1 Like

Thanks for the hint. Didn’t know that I can do that in the editor too.
Can’t select “tell current application”. But nevertheless, I typed in your example with my field:

var app = Application("DEVONthink 3");
app.includeStandardAdditions = true;
	records = app.selectedRecords();
		records.forEach (r => {
			n = app.getCustomMetaData({for:"company", from:r});
			app.displayDialog(n)
		}
	)

That is working well and shows me the information, but as soon as I put it into the smart rule function it does not work any more and give the same error again “on performSmartRule(Error: Error: Can’t convert types.)”

function performsmartrule(records) {
	var app = Application("DEVONthink 3");
	app.includeStandardAdditions = true;

	records.forEach (r => {
			n = app.getCustomMetaData({for:"company", from:r});
			app.displayDialog(n)
	})
}

First: afaik you can’t access custom meta data that way. There’s a function to get the data.

Second: there’s no plainText property in this context

Third: if it were, you couldn’t access it like that. Please read again (?) Working with Objects in Scripting with JXA | JavaScript for Automation (JXA). It’s all explained there.

Fourth: I’m not sure yet why the function would not work in a smart rule. Give me some time, please.

1 Like

Yes you are right, was initial failures. But like you see, I got it running.
I can rename out of the script editor like I want to, but as soon as I use it in a smart rule I get these conversion error. Does not make any sense for me.

Neither does it for me. In fact, I’d say that something in DT is broken here. The below script works just fine in a smart rule, though. Which hints even more at some kind of brokenness in DT.

function performsmartrule(records) {
	const app = Application("DEVONthink 3");
	app.includeStandardAdditions = true;

	records.forEach (r => {
  	  const n = app.getCustomMetaData({for:"company", from:r});
	  app.displayDialog(n.toString())
	})
}

(() => {
  const app = Application("DEVONthink 3");
  performsmartrule(app.selectedRecords());
})()

As you can see, I added a self-executing anonymous function to the end of the original script code (starting at (()=> {. With this piece of code (that should be completely useless in a smart rule!), it runs fine in Script Editor and in a smart rule. Without it (i.e. if you comment out the self-executing anonymous function), it throws an error.

@cgrunenberg might want to chime in on this. In my opinion, your original code is just fine. And it should of course work identically in a smart rule and outside of it.

2 Likes

I also tried this…

const n = app.getCustomMetaData(undefined, {for:'company', from:r});

…but this just causes another error now (Error: Named parameters must be passed as an object.). However, this revision definitely works:

function performsmartrule(records) {
	const app = Application("DEVONthink 3");
	app.includeStandardAdditions = true;

	records.forEach (r => {
      const m = r.customMetaData();
      app.displayDialog(m['mdcompany']);
	})
}

(() => {
  const app = Application("DEVONthink 3");
  performsmartrule(app.selectedRecords());
})()
1 Like

Thanks. That seems to indicate that getCustomMetaData is not quite up to par. But we have a workaround.

1 Like

It’s actually not even called by the JXA bridge in case of smart rules. Definitely weird. In the Script Editor this revision seems to work with and without defined custom metadata:

function performsmartrule(records) {
	const app = Application("DEVONthink 3");
	app.includeStandardAdditions = true;

	records.forEach (r => {
  	  const n = app.getCustomMetaData({for:"company", from:r, defaultValue:""});
	  app.displayDialog(n.toString())
	})
}

(() => {
  const app = Application("DEVONthink 3");
  performsmartrule(app.selectedRecords());
})()
1 Like

You people should just use AppleScript :smiling_imp: There’s a global shortage of parenthesis and here you are throwing them about willy nilly :crazy_face:

5 Likes

Thanks to all of you. But using the self-executing anonymous function means it doesn’t work for the folder by default like configured in the smart rule, it only works if I select the entities.

@Blanc I would love to use apple script, but I don’t really get the syntax. It’s confusing for me. I was a developer for c# .NET with expertise also in JavaScript, PHP and so on, because of this, I know the JavaScript syntax much better.
But maybe I will try it with AppleScript again, if JavaScript doesn’t work 100%.

1 Like

Although it does work if one adds the anonymous self-executing function after the smart rule function performsmartrule. Even funnier: This self-executing function actually gets executed inside the smart rule. Try this:

function performsmartrule(records) {
	const app = Application("DEVONthink 3");
	records.forEach (r => {
			const n = app.getCustomMetaData({for:"amount", from:r});
			app.logMessage(n)
	})
}

(() => {
const app = Application("DEVONthink 3");
app.logMessage('anonymous');
performsmartrule(app.selectedRecords());
})()

Here’s the output in DT’s protocol window:

As you can see, the anonymous function is executed first. That should not happen at all, in my opinion: the smart rule is not supposed to execute the JS code but only to call performsmartrule.

The anonymous function then calls performsmartrule passing it the selectedRecords() and everything is fine (note that in my case I used a different metadata field, but that is not important here).

Only after all that performsmartrule is called by the smart rule and fails probably at the n.toString() call.

One final thing: If I use app.selectedRecords() inside performsmartrule (instead of the records parameter passed into it) , everything is just honky-dory. No error, correct output. I ran the smart rule from the context menu for one single selected record in all these cases.

So I see two issues here:

  1. the smart rule calls the anonymous self-executing function which it shouldn’t do.
  2. there seems to be a difference between the records parameter passed into performsmartrule from DT and the selectedRecords() list although they should (at least in this case) be exactly the same.

HTH

in this one case, AppleScript is almost as brief as JS; the basic format would be:

on performSmartRule(theRecords)
	tell application id "DNtp"
		repeat with theRecord in theRecords
			set md to custom meta data of theRecord
			set theName to mdcompany of md # use mdNameOfYourMetaData instead of mdcompany
			set name of theRecord to theName
		end repeat
	end tell
end performSmartRule

This presumes your smart rule only directs records which actually contain said metadata to the script; otherwise the script will fail. If you actually choose to use it, let me know - then it should at least contain an error routine. Also, if you were going to be using it on many many files at a time, a routine for the progress indicator could be added.

Note that any changes made by script cannot be undone.

Nope. At least not in theory. The self-executing function is actually there so that you can run the same script in Script Editor and in DT’s smart rule. The smart rule should call performsmartrule only, which gets passed the records selected by the smart rule’s conditions. In Script Editor, you must select record(s) which then are passed from the anonymous function to performsmartrule. Well, all this works in theory. Right now it seems that DT is running the anonymous function first and performsmartrule second, so that the letter gets in fact called twice.

Rant follows
@Blanc: Right, of course. It is definitely a lot more fun to use AS given its obscure syntax combined with a lack of functionality and reasonable error handling.

Exactly my point: You have to know that “custom meta data” is an attribute (or property or whatever you want to call it in AppleScript). That’s what I call “obscure syntax” - nobody can see if perhaps custom might be a modifier that is applied to meta data.

Similarly with the word “Record” in DT vs. AppleScript: In DT, “Record” is one of the basic objects in a database. In AS, “Record” is a key-value datastructure. And what might the custom meta data property of a DT Record be? A “Record”, again. Albeit an AS one, so a key-value data structure. Any relevant programming language would have prevented DT from repurposing a keyword for their own structures. How things stand, you have no chance to see immediately even the type of an object/attribute/property. It is a bloody mess. But at least AS is not exhausting our supply of parenthesis :wink:

What I really don’t get: Apple had no problems at all to drop old hardware regularly (think FireWire, SCSI, parallel interfaces, all that). But dropping a language that is even worse than Basic – no way. They’re not even trying to build a viable, modern alternative to it. Just letting the old horse stumble along.

1 Like

I’ll deal with it as soon as I have done with the even more pressing issues.
Thanks for your rant :smiley: I’ll learn JS one day, and I will then be enlightened :innocent:

1 Like

As an elderly equestrian I like old horses. :grinning:

Stephen

2 Likes

Indeed the naming wasn’t the greatest one 16 years ago :wink: but it’s not possible to change this now without breaking all the existing scripts and JXA was definitely not on the horizon either.

BTW: Isn’t JXA a mess on its own? :slight_smile:

2 Likes

Actually smart rules are always executed the same way, only the function is directly called by DEVONthink. But I just compared the output of these AppleScript and JXA scripts:

on performSmartRule(theRecords)
	tell application id "DNtp"
		log message "perform"
		repeat with theRecord in theRecords
			log message (name of theRecord as string)
		end repeat
	end tell
end performSmartRule

on run
	tell application id "DNtp" to log message "run"
end run
function performsmartrule(records) {
	const app = Application("DEVONthink 3");
	app.logMessage('perform');
	records.forEach (r => {
			app.logMessage(r.name())
	})
}

(() => {
const app = Application("DEVONthink 3");
app.logMessage('anonymous');
})()

The first one always works as expected, run is never logged. The second one logs anonymous the first time (!) it’s executed but not anymore afterwards (as compiled scripts are internally cached to speed up smart rules).

Therefore the anonymous function seems to be only called the first time a JXA script is executed after compiling. Due to the lack of JXA documentation it’s hard to tell whether that’s intended or not.