Error when perform smart rule to apply script in Devonthink 4

I use javascript in smart rule to covert pdf to markdown through an api request. It works in DEVONthink 3, but it shows (Error: Error: A privilege violation occurred) in my another Mac using Devonthink 4. I also allow DEVONthink to access all my files and folders. Could you please help me to figure out how to solve the error?


my version:

Show your code, please.

And run your code in script editor with AppleEvent tab enabled to see where it fails.

谢谢

below is the javascript:

// JXA Script for DEVONthink Smart Rule: Convert PDF to Markdown via TextIn API
// (console.error replaced with console.log)

function performsmartrule(records) { // Using the function name from the template
	const app = Application("DEVONthink 3");
	app.includeStandardAdditions = true; // Needed for doShellScript

	// --- Configuration (from AppleScript properties) ---
	const textinAppId = "";
	const textinSecretCode = "";
	const apiUrl = "https://api.textin.com/ai/service/v1/pdf_to_markdown?page_details=0&markdown_details=0&get_image=objects&image_output_type=base64str";
	// --- End Configuration ---

	// Helper function to safely quote strings for shell commands (specifically file paths)
	function quotePath(str) {
		// Basic quoting for paths; might need refinement for extreme edge cases
        if (!str) return "''";
		return "'" + str.replace(/'/g, "'\\''") + "'";
	}

	let successCount = 0;
	let errorCount = 0;
	let skippedCount = 0;

	// Iterate through the records provided by the Smart Rule using the template's forEach
	records.forEach(r => {
		let recordName = "(Unknown Record)"; // Default name for error logging if needed
		try {
			recordName = r.name(); // Get name early for logging
			const recordPath = r.path();
            if (!recordPath) { // Check path *after* getting name if possible
                console.log(`Skipping record without path: ${recordName}`);
				skippedCount++;
				return; // continue in forEach is 'return'
            }
			const recordTags = r.tags(); // Get tags

			// 2. Construct the curl command
			const quotedFilePath = quotePath(recordPath);
			const quotedApiUrl = quotePath(apiUrl);

			const curlCommand = `curl -X POST ${quotedApiUrl} \\
        -H 'x-ti-app-id: ${textinAppId}' \\
        -H 'x-ti-secret-code: ${textinSecretCode}' \\
        -H 'Content-Type: application/octet-stream' \\
        --data-binary @${quotedFilePath} \\
        --silent --show-error --connect-timeout 60 --max-time 300`;

			// 3. Execute the curl command
			const apiResponse = app.doShellScript(curlCommand);

			// 4. Parse the JSON response directly
			let markdownContent = "";
			let apiErrorMsg = "";
			let apiNoteMsg = "";

			try {
				// Basic check if response seems empty before parsing
                if (!apiResponse || apiResponse.trim() === "") {
                    throw new Error("API returned empty response.");
                }
				const jsonResponse = JSON.parse(apiResponse);

				if (jsonResponse.code && jsonResponse.code !== 200) {
					apiErrorMsg = `API Error: Code ${jsonResponse.code}, Message: ${jsonResponse.message || 'No message provided'}`;
				} else if (jsonResponse.result && jsonResponse.result.markdown !== undefined && jsonResponse.result.markdown !== null) {
					markdownContent = jsonResponse.result.markdown;
				} else if (jsonResponse.result && jsonResponse.result.valid_page_number === 0) {
					apiNoteMsg = "API Note: Engine processed 0 pages (empty file or format issue?).";
				} else if (jsonResponse.result) {
					apiErrorMsg = "API Error: Response missing 'markdown' field in result.";
				} else {
					apiErrorMsg = "API Error: Cannot find 'result' field in response.";
				}
			} catch (e) {
				apiErrorMsg = `JSON Parsing Error: ${e.message}. Response (start): ${apiResponse.substring(0, 200)}`;
			}

			// 5. Handle errors or notes from API processing
			if (apiErrorMsg) {
				// *** REPLACED console.error with console.log ***
				console.log(`ERROR: Error processing ${recordName}: ${apiErrorMsg}`);
				errorCount++;
				return; // continue
			}

			if (apiNoteMsg) {
				console.log(`Note for ${recordName}: ${apiNoteMsg}`);
				skippedCount++;
				return; // continue (Skipping creation for 0-page notes)
			}

			// 6. Create the new Markdown record if content was found
			if (markdownContent) {
				try {
					let baseName = recordName;
					if (baseName.toLowerCase().endsWith(".pdf")) {
						baseName = baseName.substring(0, baseName.length - 4);
					}
					const newRecordName = `${baseName} (Markdown)`;

					const parentGroups = r.parents();
					if (!parentGroups || parentGroups.length === 0) {
                        // *** REPLACED console.error with console.log ***
						console.log(`ERROR: Could not find parent group for record: ${recordName}. Cannot create Markdown note.`);
						errorCount++;
						return; // continue
					}
					const parentGroup = parentGroups[0];

					const newRecord = app.createRecordWith({
						name: newRecordName,
						type: 'markdown',
						content: markdownContent,
						tags: recordTags
					}, { in: parentGroup });

					console.log(`Successfully created Markdown note: ${newRecordName} in group '${parentGroup.name()}'`);
					successCount++;

				} catch (e) {
                    // *** REPLACED console.error with console.log ***
					console.log(`ERROR: Failed to create Markdown record for ${recordName}: ${e}`);
					errorCount++;
				}
			} else {
				console.log(`No markdown content found for ${recordName}, but no specific error/note was flagged.`);
				skippedCount++;
			}

		} catch (e) {
			// Catch errors during the processing of a single record (e.g., doShellScript fails)
            // *** REPLACED console.error with console.log ***
			console.log(`ERROR: General error processing record ${recordName}: ${e}`);
			errorCount++;
		}
	}); // End of records.forEach

	// 7. Final Summary Log
	console.log(`Processing complete. Success: ${successCount}, Failed: ${errorCount}, Skipped: ${skippedCount}`);

	// Optional: Display a final notification
	if (successCount > 0 || errorCount > 0 || skippedCount > 0) {
		app.displayNotification(`TextIn conversion finished.`, {
			withTitle: "Markdown Conversion",
			subtitle: `Success: ${successCount}, Failed: ${errorCount}, Skipped: ${skippedCount}`
		});
	}
} // End of performsmartrule function

Does DEVONthink have its Automation requests allowed in System Settings > Privacy & Security?

1 Like

In addition to the question asked by @BLUEFROG:

  • your quotePath function should use replaceAll instead of replace.
  • it would be more economical to build quotedApiUrl outside of the loop. And you really don’t need to quote that one, as it is not a path, but a URL.
  • If you’re running this script inside DT, you might want to use app.logMessage instead of console.log. The output of console.log will not be visible anywhere if the script is running from DT.
  • Instead of your baseName calculations, you could use nameWithoutExtension from DT
  • Checking if a record’s parent exists seems a bit excessive. Instead of all that, you could use the locationGroup property of the record
  • I think that you’re overdoing it a bit with the error checking – some checks (like “does this record have a path” or “does this record have a parent”) will probably never fail unless your database is really, really broken.
1 Like

yes, it is allowed in Automation

Can you try

  • selecting one or more records that you want to process with your smart rule
  • copy/paste the code below (augmented with your code) into Script Editor
  • Type Cmd-3 in Script Editor to open the protocol window
  • Run the code in Script Editor

you should be able to see where the error arises in the protocol window.

const r = Application("DEVONthink").selectedRecords();
performsmartrule(r);

// Add your code here

My guess is that your code fails at the doShellScript line. AFAIK, doShellScript must be called at Application.currentApplication(). Which you don’t do, you call it at Application('DEVONthink').

I did it as you said, and I can make sure the privilege error occurs at app.doShellScript()

Then change that to

const curApp = Application.currentApplication();
curApp.includeStandardAdditions = true;
const apiResponse = curApp.doShellScript(…)

that should fix it.

function performsmartrule(records) { // Using the function name from the template
	
	const app = Application.currentApplication();
	app.logMessage('start');
	app.includeStandardAdditions = true; // Needed for doShellScript
        ...

I change it in Devonthink, the new error is “Message is not understood” in Line const app = Application.currentApplication();.
However, it works in Apple Script Edit. Expert, help plz :pleading_face:

That is not at all what I proposed. Please read my post again. You need

const app = Application('DEVONthink');
const curApp = Application.currentApplication();
curApp.includeStandardAdditions = true;
…
const apiResponse = curApp.doShellScript(…)

And perhaps read up about JXA.

2 Likes

Amazing! the doshellscript passed. One more question plz:
I followed your suggestion to use locationGroup(), but while run below code:

const location = r.locationGroup();
const newRecord = app.createRecordWith({
	name: newRecordName,
	type: 'markdown',
	content: markdownContent,
	tags: recordTags
}, { in: location });

error occurs “cannot convert type”(same error when using parentGroup)
What’s the problem? BTW could you tell me where to find the script function dictionary, It is hard to find reference.

The strange thing is that it works in Devonthink 3, but it errors in Devonthink 4.

In the link about JSA:
In addition, some property names have changed. Notably, typeis norecordType . In case one of the scripts in this sections fails to run with DEVONthink 4 and later, you should first verify that the property names and method parameters are still correct.

Is it the reason?

How did you ever write that code if you didn’t have access to the scripting dictionary? I have a hunch …

1 Like

AI :laughing: Then edit to run.

That’s what I thought.

I am not going to fix AI-generated code. You can easily learn about JXA, scripting dictionaries and all that. But I’m certainly not helping AI companies to get their act together for free.

I think AI generated code is a good template for starters like me to improve efficiency, since it does run successfully after some of my debugging and editing when I am using Devonthink 3. I also spent a lot of time to search and debug, for instance I searched numbers of historical discussion in the forum. I also saw your post saying > createRecordWith Record : The properties of the record

(‘name’, ‘type’, ‘comment’, ‘aliases’, ‘path’, ‘URL’, ‘creation’, ‘modification’, ‘date’, ‘plain text’, ‘rich text’, ‘source’, ‘data’, ‘content’, ‘columns’, ‘cells’, ‘thumbnail’ and ‘tags’ are possible values).
[in: Record] : The destination group for the new record. Uses incoming group or group selector if not specified.
→ Record

But the problem is that it is not working in Devonthink 4, and I think it is reasonable to post in the forum instead of free ride to ask for debugging for AI generated code.

appreciate for your professional help :heart:

I already posted a link to an extensive JXA documentation. Check it out. It explains how to get to the scripting library and has examples for DT.

The most notable changes between version 3 and 4 are in the application’s name (“DEVONthink 3” vs. “DEVONthink”) and the change from type to recordType in the record properties.

Handwritten JXA and AppleScript code is in most cases orders of magnitude better and easier to grasp than what an AI produces. Any AI that I’ve seen so far. The example you posted here, is ridden with error checks that look nice but distract from the flow of the code and are often utterly pointless.