淋语化

将网页上的文字都变成淋语~

// ==UserScript==
// @name         淋语化
// @namespace    azite.cn
// @version      0.1.2
// @description  将网页上的文字都变成淋语~
// @author       Azite
// @license      MIT
// @match        *
// ==/UserScript==

(function () {
    'use strict';

    // 用闭包的方式实现唯一编号生成
    const getId = (() => {
        let counter = 0;
        return () => ++counter;
    })();

    function workerFn() {
        // 基础淋语词库
        const linyuBase = new Map([
            // 多字词语
            ["笑死", "孝鼠"],
            ["真的?是", "珍素"],
            ["大胆", "Big胆"],
            ["喜欢", "洗翻"],
            ["蠢直", "春芝"],
            ["姐姐", "解解"],
            ["得到|获得", "阴到"],
            ["nobody cares|[没|无]人在意", "nbcs"],
            ["逼飞奶炸", "bfnz"],
            ["蔡依林", ["淋淋", "脚淋"]],
            ["天[哪|啊]", "天啦噜"],
            ["同性恋", "通讯录"],
            ["几把|鸡巴", "乌干达"],
            ["孩子", "孩柱"],
            ["样子", "样柱"],
            ["停止", "婷芷"],
            ["天才", "地才"],
            ["美人", "花蝴蝶"],
            ["丑[得|的|地]要死", "丑Die"],
            ["盯上", "锁定"],
            ["聆听", "淋听"],
            ["足控", "甜椒婆"],
            ["nmsl|你妈死了", "尼美舒利"],

            // 语气词
            ["了", "惹"],
            ["鼠惹", "鼠了"], // 校正
            ["嘻", "吸"],
            ["呵+[!|!]", "厚!"],
            ["啊+[!|!]", "厚!"],
            ["啊+。?$", "厚"],
            ["哦", "厚"],
            ["吗[?|?]", "厚?"],

            // 称谓
            ["兄弟", "姐妹"],
            ["xdm", "姐妹们"],

            // 一些单字
            ["哭", "Cry"],
            ["死", "鼠"],
            ["是", "素"],
            ["停", "婷"],
            ["谢", "靴"],
            ["心", "熏"],
            ["我", "本可"],
            // ["逼", ["β", "贝塔"]]
        ]);

        // 其他淋语词库,如有更多词库可以联系我添加

        // 王者荣耀词库
        const linyuWzry = new Map([
            ["杨玉环", ["李华", "你华"]],
            ["貂蝉", "淋蝉"],
            ["西施", ["拉客妹", "吸虱"]],
            ["王昭君", "鸡菌"],
            ["(不知)?火舞", "东瀛女特务"],
            ["(上官)?婉儿", ["蒜姐", "飞天婆"]],
            ["武则天", "自控婆"],
            ["米莱迪", ["超生婆", "下蛋婆"]],
            ["安琪拉", "安嫔"],
            ["妲己", "狐臭女"],
            ["甄姬", "泔水婆"],
            ["公孙离", "驴姐"],
            ["小乔", "矮子乔"],
            ["沈梦溪", "阴猫"],
            ["妓芈", "妓芈"],
            ["孙尚香", "滚地婆"],
            ["虞姬", "🐟🐔"],
            ["艾琳", "蚊子婆"],
            ["伽罗", "长臂猿"],
            ["花木兰", "花铁t"],
            ["夏洛特", ["夏铁t", "西洋女特务"]],
            ["孙膑", "孙答应"],
            ["(百里)?守约", "阴约"],
            ["诸葛亮", "母亮"],
            ["韩信", "阴信"],
            ["李白", "母白"],
            ["元歌", "阴歌"],
            ["扁鹊", "下毒婆"],
            ["世隐", "明公公"],
            ["狄仁杰", "狄公公"],
            ["云缨", "孕婴"],
            ["司空震", "司空daddy"],
            ["孙策", "孙笨"],
            ["李信", "阴信"],
            ["暃", "母妃"],
            ["孙悟空|猴子", "阴🐒"],
            ["曜|臭脚曜|🐭曜", ["臭脚曜", "🐭曜"]],
        ]);

        // 原神词库(不完全,欢迎补充)
        const linyuGenshin = new Map([
            ["(珊瑚宫)?心海", "水母婆"],
            ["可莉", "炸弹婆"],
            ["七七", "僵尸婆"],
            ["夜兰", "我腋"],
        ])

        const linyuMap = new Map([...linyuBase, ...linyuWzry, ...linyuGenshin]);

        function convert(text) {
            for (let [key, value] of linyuMap) {
                // 将key转换为正则表达式并设置全局匹配
                if (!(key instanceof RegExp)) {
                    key = new RegExp(key, "g");
                }

                // 若值为一个数组,则从数组中随机抽取一个
                if (value instanceof Array) {
                    value = value[Math.floor(Math.random() * value.length)];
                }

                text = text.replace(key, value);
            }
            return text;
        }

        onmessage = function (e) {
            const data = e.data;
            data.text = convert(data.text);
            // 将转换后的数据传给主线程
            postMessage(data);
        }
    }

    // 这里用worker是为了防止大量匹配正则导致渲染卡顿
    // 将函数转换成url,再构建worker
    const workerUrl = URL.createObjectURL(new Blob(['(' + workerFn.toString() + ')()'], { type: 'text/javascript' }));
    const worker = new Worker(workerUrl);

    // 存放未处理的节点(听说用map效率会比object高)
    const unprocessedNodes = new Map();

    // 从worker收到处理后的数据
    worker.onmessage = function (e) {
        const data = e.data;
        const node = unprocessedNodes.get(data.id);
        // 当转换后与原文本不一致时才修改dom,减少性能开销
        if (data.text !== node.nodeValue) {
            node.nodeValue = data.text;
        }
        // 处理完成后删除记录
        unprocessedNodes.delete(data.id);
    };

    function handleNode(node) {
        // 只处理非脚本的文本节点
        if (node.nodeType === Node.TEXT_NODE && node.nodeName !== "SCRIPT") {
            // 忽略空白节点
            if (node.nodeValue.trim() === "") return;

            // 给每个节点分配一个唯一id,传入worker
            const id = getId();
            unprocessedNodes.set(id, node);
            worker.postMessage({ id, text: node.nodeValue });
        } else {
            if (node.childNodes.length > 0) {
                node.childNodes.forEach(handleNode);
            }
        }
    }

    handleNode(document.body);

    // 观察整个body的节点变动
    new MutationObserver((mutationList) => {
        mutationList.forEach(mutation => {
            handleNode(mutation.target);
        });
    }).observe(document.body, {
        subtree: true,
        childList: true,
        attributes: false // 不监听属性值变动
    });
})();