Last active
November 18, 2024 19:57
-
-
Save bellydrum/00850303addd8424735560d01b060c6a to your computer and use it in GitHub Desktop.
Famiboards Browser Utility
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Famiboards Browser Utility | |
// @namespace http://tampermonkey.net/ | |
// @version 2024-10-16 | |
// @description Toggle on/off various browser tools for a custom Famiboards experience. | |
// @author bellydrum | |
// @match https://famiboards.com/* | |
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== | |
// @grant none | |
// @require https://gist.githubusercontent.com/bellydrum/cfc7869243b4d5c4e7ae710ea59edf67/raw/e8be38fc17877c38601edd818fc07c5502173b94/cookieHelper.js | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
let blockerToggle | |
let censorToggle | |
let switchToPreferredTheme | |
const cookie = new CookieHelper() | |
/** | |
* turn the functions on and off here | |
*/ | |
blockerToggle = true | |
censorToggle = true | |
switchToPreferredTheme = true | |
// add to the list of blocked users here | |
const usersToBlock = [ | |
// "testUsername1", | |
// "testUsername2", | |
] | |
// add to the list of word replacements here | |
const wordsToReplace = { | |
// "wordToCensor1": "wordReplacement1", | |
// "wordToCensor2": "wordReplacement2", | |
} | |
const defaultPreferredTheme = 38 | |
const themeId = cookie.getValueByKey("preferredThemeId") ? cookie.getValueByKey("preferredThemeId") : defaultPreferredTheme | |
// set the default text for replaced quote text | |
const quoteReplacementText = "*** text replaced by browser utility ***" | |
/** | |
* | |
* Various utility functions used in several places | |
* | |
*/ | |
function getPostsOnPage() { | |
// return a NodeList of thread posts on the current page. | |
return [...document.querySelectorAll("article.message--post")] | |
} | |
function getQuoteBlocksOnPage() { | |
return [...document.querySelectorAll(".bbCodeBlock--quote")] | |
} | |
function getUsernameFromPost(post) { | |
// return the username of the user who created the provided post. | |
return post.querySelector(".username").innerText | |
} | |
function getUsernameFromQuoteBlock(quoteBlock) { | |
return quoteBlock.querySelector(".bbCodeBlock-sourceJump").innerText.replace(" said:", "") | |
} | |
function getUsersOnPage() { | |
// return an array of usernames of users who have made posts on the current page. | |
return Array.from(new Set([...(getPostsOnPage())].map(post => { | |
return getUsernameFromPost(post) | |
}))) | |
} | |
/** | |
* | |
* Main functions | |
* - block | |
* - censor | |
* | |
*/ | |
function block() { | |
/** | |
* | |
* Finds posts made by users in the usersToBlock list and removes them entirely from the DOM. | |
* | |
*/ | |
let blockedPosts = {} | |
const usersToBlockLowerCase = usersToBlock.map(username => username.toLowerCase()) | |
for (const post of getPostsOnPage()) { | |
const usernameFromPost = getUsernameFromPost(post) | |
if (usersToBlockLowerCase.includes(usernameFromPost.toLowerCase())) { | |
blockedPosts[usernameFromPost] ? blockedPosts[usernameFromPost]++ : blockedPosts[usernameFromPost] = 1 | |
post.remove() | |
} | |
} | |
for (const quoteBlock of getQuoteBlocksOnPage()) { | |
const usernameFromQuoteBlock = getUsernameFromQuoteBlock(quoteBlock) | |
if (usersToBlockLowerCase.includes(usernameFromQuoteBlock.toLowerCase())) { | |
// console.log(quoteBlock.querySelector(".bbCodeBlock-expandContent").innerText) | |
quoteBlock.querySelector(".bbCodeBlock-expandContent").innerText = quoteReplacementText | |
} | |
} | |
console.log(`Posts blocked:`) | |
console.log(blockedPosts) | |
} | |
function censor() { | |
/** | |
* | |
* Finds instances of words in posts that are found in the wordsToReplace list and changes them to the preferred word. | |
* | |
*/ | |
let replacedWords = {} | |
function replaceWordsInNode(node) { | |
const textOnly = !node.classList | |
const textContentPropName = "textContent" | |
const innerHTMLPropName = "innerHTML" | |
for (const wordToReplace of Object.keys(wordsToReplace)) { | |
const wordToReplaceRegex = new RegExp("\\b" + wordToReplace + "\\b", "ig") | |
// determine whether the node contains text or additional HTML | |
const propertyToModify = textOnly ? textContentPropName : innerHTMLPropName | |
if (node[propertyToModify].toLowerCase().includes(wordToReplace.toLowerCase())) { | |
try { | |
// modify the text content of the given node | |
// if textContent, use .replaceAll() | |
// if innerHTML, use .replace() | |
node[propertyToModify] = propertyToModify == textContentPropName | |
? node[propertyToModify].replaceAll(wordToReplaceRegex, wordsToReplace[wordToReplace]) | |
: node[propertyToModify].replace(wordToReplaceRegex, wordsToReplace[wordToReplace]) | |
// upon successful word replacement, increment replacement counter for current word | |
if (!node[propertyToModify].toLowerCase().includes(wordToReplace.toLowerCase())) { | |
replacedWords[wordToReplace] = typeof replacedWords[wordToReplace] == "number" | |
? replacedWords[wordToReplace] + 1 | |
: 1 | |
} | |
} catch(error) { | |
console.log(`wordReplacement error | ${error}`) | |
} | |
} | |
} | |
} | |
function updatePage() { | |
// replace words in thread posts | |
for (let block of document.querySelectorAll("div.bbWrapper")) { | |
for (let node of Object.values(block.childNodes)) { | |
replaceWordsInNode(node) | |
} | |
} | |
// replace words on user comment history page | |
for (let block of document.querySelectorAll(".contentRow-snippet")) { | |
replaceWordsInNode(block) | |
} | |
// log progress to the console | |
for (let replacedWord of Object.keys(replacedWords)) { | |
console.log(`Replaced ${replacedWords[replacedWord]} instance(s) of "${replacedWord}" with "${wordsToReplace[replacedWord]}".`) | |
} | |
console.log(`Words censored:`) | |
console.log(replacedWords) | |
} | |
updatePage() | |
} | |
function switchToTheme(themeId) { | |
if (!themeId) { | |
console.log(`switchToTheme | Provide a valid value for themeId parameter, not "${themeId}".`) | |
return | |
} | |
const currentThemeId = document.querySelector("html").getAttribute("data-style") | |
console.log(`currentThemeId: ${currentThemeId}`) | |
console.log(`themeId: ${themeId}`) | |
if (currentThemeId == themeId) { | |
console.log(`switchToTheme | Currently on preferred theme.`) | |
return | |
} else { | |
console.log(`switchToTheme | Switching to preferred theme "${themeId}".`) | |
window.location.href = `https://famiboards.com/misc/style?style_id=${themeId}` | |
return | |
} | |
const themeObjectsCookie = cookie.getValueByKey("themeObjects") | |
let themeObjects | |
try { | |
themeObjects = JSON.parse(themeObjectsCookie) | |
} catch (error) { | |
console.log(`switchToTheme | WARN | Could not parse themeObjectsCookie.`) | |
} | |
if (Array.isArray(themeObjects) && themeObjects.length) { | |
console.log(themeObjects) | |
} else { | |
document.querySelector(`[data-original-title="Style chooser"]`).click() | |
setTimeout(() => { | |
console.log("...Checking cookie for themeObjects:") | |
themeObjects = cookie.getAsObject() | |
console.log(themeObjects) | |
document.querySelector(".overlay-titleCloser").click() | |
}, 300) | |
} | |
} | |
// Gather all style information | |
document.addEventListener("click", (e) => { | |
if (e.target.getAttribute("data-original-title") == "Style chooser") { | |
setTimeout(() => { | |
const themeLinks = document.querySelectorAll(".overlay-content a.menu-linkRow") | |
const re = /(?:style_id=)(\d*)/ | |
const themeObjects = Array.from(themeLinks).map(link => { | |
let themeId = null | |
const themeName = link.innerText | |
if (link.href) { | |
const matchObject = link.href.match(re) | |
if (matchObject.length > 1) { | |
themeId = matchObject[1] | |
} | |
} | |
// add event listener that updates the preferred Theme ID if one is selected | |
link.addEventListener("click", () => { | |
console.log("switchToTheme | Added selected theme as preferred theme.") | |
cookie.addObject({"preferredThemeId": themeId}) | |
const cookieObject = cookie.getAsObject() | |
console.log(Object.entries(cookieObject)) | |
}) | |
return { | |
themeId: themeId, | |
themeName: link.innerText | |
} | |
}) | |
console.log(`Adding the following to cookie under "themeObjects":`) | |
console.log(JSON.stringify(themeObjects)) | |
cookie.addObject({"themeObjects": JSON.stringify(themeObjects)}) | |
}, 200) | |
} | |
}) | |
if (switchToPreferredTheme) { | |
switchToTheme(themeId) | |
} | |
if (blockerToggle) { | |
block() | |
} | |
if (censorToggle) { | |
censor() | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment