BlueSky Correct Terminology

Ensures BlueSky is using the correct terminology by replacing all instances of "post" with "skeet".

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        BlueSky Correct Terminology
// @description Ensures BlueSky is using the correct terminology by replacing all instances of "post" with "skeet".
// @namespace   vivelin.net
// @match       https://bsky.app/*
// @grant       none
// @version     1.0.7
// @author      Vivelin
// @license     MIT
// ==/UserScript==
(function () {
    "use strict";

    /**
     * An object containing an array of RegExp patterns and string replacements for each language.
     */
    const allReplacements = {
        en: [
            { pattern: /\bPost(s)?\b/g, replacement: "Skeet$1" },
            { pattern: /\b([Rr]e)?post(s|ed)?\b/g, replacement: "$1skeet$2" },
        ],
    };

    /**
     * Controls whether user-generated content should be processed (true) or left alone (false, default).
     */
    const shouldReplaceSkeetContent = false;

    /**
     * Replaces the string, if necessary.
     * @param {string} text The original text to replace.
     * @param replacements An array of objects with the RegExp pattern and string replacement.
     * @returns {string} The replaced text.
     */
    function replaceText(text, replacements) {
        if (!text) return text;

        for (const replacement of replacements) {
            if (text.match(replacement.pattern)) {
                text = text.replaceAll(
                    replacement.pattern,
                    replacement.replacement,
                );
            }
        }

        return text;
    }

    /**
     * Recursively processes and replaces all instances of text.
     * @param {Node} node The node to process.
     * @returns {void}
     */
    function processNode(node, replacements) {
        node = node || document.body;

        if (node instanceof Text) {
            node.data = replaceText(node.data, replacements);
        } else if (node instanceof HTMLElement) {
            if (
                node.isContentEditable ||
                (!shouldReplaceSkeetContent &&
                    (node.dataset.wordWrap === "1" ||
                        node.dataset.testid === "profileHeaderDisplayName" ||
                        node.ariaLabel === "Expand alt text" ||
                        (node instanceof HTMLAnchorElement &&
                            node.href &&
                            node.href.match(/\/profile\//))))
            ) {
                return;
            }

            for (const child of node.childNodes) {
                processNode(child, replacements);
            }
        }
    }

    function observeCallback(_mutationList, _observer) {
        const lang = document.getElementsByTagName("html")[0].lang || "en";
        const replacements = allReplacements[lang];
        if (replacements) {
            processNode(document.body, replacements);
        }
    }

    const observer = new MutationObserver(observeCallback);
    observer.observe(document.body, { childList: true, subtree: true });
})();