// ==UserScript==
// @name FA Blacklist
// @namespace https://greasyfork.org/en/scripts/453103-fa-blacklist
// @version 1.1.0
// @description Adds a blacklist to Fur Affinity. Also adds the ability to replace typed terms with other terms. If installed correctly you should see a link titled "Edit Blacklist" below the search box on FA's search page.
// @author Nin
// @license GNU GPLv3
// @match https://www.furaffinity.net/*
// @icon https://www.google.com/s2/favicons?domain=furaffinity.net
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-end
// @downloadURL none
// ==/UserScript==
// How many tabs to add between the blacklist and search
var tabBuffer = "\t".repeat(30);
// Save a script setting
function saveUserData(key, value) {
'use strict';
GM_setValue(key, JSON.stringify(value));
}
// Load a script setting
async function loadUserData(key, defaultValue) {
'use strict';
let data = await GM_getValue(key);
if (data === undefined) {
return defaultValue;
}
return JSON.parse(data);
}
// Add extra search settings for using this script to FA's Search Settings section
function generateSearchSettings(blacklist, replace) {
'use strict';
if (!window.location.pathname.startsWith("/controls/site-settings/")){
return;
}
let replaceList = [];
for (const property in replace) {
replaceList.push(property + "=" + replace[property]);
}
const replaceString = replaceList.join(", ");
prependSearchSetting(
"Find and Replace",
"A comma separated list of search terms to replace. In the format: term1=replacement1, term2=replacement2, tf=transformation Replacements can contain advanced FA queries: noodles=(dragons|snakes), ramen=(snakes&soup)",
replaceString, "replace", "Comma separated list...");
const blacklistString = blacklist.join(", ");
prependSearchSetting(
"Blacklist",
"A comma separated list of words to blacklist. In the format: these, are, search, terms, I, dislike",
blacklistString, "blacklist", "Comma separated list...");
const saveButton = document.getElementsByName("save_settings")[0];
saveButton.addEventListener("click", function(){
let blacklist = document.getElementById("blacklist").value.replaceAll(/\s/g, "").split(",");
let replaceList = document.getElementById("replace").value.replaceAll(/\s/g, "").split(",");
let replace = {};
for (const replaceText of replaceList) {
const split = replaceText.split("=");
replace[split[0]] = split[1];
}
saveUserData("blacklist", blacklist);
saveUserData("replace", replace);
});
}
// Add a search setting option to the start of the list of search settings on the Global Site Settings page
function prependSearchSetting(title, description, data, id, placeholder) {
'use strict';
let html = `
${title}
${description}
`;
const element = document.getElementsByClassName("section-body")[2];
element.innerHTML = html + element.innerHTML;
}
// Add a link to edit the blacklist to search pages
function addSearchSettingsLink(){
'use strict';
if (!window.location.pathname.startsWith("/search/")){
return;
}
const searchBox = document.getElementsByClassName("browser-sidebar-search-box")[0];
searchBox.outerHTML += "Edit Blacklist";
const ratingSection = document.getElementsByClassName("gridContainer")[1];
ratingSection.innerHTML += '
';
}
// Remove the added query text from the query inputs on page load
function cleanInput() {
'use strict';
document.getElementsByName("q").forEach(function(input){
if (input.value !== "") {
console.log('Actual Search:\n' + input.value.replaceAll("\t", ""));
}
if (input.value.includes("\t")) {
input.value = input.value.substring(0, input.value.indexOf("\t"));
}
// Remove any sent zero width spaces
input.value = input.value.replaceAll("\u200B", "");
});
}
// Remove the blacklist text from FA's list of tags you searched
function cleanQueryStats(blacklist) {
'use strict';
if (document.getElementById("query-stats") !== null) {
var queryStats = document.getElementById("query-stats").children;
while (queryStats.length > 0 && blacklist.includes(queryStats[queryStats.length - 1].children[0].children[0].innerHTML)) {
queryStats[queryStats.length - 1].remove();
}
}
}
// Replace keywords in the query string according to the specified replacements
function replaceKeywords(replace) {
'use strict';
document.getElementsByName("q").forEach(function(input){
let append = "";
for (const property in replace) {
const pos_regex = new RegExp('(? 0 && input.value.match(/ -bl\b/) === null){
input.value += " -" + blacklist.join(" -");
if (input.value.endsWith("-")) {
input.value = input.value.substring(0, input.value.length - 1);
}
}
});
}
}
// Adds a buffer of tabs to hide the added query text
function addBuffer() {
'use strict';
document.getElementsByName("q").forEach(function(input){
input.value += tabBuffer;
});
}
// Adds an onsubmit trigger for the element with the given ID to add the blacklist
function attachHandlers(elementID, blacklist, replace) {
'use strict';
var element = document.getElementById(elementID);
if (element !== null) {
element.addEventListener("submit", function(){
addBuffer();
replaceKeywords(replace);
addBlacklist(blacklist);
});
}
}
// Return some URI text that can be appended to a URI to add the blacklist
function blacklistURI(blacklist) {
'use strict';
let result = tabBuffer + "-" + blacklist.join(" -");
if (result.endsWith("-")) {
result = result.substring(0, result.length - 1);
}
return encodeURIComponent(result);
}
// If we somehow end up searching without the blacklist, redirect to add the blacklist
function redirect(blacklist) {
'use strict';
if ((!window.location.pathname.startsWith("/search/"))
|| (window.location.pathname === "/search/" && window.location.search === "")
|| window.location.pathname.includes("09%")
|| window.location.search.includes("09%")){
return;
}
console.log("Redirecting to add blacklist...");
window.location.href = window.location.href + blacklistURI(blacklist);
}
// Update the links on the tags of images to add the blacklist
function updateTags(blacklist) {
'use strict';
if (!window.location.pathname.startsWith("/view/")){
return;
}
[...document.getElementsByClassName("tags")].forEach(function(tag){
tag.firstChild.href += blacklistURI(blacklist);
});
}
async function main() {
'use strict';
const blacklist = loadUserData("blacklist", []);
const replace = loadUserData("replace", {});
addSearchSettingsLink();
cleanInput();
cleanQueryStats(await blacklist);
attachHandlers("search-form", await blacklist, await replace);
attachHandlers("searchbox", await blacklist, await replace);
generateSearchSettings(await blacklist, await replace);
redirect(await blacklist);
updateTags(await blacklist);
}
main();