Sure, this is a script that works great when run from the Script Editor, but not when it’s through Smart Rules (simplified slightly as to not make this too long):
(() => {
if (currentAppID() === "DNtp") return;
const app = Application("DEVONthink 3");
performsmartsule(app.selectedRecords());
})();
function currentAppID() {
const p = Application.currentApplication().properties();
return Application(p.name).id();
}
function performsmartsule(records) {
var app = Application("DEVONthink 3");
app.includeStandardAdditions = true;
records.forEach(record => {
const content = record.plainText();
const result = processTextBasedOnPhrase(content);
if (result) {
record.name = result.name;
record.tags = result.tags;
}
});
}
function processTextBasedOnPhrase(text) {
const receipts = [
{ title: 'Patreon', phrases: ['Patreon'], dateFormat: 'UK-monthStr', tags: ['Subscription','Patreon'] },
{ title: 'Apple Music', phrases: ['Apple Music'], dateFormat: 'GMAIL', tags: ['Subscription'] },
];
for (const receipt of receipts) {
if ( areAllPhrasesInText(text, receipt.phrases) ) {
const date = extractDateStructured(text, receipt.dateFormat );
if (date) {
const title = `${receipt.title} - ${dateToTitleStr(date.day, date.month, date.year)}`;
receipt.tags.push(date.year);
return { name: title, tags: receipt.tags };
}
}
}
return null;
}
function extractDateStructured(text, dateFormat) {
const monthRegexPart = "(Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)";
const dateRegexMap = {
"ISO": /\d{4}-\d{2}-\d{2}/, // YYYY-MM-DD
"US": /(\d{2})-(\d{2})-(\d{4})/, // MM-DD-YYYY
"UK": /(\d{2})\/(\d{2})\/(\d{4})/, // DD/MM/YYYY
"UK-shortYear": /(\d{2})\/(\d{2})\/(\d{2})/, // DD/MM/YY
"EU": /(\d{2})\.(\d{2})\.(\d{4})/, // DD.MM.YYYY
"GMAIL": new RegExp(`${monthRegexPart} (\\d{1,2}), (\\d{4})`, "i"), // MMM D, YYYY
"UK-monthStr": new RegExp(`(\\d{1,2}) ${monthRegexPart} (\\d{4})`, "i"), // DD MMM YYYY
};
let regex = dateRegexMap[dateFormat];
let match = text.match(regex);
// Initialize date structure
let dateStructure = {
day: "",
month: "",
year: ""
};
if (match && match[1]) {
let year, month, day;
switch (dateFormat) {
case "US":
[month, day, year] = [match[1], match[2], match[3]];
break;
case "UK":
case "UK-shortYear":
case "EU":
[day, month, year] = [match[1], match[2], match[3]];
break;
case "GMAIL":
[day, month, year] = [match[2], getMonthNumber(match[1]), match[3]];
break;
case "UK-monthStr":
[day, month, year] = [match[1], getMonthNumber(match[2]), match[3]];
break;
case "ISO":
default:
[year, month, day] = match[1].split('-');
break;
}
if (dateFormat == "UK-shortYear") {
year = '20' + year;
}
// Assign parsed values
dateStructure = { day, month, year };
}
return dateStructure;
}
function areAllPhrasesInText(text, phrases) {
return phrases.every(phrase => text.includes(phrase));
}
function dateToTitleStr(day, month, year) {
day = parseInt(day, 10);
const date = new Date(year, month - 1, day);
const monthStr = date.toLocaleString('default', { month: 'long' });
return `${monthStr} ${day}, ${year}`;
}
function getMonthNumber(monthName) {
const monthNames = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
const abbreviatedMonthNames = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];
// Normalize input for comparison
monthName = monthName.toLowerCase();
let index = monthNames.findIndex(name => name.toLowerCase().startsWith(monthName));
if (index !== -1) return index + 1;
index = abbreviatedMonthNames.findIndex(name => name.toLowerCase() === monthName);
if (index !== -1) return index + 1;
return null; // Return null if no valid month is found
}