标题替换

支持标签页关键词删除、替换、自定义规则、正则、split。规则按域名保存,仅对当前网站生效。

// ==UserScript==
// @name         标题替换
// @version      1.01
// @description  支持标签页关键词删除、替换、自定义规则、正则、split。规则按域名保存,仅对当前网站生效。
// @author         yzcjd
// @author2       ChatGPT4 辅助
// @namespace    https://gf.qytechs.cn/users/1171320
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    const siteKey = location.hostname;

    const helpText =
`✅ 规则语法说明(中间有空格,不能删完整个标题)
- 关键词              删除关键词
-i 关键词             忽略大小写删除
-s 关键词             删除关键词及空格
-^ 关键词             删除标题开头关键词
-$ 关键词             删除标题结尾关键词
>split 关键词          截取关键词前内容
r 原词 => 新词        替换关键词为新词(支持正则)

✅ 示例
  -i - 【4k】
  -s - YouTube
  r 营销管理平台 => 替换词
  r /在线.*工具/ => 替换词2

✅ 多个规则用英文逗号分隔,如:
  -i - 【4k】,r /营销.*平台/ => 替换词
`;

    let savedRules = GM_getValue('rules_' + siteKey, '');

    GM_registerMenuCommand('📝 规则', () => {
        let input = prompt(helpText + '\n当前页面规则:\n' + (savedRules || '无'), savedRules);
        if (input === null) return;

        const entries = input
            .replace(/,/g, ',')
            .split(',')
            .map(s => s.trim())
            .filter(s => s.length > 0);

        GM_setValue('rules_' + siteKey, entries.join(','));
        showNotice('✅ 修改成功,下次打开网页生效');
    });

    function showNotice(msg) {
        const box = document.createElement('div');
        box.textContent = msg;
        Object.assign(box.style, {
            position: 'fixed',
            top: '20px',
            left: '50%',
            transform: 'translateX(-50%)',
            background: 'rgba(0,0,0,0.8)',
            color: 'white',
            padding: '10px 20px',
            borderRadius: '8px',
            fontSize: '14px',
            zIndex: 99999,
            transition: 'opacity 1s ease',
            opacity: '1',
        });
        document.body.appendChild(box);
        setTimeout(() => { box.style.opacity = '0'; }, 2000);
        setTimeout(() => { box.remove(); }, 3000);
    }

    function escapeRegExp(str) {
        return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }

    function parseRule(ruleStr) {
        if (!ruleStr) return null;

        // 替换规则 r 原词 => 新词(支持正则)
        if (ruleStr.startsWith('r ')) {
            const match = ruleStr.match(/^r\s+(.+?)\s*=>\s*(.+)$/);
            if (!match) return null;
            const from = match[1].trim();
            const to = match[2];

            try {
                const reg = from.startsWith('/') && from.endsWith('/')
                    ? new RegExp(from.slice(1, -1), 'g')
                    : new RegExp(escapeRegExp(from), 'g');
                return title => title.replace(reg, to);
            } catch {
                return null;
            }
        }

        // split
        if (ruleStr.startsWith('>split')) {
            const key = ruleStr.slice(6).trim();
            return title => title.split(key)[0];
        }

        const m = ruleStr.match(/^(-i|-s|-\^|-\$|-)\s+(.+)$/);
        if (!m) return null;

        const prefix = m[1];
        const keywordRaw = m[2].replace(/%7C/gi, '|');
        const regexFlags = 'gi';

        let keywordEscaped;
        if (keywordRaw.includes('|')) {
            keywordEscaped = keywordRaw
                .split('|')
                .map(k => escapeRegExp(k.trim()))
                .join('|');
        } else {
            keywordEscaped = escapeRegExp(keywordRaw);
        }

        let pattern = '';
        switch (prefix) {
            case '-i': pattern = keywordEscaped; break;
            case '-s': pattern = `\\s*(${keywordEscaped})\\s*`; break;
            case '-^': pattern = `^(${keywordEscaped})\\s*`; break;
            case '-$': pattern = `\\s*(${keywordEscaped})$`; break;
            case '-':  pattern = `\\s*(${keywordEscaped})\\s*`; break;
            default: return null;
        }

        try {
            const reg = new RegExp(pattern, regexFlags);
            return title => title.replace(reg, '');
        } catch {
            return null;
        }
    }

    const ruleFuncs = [];
    if (savedRules) {
        for (const r of savedRules.split(',').map(s => s.trim())) {
            const f = parseRule(r);
            if (f) ruleFuncs.push(f);
        }
    }

    if (ruleFuncs.length === 0) return;

    function cleanTitle(title) {
        let t = title;
        for (const f of ruleFuncs) t = f(t);
        return t;
    }

    let lastTitle = '';
    function forceTitle() {
        const newTitle = cleanTitle(document.title);
        if (newTitle && newTitle !== document.title) {
            document.title = newTitle;
            lastTitle = newTitle;
        }
    }

    // 启动后立即尝试修改
    forceTitle();

    // 监听 <title> 节点变化
    const titleEl = document.querySelector('title');
    if (titleEl) {
        const observer = new MutationObserver(() => {
            if (document.title !== lastTitle) {
                forceTitle();
            }
        });
        observer.observe(titleEl, { childList: true });
    }

    // 定时兜底(避免 Vue 或延迟加载还原标题)
    setInterval(() => {
        if (document.title !== lastTitle) {
            forceTitle();
        }
    }, 1200);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址