diff --git a/js/mzta-spamreport.js b/js/mzta-spamreport.js index f18a59f1..98d77b51 100644 --- a/js/mzta-spamreport.js +++ b/js/mzta-spamreport.js @@ -16,11 +16,20 @@ * along with this program. If not, see . */ +import { taStorage } from './mzta-storage.js'; + export const taSpamReport = { logger: console, + do_debug: false, _data_prefix: 'mzta-spam-report-', _processing_prefix: 'mzta-spam-processing-', _max_reports: 100, + _storage: null, + + _getStorage() { + if (!this._storage) this._storage = new taStorage(this.do_debug); + return this._storage; + }, async setProcessing(data_id) { const key = this._processing_prefix + data_id; @@ -34,8 +43,7 @@ export const taSpamReport = { }, async saveReportData(data, data_id) { - const key = this._data_prefix + data_id; - await browser.storage.session.set({ [key]: data }); + await this._getStorage().writeSpam(data_id, data, true); await browser.storage.session.remove(this._processing_prefix + data_id); }, @@ -51,47 +59,53 @@ export const taSpamReport = { }, async loadReportData(data_id) { - const key = this._data_prefix + data_id; - let output = await browser.storage.session.get(key); - return output[key] || null; + let record = await this._getStorage().getRecord(data_id); + if (!record || !this._getStorage().hasField(record, 'spam')) return null; + let spam = record.spam; + return { + headerMessageId: data_id, + spamValue: spam.spamValue, + explanation: spam.explanation, + report_date: new Date(spam.ts), + subject: spam.subject, + from: spam.from, + message_date: spam.message_date, + moved: spam.moved, + SpamThreshold: spam.SpamThreshold, + }; }, async removeReportData(data_id) { - const key = this._data_prefix + data_id; - await browser.storage.session.remove(key); + await this._getStorage().deleteSpamField(data_id); await browser.storage.session.remove(this._processing_prefix + data_id); }, async getAllReportData() { - let allData = await browser.storage.session.get(null); - let reportData = {}; - - for (const [key, value] of Object.entries(allData)) { - if (key.startsWith(this._data_prefix)) { - reportData[key.replace(this._data_prefix, '')] = value; - } - } - - return reportData; + return await this._getStorage().getAllSpamRecords(); }, async clearReportData() { - let allData = await browser.storage.session.get(null); - let keysToDelete = Object.keys(allData).filter(key => key.startsWith(this._data_prefix) || key.startsWith(this._processing_prefix)); - + let storage = this._getStorage(); + let allSpam = await storage.getAllSpamRecords(); + for (let messageId of Object.keys(allSpam)) { + await storage.deleteSpamField(messageId); + } + let allSession = await browser.storage.session.get(null); + let keysToDelete = Object.keys(allSession).filter(k => k.startsWith(this._processing_prefix)); for (let key of keysToDelete) { await browser.storage.session.remove(key); } }, async truncReportData() { - let data = await this.getAllReportData(); + let data = await this._getStorage().getAllSpamRecords(); let sortedData = this.sortReportsByDate(data); let keys = Object.keys(sortedData); if (keys.length > this._max_reports) { + let storage = this._getStorage(); for (let i = this._max_reports; i < keys.length; i++) { - await browser.storage.session.remove(this._data_prefix + keys[i]); + await storage.deleteSpamField(keys[i]); } } }, diff --git a/js/mzta-storage.js b/js/mzta-storage.js index 2decdd72..44fa9b28 100644 --- a/js/mzta-storage.js +++ b/js/mzta-storage.js @@ -72,11 +72,11 @@ export class taStorage { /** * Write the spam field for a given Message-ID. * @param {string} messageId - The Message-ID header string. - * @param {number} score - Spam score (float 0-1). - * @param {string} reason - Textual motivation for the score. + * @param {object} report_data - The full spam report object with fields: + * spamValue, explanation, subject, from, message_date, moved, SpamThreshold. * @param {boolean} [force=true] - If true, overwrite existing spam data. */ - async writeSpam(messageId, score, reason, force = true) { + async writeSpam(messageId, report_data, force = true) { try { let key = this._buildKey(messageId); let record = await this.getRecord(messageId) || { v: taStorage.SCHEMA_VERSION }; @@ -84,7 +84,18 @@ export class taStorage { return; } let now = Date.now(); - record.spam = { score: score, reason: reason, ts: now }; + record.spam = { + spamValue: report_data.spamValue, + explanation: report_data.explanation, + subject: report_data.subject, + from: report_data.from, + message_date: report_data.message_date instanceof Date + ? report_data.message_date.toISOString() + : report_data.message_date, + moved: report_data.moved, + SpamThreshold: report_data.SpamThreshold, + ts: now, + }; record.ts = now; await messenger.storage.local.set({ [key]: record }); } catch (e) { @@ -92,6 +103,60 @@ export class taStorage { } } + /** + * Get all records that contain a spam field. + * @returns {Promise} Map of messageId -> spam data object (legacy shape). + */ + async getAllSpamRecords() { + try { + let all = await messenger.storage.local.get(null); + let result = {}; + for (let [key, record] of Object.entries(all)) { + if (!key.startsWith(taStorage.STORAGE_KEY_PREFIX)) continue; + if (!this.hasField(record, 'spam')) continue; + let messageId = key.slice(taStorage.STORAGE_KEY_PREFIX.length); + let spam = record.spam; + result[messageId] = { + headerMessageId: messageId, + spamValue: spam.spamValue, + explanation: spam.explanation, + report_date: new Date(spam.ts), + subject: spam.subject, + from: spam.from, + message_date: spam.message_date, + moved: spam.moved, + SpamThreshold: spam.SpamThreshold, + }; + } + return result; + } catch (e) { + this.taLog.error('getAllSpamRecords error: ' + e); + return {}; + } + } + + /** + * Delete only the spam field from a record. + * Deletes the entire record if no other data fields remain. + * @param {string} messageId - The Message-ID header string. + */ + async deleteSpamField(messageId) { + try { + let key = this._buildKey(messageId); + let record = await this.getRecord(messageId); + if (!record || !('spam' in record)) return; + delete record.spam; + const remainingFields = Object.keys(record).filter(k => k !== 'v' && k !== 'ts'); + if (remainingFields.length === 0) { + await messenger.storage.local.remove(key); + } else { + await messenger.storage.local.set({ [key]: record }); + } + } catch (e) { + this.taLog.error('deleteSpamField error: ' + e); + } + } + /** * Write the summary field for a given Message-ID. * @param {string} messageId - The Message-ID header string. diff --git a/mzta-background.js b/mzta-background.js index 7c901ac3..ef90c50e 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1043,6 +1043,7 @@ const newEmailListener = (folder, messagesList) => { let messages = getMessages(messagesList); taSpamReport.logger = taLog; + taSpamReport.do_debug = prefs_init.do_debug; let add_tags_auto_enabled = prefs_init.add_tags && prefs_init.add_tags_auto;