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 
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 no
recordType . 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
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 
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.