├── .github └── workflows │ └── test.yaml ├── CFTest.py ├── README.md ├── _worker.js └── output.txt /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: 自动测试 2 | 3 | on: 4 | schedule: 5 | - cron: '0 16 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: CFTest 17 | run: python CFTest.py -i 50 18 | 19 | - name: Commit changes 20 | run: | 21 | git config --local user.email "action@github.com" 22 | git config --local user.name "GitHub Action" 23 | git add output.txt 24 | git commit -m "自动测试" 25 | git push -------------------------------------------------------------------------------- /CFTest.py: -------------------------------------------------------------------------------- 1 | MAX_CONCURRENT_THREADS = 1000 2 | REQUEST_TIMEOUT = 3 3 | 4 | import sys 5 | import requests 6 | import ipaddress 7 | from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED 8 | import threading 9 | import argparse 10 | from collections import defaultdict 11 | 12 | def fetch_ip_ranges(url): 13 | """从指定URL获取IP段列表""" 14 | try: 15 | response = requests.get(url, timeout=10) 16 | response.raise_for_status() 17 | 18 | ip_ranges = [] 19 | for line in response.text.splitlines(): 20 | line = line.strip() 21 | if line: 22 | ip_ranges.append(line) 23 | 24 | return ip_ranges 25 | except Exception as e: 26 | print(f"获取IP段失败: {e}") 27 | sys.exit(1) 28 | 29 | def expand_ip_range(ip_range): 30 | """将IP段扩展为具体的所有IP地址列表""" 31 | try: 32 | network = ipaddress.ip_network(ip_range, strict=True) 33 | return [str(ip) for ip in network] 34 | 35 | except ValueError as e: 36 | print(f"解析IP段 {ip_range} 失败: {e}") 37 | return [] 38 | 39 | def check_ip_location(ip, target_colos, stop_event): 40 | """检查IP是否连通,返回IP和对应的三字码(支持多地区筛选)""" 41 | if stop_event.is_set(): 42 | return None 43 | 44 | try: 45 | ipaddress.IPv4Address(ip) 46 | except ValueError: 47 | print(f"无效的IP地址: {ip}") 48 | return None 49 | 50 | url = f"http://{ip}/cdn-cgi/trace" 51 | try: 52 | if stop_event.is_set(): 53 | return None 54 | 55 | response = requests.get(url, timeout=REQUEST_TIMEOUT) 56 | response.raise_for_status() 57 | 58 | # 提取三字码 59 | colo = None 60 | for line in response.text.splitlines(): 61 | if line.startswith('colo='): 62 | colo = line.split('=')[1].strip() 63 | break 64 | 65 | # 验证目标机场码(支持多个目标) 66 | if target_colos and colo not in target_colos: 67 | return None 68 | 69 | return (ip, colo) if colo else None 70 | except Exception: 71 | return None 72 | 73 | def main(): 74 | # 解析命令行参数(支持多个地区参数) 75 | parser = argparse.ArgumentParser(description='CFTest') 76 | parser.add_argument('-d', nargs='+', help='机场三字码 (可选, 可指定多个, 不填则匹配所有可连通IP)') 77 | parser.add_argument('-i', type=int, default=10, help='测试数量, 默认10个') 78 | parser.add_argument('-o', default='output.txt', help='输出文件名, 默认output.txt') 79 | args = parser.parse_args() 80 | 81 | # 处理多个地区参数(转为大写) 82 | target_colos = [col.upper() for col in args.d] if args.d else None 83 | try: 84 | max_count = args.i 85 | if max_count <= 0: 86 | raise ValueError("最大数量必须为正数") 87 | except ValueError as e: 88 | print(f"无效的最大数量: {e}") 89 | sys.exit(1) 90 | 91 | output_file = args.o 92 | ip_ranges_url = "https://www.cloudflare-cn.com/ips-v4" 93 | 94 | print(f"正在从 {ip_ranges_url} 获取IP段...") 95 | ip_ranges = fetch_ip_ranges(ip_ranges_url) 96 | print(f"成功获取 {len(ip_ranges)} 个IP段") 97 | 98 | print("正在扩展IP段...") 99 | all_ips = [] 100 | for range_str in ip_ranges: 101 | ips = expand_ip_range(range_str) 102 | all_ips.extend(ips) 103 | print(f" 从 {range_str} 扩展出 {len(ips)} 个IP") 104 | 105 | all_ips = list(set(all_ips)) 106 | # 调整搜索模式描述(支持多地区显示) 107 | if target_colos: 108 | search_mode = f"属于 {', '.join(target_colos)} 的IP" 109 | else: 110 | search_mode = "可连通的IP" 111 | print(f"共扩展出 {len(all_ips)} 个唯一IP地址,正在检查每个IP...") 112 | print(f"找到 {max_count} 个{search_mode}后将停止搜索") 113 | 114 | stop_event = threading.Event() 115 | max_workers = min(MAX_CONCURRENT_THREADS, len(all_ips)) 116 | 117 | with ThreadPoolExecutor(max_workers=max_workers) as executor: 118 | futures = [] 119 | for ip in all_ips: 120 | # 传入多个目标地区 121 | future = executor.submit(check_ip_location, ip, target_colos, stop_event) 122 | futures.append(future) 123 | 124 | total = len(futures) 125 | completed = 0 126 | matched_results = [] # 存储(IP, 三字码)元组 127 | 128 | while completed < total and len(matched_results) < max_count: 129 | done, not_done = wait(futures, return_when=FIRST_COMPLETED) 130 | 131 | for future in done: 132 | if future in futures: 133 | futures.remove(future) 134 | completed += 1 135 | 136 | result = future.result() 137 | if result: 138 | matched_results.append(result) 139 | print(f"已找到 {len(matched_results)}/{max_count} 个{search_mode}") 140 | 141 | if len(matched_results) >= max_count: 142 | print(f"\n已找到 {max_count} 个{search_mode},停止搜索") 143 | stop_event.set() 144 | for f in futures: 145 | f.cancel() 146 | break 147 | 148 | if stop_event.is_set(): 149 | break 150 | 151 | if completed % 50 == 0 or completed == total: 152 | print(f"进度: {completed}/{total} ({(completed/total)*100:.1f}%),已找到 {len(matched_results)} 个{search_mode}") 153 | 154 | if len(matched_results) > max_count: 155 | matched_results = matched_results[:max_count] 156 | 157 | # 按IP地址排序 158 | matched_results.sort(key=lambda x: ipaddress.IPv4Address(x[0])) 159 | 160 | # 写入文件(根据是否指定-d参数使用不同格式) 161 | with open(output_file, 'w') as f: 162 | if target_colos: 163 | # 按三字码分组并计数(指定-d时保留原格式) 164 | colo_groups = defaultdict(list) 165 | for ip, colo in matched_results: 166 | colo_groups[colo].append(ip) 167 | # 按三字码字母顺序排序 168 | for colo in sorted(colo_groups.keys()): 169 | ips = colo_groups[colo] 170 | for idx, ip in enumerate(ips, 1): 171 | f.write(f"{ip}#{colo} {idx}\n") 172 | else: 173 | # 不指定-d时使用"IP#优选IP 序号"格式 174 | for idx, (ip, _) in enumerate(matched_results, 1): 175 | f.write(f"{ip}#优选IP {idx}\n") 176 | 177 | print(f"完成!共找到 {len(matched_results)} 个{search_mode},已保存到 {output_file}") 178 | 179 | if __name__ == "__main__": # 补充主程序入口判断 180 | main() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Edge Tunnel 2 | 3 | Edge Tunnel 是一个基于 Cloudflare Pages 的免费代理解决方案, 配置精简, 适合新手快速上手 4 | 5 | ## 项目特点 6 | 7 | - **免费**:利用 Cloudflare Pages 免费托管 8 | - **易用**:通过环境变量灵活配置 9 | - **兼容性强**:支持 v2ray 和 clash 客户端 10 | 11 | > **欢迎各位大佬指正代码中存在的问题!** 12 | 13 | [![Stargazers over time](https://starchart.cc/ImLTHQ/edgetunnel.svg?variant=adaptive)](https://starchart.cc/ImLTHQ/edgetunnel) 14 | 15 | 如果本项目对您有帮助, 请点 Star 支持 ! 16 | 17 | ## 使用方法 18 | 19 | 1. **Fork 本项目** 20 | 2. **创建 Cloudflare Pages** 21 | - **导入您 Fork 的仓库** 22 | - **添加环境变量** 23 | - **保存并部署** 24 | 4. **导入订阅(域名/订阅路径)并开始使用** 25 | 26 | ## 环境变量说明 27 | 28 | | 变量名 | 示例值 | 说明 | 29 | |-|-|-| 30 | | SUB_PATH | `订阅路径` | 域名/`订阅路径` | 31 | | TXT_URL | `https://raw.domain.com/CFST.txt` | 优选 IP 列表, 格式:`地址:端口#节点名称`, 端口默认 `443` | 32 | | NAT64 | `2a02:898:146:64::/96` | NAT64 前缀 | 33 | | DOH | `1.1.1.1` | DOH地址 | 34 | | PROXY_IP | `proxyip.cmliussss.net` | 反代地址和端口, 端口不填默认 `443` | 35 | | FAKE_WEB | `baidu.com` | 伪装网页 | 36 | 37 |
38 | CFTest说明 39 | 40 | # 简介 41 | 42 | CFTest 是用于检测 Cloudflare IP 地址的工具,可帮助用户快速筛选出可连通的 Cloudflare IP 地址,并支持按指定地区(机场三字码)进行筛选。该工具适用于需要寻找优质 Cloudflare IP 节点的场景 43 | 44 | - 所需 Python 库:requests 45 | - 可通过以下命令安装依赖:`pip install requests` 46 | 47 | | 可选参数 | 说明 | 48 | |-|-| 49 | | `-d` | 可选, 指定一个或多个机场三字码 (如 LAX SJC), 仅返回属于这些地区的 IP | 50 | | `-i` | 指定需要获取的 IP 数量, 默认值为 10 | 51 | | `-o` | 指定输出文件名称, 默认值为 `output.txt` | 52 | 53 |
54 | 55 | ## 提醒 56 | 57 | - CloudFlare 明文禁止优选IP和使用CF Pages部署代理, 封号风险自己承担 58 | - 建议定期同步上游仓库以获取最新功能和修复 -------------------------------------------------------------------------------- /_worker.js: -------------------------------------------------------------------------------- 1 | import { connect } from "cloudflare:sockets"; 2 | 3 | // 配置区块 4 | let 订阅路径 = "订阅路径"; 5 | let 伪装网页; 6 | let 验证UUID; 7 | let 优选链接 = "https://raw.githubusercontent.com/ImLTHQ/edgetunnel/main/output.txt"; 8 | let 优选列表 = []; 9 | let NAT64前缀 = "2a02:898:146:64::"; 10 | let DOH地址 = "1.1.1.1"; 11 | let 反代IP = "proxyip.cmliussss.net"; 12 | 13 | // 关键词拆分(防检测) 14 | const 威图锐拆分 = ["v2", "ray"]; 15 | const 科拉什拆分 = ["cla", "sh"]; 16 | const 维列斯拆分 = ["vl", "ess"]; 17 | 18 | const 威图锐 = 威图锐拆分.join(""); 19 | const 科拉什 = 科拉什拆分.join(""); 20 | const 维列斯 = 维列斯拆分.join(""); 21 | 22 | // 网页入口 23 | export default { 24 | async fetch(访问请求, env) { 25 | 订阅路径 = env.SUB_PATH ?? 订阅路径; 26 | 验证UUID = 生成UUID(); 27 | 优选链接 = env.TXT_URL ?? 优选链接; 28 | NAT64前缀 = env.NAT64 ?? NAT64前缀; 29 | DOH地址 = env.DOH ?? DOH地址; 30 | 反代IP = env.PROXY_IP ?? 反代IP; 31 | 伪装网页 = env.FAKE_WEB; 32 | 33 | const url = new URL(访问请求.url); 34 | const 读取我的请求标头 = 访问请求.headers.get("Upgrade"); 35 | const WS请求 = 读取我的请求标头 == "websocket"; 36 | 37 | const 路径配置 = { 38 | 威图锐: `/${encodeURIComponent(订阅路径)}/${威图锐}`, 39 | 科拉什: `/${encodeURIComponent(订阅路径)}/${科拉什}`, 40 | 订阅聚合: `/${encodeURIComponent(订阅路径)}/info`, 41 | 通用订阅: `/${encodeURIComponent(订阅路径)}`, 42 | }; 43 | 44 | const 是正确路径 = url.pathname === 路径配置.威图锐 || 45 | url.pathname === 路径配置.科拉什 || 46 | url.pathname === 路径配置.订阅聚合 || 47 | url.pathname === `/${encodeURIComponent(订阅路径)}` 48 | 49 | if (!WS请求 && !是正确路径) { 50 | if (伪装网页) { 51 | try { 52 | const targetBase = 伪装网页.startsWith('http://') || 伪装网页.startsWith('https://') 53 | ? 伪装网页 54 | : `https://${伪装网页}`; 55 | 56 | const targetUrl = new URL(targetBase); 57 | targetUrl.pathname = url.pathname; 58 | targetUrl.search = url.search; 59 | 60 | const 请求对象 = new Request(targetUrl.toString(), { 61 | method: 访问请求.method, 62 | headers: 访问请求.headers, 63 | body: 访问请求.body, 64 | }); 65 | 66 | const 响应对象 = await fetch(请求对象); 67 | return 响应对象; 68 | } catch { 69 | console.error(`[伪装网页请求失败] 目标: ${伪装网页}, 路径: ${url.pathname}`); 70 | return new Response(null, { status: 404 }); 71 | } 72 | } else { 73 | return new Response(null, { status: 404 }); 74 | } 75 | } 76 | 77 | if (!WS请求) { 78 | if (是正确路径) { 79 | 优选列表 = await 获取优选列表(); 80 | } 81 | 82 | if (url.pathname === 路径配置.威图锐) { 83 | return 威图锐配置文件(访问请求.headers.get("Host")); 84 | } 85 | else if (url.pathname === 路径配置.科拉什) { 86 | return 科拉什配置文件(访问请求.headers.get("Host")); 87 | } 88 | else if (url.pathname === 路径配置.订阅聚合) { 89 | return 聚合信息(访问请求.headers.get("Host")); 90 | } 91 | else if (url.pathname === 路径配置.通用订阅) { 92 | const 用户代理 = 访问请求.headers.get("User-Agent").toLowerCase(); 93 | const 配置生成器 = { 94 | [威图锐]: 威图锐配置文件, 95 | [科拉什]: 科拉什配置文件, 96 | tips: 提示界面, 97 | }; 98 | const 工具 = Object.keys(配置生成器).find((工具) => 用户代理.includes(工具)); 99 | 优选列表 = await 获取优选列表(); 100 | const 生成配置 = 配置生成器[工具 || "tips"]; 101 | return 生成配置(访问请求.headers.get("Host")); 102 | } 103 | } 104 | 105 | if (WS请求) { 106 | return await 升级WS请求(); 107 | } 108 | }, 109 | }; 110 | 111 | // 脚本主要架构 112 | async function 升级WS请求() { 113 | const 创建WS接口 = new WebSocketPair(); 114 | const [客户端, WS接口] = Object.values(创建WS接口); 115 | WS接口.accept(); 116 | WS接口.send(new Uint8Array([0, 0])); 117 | 启动传输管道(WS接口); 118 | return new Response(null, { status: 101, webSocket: 客户端 }); 119 | } 120 | 121 | async function 启动传输管道(WS接口) { 122 | let TCP接口, 123 | 首包数据 = false, 124 | 首包处理 = Promise.resolve(), 125 | 传输数据; 126 | WS接口.addEventListener("message", async (event) => { 127 | 首包处理 = 首包处理.then(async () => { 128 | if (!首包数据) { 129 | 首包数据 = true; 130 | await 解析VL标头(event.data); 131 | } else { 132 | await 传输数据.write(event.data); 133 | } 134 | }); 135 | }); 136 | 137 | async function 解析VL标头(VL数据) { 138 | if (验证VL的密钥(new Uint8Array(VL数据.slice(1, 17))) !== 验证UUID) { 139 | return new Response(null, { status: 400 }); 140 | } 141 | 142 | const 获取数据定位 = new Uint8Array(VL数据)[17]; 143 | const 提取端口索引 = 18 + 获取数据定位 + 1; 144 | const 建立端口缓存 = VL数据.slice(提取端口索引, 提取端口索引 + 2); 145 | const 访问端口 = new DataView(建立端口缓存).getUint16(0); 146 | 147 | const 提取地址索引 = 提取端口索引 + 2; 148 | const 建立地址缓存 = new Uint8Array(VL数据.slice(提取地址索引, 提取地址索引 + 1)); 149 | const 识别地址类型 = 建立地址缓存[0]; 150 | 151 | let 地址长度 = 0; 152 | let 访问地址 = ""; 153 | let 地址信息索引 = 提取地址索引 + 1; 154 | 155 | switch (识别地址类型) { 156 | case 1: 157 | 地址长度 = 4; 158 | 访问地址 = new Uint8Array(VL数据.slice(地址信息索引, 地址信息索引 + 地址长度)).join("."); 159 | break; 160 | case 2: 161 | 地址长度 = new Uint8Array(VL数据.slice(地址信息索引, 地址信息索引 + 1))[0]; 162 | 地址信息索引 += 1; 163 | 访问地址 = new TextDecoder().decode(VL数据.slice(地址信息索引, 地址信息索引 + 地址长度)); 164 | break; 165 | case 3: 166 | 地址长度 = 16; 167 | const dataView = new DataView(VL数据.slice(地址信息索引, 地址信息索引 + 地址长度)); 168 | const ipv6 = []; 169 | for (let i = 0; i < 8; i++) { 170 | ipv6.push(dataView.getUint16(i * 2).toString(16)); 171 | } 172 | 访问地址 = ipv6.join(":"); 173 | break; 174 | default: 175 | return new Response(null, { status: 400 }); 176 | } 177 | 178 | const 写入初始数据 = VL数据.slice(地址信息索引 + 地址长度); 179 | 180 | try { 181 | // 第一步:尝试直连 182 | TCP接口 = await connect({ hostname: 访问地址, port: 访问端口, allowHalfOpen: true }); 183 | await TCP接口.opened; 184 | } catch { 185 | // 直连失败,检查是否有NAT64前缀 186 | if (NAT64前缀) { 187 | try { 188 | // 第二步:尝试NAT64连接 189 | const NAT64地址 = 识别地址类型 === 1 190 | ? 转换IPv4到NAT64(访问地址) 191 | : 转换IPv4到NAT64(await 解析域名到IPv4(访问地址)); 192 | TCP接口 = await connect({ hostname: NAT64地址, port: 访问端口 }); 193 | await TCP接口.opened; 194 | } catch { 195 | // NAT64连接失败,使用反代 196 | if (反代IP) { 197 | try { 198 | let [反代IP地址, 反代IP端口] = 反代IP.split(":"); 199 | TCP接口 = await connect({ 200 | hostname: 反代IP地址, 201 | port: 反代IP端口 || 443, 202 | }); 203 | await TCP接口.opened; 204 | } catch { 205 | console.error("连接均失败"); 206 | } 207 | } else { 208 | console.error("连接均失败"); 209 | } 210 | } 211 | } else { 212 | // 没有NAT64前缀,尝试反代连接 213 | if (反代IP) { 214 | try { 215 | let [反代IP地址, 反代IP端口] = 反代IP.split(":"); 216 | TCP接口 = await connect({ 217 | hostname: 反代IP地址, 218 | port: 反代IP端口 || 443, 219 | }); 220 | await TCP接口.opened; 221 | } catch { 222 | console.error("连接均失败"); 223 | } 224 | } else { 225 | console.error("仅直连但失败"); 226 | } 227 | } 228 | } 229 | 230 | 建立传输管道(写入初始数据); 231 | } 232 | 233 | function 验证VL的密钥(arr, offset = 0) { 234 | const uuid = ( 235 | 转换密钥格式[arr[offset + 0]] + 236 | 转换密钥格式[arr[offset + 1]] + 237 | 转换密钥格式[arr[offset + 2]] + 238 | 转换密钥格式[arr[offset + 3]] + 239 | "-" + 240 | 转换密钥格式[arr[offset + 4]] + 241 | 转换密钥格式[arr[offset + 5]] + 242 | "-" + 243 | 转换密钥格式[arr[offset + 6]] + 244 | 转换密钥格式[arr[offset + 7]] + 245 | "-" + 246 | 转换密钥格式[arr[offset + 8]] + 247 | 转换密钥格式[arr[offset + 9]] + 248 | "-" + 249 | 转换密钥格式[arr[offset + 10]] + 250 | 转换密钥格式[arr[offset + 11]] + 251 | 转换密钥格式[arr[offset + 12]] + 252 | 转换密钥格式[arr[offset + 13]] + 253 | 转换密钥格式[arr[offset + 14]] + 254 | 转换密钥格式[arr[offset + 15]] 255 | ).toLowerCase(); 256 | return uuid; 257 | } 258 | 259 | const 转换密钥格式 = []; 260 | for (let i = 0; i < 256; ++i) { 261 | 转换密钥格式.push((i + 256).toString(16).slice(1)); 262 | } 263 | 264 | async function 建立传输管道(写入初始数据) { 265 | 传输数据 = TCP接口.writable.getWriter(); 266 | if (写入初始数据) await 传输数据.write(写入初始数据); 267 | TCP接口.readable.pipeTo( 268 | new WritableStream({ 269 | async write(VL数据) { 270 | WS接口.send(VL数据); 271 | }, 272 | }) 273 | ); 274 | } 275 | } 276 | 277 | // 其它工具函数 278 | function 转换IPv4到NAT64(ipv4地址) { 279 | const 清理后的前缀 = NAT64前缀.replace(/\/\d+$/, ''); 280 | const 十六进制 = ipv4地址.split(".").map(段 => (+段).toString(16).padStart(2, "0")); 281 | return `[${清理后的前缀}${十六进制[0]}${十六进制[1]}:${十六进制[2]}${十六进制[3]}]`; 282 | } 283 | 284 | async function 解析域名到IPv4(域名) { 285 | const { Answer } = await (await fetch(`https://${DOH地址}/dns-query?name=${域名}&type=A`, { 286 | headers: { "Accept": "application/dns-json" } 287 | })).json(); 288 | return Answer.find(({ type }) => type === 1).data; 289 | } 290 | 291 | function 生成UUID() { 292 | const 二十位 = Array.from(new TextEncoder().encode(订阅路径)) 293 | .map((byte) => byte.toString(16).padStart(2, "0")) 294 | .join("") 295 | .slice(0, 20) 296 | .padEnd(20, "0"); 297 | 298 | const 前八位 = 二十位 299 | .slice(0, 8); 300 | const 后十二位 = 二十位 301 | .slice(-12); 302 | 303 | return `${前八位}-0000-4000-8000-${后十二位}`; 304 | } 305 | 306 | async function 获取优选列表() { 307 | let 原始列表 = []; 308 | if (优选链接) { 309 | try { 310 | const 读取优选文本 = await fetch(优选链接); 311 | const 转换优选文本 = await 读取优选文本.text(); 312 | 原始列表 = 转换优选文本 313 | .split("\n") 314 | .map((line) => line.trim()) 315 | .filter((line) => line); 316 | 317 | if (原始列表.length > 0) { 318 | return 原始列表; 319 | } 320 | } 321 | catch { 322 | return []; 323 | } 324 | } 325 | return []; 326 | } 327 | 328 | function 处理优选列表(优选列表, hostName) { 329 | 优选列表.unshift(`${hostName}#原生节点`); 330 | return 优选列表.map((获取优选, index) => { 331 | const [地址端口, 节点名字 = `节点 ${index + 1}`] = 获取优选.split("#"); 332 | const 拆分地址端口 = 地址端口.split(":"); 333 | const 端口 = 拆分地址端口.length > 1 ? Number(拆分地址端口.pop()) : 443; 334 | const 地址 = 拆分地址端口.join(":"); 335 | return { 地址, 端口, 节点名字 }; 336 | }); 337 | } 338 | 339 | // 订阅页面 340 | async function 提示界面() { 341 | const 提示界面 = ` 342 | 订阅-${订阅路径} 343 | 358 | 请把链接导入 ${科拉什} 或 ${威图锐} 359 | `; 360 | 361 | return new Response(提示界面, { 362 | status: 200, 363 | headers: { "Content-Type": "text/html;charset=utf-8" }, 364 | }); 365 | } 366 | 367 | function 威图锐配置文件(hostName) { 368 | const 节点列表 = 处理优选列表(优选列表, hostName); 369 | const 配置内容 = 节点列表 370 | .map(({ 地址, 端口, 节点名字 }) => { 371 | return `${维列斯}://${验证UUID}@${地址}:${端口}?encryption=none&security=tls&sni=${hostName}&fp=chrome&type=ws&host=${hostName}#${节点名字}`; 372 | }) 373 | .join("\n"); 374 | 375 | return new Response(配置内容, { 376 | status: 200, 377 | headers: { "Content-Type": "text/plain;charset=utf-8" }, 378 | }); 379 | } 380 | 381 | function 科拉什配置文件(hostName) { 382 | const 节点列表 = 处理优选列表(优选列表, hostName); 383 | const 生成节点 = (节点列表) => { 384 | return 节点列表.map(({ 地址, 端口, 节点名字 }) => { 385 | return { 386 | nodeConfig: `- name: ${节点名字} 387 | type: ${维列斯} 388 | server: ${地址} 389 | port: ${端口} 390 | uuid: ${验证UUID} 391 | udp: true 392 | tls: true 393 | sni: ${hostName} 394 | network: ws 395 | ws-opts: 396 | headers: 397 | Host: ${hostName} 398 | User-Agent: Chrome`, 399 | proxyConfig: ` - ${节点名字}`, 400 | }; 401 | }); 402 | }; 403 | 404 | const 节点配置 = 生成节点(节点列表) 405 | .map((node) => node.nodeConfig) 406 | .join("\n"); 407 | const 代理配置 = 生成节点(节点列表) 408 | .map((node) => node.proxyConfig) 409 | .join("\n"); 410 | 411 | const 配置内容 = ` 412 | proxies: 413 | ${节点配置} 414 | 415 | proxy-groups: 416 | - name: 海外规则 417 | type: select 418 | proxies: 419 | - 延迟优选 420 | - 故障转移 421 | - DIRECT 422 | - REJECT 423 | ${代理配置} 424 | - name: 国内规则 425 | type: select 426 | proxies: 427 | - DIRECT 428 | - 延迟优选 429 | - 故障转移 430 | - REJECT 431 | ${代理配置} 432 | - name: 广告屏蔽 433 | type: select 434 | proxies: 435 | - REJECT 436 | - DIRECT 437 | - 延迟优选 438 | - 故障转移 439 | ${代理配置} 440 | - name: 延迟优选 441 | type: url-test 442 | url: https://www.google.com/generate_204 443 | interval: 30 444 | tolerance: 50 445 | proxies: 446 | ${代理配置} 447 | - name: 故障转移 448 | type: fallback 449 | url: https://www.google.com/generate_204 450 | interval: 30 451 | proxies: 452 | ${代理配置} 453 | 454 | rules: 455 | - GEOSITE,category-ads-all,广告屏蔽 456 | - GEOSITE,cn,国内规则 457 | - GEOIP,CN,国内规则,no-resolve 458 | - MATCH,海外规则 459 | `; 460 | 461 | return new Response(配置内容, { 462 | status: 200, 463 | headers: { "Content-Type": "text/plain;charset=utf-8" }, 464 | }); 465 | } 466 | 467 | function 聚合信息(hostName) { 468 | return new Response(`${hostName}#${验证UUID}`, { 469 | status: 200, 470 | headers: { "Content-Type": "text/plain;charset=utf-8" }, 471 | }); 472 | } -------------------------------------------------------------------------------- /output.txt: -------------------------------------------------------------------------------- 1 | 104.16.111.168#优选IP 1 2 | 104.16.118.44#优选IP 2 3 | 104.16.194.198#优选IP 3 4 | 104.16.240.128#优选IP 4 5 | 104.17.224.233#优选IP 5 6 | 104.18.43.255#优选IP 6 7 | 104.18.57.184#优选IP 7 8 | 104.18.102.215#优选IP 8 9 | 104.18.114.103#优选IP 9 10 | 104.18.147.7#优选IP 10 11 | 104.18.178.10#优选IP 11 12 | 104.18.187.104#优选IP 12 13 | 104.18.197.136#优选IP 13 14 | 104.18.205.219#优选IP 14 15 | 104.19.22.90#优选IP 15 16 | 104.19.22.223#优选IP 16 17 | 104.19.166.148#优选IP 17 18 | 104.19.181.32#优选IP 18 19 | 104.19.198.69#优选IP 19 20 | 104.20.13.123#优选IP 20 21 | 104.20.18.136#优选IP 21 22 | 104.21.29.69#优选IP 22 23 | 104.21.112.117#优选IP 23 24 | 104.21.120.1#优选IP 24 25 | 104.24.32.109#优选IP 25 26 | 104.24.40.237#优选IP 26 27 | 104.24.80.167#优选IP 27 28 | 104.24.143.133#优选IP 28 29 | 104.24.192.225#优选IP 29 30 | 104.25.27.30#优选IP 30 31 | 104.25.148.185#优选IP 31 32 | 104.25.160.106#优选IP 32 33 | 104.25.184.219#优选IP 33 34 | 104.25.219.233#优选IP 34 35 | 104.25.222.175#优选IP 35 36 | 104.25.243.168#优选IP 36 37 | 104.25.254.2#优选IP 37 38 | 104.27.0.41#优选IP 38 39 | 104.27.5.51#优选IP 39 40 | 104.27.14.46#优选IP 40 41 | 104.27.197.134#优选IP 41 42 | 162.159.15.143#优选IP 42 43 | 162.159.27.109#优选IP 43 44 | 162.159.41.94#优选IP 44 45 | 162.159.251.234#优选IP 45 46 | 172.67.77.87#优选IP 46 47 | 172.67.97.155#优选IP 47 48 | 172.67.177.232#优选IP 48 49 | 172.67.181.146#优选IP 49 50 | 188.114.99.227#优选IP 50 51 | --------------------------------------------------------------------------------