spam report added. see #231
This commit is contained in:
parent
943d997f72
commit
be6510f5fe
6 changed files with 282 additions and 39 deletions
|
|
@ -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": ""
|
||||
}
|
||||
}
|
||||
91
js/mzta-spamreport.js
Normal file
91
js/mzta-spamreport.js
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<td><span>__MSG_prefs_OptionText_spamfilter_threshold__</span></td>
|
||||
<td>
|
||||
<label>
|
||||
<input type="number" id="spamfilter_threshold" name="spamfilter_threshold" min="0" max="100" class="option-input" /><span id="spam_threshold_too_low">__MSG_spam_threshold_too_low__</span>
|
||||
<input type="number" id="spamfilter_threshold" name="spamfilter_threshold" min="0" max="100" class="option-input" /><span id="spamfilter_threshold_too_low">__MSG_spamfilter_threshold_too_low__</span>
|
||||
<br>__MSG_prefs_OptionText_spamfilter_threshold_Info__
|
||||
</label>
|
||||
</td>
|
||||
|
|
@ -35,6 +35,24 @@
|
|||
<div id="spamfilter_info_additional_statements"></div>
|
||||
<div class="btn_div"><button id="btn_reset_prompt" disabled>__MSG_reset_default__</button><button id="btn_save_prompt" disabled>__MSG_save__</button></div>
|
||||
</div>
|
||||
<h2>__MSG_SpamReport_Title__</h2>
|
||||
<table id="report_data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Message_ID</th>
|
||||
<th>__MSG_Date__</th>
|
||||
<th>__MSG_From__</th>
|
||||
<th>__MSG_Subject__</th>
|
||||
<th>__MSG_Spam_Value__</th>
|
||||
<th>__MSG_Moved_to_Spam__</th>
|
||||
<th>__MSG_Explanation__</th>
|
||||
<th>__MSG_Report_Date__</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="report_data_body">
|
||||
<!-- Rows will be inserted dynamically -->
|
||||
</tbody>
|
||||
</table>
|
||||
<script src="mzta-spamfilter.js" type="module"></script>
|
||||
<script src="../../js/mzta-i18n.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -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 = `
|
||||
<td>${report.headerMessageId}</td>
|
||||
<td>${new Date(report.message_date).toLocaleString()}</td>
|
||||
<td>${report.from.join(", ")}</td>
|
||||
<td>${report.subject.join(", ")}</td>
|
||||
<td>${report.spamValue}</td>
|
||||
<td>${report.moved?browser.i18n.getMessage("spamfilter_moved"):browser.i18n.getMessage("spamfilter_not_moved")} (${report.SpamThreshold})</td>
|
||||
<td>${report.explanation}</td>
|
||||
<td>${new Date(report.report_date).toLocaleString()}</td>
|
||||
`;
|
||||
|
||||
// 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 = {};
|
||||
|
|
|
|||
Loading…
Reference in a new issue