├── .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 | [](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 |
--------------------------------------------------------------------------------