├── README.md └── _worker.js /README.md: -------------------------------------------------------------------------------- 1 | # 服务器优选工具 - 简化版 2 | 3 | 4 | ## 功能特性 5 | 6 | - **优选域名**:自动使用内置的优选域名列表 7 | - **优选IP**:15分钟优选一次 8 | - **GitHub优选**:从 GitHub 仓库获取优选IP列表 9 | - **节点生成**:支持生成 Clash、Surge、Quantumult 等格式的订阅 10 | - **客户端选择**:支持 Clash、Surge、Quantumult X 等多种客户端格式 11 | - **IPv4/IPv6 选择**:可选择使用 IPv4 或 IPv6 优选IP 12 | - **运营商筛选**:支持按移动、联通、电信筛选优选IP 13 | 14 | ## 使用方法 15 | 16 | ### 1. 部署到 Cloudflare Workers 17 | 18 | 1. 登录 Cloudflare Dashboard 19 | 2. 进入 Workers & Pages 20 | 3. 创建新的 Worker 21 | 4. 将 `worker.js` 的内容复制到编辑器 22 | 5. 保存并部署 23 | 24 | ### 2. 使用界面 25 | 26 | 27 | 1. **输入域名**:输入您的 Cloudflare Workers 域名 28 | 2. **输入UUID**:输入您的 UUID(格式:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) 29 | 3. **配置选项**: 30 | - 启用优选域名:使用内置的优选域名列表 31 | - 启用优选IP:从 wetest.vip 获取动态IP 32 | - 启用GitHub优选:从 GitHub 获取优选IP 33 | - 客户端选择:选择订阅格式(Base64/Clash/Surge/Quantumult X) 34 | - IP版本选择:选择使用 IPv4 或 IPv6 35 | - 运营商选择:选择移动、联通、电信运营商 36 | 4. **生成订阅**:点击"生成订阅链接"按钮 37 | 38 | ### 3. 订阅链接格式 39 | 40 | 生成的订阅链接格式为: 41 | ``` 42 | https://your-worker.workers.dev/{UUID}/sub?domain=your-domain.com&epd=yes&epi=yes&egi=yes 43 | ``` 44 | 45 | ### 4. 支持的订阅格式 46 | 47 | 在订阅链接后添加 `&target=` 参数可以指定格式: 48 | 49 | - `&target=base64` - Base64 编码(默认) 50 | - `&target=clash` - Clash 配置 51 | - `&target=surge` - Surge 配置 52 | - `&target=quantumult` - Quantumult 配置 53 | 54 | ## 配置说明 55 | 56 | ### 环境变量(可选) 57 | 58 | 无需配置环境变量,所有功能通过URL参数控制。 59 | 60 | ### URL 参数 61 | 62 | - `domain`: 您的域名(必需) 63 | - `epd`: 启用优选域名(yes/no,默认:yes) 64 | - `epi`: 启用优选IP(yes/no,默认:yes) 65 | - `egi`: 启用GitHub优选(yes/no,默认:yes) 66 | - `piu`: 自定义优选IP来源URL(可选) 67 | - `target`: 订阅格式(base64/clash/surge/quantumult) 68 | - `ipv4`: 启用IPv4(yes/no,默认:yes) 69 | - `ipv6`: 启用IPv6(yes/no,默认:yes) 70 | - `ispMobile`: 启用移动运营商(yes/no,默认:yes) 71 | - `ispUnicom`: 启用联通运营商(yes/no,默认:yes) 72 | - `ispTelecom`: 启用电信运营商(yes/no,默认:yes) 73 | 74 | ## 注意事项 75 | 76 | 1. **这不是代理工具**:此工具仅用于生成订阅链接,不提供代理功能 77 | 2. **需要配合其他服务**:生成的节点需要配合其他代理服务使用 78 | 3. **域名要求**:输入的域名应该是您实际使用的 服务器 域名 79 | 4. **UUID格式**:UUID 必须是标准的 UUID v4 格式 80 | -------------------------------------------------------------------------------- /_worker.js: -------------------------------------------------------------------------------- 1 | // Cloudflare Worker - 简化版优选工具 2 | // 仅保留优选域名、优选IP、GitHub、上报和节点生成功能 3 | 4 | // 默认配置 5 | let customPreferredIPs = []; 6 | let customPreferredDomains = []; 7 | let epd = true; // 启用优选域名 8 | let epi = true; // 启用优选IP 9 | let egi = true; // 启用GitHub优选 10 | let ev = true; // 启用VLESS协议 11 | let et = false; // 启用Trojan协议 12 | let vm = false; // 启用VMess协议 13 | let scu = 'https://url.v1.mk/sub'; // 订阅转换地址 14 | 15 | // 默认优选域名列表 16 | const directDomains = [ 17 | { name: "cloudflare.182682.xyz", domain: "cloudflare.182682.xyz" }, 18 | { domain: "freeyx.cloudflare88.eu.org" }, 19 | { domain: "bestcf.top" }, 20 | { domain: "cdn.2020111.xyz" }, 21 | { domain: "cf.0sm.com" }, 22 | { domain: "cf.090227.xyz" }, 23 | { domain: "cf.zhetengsha.eu.org" }, 24 | { domain: "cfip.1323123.xyz" }, 25 | { domain: "cloudflare-ip.mofashi.ltd" }, 26 | { domain: "cf.877771.xyz" }, 27 | { domain: "xn--b6gac.eu.org" } 28 | ]; 29 | 30 | // 默认优选IP来源URL 31 | const defaultIPURL = 'https://raw.githubusercontent.com/qwer-search/bestip/refs/heads/main/kejilandbestip.txt'; 32 | 33 | // UUID验证 34 | function isValidUUID(str) { 35 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; 36 | return uuidRegex.test(str); 37 | } 38 | 39 | // 从环境变量获取配置 40 | function getConfigValue(key, defaultValue) { 41 | return defaultValue || ''; 42 | } 43 | 44 | // 获取动态IP列表(支持IPv4/IPv6和运营商筛选) 45 | async function fetchDynamicIPs(ipv4Enabled = true, ipv6Enabled = true, ispMobile = true, ispUnicom = true, ispTelecom = true) { 46 | const v4Url = "https://www.wetest.vip/page/cloudflare/address_v4.html"; 47 | const v6Url = "https://www.wetest.vip/page/cloudflare/address_v6.html"; 48 | let results = []; 49 | 50 | try { 51 | const fetchPromises = []; 52 | if (ipv4Enabled) { 53 | fetchPromises.push(fetchAndParseWetest(v4Url)); 54 | } else { 55 | fetchPromises.push(Promise.resolve([])); 56 | } 57 | if (ipv6Enabled) { 58 | fetchPromises.push(fetchAndParseWetest(v6Url)); 59 | } else { 60 | fetchPromises.push(Promise.resolve([])); 61 | } 62 | 63 | const [ipv4List, ipv6List] = await Promise.all(fetchPromises); 64 | results = [...ipv4List, ...ipv6List]; 65 | 66 | // 按运营商筛选 67 | if (results.length > 0) { 68 | results = results.filter(item => { 69 | const isp = item.isp || ''; 70 | if (isp.includes('移动') && !ispMobile) return false; 71 | if (isp.includes('联通') && !ispUnicom) return false; 72 | if (isp.includes('电信') && !ispTelecom) return false; 73 | return true; 74 | }); 75 | } 76 | 77 | return results.length > 0 ? results : []; 78 | } catch (e) { 79 | return []; 80 | } 81 | } 82 | 83 | // 解析wetest页面 84 | async function fetchAndParseWetest(url) { 85 | try { 86 | const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }); 87 | if (!response.ok) return []; 88 | const html = await response.text(); 89 | const results = []; 90 | const rowRegex = //g; 91 | const cellRegex = /(.+?)<\/td>[\s\S]*?([\d.:a-fA-F]+)<\/td>[\s\S]*?(.+?)<\/td>/; 92 | 93 | let match; 94 | while ((match = rowRegex.exec(html)) !== null) { 95 | const rowHtml = match[0]; 96 | const cellMatch = rowHtml.match(cellRegex); 97 | if (cellMatch && cellMatch[1] && cellMatch[2]) { 98 | const colo = cellMatch[3] ? cellMatch[3].trim().replace(/<.*?>/g, '') : ''; 99 | results.push({ 100 | isp: cellMatch[1].trim().replace(/<.*?>/g, ''), 101 | ip: cellMatch[2].trim(), 102 | colo: colo 103 | }); 104 | } 105 | } 106 | return results; 107 | } catch (error) { 108 | return []; 109 | } 110 | } 111 | 112 | // 整理成数组 113 | async function 整理成数组(内容) { 114 | var 替换后的内容 = 内容.replace(/[ "'\r\n]+/g, ',').replace(/,+/g, ','); 115 | if (替换后的内容.charAt(0) == ',') 替换后的内容 = 替换后的内容.slice(1); 116 | if (替换后的内容.charAt(替换后的内容.length - 1) == ',') 替换后的内容 = 替换后的内容.slice(0, 替换后的内容.length - 1); 117 | const 地址数组 = 替换后的内容.split(','); 118 | return 地址数组; 119 | } 120 | 121 | // 请求优选API 122 | async function 请求优选API(urls, 默认端口 = '443', 超时时间 = 3000) { 123 | if (!urls?.length) return []; 124 | const results = new Set(); 125 | await Promise.allSettled(urls.map(async (url) => { 126 | try { 127 | const controller = new AbortController(); 128 | const timeoutId = setTimeout(() => controller.abort(), 超时时间); 129 | const response = await fetch(url, { signal: controller.signal }); 130 | clearTimeout(timeoutId); 131 | let text = ''; 132 | try { 133 | const buffer = await response.arrayBuffer(); 134 | const contentType = (response.headers.get('content-type') || '').toLowerCase(); 135 | const charset = contentType.match(/charset=([^\s;]+)/i)?.[1]?.toLowerCase() || ''; 136 | 137 | // 根据 Content-Type 响应头判断编码优先级 138 | let decoders = ['utf-8', 'gb2312']; // 默认优先 UTF-8 139 | if (charset.includes('gb') || charset.includes('gbk') || charset.includes('gb2312')) { 140 | decoders = ['gb2312', 'utf-8']; // 如果明确指定 GB 系编码,优先尝试 GB2312 141 | } 142 | 143 | // 尝试多种编码解码 144 | let decodeSuccess = false; 145 | for (const decoder of decoders) { 146 | try { 147 | const decoded = new TextDecoder(decoder).decode(buffer); 148 | // 验证解码结果的有效性 149 | if (decoded && decoded.length > 0 && !decoded.includes('\ufffd')) { 150 | text = decoded; 151 | decodeSuccess = true; 152 | break; 153 | } else if (decoded && decoded.length > 0) { 154 | // 如果有替换字符 (U+FFFD),说明编码不匹配,继续尝试下一个编码 155 | continue; 156 | } 157 | } catch (e) { 158 | // 该编码解码失败,尝试下一个 159 | continue; 160 | } 161 | } 162 | 163 | // 如果所有编码都失败或无效,尝试 response.text() 164 | if (!decodeSuccess) { 165 | text = await response.text(); 166 | } 167 | 168 | // 如果返回的是空或无效数据,返回 169 | if (!text || text.trim().length === 0) { 170 | return; 171 | } 172 | } catch (e) { 173 | console.error('Failed to decode response:', e); 174 | return; 175 | } 176 | const lines = text.trim().split('\n').map(l => l.trim()).filter(l => l); 177 | const isCSV = lines.length > 1 && lines[0].includes(','); 178 | const IPV6_PATTERN = /^[^\[\]]*:[^\[\]]*:[^\[\]]/; 179 | if (!isCSV) { 180 | lines.forEach(line => { 181 | const hashIndex = line.indexOf('#'); 182 | const [hostPart, remark] = hashIndex > -1 ? [line.substring(0, hashIndex), line.substring(hashIndex)] : [line, '']; 183 | let hasPort = false; 184 | if (hostPart.startsWith('[')) { 185 | hasPort = /\]:(\d+)$/.test(hostPart); 186 | } else { 187 | const colonIndex = hostPart.lastIndexOf(':'); 188 | hasPort = colonIndex > -1 && /^\d+$/.test(hostPart.substring(colonIndex + 1)); 189 | } 190 | const port = new URL(url).searchParams.get('port') || 默认端口; 191 | results.add(hasPort ? line : `${hostPart}:${port}${remark}`); 192 | }); 193 | } else { 194 | const headers = lines[0].split(',').map(h => h.trim()); 195 | const dataLines = lines.slice(1); 196 | if (headers.includes('IP地址') && headers.includes('端口') && headers.includes('数据中心')) { 197 | const ipIdx = headers.indexOf('IP地址'), portIdx = headers.indexOf('端口'); 198 | const remarkIdx = headers.indexOf('国家') > -1 ? headers.indexOf('国家') : 199 | headers.indexOf('城市') > -1 ? headers.indexOf('城市') : headers.indexOf('数据中心'); 200 | const tlsIdx = headers.indexOf('TLS'); 201 | dataLines.forEach(line => { 202 | const cols = line.split(',').map(c => c.trim()); 203 | if (tlsIdx !== -1 && cols[tlsIdx]?.toLowerCase() !== 'true') return; 204 | const wrappedIP = IPV6_PATTERN.test(cols[ipIdx]) ? `[${cols[ipIdx]}]` : cols[ipIdx]; 205 | results.add(`${wrappedIP}:${cols[portIdx]}#${cols[remarkIdx]}`); 206 | }); 207 | } else if (headers.some(h => h.includes('IP')) && headers.some(h => h.includes('延迟')) && headers.some(h => h.includes('下载速度'))) { 208 | const ipIdx = headers.findIndex(h => h.includes('IP')); 209 | const delayIdx = headers.findIndex(h => h.includes('延迟')); 210 | const speedIdx = headers.findIndex(h => h.includes('下载速度')); 211 | const port = new URL(url).searchParams.get('port') || 默认端口; 212 | dataLines.forEach(line => { 213 | const cols = line.split(',').map(c => c.trim()); 214 | const wrappedIP = IPV6_PATTERN.test(cols[ipIdx]) ? `[${cols[ipIdx]}]` : cols[ipIdx]; 215 | results.add(`${wrappedIP}:${port}#CF优选 ${cols[delayIdx]}ms ${cols[speedIdx]}MB/s`); 216 | }); 217 | } 218 | } 219 | } catch (e) { } 220 | })); 221 | return Array.from(results); 222 | } 223 | 224 | // 从GitHub获取优选IP(保留原有功能,同时支持优选API) 225 | async function fetchAndParseNewIPs(piu) { 226 | const url = piu || defaultIPURL; 227 | try { 228 | const response = await fetch(url); 229 | if (!response.ok) return []; 230 | const text = await response.text(); 231 | const results = []; 232 | const lines = text.trim().replace(/\r/g, "").split('\n'); 233 | const regex = /^([^:]+):(\d+)#(.*)$/; 234 | 235 | for (const line of lines) { 236 | const trimmedLine = line.trim(); 237 | if (!trimmedLine) continue; 238 | const match = trimmedLine.match(regex); 239 | if (match) { 240 | results.push({ 241 | ip: match[1], 242 | port: parseInt(match[2], 10), 243 | name: match[3].trim() || match[1] 244 | }); 245 | } 246 | } 247 | return results; 248 | } catch (error) { 249 | return []; 250 | } 251 | } 252 | 253 | // 生成VLESS链接 254 | function generateLinksFromSource(list, user, workerDomain, disableNonTLS = false, customPath = '/') { 255 | const CF_HTTP_PORTS = [80, 8080, 8880, 2052, 2082, 2086, 2095]; 256 | const CF_HTTPS_PORTS = [443, 2053, 2083, 2087, 2096, 8443]; 257 | const defaultHttpsPorts = [443]; 258 | const defaultHttpPorts = disableNonTLS ? [] : [80]; 259 | const links = []; 260 | const wsPath = customPath || '/'; 261 | const proto = 'vless'; 262 | 263 | list.forEach(item => { 264 | let nodeNameBase = item.isp ? item.isp.replace(/\s/g, '_') : (item.name || item.domain || item.ip); 265 | if (item.colo && item.colo.trim()) { 266 | nodeNameBase = `${nodeNameBase}-${item.colo.trim()}`; 267 | } 268 | const safeIP = item.ip.includes(':') ? `[${item.ip}]` : item.ip; 269 | 270 | let portsToGenerate = []; 271 | 272 | if (item.port) { 273 | const port = item.port; 274 | if (CF_HTTPS_PORTS.includes(port)) { 275 | portsToGenerate.push({ port: port, tls: true }); 276 | } else if (CF_HTTP_PORTS.includes(port)) { 277 | portsToGenerate.push({ port: port, tls: false }); 278 | } else { 279 | portsToGenerate.push({ port: port, tls: true }); 280 | } 281 | } else { 282 | defaultHttpsPorts.forEach(port => { 283 | portsToGenerate.push({ port: port, tls: true }); 284 | }); 285 | defaultHttpPorts.forEach(port => { 286 | portsToGenerate.push({ port: port, tls: false }); 287 | }); 288 | } 289 | 290 | portsToGenerate.forEach(({ port, tls }) => { 291 | if (tls) { 292 | const wsNodeName = `${nodeNameBase}-${port}-WS-TLS`; 293 | const wsParams = new URLSearchParams({ 294 | encryption: 'none', 295 | security: 'tls', 296 | sni: workerDomain, 297 | fp: 'chrome', 298 | type: 'ws', 299 | host: workerDomain, 300 | path: wsPath 301 | }); 302 | links.push(`${proto}://${user}@${safeIP}:${port}?${wsParams.toString()}#${encodeURIComponent(wsNodeName)}`); 303 | } else { 304 | const wsNodeName = `${nodeNameBase}-${port}-WS`; 305 | const wsParams = new URLSearchParams({ 306 | encryption: 'none', 307 | security: 'none', 308 | type: 'ws', 309 | host: workerDomain, 310 | path: wsPath 311 | }); 312 | links.push(`${proto}://${user}@${safeIP}:${port}?${wsParams.toString()}#${encodeURIComponent(wsNodeName)}`); 313 | } 314 | }); 315 | }); 316 | return links; 317 | } 318 | 319 | // 生成Trojan链接 320 | async function generateTrojanLinksFromSource(list, user, workerDomain, disableNonTLS = false, customPath = '/') { 321 | const CF_HTTP_PORTS = [80, 8080, 8880, 2052, 2082, 2086, 2095]; 322 | const CF_HTTPS_PORTS = [443, 2053, 2083, 2087, 2096, 8443]; 323 | const defaultHttpsPorts = [443]; 324 | const defaultHttpPorts = disableNonTLS ? [] : [80]; 325 | const links = []; 326 | const wsPath = customPath || '/'; 327 | const password = user; // Trojan使用UUID作为密码 328 | 329 | list.forEach(item => { 330 | let nodeNameBase = item.isp ? item.isp.replace(/\s/g, '_') : (item.name || item.domain || item.ip); 331 | if (item.colo && item.colo.trim()) { 332 | nodeNameBase = `${nodeNameBase}-${item.colo.trim()}`; 333 | } 334 | const safeIP = item.ip.includes(':') ? `[${item.ip}]` : item.ip; 335 | 336 | let portsToGenerate = []; 337 | 338 | if (item.port) { 339 | const port = item.port; 340 | if (CF_HTTPS_PORTS.includes(port)) { 341 | portsToGenerate.push({ port: port, tls: true }); 342 | } else if (CF_HTTP_PORTS.includes(port)) { 343 | if (!disableNonTLS) { 344 | portsToGenerate.push({ port: port, tls: false }); 345 | } 346 | } else { 347 | portsToGenerate.push({ port: port, tls: true }); 348 | } 349 | } else { 350 | defaultHttpsPorts.forEach(port => { 351 | portsToGenerate.push({ port: port, tls: true }); 352 | }); 353 | defaultHttpPorts.forEach(port => { 354 | portsToGenerate.push({ port: port, tls: false }); 355 | }); 356 | } 357 | 358 | portsToGenerate.forEach(({ port, tls }) => { 359 | if (tls) { 360 | const wsNodeName = `${nodeNameBase}-${port}-Trojan-WS-TLS`; 361 | const wsParams = new URLSearchParams({ 362 | security: 'tls', 363 | sni: workerDomain, 364 | fp: 'chrome', 365 | type: 'ws', 366 | host: workerDomain, 367 | path: wsPath 368 | }); 369 | links.push(`trojan://${password}@${safeIP}:${port}?${wsParams.toString()}#${encodeURIComponent(wsNodeName)}`); 370 | } else { 371 | const wsNodeName = `${nodeNameBase}-${port}-Trojan-WS`; 372 | const wsParams = new URLSearchParams({ 373 | security: 'none', 374 | type: 'ws', 375 | host: workerDomain, 376 | path: wsPath 377 | }); 378 | links.push(`trojan://${password}@${safeIP}:${port}?${wsParams.toString()}#${encodeURIComponent(wsNodeName)}`); 379 | } 380 | }); 381 | }); 382 | return links; 383 | } 384 | 385 | // 生成VMess链接 386 | function generateVMessLinksFromSource(list, user, workerDomain, disableNonTLS = false, customPath = '/') { 387 | const CF_HTTP_PORTS = [80, 8080, 8880, 2052, 2082, 2086, 2095]; 388 | const CF_HTTPS_PORTS = [443, 2053, 2083, 2087, 2096, 8443]; 389 | const defaultHttpsPorts = [443]; 390 | const defaultHttpPorts = disableNonTLS ? [] : [80]; 391 | const links = []; 392 | const wsPath = customPath || '/'; 393 | 394 | list.forEach(item => { 395 | let nodeNameBase = item.isp ? item.isp.replace(/\s/g, '_') : (item.name || item.domain || item.ip); 396 | if (item.colo && item.colo.trim()) { 397 | nodeNameBase = `${nodeNameBase}-${item.colo.trim()}`; 398 | } 399 | const safeIP = item.ip.includes(':') ? `[${item.ip}]` : item.ip; 400 | 401 | let portsToGenerate = []; 402 | 403 | if (item.port) { 404 | const port = item.port; 405 | if (CF_HTTPS_PORTS.includes(port)) { 406 | portsToGenerate.push({ port: port, tls: true }); 407 | } else if (CF_HTTP_PORTS.includes(port)) { 408 | if (!disableNonTLS) { 409 | portsToGenerate.push({ port: port, tls: false }); 410 | } 411 | } else { 412 | portsToGenerate.push({ port: port, tls: true }); 413 | } 414 | } else { 415 | defaultHttpsPorts.forEach(port => { 416 | portsToGenerate.push({ port: port, tls: true }); 417 | }); 418 | defaultHttpPorts.forEach(port => { 419 | portsToGenerate.push({ port: port, tls: false }); 420 | }); 421 | } 422 | 423 | portsToGenerate.forEach(({ port, tls }) => { 424 | const vmessConfig = { 425 | v: "2", 426 | ps: tls ? `${nodeNameBase}-${port}-VMess-WS-TLS` : `${nodeNameBase}-${port}-VMess-WS`, 427 | add: safeIP, 428 | port: port.toString(), 429 | id: user, 430 | aid: "0", 431 | scy: "auto", 432 | net: "ws", 433 | type: "none", 434 | host: workerDomain, 435 | path: wsPath, 436 | tls: tls ? "tls" : "none" 437 | }; 438 | if (tls) { 439 | vmessConfig.sni = workerDomain; 440 | vmessConfig.fp = "chrome"; 441 | } 442 | const vmessBase64 = btoa(JSON.stringify(vmessConfig)); 443 | links.push(`vmess://${vmessBase64}`); 444 | }); 445 | }); 446 | return links; 447 | } 448 | 449 | // 从GitHub IP生成链接(VLESS) 450 | function generateLinksFromNewIPs(list, user, workerDomain, customPath = '/') { 451 | const CF_HTTP_PORTS = [80, 8080, 8880, 2052, 2082, 2086, 2095]; 452 | const CF_HTTPS_PORTS = [443, 2053, 2083, 2087, 2096, 8443]; 453 | const links = []; 454 | const wsPath = customPath || '/'; 455 | const proto = 'vless'; 456 | 457 | list.forEach(item => { 458 | const nodeName = item.name.replace(/\s/g, '_'); 459 | const port = item.port; 460 | 461 | if (CF_HTTPS_PORTS.includes(port)) { 462 | const wsNodeName = `${nodeName}-${port}-WS-TLS`; 463 | const link = `${proto}://${user}@${item.ip}:${port}?encryption=none&security=tls&sni=${workerDomain}&fp=chrome&type=ws&host=${workerDomain}&path=${wsPath}#${encodeURIComponent(wsNodeName)}`; 464 | links.push(link); 465 | } else if (CF_HTTP_PORTS.includes(port)) { 466 | const wsNodeName = `${nodeName}-${port}-WS`; 467 | const link = `${proto}://${user}@${item.ip}:${port}?encryption=none&security=none&type=ws&host=${workerDomain}&path=${wsPath}#${encodeURIComponent(wsNodeName)}`; 468 | links.push(link); 469 | } else { 470 | const wsNodeName = `${nodeName}-${port}-WS-TLS`; 471 | const link = `${proto}://${user}@${item.ip}:${port}?encryption=none&security=tls&sni=${workerDomain}&fp=chrome&type=ws&host=${workerDomain}&path=${wsPath}#${encodeURIComponent(wsNodeName)}`; 472 | links.push(link); 473 | } 474 | }); 475 | return links; 476 | } 477 | 478 | // 生成订阅内容 479 | async function handleSubscriptionRequest(request, user, customDomain, piu, ipv4Enabled, ipv6Enabled, ispMobile, ispUnicom, ispTelecom, evEnabled, etEnabled, vmEnabled, disableNonTLS, customPath) { 480 | const url = new URL(request.url); 481 | const finalLinks = []; 482 | const workerDomain = url.hostname; // workerDomain始终是请求的hostname 483 | const nodeDomain = customDomain || url.hostname; // 用户输入的域名用于生成节点时的host/sni 484 | const target = url.searchParams.get('target') || 'base64'; 485 | const wsPath = customPath || '/'; 486 | 487 | async function addNodesFromList(list) { 488 | // 确保至少有一个协议被启用 489 | const hasProtocol = evEnabled || etEnabled || vmEnabled; 490 | const useVL = hasProtocol ? evEnabled : true; // 如果没有选择任何协议,默认使用VLESS 491 | 492 | if (useVL) { 493 | finalLinks.push(...generateLinksFromSource(list, user, nodeDomain, disableNonTLS, wsPath)); 494 | } 495 | if (etEnabled) { 496 | finalLinks.push(...await generateTrojanLinksFromSource(list, user, nodeDomain, disableNonTLS, wsPath)); 497 | } 498 | if (vmEnabled) { 499 | finalLinks.push(...generateVMessLinksFromSource(list, user, nodeDomain, disableNonTLS, wsPath)); 500 | } 501 | } 502 | 503 | // 原生地址 504 | const nativeList = [{ ip: workerDomain, isp: '原生地址' }]; 505 | await addNodesFromList(nativeList); 506 | 507 | // 优选域名 508 | if (epd) { 509 | const domainList = directDomains.map(d => ({ ip: d.domain, isp: d.name || d.domain })); 510 | await addNodesFromList(domainList); 511 | } 512 | 513 | // 优选IP 514 | if (epi) { 515 | try { 516 | const dynamicIPList = await fetchDynamicIPs(ipv4Enabled, ipv6Enabled, ispMobile, ispUnicom, ispTelecom); 517 | if (dynamicIPList.length > 0) { 518 | await addNodesFromList(dynamicIPList); 519 | } 520 | } catch (error) { 521 | console.error('获取动态IP失败:', error); 522 | } 523 | } 524 | 525 | // GitHub优选 / 优选API 526 | if (egi) { 527 | try { 528 | // 检查是否是优选API URL(以https://开头) 529 | if (piu && piu.toLowerCase().startsWith('https://')) { 530 | // 从优选API获取IP列表 531 | const 优选API的IP = await 请求优选API([piu]); 532 | if (优选API的IP && 优选API的IP.length > 0) { 533 | // 解析IP字符串格式:IP:端口#备注 534 | const IP列表 = 优选API的IP.map(原始地址 => { 535 | // 统一正则: 匹配 域名/IPv4/IPv6地址 + 可选端口 + 可选备注 536 | const regex = /^(\[[\da-fA-F:]+\]|[\d.]+|[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*)(?::(\d+))?(?:#(.+))?$/; 537 | const match = 原始地址.match(regex); 538 | 539 | if (match) { 540 | const 节点地址 = match[1].replace(/[\[\]]/g, ''); // 移除IPv6的方括号 541 | const 节点端口 = match[2] || 443; 542 | const 节点备注 = match[3] || 节点地址; 543 | return { 544 | ip: 节点地址, 545 | port: parseInt(节点端口), 546 | name: 节点备注 547 | }; 548 | } 549 | return null; 550 | }).filter(item => item !== null); 551 | 552 | if (IP列表.length > 0) { 553 | const hasProtocol = evEnabled || etEnabled || vmEnabled; 554 | const useVL = hasProtocol ? evEnabled : true; 555 | 556 | if (useVL) { 557 | finalLinks.push(...generateLinksFromNewIPs(IP列表, user, nodeDomain, wsPath)); 558 | } 559 | } 560 | } 561 | } else if (piu && piu.includes('\n')) { 562 | // 支持多行文本,包含混合格式(优选API URL + IP列表) 563 | const 完整优选列表 = await 整理成数组(piu); 564 | const 优选API = [], 优选IP = [], 其他节点 = []; 565 | 566 | for (const 元素 of 完整优选列表) { 567 | if (元素.toLowerCase().startsWith('https://')) { 568 | 优选API.push(元素); 569 | } else if (元素.toLowerCase().includes('://')) { 570 | 其他节点.push(元素); 571 | } else { 572 | 优选IP.push(元素); 573 | } 574 | } 575 | 576 | // 从优选API获取IP 577 | if (优选API.length > 0) { 578 | const 优选API的IP = await 请求优选API(优选API); 579 | 优选IP.push(...优选API的IP); 580 | } 581 | 582 | // 解析所有IP并生成节点 583 | if (优选IP.length > 0) { 584 | const IP列表 = 优选IP.map(原始地址 => { 585 | const regex = /^(\[[\da-fA-F:]+\]|[\d.]+|[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*)(?::(\d+))?(?:#(.+))?$/; 586 | const match = 原始地址.match(regex); 587 | 588 | if (match) { 589 | const 节点地址 = match[1].replace(/[\[\]]/g, ''); 590 | const 节点端口 = match[2] || 443; 591 | const 节点备注 = match[3] || 节点地址; 592 | return { 593 | ip: 节点地址, 594 | port: parseInt(节点端口), 595 | name: 节点备注 596 | }; 597 | } 598 | return null; 599 | }).filter(item => item !== null); 600 | 601 | if (IP列表.length > 0) { 602 | const hasProtocol = evEnabled || etEnabled || vmEnabled; 603 | const useVL = hasProtocol ? evEnabled : true; 604 | 605 | if (useVL) { 606 | finalLinks.push(...generateLinksFromNewIPs(IP列表, user, nodeDomain, wsPath)); 607 | } 608 | } 609 | } 610 | } else { 611 | // 原有的GitHub优选逻辑(单URL) 612 | const newIPList = await fetchAndParseNewIPs(piu); 613 | if (newIPList.length > 0) { 614 | const hasProtocol = evEnabled || etEnabled || vmEnabled; 615 | const useVL = hasProtocol ? evEnabled : true; 616 | 617 | if (useVL) { 618 | finalLinks.push(...generateLinksFromNewIPs(newIPList, user, nodeDomain, wsPath)); 619 | } 620 | } 621 | } 622 | } catch (error) { 623 | console.error('获取优选IP失败:', error); 624 | } 625 | } 626 | 627 | if (finalLinks.length === 0) { 628 | const errorRemark = "所有节点获取失败"; 629 | const errorLink = `vless://00000000-0000-0000-0000-000000000000@127.0.0.1:80?encryption=none&security=none&type=ws&host=error.com&path=%2F#${encodeURIComponent(errorRemark)}`; 630 | finalLinks.push(errorLink); 631 | } 632 | 633 | let subscriptionContent; 634 | let contentType = 'text/plain; charset=utf-8'; 635 | 636 | switch (target.toLowerCase()) { 637 | case 'clash': 638 | case 'clashr': 639 | subscriptionContent = generateClashConfig(finalLinks); 640 | contentType = 'text/yaml; charset=utf-8'; 641 | break; 642 | case 'surge': 643 | case 'surge2': 644 | case 'surge3': 645 | case 'surge4': 646 | subscriptionContent = generateSurgeConfig(finalLinks); 647 | break; 648 | case 'quantumult': 649 | case 'quanx': 650 | subscriptionContent = generateQuantumultConfig(finalLinks); 651 | break; 652 | default: 653 | subscriptionContent = btoa(finalLinks.join('\n')); 654 | } 655 | 656 | return new Response(subscriptionContent, { 657 | headers: { 658 | 'Content-Type': contentType, 659 | 'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', 660 | }, 661 | }); 662 | } 663 | 664 | // 生成Clash配置(简化版,返回YAML格式) 665 | function generateClashConfig(links) { 666 | let yaml = 'port: 7890\n'; 667 | yaml += 'socks-port: 7891\n'; 668 | yaml += 'allow-lan: false\n'; 669 | yaml += 'mode: rule\n'; 670 | yaml += 'log-level: info\n\n'; 671 | yaml += 'proxies:\n'; 672 | 673 | const proxyNames = []; 674 | links.forEach((link, index) => { 675 | const name = decodeURIComponent(link.split('#')[1] || `节点${index + 1}`); 676 | proxyNames.push(name); 677 | const server = link.match(/@([^:]+):(\d+)/)?.[1] || ''; 678 | const port = link.match(/@[^:]+:(\d+)/)?.[1] || '443'; 679 | const uuid = link.match(/vless:\/\/([^@]+)@/)?.[1] || ''; 680 | const tls = link.includes('security=tls'); 681 | const path = link.match(/path=([^&#]+)/)?.[1] || '/'; 682 | const host = link.match(/host=([^&#]+)/)?.[1] || ''; 683 | const sni = link.match(/sni=([^&#]+)/)?.[1] || ''; 684 | 685 | yaml += ` - name: ${name}\n`; 686 | yaml += ` type: vless\n`; 687 | yaml += ` server: ${server}\n`; 688 | yaml += ` port: ${port}\n`; 689 | yaml += ` uuid: ${uuid}\n`; 690 | yaml += ` tls: ${tls}\n`; 691 | yaml += ` network: ws\n`; 692 | yaml += ` ws-opts:\n`; 693 | yaml += ` path: ${path}\n`; 694 | yaml += ` headers:\n`; 695 | yaml += ` Host: ${host}\n`; 696 | if (sni) { 697 | yaml += ` servername: ${sni}\n`; 698 | } 699 | }); 700 | 701 | yaml += '\nproxy-groups:\n'; 702 | yaml += ' - name: PROXY\n'; 703 | yaml += ' type: select\n'; 704 | yaml += ` proxies: [${proxyNames.map(n => `'${n}'`).join(', ')}]\n`; 705 | yaml += '\nrules:\n'; 706 | yaml += ' - DOMAIN-SUFFIX,local,DIRECT\n'; 707 | yaml += ' - IP-CIDR,127.0.0.0/8,DIRECT\n'; 708 | yaml += ' - GEOIP,CN,DIRECT\n'; 709 | yaml += ' - MATCH,PROXY\n'; 710 | 711 | return yaml; 712 | } 713 | 714 | // 生成Surge配置 715 | function generateSurgeConfig(links) { 716 | let config = '[Proxy]\n'; 717 | links.forEach(link => { 718 | const name = decodeURIComponent(link.split('#')[1] || '节点'); 719 | config += `${name} = vless, ${link.match(/@([^:]+):(\d+)/)?.[1] || ''}, ${link.match(/@[^:]+:(\d+)/)?.[1] || '443'}, username=${link.match(/vless:\/\/([^@]+)@/)?.[1] || ''}, tls=${link.includes('security=tls')}, ws=true, ws-path=${link.match(/path=([^&#]+)/)?.[1] || '/'}, ws-headers=Host:${link.match(/host=([^&#]+)/)?.[1] || ''}\n`; 720 | }); 721 | config += '\n[Proxy Group]\nPROXY = select, ' + links.map((_, i) => decodeURIComponent(links[i].split('#')[1] || `节点${i + 1}`)).join(', ') + '\n'; 722 | return config; 723 | } 724 | 725 | // 生成Quantumult配置 726 | function generateQuantumultConfig(links) { 727 | return btoa(links.join('\n')); 728 | } 729 | 730 | // 在线测试延迟 - 测试IP或域名的延迟 731 | async function testLatency(host, port = 443, timeout = 5000) { 732 | const startTime = Date.now(); 733 | try { 734 | // 解析地址和端口 735 | let testHost = host; 736 | let testPort = port; 737 | 738 | // 如果host包含端口,提取出来 739 | if (host.includes(':')) { 740 | const parts = host.split(':'); 741 | testHost = parts[0].replace(/[\[\]]/g, ''); // 移除IPv6的方括号 742 | testPort = parseInt(parts[1]) || port; 743 | } 744 | 745 | // 构建测试URL 746 | const protocol = testPort === 443 || testPort === 8443 ? 'https' : 'http'; 747 | const testUrl = `${protocol}://${testHost}:${testPort}/cdn-cgi/trace`; 748 | 749 | // 使用AbortController控制超时 750 | const controller = new AbortController(); 751 | const timeoutId = setTimeout(() => controller.abort(), timeout); 752 | 753 | try { 754 | const response = await fetch(testUrl, { 755 | signal: controller.signal, 756 | headers: { 757 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' 758 | } 759 | }); 760 | 761 | clearTimeout(timeoutId); 762 | 763 | const responseTime = Date.now() - startTime; 764 | 765 | if (response.ok) { 766 | const text = await response.text(); 767 | const ipMatch = text.match(/ip=([^\s]+)/); 768 | const locMatch = text.match(/loc=([^\s]+)/); 769 | const coloMatch = text.match(/colo=([^\s]+)/); 770 | 771 | return { 772 | success: true, 773 | host: host, 774 | port: testPort, 775 | latency: responseTime, 776 | ip: ipMatch ? ipMatch[1] : null, 777 | location: locMatch ? locMatch[1] : null, 778 | colo: coloMatch ? coloMatch[1] : null 779 | }; 780 | } else { 781 | return { 782 | success: false, 783 | host: host, 784 | port: testPort, 785 | latency: responseTime, 786 | error: `HTTP ${response.status}` 787 | }; 788 | } 789 | } catch (fetchError) { 790 | clearTimeout(timeoutId); 791 | const responseTime = Date.now() - startTime; 792 | 793 | if (fetchError.name === 'AbortError') { 794 | return { 795 | success: false, 796 | host: host, 797 | port: testPort, 798 | latency: timeout, 799 | error: '请求超时' 800 | }; 801 | } 802 | 803 | return { 804 | success: false, 805 | host: host, 806 | port: testPort, 807 | latency: responseTime, 808 | error: fetchError.message || '连接失败' 809 | }; 810 | } 811 | } catch (error) { 812 | const responseTime = Date.now() - startTime; 813 | return { 814 | success: false, 815 | host: host, 816 | port: port, 817 | latency: responseTime, 818 | error: error.message || '未知错误' 819 | }; 820 | } 821 | } 822 | 823 | // 批量测试延迟 824 | async function batchTestLatency(hosts, port = 443, timeout = 5000, concurrency = 5) { 825 | const results = []; 826 | const chunks = []; 827 | 828 | // 将hosts分成多个批次 829 | for (let i = 0; i < hosts.length; i += concurrency) { 830 | chunks.push(hosts.slice(i, i + concurrency)); 831 | } 832 | 833 | // 按批次测试 834 | for (const chunk of chunks) { 835 | const chunkResults = await Promise.allSettled( 836 | chunk.map(host => testLatency(host, port, timeout)) 837 | ); 838 | 839 | chunkResults.forEach((result, index) => { 840 | if (result.status === 'fulfilled') { 841 | results.push(result.value); 842 | } else { 843 | results.push({ 844 | success: false, 845 | host: chunk[index], 846 | port: port, 847 | latency: timeout, 848 | error: result.reason?.message || '测试失败' 849 | }); 850 | } 851 | }); 852 | } 853 | 854 | // 按延迟排序 855 | results.sort((a, b) => { 856 | if (a.success && !b.success) return -1; 857 | if (!a.success && b.success) return 1; 858 | return a.latency - b.latency; 859 | }); 860 | 861 | return results; 862 | } 863 | 864 | // 生成iOS 26风格的主页 865 | function generateHomePage(scuValue) { 866 | const scu = scuValue || 'https://url.v1.mk/sub'; 867 | return ` 868 | 869 | 870 | 871 | 872 | 873 | 874 | 服务器优选工具 875 | 1218 | 1219 | 1220 |
1221 |
1222 |

服务器优选工具

1223 |

智能优选 • 一键生成

1224 |
1225 | 1226 |
1227 |
1228 | 1229 | 1230 |
1231 | 1232 |
1233 | 1234 | 1235 |
1236 | 1237 |
1238 | 1239 | 1240 | 自定义WebSocket路径,例如:/v2ray 或 / 1241 |
1242 | 1243 |
1244 | 1245 |
1246 |
1247 | 1248 |
1249 | 1250 |
1251 |
1252 | 1253 |
1254 | 1255 |
1256 |
1257 | 1258 |
1259 | 1260 | 1261 | 自定义优选IP列表来源URL,留空则使用默认地址 1262 |
1263 | 1264 |
1265 | 1266 |
1267 |
1268 | 1269 |
1270 |
1271 |
1272 | 1273 |
1274 |
1275 |
1276 | 1277 |
1278 |
1279 |
1280 |
1281 | 1282 |
1283 | 1284 |
1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 |
1296 | 1297 |
1298 | 1299 |
1300 | 1301 |
1302 | 1306 | 1310 |
1311 |
1312 | 1313 |
1314 | 1315 |
1316 | 1320 | 1324 | 1328 |
1329 |
1330 | 1331 |
1332 | 1333 |
1334 |
1335 | 启用后只生成带TLS的节点,不生成非TLS节点(如80端口) 1336 |
1337 | 1338 |
1339 |
1340 | 1341 | 1342 |
1343 | 1344 | 1345 |
1346 | 1347 | 1348 |
1349 | 1350 |
1351 | 1352 | 1353 |
1354 | 1355 | 1356 |
1357 | 1358 | 1359 |
1360 |
1361 | 1362 | 1369 |
1370 | 1371 | 1742 | 1743 | `; 1744 | } 1745 | 1746 | // 主处理函数 1747 | export default { 1748 | async fetch(request, env, ctx) { 1749 | const url = new URL(request.url); 1750 | const path = url.pathname; 1751 | 1752 | // 主页 1753 | if (path === '/' || path === '') { 1754 | const scuValue = env?.scu || scu; 1755 | return new Response(generateHomePage(scuValue), { 1756 | headers: { 'Content-Type': 'text/html; charset=utf-8' } 1757 | }); 1758 | } 1759 | 1760 | // 在线测试延迟 API: /test?host=xxx&port=443 1761 | if (path === '/test') { 1762 | const host = url.searchParams.get('host'); 1763 | const port = parseInt(url.searchParams.get('port') || '443'); 1764 | const timeout = parseInt(url.searchParams.get('timeout') || '5000'); 1765 | 1766 | if (!host) { 1767 | return new Response(JSON.stringify({ 1768 | success: false, 1769 | error: '缺少host参数' 1770 | }), { 1771 | status: 400, 1772 | headers: { 'Content-Type': 'application/json; charset=utf-8' } 1773 | }); 1774 | } 1775 | 1776 | const result = await testLatency(host, port, timeout); 1777 | return new Response(JSON.stringify(result, null, 2), { 1778 | headers: { 1779 | 'Content-Type': 'application/json; charset=utf-8', 1780 | 'Access-Control-Allow-Origin': '*', 1781 | 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 1782 | 'Access-Control-Allow-Headers': 'Content-Type' 1783 | } 1784 | }); 1785 | } 1786 | 1787 | // 批量测试延迟 API: /batch-test 1788 | if (path === '/batch-test') { 1789 | if (request.method === 'OPTIONS') { 1790 | return new Response(null, { 1791 | headers: { 1792 | 'Access-Control-Allow-Origin': '*', 1793 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 1794 | 'Access-Control-Allow-Headers': 'Content-Type' 1795 | } 1796 | }); 1797 | } 1798 | 1799 | if (request.method === 'POST') { 1800 | try { 1801 | const body = await request.json(); 1802 | const hosts = body.hosts || []; 1803 | const port = parseInt(body.port || '443'); 1804 | const timeout = parseInt(body.timeout || '5000'); 1805 | const concurrency = parseInt(body.concurrency || '5'); 1806 | 1807 | if (!Array.isArray(hosts) || hosts.length === 0) { 1808 | return new Response(JSON.stringify({ 1809 | success: false, 1810 | error: 'hosts必须是非空数组' 1811 | }), { 1812 | status: 400, 1813 | headers: { 1814 | 'Content-Type': 'application/json; charset=utf-8', 1815 | 'Access-Control-Allow-Origin': '*' 1816 | } 1817 | }); 1818 | } 1819 | 1820 | const results = await batchTestLatency(hosts, port, timeout, concurrency); 1821 | return new Response(JSON.stringify({ 1822 | success: true, 1823 | results: results, 1824 | total: results.length, 1825 | successCount: results.filter(r => r.success).length 1826 | }, null, 2), { 1827 | headers: { 1828 | 'Content-Type': 'application/json; charset=utf-8', 1829 | 'Access-Control-Allow-Origin': '*' 1830 | } 1831 | }); 1832 | } catch (error) { 1833 | return new Response(JSON.stringify({ 1834 | success: false, 1835 | error: error.message 1836 | }), { 1837 | status: 500, 1838 | headers: { 1839 | 'Content-Type': 'application/json; charset=utf-8', 1840 | 'Access-Control-Allow-Origin': '*' 1841 | } 1842 | }); 1843 | } 1844 | } 1845 | } 1846 | 1847 | // 测试优选API API: /test-optimize-api?url=xxx&port=443 1848 | if (path === '/test-optimize-api') { 1849 | if (request.method === 'OPTIONS') { 1850 | return new Response(null, { 1851 | headers: { 1852 | 'Access-Control-Allow-Origin': '*', 1853 | 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 1854 | 'Access-Control-Allow-Headers': 'Content-Type' 1855 | } 1856 | }); 1857 | } 1858 | 1859 | const apiUrl = url.searchParams.get('url'); 1860 | const port = url.searchParams.get('port') || '443'; 1861 | const timeout = parseInt(url.searchParams.get('timeout') || '3000'); 1862 | 1863 | if (!apiUrl) { 1864 | return new Response(JSON.stringify({ 1865 | success: false, 1866 | error: '缺少url参数' 1867 | }), { 1868 | status: 400, 1869 | headers: { 1870 | 'Content-Type': 'application/json; charset=utf-8', 1871 | 'Access-Control-Allow-Origin': '*' 1872 | } 1873 | }); 1874 | } 1875 | 1876 | try { 1877 | const results = await 请求优选API([apiUrl], port, timeout); 1878 | return new Response(JSON.stringify({ 1879 | success: true, 1880 | results: results, 1881 | total: results.length, 1882 | message: `成功获取 ${results.length} 个优选IP` 1883 | }, null, 2), { 1884 | headers: { 1885 | 'Content-Type': 'application/json; charset=utf-8', 1886 | 'Access-Control-Allow-Origin': '*' 1887 | } 1888 | }); 1889 | } catch (error) { 1890 | return new Response(JSON.stringify({ 1891 | success: false, 1892 | error: error.message 1893 | }), { 1894 | status: 500, 1895 | headers: { 1896 | 'Content-Type': 'application/json; charset=utf-8', 1897 | 'Access-Control-Allow-Origin': '*' 1898 | } 1899 | }); 1900 | } 1901 | } 1902 | 1903 | // 订阅请求格式: /{UUID}/sub?domain=xxx&epd=yes&epi=yes&egi=yes 1904 | const pathMatch = path.match(/^\/([^\/]+)\/sub$/); 1905 | if (pathMatch) { 1906 | const uuid = pathMatch[1]; 1907 | 1908 | if (!isValidUUID(uuid)) { 1909 | return new Response('无效的UUID格式', { status: 400 }); 1910 | } 1911 | 1912 | const domain = url.searchParams.get('domain'); 1913 | if (!domain) { 1914 | return new Response('缺少域名参数', { status: 400 }); 1915 | } 1916 | 1917 | // 从URL参数获取配置 1918 | epd = url.searchParams.get('epd') !== 'no'; 1919 | epi = url.searchParams.get('epi') !== 'no'; 1920 | egi = url.searchParams.get('egi') !== 'no'; 1921 | const piu = url.searchParams.get('piu') || defaultIPURL; 1922 | 1923 | // 协议选择 1924 | const evEnabled = url.searchParams.get('ev') === 'yes' || (url.searchParams.get('ev') === null && ev); 1925 | const etEnabled = url.searchParams.get('et') === 'yes'; 1926 | const vmEnabled = url.searchParams.get('vm') === 'yes'; 1927 | 1928 | // IPv4/IPv6选择 1929 | const ipv4Enabled = url.searchParams.get('ipv4') !== 'no'; 1930 | const ipv6Enabled = url.searchParams.get('ipv6') !== 'no'; 1931 | 1932 | // 运营商选择 1933 | const ispMobile = url.searchParams.get('ispMobile') !== 'no'; 1934 | const ispUnicom = url.searchParams.get('ispUnicom') !== 'no'; 1935 | const ispTelecom = url.searchParams.get('ispTelecom') !== 'no'; 1936 | 1937 | // TLS控制 1938 | const disableNonTLS = url.searchParams.get('dkby') === 'yes'; 1939 | 1940 | // 自定义路径 1941 | const customPath = url.searchParams.get('path') || '/'; 1942 | 1943 | return await handleSubscriptionRequest(request, uuid, domain, piu, ipv4Enabled, ipv6Enabled, ispMobile, ispUnicom, ispTelecom, evEnabled, etEnabled, vmEnabled, disableNonTLS, customPath); 1944 | } 1945 | 1946 | return new Response('Not Found', { status: 404 }); 1947 | } 1948 | }; 1949 | 1950 | --------------------------------------------------------------------------------