diff --git a/README.md b/README.md index 2244d49..3cdc2cc 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ message meets a specified criterion. - **Markdown conversion** – optionally convert HTML bodies to Markdown before sending them to the AI service. - **Debug logging** – optional colorized logs help troubleshoot interactions with the AI service. - **Light/Dark themes** – automatically match Thunderbird's appearance with optional manual override. -- **Automatic rules** – create rules that tag or move new messages based on AI classification. +- **Automatic rules** – create rules that tag, move, mark read/unread or flag/unflag messages based on AI classification. - **Rule ordering** – drag rules to prioritize them and optionally stop processing after a match. - **Context menu** – apply AI rules from the message list or the message display action button. - **Status icons** – toolbar icons show when classification is in progress and briefly display success or error states. @@ -101,7 +101,7 @@ Here are some useful and fun example criteria you can use in your filters. Filte For when you're ready to filter based on vibes. You can define as many filters as you'd like, each using a different prompt and -triggering tags, moves, or actions based on the model's classification. +triggering tags, moves, read/unread changes or flag updates based on the model's classification. ## Required Permissions @@ -110,7 +110,7 @@ Sortana requests the following Thunderbird permissions: - `storage` – store configuration and cached classification results. - `messagesRead` – read message contents for classification. - `messagesMove` – move messages when a rule specifies a target folder. -- `messagesUpdate` – change message properties such as tags and junk status. + - `messagesUpdate` – change message properties such as tags, junk status, read/unread state and flags. - `messagesTagsList` – retrieve existing message tags for rule actions. - `accountsRead` – list accounts and folders for move actions. - `menus` – add context menu commands. diff --git a/_locales/en-US/messages.json b/_locales/en-US/messages.json index 97a356b..01d6c92 100644 --- a/_locales/en-US/messages.json +++ b/_locales/en-US/messages.json @@ -19,4 +19,10 @@ "options.stripUrlParams": { "message": "Remove URL tracking parameters" }, "options.altTextImages": { "message": "Replace images with alt text" }, "options.collapseWhitespace": { "message": "Collapse long whitespace" } + ,"action.read": { "message": "read" } + ,"action.flag": { "message": "flag" } + ,"param.markRead": { "message": "mark read" } + ,"param.markUnread": { "message": "mark unread" } + ,"param.flag": { "message": "flag" } + ,"param.unflag": { "message": "unflag" } } diff --git a/background.js b/background.js index 7e9b9c7..6469288 100644 --- a/background.js +++ b/background.js @@ -229,6 +229,10 @@ async function processMessage(id) { await messenger.messages.move([id], act.folder); } else if (act.type === 'junk') { await messenger.messages.update(id, { junk: !!act.junk }); + } else if (act.type === 'read') { + await messenger.messages.update(id, { read: !!act.read }); + } else if (act.type === 'flag') { + await messenger.messages.update(id, { flagged: !!act.flagged }); } } if (rule.stopProcessing) { diff --git a/options/options.js b/options/options.js index 83893c5..35d9a2c 100644 --- a/options/options.js +++ b/options/options.js @@ -171,7 +171,7 @@ document.addEventListener('DOMContentLoaded', async () => { const typeWrapper = document.createElement('div'); typeWrapper.className = 'select is-small mr-2'; const typeSelect = document.createElement('select'); - ['tag','move','junk'].forEach(t => { + ['tag','move','junk','read','flag'].forEach(t => { const opt = document.createElement('option'); opt.value = t; opt.textContent = t; @@ -222,6 +222,26 @@ document.addEventListener('DOMContentLoaded', async () => { sel.value = String(action.junk ?? true); wrap.appendChild(sel); paramSpan.appendChild(wrap); + } else if (typeSelect.value === 'read') { + const wrap = document.createElement('div'); + wrap.className = 'select is-small'; + const sel = document.createElement('select'); + sel.className = 'read-select'; + sel.appendChild(new Option('mark read','true')); + sel.appendChild(new Option('mark unread','false')); + sel.value = String(action.read ?? true); + wrap.appendChild(sel); + paramSpan.appendChild(wrap); + } else if (typeSelect.value === 'flag') { + const wrap = document.createElement('div'); + wrap.className = 'select is-small'; + const sel = document.createElement('select'); + sel.className = 'flag-select'; + sel.appendChild(new Option('flag','true')); + sel.appendChild(new Option('unflag','false')); + sel.value = String(action.flagged ?? true); + wrap.appendChild(sel); + paramSpan.appendChild(wrap); } } @@ -334,6 +354,12 @@ document.addEventListener('DOMContentLoaded', async () => { if (type === 'junk') { return { type, junk: row.querySelector('.junk-select').value === 'true' }; } + if (type === 'read') { + return { type, read: row.querySelector('.read-select').value === 'true' }; + } + if (type === 'flag') { + return { type, flagged: row.querySelector('.flag-select').value === 'true' }; + } return { type }; }); const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked; @@ -474,6 +500,12 @@ document.addEventListener('DOMContentLoaded', async () => { if (type === 'junk') { return { type, junk: row.querySelector('.junk-select').value === 'true' }; } + if (type === 'read') { + return { type, read: row.querySelector('.read-select').value === 'true' }; + } + if (type === 'flag') { + return { type, flagged: row.querySelector('.flag-select').value === 'true' }; + } return { type }; }); const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked;