前缀转发

使用 JSON 格式配置规则,支持多条规则、独立开关

Od 19.07.2025.. Pogledajte najnovija verzija.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        前缀转发
// @namespace   http://example.com/
// @license     GPL3
// @version     1
// @description 使用 JSON 格式配置规则,支持多条规则、独立开关
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_registerMenuCommand
// @grant       GM_unregisterMenuCommand
// @run-at      document-start
// @include     http://example.com/
// ==/UserScript==

(function () {
    'use strict';

    /* ------------ 默认配置 ------------ */
    const DEFAULT_RULES = [
        {
            "name": "测试规则",
            "enabled": true,
            "prefix": "https://dev.com/api/",
            "replacement": "http://localhost:8080/"
        },
        {
            "name": "测试规则2",
            "enabled": false,
            "prefix": "https://dev.com/api2/",
            "replacement": "http://localhost:8082/"
        }
    ];

    /* ------------ 读取和保存规则 ------------ */
    function getRules() {
        const savedRules = GM_getValue('rules');
        if (savedRules && typeof savedRules === 'string') {
            try {
                return JSON.parse(savedRules);
            } catch (e) {
                console.error('加载保存的规则出错:', e);
                GM_setValue('rules', JSON.stringify(DEFAULT_RULES));
                return DEFAULT_RULES;
            }
        }
        return DEFAULT_RULES;
    }

    function saveRules(rules) {
        if (Array.isArray(rules)) {
            GM_setValue('rules', JSON.stringify(rules));
        } else {
            console.error('无效的规则格式。规则必须是数组格式。');
            throw new Error('无效的规则格式。规则必须是数组格式。');
        }
    }

    /* ------------ 核心替换逻辑 ------------ */
    function redirect(url) {
        if (typeof url !== 'string') return url;

        const rules = getRules();
        const normalizedUrl = url.trim();

        for (const rule of rules) {
            if (rule.enabled) {
                const normalizedPrefix = rule.prefix.trim();
                if (normalizedUrl.startsWith(normalizedPrefix)) {
                    return rule.replacement + normalizedUrl.slice(normalizedPrefix.length);
                }
            }
        }
        return url;
    }

    /* ------------ 拦截 XHR ------------ */
    const XHROPEN = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function (method, url, ...rest) {
        url = redirect(url);
        return XHROPEN.call(this, method, url, ...rest);
    };

    /* ------------ 拦截 fetch ------------ */
    const ORIG_FETCH = window.fetch;
    window.fetch = function (input, init) {
        if (typeof input === 'string') {
            input = redirect(input);
        } else if (input instanceof Request) {
            const newUrl = redirect(input.url);
            if (newUrl !== input.url) {
                input = new Request(newUrl, input);
            }
        } else if (typeof input === 'object' && input !== null && 'url' in input) {
            input.url = redirect(input.url);
        }
        return ORIG_FETCH.call(this, input, init);
    };

    /* ------------ 菜单管理 ------------ */
    let menuCommands = [];

    function buildMenu() {
        menuCommands.forEach(id => GM_unregisterMenuCommand(id));
        menuCommands = [];

        const configCommand = GM_registerMenuCommand('#️⃣ 配置规则', () => {
            const currentRules = getRules();
            const prettyJson = JSON.stringify(currentRules, null, 2);
            const updatedJson = prompt(
                '输入 JSON 格式的规则:\n[\n  {\n    "name": "规则名称",\n    "enabled": true,\n    "prefix": "原前缀",\n    "replacement": "新前缀"\n  }\n]',
                prettyJson
            );
            if (updatedJson !== null) {
                try {
                    const newRules = JSON.parse(updatedJson);
                    if (Array.isArray(newRules)) {
                        saveRules(newRules);
                        showNotification('已保存,即时生效', 'success');
                        buildMenu();
                    } else {
                        alert('规则必须是数组格式,请检查后重试');
                    }
                } catch (e) {
                    console.error('解析输入的 JSON 出错:', e);
                    alert('JSON 格式错误,请检查后重试');
                }
            }
        });
        menuCommands.push(configCommand);

        const rules = getRules();
        rules.forEach((rule, index) => {
            const toggleCommand = GM_registerMenuCommand(
                `${rule.enabled ? '✅' : '❌'} ${rule.name}`,
                () => {
                    const newRules = getRules();
                    if (index >= 0 && index < newRules.length) {
                        newRules[index].enabled = !newRules[index].enabled;
                        saveRules(newRules);
                        showNotification(`${rule.name} 已${newRules[index].enabled ? '启用' : '禁用'},即时生效`, newRules[index].enabled ? 'success' : 'info');
                        buildMenu();
                    }
                }
            );
            menuCommands.push(toggleCommand);
        });
    }

    buildMenu();

    /* ------------ 通知功能增强 ------------ */
    function showNotification(message, type = 'info') {
        if (!document.body) {
            setTimeout(() => showNotification(message, type), 100);
            return;
        }

        const notification = document.createElement('div');
        notification.className = `tm-notification ${type}`; // 添加类型类名
        notification.textContent = message;

        document.body.appendChild(notification);

        setTimeout(() => {
            notification.classList.add('show');
        }, 10);

        setTimeout(() => {
            notification.classList.remove('show');
            setTimeout(() => {
                document.body.removeChild(notification);
            }, 300);
        }, 3000);
    }

    // 添加样式
    const style = document.createElement('style');
    style.textContent = `
        .tm-notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 10px 20px;
            border-radius: 4px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            transform: translateY(-100%);
            opacity: 0;
            transition: transform 0.3s ease, opacity 0.3s ease;
            z-index: 9999;
            pointer-events: none;
        }
        .tm-notification.success {
            background-color: #4CAF50; /* 启用的绿色背景 */
            color: white;
        }
        .tm-notification.info {
            background-color: #9E9E9E; /* 禁用的灰色背景 */
            color: white;
        }
        .tm-notification.show {
            transform: translateY(0);
            opacity: 1;
        }
    `;
    document.head.appendChild(style);

    /* ------------ 页面加载时显示启用的规则(3秒后消失)------------ */
    function showEnabledRules() {
        const rules = getRules();
        const enabledRules = rules.filter(rule => rule.enabled);
        if (enabledRules.length > 0) {
            const enabledRuleNames = enabledRules.map(rule => rule.name).join(',');
            showNotification(`当前启用的规则:${enabledRuleNames}`, 'success');
        }
    }

    // 页面加载完成后显示启用规则提示
    window.addEventListener('load', showEnabledRules);
})();