2024-02-28 22:30:32 -06:00
|
|
|
|
let port = undefined;
|
|
|
|
|
|
|
|
|
|
// Run the shellfox helper program.
|
2024-02-28 23:23:08 -06:00
|
|
|
|
function initShellfoxProgram() {
|
2024-02-28 22:30:32 -06:00
|
|
|
|
port = browser.runtime.connectNative("shellfox");
|
|
|
|
|
port.onDisconnect.addListener((port) => {
|
|
|
|
|
console.log(port.error);
|
|
|
|
|
port = undefined;
|
2024-03-04 12:58:05 -06:00
|
|
|
|
|
|
|
|
|
// Tell the user about the error…
|
|
|
|
|
browser.tabs.query({"active": true}).then((tabs) => {
|
|
|
|
|
let openerTab = undefined;
|
|
|
|
|
if (tabs && tabs.length > 0)
|
|
|
|
|
openerTab = tabs[0].id;
|
|
|
|
|
|
|
|
|
|
browser.tabs.create({
|
|
|
|
|
"active": true,
|
|
|
|
|
"url": "/error.html",
|
|
|
|
|
"openerTabId": openerTab
|
|
|
|
|
})
|
|
|
|
|
});
|
2024-02-28 22:30:32 -06:00
|
|
|
|
});
|
|
|
|
|
}
|
2024-02-27 23:53:53 -06:00
|
|
|
|
|
|
|
|
|
|
2024-03-02 22:13:20 -06:00
|
|
|
|
// Given the name of an array saved to localStorage, return it (if possible).
|
|
|
|
|
function savedArray(name) {
|
|
|
|
|
try {
|
2024-03-03 10:46:17 -06:00
|
|
|
|
let saved = JSON.parse(localStorage.getItem(name));
|
2024-03-02 22:13:20 -06:00
|
|
|
|
return saved;
|
2024-03-03 10:46:17 -06:00
|
|
|
|
} catch { return []; };
|
2024-03-02 22:13:20 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-02-28 18:37:13 -06:00
|
|
|
|
// Return the command-string associated with a URL, if any.
|
2024-03-02 22:13:20 -06:00
|
|
|
|
function getUrlCommands(url) {
|
|
|
|
|
let matchCommands = [];
|
2024-02-28 18:37:13 -06:00
|
|
|
|
let matchRegex = "";
|
|
|
|
|
try {
|
2024-03-02 22:13:20 -06:00
|
|
|
|
let savedCommands = savedArray("commands");
|
|
|
|
|
let savedRegexRules = savedArray("urlRules");
|
2024-02-28 18:52:02 -06:00
|
|
|
|
// Find the most-applicable command…
|
2024-03-02 13:25:06 -06:00
|
|
|
|
for (regexCommandIPair of savedRegexRules) {
|
|
|
|
|
let regex = regexCommandIPair[0];
|
2024-02-28 18:37:13 -06:00
|
|
|
|
let match = url.match(regex);
|
2024-03-02 22:13:20 -06:00
|
|
|
|
let command_i = regexCommandIPair[1];
|
|
|
|
|
let command = savedCommands[command_i][1];
|
|
|
|
|
|
2024-02-28 18:37:13 -06:00
|
|
|
|
let compared = compareRegexComplexity(matchRegex, regex);
|
|
|
|
|
if (match && (compared == 0 || compared == 1)) {
|
2024-03-02 22:13:20 -06:00
|
|
|
|
matchCommands.unshift(savedCommands[command_i][1]);
|
2024-02-28 18:37:13 -06:00
|
|
|
|
matchRegex = regex;
|
2024-03-02 22:13:20 -06:00
|
|
|
|
} else if (match)
|
|
|
|
|
matchCommands.push(command);
|
2024-02-28 18:37:13 -06:00
|
|
|
|
}
|
|
|
|
|
} catch {};
|
2024-03-02 22:13:20 -06:00
|
|
|
|
|
|
|
|
|
if (matchCommands.length == 0)
|
|
|
|
|
return undefined;
|
|
|
|
|
return matchCommands;
|
2024-02-28 18:37:13 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-03-03 22:48:34 -06:00
|
|
|
|
// Return the download-command string (if any) associated with a URL and type
|
|
|
|
|
// integer (0→started; 1→finished).
|
|
|
|
|
function getDownloadCommand(url, type) {
|
|
|
|
|
let matchCommand = undefined;
|
|
|
|
|
let matchRegex = "";
|
|
|
|
|
try {
|
|
|
|
|
let savedDownloads = savedArray("downloadCommands");
|
|
|
|
|
// Find the most-applicable command.
|
|
|
|
|
for (regexCommandType of savedDownloads) {
|
|
|
|
|
let regex = regexCommandType[0];
|
|
|
|
|
let match = url.match(regex);
|
|
|
|
|
|
|
|
|
|
let compared = compareRegexComplexity(matchRegex, regex);
|
|
|
|
|
if ((match && (compared == 0 || compared == 1))
|
|
|
|
|
&& (regexCommandType[2] == type))
|
|
|
|
|
{
|
|
|
|
|
matchCommand = regexCommandType[1];
|
|
|
|
|
matchRegex = regex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {};
|
|
|
|
|
return matchCommand;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Execute the given command string, subsituting “$URL” with url and
|
|
|
|
|
// “$FILE” with filepath.
|
|
|
|
|
function runCommand(command, url, filepath) {
|
2024-02-28 22:30:32 -06:00
|
|
|
|
if (!port)
|
2024-02-28 23:23:08 -06:00
|
|
|
|
initShellfoxProgram();
|
2024-03-02 22:13:20 -06:00
|
|
|
|
if (command && port)
|
2024-03-03 22:48:34 -06:00
|
|
|
|
port.postMessage(command
|
|
|
|
|
.replaceAll("$URL", url)
|
|
|
|
|
.replaceAll("${URL}", url)
|
|
|
|
|
.replaceAll("$FILE", filepath)
|
|
|
|
|
.replaceAll("${FILE}", filepath));
|
2024-03-02 22:13:20 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Execute the shell command associated with the given URL, if any.
|
|
|
|
|
function runUrlCommand(url) {
|
|
|
|
|
let commands = getUrlCommands(url);
|
|
|
|
|
if (commands)
|
|
|
|
|
runCommand(commands[0], url);
|
2024-02-28 18:37:13 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Compare two regular expressions, returning which one is more specific.
|
|
|
|
|
// Returns -1 if a is more specific, 1 if b is, and 0 if they are equal.
|
|
|
|
|
// It’s a simple (and unreliable) algorithm, for now — purely based on length.
|
|
|
|
|
function compareRegexComplexity(a, b) {
|
|
|
|
|
if (a && ((a && !b) || (a.length > b.length)))
|
|
|
|
|
return -1;
|
|
|
|
|
else if (b && ((b && !a) || (a.length < b.length)))
|
|
|
|
|
return 1
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-03-02 11:39:44 -06:00
|
|
|
|
// Display the “Run shell command” context-menu item.
|
|
|
|
|
function showPageContextMenuItem() {
|
|
|
|
|
browser.menus.update("run-page-command", { "visible": true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Display the “Run command on link” context-menu item.
|
|
|
|
|
function showLinkContextMenuItem() {
|
|
|
|
|
browser.menus.update("run-page-commands", { "visible": true });
|
2024-02-28 23:23:08 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-03-02 11:39:44 -06:00
|
|
|
|
// Hide the “Run shell command context-menu item.
|
|
|
|
|
function hidePageContextMenuItem() {
|
|
|
|
|
browser.menus.update("run-page-command", { "visible": false });
|
2024-02-28 23:23:08 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-03-02 11:39:44 -06:00
|
|
|
|
// Hide the “Run command on link” context-menu item.
|
|
|
|
|
function hideLinkContextMenuItem() {
|
|
|
|
|
browser.menus.update("run-page-commands", { "visible": false });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-03-03 22:48:34 -06:00
|
|
|
|
// (Re-)Create the menu-items for each context menu.
|
2024-03-02 22:13:20 -06:00
|
|
|
|
function createCommandMenuItems() {
|
2024-03-03 22:48:34 -06:00
|
|
|
|
let savedCommands = savedArray("commands") || [];
|
2024-03-02 22:13:20 -06:00
|
|
|
|
for (let i = 0; i < savedCommands.length; i++) {
|
|
|
|
|
let nameCommandPair = savedCommands[i];
|
|
|
|
|
let name = nameCommandPair[0];
|
2024-03-02 22:24:51 -06:00
|
|
|
|
let actionId = "run-pageaction-command-" + i;
|
|
|
|
|
browser.menus.remove(actionId);
|
|
|
|
|
browser.menus.create(
|
|
|
|
|
{
|
|
|
|
|
id: actionId,
|
2024-03-03 10:46:17 -06:00
|
|
|
|
title: browser.i18n.getMessage("pageCommandContextMenu", name),
|
2024-03-02 22:24:51 -06:00
|
|
|
|
contexts: ["page_action"]
|
|
|
|
|
});
|
|
|
|
|
|
2024-03-02 22:13:20 -06:00
|
|
|
|
let pageId = "run-page-command-" + i;
|
|
|
|
|
browser.menus.remove(pageId);
|
|
|
|
|
browser.menus.create(
|
|
|
|
|
{
|
|
|
|
|
id: pageId,
|
2024-03-03 10:46:17 -06:00
|
|
|
|
title: browser.i18n.getMessage("pageCommandContextMenu", name),
|
2024-03-02 22:24:51 -06:00
|
|
|
|
contexts: ["page"]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let linkId = "run-link-command-" + i;
|
|
|
|
|
browser.menus.remove(linkId);
|
|
|
|
|
browser.menus.create(
|
|
|
|
|
{
|
|
|
|
|
id: linkId,
|
2024-03-03 10:46:17 -06:00
|
|
|
|
title: browser.i18n.getMessage("linkCommandContextMenu", name),
|
2024-03-02 22:24:51 -06:00
|
|
|
|
contexts: ["link"]
|
2024-03-02 22:13:20 -06:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
browser.menus.refresh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-03-02 11:39:44 -06:00
|
|
|
|
// Add a context-menu item for running the current page’s associated command.
|
|
|
|
|
browser.menus.create(
|
|
|
|
|
{
|
|
|
|
|
id: "run-page-command",
|
2024-03-03 10:46:17 -06:00
|
|
|
|
title: browser.i18n.getMessage("pageCommandDefaultContextMenu"),
|
2024-03-02 11:39:44 -06:00
|
|
|
|
contexts: ["page"]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add a context-menu item for running the command associated with a link.
|
|
|
|
|
browser.menus.create(
|
|
|
|
|
{
|
|
|
|
|
id: "run-link-command",
|
2024-03-03 10:46:17 -06:00
|
|
|
|
title: browser.i18n.getMessage("linkCommandDefaultContextMenu"),
|
2024-03-02 11:39:44 -06:00
|
|
|
|
contexts: ["link"]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
2024-02-28 22:30:32 -06:00
|
|
|
|
// When the address-bar button is clicked, run the according command (if any).
|
2024-02-28 18:37:13 -06:00
|
|
|
|
browser.pageAction.onClicked.addListener((tab) => {
|
|
|
|
|
runUrlCommand(tab.url);
|
2024-02-27 23:53:53 -06:00
|
|
|
|
});
|
2024-02-28 18:37:13 -06:00
|
|
|
|
|
|
|
|
|
|
2024-03-02 22:13:20 -06:00
|
|
|
|
// When a context-menu (right-click menu) is opened, only display the SHellfox
|
|
|
|
|
// item if there is a configured command for that page.
|
2024-03-02 11:39:44 -06:00
|
|
|
|
browser.menus.onShown.addListener(info => {
|
2024-03-02 22:13:20 -06:00
|
|
|
|
if (info.contexts.includes("link") && getUrlCommands(info.linkUrl)) {
|
2024-03-02 11:39:44 -06:00
|
|
|
|
showLinkContextMenuItem();
|
2024-03-02 22:13:20 -06:00
|
|
|
|
} else if (info.contexts.includes("page") && getUrlCommands(info.pageUrl)) {
|
|
|
|
|
showPageContextMenuItem();
|
|
|
|
|
} else {
|
2024-03-02 11:39:44 -06:00
|
|
|
|
hidePageContextMenuItem();
|
|
|
|
|
hideLinkContextMenuItem();
|
|
|
|
|
}
|
|
|
|
|
browser.menus.refresh();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2024-02-28 22:30:32 -06:00
|
|
|
|
// When a tab’s URL has been changed, enable/disable the address-bar button
|
|
|
|
|
// based on whether or not there is an according command.
|
2024-02-28 18:37:13 -06:00
|
|
|
|
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
2024-03-02 22:13:20 -06:00
|
|
|
|
let command = getUrlCommands(tab.url);
|
2024-03-02 11:39:44 -06:00
|
|
|
|
if (command)
|
2024-02-28 18:37:13 -06:00
|
|
|
|
browser.pageAction.show(tabId);
|
2024-03-02 11:39:44 -06:00
|
|
|
|
else
|
2024-02-28 18:37:13 -06:00
|
|
|
|
browser.pageAction.hide(tabId);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2024-02-28 22:30:32 -06:00
|
|
|
|
// When the active tab has changed, enable/disable the address-bar button based
|
|
|
|
|
// on whether or not there is an according command for it.
|
2024-02-28 18:37:13 -06:00
|
|
|
|
browser.tabs.onActivated.addListener((activeInfo) => {
|
2024-02-28 22:30:32 -06:00
|
|
|
|
browser.tabs.get(activeInfo.tabId).then((tab) => {
|
2024-03-02 22:13:20 -06:00
|
|
|
|
if (getUrlCommands(tab.url))
|
2024-02-28 22:30:32 -06:00
|
|
|
|
browser.pageAction.show(tab.id);
|
2024-03-02 11:39:44 -06:00
|
|
|
|
else
|
2024-02-28 23:23:08 -06:00
|
|
|
|
browser.pageAction.hide(tab.id);
|
2024-02-28 22:30:32 -06:00
|
|
|
|
});
|
2024-02-28 18:37:13 -06:00
|
|
|
|
});
|
2024-02-28 23:23:08 -06:00
|
|
|
|
|
|
|
|
|
|
2024-03-02 11:39:44 -06:00
|
|
|
|
// When a context-menu item is selected, let’s execute its will!
|
2024-02-28 23:23:08 -06:00
|
|
|
|
browser.menus.onClicked.addListener((info, tab) => {
|
2024-03-02 22:13:20 -06:00
|
|
|
|
let itemName = info.menuItemId;
|
|
|
|
|
if (itemName == "run-page-command")
|
2024-02-28 23:23:08 -06:00
|
|
|
|
runUrlCommand(tab.url);
|
2024-03-02 22:13:20 -06:00
|
|
|
|
else if (itemName == "run-link-command" && info.linkUrl)
|
2024-03-02 11:39:44 -06:00
|
|
|
|
runUrlCommand(info.linkUrl);
|
2024-03-02 22:24:51 -06:00
|
|
|
|
else if (itemName.startsWith("run-")) {
|
|
|
|
|
let command_i = itemName.split("-command-")[1];
|
|
|
|
|
runCommand(savedArray("commands")[command_i][1], info.linkUrl || tab.url);
|
2024-03-02 22:13:20 -06:00
|
|
|
|
}
|
|
|
|
|
});
|
2024-03-02 11:39:44 -06:00
|
|
|
|
|
2024-03-02 22:13:20 -06:00
|
|
|
|
|
2024-03-03 22:48:34 -06:00
|
|
|
|
// When a download starts, run any applicable download commands.
|
|
|
|
|
browser.downloads.onCreated.addListener((downloadItem) => {
|
|
|
|
|
let command = getDownloadCommand(downloadItem.url, 0);
|
|
|
|
|
if (command)
|
|
|
|
|
runCommand(command, downloadItem.url, downloadItem.filename);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// When a download completes, run any applicable download commands.
|
|
|
|
|
browser.downloads.onChanged.addListener((downloadDelta) => {
|
|
|
|
|
browser.downloads.search({ "id": downloadDelta.id }).then((downloadItems) => {
|
|
|
|
|
if (downloadDelta.state.current == "complete" && downloadItems.length > 0) {
|
|
|
|
|
let command = getDownloadCommand(downloadItems[0].url, 1);
|
|
|
|
|
if (command)
|
|
|
|
|
runCommand(command, downloadItems[0].url, downloadItems[0].filename);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2024-03-02 22:13:20 -06:00
|
|
|
|
// Whenever settings (commands) are updated, repopulate context-menus’ items.
|
|
|
|
|
window.addEventListener("storage", (e) => {
|
|
|
|
|
createCommandMenuItems();
|
2024-02-28 23:23:08 -06:00
|
|
|
|
});
|
2024-03-02 22:13:20 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
createCommandMenuItems();
|