### GNU GPL Notice for Interactive Mode Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/license.txt This snippet shows a sample notice that a program should display when it starts in interactive mode, informing the user about its warranty status and redistribution rights under the GNU GPL. ```text Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. ``` -------------------------------- ### Get User-Defined Preference Value with LegacyPrefs API Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/LegacyPrefs/README.md This example shows how to retrieve a user-specific preference value using getUserPref. It ignores default values and only returns explicitly set preferences, returning null if no user-defined value exists. This helps differentiate between system defaults and user customizations. ```javascript const userPrefValue = await browser.LegacyPrefs.getUserPref("user.setting.name"); console.log(userPrefValue); ``` -------------------------------- ### Listen for Preference Changes with LegacyPrefs API Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/LegacyPrefs/README.md This example shows how to register a listener for preference changes using onChanged.addListener. The listener function is called whenever a preference within the specified branch is modified, providing the name and new value of the changed preference. ```javascript browser.LegacyPrefs.onChanged.addListener(async (name, value) => { console.log(`Changed value in "mailnews.": ${name} = ${value}`); }, "mailnews."); ``` -------------------------------- ### Example Filtaquilla Filter Action for Printing Source: https://context7.com/realraven2000/filtaquilla/llms.txt This JavaScript object defines a Filtaquilla filter action named 'Print Message'. It checks if the PrintingTools NG addon is installed and uses it for printing if available; otherwise, it falls back to the default Thunderbird print functionality. This action is asynchronous and always valid for any message type. ```javascript const printAction = { id: "filtaquilla@mesquilla.com#print", name: "Print Message", applyAction: async function (aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) { const msgHdr = aMsgHdrs[0]; // Check if PrintingTools NG is available const hasPrintingTools = await isAddonInstalled( "PrintingToolsNG@cleidigh.kokkini.net" ); if (hasPrintingTools) { // Use PrintingTools NG for advanced printing const messageHeader = { id: msgHdr.messageId, subject: msgHdr.mime2DecodedSubject, author: msgHdr.mime2DecodedAuthor }; await printMessageWithPrintingTools(messageHeader); } else { // Fall back to built-in print window.PrintUtils.printWindow( msgHdr.folder.getUriForMsg(msgHdr), null ); } }, isValidForType: function (type, scope) { return true; }, validateActionValue: function (value, folder, type) { return null; }, isAsync: true }; ``` -------------------------------- ### Get Preference Value with Fallback using LegacyPrefs API Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/LegacyPrefs/README.md Demonstrates how to retrieve the value of a Thunderbird system preference using the getPref function. If the preference is not found, a fallback value (defaulting to null) is returned. This is useful for accessing settings that might not always be defined. ```javascript const prefValue = await browser.LegacyPrefs.getPref("some.preference.name", null); console.log(prefValue); ``` -------------------------------- ### Check if Thunderbird Addon is Installed Source: https://context7.com/realraven2000/filtaquilla/llms.txt This JavaScript function checks if a specified Thunderbird addon is installed and enabled. It uses the `messenger.management.get` API to retrieve addon information and returns `true` if the addon exists and is enabled, otherwise `false`. ```javascript async function isAddonInstalled(addonId) { try { const addon = await messenger.management.get(addonId); return addon && addon.enabled; } catch (e) { return false; } } ``` -------------------------------- ### Integrate SmartTemplate for Message Forwarding Source: https://context7.com/realraven2000/filtaquilla/llms.txt This JavaScript function forwards an email using the SmartTemplate addon. It requires the SmartTemplate addon to be installed. The function sends a 'forwardMessage' command along with the message ID and a specified template ID, returning the result from the addon. ```javascript async function forwardWithSmartTemplate(messageHeader, templateId) { const SMARTTEMPLATE_ID = "smarttemplate4@thunderbird.extension"; try { const result = await messenger.runtime.sendMessage( SMARTTEMPLATE_ID, { command: "forwardMessage", messageId: messageHeader.id, templateId: templateId }, {} ); return result; } catch (e) { console.error("SmartTemplate not available:", e); return { success: false, error: e.message }; } } ``` -------------------------------- ### Integrate PrintingTools NG for Message Printing Source: https://context7.com/realraven2000/filtaquilla/llms.txt This JavaScript function sends a message to the PrintingTools NG addon to print an email. It requires the addon to be installed and enabled. The function sends a command 'printMessage' with the message header details and returns a success or error status from the addon. ```javascript async function printMessageWithPrintingTools(messageHeader) { const PRINTING_TOOLS_ID = "PrintingToolsNG@cleidigh.kokkini.net"; try { const result = await messenger.runtime.sendMessage( PRINTING_TOOLS_ID, { command: "printMessage", messageHeader: messageHeader }, {} // options parameter required for extensionId ); if (result.success) { console.log("Message printed successfully"); } else { console.error("Print failed:", result.error); } return result; } catch (e) { console.error("PrintingTools NG not installed or unavailable:", e); return { success: false, error: "Addon not available" }; } } ``` -------------------------------- ### GNU GPL Notice for Source Files Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/license.txt This snippet provides the standard notice to be included at the beginning of each source file when licensing a program under the GNU General Public License. It specifies the copyright holder, year, distribution terms, and warranty disclaimer. ```text Copyright (C) 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 . ``` -------------------------------- ### Manifest Configuration for LegacyPrefs API Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/LegacyPrefs/README.md This snippet shows how to declare the LegacyPrefs API in your add-on's manifest.json file. It specifies the schema and implementation paths for the API, making it available for use within your extension. ```json { "experiment_apis": { "LegacyPrefs": { "schema": "api/LegacyPrefs/schema.json", "parent": { "scopes": ["addon_parent"], "paths": [["LegacyPrefs"]], "script": "api/LegacyPrefs/implementation.js" } } } } ``` -------------------------------- ### Receiving Notifications in Background Page (JavaScript) Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/NotifyTools/README.md Demonstrates how to set up a listener in the WebExtension's background page to receive notifications from experiment scripts using the `onNotifyBackground` event. It includes a basic switch case for handling different commands. ```javascript messenger.NotifyTools.onNotifyBackground.addListener(async (info) => { switch (info.command) { case "doSomething": //do something let rv = await doSomething(); return rv; break; } }); ``` -------------------------------- ### NotifyTools - Receiving Notifications from Experiment Scripts Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/NotifyTools/README.md This section details how to set up a listener in your WebExtension's background page to receive notifications from Experiment scripts using the `onNotifyBackground` event. ```APIDOC ## NotifyTools - Receiving Notifications from Experiment Scripts ### Description Set up a listener in your WebExtension's background page to receive notifications sent from Experiment scripts. The `onNotifyBackground` event handles incoming commands and allows for asynchronous responses. ### Method `messenger.NotifyTools.onNotifyBackground.addListener` ### Endpoint N/A (Event Listener) ### Parameters #### Event Listener Parameters - **info** (object) - Required - An object containing the command and any associated data sent from the Experiment script. - **command** (string) - The specific command to be executed. - **...other_data** (any) - Additional data payload for the command. #### Request Body (from Experiment Script) ```json { "command": "doSomething", "...other_data": "..." } ``` ### Request Example (Experiment Script Sending Notification) ```javascript // Include notifyTools.js script in your Experiment script notifyTools.notifyBackground({command: "doSomething", data: "someValue"}).then((data) => { console.log(data); }); ``` ### Response #### Success Response (from Listener to Experiment Script) - **rv** (any) - The return value from the executed command, sent back to the Experiment script. #### Response Example (from Listener) ```json { "result": "success", "processedData": "..." } ``` **Note**: If multiple `onNotifyBackground` listeners are registered and more than one returns data, only the first returned value is sent back to the Experiment script. Ensure only one listener responds per request. ``` -------------------------------- ### LegacyPrefs API Functions Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/LegacyPrefs/README.md This section details the functions available in the LegacyPrefs API for interacting with Thunderbird preferences. ```APIDOC ## LegacyPrefs API ### Description Use this API to access Thunderbird's system preferences or to migrate your own preferences from the Thunderbird preference system to the local storage of your MailExtension. ### Setup Add the [LegacyPrefs API](https://github.com/thunderbird/addon-developer-support/tree/master/auxiliary-apis/LegacyPrefs) to your add-on. Your `manifest.json` needs an entry like this: ```json "experiment_apis": { "LegacyPrefs": { "schema": "api/LegacyPrefs/schema.json", "parent": { "scopes": ["addon_parent"], "paths": [["LegacyPrefs"]], "script": "api/LegacyPrefs/implementation.js" } } }, ``` ### API Functions #### async getPref(aName, [aFallback]) * **Description**: Returns the value for the `aName` preference. If it is not defined or has no default value assigned, `aFallback` will be returned (which defaults to `null`). * **Method**: GET * **Endpoint**: `browser.LegacyPrefs.getPref` * **Parameters**: * **Path Parameters**: * None * **Query Parameters**: * `aName` (string) - Required - The name of the preference to retrieve. * `aFallback` (any) - Optional - The fallback value to return if the preference is not found. * **Request Example**: ```javascript const prefValue = await browser.LegacyPrefs.getPref("browser.tabs.closeWindowWithLastTab", false); ``` * **Response**: * **Success Response (200)**: * `value` (any) - The value of the preference. * **Response Example**: ```json { "value": false } ``` #### async getUserPref(aName) * **Description**: Returns the user defined value for the `aName` preference. This will ignore any defined default value and will only return an explicitly set value, which differs from the default. Otherwise it will return `null`. * **Method**: GET * **Endpoint**: `browser.LegacyPrefs.getUserPref` * **Parameters**: * **Path Parameters**: * None * **Query Parameters**: * `aName` (string) - Required - The name of the preference to retrieve. * **Request Example**: ```javascript const userPrefValue = await browser.LegacyPrefs.getUserPref("mail.defaultFolder"); ``` * **Response**: * **Success Response (200)**: * `value` (any) - The user-defined value of the preference. * **Response Example**: ```json { "value": "/path/to/default/folder" } ``` #### clearUserPref(aName) * **Description**: Clears the user defined value for preference `aName`. Subsequent calls to `getUserPref(aName)` will return `null`. * **Method**: DELETE * **Endpoint**: `browser.LegacyPrefs.clearUserPref` * **Parameters**: * **Path Parameters**: * None * **Query Parameters**: * `aName` (string) - Required - The name of the preference to clear. * **Request Example**: ```javascript browser.LegacyPrefs.clearUserPref("extensions.some.setting"); ``` * **Response**: * **Success Response (200)**: * Indicates the preference was cleared successfully. * **Response Example**: ```json { "success": true } ``` #### async setPref(aName, aValue) * **Description**: Set the `aName` preference to the given value. Will return false and log an error to the console, if the type of `aValue` does not match the type of the preference. * **Method**: POST * **Endpoint**: `browser.LegacyPrefs.setPref` * **Parameters**: * **Path Parameters**: * None * **Query Parameters**: * None * **Request Body**: * `aName` (string) - Required - The name of the preference to set. * `aValue` (any) - Required - The new value for the preference. * **Request Example**: ```javascript const setResult = await browser.LegacyPrefs.setPref("browser.newtabpage.enabled", true); ``` * **Response**: * **Success Response (200)**: * `success` (boolean) - True if the preference was set successfully, false otherwise. * **Response Example**: ```json { "success": true } ``` ``` -------------------------------- ### Manage Thunderbird Windows with WindowListener API (JavaScript) Source: https://context7.com/realraven2000/filtaquilla/llms.txt This snippet shows how to use the WindowListener API to manage scripts and preferences associated with specific Thunderbird windows. It includes registering custom chrome URLs, scripts for window opening and closing, startup and shutdown scripts, and default preferences. The API also allows listening for window events and waiting for the master password. Dependencies include the 'messenger' API. ```javascript // Register chrome URLs for custom content messenger.WindowListener.registerChromeUrl([ ["content", "filtaquilla", "chrome://filtaquilla/content/"], ["locale", "filtaquilla", "chrome://filtaquilla/locale/"] ]); // Register script to run when window opens messenger.WindowListener.registerWindow( "chrome://messenger/content/messenger.xhtml", // Thunderbird main window "chrome://filtaquilla/content/scripts/filtaquilla-messenger.js" ); messenger.WindowListener.registerWindow( "chrome://messenger/content/FilterEditor.xhtml", // Filter editor window "chrome://filtaquilla/content/fq_FilterEditor.js" ); // Register startup script (runs once on extension load) messenger.WindowListener.registerStartupScript( "chrome://filtaquilla/content/filtaquilla-startup.js" ); // Register shutdown script (runs on extension unload) messenger.WindowListener.registerShutdownScript( "chrome://filtaquilla/content/filtaquilla-shutdown.js" ); // Load default preferences messenger.WindowListener.registerDefaultPrefs( "defaults/preferences/filtaquilla.js" ); // Start listening for window events await messenger.WindowListener.startListening(); // Wait for master password if needed await messenger.WindowListener.waitForMasterPassword(); async function initializeExtension() { messenger.WindowListener.registerChromeUrl([ ["content", "filtaquilla", "chrome://filtaquilla/content/"], ["locale", "filtaquilla", "chrome://filtaquilla/locale/"], ["skin", "filtaquilla", "chrome://filtaquilla/skin/"] ]); messenger.WindowListener.registerDefaultPrefs( "defaults/preferences/filtaquilla.js" ); const windows = [ ["chrome://messenger/content/messenger.xhtml", "filtaquilla-messenger.js"], ["chrome://messenger/content/FilterEditor.xhtml", "fq_FilterEditor.js"], ["chrome://messenger/content/FilterListDialog.xhtml", "fq_FilterList.js"] ]; for (const [windowUrl, scriptFile] of windows) { messenger.WindowListener.registerWindow( windowUrl, `chrome://filtaquilla/content/scripts/${scriptFile}` ); } messenger.WindowListener.registerStartupScript( "chrome://filtaquilla/content/filtaquilla-startup.js" ); messenger.WindowListener.registerShutdownScript( "chrome://filtaquilla/content/filtaquilla-shutdown.js" ); await messenger.WindowListener.startListening(); await messenger.WindowListener.waitForMasterPassword(); console.log("FiltaQuilla extension initialized"); } ``` -------------------------------- ### LegacyPrefs API Events Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/LegacyPrefs/README.md This section details the events provided by the LegacyPrefs API for monitoring preference changes. ```APIDOC ### API Events #### onChanged.addListener(listener, branch) * **Description**: Register a listener which is notified each time a value in the specified branch is changed. The listener returns the name and the new value of the changed preference. * **Method**: SUBSCRIBE * **Endpoint**: `browser.LegacyPrefs.onChanged.addListener` * **Parameters**: * **Path Parameters**: * None * **Query Parameters**: * None * **Listener Arguments**: * `listener` (function) - Required - The callback function to execute when a preference changes. It receives `name` (string) and `value` (any) as arguments. * `branch` (string) - Required - The preference branch to monitor (e.g., `"mailnews."`). * **Example**: ```javascript browser.LegacyPrefs.onChanged.addListener(async (name, value) => { console.log(`Changed value in "mailnews.": ${name} = ${value}`); }, "mailnews."); ``` * **Response**: * No direct response, registers a listener. ``` -------------------------------- ### NotifyTools - Sending Notifications to Experiment Scripts Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/NotifyTools/README.md This section explains how to send notifications from the WebExtension's background page to Experiment scripts using the `notifyExperiment` method. ```APIDOC ## NotifyTools - Sending Notifications to Experiment Scripts ### Description Send a notification from the WebExtension's background page to Experiment scripts. The receiving Experiment script must have the `notifyTools.js` script included and set up a listener. ### Method `messenger.NotifyTools.notifyExperiment` ### Endpoint N/A (Method Call) ### Parameters #### Request Body - **notification** (object) - Required - An object containing the command and any associated data to send to the Experiment script. - **command** (string) - The specific command to be executed in the Experiment script. - **...other_data** (any) - Additional data payload for the command. ### Request Example (Background Page Sending Notification) ```javascript messenger.NotifyTools.notifyExperiment({ command: "processData", payload: { id: 123, value: "test" } }).then((data) => { console.log(data); // Data returned from the Experiment script listener }); ``` ### Response #### Success Response (200 - from Experiment Script Listener) - **data** (any) - The data returned from the listener in the Experiment script. #### Response Example (from Experiment Script Listener) ```json { "status": "completed", "result": "processed successfully" } ``` **Note**: Ensure the `notifyTools.js` script is included in your Experiment script and that a corresponding listener is set up to handle incoming `notifyExperiment` calls. ``` -------------------------------- ### Sending Notifications from Background Page (JavaScript) Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/NotifyTools/README.md Illustrates how to send a notification from the WebExtension's background page to experiment scripts using the `notifyExperiment()` method. The method returns a promise that resolves with any data sent back from the experiment script. ```javascript messenger.NotifyTools.notifyExperiment({command: "doSomething"}).then((data) => { console.log(data) }); ``` -------------------------------- ### Sending Notifications from Experiment Script (JavaScript) Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/NotifyTools/README.md This code snippet shows how to send a notification from an experiment script to the WebExtension's background page using the `notifyTools.js` script and its `notifyBackground` method. It expects a promise that resolves with the data returned from the background. ```javascript notifyTools.notifyBackground({command: "doSomething"}).then((data) => { console.log(data); }); ``` -------------------------------- ### NotifyTools Manifest Configuration Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/NotifyTools/README.md This snippet shows the necessary configuration in the `manifest.json` file to integrate NotifyTools into your Thunderbird add-on. It specifies the schema, parent script, and event listeners for the NotifyTools API. ```json { "experiment_apis": { "NotifyTools": { "schema": "api/NotifyTools/schema.json", "parent": { "scopes": ["addon_parent"], "paths": [["NotifyTools"]], "script": "api/NotifyTools/implementation.js", "events": ["startup"] } } } } ``` -------------------------------- ### Run External File Action with Token Replacement (JavaScript) Source: https://context7.com/realraven2000/filtaquilla/llms.txt Executes an external program, passing message data as parameters. Supports token replacement for dynamic parameter generation and allows specifying character encoding. Input is a string 'filepath|encoding|parameters'. ```javascript const runFileAction = { id: "filtaquilla@mesquilla.com#runFile", name: "Run File", applyAction: function (aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) { const msgHdr = aMsgHdrs[0]; // Parse action value: format is "filepath|encoding|parameters" const parts = aActionValue.split("|"); const filepath = parts[0]; const encoding = parts[1] || "UTF-8"; // Default to UTF-8 const parameters = parts[2] || ""; // Token replacement patterns const tokens = { "@SUBJECT@": msgHdr.mime2DecodedSubject, "@AUTHOR@": msgHdr.mime2DecodedAuthor, "@RECIPIENTS@": msgHdr.mime2DecodedRecipients, "@DATE@": new Date(msgHdr.date / 1000).toISOString(), "@MESSAGEURI@": msgHdr.folder.getUriForMsg(msgHdr), "@MESSAGEID@": msgHdr.messageId, "@FOLDER@": msgHdr.folder.prettyName, "@ACCOUNTNAME@": msgHdr.folder.server.prettyName }; // Replace all tokens in parameters let processedParams = parameters; for (const [token, value] of Object.entries(tokens)) { processedParams = processedParams.replace(new RegExp(token, "g"), value); } // Create process const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.initWithPath(filepath); const process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); process.init(file); // Convert parameters to array, respecting quoted strings const args = processedParams.match(/(?:[^\s"]+|"[^"]*")+/g) || []; const cleanArgs = args.map(arg => arg.replace(/^"|"$/g, "")); // Run process asynchronously process.runAsync(cleanArgs, cleanArgs.length); }, isValidForType: function (type, scope) { return true; }, validateActionValue: function (value, folder, type) { const parts = value.split("|"); if (!parts[0]) return "File path required"; try { const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.initWithPath(parts[0]); if (!file.exists()) return "File does not exist"; if (!file.isExecutable()) return "File is not executable"; } catch (e) { return "Invalid file path: " + e.message; } return null; }, needsBody: false, allowDuplicates: true }; ``` -------------------------------- ### Set Preference Value with LegacyPrefs API Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/LegacyPrefs/README.md This snippet demonstrates how to set a Thunderbird system preference using the setPref function. It takes a preference name and a value. The API will return false and log an error if the data type of the provided value does not match the preference's expected type. ```javascript const success = await browser.LegacyPrefs.setPref("some.preference.name", "someValue"); if (!success) { console.error("Failed to set preference."); } ``` -------------------------------- ### JavaScript Version Comparison and Feature Detection for Thunderbird Source: https://context7.com/realraven2000/filtaquilla/llms.txt Provides JavaScript functions to compare Thunderbird versions and detect feature availability. It includes utilities for version comparison, checking if a version is greater than or equal to a specific release, and conditional initialization of features based on the detected Thunderbird version. This helps in adapting the extension's behavior to support older and newer versions of Thunderbird. ```javascript function compareVersions(v1, v2) { const v1Parts = v1.split(".").map(Number); const v2Parts = v2.split(".").map(Number); const maxLength = Math.max(v1Parts.length, v2Parts.length); for (let i = 0; i < maxLength; i++) { const part1 = v1Parts[i] || 0; const part2 = v2Parts[i] || 0; if (part1 > part2) return 1; if (part1 < part2) return -1; } return 0; } function greaterThan(v1, v2) { return compareVersions(v1, v2) > 0; } function versionGreaterOrEqual(v1, v2) { return compareVersions(v1, v2) >= 0; } async function initializeWithVersionCheck() { const info = await browser.runtime.getBrowserInfo(); const tbVersion = info.version; console.log(`Thunderbird version: ${tbVersion}`); const features = { hasAttachmentHeaders: greaterThan(tbVersion, "135.0"), hasCompose3PaneAPI: greaterThan(tbVersion, "128.0"), hasNewMessagesAPI: versionGreaterOrEqual(tbVersion, "130.0"), supportsWebExtExperiments: true // All supported versions }; if (features.hasAttachmentHeaders) { console.log("Using native attachment headers"); await initializeAttachmentHandling(); } else { console.log("Using workaround for attachment headers"); await initializeAttachmentHandlingLegacy(); } return features; } async function listAttachmentsCompat(messageId) { const info = await browser.runtime.getBrowserInfo(); const isPrerelease = !greaterThan(info.version, "135.0"); const attachments = await browser.messages.listAttachments(messageId); if (isPrerelease) { await addHeaders(attachments, messageId); } return attachments; } async function addHeaders(attachments, messageId) { const message = await browser.messages.get(messageId); for (const at of attachments) { at.myMessageId = messageId; at.messageSubject = message.subject; at.messageAuthor = message.author; at.messageDate = message.date; } } async function shouldEnableFeature(featureName) { const info = await browser.runtime.getBrowserInfo(); const tbVersion = info.version; const featureRequirements = { "saveAttachment": "128.0", "detachAttachments": "130.0", "javascriptAction": "128.0", "print": "128.0" }; const requiredVersion = featureRequirements[featureName]; if (!requiredVersion) return true; return versionGreaterOrEqual(tbVersion, requiredVersion); } ``` -------------------------------- ### Manage Thunderbird Preferences with LegacyPrefs API (JavaScript) Source: https://context7.com/realraven2000/filtaquilla/llms.txt This snippet demonstrates how to interact with Thunderbird preferences using the LegacyPrefs API in a WebExtension context. It covers reading preferences with fallback values, setting preference values with specified types, creating new preferences, and listening for preference changes. Dependencies include the 'messenger' API provided by Thunderbird extensions. It handles boolean, integer, and string preference types. ```javascript const debugEnabled = await messenger.LegacyPrefs.getPref( "extensions.filtaquilla.debug", false // fallback value if pref doesn't exist ); await messenger.LegacyPrefs.setPref( "extensions.filtaquilla.saveAttachment.enabled", true, "bool" // type: "bool", "int", "string" ); await messenger.LegacyPrefs.createPref( "extensions.filtaquilla.custom.setting", "defaultValue", "string" ); messenger.LegacyPrefs.onChanged.addListener("extensions.filtaquilla", (name, value) => { console.log(`Preference changed: ${name} = ${value}`); if (name === "extensions.filtaquilla.debug") { // React to debug setting change enableDebugLogging(value); } }); async function initializeSettings() { const features = { subjectAppend: await messenger.LegacyPrefs.getPref( "extensions.filtaquilla.subjectAppend.enabled", false ), saveAttachment: await messenger.LegacyPrefs.getPref( "extensions.filtaquilla.saveAttachment.enabled", false ), javascriptAction: await messenger.LegacyPrefs.getPref( "extensions.filtaquilla.javascriptAction.enabled", false ), maxThreadScan: await messenger.LegacyPrefs.getPref( "extensions.filtaquilla.maxthreadscan", 20 ) }; messenger.LegacyPrefs.onChanged.addListener( "extensions.filtaquilla", async (prefName, newValue) => { console.log(`Pref changed: ${prefName} = ${newValue}`); if (prefName.endsWith(".enabled")) { const featureName = prefName.split(".")[2]; await reloadFeature(featureName, newValue); } } ); return features; } ``` -------------------------------- ### Add a listener for WebExtension notifications Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/NotifyTools/README.md The `addListener` function registers a callback function to be invoked upon receiving a notification from the WebExtension's background page. It returns a unique ID that can be used later to remove the listener. Note that NotifyTools has slightly different behavior regarding non-Promise return values compared to `runtime.sendMessage`. ```javascript function doSomething(data) { console.log(data); return true; } let id = notifyTools.addListener(doSomething); ``` -------------------------------- ### JavaScript: Bidirectional Messaging with NotifyTools API Source: https://context7.com/realraven2000/filtaquilla/llms.txt Demonstrates using the NotifyTools API for bidirectional communication between browser extension contexts (like background scripts) and content scripts. It covers listening for notifications from content scripts and sending notifications to them, enabling features like saving attachments or printing messages. ```javascript // In background script - listen for notifications from content scripts messenger.NotifyTools.onNotifyBackground.addListener(async (data) => { console.log("Received notification:", data.func); switch(data.func) { case "saveAttachments": return await handleSaveAttachments(data); case "printMessage": return await handlePrintMessage(data); case "showMessage": return await showFQmessage(data.msgIds, data.features, data.msg); default: console.warn("Unknown notification function:", data.func); return { success: false, error: "Unknown function" }; } }); // In content script - send notification to background async function saveMessageAttachments(messageHeader, savePath) { try { const result = await messenger.NotifyTools.notifyExperiment({ func: "saveAttachments", messageHeader: messageHeader, path: savePath }); if (result.success) { console.log(`Saved ${result.attachments.length} attachments`); result.attachments.forEach(at => { console.log(` ${at.fileName}: ${at.success ? "OK" : "FAILED"}`); }); } return result; } catch (e) { console.error("Save attachments failed:", e); return { success: false, error: e.message }; } } // Example usage in filter action messenger.NotifyTools.notifyExperiment({ func: "saveAttachments", messageHeader: { id: 123, subject: "Email with attachments", author: "sender@example.com" }, path: "/home/user/mail-attachments" }); ``` -------------------------------- ### Save Files to Disk with FiltaQuilla API (JavaScript) Source: https://context7.com/realraven2000/filtaquilla/llms.txt Provides functions to save File objects to disk using the FiltaQuilla API. It includes a helper function to sanitize filenames by removing dangerous characters and limiting their length. This functionality is crucial for securely handling file attachments. ```javascript async function saveAttachmentFile(file, targetPath) { // file is a File object from browser.messages.getAttachmentFile() // targetPath is the directory path (e.g., "/home/user/attachments") const success = await messenger.FiltaQuilla.saveFile( file, targetPath, file.name // optional custom filename ); if (success) { console.log(`Saved ${file.name} to ${targetPath}`); } else { console.error(`Failed to save ${file.name}`); } return success; } // Complete attachment save workflow async function handleAttachmentSave(messageId, savePath) { const attachments = await browser.messages.listAttachments(messageId); const results = []; for (const attachment of attachments) { // Skip inline content if (attachment.contentType.startsWith("text/")) continue; if (attachment.size === 0) continue; // Get attachment file const file = await browser.messages.getAttachmentFile( messageId, attachment.partName ); // Sanitize filename and save const success = await messenger.FiltaQuilla.saveFile( file, savePath, sanitizeFilename(file.name) ); results.push({ name: file.name, size: file.size, type: file.type, saved: success }); } return results; } // Helper function to sanitize filename function sanitizeFilename(filename) { // Remove dangerous characters let safe = filename.replace(/[<>:"|?*\/\\]/g, "_"); // Limit length (from preferences) const maxLength = 60; // extensions.filtaquilla.fileNames.maxLength if (safe.length > maxLength) { const ext = safe.substring(safe.lastIndexOf(".")); safe = safe.substring(0, maxLength - ext.length) + ext; } return safe; } // Open options dialog messenger.FiltaQuilla.showOptions(); // Open about:config with filter messenger.FiltaQuilla.showAboutConfig("extensions.filtaquilla"); ``` -------------------------------- ### Set the WebExtension add-on ID Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/NotifyTools/README.md The `setAddOnId` function allows you to specify the ID of your add-on. This is necessary for the `notifyTools.js` script to correctly listen for messages. Alternatively, the add-on ID can be hardcoded in the script. ```javascript notifyTools.setAddOnId(add-on-id); ``` -------------------------------- ### Display Custom Message Dialogs with FiltaQuilla API (JavaScript) Source: https://context7.com/realraven2000/filtaquilla/llms.txt Enables the display of custom dialogs to users, allowing configurable buttons and capturing user responses. This is achieved by creating popup windows with specific HTML content and handling messages from the popup. It supports passing custom messages via local storage. ```javascript // Display message dialog with buttons async function showFQmessage(messageIds, features, message = "") { const MESSAGE_STORAGE_KEY = "filtaquilla-message-content"; const url = new URL(browser.runtime.getURL("html/fq-message.html")); // Store message in local storage if provided if (message) { await browser.storage.local.set({ [MESSAGE_STORAGE_KEY]: message }); url.searchParams.set("msg_storage", "true"); } // Pass message IDs and button features url.searchParams.set("msgId", messageIds); url.searchParams.set("features", features.join(",")); // Create popup window const winRet = await messenger.windows.create({ type: "popup", url: url.toString(), width: 660, height: 480, allowScriptsToClose: true }); const tabId = winRet.tabs[0].id; // Wait for user to click a button return new Promise((resolve) => { const listener = async (message, sender) => { if (sender.tab?.id === tabId && message.command === "filtaquilla-message") { browser.runtime.onMessage.removeListener(listener); // Clean up storage await browser.storage.local.remove(MESSAGE_STORAGE_KEY); // Return button clicked resolve(message.result); // Close window await messenger.windows.remove(winRet.id); } }; browser.runtime.onMessage.addListener(listener); }); } // Example usage: Show update notification const buttonClicked = await showFQmessage( "5.5.0", // message ID ["ok", "cancel", "changeLog"], // available buttons `

FiltaQuilla Updated to 5.5.0

New features:

  • Support for Thunderbird 128+
  • Improved attachment handling
  • New regex search conditions

Click "Change Log" to see full details.

` ); if (buttonClicked === "changeLog") { browser.tabs.create({ url: "https://github.com/RealRaven2000/FiltaQuilla/releases" }); } else if (buttonClicked === "ok") { console.log("User acknowledged update"); } // Show confirmation dialog async function confirmAction(message) { const result = await showFQmessage( "confirm", ["ok", "cancel"], message ); return result === "ok"; } // Example: Confirm before dangerous action if (await confirmAction("

This will delete all messages. Continue?

")) { // Proceed with deletion } ``` -------------------------------- ### Clear User Preference with LegacyPrefs API Source: https://github.com/realraven2000/filtaquilla/blob/ESR128/content/api/LegacyPrefs/README.md Illustrates how to remove a user-defined preference using clearUserPref. After this function is called, subsequent calls to getUserPref for the same preference name will return null, effectively resetting it to its default or unset state. ```javascript browser.LegacyPrefs.clearUserPref("user.setting.name"); ``` -------------------------------- ### JavaScript: Custom Regex Search Terms for Email Filters Source: https://context7.com/realraven2000/filtaquilla/llms.txt Defines custom search terms for email filters using JavaScript. It includes logic for matching subjects and bodies against regular expressions, supporting case-insensitive and multiline matching. These terms can be registered with a filter service to create advanced search conditions. ```javascript // Subject regex search term const subjectRegexTerm = { id: "filtaquilla@mesquilla.com#SubjectRegex", name: "Subject matches regex", scope: Components.interfaces.nsMsgSearchScope.allSearchableGroups, getEnabled: function(scope, op) { return true; }, getAvailable: function(scope, op) { return true; }, getAvailableOperators: function(scope) { const Ci = Components.interfaces.nsMsgSearchOp; return [Ci.Matches, Ci.DoesntMatch]; }, // Match function called for each message match: function(aMsgHdr, aSearchValue, aSearchOp) { const Ci = Components.interfaces.nsMsgSearchOp; const subject = aMsgHdr.mime2DecodedSubject; try { const regex = new RegExp(aSearchValue, "i"); // Case-insensitive const matches = regex.test(subject); return aSearchOp === Ci.Matches ? matches : !matches; } catch (e) { console.error("Invalid regex pattern:", aSearchValue, e); return false; } } }; // Body regex search term with multiline support const bodyRegexTerm = { id: "filtaquilla@mesquilla.com#BodyRegex", name: "Body matches regex", scope: Components.interfaces.nsMsgSearchScope.offlineMail, needsBody: true, getEnabled: function(scope, op) { return true; }, getAvailable: function(scope, op) { return scope !== Components.interfaces.nsMsgSearchScope.news; }, getAvailableOperators: function(scope) { const Ci = Components.interfaces.nsMsgSearchOp; return [Ci.Matches, Ci.DoesntMatch]; }, match: function(aMsgHdr, aSearchValue, aSearchOp) { const Ci = Components.interfaces.nsMsgSearchOp; // Get message body content const messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger); const msgUri = aMsgHdr.folder.getUriForMsg(aMsgHdr); const msgService = messenger.messageServiceFromURI(msgUri); let body = ""; const streamListener = { onDataAvailable: function(request, inputStream, offset, count) { const scriptableInputStream = Cc["@mozilla.org/scriptableinputstream;1"] .createInstance(Ci.nsIScriptableInputStream); scriptableInputStream.init(inputStream); body += scriptableInputStream.read(count); }, onStartRequest: function(request) {}, onStopRequest: function(request, status) {} }; msgService.streamMessage(msgUri, streamListener, null, null, false, ""); try { const regex = new RegExp(aSearchValue, "im"); // Case-insensitive, multiline const matches = regex.test(body); return aSearchOp === Ci.Matches ? matches : !matches; } catch (e) { console.error("Body regex error:", e); return false; } } }; // Register search terms filterService.addCustomTerm(subjectRegexTerm); filterService.addCustomTerm(bodyRegexTerm); ``` -------------------------------- ### Execute JavaScript in Sandbox (JavaScript) Source: https://context7.com/realraven2000/filtaquilla/llms.txt Executes custom JavaScript code within a secure sandbox, providing access to message properties and Thunderbird APIs. It exposes specific message header details and utility functions like logging and modifying message status. User-provided code is evaluated in this controlled environment. ```javascript // JavaScript action with security sandbox const javascriptAction = { id: "filtaquilla@mesquilla.com#javascriptAction", name: "Execute JavaScript", applyAction: function (aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) { const msgHdr = aMsgHdrs[0]; // Create sandbox with limited privileges const sandbox = Components.utils.Sandbox( Components.utils.getGlobalForObject({}), { sandboxPrototype: {}, wantXrays: true } ); // Expose safe APIs to sandbox sandbox.msgHdr = msgHdr; sandbox.folder = msgHdr.folder; sandbox.subject = msgHdr.mime2DecodedSubject; sandbox.author = msgHdr.mime2DecodedAuthor; sandbox.recipients = msgHdr.mime2DecodedRecipients; sandbox.date = new Date(msgHdr.date / 1000); sandbox.messageId = msgHdr.messageId; sandbox.flags = msgHdr.flags; // Utility functions sandbox.log = function(msg) { console.log("[FiltaQuilla JavaScript Action]", msg); }; sandbox.markRead = function() { msgHdr.folder.markMessagesRead([msgHdr], true); }; sandbox.addTag = function(tag) { msgHdr.folder.addKeywordsToMessages([msgHdr], tag); }; try { // Execute user code in sandbox Components.utils.evalInSandbox(aActionValue, sandbox); } catch (e) { console.error("JavaScript action error:", e); Components.utils.reportError("FiltaQuilla JavaScript action failed: " + e.message); } }, isValidForType: function (type, scope) { return true; }, validateActionValue: function (value, folder, type) { if (!value || value.trim().length === 0) { return "JavaScript code required"; } // Basic syntax validation try { new Function(value); // Test if code is syntactically valid return null; } catch (e) { return "Syntax error: " + e.message; } }, needsBody: false, allowDuplicates: false }; // Example usage in filter: // aActionValue = 'if (subject.includes("URGENT")) { addTag("priority"); markRead(); }' ```