L站佬友专用DNS分流器

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

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

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

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

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

您需要先安装一个扩展,例如 篡改猴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();
    }
})();