Restructured code, added blacklist/whitelist functionality, improved performance, fixed some bugs

tests
Lars 2022-11-17 14:54:48 +01:00 zatwierdzone przez GitHub
rodzic 118202b449
commit 877a423c3b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
1 zmienionych plików z 171 dodań i 51 usunięć

222
inject.js
Wyświetl plik

@ -1,50 +1,103 @@
// prep
var buttonPaths = ["div.account__header button.logo-button","div.public-account-header a.logo-button"];
var domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/;
var handleRegex = /^(?:https?:\/\/(www\.)?.*\..*?\/)(?<handle>@\w+(?:@\w+\.\w+)?)(?:\/?.*|\z)$/;
var target = "_blank";
var enableConsoleLog = true;
var logPrepend = "[FediFollow]";
var maxElementWaitFactor = 200; // x 100ms for total time
var lastUrl = window.location.href;
var instance;
var showAlert;
var lastUrl = window.location.href;
var buttonPaths = ["div.account__header button.logo-button","div.public-account-header a.logo-button"];
var mode;
var whitelist;
var blacklist;
// function to wait for given elements to appear - first found element gets returned
var waitForEl = function(selectors, callback) {
// wrapper to prepend to log messages
function log(text) {
if (enableConsoleLog) {
console.log(logPrepend + ' ' + text)
}
}
// function to wait for given elements to appear - first found element gets returned (but as of now the selectors are for different layouts anyways)
var waitForEl = function(counter, selectors, callback) {
var match = false;
// check all of the selectors
for (const selector of selectors) {
// if found
if ($(selector).length) {
// set match = true to prevent repetition hand over the found element
match = true;
callback(selector);
}
}
if (!match) {
// repeat if no match was found and we did not exceed the wait factor yet
if (!match && counter < maxElementWaitFactor) {
setTimeout(function() {
waitForEl(selectors, callback);
}, 100);
// increase counter
waitForEl(counter + 1, selectors, callback);
}, 100);
}
};
// regex match to extract @handle from any (as always, the regex was awful to build)
function extractHandle(url) {
if (!url) return null;
// extract handle from any mastodon url
var extractHandle = function(url, callback) {
// regex with named match group
const match = url.match(/^(?:https?:\/\/(www\.)?.*\..*?\/)(?<handle>@\w+(?:@\w+\.\w+)?)(?:\/?.*|\z)$/);
// return the named match group
return match.groups.handle
}
var match = url.match(handleRegex);
match = match.groups.handle
// check if match is valid
ats = (match.match(/@/g) || []).length;
if (!(match == null) && ats >= 1 && ats <= 2) {
// return the named match group
callback(match);
}
}
// test if the current site should be processed or not
// this will also be the function for whitelist/blacklist feature
function checkSite() {
// is this site on our home instance?
if (document.domain == instance) {
log("Current site is your home instance.");
return false;
}
if (mode == "whitelist") {
if ($.inArray(document.domain, whitelist) < 0) {
log("Current site is not in whitelist.");
return false;
}
} else {
if ($.inArray(document.domain, blacklist) > -1) {
log("Current site is in blacklist.");
return false;
}
}
// check if the current site looks like Mastodon
$(document).ready(function() {
if (!($("head").text().includes("mastodon") || $("head").text().includes("Mastodon") || $("div#mastodon").length)) {
log("Could not find a reference that this is a Mastodon site.")
return false;
}
});
return true;
}
// main function to listen for the follow button pressed and open a new tab with the home instance
function processButton() {
// is this site on our home instance?
if (!(document.domain == instance)) {
// wait for the DOM...
$(document).ready(function() {
// check if we are on a mastodon site with a handle in url
if (($("head").text().includes("mastodon") || $("head").text().includes("Mastodon") || $("div#mastodon").length) && window.location.href.includes("@")) {
// wait until follow button appears
waitForEl(buttonPaths, function(found) {
// grab the user handle
handle = extractHandle(window.location.href);
// if we got one...
if (!(handle == null) && (handle.match(/@/g) || []).length > 0) {
function processSite() {
// check if we have a handle in the url
if (window.location.href.includes("@")) {
// grab the user handle
extractHandle(window.location.href, function(handle) {
// if we got one...
if (handle) {
// wait until follow button appears (document is already ready, but most content is loaded afterwards)
waitForEl(0, buttonPaths, function(found) {
if (found) {
// setup the button click listener
$(found).click(function(e) {
// prevent default action and other handlers
e.preventDefault();
e.stopImmediatePropagation();
// check the alert setting and show it if set
@ -57,7 +110,7 @@ function processButton() {
$(found).text("Redirecting...");
// timeout 1000ms to make it possible to notice the redirection indication
setTimeout(function() {
// if more than 1 @, we have a domain
// if more than 1 @, we have a domain in the handle
if ((handle.match(/@/g) || []).length > 1) {
// but if its our own...
if (handle.includes(instance)) {
@ -67,26 +120,33 @@ function processButton() {
// request string
var request = 'https://'+instance+'/'+handle;
} else {
// with only 1 @, we have a local handle and need to append to domain
// with only 1 @, we have a local handle and need to append the domain
var request = 'https://'+instance+'/'+handle+'@'+document.domain;
}
// open the window
var win = window.open(request, '_blank');
var win = window.open(request, target);
log("Redirected to " + request)
// focus the new tab if open was successfull
if (win) {
win.focus();
} else {
// otherwise notify user...
console.log('Please allow popups for this website');
log('Could not open new window. Please allow popups for this website.');
}
// restore original button text
$(found).text(originaltext)
$(found).text(originaltext);
}, 1000);
});
} else {
log("Could not find any follow button.");
}
});
} else {
log("Could not find a handle.");
}
});
} else {
log("No handle in this URL.");
}
}
@ -98,31 +158,91 @@ function urlChangeLoop() {
if (!(lastUrl == window.location.href)) {
// update lastUrl and run main script
lastUrl = window.location.href;
processButton();
processSite();
}
// repeat
urlChangeLoop();
}, 300);
}
// get the extension setting for the users' Mastadon home instance
chrome.storage.local.get(['mastodonhomeinstance'], function(fetchedData) {
instance = fetchedData.mastodonhomeinstance;
// and alert setting
chrome.storage.local.get(['mastodonalert'], function(fetchedData) {
showAlert = fetchedData.mastodonalert;
// if the value is empty/null/undefined...
if (instance == null || !instance) {
console.log("Mastodon home instance is not set.");
function processDomainList(newLineList) {
// split by new line
var arrayFromList = newLineList.split(/\r?\n/);
// array to put checked domains into
var cleanedArray = [];
for (var domain of arrayFromList) {
// remove whitespace
domain = domain.trim();
if (domainRegex.test(domain)) {
cleanedArray.push(domain)
} else {
// if the value looks like a domain...
if (/\w+\.\w+/.test(instance)) {
// ... run the actual script (once for the start and then in a loop depending on url changes)
processButton();
urlChangeLoop();
} else {
console.log("Instance setting is not a valid domain name.");
}
log("Removed invalid domain " + domain + " from blacklist/whitelist.")
}
}
// return newly created set (remvoes duplicates)
return [...new Set(cleanedArray)];;
}
function checkSettings() {
// if the home instance is undefined/null/empty
if (instance == null || !instance) {
log("Mastodon home instance is not set.");
return false;
}
// if the value looks like a domain...
if (!(domainRegex.test(instance))) {
log("Instance setting is not a valid domain name.");
return false;
}
// set default if no value
if ($.inArray(mode, ["blacklist","whitelist"]) < 0) {
mode = "blacklist";
}
if (mode == "whitelist") {
// if in whitelist mode and the cleaned whitelist is empty, return false
whitelist = processDomainList(whitelist);
if (whitelist.length < 1) {
log("Whitelist is empty or invalid.")
return false;
}
} else {
// also process the blacklist if in blacklist mode, but an empty blacklist is OK so we do not return false
blacklist = processDomainList(blacklist);
}
return true;
}
function run() {
// get the extension setting for the users' Mastadon home instance
chrome.storage.local.get(['fedifollow_homeinstance'], function(fetchedData) {
instance = fetchedData.fedifollow_homeinstance;
// and alert setting
chrome.storage.local.get(['fedifollow_alert'], function(fetchedData) {
showAlert = fetchedData.fedifollow_alert;
// mode
chrome.storage.local.get(['fedifollow_mode'], function(fetchedData) {
mode = fetchedData.fedifollow_mode;
// whitelist
chrome.storage.local.get(['fedifollow_whitelist'], function(fetchedData) {
whitelist = fetchedData.fedifollow_whitelist;
// blacklist
chrome.storage.local.get(['fedifollow_blacklist'], function(fetchedData) {
blacklist = fetchedData.fedifollow_blacklist;
if (checkSettings()) {
// check if the current URL should be processed
if (checkSite()) {
// ... run the actual script (once for the start and then in a loop depending on url changes)
processSite();
urlChangeLoop();
} else {
log("Will not process this URL.")
}
}
});
});
});
});
});
});
}
run();