south-plus域名自动替换 (v2.0)

自动替换域名并支持自定义替换规则

// ==UserScript==
// @name         south-plus域名自动替换 (v2.0)
// @namespace    https://github.com/qgdyyg/automatically-replace-southplus-domain
// @version      2.0
// @description  自动替换域名并支持自定义替换规则
// @author       qgdyyg
// @match        *://*/*
// @grant        GM_openInTab
// @grant        GM_registerMenuCommand
// @grant        GM_webRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    // 初始化存储的替换规则
    const DEFAULT_RULES = [
        { source: 'south-plus.net', target: 'bbs.imoutolove.me' },
        { source: 'north-plus.net', target: 'bbs.imoutolove.me' },
        { source: 'white-plus.net', target: 'bbs.imoutolove.me' },
        { source: 'blue-plus.net', target: 'bbs.imoutolove.me' },
        { source: 'snow-plus.net', target: 'bbs.imoutolove.me' },
        { source: 'spring-plus.net', target: 'bbs.imoutolove.me' }
    ];

    // 从存储加载用户规则,如果没有则使用默认规则
    let REPLACEMENT_RULES = GM_getValue('replacementRules', DEFAULT_RULES);

    // 配置区域
    const CONFIG = {
        debug: false,
        autoRefresh: true,
        refreshDelay: 1500
    };

    // 保存规则到存储
    const saveRules = () => {
        GM_setValue('replacementRules', REPLACEMENT_RULES);
        if (CONFIG.debug) {
            console.log('[规则保存] 替换规则已保存', REPLACEMENT_RULES);
        }
    };

    // 重置为默认规则
    const resetToDefault = () => {
        REPLACEMENT_RULES = [...DEFAULT_RULES];
        saveRules();
        alert('已重置为默认替换规则');
    };

    // 添加新规则
    const addRule = () => {
        const source = prompt('请输入要替换的源域名 (例如: example.com)', '');
        if (!source) return;
        
        const target = prompt('请输入替换后的目标域名 (例如: newdomain.com)', 'bbs.imoutolove.me');
        if (!target) return;
        
        // 确保域名格式正确
        const cleanSource = source.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
        const cleanTarget = target.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
        
        if (!cleanSource || !cleanTarget) {
            alert('域名格式不正确,请重新输入');
            return;
        }
        
        // 检查是否已存在相同源域名的规则
        const exists = REPLACEMENT_RULES.some(rule => rule.source === cleanSource);
        if (exists) {
            if (!confirm(`已存在源域名为 "${cleanSource}" 的规则,是否覆盖?`)) {
                return;
            }
            REPLACEMENT_RULES = REPLACEMENT_RULES.filter(rule => rule.source !== cleanSource);
        }
        
        REPLACEMENT_RULES.push({ source: cleanSource, target: cleanTarget });
        saveRules();
        alert(`已添加新规则:\n${cleanSource} → ${cleanTarget}`);
    };

    // 编辑现有规则
    const editRule = () => {
        if (REPLACEMENT_RULES.length === 0) {
            alert('当前没有可用的替换规则');
            return;
        }
        
        const ruleList = REPLACEMENT_RULES.map((rule, i) => 
            `${i + 1}. ${rule.source} → ${rule.target}`
        ).join('\n');
        
        const index = prompt(
            '请输入要编辑的规则编号:\n\n' + ruleList, 
            '1'
        );
        
        if (!index) return;
        
        const ruleIndex = parseInt(index) - 1;
        if (isNaN(ruleIndex) || ruleIndex < 0 || ruleIndex >= REPLACEMENT_RULES.length) {
            alert('无效的规则编号');
            return;
        }
        
        const currentRule = REPLACEMENT_RULES[ruleIndex];
        const newSource = prompt('修改源域名:', currentRule.source);
        if (newSource === null) return; // 用户点击了取消
        
        const newTarget = prompt('修改目标域名:', currentRule.target);
        if (newTarget === null) return; // 用户点击了取消
        
        // 确保域名格式正确
        const cleanSource = newSource.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
        const cleanTarget = newTarget.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
        
        if (!cleanSource || !cleanTarget) {
            alert('域名格式不正确,请重新输入');
            return;
        }
        
        // 检查是否已存在相同源域名的规则(除了当前规则)
        const exists = REPLACEMENT_RULES.some((rule, i) => 
            i !== ruleIndex && rule.source === cleanSource
        );
        
        if (exists) {
            if (!confirm(`已存在源域名为 "${cleanSource}" 的规则,是否覆盖?`)) {
                return;
            }
            REPLACEMENT_RULES = REPLACEMENT_RULES.filter((_, i) => i !== ruleIndex);
            REPLACEMENT_RULES.push({ source: cleanSource, target: cleanTarget });
        } else {
            REPLACEMENT_RULES[ruleIndex] = { 
                source: cleanSource, 
                target: cleanTarget 
            };
        }
        
        saveRules();
        alert(`规则已更新:\n${cleanSource} → ${cleanTarget}`);
    };

    // 删除规则
    const deleteRule = () => {
        if (REPLACEMENT_RULES.length === 0) {
            alert('当前没有可用的替换规则');
            return;
        }
        
        const ruleList = REPLACEMENT_RULES.map((rule, i) => 
            `${i + 1}. ${rule.source} → ${rule.target}`
        ).join('\n');
        
        const index = prompt(
            '请输入要删除的规则编号:\n\n' + ruleList, 
            '1'
        );
        
        if (!index) return;
        
        const ruleIndex = parseInt(index) - 1;
        if (isNaN(ruleIndex) || ruleIndex < 0 || ruleIndex >= REPLACEMENT_RULES.length) {
            alert('无效的规则编号');
            return;
        }
        
        const rule = REPLACEMENT_RULES[ruleIndex];
        if (confirm(`确定要删除规则吗?\n${rule.source} → ${rule.target}`)) {
            REPLACEMENT_RULES = REPLACEMENT_RULES.filter((_, i) => i !== ruleIndex);
            saveRules();
            alert('规则已删除');
        }
    };

    // 显示当前规则
    const showCurrentRules = () => {
        if (REPLACEMENT_RULES.length === 0) {
            alert('当前没有配置替换规则');
            return;
        }
        
        const ruleList = REPLACEMENT_RULES.map(rule => 
            `• ${rule.source} → ${rule.target}`
        ).join('\n');
        
        alert(
            `当前替换规则 (${REPLACEMENT_RULES.length}条):\n\n` +
            ruleList + 
            '\n\n点击 plus.net 域名链接将自动替换为目标域名'
        );
    };

    // 注册右键菜单
    GM_registerMenuCommand('🛠️ 配置自动替换规则', () => {
        const options = [
            '1. 查看当前规则',
            '2. 添加新规则',
            '3. 编辑现有规则',
            '4. 删除规则',
            '5. 重置为默认规则',
            '6. 退出'
        ].join('\n');
        
        const choice = prompt(
            `请选择操作:\n\n${options}`, 
            '1'
        );
        
        switch (choice) {
            case '1':
                showCurrentRules();
                break;
            case '2':
                addRule();
                break;
            case '3':
                editRule();
                break;
            case '4':
                deleteRule();
                break;
            case '5':
                if (confirm('确定要重置为默认规则吗?当前自定义规则将丢失')) {
                    resetToDefault();
                }
                break;
            default:
                // 任何其他选择或取消都视为退出
                break;
        }
    });

    // 1. 点击链接时替换域名
    document.addEventListener('click', function (e) {
        if (e.target.tagName.toLowerCase() === 'a') {
            try {
                const url = new URL(e.target.href);
                const matchedRule = REPLACEMENT_RULES.find(rule => 
                    url.hostname.endsWith(rule.source)
                );

                if (matchedRule) {
                    const oldUrl = e.target.href;
                    url.hostname = matchedRule.target;
                    const newUrl = url.toString();
                    
                    if (CONFIG.debug) {
                        console.log(`[自动替换] ${oldUrl} → ${newUrl}`);
                    }
                    
                    e.preventDefault();
                    GM_openInTab(newUrl, {
                        active: true,
                        insert: true,
                        setParent: true
                    });
                }
            } catch (error) {
                console.warn('[链接解析失败]', e.target.href, error);
            }
        }
    });

    // 2. 使用 webRequest API 重定向请求
    const rules = REPLACEMENT_RULES.map(rule => ({
        selector: `*://*.${rule.source}/*`,
        action: {
            type: "redirect",
            redirectUrl: (details) => {
                const url = new URL(details.url);
                url.hostname = rule.target;
                const newUrl = url.toString();
                
                if (CONFIG.debug) {
                    console.log(`[请求重定向] ${details.url} → ${newUrl}`);
                }
                
                return newUrl;
            }
        }
    }));

    GM_webRequest(rules);

    // 3. 自动刷新失败页面功能
    if (CONFIG.autoRefresh) {
        const checkPageStatus = () => {
            const isEmpty = !document.body || 
                           (document.body.textContent || '').trim() === '' ||
                           document.body.innerHTML === '';
            
            const hasError = document.body && (
                document.body.textContent.includes('404') ||
                document.body.textContent.includes('Not Found') ||
                document.body.textContent.includes('Connection refused') ||
                document.body.textContent.includes('Unable to connect') ||
                document.body.textContent.includes('This site can’t be reached')
            );
            
            const currentDomain = window.location.hostname;
            const isTargetDomain = REPLACEMENT_RULES.some(rule => 
                currentDomain.endsWith(rule.source)
            );
            
            if (isTargetDomain && (isEmpty || hasError)) {
                if (CONFIG.debug) {
                    console.log('[自动刷新] 检测到加载失败页面,尝试刷新...');
                }
                
                try {
                    const url = new URL(window.location.href);
                    const matchedRule = REPLACEMENT_RULES.find(rule => 
                        currentDomain.endsWith(rule.source)
                    );
                    
                    if (matchedRule) {
                        url.hostname = matchedRule.target;
                        const newUrl = url.toString();
                        
                        if (CONFIG.debug) {
                            console.log(`[自动刷新] 尝试重定向: ${window.location.href} → ${newUrl}`);
                        }
                        
                        setTimeout(() => {
                            window.location.replace(newUrl);
                        }, CONFIG.refreshDelay);
                    }
                } catch (error) {
                    console.error('[自动刷新] 重定向失败:', error);
                }
            }
        };
        
        window.addEventListener('load', checkPageStatus);
        setInterval(checkPageStatus, 3000);
    }
    
    // 4. 添加视觉指示器(脚本运行状态提示)
    GM_addStyle(`
        .vm-plusnet-indicator {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: #4CAF50;
            color: white;
            padding: 8px 12px;
            border-radius: 4px;
            font-size: 12px;
            z-index: 9999;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            cursor: pointer;
            animation: vm-pulse 2s infinite;
            user-select: none;
        }
        @keyframes vm-pulse {
            0% { opacity: 0.7; }
            50% { opacity: 1; }
            100% { opacity: 0.7; }
        }
    `);
    
    const indicator = document.createElement('div');
    indicator.className = 'vm-plusnet-indicator';
    indicator.textContent = `自动替换已启用 (${REPLACEMENT_RULES.length}条规则) ✅`;
    indicator.title = '点击显示当前规则';
    indicator.addEventListener('click', showCurrentRules);
    document.body.appendChild(indicator);
})();