diff --git a/_locales/en/messages.json b/_locales/en/messages.json index dc5e251a..7c768c77 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1448,6 +1448,26 @@ "message": "The sender is in the email address antispam skip list.", "description": "" }, + "prefs_OptionText_spamfilter_skip_addressbook": { + "message": "Skip address books addresses", + "description": "" + }, + "prefs_OptionText_spamfilter_skip_addressbook_Info": { + "message": "If checked, emails from senders in your address books will not be sent to the AI for spam filtering.", + "description": "" + }, + "spamfilter_skip_addressbook_explanation": { + "message": "The sender is a contact in the address book.", + "description": "" + }, + "addressbook_permission_denied": { + "message": "Address book permission was denied. Please enable the feature again to grant permission.", + "description": "" + }, + "addressbook_permission_error": { + "message": "Error requesting address book permission. Please try again.", + "description": "" + }, "SpamReport_Title": { "message": "Spam Filter Reports", "description": "" diff --git a/manifest.json b/manifest.json index 769e5051..991cb94e 100644 --- a/manifest.json +++ b/manifest.json @@ -34,6 +34,7 @@ "messagesTagsList" ], "optional_permissions": [ + "addressBooks", "clipboardRead", "messagesTags", "messagesUpdate", diff --git a/mzta-background.js b/mzta-background.js index 00f4a815..0fa99ddd 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -805,11 +805,13 @@ async function _generateSpamReportForMessage(headerMessageId, options = {}) { body_text = msg_text.text.replace(/\s+/g, ' ').trim(); } } -console.log(">>>>>>>>>>>>>> options.skip_addresses: " + JSON.stringify(options.skip_addresses)); + + // Extract sender email for skip checks + let senderEmail = (message.author.match(/[\w.-]+@[\w.-]+\.\w+/) || [''])[0].toLowerCase(); + // Check if sender is in the skip addresses list let skip_addresses = options.skip_addresses || (await browser.storage.sync.get({spamfilter_skip_addresses: prefs_default.spamfilter_skip_addresses})).spamfilter_skip_addresses; if (skip_addresses.length > 0) { - let senderEmail = (message.author.match(/[\w.-]+@[\w.-]+\.\w+/) || [''])[0].toLowerCase(); if (senderEmail && skip_addresses.includes(senderEmail)) { taLog.log("Sender " + senderEmail + " is in the skip addresses list, skipping spam filter."); let report_data = {}; @@ -828,6 +830,43 @@ console.log(">>>>>>>>>>>>>> options.skip_addresses: " + JSON.stringify(options.s } } + // Check if sender is in any address book + let skip_addressbook = options.skip_addressbook !== undefined + ? options.skip_addressbook + : (await browser.storage.sync.get({spamfilter_skip_addressbook: prefs_default.spamfilter_skip_addressbook})).spamfilter_skip_addressbook; + if (skip_addressbook && senderEmail) { + try { + let hasPermission = await browser.permissions.contains({ permissions: ["addressBooks"] }); + if (hasPermission) { + let matchingContacts = await browser.contacts.quickSearch({ searchString: senderEmail }); + let isInAddressBook = matchingContacts.some(contact => { + let props = contact.properties; + return (props.PrimaryEmail && props.PrimaryEmail.toLowerCase() === senderEmail) || + (props.SecondEmail && props.SecondEmail.toLowerCase() === senderEmail); + }); + if (isInAddressBook) { + taLog.log("Sender " + senderEmail + " is in the address book, skipping spam filter."); + let report_data = {}; + report_data.report_date = new Date(); + report_data.headerMessageId = headerMessageId; + report_data.spamValue = 0; + report_data.explanation = browser.i18n.getMessage('spamfilter_skip_addressbook_explanation'); + report_data.subject = curr_fullMessage.headers.subject; + report_data.from = curr_fullMessage.headers.from; + report_data.message_date = new Date(message.date); + report_data.moved = false; + report_data.SpamThreshold = prefs.spamfilter_threshold || prefs_init.spamfilter_threshold; + spamReport.saveReportData(report_data, headerMessageId); + await updateSpamPanel(headerMessageId, "showSpamReport", report_data); + return { success: true }; + } + } + } catch (err) { + taLog.error("Error checking address book for sender: " + err); + // Fail open — continue with normal spam check + } + } + let curr_prompt_spamfilter = await getSpamFilterPrompt(); let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_spamfilter); let specialFullPrompt_spamfilter = await taPromptUtils.preparePrompt({ @@ -1673,11 +1712,13 @@ async function processEmails(args) { add_tags_auto_uselist_list: prefs_default.add_tags_auto_uselist_list, spamfilter_enabled_accounts: prefs_default.spamfilter_enabled_accounts, spamfilter_skip_addresses: prefs_default.spamfilter_skip_addresses, + spamfilter_skip_addressbook: prefs_default.spamfilter_skip_addressbook, ...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type']), do_debug: prefs_default.do_debug, }); // console.log(">>>>>>>>>>>>>>>> prefs_aats: " + JSON.stringify(prefs_aats)); let spamfilter_skip_addresses = prefs_aats.spamfilter_skip_addresses; + let spamfilter_skip_addressbook = prefs_aats.spamfilter_skip_addressbook; for await (let message of messages) { let curr_fullMessage = null; @@ -1755,7 +1796,8 @@ async function processEmails(args) { messageData: { message, fullMessage: curr_fullMessage, body_text, msg_text }, prefs: prefs_aats, autoMove: true, - skip_addresses: spamfilter_skip_addresses + skip_addresses: spamfilter_skip_addresses, + skip_addressbook: spamfilter_skip_addressbook }); if (!result.success) continue; } diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index 3c16b111..72ed1b1c 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -139,6 +139,7 @@ export const prefs_default = { spamfilter_threshold: 70, spamfilter_enabled_accounts: [], spamfilter_skip_addresses: [], + spamfilter_skip_addressbook: false, summarize: false, summarize_auto: 1, // 0: disabled, 1: manual button, 2: automatic on message open, 3: generate on email receive summarize_display_mode: 'inline', // 'inline' or 'webchat' diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html index 2e7ba072..227ef938 100644 --- a/pages/spamfilter/mzta-spamfilter.html +++ b/pages/spamfilter/mzta-spamfilter.html @@ -71,6 +71,7 @@
__MSG_SpamFilter_skip_addresses_infoline2__
+
__MSG_SpamReport_Title__
diff --git a/pages/spamfilter/mzta-spamfilter.js b/pages/spamfilter/mzta-spamfilter.js index 382690e6..e803bd1d 100644 --- a/pages/spamfilter/mzta-spamfilter.js +++ b/pages/spamfilter/mzta-spamfilter.js @@ -158,6 +158,31 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById('skip_addresses_unsaved').classList.add('hidden'); }); + // Address book skip option + let skip_addressbook_checkbox = document.getElementById('spamfilter_skip_addressbook'); + let prefs_skip_ab = await browser.storage.sync.get({ spamfilter_skip_addressbook: prefs_default.spamfilter_skip_addressbook }); + skip_addressbook_checkbox.checked = prefs_skip_ab.spamfilter_skip_addressbook; + + skip_addressbook_checkbox.addEventListener('change', async (event) => { + if (event.target.checked) { + try { + const granted = await browser.permissions.request({ permissions: ["addressBooks"] }); + if (granted) { + await browser.storage.sync.set({ spamfilter_skip_addressbook: true }); + } else { + event.target.checked = false; + alert(browser.i18n.getMessage("addressbook_permission_denied")); + } + } catch (err) { + taLog.error("Error requesting addressBooks permission:", err); + event.target.checked = false; + alert(browser.i18n.getMessage("addressbook_permission_error")); + } + } else { + await browser.storage.sync.set({ spamfilter_skip_addressbook: false }); + } + }); + //Accounts manager let accounts = await getAccountsList(); const accountsContainer = document.getElementById('account_selector_checkboxes');