This will be a quick guide covering writing a simple firefox extension. I want to add a button to various sites so that I can send torrents to my transmission server rather than copy and paste the magnet link.
That's a bit more involved so I'll show that extension at the end, the extension I'll cover properly though will be one where we convert tables to datatables automatically.
The MDN article, Your First Extension is quite good. It's indepth and makes it clear what you need to do. This tutorial will be a stripped down version as I think it's easier to get the picture when there is very little left.
The first step is to create a new directory and cd into it:
mkdir BabysFirstExtension
cd BabysFirstExtension
The next step is to write the manifest.json:
{
"manifest_version": 2,
"name": "Baby's First Extension",
"version": "1.0",
"description": "An extension to add datatablrs!",
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["index.js"]
}
]
}
This is the minimal manifest we need. The manifest is where we will add external scripts like jquery or DataTables should we need them. We also add css files here if we need them.
The most important lines are the content_scripts object. We have the sites to run our extension on. Here we will run it on every page. We could make this much more restrictive by specifying a specific website or web page.
The js property contains a list of javascript files that our extension is to load. We are going to just load a file called index.js.
Now we can write the last thing we need, index.js:
console.log("Hello, World!");
With that our extension is done! We can load it into firefox by going to about:debugging via the URL and then clicking the This Firefox link underneath Setup.
We can then click Load Temporary Add-on and navigate to the directory where our extension is. We can select the index.js file and load it in.
Anytime we make a change to the extension, we will need to come to the about:debugging page to reload the extension. It doesn't automatically refresh.
Now we can refresh a page and we should see our console.log in the console.
Now that we have a basic extension working, the next thing to do is load in external scripts and css. For this, I'm going to use the DataTables library and jQuery.
The first thing is to get the files we need from the CDN:
wget https://code.jquery.com/jquery-3.7.1.min.js
wget https://cdn.datatables.net/2.3.4/js/dataTables.min.js
wget https://cdn.datatables.net/2.3.4/css/dataTables.dataTables.min.css
These will be sitting next to our manifest.json and index.js.
Now we can update our manifest.json file:
{
"manifest_version": 2,
"name": "Tables",
"version": "1.0",
"description": "Make a table intoa datatable",
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["jquery-3.7.1.min.js", "dataTables.min.js", "index.js"],
"css": ["dataTables.dataTables.min.css"]
}
]
}
The key part here is that we add the scripts we downloaded to js. These also need to be the correct order. Datatables relies on jquery and so jquery is first.
Our index.js file is last because it will require the other two scripts.
We also now have a css file to include.
Now with the manifest updated, we can update our index to convert regular tables to datatables:
$(document).ready(function() {
$("table").each(function() {
$(this).DataTable();
});
});
With that we can then reload the extension via about:debugging and then take a look at a regular table on a regular web page.
You can look at the datasette example to see a table without search or sorting, and then when the extension is active, searching and sorting will be added into it.
Now the last example is to click a button and send that data elsewhere. This requires an update to the manifest to ask for permissions. Extensions need permissions to send data and make network requests. They also need permission to acess the storage.
The manifest.json:
{
"manifest_version": 2,
"name": "SendToTransmission",
"version": "1.0",
"description": "Add a send to transmission button.",
"icons": {
"48": "icons/boat-48.png"
},
"content_scripts": [
{
"matches": ["*://*.thepiratebay.org/*"],
"js": ["jquery-3.7.1.min.js", "index.js"]
}
],
"permissions": [
"<all_urls>",
"webRequest",
"webRequestBlocking",
"webNavigation"
]
}
The key part is the permissions here. We want to be able to make network request to all URLs. This can be restricted and should be but it's easier to develop if we leave it open.
The other thing to note is that I've added an icon here.
I'm still including jquery as it makes it easier to do the dom manipulation that I need to do to add in the buttons.
The index.js:
$(".item-title").css("display", "flex");
$(".item-title a").css("flex", "1");
$(".item-title").append("<button class='transmission animated'>An</button>");
$(".item-title").append("<button class='transmission'>Download</button>");
$(".transmission").css("cursor", "pointer");
$(".transmission").css("margin-left", "5px");
$(document).on("click",".transmission", async function() {
let magnet = $(this).closest("li").find(".item-icons a").attr("href");
let pageCat = $(this).closest("li").find(".item-type").text();
let category = "movies";
if ($(this).hasClass("animated")) {
category = "animated";
} else if (pageCat.indexOf("Movies") > -1) {
category = "movies";
} else if (pageCat.indexOf("TV") > -1) {
category = "tv";
} else {
alert("Unable to detect category.");
return;
}
try {
let response = await fetch("http://192.168.12.130:7221/magnet", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
category: category,
magnet: magnet,
}),
});
if (response.status !== 200) {
alert("Failed to add torrent.");
}
} catch (err) {
alert("Failed to send torrent.");
}
});
I am adding two buttons that will ultimately send manget links to transmission. The reason for a separate button for animated is that there is no way to tell if a tv show is an animated show or not. This way I can manually categorize the torrent.
I'm adding a button to every .item-title and then there is click handler that will then find the magnet link, get the category and then send that to my transmission server.
On the server side, I have little application that will take a web request and then shell our and add the magnet link to transmission.
These are some quick and dirty personal extensions. There is a second article that goes deeper into making better extensions with prompts and saving things.
However these extensions are already useful. The most unfortunate thing is that there doesn't seem to be an easy way to make these extensions permanently run.
You can use the Firefox Developer edition or nightly to disable the extension signature checks. This way you could install a personal extension permanently but having to download another browser is a pain in the ass.
I'll need to find a better way to have these extensions available.