cfpilot-scanner 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -89
- package/dist/cli.js +83 -31
- package/package.json +41 -41
package/README.md
CHANGED
|
@@ -1,89 +1,119 @@
|
|
|
1
|
-
# cfpilot-scanner
|
|
2
|
-
|
|
3
|
-
**CFPilot 本地真连接优选器** —— 用你自己的 Worker 域名做 SNI/Host,完成
|
|
4
|
-
**WebSocket 101 真连接校验**,只保留真正能连通的 Cloudflare 优选 IP,
|
|
5
|
-
彻底消灭客户端里的 `-1`。
|
|
6
|
-
|
|
7
|
-
> 绝大多数"优选 IP"工具只做 TCP 握手(端口通就算可用),
|
|
8
|
-
> 结果填进客户端大量 `-1`。本工具会真的发起 TLS + WebSocket 升级,
|
|
9
|
-
> 走完你自己 Worker 的入站路径,只有返回 `101` 的 IP 才会被保留。
|
|
10
|
-
|
|
11
|
-
## 安装
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx -y cfpilot-scanner --help
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
无需安装,`npx` 直接运行;也可 `npm i -g cfpilot-scanner`。
|
|
18
|
-
|
|
19
|
-
## 快速开始
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
# 1. 在你的面板里生成一次性扫描令牌(有效期短,用完即废)
|
|
23
|
-
# 2. 执行优选(域名必须是绑好自定义域名的那个)
|
|
24
|
-
npx -y cfpilot-scanner scan --host cfpilot.example.com --token <令牌>
|
|
25
|
-
|
|
26
|
-
# 结果会自动回填到面板的「手动优选 IP」池
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
只打印不回填:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npx -y cfpilot-scanner scan --host cfpilot.example.com --no-callback --top 20
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
##
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1
|
+
# cfpilot-scanner
|
|
2
|
+
|
|
3
|
+
**CFPilot 本地真连接优选器** —— 用你自己的 Worker 域名做 SNI/Host,完成
|
|
4
|
+
**WebSocket 101 真连接校验**,只保留真正能连通的 Cloudflare 优选 IP,
|
|
5
|
+
彻底消灭客户端里的 `-1`。
|
|
6
|
+
|
|
7
|
+
> 绝大多数"优选 IP"工具只做 TCP 握手(端口通就算可用),
|
|
8
|
+
> 结果填进客户端大量 `-1`。本工具会真的发起 TLS + WebSocket 升级,
|
|
9
|
+
> 走完你自己 Worker 的入站路径,只有返回 `101` 的 IP 才会被保留。
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx -y cfpilot-scanner --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
无需安装,`npx` 直接运行;也可 `npm i -g cfpilot-scanner`。
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. 在你的面板里生成一次性扫描令牌(有效期短,用完即废)
|
|
23
|
+
# 2. 执行优选(域名必须是绑好自定义域名的那个)
|
|
24
|
+
npx -y cfpilot-scanner scan --host cfpilot.example.com --token <令牌>
|
|
25
|
+
|
|
26
|
+
# 结果会自动回填到面板的「手动优选 IP」池
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
只打印不回填:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx -y cfpilot-scanner scan --host cfpilot.example.com --no-callback --top 20
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 对接任意 VLESS-WS 项目(CFNext / cfnew / BPB / edgetunnel)
|
|
36
|
+
|
|
37
|
+
不依赖 CFPilot 自己的探针端点,只要知道你项目的 **WebSocket 路径**即可:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# CFNext:路径通常就是 UUID(或面板里配置的 path)
|
|
41
|
+
npx -y cfpilot-scanner scan --host your-domain.com --path /<uuid> --no-callback
|
|
42
|
+
|
|
43
|
+
# 常见约定:/vless
|
|
44
|
+
npx -y cfpilot-scanner scan --host your-domain.com --path /vless --no-callback
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- 阶段二会对 `<path>` 发起真实的 **TLS + WebSocket 101** 握手,只有 101 才算可用
|
|
48
|
+
- 如果目标没有测速端点,会自动跳过测速、只按延迟排序(不会再干等 12 秒)
|
|
49
|
+
- 想测速可加 `--probe-path <路径>` 与 `--no-speed` 控制行为
|
|
50
|
+
|
|
51
|
+
## 输出:直接可复制的列表
|
|
52
|
+
|
|
53
|
+
扫描结束后会额外打印一段,**一行一个,直接粘进同类项目的「自定义 IP / 优选 IP」输入框**:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
── 可直接复制(粘贴到「自定义 IP / 优选 IP」输入框)──
|
|
57
|
+
104.19.73.179:443#LAX
|
|
58
|
+
104.17.242.168:443#LAX
|
|
59
|
+
104.16.59.243:443#LAX
|
|
60
|
+
104.27.19.224:443#AMS
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
(`IP:端口#机房` 是这类项目通用的语法;优选几个就有几行,不用再手动删改)
|
|
64
|
+
|
|
65
|
+
## 三阶扫描
|
|
66
|
+
|
|
67
|
+
| 阶段 | 做什么 | 默认开销 |
|
|
68
|
+
|------|--------|----------|
|
|
69
|
+
| 一阶 · 延迟探测 | 采样大量 IP 做 TCP 连接测延迟 | 采样 20000,并发 300 |
|
|
70
|
+
| 二阶 · 真连接校验 | 对候选发起 **TLS + WebSocket 101** 握手 | 120 个候选 |
|
|
71
|
+
| 三阶 · 下行测速 | 通过 `/__edge_probe` 流式下载测真实速度 | 30 个候选,5 MB |
|
|
72
|
+
|
|
73
|
+
只有通过二阶的 IP 才算"可用",三阶用于排序。
|
|
74
|
+
|
|
75
|
+
## 常用参数
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
--host <domain> 你的 Worker 自定义域名(SNI 与 Host)
|
|
79
|
+
--token <token> 面板生成的一次性令牌
|
|
80
|
+
--top <n> 保留条数(默认 15)
|
|
81
|
+
--concurrency <n> 一阶并发(默认 300)
|
|
82
|
+
--sample <n> 候选采样数(默认 20000)
|
|
83
|
+
--cidr <a,b> 自定义网段
|
|
84
|
+
--ip-file <path> 使用本地 IP 列表(支持 CloudflareSpeedTest 的 result.csv)
|
|
85
|
+
--max-latency <ms> 一阶延迟上限
|
|
86
|
+
--json 以 JSON 输出
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 本地面板桥接
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx -y cfpilot-scanner serve
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
在 `127.0.0.1:19527` 起一个本地服务,面板里点「本地真连接优选」会自动唤醒它,
|
|
96
|
+
通过 SSE 实时推送进度,不需要手动复制令牌。
|
|
97
|
+
|
|
98
|
+
## 输出示例
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
阶段一:采样 20000 个候选,并发 300 …
|
|
102
|
+
阶段二:120 个候选进入 101 校验 …
|
|
103
|
+
阶段三:30 个候选测速(5 MB)…
|
|
104
|
+
✅ 可用 14 个,已回填面板
|
|
105
|
+
|
|
106
|
+
104.17.242.168:443 12ms 18.4 MB/s
|
|
107
|
+
104.19.73.179:443 18ms 15.1 MB/s
|
|
108
|
+
...
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 为什么需要它
|
|
112
|
+
|
|
113
|
+
随机抽取的 Cloudflare 官方 IP 里,能真正完成 WebSocket 升级的比例只有约 1/4,
|
|
114
|
+
而且**因地区、运营商、时间而异**。只有用你自己的域名实测过,
|
|
115
|
+
才能得到当前网络下真正可用的那批 IP。
|
|
116
|
+
|
|
117
|
+
## 许可
|
|
118
|
+
|
|
119
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -337,7 +337,7 @@ function extractColo(text) {
|
|
|
337
337
|
const match = /cf-ray:\s*[0-9a-f]+-([A-Z]{3})/i.exec(text);
|
|
338
338
|
return match ? match[1].toUpperCase() : "";
|
|
339
339
|
}
|
|
340
|
-
function probeWs101(ip, port, host, timeoutMs = 3e3) {
|
|
340
|
+
function probeWs101(ip, port, host, timeoutMs = 3e3, path = "/__edge_probe") {
|
|
341
341
|
return new Promise((resolve) => {
|
|
342
342
|
const started = Date.now();
|
|
343
343
|
const key = crypto.randomBytes(16).toString("base64");
|
|
@@ -354,7 +354,7 @@ function probeWs101(ip, port, host, timeoutMs = 3e3) {
|
|
|
354
354
|
resolve({ ...result, latencyMs: Date.now() - started });
|
|
355
355
|
};
|
|
356
356
|
const request = [
|
|
357
|
-
`GET
|
|
357
|
+
`GET ${path} HTTP/1.1`,
|
|
358
358
|
`Host: ${host}`,
|
|
359
359
|
`Upgrade: websocket`,
|
|
360
360
|
`Connection: Upgrade`,
|
|
@@ -405,7 +405,7 @@ function probeWs101(ip, port, host, timeoutMs = 3e3) {
|
|
|
405
405
|
});
|
|
406
406
|
});
|
|
407
407
|
}
|
|
408
|
-
function measureDownload(ip, port, host, bytes, timeoutMs = 12e3) {
|
|
408
|
+
function measureDownload(ip, port, host, bytes, timeoutMs = 12e3, path = "/__edge_probe") {
|
|
409
409
|
return new Promise((resolve) => {
|
|
410
410
|
const started = Date.now();
|
|
411
411
|
let headerEnd = -1;
|
|
@@ -423,7 +423,7 @@ function measureDownload(ip, port, host, bytes, timeoutMs = 12e3) {
|
|
|
423
423
|
resolve({ ...result, ms: Date.now() - started });
|
|
424
424
|
};
|
|
425
425
|
const request = [
|
|
426
|
-
`GET
|
|
426
|
+
`GET ${path}?bytes=${bytes} HTTP/1.1`,
|
|
427
427
|
`Host: ${host}`,
|
|
428
428
|
`User-Agent: ${USER_AGENT}`,
|
|
429
429
|
`Accept: */*`,
|
|
@@ -463,6 +463,11 @@ function measureDownload(ip, port, host, bytes, timeoutMs = 12e3) {
|
|
|
463
463
|
socket.on("error", (err) => {
|
|
464
464
|
finish({ ok: false, bytes: received, speedMBps: 0, error: err.message });
|
|
465
465
|
});
|
|
466
|
+
const settle = () => {
|
|
467
|
+
if (settled) return;
|
|
468
|
+
const seconds = Math.max(1e-3, (Date.now() - started) / 1e3);
|
|
469
|
+
finish({ ok: received > 0, bytes: received, speedMBps: received / 1024 / 1024 / seconds });
|
|
470
|
+
};
|
|
466
471
|
socket.on("data", (chunk) => {
|
|
467
472
|
if (headerEnd < 0) {
|
|
468
473
|
buffer += chunk.toString("latin1");
|
|
@@ -475,10 +480,13 @@ function measureDownload(ip, port, host, bytes, timeoutMs = 12e3) {
|
|
|
475
480
|
const body = Buffer.from(buffer, "latin1").subarray(headerEnd);
|
|
476
481
|
received += body.byteLength;
|
|
477
482
|
headerEnd = 0;
|
|
478
|
-
|
|
483
|
+
} else {
|
|
484
|
+
received += chunk.byteLength;
|
|
479
485
|
}
|
|
480
|
-
received
|
|
486
|
+
if (received >= bytes) settle();
|
|
481
487
|
});
|
|
488
|
+
socket.on("end", settle);
|
|
489
|
+
socket.on("close", settle);
|
|
482
490
|
});
|
|
483
491
|
}
|
|
484
492
|
|
|
@@ -554,7 +562,7 @@ async function runScan(options) {
|
|
|
554
562
|
report({ type: "stage", stage: 2, name: "WebSocket 101 \u771F\u8FDE\u63A5\u6821\u9A8C", total: stage1.length });
|
|
555
563
|
let probeDone = 0;
|
|
556
564
|
const probeResults = await pool(stage1, probeConcurrency, async (item) => {
|
|
557
|
-
const result = await probeWs101(item.ip, options.port, options.host, probeTimeoutMs);
|
|
565
|
+
const result = await probeWs101(item.ip, options.port, options.host, probeTimeoutMs, options.wsPath ?? "/__edge_probe");
|
|
558
566
|
probeDone++;
|
|
559
567
|
report({
|
|
560
568
|
type: "progress",
|
|
@@ -567,33 +575,40 @@ async function runScan(options) {
|
|
|
567
575
|
});
|
|
568
576
|
const wsOk = probeResults.filter((r) => r.probe.ok);
|
|
569
577
|
const stage2 = wsOk.sort((a, b) => a.latency - b.latency).slice(0, speedLimit);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
578
|
+
let speedResults;
|
|
579
|
+
if (options.skipSpeed) {
|
|
580
|
+
report({ type: "stage", stage: 3, name: "\u8DF3\u8FC7\u6D4B\u901F\uFF08\u6309\u5EF6\u8FDF\u6392\u5E8F\uFF09", total: stage2.length });
|
|
581
|
+
speedResults = stage2.map((r) => ({ ...r }));
|
|
582
|
+
} else {
|
|
583
|
+
report({ type: "stage", stage: 3, name: "\u771F\u5B9E\u4E0B\u884C\u901F\u5EA6\u6D4B\u8BD5", total: stage2.length });
|
|
584
|
+
let speedDone = 0;
|
|
585
|
+
speedResults = await pool(stage2, speedConcurrency, async (item) => {
|
|
586
|
+
const result = await measureDownload(
|
|
587
|
+
item.ip,
|
|
588
|
+
options.port,
|
|
589
|
+
options.host,
|
|
590
|
+
speedBytes,
|
|
591
|
+
speedTimeoutMs,
|
|
592
|
+
options.probePath ?? "/__edge_probe"
|
|
593
|
+
);
|
|
594
|
+
speedDone++;
|
|
595
|
+
report({
|
|
596
|
+
type: "progress",
|
|
597
|
+
stage: 3,
|
|
598
|
+
done: speedDone,
|
|
599
|
+
total: stage2.length,
|
|
600
|
+
message: `\u6D4B\u901F ${speedDone}/${stage2.length} ${result.speedMBps.toFixed(1)} MB/s`
|
|
601
|
+
});
|
|
602
|
+
return { ...item, download: result };
|
|
587
603
|
});
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const entries = speedResults.filter((r) => r.download.ok && r.download.speedMBps > 0).map((r) => ({
|
|
604
|
+
}
|
|
605
|
+
const entries = speedResults.filter((r) => options.skipSpeed ? true : Boolean(r.download && r.download.ok && r.download.speedMBps > 0)).map((r) => ({
|
|
591
606
|
ip: r.ip,
|
|
592
607
|
port: options.port,
|
|
593
608
|
colo: r.probe.colo || "UNK",
|
|
594
609
|
latency: r.latency,
|
|
595
|
-
speed: Number(r.download.speedMBps.toFixed(
|
|
596
|
-
})).sort((a, b) => b.speed - a.speed || a.latency - b.latency).slice(0, top);
|
|
610
|
+
speed: r.download ? Number(r.download.speedMBps.toFixed(2)) : 0
|
|
611
|
+
})).sort((a, b) => options.skipSpeed ? a.latency - b.latency : b.speed - a.speed || a.latency - b.latency).slice(0, top);
|
|
597
612
|
for (const entry of entries) {
|
|
598
613
|
report({ type: "result", entry });
|
|
599
614
|
}
|
|
@@ -610,6 +625,9 @@ async function runScan(options) {
|
|
|
610
625
|
report({ type: "done", count: entries.length });
|
|
611
626
|
return summary;
|
|
612
627
|
}
|
|
628
|
+
function formatCopyable(entries) {
|
|
629
|
+
return entries.map((e) => `${e.ip}:${e.port}#${e.colo}`).join("\n");
|
|
630
|
+
}
|
|
613
631
|
function formatEntries(entries) {
|
|
614
632
|
return entries.map((e) => `${e.ip}:${e.port}#${e.colo}_${e.latency}_${e.speed}`);
|
|
615
633
|
}
|
|
@@ -639,7 +657,7 @@ async function postResults(callbackUrl, token, entries, timeoutMs = 15e3) {
|
|
|
639
657
|
|
|
640
658
|
// src/server.ts
|
|
641
659
|
import { createServer } from "node:http";
|
|
642
|
-
var BRIDGE_VERSION = "0.
|
|
660
|
+
var BRIDGE_VERSION = "0.2.0";
|
|
643
661
|
var DEFAULT_BRIDGE_PORT = 19527;
|
|
644
662
|
function normalizeSample(value) {
|
|
645
663
|
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
@@ -906,7 +924,7 @@ async function handleRequest(req, res) {
|
|
|
906
924
|
}
|
|
907
925
|
|
|
908
926
|
// src/version.ts
|
|
909
|
-
var VERSION = "0.
|
|
927
|
+
var VERSION = "0.2.0";
|
|
910
928
|
var NAME = "cfpilot-scanner";
|
|
911
929
|
|
|
912
930
|
// src/cli.ts
|
|
@@ -928,6 +946,10 @@ scan \u9009\u9879:
|
|
|
928
946
|
--token <token> \u56DE\u586B\u4EE4\u724C\uFF08\u9762\u677F\u751F\u6210\u7684\u4E00\u6B21\u6027\u4EE4\u724C\u6216 SCANNER_TOKEN\uFF09
|
|
929
947
|
--callback <url> \u56DE\u586B\u5730\u5740\uFF0C\u9ED8\u8BA4 https://<host>/api/v1/preferred-ips
|
|
930
948
|
--no-callback \u53EA\u6253\u5370\u7ED3\u679C\uFF0C\u4E0D\u56DE\u586B
|
|
949
|
+
--path <path> \u9636\u6BB5\u4E8C\u6821\u9A8C\u7684 WebSocket \u8DEF\u5F84\uFF0C\u9ED8\u8BA4 /__edge_probe
|
|
950
|
+
\uFF08\u5BF9\u63A5\u5176\u5B83\u9879\u76EE\u65F6\u586B\u5B83\u81EA\u5DF1\u7684 WS \u8DEF\u5F84\uFF0C\u4F8B\u5982 /vless \u6216 /<uuid>\uFF09
|
|
951
|
+
--probe-path <path> \u9636\u6BB5\u4E09\u6D4B\u901F\u8DEF\u5F84\uFF0C\u9ED8\u8BA4 /__edge_probe
|
|
952
|
+
--no-speed \u8DF3\u8FC7\u6D4B\u901F\uFF0C\u53EA\u6309\u5EF6\u8FDF\u6392\u5E8F\uFF08\u76EE\u6807\u6CA1\u6709 CFPilot \u63A2\u9488\u65F6\u7528\uFF09
|
|
931
953
|
--top <n> \u8FD4\u56DE\u6761\u6570\uFF0C\u9ED8\u8BA4 15
|
|
932
954
|
--concurrency <n> \u9636\u6BB5\u4E00\u5E76\u53D1\uFF0C\u9ED8\u8BA4 300
|
|
933
955
|
--sample <n> \u5019\u9009 IP \u91C7\u6837\u6570\uFF0C\u9ED8\u8BA4 20000
|
|
@@ -1057,6 +1079,11 @@ function printTable(entries) {
|
|
|
1057
1079
|
`);
|
|
1058
1080
|
});
|
|
1059
1081
|
}
|
|
1082
|
+
function printCopyable(entries) {
|
|
1083
|
+
if (entries.length === 0) return;
|
|
1084
|
+
process.stdout.write("\n\u2500\u2500 \u53EF\u76F4\u63A5\u590D\u5236\uFF08\u7C98\u8D34\u5230\u300C\u81EA\u5B9A\u4E49 IP / \u4F18\u9009 IP\u300D\u8F93\u5165\u6846\uFF09\u2500\u2500\n");
|
|
1085
|
+
process.stdout.write(formatCopyable(entries) + "\n");
|
|
1086
|
+
}
|
|
1060
1087
|
async function commandScan(args) {
|
|
1061
1088
|
const host = flagString(args, "host");
|
|
1062
1089
|
const quiet = flagBool(args, "quiet");
|
|
@@ -1070,6 +1097,9 @@ async function commandScan(args) {
|
|
|
1070
1097
|
const top = flagNumber(args, "top", 15);
|
|
1071
1098
|
const token = flagString(args, "token");
|
|
1072
1099
|
const noCallback = flagBool(args, "no-callback");
|
|
1100
|
+
const wsPath = flagString(args, "path", "/__edge_probe");
|
|
1101
|
+
const probePath = flagString(args, "probe-path", "/__edge_probe");
|
|
1102
|
+
const noSpeed = flagBool(args, "no-speed");
|
|
1073
1103
|
const callback = noCallback ? "" : flagString(args, "callback", token ? `https://${host}/api/v1/preferred-ips` : "");
|
|
1074
1104
|
if (!quiet) {
|
|
1075
1105
|
process.stdout.write(`
|
|
@@ -1086,10 +1116,31 @@ ${NAME} v${VERSION}
|
|
|
1086
1116
|
}
|
|
1087
1117
|
if (!quiet) process.stdout.write(`\u5019\u9009 IP\uFF1A${candidates.length} \u4E2A
|
|
1088
1118
|
`);
|
|
1119
|
+
let skipSpeed = noSpeed;
|
|
1120
|
+
if (!skipSpeed) {
|
|
1121
|
+
try {
|
|
1122
|
+
const probe = await fetch(`https://${host}${probePath}?bytes=1024`, {
|
|
1123
|
+
method: "GET",
|
|
1124
|
+
signal: AbortSignal.timeout(8e3)
|
|
1125
|
+
});
|
|
1126
|
+
if (!probe.headers.get("x-probe-bytes")) {
|
|
1127
|
+
skipSpeed = true;
|
|
1128
|
+
if (!quiet) {
|
|
1129
|
+
process.stdout.write("\n\u63D0\u793A\uFF1A" + probePath + " \u4E0D\u662F CFPilot \u63A2\u9488\u7AEF\u70B9\uFF0C\u5DF2\u8DF3\u8FC7\u6D4B\u901F\uFF08\u53EA\u6309\u5EF6\u8FDF\u6392\u5E8F\uFF09\n");
|
|
1130
|
+
process.stdout.write(" \u5982\u9700\u6D4B\u901F\uFF0C\u8BF7\u7528 --probe-path \u6307\u5B9A\u76EE\u6807\u9879\u76EE\u7684\u6D4B\u901F\u8DEF\u5F84\n");
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
} catch {
|
|
1134
|
+
skipSpeed = true;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1089
1137
|
const summary = await runScan({
|
|
1090
1138
|
host,
|
|
1091
1139
|
port,
|
|
1092
1140
|
candidates,
|
|
1141
|
+
wsPath,
|
|
1142
|
+
probePath,
|
|
1143
|
+
skipSpeed,
|
|
1093
1144
|
concurrency: flagNumber(args, "concurrency", 300),
|
|
1094
1145
|
tcpTimeoutMs: flagNumber(args, "timeout", 1200),
|
|
1095
1146
|
maxLatencyMs: flagNumber(args, "max-latency", 0),
|
|
@@ -1108,6 +1159,7 @@ ${NAME} v${VERSION}
|
|
|
1108
1159
|
);
|
|
1109
1160
|
} else {
|
|
1110
1161
|
printTable(summary.entries);
|
|
1162
|
+
printCopyable(summary.entries);
|
|
1111
1163
|
}
|
|
1112
1164
|
if (callback && token) {
|
|
1113
1165
|
const result = await postResults(callback, token, formatEntries(summary.entries));
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cfpilot-scanner",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CFPilot
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "CFPilot Contributors",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/xmwengxing/CFPilot.git"
|
|
10
|
-
},
|
|
11
|
-
"keywords": [
|
|
12
|
-
"cloudflare",
|
|
13
|
-
"ip",
|
|
14
|
-
"optimize",
|
|
15
|
-
"scanner",
|
|
16
|
-
"vless",
|
|
17
|
-
"websocket",
|
|
18
|
-
"cfpilot"
|
|
19
|
-
],
|
|
20
|
-
"type": "module",
|
|
21
|
-
"bin": {
|
|
22
|
-
"cfpilot-scanner": "dist/cli.js"
|
|
23
|
-
},
|
|
24
|
-
"files": [
|
|
25
|
-
"dist",
|
|
26
|
-
"README.md"
|
|
27
|
-
],
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=18.17.0"
|
|
30
|
-
},
|
|
31
|
-
"scripts": {
|
|
32
|
-
"build": "node scripts/build.mjs",
|
|
33
|
-
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
34
|
-
"prepublishOnly": "npm run build"
|
|
35
|
-
},
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@types/node": "^26.6.5",
|
|
38
|
-
"esbuild": "^0.28.2",
|
|
39
|
-
"typescript": "^5.9.3"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "cfpilot-scanner",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CFPilot 优选器:用你自己的域名做 WebSocket 101 真连接校验,只保留真正可用的 Cloudflare IP;兼容任意 VLESS-WS 项目",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "CFPilot Contributors",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/xmwengxing/CFPilot.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cloudflare",
|
|
13
|
+
"ip",
|
|
14
|
+
"optimize",
|
|
15
|
+
"scanner",
|
|
16
|
+
"vless",
|
|
17
|
+
"websocket",
|
|
18
|
+
"cfpilot"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"cfpilot-scanner": "dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.17.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "node scripts/build.mjs",
|
|
33
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^26.6.5",
|
|
38
|
+
"esbuild": "^0.28.2",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
}
|
|
41
|
+
}
|