BlueSky Correct Terminology

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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 });
})();