From caeb03cc4c2870a38d4efa5b2a697cea41617817 Mon Sep 17 00:00:00 2001 From: mic Date: Sun, 9 Feb 2025 23:43:26 +0100 Subject: [PATCH 01/20] spamfilter default option added --- options/mzta-options-default.js | 1 + 1 file changed, 1 insertion(+) diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index ecde6a58..4e396083 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -51,4 +51,5 @@ export const prefs_default = { add_tags_auto_force_existing: false, add_tags_auto_only_inbox: true, get_calendar_event: true, + spamfilter: false, } \ No newline at end of file From dde5c9fef6943de7e60aa4a9ff5117ec0313cc0c Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 10 Feb 2025 00:02:07 +0100 Subject: [PATCH 02/20] start working on the spam filter --- mzta-background.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index ea022817..f514e305 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -689,9 +689,8 @@ const newEmailListener = (folder, messagesList) => { let curr_fullMessage = null; // if some auto feature is active prepare some data - // this will be used when will be implemented the spam filter (https://github.com/micz/ThunderAI/issues/231) - // or the auto translator https://github.com/micz/ThunderAI/issues/247 - if(prefs_init.add_tags_auto){ + // this will be used when will be implemented also the auto translator https://github.com/micz/ThunderAI/issues/247 + if(prefs_init.add_tags_auto || prefs_init.spamfilter){ curr_fullMessage = await browser.messages.getFull(message.id); } @@ -719,6 +718,13 @@ const newEmailListener = (folder, messagesList) => { let _data = {messageId: message.id, tags: tags_current_email.split(/,\s*/)}; _assign_tags(_data, !prefs_aat.add_tags_auto_force_existing); } + + + // Spam filter + if(prefs_init.spamfilter){ + let specialFullPrompt_spamfilter = ''; + + } } } From 7355934f890522ca11b1d634077cda2a1273e3ce Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 21:39:35 +0100 Subject: [PATCH 03/20] spamfilter added to options page. see #231 --- _locales/en/messages.json | 12 ++++++++++++ options/mzta-options.html | 10 ++++++++++ options/mzta-options.js | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index acafc53d..927f9da5 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1050,5 +1050,17 @@ "thunderai_def_lang": { "message": "Default language as defined in ThunderAI options.", "description": "" + }, + "prefs_OptionText_spamfilter": { + "message": "Spam Filter", + "description": "" + }, + "prefs_OptionText_spamfilter_Info": { + "message": "If checked, ThunderAI will automatically move spam emails to the spam folder.", + "description": "" + }, + "prefs_OptionText_btnManageSpamFilterInfo": { + "message": "Manage Spam Filter settings", + "description": "" } } \ No newline at end of file diff --git a/options/mzta-options.html b/options/mzta-options.html index 8cd6443e..0590c2e7 100644 --- a/options/mzta-options.html +++ b/options/mzta-options.html @@ -334,6 +334,16 @@ + + __MSG_prefs_OptionText_spamfilter__ +
+ + + + __MSG_prefs_OptionText_get_calendar_event__
diff --git a/options/mzta-options.js b/options/mzta-options.js index 81844d01..04d390fd 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -304,6 +304,13 @@ document.addEventListener('DOMContentLoaded', async () => { }); addtags_info_btn.disabled = addtags_el.checked ? '' : 'disabled'; + let spamfilter_el = document.getElementById('spamfilter'); + let spamfilter_info_btn = document.getElementById('btnManageSpamFilterInfo'); + spamfilter_el.addEventListener('click', (event) => { + spamfilter_info_btn.disabled = event.target.checked ? '' : 'disabled'; + }); + spamfilter_info_btn.disabled = spamfilter_el.checked ? '' : 'disabled'; + let get_calendar_event_el = document.getElementById('get_calendar_event'); let get_calendar_event_info_btn = document.getElementById('btnManageCalendarEventInfo'); get_calendar_event_el.addEventListener('click', (event) => { @@ -337,6 +344,19 @@ document.addEventListener('DOMContentLoaded', async () => { }) }); + document.getElementById('btnManageSpamFilterInfo').addEventListener('click', () => { + // check if the tab is already there + browser.tabs.query({url: browser.runtime.getURL('../pages/spamfilter/mzta-spamfilter.html')}).then((tabs) => { + if (tabs.length > 0) { + // if the tab is already there, focus it + browser.tabs.update(tabs[0].id, {active: true}); + } else { + // if the tab is not there, create it + browser.tabs.create({url: browser.runtime.getURL('../pages/spamfilter/mzta-spamfilter.html')}); + } + }) + }); + document.getElementById('btnManageCalendarEventInfo').addEventListener('click', () => { // check if the tab is already there browser.tabs.query({url: browser.runtime.getURL('../pages/get-calendar-event/mzta-get-calendar-event.html')}).then((tabs) => { From 262cfd8c17fe2cab4106445fcceaed8324ecd62d Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 22:04:15 +0100 Subject: [PATCH 04/20] prompt_spamfilter added. see #231 --- js/mzta-prompts.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js index bf7e35da..fdc067f1 100644 --- a/js/mzta-prompts.js +++ b/js/mzta-prompts.js @@ -195,6 +195,19 @@ const specialPrompts = [ is_default: "1", is_special: "1", }, + { + id: 'prompt_spamfilter', + name: "__MSG_prompt_spamfilter__", + text: "prompt_spamfilter_full_text", + type: "1", + action: "0", + need_selected: "0", + need_signature: "0", + need_custom_text: "0", + define_response_lang: "0", + is_default: "1", + is_special: "1", + }, ]; From a9c8c1af0b15a2841546b4625d737961f23f7568 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 22:04:32 +0100 Subject: [PATCH 05/20] spamfilter manage page added. see #231 --- _locales/en/messages.json | 26 ++++- pages/spamfilter/mzta-spamfilter.css | 87 +++++++++++++++ pages/spamfilter/mzta-spamfilter.html | 29 +++++ pages/spamfilter/mzta-spamfilter.js | 149 ++++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 pages/spamfilter/mzta-spamfilter.css create mode 100644 pages/spamfilter/mzta-spamfilter.html create mode 100644 pages/spamfilter/mzta-spamfilter.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 927f9da5..e8790e2e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1060,7 +1060,31 @@ "description": "" }, "prefs_OptionText_btnManageSpamFilterInfo": { - "message": "Manage Spam Filter settings", + "message": "Manage spam filter settings", "description": "" + }, + "SpamFilter_PageTitle": { + "message": "Manage Spam Filter Settings", + "description": "" + }, + "SpamFilter_info_default": { + "message": "In this page you can modify the default prompt used to detect spam emails.", + "description": "" + }, + "SpamFilter_prompt_text_title": { + "message": "Current prompt text", + "description": "" + }, + "prefs_OptionText_spamfilter_infoline": { + "message": "You can change the prompt as you wish, but the response received from the AI must be in JSON format as specified in the default prompt!", + "description": "" + }, + "prompt_spamfilter" : { + "message" : "Detect spam emails", + "description" : "" + }, + "prompt_spamfilter_full_text" : { + "message" : "Analyze the following email and determine if it is spam or not. Consider factors such as suspicious keywords, excessive promotional language, misleading subject lines, requests for personal information, and unusual sender addresses.\nProvide a value from 0 (not spam) to 100 (spam) and an explaination of no more than 10 words.\nGenerate a response in JSON format only. Do not include any additional text or explanation; provide only the JSON. Here is the format to be used:\n{\n\"spamValue\": ,\n\"explanation\": \"Brief explanation of your reasoning\",\n}\nHere there are the mail information:\nSender: \"{%author%}\"\nSubject: \"{%mail_subject%}\"\nHtml body: \"{%mail_html_body%}\"", + "description" : "" } } \ No newline at end of file diff --git a/pages/spamfilter/mzta-spamfilter.css b/pages/spamfilter/mzta-spamfilter.css new file mode 100644 index 00000000..7d984c80 --- /dev/null +++ b/pages/spamfilter/mzta-spamfilter.css @@ -0,0 +1,87 @@ +#spamfilter_prompt_container{ + width: 90%; + margin: 20px auto; +} + +#spamfilter_prompt_text{ + width: 100%; +} + +.infoline{ + font-size: 0.8em; + font-style: italic; +} + +.btn_div{ + width: 100%; + display: flex; + justify-content: space-between; +} + +.autocomplete-container { + position: relative; +} + +.autocomplete-list { + position: absolute; + top: 100%; + left: 0; + right: 0; + background-color: white; + border: 1px solid #ccc; + z-index: 1000; + max-height: 200px; + overflow-y: auto; + padding: 0; + margin: 0; + list-style: none; + font-size: small; +} + +.autocomplete-list li { + padding: 8px; + cursor: pointer; +} + +.autocomplete-list li:hover { + background-color: #f0f0f0; +} + +.autocomplete-list li.active { + background-color: #ddd; +} + +.hidden { + display: none; +} + +.unsaved{ + color: red; +} + + +@media (prefers-color-scheme: dark) { + body { + background-color: #1C1B22; + color: rgb(251, 251, 254); + } + + a:link { color: #409EFF; } + a:visited { color: #409EFF; } + a:hover { color: #66B1FF; } + a:active { color: #66B1FF; } + a:active { color: #66B1FF; } + + .autocomplete-list { + background-color: #2E2F36; + border: 1px solid #2E2F36; + } + + .autocomplete-list li:hover { + background-color: #4c4e58; + } + + .autocomplete-list li.active { + background-color: #4c4e58; + } +} \ No newline at end of file diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html new file mode 100644 index 00000000..c717674f --- /dev/null +++ b/pages/spamfilter/mzta-spamfilter.html @@ -0,0 +1,29 @@ + + + + + ThunderAI - __MSG_SpamFilter_PageTitle__ + + + + +
+

__MSG_SpamFilter_PageTitle__

+

__MSG_SpamFilter_info_default__

+
+
+ __MSG_GetCalendarEvent_prompt_text_title__ +
__MSG_prefs_OptionText_btnManagePrompts_infoline__ __MSG_customPrompts_managePrompts_help__ +
__MSG_prefs_OptionText_GetCalendarEvent_infoline2__
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/pages/spamfilter/mzta-spamfilter.js b/pages/spamfilter/mzta-spamfilter.js new file mode 100644 index 00000000..54fca13f --- /dev/null +++ b/pages/spamfilter/mzta-spamfilter.js @@ -0,0 +1,149 @@ +/* + * ThunderAI [https://micz.it/thunderbird-addon-thunderai/] + * Copyright (C) 2024 - 2025 Mic (m@micz.it) + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { prefs_default } from '../../options/mzta-options-default.js'; +import { taLogger } from '../../js/mzta-logger.js'; +import { getSpecialPrompts, setSpecialPrompts } from "../../js/mzta-prompts.js"; +import { getPlaceholders } from "../../js/mzta-placeholders.js"; +import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js"; + +let autocompleteSuggestions = []; +let taLog = new taLogger("mzta-spamfilter-page",true); + +document.addEventListener('DOMContentLoaded', async () => { + + i18n.updateDocument(); + await restoreOptions(); + + document.querySelectorAll(".option-input").forEach(element => { + element.addEventListener("change", saveOptions); + }); + + let spamfilter_textarea = document.getElementById('spamfilter_prompt_text'); + let spamfilter_save_btn = document.getElementById('btn_save_prompt'); + let spamfilter_reset_btn = document.getElementById('btn_reset_prompt'); + + let specialPrompts = await getSpecialPrompts(); + let spamfilter_prompt = specialPrompts.find(prompt => prompt.id === 'prompt_spamfilter'); + + spamfilter_textarea.addEventListener('input', (event) => { + spamfilter_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_spamfilter_full_text')); + spamfilter_save_btn.disabled = (event.target.value === spamfilter_prompt.text); + if(spamfilter_save_btn.disabled){ + document.getElementById('spamfilter_prompt_unsaved').classList.add('hidden'); + } else { + document.getElementById('spamfilter_prompt_unsaved').classList.remove('hidden'); + } + }); + + spamfilter_reset_btn.addEventListener('click', () => { + spamfilter_textarea.value = browser.i18n.getMessage('prompt_spamfilter_full_text'); + spamfilter_reset_btn.disabled = true; + let event = new Event('input', { bubbles: true, cancelable: true }); + spamfilter_textarea.dispatchEvent(event); + }); + + spamfilter_save_btn.addEventListener('click', () => { + specialPrompts.find(prompt => prompt.id === 'prompt_spamfilter').text = spamfilter_textarea.value; + setSpecialPrompts(specialPrompts); + spamfilter_save_btn.disabled = true; + document.getElementById('spamfilter_prompt_unsaved').classList.add('hidden'); + browser.runtime.sendMessage({command: "reload_menus"}); + }); + + if(spamfilter_prompt.text === 'prompt_spamfilter_full_text'){ + spamfilter_prompt.text = browser.i18n.getMessage(spamfilter_prompt.text); + } + spamfilter_textarea.value = spamfilter_prompt.text; + spamfilter_reset_btn.disabled = (spamfilter_textarea.value === browser.i18n.getMessage('prompt_spamfilter_full_text')); + + autocompleteSuggestions = (await getPlaceholders(true)).filter(p => !(p.id === 'additional_text')).map(p => ({command: '{%'+p.id+'%}', type: p.type})); + textareaAutocomplete(spamfilter_textarea, autocompleteSuggestions, 1); // type_value = 1, only when reading an email + +}); + + + +// Methods to manage options, derived from: /options/mzta-options.js + +function saveOptions(e) { + e.preventDefault(); + let options = {}; + let element = e.target; + + switch (element.type) { + case 'checkbox': + options[element.id] = element.checked; + break; + case 'number': + options[element.id] = element.valueAsNumber; + break; + case 'text': + case 'password': + options[element.id] = element.value.trim(); + break; + default: + if (element.tagName === 'SELECT') { + options[element.id] = element.value; + }else{ + console.error("[ThunderAI] Unhandled input type:", element.type); + } + } + + browser.storage.sync.set(options); +} + +async function restoreOptions() { + function setCurrentChoice(result) { + document.querySelectorAll(".option-input").forEach(element => { + taLog.log("Options restoring " + element.id + " = " + (element.id=="chatgpt_api_key" || element.id=="openai_comp_api_key" ? "****************" : result[element.id])); + switch (element.type) { + case 'checkbox': + element.checked = result[element.id] || false; + break; + case 'number': + let default_number_value = 0; + if(element.id == 'chatgpt_win_height') default_number_value = prefs_default.chatgpt_win_height; + if(element.id == 'chatgpt_win_width') default_number_value = prefs_default.chatgpt_win_width; + element.value = result[element.id] ?? default_number_value; + break; + case 'text': + case 'password': + let default_text_value = ''; + if(element.id == 'default_chatgpt_lang') default_text_value = prefs_default.default_chatgpt_lang; + element.value = result[element.id] || default_text_value; + break; + default: + if (element.tagName === 'SELECT') { + let default_select_value = ''; + if(element.id == 'reply_type') default_select_value = 'reply_all'; + if(element.id == 'connection_type') default_select_value = 'chatgpt_web'; + element.value = result[element.id] || default_select_value; + if (element.value === '') { + element.selectedIndex = -1; + } + }else{ + console.error("[ThunderAI] Unhandled input type:", element.type); + } + } + }); + } + + let getting = await browser.storage.sync.get(prefs_default); + setCurrentChoice(getting); +} \ No newline at end of file From 0aa8675e48bf42f25b7162caa9d4def2509f0a97 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 22:56:12 +0100 Subject: [PATCH 06/20] extractJsonObject method added --- js/mzta-utils.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/js/mzta-utils.js b/js/mzta-utils.js index 17b70de4..0e47b85e 100644 --- a/js/mzta-utils.js +++ b/js/mzta-utils.js @@ -302,6 +302,23 @@ export function getActiveSpecialPromptsIDs(addtags = false, get_calendar_event = return output; } +export function extractJsonObject(inputString) { + try { + const jsonMatch = inputString.match(/\{[\s\S]*\}/); + if (jsonMatch) { + const jsonObject = JSON.parse(jsonMatch[0]); + // console.log(">>>>>>>>>> Extracted JSON object:", jsonObject); + return jsonObject; + } else { + console.error("[ThunderAI-Sparks] No JSON object found in the input string."); + return null; + } + } catch (error) { + console.error("[ThunderAI-Sparks] Error extracting JSON object:", error); + return null; + } +} + export async function checkSparksPresence() { try { return (await browser.runtime.sendMessage('thunderai-sparks@micz.it',{action: "checkPresence"}) === 'ok'); From fda58db25452def8c3cef78d51e0c5d9f8c31ab8 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 23:31:46 +0100 Subject: [PATCH 07/20] getSpamFilterPrompt method added. see #231 --- js/mzta-prompts.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js index fdc067f1..6ed4bad4 100644 --- a/js/mzta-prompts.js +++ b/js/mzta-prompts.js @@ -372,4 +372,8 @@ export async function getSpecialPrompts(){ export async function setSpecialPrompts(prompts) { // console.log(">>>>>>>>>>>> setSpecialPrompts prompts: " + JSON.stringify(prompts)); await browser.storage.local.set({_special_prompts: prompts}); +} + +export async function getSpamFilterPrompt(){ + return (await getSpecialPrompts()).find(prompt => prompt.id == 'prompt_spamfilter'); } \ No newline at end of file From cc2a9a90f27fdfa22a2fc6ad309b0e0e6eed314f Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 23:32:10 +0100 Subject: [PATCH 08/20] getPlaceholdersValues fixed, it's now returnig a value. see #231 --- js/mzta-placeholders.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js index bafc36b1..65bce34a 100644 --- a/js/mzta-placeholders.js +++ b/js/mzta-placeholders.js @@ -318,6 +318,8 @@ export const placeholdersUtils = { break; } } + + return finalSubs; } } \ No newline at end of file From f4e8ce32278e735cf3a40f2385f73023c84a7636 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 23:32:20 +0100 Subject: [PATCH 09/20] added a missing await --- js/mzta-utils-prompt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/mzta-utils-prompt.js b/js/mzta-utils-prompt.js index b8797d49..36cdf687 100644 --- a/js/mzta-utils-prompt.js +++ b/js/mzta-utils-prompt.js @@ -37,7 +37,7 @@ export const taPromptUtils = { fullPrompt = curr_prompt.text + (String(curr_prompt.need_signature) == "1" ? " " + await taPromptUtils.getDefaultSignature():"") + " " + chatgpt_lang + " \"" + (selection_text=='' ? body_text : selection_text) + "\" "; }else{ // we have at least a placeholder, do the magic! - let finalSubs = placeholdersUtils.getPlaceholdersValues(curr_prompt.text, curr_message, subject_text, body_text, msg_text, only_typed_text, selection_text, tags_full_list); + let finalSubs = await placeholdersUtils.getPlaceholdersValues(curr_prompt.text, curr_message, subject_text, body_text, msg_text, only_typed_text, selection_text, tags_full_list); // console.log(">>>>>>>>>> finalSubs: " + JSON.stringify(finalSubs)); let prefs_ph = await browser.storage.sync.get({placeholders_use_default_value: false}); fullPrompt = (placeholdersUtils.replacePlaceholders(curr_prompt.text, finalSubs, prefs_ph.placeholders_use_default_value, true) + (String(curr_prompt.need_signature) == "1" ? " " + await taPromptUtils.getDefaultSignature():"") + " " + chatgpt_lang).trim(); From cb10e4fa5983d9ac3efa65b8f641238c2c1de688 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 23:39:58 +0100 Subject: [PATCH 10/20] comments updated --- js/mzta-utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/mzta-utils.js b/js/mzta-utils.js index 0e47b85e..6bb7f665 100644 --- a/js/mzta-utils.js +++ b/js/mzta-utils.js @@ -310,11 +310,11 @@ export function extractJsonObject(inputString) { // console.log(">>>>>>>>>> Extracted JSON object:", jsonObject); return jsonObject; } else { - console.error("[ThunderAI-Sparks] No JSON object found in the input string."); + console.error("[ThunderAI] No JSON object found in the input string."); return null; } } catch (error) { - console.error("[ThunderAI-Sparks] Error extracting JSON object:", error); + console.error("[ThunderAI] Error extracting JSON object:", error); return null; } } From 29e7b5591ce5135f5a3cbec99aae29edaf0bce69 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 23:44:31 +0100 Subject: [PATCH 11/20] sending the prompt and receiving a json. see #231 --- mzta-background.js | 47 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index f514e305..753e1ddb 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -20,9 +20,10 @@ import { mzta_script } from './js/mzta-chatgpt.js'; import { prefs_default } from './options/mzta-options-default.js'; import { mzta_Menus } from './js/mzta-menus.js'; import { taLogger } from './js/mzta-logger.js'; -import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, getTagsList, createTag, assignTagsToMessage, checkIfTagExists, getActiveSpecialPromptsIDs, checkSparksPresence, getMessages, getMailBody } from './js/mzta-utils.js'; +import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, getTagsList, createTag, assignTagsToMessage, checkIfTagExists, getActiveSpecialPromptsIDs, checkSparksPresence, getMessages, getMailBody, extractJsonObject } from './js/mzta-utils.js'; import { taPromptUtils } from './js/mzta-utils-prompt.js'; import { mzta_specialCommand } from './js/mzta-special-commands.js'; +import { getSpamFilterPrompt } from './js/mzta-prompts.js'; await migrateCustomPromptsStorage(); await migrateDefaultPromptsPropStorage(); @@ -601,7 +602,7 @@ async function doGetCalendarEvent(get_calendar_event) { } async function reload_pref_init(){ - prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: true, get_calendar_event: true, connection_type: 'chatgpt_web', add_tags_auto: false, add_tags_auto_force_existing: false, add_tags_auto_only_inbox: true}); + prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: true, get_calendar_event: true, connection_type: 'chatgpt_web', add_tags_auto: false, add_tags_auto_force_existing: false, add_tags_auto_only_inbox: true, spamfilter: false}); } @@ -687,21 +688,23 @@ const newEmailListener = (folder, messagesList) => { } let curr_fullMessage = null; + let msg_text = null; + let body_text = ''; // if some auto feature is active prepare some data // this will be used when will be implemented also the auto translator https://github.com/micz/ThunderAI/issues/247 if(prefs_init.add_tags_auto || prefs_init.spamfilter){ curr_fullMessage = await browser.messages.getFull(message.id); + msg_text = await getMailBody(curr_fullMessage); + body_text = msg_text.text.replace(/\s+/g, ' ').trim(); } // Auto add_tags if(prefs_init.add_tags_auto){ let specialFullPrompt_add_tags = ''; let curr_prompt_add_tags = menus.allPrompts.find(p => p.id === 'prompt_add_tags'); - let chatgpt_lang = taPromptUtils.getDefaultLang(curr_prompt_add_tags); - let msg_text = await getMailBody(curr_fullMessage); - let body_text = msg_text.text.replace(/\s+/g, ' ').trim(); let tags_full_list = await getTagsList(); + let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_add_tags); specialFullPrompt_add_tags = await taPromptUtils.preparePrompt(curr_prompt_add_tags, message, chatgpt_lang, /*selection_text*/ '', body_text, curr_fullMessage.headers.subject, msg_text, /*only_typed_text*/ '', tags_full_list); let prefs_aat = await browser.storage.sync.get({add_tags_maxnum: 3, connection_type: '', add_tags_force_lang: true, default_chatgpt_lang: '', add_tags_auto_force_existing: false}); specialFullPrompt_add_tags = taPromptUtils.finalizePrompt_add_tags(specialFullPrompt_add_tags, prefs_aat.add_tags_maxnum, prefs_aat.add_tags_force_lang, prefs_aat.default_chatgpt_lang); @@ -722,8 +725,38 @@ const newEmailListener = (folder, messagesList) => { // Spam filter if(prefs_init.spamfilter){ - let specialFullPrompt_spamfilter = ''; - + let curr_prompt_spamfilter = await getSpamFilterPrompt(); + //console.log(">>>>>>>>>>>> curr_prompt_spamfilter: " + JSON.stringify(curr_prompt_spamfilter)); + let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_spamfilter); + let specialFullPrompt_spamfilter = await taPromptUtils.preparePrompt(curr_prompt_spamfilter, message, chatgpt_lang, /*selection_text*/ '', body_text, curr_fullMessage.headers.subject, msg_text, /*only_typed_text*/ '', ''); + taLog.log("Special prompt: " + specialFullPrompt_spamfilter); + let cmd_spamfilter = new mzta_specialCommand(specialFullPrompt_spamfilter,prefs_init.connection_type,prefs_init.do_debug); + await cmd_spamfilter.initWorker(); + /* We expect to receive from the AI a JSON object like this: + * { + * "spamValue": , + * "explanation": "Brief explanation of your reasoning", + * } + */ + let spamfilter_result = ''; + taLog.log("Sending the prompt..."); + try{ + spamfilter_result = (await cmd_spamfilter.sendPrompt()).trim(); + }catch(err){ + console.error("[ThunderAI | SpamFilter] Error getting spamfilter: ", err); + } + taLog.log("spamfilter_result: " + spamfilter_result); + let jsonObj = {}; + taLog.log("Decoding the AI response..."); + try{ + jsonObj = extractJsonObject(spamfilter_result); + }catch(e){ + console.error("[ThunderAI | SpamFilter ] Error extracting JSON from AI response: ", e); + } + taLog.log("SpamFilter jsonObj: ", JSON.stringify(jsonObj)); + //TODO save log + + //TODO move email if it's spam } } } From abeb16b0d99447206fbc3030da1dfef8b5186960 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 23:44:56 +0100 Subject: [PATCH 12/20] typo fixed --- mzta-background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mzta-background.js b/mzta-background.js index 753e1ddb..cce67829 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -751,7 +751,7 @@ const newEmailListener = (folder, messagesList) => { try{ jsonObj = extractJsonObject(spamfilter_result); }catch(e){ - console.error("[ThunderAI | SpamFilter ] Error extracting JSON from AI response: ", e); + console.error("[ThunderAI | SpamFilter] Error extracting JSON from AI response: ", e); } taLog.log("SpamFilter jsonObj: ", JSON.stringify(jsonObj)); //TODO save log From 35d1d3bdac79b24cb985a7a2bbe9f4951adf7776 Mon Sep 17 00:00:00 2001 From: mic Date: Wed, 12 Feb 2025 23:36:15 +0100 Subject: [PATCH 13/20] spamfilter_threshold option added. see #231 --- _locales/en/messages.json | 12 ++++++++++ options/mzta-options-default.js | 1 + pages/spamfilter/mzta-spamfilter.css | 32 +++++++++++++++++++++++++++ pages/spamfilter/mzta-spamfilter.html | 12 ++++++++++ 4 files changed, 57 insertions(+) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index e8790e2e..dd6c221e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1086,5 +1086,17 @@ "prompt_spamfilter_full_text" : { "message" : "Analyze the following email and determine if it is spam or not. Consider factors such as suspicious keywords, excessive promotional language, misleading subject lines, requests for personal information, and unusual sender addresses.\nProvide a value from 0 (not spam) to 100 (spam) and an explaination of no more than 10 words.\nGenerate a response in JSON format only. Do not include any additional text or explanation; provide only the JSON. Here is the format to be used:\n{\n\"spamValue\": ,\n\"explanation\": \"Brief explanation of your reasoning\",\n}\nHere there are the mail information:\nSender: \"{%author%}\"\nSubject: \"{%mail_subject%}\"\nHtml body: \"{%mail_html_body%}\"", "description" : "" + }, + "SpamFilter_prompt_prefs_title": { + "message": "Spam Filter Options", + "description": "" + }, + "prefs_OptionText_spamfilter_threshold": { + "message": "Spam threshold", + "description": "" + }, + "prefs_OptionText_spamfilter_threshold_Info": { + "message": "If the value returned by the AI is above this threshold, the email will be moved to the spam folder.", + "description": "" } } \ No newline at end of file diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index 4e396083..8129487b 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -52,4 +52,5 @@ export const prefs_default = { add_tags_auto_only_inbox: true, get_calendar_event: true, spamfilter: false, + spamfilter_threshold: 70, } \ No newline at end of file diff --git a/pages/spamfilter/mzta-spamfilter.css b/pages/spamfilter/mzta-spamfilter.css index 7d984c80..4e4a769a 100644 --- a/pages/spamfilter/mzta-spamfilter.css +++ b/pages/spamfilter/mzta-spamfilter.css @@ -18,6 +18,38 @@ justify-content: space-between; } +table#miczPrefs { + padding: 10px; + border-spacing: 0px; + width: 90%; + margin: 0px auto 40px auto; + border: 1px solid #ccc; +} + +.spamfilter_table_title{ + width: 90%; + margin: auto; +} + +.section_title{ + font-weight: bold; +} + +table#miczPrefs td{ + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + vertical-align: top; + padding: 2px; +} + +table#miczPrefs tr:first-child td { + border-top: none; +} + +table#miczPrefs tr:last-child td { + border-bottom: none; +} + .autocomplete-container { position: relative; } diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html index c717674f..1791e1b5 100644 --- a/pages/spamfilter/mzta-spamfilter.html +++ b/pages/spamfilter/mzta-spamfilter.html @@ -11,6 +11,18 @@

__MSG_SpamFilter_PageTitle__

__MSG_SpamFilter_info_default__

+
__MSG_SpamFilter_prompt_prefs_title__
+ + + + + +
__MSG_prefs_OptionText_spamfilter_threshold__ + +
__MSG_GetCalendarEvent_prompt_text_title__
__MSG_prefs_OptionText_btnManagePrompts_infoline__ __MSG_customPrompts_managePrompts_help__ From 9b1db0ed8ba8149b2ed159dbeed88f9ccf8435cc Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 13 Feb 2025 23:13:05 +0100 Subject: [PATCH 14/20] messagesMove is now an optional permission. see #231 --- _locales/en/messages.json | 2 +- manifest.json | 3 +++ options/mzta-options.js | 10 ++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index dd6c221e..15f4f918 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1052,7 +1052,7 @@ "description": "" }, "prefs_OptionText_spamfilter": { - "message": "Spam Filter", + "message": "Automatic Spam Filter", "description": "" }, "prefs_OptionText_spamfilter_Info": { diff --git a/manifest.json b/manifest.json index 4eaa8423..0e67ad92 100644 --- a/manifest.json +++ b/manifest.json @@ -36,6 +36,9 @@ "messagesUpdate", "https://*.chatgpt.com/*" ], + "optional_permissions": [ + "messagesMove" + ], "background": { "page": "mzta-background.html" }, diff --git a/options/mzta-options.js b/options/mzta-options.js index 04d390fd..1918f26c 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -307,6 +307,16 @@ document.addEventListener('DOMContentLoaded', async () => { let spamfilter_el = document.getElementById('spamfilter'); let spamfilter_info_btn = document.getElementById('btnManageSpamFilterInfo'); spamfilter_el.addEventListener('click', (event) => { + async function _spamfilter_el_change() { + if (event.target.checked) { + const granted = await messenger.permissions.request({ permissions: ["messagesMove"] }); + if (!granted) { + event.target.checked = false; + spamfilter_info_btn.disabled = 'disabled'; + } + } + } + _spamfilter_el_change(); spamfilter_info_btn.disabled = event.target.checked ? '' : 'disabled'; }); spamfilter_info_btn.disabled = spamfilter_el.checked ? '' : 'disabled'; From 5cdeae70dcb350c863ee8f8a147a07fcdfb0c8c0 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 14 Feb 2025 22:05:15 +0100 Subject: [PATCH 15/20] marking messages as spam and moving to the account spam folder. see #231 --- mzta-background.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index cce67829..b9aa30b4 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -25,6 +25,8 @@ import { taPromptUtils } from './js/mzta-utils-prompt.js'; import { mzta_specialCommand } from './js/mzta-special-commands.js'; import { getSpamFilterPrompt } from './js/mzta-prompts.js'; +//browser.permissions.remove({ permissions: ["messagesMove"] }).then(console.log); + await migrateCustomPromptsStorage(); await migrateDefaultPromptsPropStorage(); @@ -602,7 +604,7 @@ async function doGetCalendarEvent(get_calendar_event) { } async function reload_pref_init(){ - prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: true, get_calendar_event: true, connection_type: 'chatgpt_web', add_tags_auto: false, add_tags_auto_force_existing: false, add_tags_auto_only_inbox: true, spamfilter: false}); + prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: true, get_calendar_event: true, connection_type: 'chatgpt_web', add_tags_auto: false, add_tags_auto_force_existing: false, add_tags_auto_only_inbox: true, spamfilter: false, spamfilter_threshold: 70, dynamic_menu_force_enter: false}); } @@ -753,10 +755,19 @@ const newEmailListener = (folder, messagesList) => { }catch(e){ console.error("[ThunderAI | SpamFilter] Error extracting JSON from AI response: ", e); } - taLog.log("SpamFilter jsonObj: ", JSON.stringify(jsonObj)); + taLog.log("SpamFilter jsonObj: " + JSON.stringify(jsonObj)); //TODO save log //TODO move email if it's spam + if(jsonObj.spamValue >= prefs_init.spamfilter_threshold){ + // mark as spam + messenger.messages.update(message.id, { junk: true }); + //get the spam folder + let spamFolder = await messenger.folders.query({accountId: message.folder.accountId, specialUse: ['junk']}) + console.log(">>>>>>>>>>>> spamFolder: " + JSON.stringify(spamFolder)); + //move the message + messenger.messages.move([message.id], spamFolder[0].id); + } } } } From 720f810533c003c64c152d3a0522521911441710 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 14 Feb 2025 22:05:35 +0100 Subject: [PATCH 16/20] console.log debug statement commented out --- mzta-background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mzta-background.js b/mzta-background.js index b9aa30b4..97fa7649 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -764,7 +764,7 @@ const newEmailListener = (folder, messagesList) => { messenger.messages.update(message.id, { junk: true }); //get the spam folder let spamFolder = await messenger.folders.query({accountId: message.folder.accountId, specialUse: ['junk']}) - console.log(">>>>>>>>>>>> spamFolder: " + JSON.stringify(spamFolder)); + //console.log(">>>>>>>>>>>> spamFolder: " + JSON.stringify(spamFolder)); //move the message messenger.messages.move([message.id], spamFolder[0].id); } From f823d5c4b2e8c1c11a4e0ba5c22f33cd836113c9 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 14 Feb 2025 22:20:36 +0100 Subject: [PATCH 17/20] warning spam threshold too low added. see #231 --- _locales/en/messages.json | 8 ++++++++ pages/spamfilter/mzta-spamfilter.css | 8 ++++++++ pages/spamfilter/mzta-spamfilter.html | 2 +- pages/spamfilter/mzta-spamfilter.js | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 15f4f918..ea758ff0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1098,5 +1098,13 @@ "prefs_OptionText_spamfilter_threshold_Info": { "message": "If the value returned by the AI is above this threshold, the email will be moved to the spam folder.", "description": "" + }, + "spam_threshold_too_low": { + "message": "The spam threshold is too low! You will likely mark too much mails as spam!", + "description": "" + }, + "spam_threshold_zero": { + "message": "The spam threshold is zero! You will mark all mails as spam!", + "description": "" } } \ No newline at end of file diff --git a/pages/spamfilter/mzta-spamfilter.css b/pages/spamfilter/mzta-spamfilter.css index 4e4a769a..6a9bacc0 100644 --- a/pages/spamfilter/mzta-spamfilter.css +++ b/pages/spamfilter/mzta-spamfilter.css @@ -91,6 +91,14 @@ table#miczPrefs tr:last-child td { color: red; } +#spam_threshold_too_low{ + display: none; + color:red; + font-weight: bold; + font-size: 1.1em; + margin-left: 10px; +} + @media (prefers-color-scheme: dark) { body { diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html index 1791e1b5..4fafb0c9 100644 --- a/pages/spamfilter/mzta-spamfilter.html +++ b/pages/spamfilter/mzta-spamfilter.html @@ -17,7 +17,7 @@ __MSG_prefs_OptionText_spamfilter_threshold__ diff --git a/pages/spamfilter/mzta-spamfilter.js b/pages/spamfilter/mzta-spamfilter.js index 54fca13f..bec1afdb 100644 --- a/pages/spamfilter/mzta-spamfilter.js +++ b/pages/spamfilter/mzta-spamfilter.js @@ -34,6 +34,9 @@ document.addEventListener('DOMContentLoaded', async () => { element.addEventListener("change", saveOptions); }); + document.getElementById("spamfilter_threshold").addEventListener("input", check_spamfilter_threshold); + check_spamfilter_threshold({target: document.getElementById("spamfilter_threshold")}); + let spamfilter_textarea = document.getElementById('spamfilter_prompt_text'); let spamfilter_save_btn = document.getElementById('btn_save_prompt'); let spamfilter_reset_btn = document.getElementById('btn_reset_prompt'); @@ -77,6 +80,21 @@ document.addEventListener('DOMContentLoaded', async () => { }); +function check_spamfilter_threshold(event) { + let spam_threshold_too_low = document.getElementById("spam_threshold_too_low"); + if(event.target.value < 50){ + spam_threshold_too_low.style.display = "inline"; + if(event.target.value == 0){ + spam_threshold_too_low.textContent = browser.i18n.getMessage('spam_threshold_zero'); + spam_threshold_too_low.style.fontSize = "1.5em"; + }else{ + spam_threshold_too_low.textContent = browser.i18n.getMessage('spam_threshold_too_low'); + spam_threshold_too_low.style.fontSize = "1.1em"; + } + }else{ + spam_threshold_too_low.style.display = "none"; + } +} // Methods to manage options, derived from: /options/mzta-options.js From 943d997f72ae7adf5109c7f8fecb1857aebe4438 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 14 Feb 2025 22:36:26 +0100 Subject: [PATCH 18/20] typo fixed --- pages/spamfilter/mzta-spamfilter.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html index 4fafb0c9..293cf3fb 100644 --- a/pages/spamfilter/mzta-spamfilter.html +++ b/pages/spamfilter/mzta-spamfilter.html @@ -17,7 +17,7 @@ __MSG_prefs_OptionText_spamfilter_threshold__ From be6510f5fea2c29f18f7bf23479d7d038be5e287 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 14 Feb 2025 23:22:47 +0100 Subject: [PATCH 19/20] spam report added. see #231 --- _locales/en/messages.json | 48 +++++++++++++- js/mzta-spamreport.js | 91 +++++++++++++++++++++++++++ mzta-background.js | 77 +++++++++++++++-------- pages/spamfilter/mzta-spamfilter.css | 29 ++++++++- pages/spamfilter/mzta-spamfilter.html | 20 +++++- pages/spamfilter/mzta-spamfilter.js | 56 ++++++++++++++--- 6 files changed, 282 insertions(+), 39 deletions(-) create mode 100644 js/mzta-spamreport.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index ea758ff0..c325d267 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1099,12 +1099,56 @@ "message": "If the value returned by the AI is above this threshold, the email will be moved to the spam folder.", "description": "" }, - "spam_threshold_too_low": { + "spamfilter_threshold_too_low": { "message": "The spam threshold is too low! You will likely mark too much mails as spam!", "description": "" }, - "spam_threshold_zero": { + "spamfilter_threshold_zero": { "message": "The spam threshold is zero! You will mark all mails as spam!", "description": "" + }, + "spamfilter_no_reports": { + "message": "No messages have screened for spam yet. Here you'll find a list of the last 100 spam reports for the current session only.", + "description": "" + }, + "SpamReport_Title": { + "message": "Spam Filter Reports", + "description": "" + }, + "Date": { + "message": "Date", + "description": "" + }, + "From": { + "message": "From", + "description": "" + }, + "Subject": { + "message": "Subject", + "description": "" + }, + "Spam_Value": { + "message": "Spam Value", + "description": "" + }, + "Moved_to_Spam": { + "message": "Moved to Spam", + "description": "" + }, + "Explanation": { + "message": "Explanation", + "description": "" + }, + "Report_Date": { + "message": "Report Date", + "description": "" + }, + "spamfilter_moved": { + "message": "Yes", + "description": "" + }, + "spamfilter_not_moved": { + "message": "No", + "description": "" } } \ No newline at end of file diff --git a/js/mzta-spamreport.js b/js/mzta-spamreport.js new file mode 100644 index 00000000..308dad7b --- /dev/null +++ b/js/mzta-spamreport.js @@ -0,0 +1,91 @@ +/* + * ThunderAI [https://micz.it/thunderbird-addon-thunderai/] + * Copyright (C) 2024 - 2025 Mic (m@micz.it) + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export const taSpamReport = { + + logger: console, + _internal_data_id: 'mzta-spam-report-data', + _max_reports: 100, + + async saveReportData(data, data_id){ + let obj = await browser.storage.session.get(taSpamReport._internal_data_id); + //console.log(">>>>>>>> saveReportData obj 1: " + JSON.stringify(obj)); + if(obj == undefined){ + obj = {}; + obj[taSpamReport._internal_data_id] = {}; + //console.log(">>>>>>>> saveReportData obj 2a: " + JSON.stringify(obj)); + } + if(Object.keys(obj).length === 0){ + obj[taSpamReport._internal_data_id] = {}; + //console.log(">>>>>>>> saveReportData obj 2b: " + JSON.stringify(obj)); + } + + obj[taSpamReport._internal_data_id][data_id] = data; + //console.log(">>>>>>>> saveReportData obj 3: " + JSON.stringify(obj)); + await browser.storage.session.set(obj); + }, + + async loadReportData(data_id){ + let output = await browser.storage.session.get(taSpamReport._internal_data_id); + return output[taSpamReport._internal_data_id][data_id]; + }, + + async getAllReportData(){ + let data = await browser.storage.session.get(taSpamReport._internal_data_id); + //console.log(">>>>>>>>>>> getAllReportData: " + JSON.stringify(data)); + return data[taSpamReport._internal_data_id]; + }, + + saveAllData(data){ + browser.storage.session.set({[taSpamReport._internal_data_id]: data}); + }, + + clearReportData(){ + browser.storage.session.clear(); + }, + + async truncReportData(){ + let data = taSpamReport.sortReportsByDate(await taSpamReport.getAllReportData()); + if(data == undefined) return; + //console.log(">>>>>>>>>> truncReportData: " + JSON.stringify(data)); + if(Object.keys(data).length > taSpamReport._max_reports){ + let keys = Object.keys(data); + for(let i = taSpamReport._max_reports; i < keys.length; i++){ + // console.log(">>>>>>>>>> truncReportData: " + keys[i]); + // console.log(">>>>>>>>>> truncReportData i: " + i); + delete data[keys[i]]; + } + } + taSpamReport.saveAllData(data); + }, + + sortReportsByDate(data) { + if(data == undefined) return undefined; + const reportKeys = Object.keys(data); + reportKeys.sort((a, b) => { + const dateA = new Date(data[a].report_date); + const dateB = new Date(data[b].report_date); + return dateB - dateA; + }); + const sortedReports = {}; + reportKeys.forEach((key) => { + sortedReports[key] = data[key]; + }); + return sortedReports; + } +} \ No newline at end of file diff --git a/mzta-background.js b/mzta-background.js index 97fa7649..fe4e6878 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -24,6 +24,7 @@ import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditio import { taPromptUtils } from './js/mzta-utils-prompt.js'; import { mzta_specialCommand } from './js/mzta-special-commands.js'; import { getSpamFilterPrompt } from './js/mzta-prompts.js'; +import { taSpamReport } from './js/mzta-spamreport.js'; //browser.permissions.remove({ permissions: ["messagesMove"] }).then(console.log); @@ -660,34 +661,35 @@ const newEmailListener = (folder, messagesList) => { async function _newEmailListener(){ let messages = getMessages(messagesList); + taSpamReport.logger = taLog; + for await (let message of messages) { - let fullMessage = await browser.messages.getFull(message.id); + // let fullMessage = await browser.messages.getFull(message.id); + // taLog.log(`From: ${fullMessage.headers.from}`); + // taLog.log(`Subject: ${fullMessage.headers.subject}`); + // taLog.log(`Name: ${fullMessage.name}`); + // taLog.log(`Body: ${fullMessage.body}`); + // taLog.log(`contentType: [${fullMessage.contentType}]`); + // taLog.log(`fullMessage.parts.length: ${fullMessage.parts.length}`); - taLog.log(`From: ${fullMessage.headers.from}`); - taLog.log(`Subject: ${fullMessage.headers.subject}`); - taLog.log(`Name: ${fullMessage.name}`); - taLog.log(`Body: ${fullMessage.body}`); - taLog.log(`contentType: [${fullMessage.contentType}]`); - taLog.log(`fullMessage.parts.length: ${fullMessage.parts.length}`); - - if (fullMessage.parts.length > 0) { - for (let part of fullMessage.parts) { - taLog.log(`From: ${part.headers.from}`); - taLog.log(`Subject: ${part.headers.subject}`); - taLog.log(`Name: ${part.name}`); - taLog.log(`Body: ${part.body}`); - taLog.log(`contentType: [${part.contentType}]`); - taLog.log('contentType JSON: '+JSON.stringify(part.contentType)); - if (part.contentType.trim().toLowerCase() === "text/plain") { - taLog.log("Body (text/plain):" + part.body); - // return part.body; - } - if (part.contentType.trim().toLowerCase() === "text/html") { - taLog.log("Body (text/html):" + part.body); - // return part.body; - } - } - } + // if (fullMessage.parts.length > 0) { + // for (let part of fullMessage.parts) { + // taLog.log(`From: ${part.headers.from}`); + // taLog.log(`Subject: ${part.headers.subject}`); + // taLog.log(`Name: ${part.name}`); + // taLog.log(`Body: ${part.body}`); + // taLog.log(`contentType: [${part.contentType}]`); + // taLog.log('contentType JSON: '+JSON.stringify(part.contentType)); + // if (part.contentType.trim().toLowerCase() === "text/plain") { + // taLog.log("Body (text/plain):" + part.body); + // // return part.body; + // } + // if (part.contentType.trim().toLowerCase() === "text/html") { + // taLog.log("Body (text/html):" + part.body); + // // return part.body; + // } + // } + // } let curr_fullMessage = null; let msg_text = null; @@ -756,10 +758,22 @@ const newEmailListener = (folder, messagesList) => { console.error("[ThunderAI | SpamFilter] Error extracting JSON from AI response: ", e); } taLog.log("SpamFilter jsonObj: " + JSON.stringify(jsonObj)); - //TODO save log + + let report_data = {}; + report_data.report_date = new Date(); + report_data.headerMessageId = message.headerMessageId; + report_data.spamValue = jsonObj.spamValue; + report_data.explanation = jsonObj.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_init.spamfilter_threshold; + //console.log(">>>>>>>>>>>> report_data.SpamThreshold: " + JSON.stringify(report_data.SpamThreshold)); //TODO move email if it's spam if(jsonObj.spamValue >= prefs_init.spamfilter_threshold){ + taLog.log("Marking as spam ["+message.headerMessageId+"]"); // mark as spam messenger.messages.update(message.id, { junk: true }); //get the spam folder @@ -767,9 +781,18 @@ const newEmailListener = (folder, messagesList) => { //console.log(">>>>>>>>>>>> spamFolder: " + JSON.stringify(spamFolder)); //move the message messenger.messages.move([message.id], spamFolder[0].id); + report_data.moved = true; + taLog.log("Marked as spam ["+message.headerMessageId+"]"); } + + // Save the operation log + taSpamReport.saveReportData(report_data,message.headerMessageId); } } + + if(prefs_init.spamfilter){ + taSpamReport.truncReportData(); + } } return _newEmailListener(); diff --git a/pages/spamfilter/mzta-spamfilter.css b/pages/spamfilter/mzta-spamfilter.css index 6a9bacc0..e62e300d 100644 --- a/pages/spamfilter/mzta-spamfilter.css +++ b/pages/spamfilter/mzta-spamfilter.css @@ -91,7 +91,7 @@ table#miczPrefs tr:last-child td { color: red; } -#spam_threshold_too_low{ +#spamfilter_threshold_too_low{ display: none; color:red; font-weight: bold; @@ -99,6 +99,19 @@ table#miczPrefs tr:last-child td { margin-left: 10px; } +table#report_data { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} +table#report_data th, table#report_data td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} +table#report_data th { + background-color: #f4f4f4; +} @media (prefers-color-scheme: dark) { body { @@ -124,4 +137,18 @@ table#miczPrefs tr:last-child td { .autocomplete-list li.active { background-color: #4c4e58; } + + table#report_data th, + table#report_data td { + border-color: #444; + color: #ddd; + } + + table#report_data th { + background-color: #333; + } + + table#report_data td { + background-color: #222; + } } \ No newline at end of file diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html index 293cf3fb..2a2f2846 100644 --- a/pages/spamfilter/mzta-spamfilter.html +++ b/pages/spamfilter/mzta-spamfilter.html @@ -17,7 +17,7 @@ __MSG_prefs_OptionText_spamfilter_threshold__ @@ -35,6 +35,24 @@
+

__MSG_SpamReport_Title__

+ + + + + + + + + + + + + + + + +
Message_ID__MSG_Date____MSG_From____MSG_Subject____MSG_Spam_Value____MSG_Moved_to_Spam____MSG_Explanation____MSG_Report_Date__
diff --git a/pages/spamfilter/mzta-spamfilter.js b/pages/spamfilter/mzta-spamfilter.js index bec1afdb..4b979b94 100644 --- a/pages/spamfilter/mzta-spamfilter.js +++ b/pages/spamfilter/mzta-spamfilter.js @@ -21,9 +21,11 @@ import { taLogger } from '../../js/mzta-logger.js'; import { getSpecialPrompts, setSpecialPrompts } from "../../js/mzta-prompts.js"; import { getPlaceholders } from "../../js/mzta-placeholders.js"; import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js"; +import { taSpamReport } from '../../js/mzta-spamreport.js'; let autocompleteSuggestions = []; let taLog = new taLogger("mzta-spamfilter-page",true); +taSpamReport.logger = taLog; document.addEventListener('DOMContentLoaded', async () => { @@ -78,27 +80,65 @@ document.addEventListener('DOMContentLoaded', async () => { autocompleteSuggestions = (await getPlaceholders(true)).filter(p => !(p.id === 'additional_text')).map(p => ({command: '{%'+p.id+'%}', type: p.type})); textareaAutocomplete(spamfilter_textarea, autocompleteSuggestions, 1); // type_value = 1, only when reading an email + loadSpamReport() }); function check_spamfilter_threshold(event) { - let spam_threshold_too_low = document.getElementById("spam_threshold_too_low"); + let spamfilter_threshold_too_low = document.getElementById("spamfilter_threshold_too_low"); if(event.target.value < 50){ - spam_threshold_too_low.style.display = "inline"; + spamfilter_threshold_too_low.style.display = "inline"; if(event.target.value == 0){ - spam_threshold_too_low.textContent = browser.i18n.getMessage('spam_threshold_zero'); - spam_threshold_too_low.style.fontSize = "1.5em"; + spamfilter_threshold_too_low.textContent = browser.i18n.getMessage('spamfilter_threshold_zero'); + spamfilter_threshold_too_low.style.fontSize = "1.5em"; }else{ - spam_threshold_too_low.textContent = browser.i18n.getMessage('spam_threshold_too_low'); - spam_threshold_too_low.style.fontSize = "1.1em"; + spamfilter_threshold_too_low.textContent = browser.i18n.getMessage('spamfilter_threshold_too_low'); + spamfilter_threshold_too_low.style.fontSize = "1.1em"; } }else{ - spam_threshold_too_low.style.display = "none"; + spamfilter_threshold_too_low.style.display = "none"; } } +async function loadSpamReport(){ + let report_data = await taSpamReport.getAllReportData(); + console.log(">>>>>>>>>>>> loadSpamReport: " + JSON.stringify(report_data)); + //document.getElementById("report_data").textContent = JSON.stringify(report_data, null, 2); + if(report_data == undefined){ + document.getElementById("report_data").innerText = browser.i18n.getMessage("spamfilter_no_reports"); + }else{ + populateTable(report_data); + } +} + + // Function to populate the table + function populateTable(data) { + const tableBody = document.getElementById("report_data_body"); + tableBody.innerHTML = ""; // Clear table before inserting new data + + Object.keys(data).forEach(email => { + const report = data[email]; + + // Create a new row + const row = document.createElement("tr"); + + row.innerHTML = ` + ${report.headerMessageId} + ${new Date(report.message_date).toLocaleString()} + ${report.from.join(", ")} + ${report.subject.join(", ")} + ${report.spamValue} + ${report.moved?browser.i18n.getMessage("spamfilter_moved"):browser.i18n.getMessage("spamfilter_not_moved")} (${report.SpamThreshold}) + ${report.explanation} + ${new Date(report.report_date).toLocaleString()} + `; + + // Append the row to the table + tableBody.appendChild(row); + }); +} + // Methods to manage options, derived from: /options/mzta-options.js - function saveOptions(e) { e.preventDefault(); let options = {}; From 64520de118d06d1557472f0962d567abdb57bf34 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 14 Feb 2025 23:24:15 +0100 Subject: [PATCH 20/20] release notes updated --- CHANGELOG.md | 1 + options/mzta-release-notes.html | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c47f0da7..e9b53c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@

Version 3.2.0 - ??/??/2025

  • [ChatGPT API][Ollama API][OpenAI Comp API][Gemini API] Added an option to automatically tag incoming emails [#237].
  • +
  • [ChatGPT API][Ollama API][OpenAI Comp API][Gemini API] Added an configurable antispam filter for incoming emails [#231].
  • Added the {%thunderai_def_sign%} placeholder to get the default signature as defined in the options [#248].
  • Added the {%thunderai_def_lang%} placeholder to get the default language as defined in the options [#248].
  • Various minor improvements.
  • diff --git a/options/mzta-release-notes.html b/options/mzta-release-notes.html index 0ad7fb05..b824a085 100644 --- a/options/mzta-release-notes.html +++ b/options/mzta-release-notes.html @@ -10,6 +10,7 @@

    Version 3.2.0 - ??/??/2025

    • [ChatGPT API][Ollama API][OpenAI Comp API][Gemini API] Added an option to automatically tag incoming emails [#237].
    • +
    • [ChatGPT API][Ollama API][OpenAI Comp API][Gemini API] Added an configurable antispam filter for incoming emails [#231].
    • Added the {%thunderai_def_sign%} placeholder to get the default signature as defined in the options [#248].
    • Added the {%thunderai_def_lang%} placeholder to get the default language as defined in the options [#248].
    • Various minor improvements.