Greasy Fork

Greasy Fork is available in English.

X 黄推+币圈机器人终极隐藏器

强力过滤引流/币圈/擦边/博彩等机器人内容,支持快捷键添加、管理面板与用户名白名单。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         X 黄推+币圈机器人终极隐藏器
// @namespace    https://grok.x.ai
// @version      1.8
// @description  强力过滤引流/币圈/擦边/博彩等机器人内容,支持快捷键添加、管理面板与用户名白名单。
// @author       Grok、codex、Claude
// @match        https://x.com/*
// @match        https://twitter.com/*
// @grant        none
// @run-at       document-start
// @license      MIT
// @icon         http://greasyfork.icu/images/blacklogo16.png
// @homepageURL  http://greasyfork.icu/zh-CN/scripts/570683
// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'x_block_words_v1';
    const WHITELIST_KEY = 'x_whitelist_names_v1';
    const HIDDEN_ATTR = 'data-x-ultra-hidden';
    const KEEP_SPACE_WHEN_HIDE = false;
    const CACHE_HASH_ATTR = 'data-x-last-hash';
    const CACHE_SHOULD_ATTR = 'data-x-last-should';

    const BUILTIN_PATTERNS = [
        /快领我回家|扣1白给|陪我聊聊天|有弟弟来一起|推特第一骚|我约过她|姐姐在等你哦|你喜欢我的我都有哦/i,
        /懂[得的].{0,3}(来|私|入|dd|联系|撩|进|加|懂)/i,
        /免费破|私处|想被人炒|万达广场|约个有经验的大叔|找我那视频啊|找个哥哥调教|小m在线等调/i,
        /男大|足控|蜜桃臀|推特刷粉丝|刷粉/i,
        /主人|舔狗|小狗|线下|上门|白虎|急找|私约|野战|在线蹲|蹲一个/i,
        /有没有单男|有没有游戏搭子|有没有单女|单男/i,
        /急需一位|chu男|Chu男|处男|处女找个哥哥|无偿|免费破|调教|附近的有没有|满足我|看做爱|不如做爱/i,
        /母狗|欲望少女|纯欲|反差/i,
        /我是真人/i,
        /那亲亲吧|你的娱乐群/i,
        /空投|USDT.*(奖池|交易|转账|推广)|拉盘|打新项目|上币路线|Pre-IPO|合约.*(喊单|爆仓)/i,
        /引流|私信|DM|加V|VX|微信|电报|TG|群号|群聊|福利群|进群|进组/i,
        /开户|理财|搬砖|量化|带单|套利|返利|高收益|稳赚|保本|日赚/i,
        /博彩|赌场|下注|百家乐|时时彩|快三|网投|棋牌游戏/i,
        /约炮|一夜情|同城|包夜|全套|外围|小姐|嫩模|学生妹/i,
        /裸聊|视频聊天|视频交友|语音交友|聊骚|骚扰|撩骚/i,
        /兼职|日结|招募|代理|推广员|刷单|返现|冲量/i,
        /空降|互关|互粉|互赞|点赞关注|关注我|回关|求关/i,
        /[\u2190-\u21FF\u2B00-\u2BFF\u27A1]/,
        /搭子|固炮/i,
        /陪聊|陪玩|陪睡|宠物系/i,
        /在线等(你|哥哥|弟弟)/i,
        /淫/i,
        /温柔主(人)?/i,
        /(^|[^A-Za-z0-9])[sSmM]([^A-Za-z0-9]|$)/,
        /萌猫|小烧货|千万次心跳|护士姐姐|学妹|学姐|反差婊/i,
        /(萌|甜|软|奶|乖|小|可爱).*(猫|狗|兔|鹿|狐|虎|猫咪)/i,
        /(护士|学妹|学姐|老师|姐姐|妹妹).*(小烧货|反差|纯欲|欲望)/i,
        /\b[A-Z][a-z]{4,}[A-Z][a-z]{1,3}\d{5,}\b/,
        /有没有离得近的|被.*(小烧货|骚货|sao货)|扣1白给啊?|温柔s|爹系男友/i,
        /(射射|色色|涩涩|瑟瑟).{0,6}视频|瓦学弟|破处/i,
        /催情|春药|迷药|催春|迷幻|迷奸|买春|迷.?药/i
    ];

    const NAME_PATTERNS = [
        /^Date-/i,
        /反差|母狗|chu男|Chu男|处男|处女|臀|破处|私约|野战|处男|白虎|无偿|处女|找主人|免费|温柔主/i,
        /\b[A-Z][a-z]{4,}[A-Z][a-z]{1,3}\d{5,}\b/,
        /懂[得的].{0,3}(来|私|入|dd|联系|撩|进|加|懂)/i,
        /男大|足控|线下|曰|日|免费线下|被人炒|后入|同城|可飞|蜜桃臀|推特刷粉丝|刷粉/i
    ];
    function escapeRegExp(text) {
        return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }


    // 同型字/形近字映射:黄推常用替换手段,normalize 前先还原
    const CONFUSABLES = {
        '\u66f0': '\u65e5',  // 曰→日
        '\u2f72': '\u65e5',  // ⽇→日 (康熙部首)
        '\u7384': '\u4e3b',  // 玊→主 (少见但有)
        '\u6bcb': '\u6bcd',  // 毋→母
        '\u72ac': '\u5927',  // 犬→大
        '\u5165': '\u4eba',  // 入→人
        '\u56d7': '\u53e3',  // 囗→口
        '\u7dab': '\u7ebf',  // 線→线
        '\u7e10': '\u7ebf',  // 縐→线 (繁体)
        '\u7dda': '\u7ebf',  // 綫→线
        '\u51e6': '\u5904',  // 処→处 (日语汉字)
        '\u5df3': '\u5df2',  // 巳→已
        '\u5df1': '\u5df2',  // 己→已
    };
    // 全角字母/数字 → 半角 (U+FF21-FF3A → A-Z, U+FF41-FF5A → a-z, U+FF10-FF19 → 0-9)
    for (let i = 0; i < 26; i++) {
        CONFUSABLES[String.fromCharCode(0xFF21 + i)] = String.fromCharCode(0x41 + i);
        CONFUSABLES[String.fromCharCode(0xFF41 + i)] = String.fromCharCode(0x61 + i);
    }
    for (let i = 0; i < 10; i++) {
        CONFUSABLES[String.fromCharCode(0xFF10 + i)] = String.fromCharCode(0x30 + i);
    }
    const CONFUSABLE_RE = new RegExp('[' + Object.keys(CONFUSABLES).map(c =>
        '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')
    ).join('') + ']', 'g');

    function deconfuse(text) {
        return (text || '').replace(CONFUSABLE_RE, ch => CONFUSABLES[ch] || ch);
    }

    // 剥除零宽空格、零宽(非)连接符、方向标记、变体选择符、
    // 软连字符、BOM、字节序标记等所有不可见干扰字符
    const INVISIBLE_RE = /[\u200B-\u200F\u2028-\u202F\u2060-\u206F\uFEFF\u00AD\u034F\u061C\u180E\uFFF9-\uFFFC\u{E0001}\u{E0020}-\u{E007F}\u{13430}-\u{1343F}]/gu;

    function stripInvisible(text) {
        return (text || '').replace(INVISIBLE_RE, '');
    }

    function normalize(text) {
        return deconfuse(stripInvisible(text))
            .toLowerCase()
            .replace(/[\s\.\,\!\?\-_/\\()\[\]{}"'`~@#$%^&*+=<>|:;、,。!?【】()《》“”‘’·]+/g, '')
            .replace(/[\p{Extended_Pictographic}\uFE0F\u{1F3FB}-\u{1F3FF}]/gu, '');
    }

    function isMostlyEmoji(text) {
        const stripped = (text || '').replace(/[\p{Extended_Pictographic}\uFE0F\u{1F3FB}-\u{1F3FF}\s\.,!?;:、,。!?【】()《》“”‘’·~`'"-]/gu, '');
        const emojiCount = ((text || '').match(/[\p{Extended_Pictographic}]/gu) || []).length;
        return stripped.length === 0 && emojiCount >= 3;
    }

    function emojiOnlyWithHearts(text) {
        const t = text || '';
        const nonEmoji = t.replace(/[\p{Extended_Pictographic}\uFE0F\u{1F3FB}-\u{1F3FF}\s\.,!?;:、,。!?【】()《》“”‘’·~`'"-]/gu, '');
        if (nonEmoji.length > 2) return false;
        const emojis = (t.match(/[\p{Extended_Pictographic}]/gu) || []).length;
        if (emojis < 1) return false;
        return /[\u2764\uFE0F\u{1F49B}\u{1F49A}\u{1F499}\u{1F49C}\u{1F90E}\u{1F90D}\u{1F5A4}\u2763]/u.test(t);
    }

    function loadUserWords() {
        try {
            const raw = localStorage.getItem(STORAGE_KEY);
            const list = raw ? JSON.parse(raw) : [];
            return Array.isArray(list) ? list : [];
        } catch {
            return [];
        }
    }

    function loadWhitelist() {
        try {
            const raw = localStorage.getItem(WHITELIST_KEY);
            const list = raw ? JSON.parse(raw) : [];
            return Array.isArray(list) ? list : [];
        } catch {
            return [];
        }
    }

    let userWords = loadUserWords();
    let userRegexes = buildUserRegexes(userWords);
    let whitelistNames = loadWhitelist();
    let rulesVersion = 0;

    function buildUserRegexes(words) {
        return words
            .map(w => String(w || '').trim())
            .filter(Boolean)
            .map(w => new RegExp(`.*${escapeRegExp(w)}.*`, 'i'));
    }

    function saveUserWords() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(userWords));
        userRegexes = buildUserRegexes(userWords);
        rulesVersion++;
    }

    function saveWhitelist() {
        localStorage.setItem(WHITELIST_KEY, JSON.stringify(whitelistNames));
        rulesVersion++;
    }

    function addUserWord(word) {
        const w = String(word || '').trim();
        if (!w) return;
        if (userWords.includes(w)) return;
        userWords.push(w);
        saveUserWords();
        toast(`已加入屏蔽词:${w}`);
        scheduleScan();
    }

    function addWhitelistName(word) {
        const w = String(word || '').trim();
        if (!w) return;
        if (whitelistNames.includes(w)) return;
        whitelistNames.push(w);
        saveWhitelist();
        toast(`已加入白名单:${w}`);
        scheduleScan();
    }

    function isWhitelisted(nameText) {
        if (!nameText) return false;
        const raw = nameText.toLowerCase();
        const compact = normalize(nameText);
        return whitelistNames.some(w => {
            const ww = String(w || '').trim().toLowerCase();
            if (!ww) return false;
            const wCompact = normalize(ww);
            return raw.includes(ww) || compact.includes(wCompact);
        });
    }

    function hashString(value) {
        let hash = 5381;
        for (let i = 0; i < value.length; i++) {
            hash = ((hash << 5) + hash) ^ value.charCodeAt(i);
        }
        return hash >>> 0;
    }

    function shouldHide(text, nameText) {
        const original = deconfuse(stripInvisible(text || ''));
        const compact = normalize(original);
        const name = deconfuse(stripInvisible(nameText || ''));
        const nameCompact = normalize(name);
        const whitelisted = isWhitelisted(name);
        if (emojiOnlyWithHearts(original) || isMostlyEmoji(original)) return true;
        // 用户黑名单(内容)优先命中,避免被 quickMatch 短路
        if (userRegexes.some(r => r.test(original) || r.test(compact))) return true;
        if (!whitelisted && NAME_PATTERNS.some(r => r.test(name) || r.test(nameCompact))) return true;
        if (!whitelisted && BUILTIN_PATTERNS.some(r => r.test(name) || r.test(nameCompact))) return true;
        if (BUILTIN_PATTERNS.some(r => r.test(original) || r.test(compact))) return true;
        if (!whitelisted && userRegexes.some(r => r.test(name) || r.test(nameCompact))) return true;
        return false;
    }
    function extractTextWithEmojis(node) {
        if (!node) return '';
        let text = '';
        for (let i = 0; i < node.childNodes.length; i++) {
            const child = node.childNodes[i];
            if (child.nodeType === 3) {
                text += child.nodeValue;
            } else if (child.nodeType === 1) {
                if (child.tagName === 'IMG' && child.alt) {
                    text += child.alt;
                } else {
                    text += extractTextWithEmojis(child);
                }
            }
        }
        return text;
    }

    function collectArticleText(article) {
        const parts = [];
        article.querySelectorAll('[data-testid="tweetText"]').forEach(node => {
            const text = extractTextWithEmojis(node);
            if (text) parts.push(text);
        });
        return parts.join(' ');
    }
    function collectNameText(article) {
        const nameNode = article.querySelector('[data-testid="User-Name"]');
        return nameNode ? (nameNode.textContent || '') : '';
    }

    function unhideArticle(article) {
        if (!article.getAttribute(HIDDEN_ATTR)) return;
        article.removeAttribute(HIDDEN_ATTR);
        article.style.removeProperty('visibility');
        article.style.removeProperty('display');
        article.style.removeProperty('pointer-events');
        const container = article.closest('div[data-testid="cellInnerDiv"]');
        if (container) {
            container.style.removeProperty('display');
            container.style.removeProperty('visibility');
        }
    }

    function hideArticle(article) {
        const text = collectArticleText(article).trim();
        const nameText = collectNameText(article).trim();
        const hash = hashString(`${rulesVersion}|${text}|${nameText}`);
        const cachedHash = article.getAttribute(CACHE_HASH_ATTR);
        const cachedShould = article.getAttribute(CACHE_SHOULD_ATTR);
        let should;
        if (cachedHash && Number(cachedHash) === hash && cachedShould !== null) {
            should = cachedShould === '1';
        } else {
            should = shouldHide(text, nameText);
            article.setAttribute(CACHE_HASH_ATTR, String(hash));
            article.setAttribute(CACHE_SHOULD_ATTR, should ? '1' : '0');
        }
        if (!should) {
            unhideArticle(article);
            return;
        }
        if (article.getAttribute(HIDDEN_ATTR)) return;
        article.setAttribute(HIDDEN_ATTR, '1');
        if (KEEP_SPACE_WHEN_HIDE) {
            article.style.setProperty('visibility', 'hidden', 'important');
            article.style.setProperty('pointer-events', 'none', 'important');
        } else {
            article.style.setProperty('display', 'none', 'important');
            article.style.setProperty('visibility', 'hidden', 'important');
            const container = article.closest('div[data-testid="cellInnerDiv"]');
            if (container) container.style.setProperty('display', 'none', 'important');
        }
    }

    function scanAll() {
        document.querySelectorAll('article[data-testid="tweet"]').forEach(hideArticle);
    }

    let scanInProgress = false;
    function scheduleScan() {
        if (scanInProgress) return;
        scanInProgress = true;
        scanAll();
        scanInProgress = false;
    }

    function handleMutations(mutations) {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.nodeType !== 1) continue;
                if (node.matches && node.matches('article[data-testid="tweet"]')) {
                    hideArticle(node);
                } else if (node.querySelectorAll) {
                    node.querySelectorAll('article[data-testid="tweet"]').forEach(hideArticle);
                }
            }
        }
    }

    function getSelectionText() {
        return String(window.getSelection && window.getSelection().toString() || '').trim();
    }

    function createManagePanel() {
        const panel = document.createElement('div');
        panel.id = 'x-ultra-block-panel';
        panel.style.cssText = [
            'position:fixed',
            'right:16px',
            'bottom:64px',
            'width:280px',
            'max-height:360px',
            'overflow:auto',
            'background:#111',
            'color:#fff',
            'border:1px solid #444',
            'border-radius:10px',
            'padding:10px',
            'font-size:12px',
            'z-index:2147483647',
            'display:none'
        ].join(';');

        const header = document.createElement('div');
        header.textContent = '屏蔽词管理';
        header.style.cssText = 'font-weight:600; margin-bottom:8px;';

        const list = document.createElement('div');

        const inputRow = document.createElement('div');
        inputRow.style.cssText = 'display:flex; gap:6px; margin-bottom:8px;';
        const input = document.createElement('input');
        input.placeholder = '手动添加屏蔽词';
        input.style.cssText = 'flex:1; padding:4px 6px; border:1px solid #333; border-radius:6px; background:#0b0b0b; color:#fff;';
        const addBtn = document.createElement('button');
        addBtn.textContent = '添加';
        addBtn.style.cssText = 'padding:4px 8px; cursor:pointer;';
        inputRow.appendChild(input);
        inputRow.appendChild(addBtn);

        const quickRow = document.createElement('div');
        quickRow.style.cssText = 'display:flex; gap:6px; margin-bottom:8px;';
        const addSelBtn = document.createElement('button');
        addSelBtn.textContent = '添加选中';
        addSelBtn.style.cssText = 'flex:1; padding:4px 6px; cursor:pointer;';
        const hint = document.createElement('div');
        hint.textContent = '快捷键:Alt+B 添加选中文本';
        hint.style.cssText = 'flex:2; color:#aaa; align-self:center;';
        quickRow.appendChild(addSelBtn);
        quickRow.appendChild(hint);

        const actions = document.createElement('div');
        actions.style.cssText = 'margin-top:8px; display:flex; gap:6px;';

        const clearBtn = document.createElement('button');
        clearBtn.textContent = '清空';
        clearBtn.style.cssText = 'flex:1; padding:4px 6px; cursor:pointer;';

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '关闭';
        closeBtn.style.cssText = 'flex:1; padding:4px 6px; cursor:pointer;';

        actions.appendChild(clearBtn);
        actions.appendChild(closeBtn);

        panel.appendChild(header);
        panel.appendChild(inputRow);
        panel.appendChild(quickRow);
        panel.appendChild(list);
        panel.appendChild(actions);

        const wlHeader = document.createElement('div');
        wlHeader.textContent = '用户名白名单';
        wlHeader.style.cssText = 'font-weight:600; margin:12px 0 6px;';

        const wlInputRow = document.createElement('div');
        wlInputRow.style.cssText = 'display:flex; gap:6px; margin-bottom:8px;';
        const wlInput = document.createElement('input');
        wlInput.placeholder = '添加白名单用户名/关键词';
        wlInput.style.cssText = 'flex:1; padding:4px 6px; border:1px solid #333; border-radius:6px; background:#0b0b0b; color:#fff;';
        const wlAddBtn = document.createElement('button');
        wlAddBtn.textContent = '添加';
        wlAddBtn.style.cssText = 'padding:4px 8px; cursor:pointer;';
        wlInputRow.appendChild(wlInput);
        wlInputRow.appendChild(wlAddBtn);

        const wlHint = document.createElement('div');
        wlHint.textContent = '快捷键:Alt+W 添加选中到白名单';
        wlHint.style.cssText = 'color:#aaa; margin:0 0 8px;';

        const wlList = document.createElement('div');

        const wlActions = document.createElement('div');
        wlActions.style.cssText = 'margin-top:8px; display:flex; gap:6px;';
        const wlClearBtn = document.createElement('button');
        wlClearBtn.textContent = '清空白名单';
        wlClearBtn.style.cssText = 'flex:1; padding:4px 6px; cursor:pointer;';
        wlActions.appendChild(wlClearBtn);

        panel.appendChild(wlHeader);
        panel.appendChild(wlInputRow);
        panel.appendChild(wlHint);
        panel.appendChild(wlList);
        panel.appendChild(wlActions);
        document.documentElement.appendChild(panel);

        function renderList(scrollToEnd = false) {
            list.innerHTML = '';
            if (!userWords.length) {
                const empty = document.createElement('div');
                empty.textContent = '暂无自定义屏蔽词';
                empty.style.cssText = 'color:#888; padding:4px 0;';
                list.appendChild(empty);
                return;
            }
            userWords.forEach((w, idx) => {
                const row = document.createElement('div');
                row.style.cssText = 'display:flex; justify-content:space-between; align-items:center; padding:4px 0; border-bottom:1px dashed #333;';
                const text = document.createElement('span');
                text.textContent = w;
                const del = document.createElement('button');
                del.textContent = '删除';
                del.style.cssText = 'margin-left:8px; cursor:pointer;';
                del.addEventListener('click', () => {
                    userWords.splice(idx, 1);
                    saveUserWords();
                    renderList();
                    scheduleScan();
                });
                row.appendChild(text);
                row.appendChild(del);
                list.appendChild(row);
            });
            if (scrollToEnd) {
                list.lastElementChild?.scrollIntoView({ behavior: 'smooth', block: 'end' });
            }
        }

        function renderWhitelist(scrollToEnd = false) {
            wlList.innerHTML = '';
            if (!whitelistNames.length) {
                const empty = document.createElement('div');
                empty.textContent = '暂无白名单';
                empty.style.cssText = 'color:#888; padding:4px 0;';
                wlList.appendChild(empty);
                return;
            }
            whitelistNames.forEach((w, idx) => {
                const row = document.createElement('div');
                row.style.cssText = 'display:flex; justify-content:space-between; align-items:center; padding:4px 0; border-bottom:1px dashed #333;';
                const text = document.createElement('span');
                text.textContent = w;
                const del = document.createElement('button');
                del.textContent = '删除';
                del.style.cssText = 'margin-left:8px; cursor:pointer;';
                del.addEventListener('click', () => {
                    whitelistNames.splice(idx, 1);
                    saveWhitelist();
                    renderWhitelist();
                    scheduleScan();
                });
                row.appendChild(text);
                row.appendChild(del);
                wlList.appendChild(row);
            });
            if (scrollToEnd) {
                wlList.lastElementChild?.scrollIntoView({ behavior: 'smooth', block: 'end' });
            }
        }

        addBtn.addEventListener('click', () => {
            addUserWord(input.value);
            input.value = '';
            renderList(true);
        });
        input.addEventListener('keydown', (event) => {
            if (event.key === 'Enter') {
                addUserWord(input.value);
                input.value = '';
                renderList(true);
            }
        });
        addSelBtn.addEventListener('click', () => {
            const sel = getSelectionText();
            if (sel) addUserWord(sel);
            renderList(true);
        });

        wlAddBtn.addEventListener('click', () => {
            addWhitelistName(wlInput.value);
            wlInput.value = '';
            renderWhitelist(true);
        });
        wlInput.addEventListener('keydown', (event) => {
            if (event.key === 'Enter') {
                addWhitelistName(wlInput.value);
                wlInput.value = '';
                renderWhitelist(true);
            }
        });

        clearBtn.addEventListener('click', () => {
            userWords = [];
            saveUserWords();
            renderList();
            scheduleScan();
        });
        closeBtn.addEventListener('click', () => {
            panel.style.display = 'none';
        });

        wlClearBtn.addEventListener('click', () => {
            whitelistNames = [];
            saveWhitelist();
            renderWhitelist();
            scheduleScan();
        });

        function togglePanel() {
            renderList();
            renderWhitelist();
            panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
        }

        return { panel, togglePanel, renderWhitelist };
    }

    function toast(message) {
        const node = document.createElement('div');
        node.textContent = message;
        node.style.cssText = [
            'position:fixed',
            'right:16px',
            'bottom:16px',
            'background:#222',
            'color:#fff',
            'border:1px solid #444',
            'padding:8px 10px',
            'border-radius:8px',
            'font-size:12px',
            'z-index:2147483647'
        ].join(';');
        document.documentElement.appendChild(node);
        setTimeout(() => node.remove(), 1600);
    }

    function init() {
        scanAll();
        const observer = new MutationObserver(handleMutations);
        observer.observe(document.documentElement, { childList: true, subtree: true });
        window.addEventListener('load', scheduleScan);
        window.addEventListener('popstate', scheduleScan);
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;
        history.pushState = function () {
            const result = originalPushState.apply(this, arguments);
            scheduleScan();
            return result;
        };
        history.replaceState = function () {
            const result = originalReplaceState.apply(this, arguments);
            scheduleScan();
            return result;
        };
        window.addEventListener('scroll', () => { if (Math.random() < 0.08) scheduleScan(); }, { passive: true });
        setInterval(scheduleScan, 1200);

        const manage = createManagePanel();
        document.addEventListener('keydown', (event) => {
            if (event.altKey && !event.ctrlKey && !event.metaKey && event.key.toLowerCase() === 'm') {
                manage.togglePanel();
            }
        }, { capture: true });

        document.addEventListener('keydown', (event) => {
            if (event.altKey && !event.ctrlKey && !event.metaKey && event.key.toLowerCase() === 'b') {
                const sel = getSelectionText();
                if (sel) addUserWord(sel);
            }
        }, { capture: true });

        document.addEventListener('keydown', (event) => {
            if (event.altKey && !event.ctrlKey && !event.metaKey && event.key.toLowerCase() === 'w') {
                const sel = getSelectionText();
                if (sel) {
                    addWhitelistName(sel);
                    manage.renderWhitelist?.(true);
                }
            }
        }, { capture: true });
    }

    if (document.documentElement) {
        init();
    } else {
        document.addEventListener('readystatechange', () => {
            if (document.documentElement) init();
        }, { once: true });
    }
})();