I just finished my first (private) Thunderbird extension and felt like blogging a bit, since there certainly aren’t too many resources on this topic.

The problem I set out to solve was that soft hyphens in message subjects are displayed as whitespace in the message list pane. I use Thunderbird as an RSS reader and have subscribed to a newspaper that uses a lot of soft hyphens, which made it jarring to read the headlines.

There’s already an extension called FiltaQuilla that almost does what I needed. It adds filter actions that can prepend and append text to message subject, but it doesn’t have features for removing or replacing singular characters like I wanted.

Two possible solutions came to mind:

  1. Create a HTTP server that forwards requests to the RSS feed and removes soft hyphens from the response.
  2. Create a Thunderbird extension that removes soft hyphens when the feed items are added.

I didn’t feel like running a separate service/process for something this simple, so I decided to go with solution 2.

My first stop was Thunderbird’s documentation on supported WebExtension APIs, specifically the messages API. Alas, the API doesn’t support modifying the subject of a message, though it allows marking them as read, changing tags, fetching the subject, etc.

Since FiltaQuilla supported modifying the subject, I knew that it was possible somehow. Thankfully, FiltaQuilla is open source, so I skimmed the source and found that it’s done roughly as follows:

const Cc = Components.classes,
      Ci = Components.interfaces;
var filterService = Cc["@mozilla.org/messenger/services/filters;1"]
                    .getService(Ci.nsIMsgFilterService);
filterService.addCustomAction({
    "applyAction": (msgHdrs) => {
        for (let msgHdr of msgHdrs) {
            msgHdr.subject = msgHdr.subject.replace(...);
        }
    },
    ...
});

Keyword googling revealed that Components is some legacy API thing that has very little documentation. Luckily, this helpful forum post outlined how to properly call addCustomAction with less reliance on legacy cruft, though the documentation is still not great.

With the hello world tutorial, Experiments documentation and some trial and error, I finally got something working:

// background.js
browser.FilterActionAPI.addHyphenRemover();

// api/FilterActionAPI/implementation.js
const softHyphen = String.fromCharCode(173);
const { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");

this.FilterActionAPI = class extends ExtensionAPI {
    getAPI(context) {
        return {
            FilterActionAPI: {
                addHyphenRemover() {
                    MailServices.filters.addCustomAction({
                        id: "filter-remove-softhyphen",
                        name: "Remove soft hyphens",
                        applyAction: (msgHdrs) => {
                            for (const msgHdr of msgHdrs) {
                                if (msgHdr.subject.includes(softHyphen)) {
                                    console.log("filter-remove-softhyphen", "Removing soft hyphen from subject", msgHdr.subject);
                                    msgHdr.subject = msgHdr.subject.replace(softHyphen, "");
                                }
                            }
                        },
                        isValidForType: () => true,
                        validateActionValue: () => null,
                        allowDuplicates: false,
                        needsBody: false,
                        isAsync: false
                    })
                }
            }
        };
    }
}

Then I just added a message filter that matches my RSS feed and applies the Remove soft hyphens action. Not the prettiest but works!