// ==UserScript== // @name L站佬友专用DNS分流器 // @namespace http://tampermonkey.net/ // @license Duy // @version 1.041 // @description 简单稳定的DNS分流器 // @author You // @run-at document-start // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @connect cloudflare-dns.com // @connect doh.pub // @connect neo.doh.oaifree.com // @match https://linux.do/* // @match http://linux.do/* // @match https://*.linux.do/* // @match http://*.linux.do/* // @match https://github.com/* // @downloadURL https://update.greasyfork.icu/scripts/555181/L%E7%AB%99%E4%BD%AC%E5%8F%8B%E4%B8%93%E7%94%A8DNS%E5%88%86%E6%B5%81%E5%99%A8.user.js // @updateURL https://update.greasyfork.icu/scripts/555181/L%E7%AB%99%E4%BD%AC%E5%8F%8B%E4%B8%93%E7%94%A8DNS%E5%88%86%E6%B5%81%E5%99%A8.meta.js // ==/UserScript== (function() { 'use strict'; const CONFIG_KEY = 'dnsRouterConfig'; // 默认配置 const defaultConfig = { rules: { 'linux.do': 'https://cloudflare-dns.com/dns-query', 'github.com': 'https://cloudflare-dns.com/dns-query' } }; // 获取配置 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 shouldActivate() { const config = getConfig(); const domain = window.location.hostname; for (const ruleDomain in config.rules) { if (domain === ruleDomain || domain.endsWith('.' + ruleDomain)) { return true; } } return false; } // 增强的DNS解析 - 支持多种响应格式 function resolveWithDoH(domain, dohUrl) { return new Promise((resolve, reject) => { const url = `${dohUrl}?name=${encodeURIComponent(domain)}&type=A`; // 尝试不同的Accept头部 const tryFormats = [ { headers: { 'Accept': 'application/dns-json' } }, { headers: { 'Accept': 'application/json' } }, { headers: { 'Accept': '*/*' } } ]; let currentTry = 0; function attemptRequest() { const options = tryFormats[currentTry]; GM_xmlhttpRequest({ method: 'GET', url: url, headers: options.headers, timeout: 8000, onload: function(response) { if (response.status !== 200) { if (currentTry < tryFormats.length - 1) { currentTry++; attemptRequest(); } else { reject(new Error(`HTTP ${response.status}`)); } return; } try { // 尝试解析响应 const data = JSON.parse(response.responseText); const ip = extractIPFromResponse(data); if (ip) { resolve(ip); } else { reject(new Error('No IP found in response')); } } catch (e) { // 如果不是JSON,尝试其他格式 if (currentTry < tryFormats.length - 1) { currentTry++; attemptRequest(); } else { reject(new Error('Response format not supported')); } } }, onerror: function(error) { if (currentTry < tryFormats.length - 1) { currentTry++; attemptRequest(); } else { reject(new Error('Network error: ' + (error.statusText || 'Unknown'))); } }, ontimeout: function() { reject(new Error('Request timeout')); } }); } attemptRequest(); }); } // 从响应数据中提取IP地址 function extractIPFromResponse(data) { // 方法1: 标准Answer数组 if (data.Answer && Array.isArray(data.Answer)) { for (const answer of data.Answer) { if (answer.data && isValidIP(answer.data)) { return answer.data; } } } // 方法2: answers字段(某些服务商使用) if (data.answers && Array.isArray(data.answers)) { for (const answer of data.answers) { if (answer.data && isValidIP(answer.data)) { return answer.data; } } } // 方法3: 直接data字段 if (data.data && isValidIP(data.data)) { return data.data; } // 方法4: 在响应文本中搜索IP地址 const responseText = JSON.stringify(data); const ipMatch = responseText.match(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/); if (ipMatch && isValidIP(ipMatch[0])) { return ipMatch[0]; } return null; } // IP地址验证 function isValidIP(ip) { return /^(\d{1,3}\.){3}\d{1,3}$/.test(ip) && ip.split('.').every(part => { const num = parseInt(part, 10); return num >= 0 && num <= 255; }); } // 创建UI function createUI() { const config = getConfig(); const domain = window.location.hostname; // 清理现有UI const existing = document.getElementById('dns-router-panel'); if (existing) existing.remove(); 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; `; let html = `