Skip to content

Instantly share code, notes, and snippets.

@bellydrum
Last active November 18, 2024 19:57
Show Gist options
  • Save bellydrum/00850303addd8424735560d01b060c6a to your computer and use it in GitHub Desktop.
Save bellydrum/00850303addd8424735560d01b060c6a to your computer and use it in GitHub Desktop.
Famiboards Browser Utility
// ==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