L站佬友专用DNS分流器

高级DNS路由,为特定网站使用指定DNS解析

当前为 2025-11-08 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         L站佬友专用DNS分流器
// @namespace    http://tampermonkey.net/
// @license       Duy
// @version      1.01
// @description  高级DNS路由,为特定网站使用指定DNS解析
// @author       You
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @connect      chrome.cloudflare-dns.com
// @connect      cloudflare-dns.com
// @connect      linux.do
// @match        https://linux.do/*
// @match        http://linux.do/*
// @match        https://*.linux.do/*
// @match        http://*.linux.do/*
// @match        https://github.com/*
// ==/UserScript==

(function() {
    'use strict';

    // 配置存储
    const CONFIG_KEY = 'dnsRouterConfig';
    const MATCH_RULES_KEY = 'dnsMatchRules';

    // 默认配置 - 专门为 linux.do 添加规则
    const defaultConfig = {
        rules: {
            'linux.do': 'https://chrome.cloudflare-dns.com/dns-query',
            'github.com': 'https://chrome.cloudflare-dns.com/dns-query',
            'twitter.com': 'https://chrome.cloudflare-dns.com/dns-query'
        },
        showUI: false
    };

    // 获取配置
    function getConfig() {
        const saved = GM_getValue(CONFIG_KEY, JSON.stringify(defaultConfig));
        return JSON.parse(saved);
    }

    // 保存配置
    function saveConfig(config) {
        GM_setValue(CONFIG_KEY, JSON.stringify(config));
    }

    // 获取匹配规则
    function getMatchRules() {
        const saved = GM_getValue(MATCH_RULES_KEY, JSON.stringify([]));
        return JSON.parse(saved);
    }

    // 保存匹配规则
    function saveMatchRules(rules) {
        GM_setValue(MATCH_RULES_KEY, JSON.stringify(rules));
    }

    // 为域名生成匹配模式
    function generateMatchPatterns(domain) {
        return [
            `https://${domain}/*`,
            `http://${domain}/*`,
            `https://*.${domain}/*`,
            `http://*.${domain}/*`
        ];
    }

    // 添加新的匹配规则
    function addMatchRule(domain) {
        const patterns = generateMatchPatterns(domain);
        const existingRules = getMatchRules();

        // 避免重复添加
        for (const pattern of patterns) {
            if (!existingRules.includes(pattern)) {
                existingRules.push(pattern);
            }
        }

        saveMatchRules(existingRules);
        console.log(`[DNS Router] 为域名 ${domain} 添加匹配规则`);

        // 提示用户需要重新安装脚本
        showNotification(`已为 ${domain} 添加规则,请编辑脚本添加地址到// @match *以生效`, 'info', 5000);
    }

    // 删除匹配规则
    function removeMatchRule(domain) {
        const patterns = generateMatchPatterns(domain);
        let existingRules = getMatchRules();

        existingRules = existingRules.filter(pattern => !patterns.includes(pattern));
        saveMatchRules(existingRules);
        console.log(`[DNS Router] 为域名 ${domain} 移除匹配规则`);
    }

    let menuCommandId = null;

    // DNS解析函数 - 专门处理 DoH 解析
    async function resolveWithDoH(domain, dohEndpoint = 'https://chrome.cloudflare-dns.com/dns-query') {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `${dohEndpoint}?name=${encodeURIComponent(domain)}&type=A`,
                headers: {
                    'Accept': 'application/dns-json'
                },
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (data.Answer && data.Answer.length > 0) {
                            const ip = data.Answer[0].data;
                            console.log(`[DNS Router] ${domain} -> ${ip} via ${dohEndpoint}`);
                            resolve(ip);
                        } else {
                            reject(new Error(`No DNS answer for ${domain}`));
                        }
                    } catch (e) {
                        reject(e);
                    }
                },
                onerror: function(error) {
                    reject(error);
                }
            });
        });
    }

    // 为 linux.do 执行实际的 DNS 重写
    function setupDNSOverride() {
        const config = getConfig();
        const currentDomain = window.location.hostname;

        console.log(`[DNS Router] 为 ${currentDomain} 设置 DNS 重写`);

        // 预解析当前域名
        for (const [domain, dns] of Object.entries(config.rules)) {
            if (currentDomain === domain || currentDomain.endsWith('.' + domain)) {
                resolveWithDoH(currentDomain, dns).then(ip => {
                    console.log(`[DNS Router] 预解析成功: ${currentDomain} -> ${ip}`);
                }).catch(error => {
                    console.warn(`[DNS Router] 预解析失败: ${currentDomain}`, error);
                });
                break;
            }
        }

        // 拦截资源请求
        interceptResourceRequests();
    }

    // 拦截资源请求
    function interceptResourceRequests() {
        // 拦截 XMLHttpRequest
        const originalXHROpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(method, url, ...args) {
            const processedUrl = processURL(url);
            return originalXHROpen.call(this, method, processedUrl, ...args);
        };

        // 拦截 fetch 请求
        const originalFetch = window.fetch;
        window.fetch = function(input, init) {
            if (typeof input === 'string') {
                input = processURL(input);
            }
            return originalFetch.call(this, input, init);
        };

        console.log('[DNS Router] 资源请求拦截已启用');
    }

    // 处理 URL
    function processURL(url) {
        try {
            const urlObj = new URL(url, window.location.href);
            const config = getConfig();

            // 检查 URL 的域名是否在规则中
            for (const ruleDomain in config.rules) {
                if (urlObj.hostname === ruleDomain || urlObj.hostname.endsWith('.' + ruleDomain)) {
                    console.log(`[DNS Router] 处理请求: ${urlObj.hostname}`);
                    break;
                }
            }
        } catch (e) {
            // URL 解析失败
        }
        return url;
    }

    // 创建UI界面
    function createUI() {
        const config = getConfig();
        const currentDomain = window.location.hostname;
        const isLinuxDo = currentDomain === 'linux.do' || currentDomain.endsWith('.linux.do');

        const panel = document.createElement('div');
        panel.id = 'dns-router-panel';
        panel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #2c3e50;
            color: white;
            padding: 20px;
            border-radius: 10px;
            z-index: 10000;
            font-family: Arial, sans-serif;
            font-size: 14px;
            min-width: 400px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.3);
            border: 2px solid #3498db;
            max-height: 80vh;
            overflow-y: auto;
        `;

        let html = `
            <div style="margin-bottom: 15px; font-weight: bold; border-bottom: 1px solid #34495e; padding-bottom: 10px; display: flex; justify-content: space-between; align-items: center;">
                <span style="display: flex; align-items: center;">
                    <span style="color: #3498db; margin-right: 8px;">🌐</span>
                    DNS 路由器
                </span>
                <button id="close-panel" style="background: none; border: none; color: #e74c3c; font-size: 16px; cursor: pointer; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;">×</button>
            </div>

            <!-- Linux.Do 特别提示 -->
            ${isLinuxDo ? `
            <div style="margin-bottom: 15px; padding: 12px; background: #1a5276; border-radius: 5px; border-left: 4px solid #3498db;">
                <div style="font-size: 13px; font-weight: bold; color: #3498db; margin-bottom: 5px;">🚀 Linux.Do 专用解析</div>
                <div style="font-size: 11px; color: #bbdefb;">
                    当前使用: <strong>https://chrome.cloudflare-dns.com/dns-query</strong><br>
                    Cloudflare DNS-over-HTTPS 加密解析
                </div>
            </div>
            ` : ''}

            <!-- 当前状态 -->
            <div style="margin-bottom: 20px; padding: 12px; background: #34495e; border-radius: 5px;">
                <div style="font-size: 12px; color: #bdc3c7; margin-bottom: 5px;">当前域名</div>
                <div style="font-size: 13px; font-weight: bold; color: #ecf0f1;">${currentDomain}</div>
                <div style="font-size: 11px; color: #95a5a6; margin-top: 3px;">
                    DNS: ${isLinuxDo ? 'Cloudflare DoH' : '根据规则配置'}
                </div>
            </div>

            <!-- 添加新规则 -->
            <div style="margin-bottom: 20px; padding: 15px; background: #34495e; border-radius: 5px;">
                <div style="font-size: 13px; font-weight: bold; margin-bottom: 10px; color: #3498db;">添加新规则</div>
                <div style="display: grid; grid-template-columns: 1fr 1.5fr auto; gap: 8px; align-items: end;">
                    <div>
                        <div style="font-size: 11px; color: #bdc3c7; margin-bottom: 4px;">域名</div>
                        <input type="text" id="new-domain" placeholder="example.com" style="width: 100%; padding: 6px; border: 1px solid #555; background: #2c3e50; color: white; border-radius: 3px; font-size: 12px;">
                    </div>
                    <div>
                        <div style="font-size: 11px; color: #bdc3c7; margin-bottom: 4px;">DNS端点</div>
                        <input type="text" id="new-dns" placeholder="https://chrome.cloudflare-dns.com/dns-query" value="https://chrome.cloudflare-dns.com/dns-query" style="width: 100%; padding: 6px; border: 1px solid #555; background: #2c3e50; color: white; border-radius: 3px; font-size: 12px;">
                    </div>
                    <button id="add-rule" style="background: #27ae60; color: white; border: none; padding: 6px 12px; border-radius: 3px; cursor: pointer; font-size: 12px; white-space: nowrap;">添加</button>
                </div>
                <div style="font-size: 10px; color: #7f8c8d; margin-top: 8px;">
                    💡 添加后需要重新安装脚本才能在新域名上生效
                </div>
            </div>

            <!-- 规则列表 -->
            <div style="margin-bottom: 20px;">
                <div style="font-size: 13px; font-weight: bold; margin-bottom: 10px; color: #3498db;">规则列表</div>
                <div id="rules-list" style="max-height: 200px; overflow-y: auto;">
        `;

        // 显示规则列表
        for (const [domain, dns] of Object.entries(config.rules)) {
            const isCurrent = currentDomain === domain || currentDomain.endsWith('.' + domain);
            const isDoH = dns.includes('cloudflare-dns');
            const isLinuxDoRule = domain === 'linux.do';

            html += `
                <div class="rule-item" data-domain="${domain}" style="display: flex; justify-content: space-between; align-items: center; padding: 8px; margin-bottom: 5px; background: ${isCurrent ? (isLinuxDoRule ? '#1a237e' : '#1a5276') : '#2c3e50'}; border-radius: 5px; border-left: 4px solid ${isLinuxDoRule ? '#3498db' : (isDoH ? '#27ae60' : '#e74c3c')};">
                    <div style="flex: 1;">
                        <div style="font-size: 12px; font-weight: bold; color: ${isCurrent ? '#3498db' : '#ecf0f1'};">${domain}</div>
                        <div style="font-size: 10px; color: #bdc3c7; word-break: break-all;">${dns}</div>
                    </div>
                    <div style="display: flex; align-items: center; gap: 5px;">
                        ${isLinuxDoRule ? '<span style="font-size: 8px; background: #3498db; color: white; padding: 2px 5px; border-radius: 3px;">默认</span>' : ''}
                        <button class="delete-rule" data-domain="${domain}" style="background: #e74c3c; color: white; border: none; padding: 4px 8px; border-radius: 3px; cursor: pointer; font-size: 10px;">删除</button>
                    </div>
                </div>
            `;
        }

        html += `
                </div>
            </div>

            <!-- 操作按钮 -->
            <div style="display: flex; gap: 10px; justify-content: flex-end; border-top: 1px solid #34495e; padding-top: 15px;">
                <button id="test-dns" style="background: #e67e22; color: white; border: none; padding: 8px 15px; border-radius: 3px; cursor: pointer; font-size: 12px; display: flex; align-items: center;">
                    <span style="margin-right: 5px;">🔍</span>测试DNS
                </button>
                <button id="save-config" style="background: #27ae60; color: white; border: none; padding: 8px 15px; border-radius: 3px; cursor: pointer; font-size: 12px; display: flex; align-items: center;">
                    <span style="margin-right: 5px;">💾</span>保存配置
                </button>
            </div>
        `;

        panel.innerHTML = html;
        document.body.appendChild(panel);

        // 添加遮罩层
        const overlay = document.createElement('div');
        overlay.id = 'dns-router-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.7);
            z-index: 9999;
        `;
        document.body.appendChild(overlay);

        // 事件监听
        setupEventListeners(panel);
    }

    // 设置事件监听
    function setupEventListeners(panel) {
        // 关闭面板
        document.getElementById('close-panel').addEventListener('click', hideUI);
        document.getElementById('dns-router-overlay').addEventListener('click', hideUI);

        // 添加规则
        document.getElementById('add-rule').addEventListener('click', addNewRule);

        // 删除规则
        document.querySelectorAll('.delete-rule').forEach(btn => {
            btn.addEventListener('click', function() {
                const domain = this.getAttribute('data-domain');
                deleteRule(domain);
            });
        });

        // 测试DNS
        document.getElementById('test-dns').addEventListener('click', testCurrentDNS);

        // 保存配置
        document.getElementById('save-config').addEventListener('click', function() {
            showNotification('配置已保存', 'success');
            setTimeout(hideUI, 1000);
        });

        // 回车键添加规则
        document.getElementById('new-domain').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                addNewRule();
            }
        });
    }

    // 添加新规则
    function addNewRule() {
        const domainInput = document.getElementById('new-domain');
        const dnsInput = document.getElementById('new-dns');

        const domain = domainInput.value.trim();
        const dns = dnsInput.value.trim() || 'https://chrome.cloudflare-dns.com/dns-query';

        if (!domain) {
            showNotification('请输入域名', 'error');
            return;
        }

        const config = getConfig();
        config.rules[domain] = dns;
        saveConfig(config);

        // 添加匹配规则
        addMatchRule(domain);

        showNotification(`已添加规则: ${domain},请编辑脚本添加地址到// @match *以生效`, 'info', 5000);

        // 清空输入框
        domainInput.value = '';
        dnsInput.value = 'https://chrome.cloudflare-dns.com/dns-query';

        // 重新加载UI
        setTimeout(() => {
            hideUI();
            showUI();
        }, 1000);
    }

    // 删除规则
    function deleteRule(domain) {
        // 防止删除 linux.do 的专用规则
        if (domain === 'linux.do') {
            showNotification('不能删除 linux.do 默认规则', 'error');
            return;
        }

        const config = getConfig();
        delete config.rules[domain];
        saveConfig(config);

        // 移除匹配规则
        removeMatchRule(domain);

        showNotification(`已删除规则: ${domain}`, 'success');

        // 重新加载UI
        setTimeout(() => {
            hideUI();
            showUI();
        }, 1000);
    }

    // 测试当前域名DNS
    function testCurrentDNS() {
        const currentDomain = window.location.hostname;
        const config = getConfig();
        let dnsEndpoint = 'https://chrome.cloudflare-dns.com/dns-query';

        // 查找匹配的DNS规则
        for (const [domain, dns] of Object.entries(config.rules)) {
            if (currentDomain === domain || currentDomain.endsWith('.' + domain)) {
                dnsEndpoint = dns;
                break;
            }
        }

        const testBtn = document.getElementById('test-dns');
        const originalText = testBtn.innerHTML;

        testBtn.innerHTML = '<span style="margin-right: 5px;">⏳</span>测试中...';
        testBtn.disabled = true;

        resolveWithDoH(currentDomain, dnsEndpoint).then(ip => {
            showNotification(`✅ DNS解析成功<br>域名: ${currentDomain}<br>IP: ${ip}<br>DNS: ${dnsEndpoint}`, 'success');
            testBtn.innerHTML = originalText;
        }).catch(error => {
            showNotification(`❌ DNS解析失败<br>域名: ${currentDomain}<br>错误: ${error.message}`, 'error');
            testBtn.innerHTML = originalText;
        }).finally(() => {
            testBtn.disabled = false;
        });
    }

    // 显示UI
    function showUI() {
        if (document.getElementById('dns-router-panel')) {
            hideUI();
        }
        createUI();
    }

    // 隐藏UI
    function hideUI() {
        const panel = document.getElementById('dns-router-panel');
        const overlay = document.getElementById('dns-router-overlay');

        if (panel) panel.remove();
        if (overlay) overlay.remove();
    }

    // 显示通知
    function showNotification(message, type = 'info', duration = 4000) {
        // 移除现有通知
        const existing = document.getElementById('dns-router-notification');
        if (existing) existing.remove();

        const notification = document.createElement('div');
        notification.id = 'dns-router-notification';
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: ${type === 'success' ? '#27ae60' : type === 'error' ? '#e74c3c' : '#3498db'};
            color: white;
            padding: 12px 15px;
            border-radius: 5px;
            z-index: 10002;
            font-family: Arial, sans-serif;
            font-size: 12px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            max-width: 300px;
        `;
        notification.innerHTML = message;
        document.body.appendChild(notification);

        setTimeout(() => {
            notification.remove();
        }, duration);
    }

    // 注册菜单命令
    function registerMenuCommand() {
        if (menuCommandId) {
            GM_unregisterMenuCommand(menuCommandId);
        }

        menuCommandId = GM_registerMenuCommand('🚀 显示DNS路由器', function() {
            showUI();
        });
    }

    // 初始化
    function init() {
        registerMenuCommand();
        setupDNSOverride();
        console.log('Advanced DNS Router 已加载 - Linux.Do DoH 解析已启用');

        // 如果配置中设置为显示UI,则自动显示
        const config = getConfig();
        if (config.showUI) {
            setTimeout(showUI, 1000);
        }
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();