// ==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();