├── 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 |
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 |
1281 |
1282 |
1298 |
1299 |
1312 |
1313 |
1330 |
1331 |
1332 |
1333 |
1334 |
1335 | 启用后只生成带TLS的节点,不生成非TLS节点(如80端口)
1336 |
1337 |
1338 |
1339 |
1349 |
1350 |
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 |
--------------------------------------------------------------------------------
|