mihomo-cli 2.6.2 → 2.7.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/CHANGELOG.md +21 -0
- package/README.md +0 -4
- package/dist/index.js +1557 -2035
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,65 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
import
|
|
3
|
+
// src/paths.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
function getUserDataDir() {
|
|
8
|
+
if (process.env.MIHOMO_CLI_DIR) {
|
|
9
|
+
return process.env.MIHOMO_CLI_DIR;
|
|
10
|
+
}
|
|
11
|
+
return path.join(os.homedir(), ".mihomo-cli");
|
|
12
|
+
}
|
|
13
|
+
var USER_DATA_DIR = getUserDataDir();
|
|
14
|
+
var DIRS = {
|
|
15
|
+
kernel: path.join(USER_DATA_DIR, "kernel"),
|
|
16
|
+
subscriptions: path.join(USER_DATA_DIR, "subscriptions"),
|
|
17
|
+
logs: path.join(USER_DATA_DIR, "logs"),
|
|
18
|
+
data: path.join(USER_DATA_DIR, "data"),
|
|
19
|
+
runtime: path.join(USER_DATA_DIR, "runtime")
|
|
20
|
+
};
|
|
21
|
+
var PATHS = {
|
|
22
|
+
mihomoBinary: path.join(DIRS.kernel, "mihomo"),
|
|
23
|
+
settingsFile: path.join(USER_DATA_DIR, "settings.json"),
|
|
24
|
+
subscriptionsCacheFile: path.join(DIRS.subscriptions, "cache.json"),
|
|
25
|
+
configFile: path.join(DIRS.runtime, "config.yaml"),
|
|
26
|
+
logFile: path.join(DIRS.logs, "mihomo.log"),
|
|
27
|
+
pidFile: path.join(DIRS.runtime, "pid"),
|
|
28
|
+
configStage1Subscription: path.join(DIRS.runtime, "1.subscription.yaml"),
|
|
29
|
+
configStage2Overwrite: path.join(DIRS.runtime, "2.overwrite.yaml"),
|
|
30
|
+
configStage3System: path.join(DIRS.runtime, "3.system.yaml")
|
|
31
|
+
};
|
|
32
|
+
var DIRECTORY_TARGETS = {
|
|
33
|
+
root: { path: null, label: "\u6839\u76EE\u5F55" },
|
|
34
|
+
subs: { path: DIRS.subscriptions, label: "\u8BA2\u9605\u76EE\u5F55" },
|
|
35
|
+
logs: { path: DIRS.logs, label: "\u65E5\u5FD7\u76EE\u5F55" },
|
|
36
|
+
data: { path: DIRS.data, label: "mihomo \u6570\u636E\u76EE\u5F55" },
|
|
37
|
+
runtime: { path: DIRS.runtime, label: "\u8FD0\u884C\u65F6\u76EE\u5F55" },
|
|
38
|
+
kernel: { path: DIRS.kernel, label: "\u5185\u6838\u76EE\u5F55" }
|
|
39
|
+
};
|
|
40
|
+
function ensureDirs() {
|
|
41
|
+
for (const dir of Object.values(DIRS)) {
|
|
42
|
+
if (!fs.existsSync(dir)) {
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function fsExistsSync(p) {
|
|
48
|
+
return fs.existsSync(p);
|
|
49
|
+
}
|
|
50
|
+
function rmrf(dir) {
|
|
51
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/process.ts
|
|
55
|
+
import { execSync as execSync3, spawn } from "child_process";
|
|
5
56
|
import fs5 from "fs";
|
|
6
57
|
import path3 from "path";
|
|
7
58
|
|
|
59
|
+
// src/config.ts
|
|
60
|
+
import { execSync } from "child_process";
|
|
61
|
+
import fs4 from "fs";
|
|
62
|
+
|
|
8
63
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
9
64
|
function isNothing(subject) {
|
|
10
65
|
return typeof subject === "undefined" || subject === null;
|
|
@@ -2629,10 +2684,6 @@ var jsYaml = {
|
|
|
2629
2684
|
safeDump
|
|
2630
2685
|
};
|
|
2631
2686
|
|
|
2632
|
-
// src/config.ts
|
|
2633
|
-
import { execSync } from "child_process";
|
|
2634
|
-
import fs4 from "fs";
|
|
2635
|
-
|
|
2636
2687
|
// src/constants.ts
|
|
2637
2688
|
var AVAILABLE_MIRRORS = ["gh-proxy.org", "v6.gh-proxy.org", "hk.gh-proxy.org", "cdn.gh-proxy.org"];
|
|
2638
2689
|
var UI_URLS = {
|
|
@@ -2650,50 +2701,6 @@ var TUN_CONFIG = {
|
|
|
2650
2701
|
"strict-route": true
|
|
2651
2702
|
}
|
|
2652
2703
|
};
|
|
2653
|
-
function getFreeSubscriptionSources() {
|
|
2654
|
-
return [
|
|
2655
|
-
// 完整配置(ACL4SSR 29 组)
|
|
2656
|
-
{ name: "FreeSubsCheck", url: "https://gh-proxy.org/raw.githubusercontent.com/kooker/FreeSubsCheck/main/mihomo.yaml" },
|
|
2657
|
-
{ name: "yahr601", url: "https://gh-proxy.org/raw.githubusercontent.com/yahr601-prog/1/main/clash.yaml" },
|
|
2658
|
-
{ name: "Auto-Sync", url: "https://gh-proxy.org/raw.githubusercontent.com/walke2019/Auto-Sync/main/clash/GG/clash.yaml" },
|
|
2659
|
-
{ name: "ssrsub", url: "https://gh-proxy.org/raw.githubusercontent.com/ssrsub/ssr/master/clash.yaml" },
|
|
2660
|
-
// OpenAi → Ai平台
|
|
2661
|
-
{ name: "shaoyouvip", url: "https://gh-proxy.org/raw.githubusercontent.com/shaoyouvip/free/main/mihomo.yaml" },
|
|
2662
|
-
{ name: "dalazhi", url: "https://gh-proxy.org/raw.githubusercontent.com/dalazhi/v2ray/main/data/mihomo.yaml" },
|
|
2663
|
-
{ name: "getnode", url: "https://gh-proxy.org/raw.githubusercontent.com/limitless-d/getnode/main/clash.yaml" },
|
|
2664
|
-
// 完整配置(24 组)
|
|
2665
|
-
{ name: "freeSub", url: "https://gh-proxy.org/raw.githubusercontent.com/Ruk1ng001/freeSub/main/clash.yaml" },
|
|
2666
|
-
// 完整配置(13 组)
|
|
2667
|
-
{ name: "PuddinCat", url: "https://gh-proxy.org/raw.githubusercontent.com/PuddinCat/BestClash/refs/heads/main/proxies.yaml" },
|
|
2668
|
-
{ name: "cn-news", url: "https://gh-proxy.org/raw.githubusercontent.com/hello-world-1989/cn-news/refs/heads/main/clash.yaml" },
|
|
2669
|
-
// 基础分组(10-11 组)
|
|
2670
|
-
{ name: "naidounode", url: "https://gh-proxy.org/raw.githubusercontent.com/xiaoji235/airport-free/main/clash/naidounode.txt" },
|
|
2671
|
-
{ name: "v2rayshare", url: "https://gh-proxy.org/raw.githubusercontent.com/xiaoji235/airport-free/main/clash/v2rayshare.txt" },
|
|
2672
|
-
// 简单配置(2 组)
|
|
2673
|
-
{ name: "proxypool", url: "https://gh-proxy.org/raw.githubusercontent.com/snakem982/proxypool/main/source/clash-meta.yaml" },
|
|
2674
|
-
{ name: "chromego", url: "https://gh-proxy.org/raw.githubusercontent.com/Misaka-blog/chromego_merge/main/sub/merged_proxies_new.yaml" },
|
|
2675
|
-
// 纯节点列表
|
|
2676
|
-
{ name: "awesome-vpn", url: "https://gh-proxy.org/raw.githubusercontent.com/awesome-vpn/awesome-vpn/master/clash.yaml" },
|
|
2677
|
-
{ name: "V2RayAggregator", url: "https://gh-proxy.org/raw.githubusercontent.com/mahdibland/V2RayAggregator/master/Eternity.yml" },
|
|
2678
|
-
{ name: "Pawdroid", url: "https://gh-proxy.org/raw.githubusercontent.com/Pawdroid/Free-servers/main/sub" },
|
|
2679
|
-
{ name: "ermaozi", url: "https://gh-proxy.org/raw.githubusercontent.com/ermaozi/get_subscribe/main/subscribe/clash.yml" },
|
|
2680
|
-
{ name: "v2rayfree", url: "https://gh-proxy.org/raw.githubusercontent.com/v2raynnodes/v2rayfree/main/nodes/clashmeta.yaml" },
|
|
2681
|
-
{ name: "yudou66", url: "https://gh-proxy.org/raw.githubusercontent.com/Barabama/FreeNodes/main/nodes/yudou66.yaml" },
|
|
2682
|
-
{ name: "wenode", url: "https://gh-proxy.org/raw.githubusercontent.com/Barabama/FreeNodes/main/nodes/wenode.yaml" },
|
|
2683
|
-
{ name: "dongtai-sub", url: "https://gh-proxy.org/raw.githubusercontent.com/wenxig/dongtai-sub/refs/heads/main/data/sub.yaml" },
|
|
2684
|
-
{ name: "kasesm", url: "https://gh-proxy.org/raw.githubusercontent.com/kasesm/Free-Config/refs/heads/main/all_raw.txt" },
|
|
2685
|
-
{ name: "Au1rxx", url: "https://gh-proxy.org/raw.githubusercontent.com/Au1rxx/free-vpn-subscriptions/main/output/clash.yaml" },
|
|
2686
|
-
// 完整配置但需要完整版 GeoSite.dat(geosite-lite 不兼容, 22 组)
|
|
2687
|
-
{ name: "NoMoreWalls", url: "https://gh-proxy.org/raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.meta.yml" }
|
|
2688
|
-
];
|
|
2689
|
-
}
|
|
2690
|
-
var BENCH_CONFIG = {
|
|
2691
|
-
"mixed-port": 17890,
|
|
2692
|
-
"allow-lan": false,
|
|
2693
|
-
"external-controller": "127.0.0.1:19090",
|
|
2694
|
-
"log-level": "error",
|
|
2695
|
-
"geodata-mode": true
|
|
2696
|
-
};
|
|
2697
2704
|
var TEST_CONFIG = {
|
|
2698
2705
|
"mixed-port": 27890,
|
|
2699
2706
|
"allow-lan": false,
|
|
@@ -2726,57 +2733,6 @@ var BASE_CONFIG = {
|
|
|
2726
2733
|
import fs3 from "fs";
|
|
2727
2734
|
import path2 from "path";
|
|
2728
2735
|
|
|
2729
|
-
// src/paths.ts
|
|
2730
|
-
import fs from "fs";
|
|
2731
|
-
import os from "os";
|
|
2732
|
-
import path from "path";
|
|
2733
|
-
function getUserDataDir() {
|
|
2734
|
-
if (process.env.MIHOMO_CLI_DIR) {
|
|
2735
|
-
return process.env.MIHOMO_CLI_DIR;
|
|
2736
|
-
}
|
|
2737
|
-
return path.join(os.homedir(), ".mihomo-cli");
|
|
2738
|
-
}
|
|
2739
|
-
var USER_DATA_DIR = getUserDataDir();
|
|
2740
|
-
var DIRS = {
|
|
2741
|
-
kernel: path.join(USER_DATA_DIR, "kernel"),
|
|
2742
|
-
subscriptions: path.join(USER_DATA_DIR, "subscriptions"),
|
|
2743
|
-
logs: path.join(USER_DATA_DIR, "logs"),
|
|
2744
|
-
data: path.join(USER_DATA_DIR, "data"),
|
|
2745
|
-
runtime: path.join(USER_DATA_DIR, "runtime")
|
|
2746
|
-
};
|
|
2747
|
-
var PATHS = {
|
|
2748
|
-
mihomoBinary: path.join(DIRS.kernel, "mihomo"),
|
|
2749
|
-
settingsFile: path.join(USER_DATA_DIR, "settings.json"),
|
|
2750
|
-
subscriptionsCacheFile: path.join(DIRS.subscriptions, "cache.json"),
|
|
2751
|
-
configFile: path.join(DIRS.runtime, "config.yaml"),
|
|
2752
|
-
logFile: path.join(DIRS.logs, "mihomo.log"),
|
|
2753
|
-
pidFile: path.join(DIRS.runtime, "pid"),
|
|
2754
|
-
configStage1Subscription: path.join(DIRS.runtime, "1.subscription.yaml"),
|
|
2755
|
-
configStage2Overwrite: path.join(DIRS.runtime, "2.overwrite.yaml"),
|
|
2756
|
-
configStage3System: path.join(DIRS.runtime, "3.system.yaml")
|
|
2757
|
-
};
|
|
2758
|
-
var DIRECTORY_TARGETS = {
|
|
2759
|
-
root: { path: null, label: "\u6839\u76EE\u5F55" },
|
|
2760
|
-
subs: { path: DIRS.subscriptions, label: "\u8BA2\u9605\u76EE\u5F55" },
|
|
2761
|
-
logs: { path: DIRS.logs, label: "\u65E5\u5FD7\u76EE\u5F55" },
|
|
2762
|
-
data: { path: DIRS.data, label: "mihomo \u6570\u636E\u76EE\u5F55" },
|
|
2763
|
-
runtime: { path: DIRS.runtime, label: "\u8FD0\u884C\u65F6\u76EE\u5F55" },
|
|
2764
|
-
kernel: { path: DIRS.kernel, label: "\u5185\u6838\u76EE\u5F55" }
|
|
2765
|
-
};
|
|
2766
|
-
function ensureDirs() {
|
|
2767
|
-
for (const dir of Object.values(DIRS)) {
|
|
2768
|
-
if (!fs.existsSync(dir)) {
|
|
2769
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
function fsExistsSync(p) {
|
|
2774
|
-
return fs.existsSync(p);
|
|
2775
|
-
}
|
|
2776
|
-
function rmrf(dir) {
|
|
2777
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
2736
|
// src/settings.ts
|
|
2781
2737
|
import fs2 from "fs";
|
|
2782
2738
|
var settingsCache = null;
|
|
@@ -3366,18 +3322,6 @@ function formatDate(dateOrIso) {
|
|
|
3366
3322
|
return "\u672A\u77E5";
|
|
3367
3323
|
}
|
|
3368
3324
|
}
|
|
3369
|
-
function displayWidth(str2) {
|
|
3370
|
-
let w = 0;
|
|
3371
|
-
for (const ch of str2) {
|
|
3372
|
-
const code = ch.codePointAt(0);
|
|
3373
|
-
if (code >= 4352 && (code <= 4447 || code === 9001 || code === 9002 || code >= 11904 && code <= 42191 && code !== 12351 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 131072 && code <= 196605 || code >= 196608 && code <= 262141)) {
|
|
3374
|
-
w += 2;
|
|
3375
|
-
} else {
|
|
3376
|
-
w += 1;
|
|
3377
|
-
}
|
|
3378
|
-
}
|
|
3379
|
-
return w;
|
|
3380
|
-
}
|
|
3381
3325
|
function hasFlag(args, short, long) {
|
|
3382
3326
|
return !!args && (args.includes(short) || args.includes(long));
|
|
3383
3327
|
}
|
|
@@ -3495,325 +3439,7 @@ function isProxyValid(proxy) {
|
|
|
3495
3439
|
return true;
|
|
3496
3440
|
}
|
|
3497
3441
|
|
|
3498
|
-
// src/bench.ts
|
|
3499
|
-
var BENCH_DIR = path3.join(USER_DATA_DIR, "bench");
|
|
3500
|
-
var BENCH_DIRS = {
|
|
3501
|
-
data: path3.join(BENCH_DIR, "data"),
|
|
3502
|
-
runtime: path3.join(BENCH_DIR, "runtime")
|
|
3503
|
-
};
|
|
3504
|
-
var BENCH_PATHS = {
|
|
3505
|
-
configFile: path3.join(BENCH_DIRS.runtime, "config.yaml"),
|
|
3506
|
-
pidFile: path3.join(BENCH_DIRS.runtime, "pid"),
|
|
3507
|
-
logFile: path3.join(BENCH_DIR, "bench.log")
|
|
3508
|
-
};
|
|
3509
|
-
var BENCH_API = `http://${BENCH_CONFIG["external-controller"]}`;
|
|
3510
|
-
var BENCH_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
3511
|
-
function ensureBenchDirs() {
|
|
3512
|
-
for (const dir of Object.values(BENCH_DIRS)) {
|
|
3513
|
-
if (!fs5.existsSync(dir)) {
|
|
3514
|
-
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3515
|
-
}
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
|
-
function cleanupBenchDir() {
|
|
3519
|
-
if (fs5.existsSync(BENCH_DIR)) {
|
|
3520
|
-
fs5.rmSync(BENCH_DIR, { recursive: true, force: true });
|
|
3521
|
-
}
|
|
3522
|
-
}
|
|
3523
|
-
function tryDecodeBase64Content(content) {
|
|
3524
|
-
const trimmed = content.trim();
|
|
3525
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("proxies") || trimmed.includes("proxy-groups")) return null;
|
|
3526
|
-
try {
|
|
3527
|
-
const decoded = Buffer.from(trimmed, "base64").toString("utf8");
|
|
3528
|
-
if (decoded.includes("://")) return decoded;
|
|
3529
|
-
} catch {
|
|
3530
|
-
}
|
|
3531
|
-
return null;
|
|
3532
|
-
}
|
|
3533
|
-
function parseVmessUri(uri) {
|
|
3534
|
-
try {
|
|
3535
|
-
const b64 = uri.slice("vmess://".length);
|
|
3536
|
-
const json2 = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
3537
|
-
return {
|
|
3538
|
-
name: json2.ps || json2.add || "vmess",
|
|
3539
|
-
type: "vmess",
|
|
3540
|
-
server: json2.add,
|
|
3541
|
-
port: Number(json2.port),
|
|
3542
|
-
uuid: json2.id,
|
|
3543
|
-
alterId: Number(json2.aid) || 0,
|
|
3544
|
-
cipher: json2.security || "auto",
|
|
3545
|
-
tls: json2.tls === "tls",
|
|
3546
|
-
network: json2.net || "tcp",
|
|
3547
|
-
...json2.net === "ws" && { "ws-opts": { path: json2.path || "/", headers: json2.host ? { Host: json2.host } : void 0 } }
|
|
3548
|
-
};
|
|
3549
|
-
} catch {
|
|
3550
|
-
return null;
|
|
3551
|
-
}
|
|
3552
|
-
}
|
|
3553
|
-
function parseSsUri(uri) {
|
|
3554
|
-
try {
|
|
3555
|
-
const hashIdx = uri.indexOf("#");
|
|
3556
|
-
const name = hashIdx >= 0 ? decodeURIComponent(uri.slice(hashIdx + 1)) : "ss";
|
|
3557
|
-
const main2 = uri.slice("ss://".length, hashIdx >= 0 ? hashIdx : void 0);
|
|
3558
|
-
let decoded;
|
|
3559
|
-
const atIdx = main2.indexOf("@");
|
|
3560
|
-
if (atIdx >= 0) {
|
|
3561
|
-
const methodPassword2 = Buffer.from(main2.slice(0, atIdx), "base64").toString("utf8");
|
|
3562
|
-
decoded = `${methodPassword2}@${main2.slice(atIdx + 1)}`;
|
|
3563
|
-
} else {
|
|
3564
|
-
decoded = Buffer.from(main2, "base64").toString("utf8");
|
|
3565
|
-
}
|
|
3566
|
-
const [methodPassword, serverPort] = decoded.split("@");
|
|
3567
|
-
if (!methodPassword || !serverPort) return null;
|
|
3568
|
-
const colonIdx = methodPassword.indexOf(":");
|
|
3569
|
-
const method = methodPassword.slice(0, colonIdx);
|
|
3570
|
-
const password = methodPassword.slice(colonIdx + 1);
|
|
3571
|
-
const lastColon = serverPort.lastIndexOf(":");
|
|
3572
|
-
const server = serverPort.slice(0, lastColon);
|
|
3573
|
-
const port = Number(serverPort.slice(lastColon + 1));
|
|
3574
|
-
return { name, type: "ss", server, port, cipher: method, password };
|
|
3575
|
-
} catch {
|
|
3576
|
-
return null;
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
function parseTrojanUri(uri) {
|
|
3580
|
-
try {
|
|
3581
|
-
const hashIdx = uri.indexOf("#");
|
|
3582
|
-
const name = hashIdx >= 0 ? decodeURIComponent(uri.slice(hashIdx + 1)) : "trojan";
|
|
3583
|
-
const main2 = uri.slice("trojan://".length, hashIdx >= 0 ? hashIdx : void 0);
|
|
3584
|
-
const atIdx = main2.indexOf("@");
|
|
3585
|
-
if (atIdx < 0) return null;
|
|
3586
|
-
const password = main2.slice(0, atIdx);
|
|
3587
|
-
const rest = main2.slice(atIdx + 1).split("?")[0];
|
|
3588
|
-
const lastColon = rest.lastIndexOf(":");
|
|
3589
|
-
const server = rest.slice(0, lastColon);
|
|
3590
|
-
const port = Number(rest.slice(lastColon + 1));
|
|
3591
|
-
return { name, type: "trojan", server, port, password, sni: server };
|
|
3592
|
-
} catch {
|
|
3593
|
-
return null;
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
function parseProxyUris(content) {
|
|
3597
|
-
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3598
|
-
const proxies = [];
|
|
3599
|
-
for (const line of lines) {
|
|
3600
|
-
let proxy = null;
|
|
3601
|
-
if (line.startsWith("vmess://")) proxy = parseVmessUri(line);
|
|
3602
|
-
else if (line.startsWith("ss://")) proxy = parseSsUri(line);
|
|
3603
|
-
else if (line.startsWith("trojan://")) proxy = parseTrojanUri(line);
|
|
3604
|
-
if (proxy?.name && proxy?.server) proxies.push(proxy);
|
|
3605
|
-
}
|
|
3606
|
-
return proxies;
|
|
3607
|
-
}
|
|
3608
|
-
async function downloadAllSources(sources, onProgress) {
|
|
3609
|
-
const client = createHttpClient({ timeout: 3e4 });
|
|
3610
|
-
const tasks = sources.map(async (source) => {
|
|
3611
|
-
const entry = { name: source.name, url: source.url, proxies: [], proxyGroups: 0 };
|
|
3612
|
-
try {
|
|
3613
|
-
const response = await client.get(source.url, { responseType: "text" });
|
|
3614
|
-
const content = response.data;
|
|
3615
|
-
if (!content?.trim()) throw new Error("\u5185\u5BB9\u4E3A\u7A7A");
|
|
3616
|
-
let proxies;
|
|
3617
|
-
try {
|
|
3618
|
-
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
3619
|
-
proxies = parsed.proxies || [];
|
|
3620
|
-
const groups = parsed["proxy-groups"];
|
|
3621
|
-
if (groups) entry.proxyGroups = groups.length;
|
|
3622
|
-
} catch {
|
|
3623
|
-
const decoded = tryDecodeBase64Content(content);
|
|
3624
|
-
if (decoded) {
|
|
3625
|
-
proxies = parseProxyUris(decoded);
|
|
3626
|
-
} else {
|
|
3627
|
-
proxies = parseProxyUris(content);
|
|
3628
|
-
}
|
|
3629
|
-
if (proxies.length === 0) throw new Error("\u65E0\u6CD5\u89E3\u6790\u8BA2\u9605\u5185\u5BB9\uFF08\u975E YAML/JSON/Base64\uFF09");
|
|
3630
|
-
}
|
|
3631
|
-
entry.proxies = proxies.map((p) => ({ ...p, name: `[${source.name}] ${p.name}` }));
|
|
3632
|
-
onProgress?.(source.name, true, proxies.length, entry.proxyGroups);
|
|
3633
|
-
} catch (e) {
|
|
3634
|
-
entry.error = e.message;
|
|
3635
|
-
onProgress?.(source.name, false, 0, 0, entry.error);
|
|
3636
|
-
}
|
|
3637
|
-
return entry;
|
|
3638
|
-
});
|
|
3639
|
-
return await Promise.all(tasks);
|
|
3640
|
-
}
|
|
3641
|
-
function buildMergedBenchConfig(allProxies) {
|
|
3642
|
-
ensureBenchDirs();
|
|
3643
|
-
const validProxies = allProxies.filter(isProxyValid);
|
|
3644
|
-
const removed = allProxies.length - validProxies.length;
|
|
3645
|
-
const nameCount = /* @__PURE__ */ new Map();
|
|
3646
|
-
for (const proxy of validProxies) {
|
|
3647
|
-
const originalName = proxy.name;
|
|
3648
|
-
const count = (nameCount.get(originalName) || 0) + 1;
|
|
3649
|
-
nameCount.set(originalName, count);
|
|
3650
|
-
if (count > 1) {
|
|
3651
|
-
proxy.name = `${originalName} #${count}`;
|
|
3652
|
-
}
|
|
3653
|
-
}
|
|
3654
|
-
const config = {
|
|
3655
|
-
...BENCH_CONFIG,
|
|
3656
|
-
proxies: validProxies,
|
|
3657
|
-
"proxy-groups": [
|
|
3658
|
-
{
|
|
3659
|
-
name: "PROXY",
|
|
3660
|
-
type: "select",
|
|
3661
|
-
proxies: validProxies.map((p) => p.name)
|
|
3662
|
-
}
|
|
3663
|
-
],
|
|
3664
|
-
rules: ["MATCH,PROXY"]
|
|
3665
|
-
};
|
|
3666
|
-
const content = jsYaml.dump(config, { indent: 2, lineWidth: -1, noCompatMode: true });
|
|
3667
|
-
fs5.writeFileSync(BENCH_PATHS.configFile, content, { mode: 384 });
|
|
3668
|
-
allProxies.length = 0;
|
|
3669
|
-
allProxies.push(...validProxies);
|
|
3670
|
-
return removed;
|
|
3671
|
-
}
|
|
3672
|
-
async function startBenchInstance() {
|
|
3673
|
-
const binary2 = PATHS.mihomoBinary;
|
|
3674
|
-
if (!fs5.existsSync(binary2)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
|
|
3675
|
-
const logFd = fs5.openSync(BENCH_PATHS.logFile, "a");
|
|
3676
|
-
const child = spawn(binary2, ["-d", BENCH_DIRS.data, "-f", BENCH_PATHS.configFile], {
|
|
3677
|
-
detached: true,
|
|
3678
|
-
stdio: ["ignore", logFd, logFd]
|
|
3679
|
-
});
|
|
3680
|
-
fs5.closeSync(logFd);
|
|
3681
|
-
child.unref();
|
|
3682
|
-
const pid = child.pid;
|
|
3683
|
-
fs5.writeFileSync(BENCH_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
3684
|
-
const client = createHttpClient({ timeout: 2e3 });
|
|
3685
|
-
let ready = false;
|
|
3686
|
-
for (let i = 0; i < 60; i++) {
|
|
3687
|
-
await sleep(500);
|
|
3688
|
-
if (!isProcessRunning(pid)) break;
|
|
3689
|
-
try {
|
|
3690
|
-
await client.get(`${BENCH_API}/version`);
|
|
3691
|
-
ready = true;
|
|
3692
|
-
break;
|
|
3693
|
-
} catch {
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
if (!isProcessRunning(pid)) {
|
|
3697
|
-
let errorDetail = "";
|
|
3698
|
-
if (fs5.existsSync(BENCH_PATHS.logFile)) {
|
|
3699
|
-
try {
|
|
3700
|
-
errorDetail = fs5.readFileSync(BENCH_PATHS.logFile, "utf8").slice(-1e3);
|
|
3701
|
-
} catch {
|
|
3702
|
-
}
|
|
3703
|
-
}
|
|
3704
|
-
throw new Error(`bench \u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
3705
|
-
${errorDetail}` : ""}`);
|
|
3706
|
-
}
|
|
3707
|
-
if (!ready) {
|
|
3708
|
-
throw new Error("bench \u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
3709
|
-
}
|
|
3710
|
-
return pid;
|
|
3711
|
-
}
|
|
3712
|
-
function stopBenchInstance() {
|
|
3713
|
-
if (!fs5.existsSync(BENCH_PATHS.pidFile)) return;
|
|
3714
|
-
try {
|
|
3715
|
-
const pid = parseInt(fs5.readFileSync(BENCH_PATHS.pidFile, "utf8").trim(), 10);
|
|
3716
|
-
if (pid > 0 && isProcessRunning(pid)) {
|
|
3717
|
-
process.kill(pid, "SIGKILL");
|
|
3718
|
-
for (let i = 0; i < 20; i++) {
|
|
3719
|
-
if (!isProcessRunning(pid)) break;
|
|
3720
|
-
sleepSync(100);
|
|
3721
|
-
}
|
|
3722
|
-
}
|
|
3723
|
-
} catch {
|
|
3724
|
-
}
|
|
3725
|
-
try {
|
|
3726
|
-
fs5.unlinkSync(BENCH_PATHS.pidFile);
|
|
3727
|
-
} catch {
|
|
3728
|
-
}
|
|
3729
|
-
}
|
|
3730
|
-
async function testBenchProxy(proxyName, timeout, client) {
|
|
3731
|
-
const encodedName = encodeURIComponent(proxyName);
|
|
3732
|
-
const url = `${BENCH_API}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(BENCH_TEST_URL)}`;
|
|
3733
|
-
try {
|
|
3734
|
-
const response = await client.get(url);
|
|
3735
|
-
const data = JSON.parse(response.data);
|
|
3736
|
-
if (data.delay && data.delay > 0) {
|
|
3737
|
-
return { name: proxyName, delay: data.delay };
|
|
3738
|
-
}
|
|
3739
|
-
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
3740
|
-
} catch (e) {
|
|
3741
|
-
const err = e;
|
|
3742
|
-
let errorMsg = "timeout";
|
|
3743
|
-
if (err.response?.data?.message) {
|
|
3744
|
-
errorMsg = String(err.response.data.message);
|
|
3745
|
-
} else if (err.message) {
|
|
3746
|
-
errorMsg = err.message;
|
|
3747
|
-
}
|
|
3748
|
-
return { name: proxyName, delay: null, error: errorMsg };
|
|
3749
|
-
}
|
|
3750
|
-
}
|
|
3751
|
-
async function testBenchProxies(proxyNames, options = {}) {
|
|
3752
|
-
const { timeout = 3e3, concurrency = 100, onResult, onBatch } = options;
|
|
3753
|
-
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
3754
|
-
const results = [];
|
|
3755
|
-
let completedCount = 0;
|
|
3756
|
-
let aliveCount = 0;
|
|
3757
|
-
const delays = [];
|
|
3758
|
-
const totalBatches = Math.ceil(proxyNames.length / concurrency);
|
|
3759
|
-
for (let i = 0; i < proxyNames.length; i += concurrency) {
|
|
3760
|
-
const batch = proxyNames.slice(i, i + concurrency);
|
|
3761
|
-
const batchResults = await Promise.all(batch.map((name) => testBenchProxy(name, timeout, client)));
|
|
3762
|
-
for (const result of batchResults) {
|
|
3763
|
-
results.push(result);
|
|
3764
|
-
if (result.delay !== null) {
|
|
3765
|
-
aliveCount++;
|
|
3766
|
-
delays.push(result.delay);
|
|
3767
|
-
}
|
|
3768
|
-
onResult?.(result, completedCount, proxyNames.length);
|
|
3769
|
-
completedCount++;
|
|
3770
|
-
}
|
|
3771
|
-
delays.sort((a, b) => a - b);
|
|
3772
|
-
const median = delays.length > 0 ? delays[Math.floor(delays.length / 2)] : 0;
|
|
3773
|
-
onBatch?.(Math.floor(i / concurrency) + 1, totalBatches, aliveCount, completedCount, median);
|
|
3774
|
-
}
|
|
3775
|
-
return results;
|
|
3776
|
-
}
|
|
3777
|
-
function computeSourceResult(source, resultsByName) {
|
|
3778
|
-
const proxyNames = source.proxies.map((p) => p.name);
|
|
3779
|
-
const sourceResults = proxyNames.map((n) => resultsByName.get(n)).filter((r) => r !== void 0);
|
|
3780
|
-
const delays = sourceResults.filter((r) => r.delay !== null).map((r) => r.delay);
|
|
3781
|
-
const alive = delays.length;
|
|
3782
|
-
const dead = sourceResults.length - alive;
|
|
3783
|
-
if (alive === 0) {
|
|
3784
|
-
return {
|
|
3785
|
-
name: source.name,
|
|
3786
|
-
url: source.url,
|
|
3787
|
-
downloadOk: !source.error,
|
|
3788
|
-
downloadError: source.error,
|
|
3789
|
-
totalProxies: source.proxies.length,
|
|
3790
|
-
proxyGroups: source.proxyGroups,
|
|
3791
|
-
alive: 0,
|
|
3792
|
-
dead,
|
|
3793
|
-
avgDelay: 0,
|
|
3794
|
-
minDelay: 0,
|
|
3795
|
-
medianDelay: 0
|
|
3796
|
-
};
|
|
3797
|
-
}
|
|
3798
|
-
delays.sort((a, b) => a - b);
|
|
3799
|
-
return {
|
|
3800
|
-
name: source.name,
|
|
3801
|
-
url: source.url,
|
|
3802
|
-
downloadOk: true,
|
|
3803
|
-
totalProxies: source.proxies.length,
|
|
3804
|
-
proxyGroups: source.proxyGroups,
|
|
3805
|
-
alive,
|
|
3806
|
-
dead,
|
|
3807
|
-
avgDelay: Math.round(delays.reduce((sum, d) => sum + d, 0) / alive),
|
|
3808
|
-
minDelay: delays[0],
|
|
3809
|
-
medianDelay: delays[Math.floor(delays.length / 2)]
|
|
3810
|
-
};
|
|
3811
|
-
}
|
|
3812
|
-
|
|
3813
3442
|
// src/process.ts
|
|
3814
|
-
import { execSync as execSync3, spawn as spawn2 } from "child_process";
|
|
3815
|
-
import fs6 from "fs";
|
|
3816
|
-
import path4 from "path";
|
|
3817
3443
|
var PROCESS_WAIT_ATTEMPTS = 50;
|
|
3818
3444
|
var PROCESS_WAIT_INTERVAL = 100;
|
|
3819
3445
|
var STARTUP_WAIT_MS = 800;
|
|
@@ -3822,15 +3448,15 @@ var TUN_MODE_POST_WAIT_MS = 500;
|
|
|
3822
3448
|
var BATCH_KILL_THRESHOLD = 3;
|
|
3823
3449
|
var DEFAULT_LOG_RETENTION_DAYS = 7;
|
|
3824
3450
|
function clearRuntime() {
|
|
3825
|
-
if (
|
|
3451
|
+
if (fs5.existsSync(DIRS.runtime)) {
|
|
3826
3452
|
rmrf(DIRS.runtime);
|
|
3827
3453
|
}
|
|
3828
3454
|
ensureDirs();
|
|
3829
3455
|
}
|
|
3830
3456
|
function getPid() {
|
|
3831
|
-
if (!
|
|
3457
|
+
if (!fs5.existsSync(PATHS.pidFile)) return null;
|
|
3832
3458
|
try {
|
|
3833
|
-
const pid = parseInt(
|
|
3459
|
+
const pid = parseInt(fs5.readFileSync(PATHS.pidFile, "utf8").trim(), 10);
|
|
3834
3460
|
return pid > 0 ? pid : null;
|
|
3835
3461
|
} catch {
|
|
3836
3462
|
return null;
|
|
@@ -3851,9 +3477,9 @@ function getAllMihomoPids() {
|
|
|
3851
3477
|
}
|
|
3852
3478
|
}
|
|
3853
3479
|
function isPidFileOwnedByRoot() {
|
|
3854
|
-
if (!
|
|
3480
|
+
if (!fs5.existsSync(PATHS.pidFile)) return false;
|
|
3855
3481
|
try {
|
|
3856
|
-
const stat =
|
|
3482
|
+
const stat = fs5.statSync(PATHS.pidFile);
|
|
3857
3483
|
return stat.uid === 0;
|
|
3858
3484
|
} catch {
|
|
3859
3485
|
return false;
|
|
@@ -3873,10 +3499,10 @@ function checkStaleState() {
|
|
|
3873
3499
|
}
|
|
3874
3500
|
function savePid(pid) {
|
|
3875
3501
|
ensureDirs();
|
|
3876
|
-
|
|
3502
|
+
fs5.writeFileSync(PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
3877
3503
|
}
|
|
3878
3504
|
function clearPid() {
|
|
3879
|
-
if (!
|
|
3505
|
+
if (!fs5.existsSync(PATHS.pidFile)) return;
|
|
3880
3506
|
if (isPidFileOwnedByRoot()) {
|
|
3881
3507
|
try {
|
|
3882
3508
|
execSync3(`sudo rm -f "${PATHS.pidFile}" 2>/dev/null`, { stdio: "inherit", timeout: 1e4 });
|
|
@@ -3884,7 +3510,7 @@ function clearPid() {
|
|
|
3884
3510
|
}
|
|
3885
3511
|
} else {
|
|
3886
3512
|
try {
|
|
3887
|
-
|
|
3513
|
+
fs5.unlinkSync(PATHS.pidFile);
|
|
3888
3514
|
} catch {
|
|
3889
3515
|
}
|
|
3890
3516
|
}
|
|
@@ -4009,8 +3635,8 @@ echo "--- \u65E5\u5FD7 ---"
|
|
|
4009
3635
|
tail -25 "\${LOG_FILE}" 2>/dev/null
|
|
4010
3636
|
exit 1
|
|
4011
3637
|
`;
|
|
4012
|
-
const scriptPath =
|
|
4013
|
-
|
|
3638
|
+
const scriptPath = path3.join(DIRS.runtime, "launch-tun.sh");
|
|
3639
|
+
fs5.writeFileSync(scriptPath, scriptContent, { mode: 448 });
|
|
4014
3640
|
return scriptPath;
|
|
4015
3641
|
}
|
|
4016
3642
|
function getProcessInfo(pid) {
|
|
@@ -4051,11 +3677,11 @@ async function start(mode = "mixed") {
|
|
|
4051
3677
|
ensureDirs();
|
|
4052
3678
|
rotateAndCleanupLogs();
|
|
4053
3679
|
const binary2 = PATHS.mihomoBinary;
|
|
4054
|
-
if (!
|
|
3680
|
+
if (!fs5.existsSync(binary2)) {
|
|
4055
3681
|
throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838\uFF0C\u8BF7\u5148\u4E0B\u8F7D\u5185\u6838");
|
|
4056
3682
|
}
|
|
4057
3683
|
const configFile = PATHS.configFile;
|
|
4058
|
-
if (!
|
|
3684
|
+
if (!fs5.existsSync(configFile)) {
|
|
4059
3685
|
throw new Error("\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605\u5E76\u542F\u52A8");
|
|
4060
3686
|
}
|
|
4061
3687
|
const staleState = checkStaleState();
|
|
@@ -4084,12 +3710,12 @@ async function startMixedMode(staleState) {
|
|
|
4084
3710
|
const configFile = PATHS.configFile;
|
|
4085
3711
|
const logFile = PATHS.logFile;
|
|
4086
3712
|
const args = ["-d", DIRS.data, "-f", configFile];
|
|
4087
|
-
const logFd =
|
|
4088
|
-
const child =
|
|
3713
|
+
const logFd = fs5.openSync(logFile, "a");
|
|
3714
|
+
const child = spawn(PATHS.mihomoBinary, args, {
|
|
4089
3715
|
detached: true,
|
|
4090
3716
|
stdio: ["ignore", logFd, logFd]
|
|
4091
3717
|
});
|
|
4092
|
-
|
|
3718
|
+
fs5.closeSync(logFd);
|
|
4093
3719
|
child.unref();
|
|
4094
3720
|
const pid = child.pid;
|
|
4095
3721
|
savePid(pid);
|
|
@@ -4097,9 +3723,9 @@ async function startMixedMode(staleState) {
|
|
|
4097
3723
|
if (!isRunning()) {
|
|
4098
3724
|
clearPid();
|
|
4099
3725
|
let errorMsg = "\u542F\u52A8\u5931\u8D25";
|
|
4100
|
-
if (
|
|
3726
|
+
if (fs5.existsSync(logFile)) {
|
|
4101
3727
|
try {
|
|
4102
|
-
const logs =
|
|
3728
|
+
const logs = fs5.readFileSync(logFile, "utf8").slice(-3e3);
|
|
4103
3729
|
if (logs.trim()) {
|
|
4104
3730
|
errorMsg += "\n\u6700\u8FD1\u7684\u65E5\u5FD7:\n" + logs.split("\n").map((l) => ` ${l}`).join("\n");
|
|
4105
3731
|
}
|
|
@@ -4120,7 +3746,7 @@ async function startTunMode(staleState) {
|
|
|
4120
3746
|
execSync3(`sudo "${launchScript}"`, { stdio: "inherit", timeout: SUDO_TIMEOUT_MS });
|
|
4121
3747
|
} catch (e) {
|
|
4122
3748
|
try {
|
|
4123
|
-
|
|
3749
|
+
fs5.unlinkSync(launchScript);
|
|
4124
3750
|
} catch {
|
|
4125
3751
|
}
|
|
4126
3752
|
if (e.status === 1) {
|
|
@@ -4129,7 +3755,7 @@ async function startTunMode(staleState) {
|
|
|
4129
3755
|
throw new Error(e.message);
|
|
4130
3756
|
}
|
|
4131
3757
|
try {
|
|
4132
|
-
|
|
3758
|
+
fs5.unlinkSync(launchScript);
|
|
4133
3759
|
} catch {
|
|
4134
3760
|
}
|
|
4135
3761
|
await new Promise((resolve) => setTimeout(resolve, TUN_MODE_POST_WAIT_MS));
|
|
@@ -4168,19 +3794,19 @@ function getLogPath() {
|
|
|
4168
3794
|
}
|
|
4169
3795
|
function rotateLog() {
|
|
4170
3796
|
const logFile = PATHS.logFile;
|
|
4171
|
-
if (!
|
|
4172
|
-
const stat =
|
|
3797
|
+
if (!fs5.existsSync(logFile)) return null;
|
|
3798
|
+
const stat = fs5.statSync(logFile);
|
|
4173
3799
|
if (stat.size === 0) return null;
|
|
4174
3800
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/T/, "_").replace(/:/g, "-").replace(/\..+/, "");
|
|
4175
3801
|
const rotatedName = `mihomo.${timestamp2}.log`;
|
|
4176
|
-
const rotatedPath =
|
|
4177
|
-
|
|
3802
|
+
const rotatedPath = path3.join(DIRS.logs, rotatedName);
|
|
3803
|
+
fs5.renameSync(logFile, rotatedPath);
|
|
4178
3804
|
return rotatedPath;
|
|
4179
3805
|
}
|
|
4180
3806
|
function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
4181
3807
|
const logsDir = DIRS.logs;
|
|
4182
|
-
if (!
|
|
4183
|
-
const files =
|
|
3808
|
+
if (!fs5.existsSync(logsDir)) return { deleted: 0, errors: 0 };
|
|
3809
|
+
const files = fs5.readdirSync(logsDir);
|
|
4184
3810
|
const now = Date.now();
|
|
4185
3811
|
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
4186
3812
|
let deleted = 0;
|
|
@@ -4188,10 +3814,10 @@ function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
|
4188
3814
|
for (const file of files) {
|
|
4189
3815
|
if (!file.match(/^mihomo\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.log$/)) continue;
|
|
4190
3816
|
try {
|
|
4191
|
-
const filePath =
|
|
4192
|
-
const stat =
|
|
3817
|
+
const filePath = path3.join(logsDir, file);
|
|
3818
|
+
const stat = fs5.statSync(filePath);
|
|
4193
3819
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
4194
|
-
|
|
3820
|
+
fs5.unlinkSync(filePath);
|
|
4195
3821
|
deleted++;
|
|
4196
3822
|
}
|
|
4197
3823
|
} catch {
|
|
@@ -4203,8 +3829,8 @@ function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
|
4203
3829
|
function listLogs() {
|
|
4204
3830
|
const logsDir = DIRS.logs;
|
|
4205
3831
|
const result = { current: null, archives: [] };
|
|
4206
|
-
if (
|
|
4207
|
-
const stat =
|
|
3832
|
+
if (fs5.existsSync(PATHS.logFile)) {
|
|
3833
|
+
const stat = fs5.statSync(PATHS.logFile);
|
|
4208
3834
|
result.current = {
|
|
4209
3835
|
name: "mihomo.log (\u5F53\u524D)",
|
|
4210
3836
|
path: PATHS.logFile,
|
|
@@ -4213,14 +3839,14 @@ function listLogs() {
|
|
|
4213
3839
|
isCurrent: true
|
|
4214
3840
|
};
|
|
4215
3841
|
}
|
|
4216
|
-
if (!
|
|
4217
|
-
const files =
|
|
3842
|
+
if (!fs5.existsSync(logsDir)) return result;
|
|
3843
|
+
const files = fs5.readdirSync(logsDir);
|
|
4218
3844
|
for (const file of files) {
|
|
4219
3845
|
const match = file.match(/^mihomo\.(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.log$/);
|
|
4220
3846
|
if (!match) continue;
|
|
4221
3847
|
try {
|
|
4222
|
-
const filePath =
|
|
4223
|
-
const stat =
|
|
3848
|
+
const filePath = path3.join(logsDir, file);
|
|
3849
|
+
const stat = fs5.statSync(filePath);
|
|
4224
3850
|
result.archives.push({
|
|
4225
3851
|
name: file,
|
|
4226
3852
|
path: filePath,
|
|
@@ -4235,22 +3861,22 @@ function listLogs() {
|
|
|
4235
3861
|
return result;
|
|
4236
3862
|
}
|
|
4237
3863
|
function isPathUnderDir(filePath, baseDir) {
|
|
4238
|
-
const resolvedPath =
|
|
4239
|
-
const resolvedBase =
|
|
4240
|
-
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase +
|
|
3864
|
+
const resolvedPath = path3.resolve(filePath);
|
|
3865
|
+
const resolvedBase = path3.resolve(baseDir);
|
|
3866
|
+
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + path3.sep);
|
|
4241
3867
|
}
|
|
4242
3868
|
function getLogPathByName(name) {
|
|
4243
3869
|
const logsDir = DIRS.logs;
|
|
4244
3870
|
let targetName = name;
|
|
4245
3871
|
if (!name.endsWith(".log")) targetName = `mihomo.${name}.log`;
|
|
4246
3872
|
if (!targetName.startsWith("mihomo.")) targetName = `mihomo.${targetName}`;
|
|
4247
|
-
const filePath =
|
|
4248
|
-
if (
|
|
4249
|
-
if (
|
|
4250
|
-
const files =
|
|
3873
|
+
const filePath = path3.join(logsDir, targetName);
|
|
3874
|
+
if (fs5.existsSync(filePath) && isPathUnderDir(filePath, logsDir)) return filePath;
|
|
3875
|
+
if (fs5.existsSync(logsDir)) {
|
|
3876
|
+
const files = fs5.readdirSync(logsDir);
|
|
4251
3877
|
for (const file of files) {
|
|
4252
3878
|
if (file.includes(name)) {
|
|
4253
|
-
const candidatePath =
|
|
3879
|
+
const candidatePath = path3.join(logsDir, file);
|
|
4254
3880
|
if (isPathUnderDir(candidatePath, logsDir)) return candidatePath;
|
|
4255
3881
|
}
|
|
4256
3882
|
}
|
|
@@ -4259,7 +3885,7 @@ function getLogPathByName(name) {
|
|
|
4259
3885
|
}
|
|
4260
3886
|
function openUrl(url) {
|
|
4261
3887
|
try {
|
|
4262
|
-
const child =
|
|
3888
|
+
const child = spawn("open", [url], { stdio: "ignore", detached: true });
|
|
4263
3889
|
child.unref();
|
|
4264
3890
|
child.on("error", () => {
|
|
4265
3891
|
});
|
|
@@ -4290,7 +3916,7 @@ function viewLogWithTail(logPath, options) {
|
|
|
4290
3916
|
if (follow) tailArgs.push("-f");
|
|
4291
3917
|
tailArgs.push("-n", lines.toString());
|
|
4292
3918
|
tailArgs.push(logPath);
|
|
4293
|
-
const tail =
|
|
3919
|
+
const tail = spawn("tail", tailArgs, { stdio: "inherit" });
|
|
4294
3920
|
tail.on("close", () => process.exit(0));
|
|
4295
3921
|
tail.on("error", (e) => {
|
|
4296
3922
|
console.error(`\u65E0\u6CD5\u8BFB\u53D6\u65E5\u5FD7: ${e.message}`);
|
|
@@ -4298,1719 +3924,1615 @@ function viewLogWithTail(logPath, options) {
|
|
|
4298
3924
|
});
|
|
4299
3925
|
}
|
|
4300
3926
|
|
|
4301
|
-
// src/
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
}
|
|
4311
|
-
function loadSubscriptionConfig(subName) {
|
|
4312
|
-
const rawContent = readSubscriptionRawConfig(subName);
|
|
4313
|
-
if (!rawContent) {
|
|
4314
|
-
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
4315
|
-
}
|
|
4316
|
-
const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4317
|
-
return {
|
|
4318
|
-
raw,
|
|
4319
|
-
proxies: raw.proxies || [],
|
|
4320
|
-
proxyGroups: raw["proxy-groups"] || []
|
|
4321
|
-
};
|
|
4322
|
-
}
|
|
4323
|
-
function saveSubscriptionConfig(subName, parsed) {
|
|
4324
|
-
normalizeProxyNamesBeforeSave(parsed);
|
|
4325
|
-
parsed.raw.proxies = parsed.proxies;
|
|
4326
|
-
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4327
|
-
saveSubscriptionRawConfig(subName, jsYaml.dump(parsed.raw, YAML_DUMP_OPTS));
|
|
4328
|
-
}
|
|
4329
|
-
function parseUserInfo(header) {
|
|
4330
|
-
if (!header) return null;
|
|
4331
|
-
const info = {};
|
|
4332
|
-
const parts = header.split(";").map((p) => p.trim());
|
|
4333
|
-
for (const part of parts) {
|
|
4334
|
-
const [key, val] = part.split("=").map((s) => s.trim());
|
|
4335
|
-
if (key && val !== void 0) {
|
|
4336
|
-
const numVal = parseFloat(val);
|
|
4337
|
-
info[key] = Number.isNaN(numVal) ? 0 : numVal;
|
|
4338
|
-
}
|
|
4339
|
-
}
|
|
4340
|
-
return info;
|
|
4341
|
-
}
|
|
4342
|
-
function parseUsernameFromContentDisposition(header) {
|
|
4343
|
-
if (!header) return null;
|
|
4344
|
-
const match = header.match(/filename\s*=\s*["']?([^"';\s]+)["']?/i);
|
|
4345
|
-
if (!match) return null;
|
|
4346
|
-
const filename = match[1];
|
|
4347
|
-
const parts = filename.split("/");
|
|
4348
|
-
return parts[parts.length - 1] || null;
|
|
4349
|
-
}
|
|
4350
|
-
function formatProxySummary(info) {
|
|
4351
|
-
const parts = [];
|
|
4352
|
-
if (info.proxyGroups && info.proxyGroups > 0) parts.push(`${info.proxyGroups} \u7EC4`);
|
|
4353
|
-
parts.push(`${info.proxies || 0} \u8282\u70B9`);
|
|
4354
|
-
return parts.join(", ");
|
|
4355
|
-
}
|
|
4356
|
-
function getActiveSubscription() {
|
|
4357
|
-
const subs = getSubscriptions();
|
|
4358
|
-
if (subs.length === 0) return null;
|
|
4359
|
-
const settings = readSettings();
|
|
4360
|
-
const activeName = settings.active_subscription;
|
|
4361
|
-
if (activeName) {
|
|
4362
|
-
const found = subs.find((s) => s.name === activeName);
|
|
4363
|
-
if (found) return found;
|
|
4364
|
-
}
|
|
4365
|
-
return subs[0];
|
|
4366
|
-
}
|
|
4367
|
-
function findSubscriptionFuzzy(subs, pattern) {
|
|
4368
|
-
const lowerPattern = pattern.toLowerCase();
|
|
4369
|
-
const exact = [];
|
|
4370
|
-
const prefix = [];
|
|
4371
|
-
const includes = [];
|
|
4372
|
-
for (const s of subs) {
|
|
4373
|
-
const name = s.name.toLowerCase();
|
|
4374
|
-
if (name === lowerPattern) {
|
|
4375
|
-
exact.push(s);
|
|
4376
|
-
} else if (name.startsWith(lowerPattern)) {
|
|
4377
|
-
prefix.push(s);
|
|
4378
|
-
} else if (name.includes(lowerPattern)) {
|
|
4379
|
-
includes.push(s);
|
|
4380
|
-
}
|
|
4381
|
-
}
|
|
4382
|
-
if (exact.length > 0) return exact;
|
|
4383
|
-
if (prefix.length > 0) return prefix;
|
|
4384
|
-
return includes;
|
|
4385
|
-
}
|
|
4386
|
-
function pickSingleSubscription(subs, pattern) {
|
|
4387
|
-
if (subs.length === 0) {
|
|
4388
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
|
|
4389
|
-
process.exit(1);
|
|
4390
|
-
}
|
|
4391
|
-
if (subs.length === 1) return subs[0];
|
|
4392
|
-
console.error("\u9519\u8BEF: \u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A");
|
|
4393
|
-
console.log("\n\u5339\u914D\u7684\u8BA2\u9605:");
|
|
4394
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
4395
|
-
process.exit(1);
|
|
4396
|
-
}
|
|
4397
|
-
async function downloadSubscription(url, subName = "default") {
|
|
4398
|
-
let response;
|
|
4399
|
-
try {
|
|
4400
|
-
response = await HTTP_CLIENT.get(url, { responseType: "text" });
|
|
4401
|
-
} catch (e) {
|
|
4402
|
-
const maskedUrl = maskUrl(url);
|
|
4403
|
-
let errorMsg = `\u83B7\u53D6\u8BA2\u9605\u5931\u8D25: ${e.message}`;
|
|
4404
|
-
const err = e;
|
|
4405
|
-
if (err.response) {
|
|
4406
|
-
errorMsg += ` (HTTP ${err.response.status})`;
|
|
4407
|
-
}
|
|
4408
|
-
errorMsg += `
|
|
4409
|
-
URL: ${maskedUrl}`;
|
|
4410
|
-
throw new Error(errorMsg);
|
|
4411
|
-
}
|
|
4412
|
-
const content = response.data;
|
|
4413
|
-
if (!content?.trim()) {
|
|
4414
|
-
throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4415
|
-
}
|
|
4416
|
-
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
4417
|
-
if (!parsed) throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4418
|
-
saveSubscriptionRawConfig(subName, content);
|
|
4419
|
-
const headers = response.headers;
|
|
4420
|
-
const userInfo = parseUserInfo(headers.get("subscription-userinfo"));
|
|
4421
|
-
const updateIntervalHeader = headers.get("profile-update-interval");
|
|
4422
|
-
const updateInterval = updateIntervalHeader ? parseInt(updateIntervalHeader, 10) : null;
|
|
4423
|
-
const webPageUrl = headers.get("profile-web-page-url") || null;
|
|
4424
|
-
const username = parseUsernameFromContentDisposition(headers.get("content-disposition"));
|
|
4425
|
-
const cacheData = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4426
|
-
if (userInfo) {
|
|
4427
|
-
cacheData.upload = userInfo.upload;
|
|
4428
|
-
cacheData.download = userInfo.download;
|
|
4429
|
-
cacheData.total = userInfo.total;
|
|
4430
|
-
cacheData.expire = userInfo.expire;
|
|
4431
|
-
}
|
|
4432
|
-
if (updateInterval) cacheData.update_interval = updateInterval;
|
|
4433
|
-
if (webPageUrl) cacheData.web_page_url = webPageUrl;
|
|
4434
|
-
if (username) cacheData.username = username;
|
|
4435
|
-
saveSubscriptionCache(subName, cacheData);
|
|
4436
|
-
const proxies = parsed.proxies;
|
|
4437
|
-
const proxyGroups = parsed["proxy-groups"];
|
|
4438
|
-
return {
|
|
4439
|
-
proxies: proxies ? proxies.length : 0,
|
|
4440
|
-
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4441
|
-
userInfo,
|
|
4442
|
-
updateInterval,
|
|
4443
|
-
webPageUrl,
|
|
4444
|
-
username
|
|
4445
|
-
};
|
|
4446
|
-
}
|
|
4447
|
-
async function downloadMergedSubscription(urls, subName) {
|
|
4448
|
-
const responses = await Promise.all(
|
|
4449
|
-
urls.map(async (url, index) => {
|
|
4450
|
-
try {
|
|
4451
|
-
const response = await HTTP_CLIENT.get(url, { responseType: "text" });
|
|
4452
|
-
return { url, index, response, error: null };
|
|
4453
|
-
} catch (e) {
|
|
4454
|
-
return { url, index, response: null, error: e };
|
|
3927
|
+
// src/commands/directory.ts
|
|
3928
|
+
function cmdDirectory(args) {
|
|
3929
|
+
const action = args?.[1];
|
|
3930
|
+
if (action === "open") {
|
|
3931
|
+
const target = args[2];
|
|
3932
|
+
if (!target || target === "root") {
|
|
3933
|
+
console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
|
|
3934
|
+
const success = openUrl(USER_DATA_DIR);
|
|
3935
|
+
if (!success) {
|
|
3936
|
+
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
|
|
4455
3937
|
}
|
|
4456
|
-
|
|
4457
|
-
);
|
|
4458
|
-
for (const r of responses) {
|
|
4459
|
-
if (r.error) {
|
|
4460
|
-
const maskedUrl = maskUrl(r.url);
|
|
4461
|
-
throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${r.index + 1} \u4E2A URL \u83B7\u53D6\u5931\u8D25: ${r.error.message}
|
|
4462
|
-
URL: ${maskedUrl}`);
|
|
3938
|
+
return;
|
|
4463
3939
|
}
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
const baseProxies = base.proxies || [];
|
|
4472
|
-
const seenNames = new Set(baseProxies.map((p) => p.name));
|
|
4473
|
-
for (let i = 1; i < parsed.length; i++) {
|
|
4474
|
-
const extraProxies = parsed[i].proxies || [];
|
|
4475
|
-
for (const proxy of extraProxies) {
|
|
4476
|
-
if (!seenNames.has(proxy.name)) {
|
|
4477
|
-
baseProxies.push(proxy);
|
|
4478
|
-
seenNames.add(proxy.name);
|
|
3940
|
+
const targetInfo = DIRECTORY_TARGETS[target.toLowerCase()];
|
|
3941
|
+
if (targetInfo) {
|
|
3942
|
+
const targetPath = targetInfo.path || USER_DATA_DIR;
|
|
3943
|
+
console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
|
|
3944
|
+
const success = openUrl(targetPath);
|
|
3945
|
+
if (!success) {
|
|
3946
|
+
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
|
|
4479
3947
|
}
|
|
3948
|
+
return;
|
|
4480
3949
|
}
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
const webPageUrl = firstHeaders?.get("profile-web-page-url") || null;
|
|
4490
|
-
const username = parseUsernameFromContentDisposition(firstHeaders?.get("content-disposition") ?? null);
|
|
4491
|
-
const cacheData = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4492
|
-
if (userInfo) {
|
|
4493
|
-
cacheData.upload = userInfo.upload;
|
|
4494
|
-
cacheData.download = userInfo.download;
|
|
4495
|
-
cacheData.total = userInfo.total;
|
|
4496
|
-
cacheData.expire = userInfo.expire;
|
|
4497
|
-
}
|
|
4498
|
-
if (updateInterval) cacheData.update_interval = updateInterval;
|
|
4499
|
-
if (webPageUrl) cacheData.web_page_url = webPageUrl;
|
|
4500
|
-
if (username) cacheData.username = username;
|
|
4501
|
-
saveSubscriptionCache(subName, cacheData);
|
|
4502
|
-
const proxyGroups = base["proxy-groups"];
|
|
4503
|
-
return {
|
|
4504
|
-
proxies: baseProxies.length,
|
|
4505
|
-
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4506
|
-
userInfo,
|
|
4507
|
-
updateInterval,
|
|
4508
|
-
webPageUrl,
|
|
4509
|
-
username
|
|
4510
|
-
};
|
|
4511
|
-
}
|
|
4512
|
-
function prepareConfigForStart(mode, subName = "default") {
|
|
4513
|
-
const rawContent = readSubscriptionRawConfig(subName);
|
|
4514
|
-
if (!rawContent) {
|
|
4515
|
-
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
|
|
4516
|
-
}
|
|
4517
|
-
const buildResult = buildConfig(rawContent, mode);
|
|
4518
|
-
if (buildResult.warnings.length > 0) {
|
|
4519
|
-
for (const warning of buildResult.warnings) {
|
|
4520
|
-
console.log(`${colors.yellow("\u81EA\u52A8\u4FEE\u590D:")} ${warning}`);
|
|
3950
|
+
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`);
|
|
3951
|
+
console.log("");
|
|
3952
|
+
console.log("\u53EF\u7528\u76EE\u6807:");
|
|
3953
|
+
console.log(" root (\u9ED8\u8BA4) \u6839\u76EE\u5F55");
|
|
3954
|
+
for (const [key, val] of Object.entries(DIRECTORY_TARGETS)) {
|
|
3955
|
+
if (key !== "root") {
|
|
3956
|
+
console.log(` ${key.padEnd(14)}${val.label}`);
|
|
3957
|
+
}
|
|
4521
3958
|
}
|
|
4522
3959
|
console.log("");
|
|
3960
|
+
process.exit(1);
|
|
4523
3961
|
}
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
3962
|
+
console.log("");
|
|
3963
|
+
console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
|
|
3964
|
+
console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
|
|
3965
|
+
console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
|
|
3966
|
+
console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
|
|
3967
|
+
console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
|
|
3968
|
+
console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
|
|
3969
|
+
console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
|
|
3970
|
+
console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
|
|
3971
|
+
console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
|
|
3972
|
+
console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
3973
|
+
console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
3974
|
+
console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
|
|
3975
|
+
console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
|
|
3976
|
+
console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
|
|
3977
|
+
console.log("");
|
|
3978
|
+
console.log("\u6253\u5F00\u76EE\u5F55:");
|
|
3979
|
+
console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
|
|
3980
|
+
console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
|
|
3981
|
+
console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
|
|
3982
|
+
console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
|
|
3983
|
+
console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
|
|
3984
|
+
console.log("");
|
|
3985
|
+
console.log("\u73AF\u5883\u53D8\u91CF:");
|
|
3986
|
+
console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
|
|
3987
|
+
console.log("");
|
|
4540
3988
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
}
|
|
3989
|
+
|
|
3990
|
+
// src/commands/help.ts
|
|
3991
|
+
function printShortHelp() {
|
|
3992
|
+
console.log(`
|
|
3993
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
3994
|
+
`);
|
|
3995
|
+
console.log(
|
|
3996
|
+
`\u5E38\u7528\u547D\u4EE4:
|
|
3997
|
+
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
3998
|
+
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
3999
|
+
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
4000
|
+
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
4001
|
+
`
|
|
4002
|
+
);
|
|
4553
4003
|
}
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
}
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
}
|
|
4577
|
-
|
|
4004
|
+
function printHelp() {
|
|
4005
|
+
console.log(
|
|
4006
|
+
`
|
|
4007
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}
|
|
4008
|
+
|
|
4009
|
+
\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh
|
|
4010
|
+
|
|
4011
|
+
\u7528\u6CD5:
|
|
4012
|
+
mihomo <\u547D\u4EE4> [\u9009\u9879]
|
|
4013
|
+
|
|
4014
|
+
${colors.cyan("\u63A7\u5236:")}
|
|
4015
|
+
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)
|
|
4016
|
+
${colors.bold("stop")} \u505C\u6B62\u4EE3\u7406
|
|
4017
|
+
${colors.bold("status")} \u67E5\u770B\u72B6\u6001
|
|
4018
|
+
|
|
4019
|
+
${colors.cyan("\u754C\u9762:")}
|
|
4020
|
+
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)
|
|
4021
|
+
${colors.bold("log")} [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09
|
|
4022
|
+
${colors.bold("logs")} [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09
|
|
4023
|
+
|
|
4024
|
+
${colors.cyan("\u8BA2\u9605:")}
|
|
4025
|
+
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
4026
|
+
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
4027
|
+
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
4028
|
+
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
4029
|
+
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
4030
|
+
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
4031
|
+
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
4032
|
+
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
4033
|
+
${colors.bold("test")} [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027
|
|
4034
|
+
${colors.bold("clean")} [-t ms] [-j N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F
|
|
4035
|
+
|
|
4036
|
+
${colors.cyan("\u914D\u7F6E:")}
|
|
4037
|
+
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
4038
|
+
${colors.bold("overwrite")} on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E
|
|
4039
|
+
${colors.bold("directory")} \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09
|
|
4040
|
+
${colors.bold("directory")} open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|...
|
|
4041
|
+
|
|
4042
|
+
${colors.cyan("\u7CFB\u7EDF:")}
|
|
4043
|
+
${colors.bold("kernel")} [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09
|
|
4044
|
+
${colors.bold("update")} \u66F4\u65B0 mihomo-cli (npm install -g)
|
|
4045
|
+
${colors.bold("reset")} [\u76EE\u6807...] [--full] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8
|
|
4046
|
+
${colors.bold("help")}, -h \u663E\u793A\u5E2E\u52A9
|
|
4047
|
+
${colors.bold("version")}, -v \u663E\u793A\u7248\u672C
|
|
4048
|
+
|
|
4049
|
+
${colors.cyan("\u793A\u4F8B:")}
|
|
4050
|
+
mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F
|
|
4051
|
+
mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F
|
|
4052
|
+
mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)
|
|
4053
|
+
mihomo ui # \u6253\u5F00 Web UI
|
|
4054
|
+
|
|
4055
|
+
${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}
|
|
4056
|
+
mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)
|
|
4057
|
+
tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo
|
|
4058
|
+
|
|
4059
|
+
${colors.cyan("\u6570\u636E\u76EE\u5F55:")}
|
|
4060
|
+
\u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E
|
|
4061
|
+
\u9ED8\u8BA4: ${USER_DATA_DIR}
|
|
4062
|
+
`
|
|
4063
|
+
);
|
|
4578
4064
|
}
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
try {
|
|
4585
|
-
const response = await client.get(url);
|
|
4586
|
-
const data = JSON.parse(response.data);
|
|
4587
|
-
if (data.delay && data.delay > 0) {
|
|
4588
|
-
return { name: proxyName, delay: data.delay };
|
|
4589
|
-
}
|
|
4590
|
-
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
4591
|
-
} catch (e) {
|
|
4592
|
-
const err = e;
|
|
4593
|
-
let errorMsg = "timeout";
|
|
4594
|
-
if (err.response?.data?.message) {
|
|
4595
|
-
errorMsg = String(err.response.data.message);
|
|
4596
|
-
} else if (err.message) {
|
|
4597
|
-
errorMsg = err.message;
|
|
4598
|
-
}
|
|
4599
|
-
return { name: proxyName, delay: null, error: errorMsg };
|
|
4600
|
-
}
|
|
4065
|
+
function printVersion() {
|
|
4066
|
+
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
4067
|
+
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
4068
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
4069
|
+
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
4601
4070
|
}
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4071
|
+
|
|
4072
|
+
// src/kernel.ts
|
|
4073
|
+
import { execSync as execSync4, spawnSync } from "child_process";
|
|
4074
|
+
import fs6 from "fs";
|
|
4075
|
+
import path4 from "path";
|
|
4076
|
+
|
|
4077
|
+
// node_modules/compare-versions/lib/esm/utils.js
|
|
4078
|
+
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
4079
|
+
var validateAndParse = (version) => {
|
|
4080
|
+
if (typeof version !== "string") {
|
|
4081
|
+
throw new TypeError("Invalid argument expected string");
|
|
4607
4082
|
}
|
|
4608
|
-
const
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
for (let i = 0; i < proxies.length; i += concurrency) {
|
|
4612
|
-
const batch = proxies.slice(i, i + concurrency);
|
|
4613
|
-
const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client, apiBase)));
|
|
4614
|
-
for (const result of batchResults) {
|
|
4615
|
-
results.push(result);
|
|
4616
|
-
onResult?.(result, completedCount, proxies.length);
|
|
4617
|
-
completedCount++;
|
|
4618
|
-
}
|
|
4083
|
+
const match = version.match(semver);
|
|
4084
|
+
if (!match) {
|
|
4085
|
+
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
|
4619
4086
|
}
|
|
4620
|
-
|
|
4621
|
-
return
|
|
4622
|
-
}
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
const
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4087
|
+
match.shift();
|
|
4088
|
+
return match;
|
|
4089
|
+
};
|
|
4090
|
+
var isWildcard = (s) => s === "*" || s === "x" || s === "X";
|
|
4091
|
+
var tryParse = (v) => {
|
|
4092
|
+
const n = parseInt(v, 10);
|
|
4093
|
+
return isNaN(n) ? v : n;
|
|
4094
|
+
};
|
|
4095
|
+
var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
|
|
4096
|
+
var compareStrings = (a, b) => {
|
|
4097
|
+
if (isWildcard(a) || isWildcard(b))
|
|
4098
|
+
return 0;
|
|
4099
|
+
const [ap, bp] = forceType(tryParse(a), tryParse(b));
|
|
4100
|
+
if (ap > bp)
|
|
4101
|
+
return 1;
|
|
4102
|
+
if (ap < bp)
|
|
4103
|
+
return -1;
|
|
4104
|
+
return 0;
|
|
4105
|
+
};
|
|
4106
|
+
var compareSegments = (a, b) => {
|
|
4107
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
4108
|
+
const r = compareStrings(a[i] || "0", b[i] || "0");
|
|
4109
|
+
if (r !== 0)
|
|
4110
|
+
return r;
|
|
4635
4111
|
}
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4112
|
+
return 0;
|
|
4113
|
+
};
|
|
4114
|
+
|
|
4115
|
+
// node_modules/compare-versions/lib/esm/compareVersions.js
|
|
4116
|
+
var compareVersions = (v1, v2) => {
|
|
4117
|
+
const n1 = validateAndParse(v1);
|
|
4118
|
+
const n2 = validateAndParse(v2);
|
|
4119
|
+
const p1 = n1.pop();
|
|
4120
|
+
const p2 = n2.pop();
|
|
4121
|
+
const r = compareSegments(n1, n2);
|
|
4122
|
+
if (r !== 0)
|
|
4123
|
+
return r;
|
|
4124
|
+
if (p1 && p2) {
|
|
4125
|
+
return compareSegments(p1.split("."), p2.split("."));
|
|
4126
|
+
} else if (p1 || p2) {
|
|
4127
|
+
return p1 ? -1 : 1;
|
|
4640
4128
|
}
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4129
|
+
return 0;
|
|
4130
|
+
};
|
|
4131
|
+
|
|
4132
|
+
// src/kernel.ts
|
|
4133
|
+
var GITHUB_REPO = "MetaCubeX/mihomo";
|
|
4134
|
+
var KERNEL_HTTP_TIMEOUT = 12e4;
|
|
4135
|
+
var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
|
|
4136
|
+
var HTTP_CLIENT = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
|
|
4137
|
+
function withMirror(url, mirror) {
|
|
4138
|
+
if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
|
|
4139
|
+
return mirror + url;
|
|
4645
4140
|
}
|
|
4646
|
-
return
|
|
4141
|
+
return url;
|
|
4647
4142
|
}
|
|
4648
|
-
function
|
|
4649
|
-
const
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
let updatedGroups = 0;
|
|
4654
|
-
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
4655
|
-
for (const group of proxyGroups) {
|
|
4656
|
-
if (Array.isArray(group.proxies)) {
|
|
4657
|
-
const before = group.proxies.length;
|
|
4658
|
-
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
4659
|
-
if (group.proxies.length < before) {
|
|
4660
|
-
updatedGroups++;
|
|
4661
|
-
}
|
|
4662
|
-
if (group.proxies.length === 0) {
|
|
4663
|
-
removedGroupNames.add(group.name);
|
|
4664
|
-
}
|
|
4665
|
-
}
|
|
4666
|
-
}
|
|
4667
|
-
if (removedGroupNames.size > 0) {
|
|
4668
|
-
parsed.proxyGroups = proxyGroups.filter((g) => !removedGroupNames.has(g.name));
|
|
4669
|
-
for (const group of parsed.proxyGroups) {
|
|
4670
|
-
if (Array.isArray(group.proxies)) {
|
|
4671
|
-
group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
|
|
4672
|
-
}
|
|
4673
|
-
}
|
|
4674
|
-
}
|
|
4675
|
-
return { removedProxies, updatedGroups, removedGroups: removedGroupNames.size };
|
|
4143
|
+
function getArch() {
|
|
4144
|
+
const arch = process.arch;
|
|
4145
|
+
if (arch === "arm64") return "arm64";
|
|
4146
|
+
if (arch === "x64") return "amd64";
|
|
4147
|
+
return arch;
|
|
4676
4148
|
}
|
|
4677
|
-
|
|
4678
|
-
const
|
|
4679
|
-
const
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
updatedGroups = cleanResult.updatedGroups;
|
|
4692
|
-
removedGroups = cleanResult.removedGroups;
|
|
4693
|
-
}
|
|
4694
|
-
}
|
|
4695
|
-
if (!skipped && removedProxies > 0) {
|
|
4696
|
-
saveSubscriptionConfig(subName, parsed);
|
|
4697
|
-
}
|
|
4698
|
-
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
4149
|
+
function findMatchingAsset(assets, platform, arch) {
|
|
4150
|
+
const prefix = `mihomo-${platform}-${arch}`;
|
|
4151
|
+
const matchingAssets = assets.filter(
|
|
4152
|
+
(a) => a.name.startsWith(prefix) && a.name.endsWith(".gz") || a.name.startsWith(`${prefix}-`) && a.name.endsWith(".gz")
|
|
4153
|
+
);
|
|
4154
|
+
if (matchingAssets.length === 0) return null;
|
|
4155
|
+
if (matchingAssets.length === 1) return matchingAssets[0];
|
|
4156
|
+
const standardAsset = matchingAssets.find((a) => {
|
|
4157
|
+
const nameWithoutGz = a.name.slice(0, -3);
|
|
4158
|
+
const parts = nameWithoutGz.split("-");
|
|
4159
|
+
const lastPart = parts[parts.length - 1];
|
|
4160
|
+
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go");
|
|
4161
|
+
});
|
|
4162
|
+
return standardAsset || matchingAssets[0];
|
|
4699
4163
|
}
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
var TEST_DIRS = {
|
|
4707
|
-
data: path5.join(TEST_DIR, "data"),
|
|
4708
|
-
runtime: path5.join(TEST_DIR, "runtime")
|
|
4709
|
-
};
|
|
4710
|
-
var TEST_PATHS = {
|
|
4711
|
-
configFile: path5.join(TEST_DIRS.runtime, "config.yaml"),
|
|
4712
|
-
pidFile: path5.join(TEST_DIRS.runtime, "pid"),
|
|
4713
|
-
logFile: path5.join(TEST_DIR, "test.log")
|
|
4714
|
-
};
|
|
4715
|
-
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
4716
|
-
function ensureTestDirs() {
|
|
4717
|
-
for (const dir of Object.values(TEST_DIRS)) {
|
|
4718
|
-
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4164
|
+
async function getLatestRelease(repo, mirror) {
|
|
4165
|
+
const url = withMirror(`https://api.github.com/repos/${repo}/releases`, mirror);
|
|
4166
|
+
const response = await HTTP_CLIENT.get(url, { responseType: "json" });
|
|
4167
|
+
const releases = response.data;
|
|
4168
|
+
if (!Array.isArray(releases) || releases.length === 0) {
|
|
4169
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F");
|
|
4719
4170
|
}
|
|
4171
|
+
const stableReleases = releases.filter(
|
|
4172
|
+
(r) => !r.prerelease && !r.tag_name.toLowerCase().includes("alpha") && !r.tag_name.toLowerCase().includes("beta") && !r.tag_name.toLowerCase().includes("prerelease")
|
|
4173
|
+
);
|
|
4174
|
+
return stableReleases.length > 0 ? stableReleases[0] : releases[0];
|
|
4720
4175
|
}
|
|
4721
|
-
function
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
const
|
|
4727
|
-
if (!
|
|
4728
|
-
|
|
4729
|
-
}
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4176
|
+
async function checkUpdate(mirror) {
|
|
4177
|
+
const currentVersion = getKernelVersion();
|
|
4178
|
+
const latest = await getLatestRelease(GITHUB_REPO, mirror);
|
|
4179
|
+
const latestVersion = latest.tag_name;
|
|
4180
|
+
let needsUpdate = false;
|
|
4181
|
+
const currentDisplay = currentVersion || "\u672A\u5B89\u88C5";
|
|
4182
|
+
if (!currentVersion) {
|
|
4183
|
+
needsUpdate = true;
|
|
4184
|
+
} else {
|
|
4185
|
+
try {
|
|
4186
|
+
needsUpdate = compareVersions(latestVersion.replace(/^v/, ""), currentVersion.replace(/^v/, "")) > 0;
|
|
4187
|
+
} catch {
|
|
4188
|
+
needsUpdate = latestVersion !== currentVersion;
|
|
4189
|
+
}
|
|
4734
4190
|
}
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4191
|
+
return {
|
|
4192
|
+
current: currentDisplay,
|
|
4193
|
+
latest: latestVersion,
|
|
4194
|
+
needsUpdate,
|
|
4195
|
+
assets: latest.assets,
|
|
4196
|
+
release: latest
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
function findBinaryInDir(dir) {
|
|
4200
|
+
const files = fs6.readdirSync(dir);
|
|
4201
|
+
for (const f of files) {
|
|
4202
|
+
const fullPath = path4.join(dir, f);
|
|
4203
|
+
const stat = fs6.statSync(fullPath);
|
|
4204
|
+
if (stat.isDirectory()) {
|
|
4205
|
+
const found = findBinaryInDir(fullPath);
|
|
4206
|
+
if (found) return found;
|
|
4207
|
+
continue;
|
|
4741
4208
|
}
|
|
4209
|
+
if (f === "mihomo") return fullPath;
|
|
4210
|
+
if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
|
|
4742
4211
|
}
|
|
4743
|
-
|
|
4744
|
-
...TEST_CONFIG,
|
|
4745
|
-
proxies,
|
|
4746
|
-
"proxy-groups": [
|
|
4747
|
-
{
|
|
4748
|
-
name: "PROXY",
|
|
4749
|
-
type: "select",
|
|
4750
|
-
proxies: proxies.map((p) => p.name)
|
|
4751
|
-
}
|
|
4752
|
-
],
|
|
4753
|
-
rules: ["MATCH,PROXY"]
|
|
4754
|
-
};
|
|
4755
|
-
const content = jsYaml.dump(config, { indent: 2, lineWidth: -1, noCompatMode: true });
|
|
4756
|
-
fs7.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
4212
|
+
return null;
|
|
4757
4213
|
}
|
|
4758
|
-
async function
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
const
|
|
4763
|
-
const
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4214
|
+
async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
4215
|
+
ensureDirs();
|
|
4216
|
+
const latest = releaseInfo || await getLatestRelease(GITHUB_REPO, mirror);
|
|
4217
|
+
const arch = getArch();
|
|
4218
|
+
const platform = process.platform;
|
|
4219
|
+
const asset = findMatchingAsset(latest.assets, platform, arch);
|
|
4220
|
+
if (!asset) {
|
|
4221
|
+
const available = latest.assets.map((a) => a.name).join(", ");
|
|
4222
|
+
let hint = "";
|
|
4223
|
+
if (available) hint = `
|
|
4224
|
+
\u53EF\u7528\u7248\u672C: ${available}`;
|
|
4225
|
+
throw new Error(`\u672A\u627E\u5230\u5339\u914D\u7684\u5185\u6838\u6587\u4EF6
|
|
4226
|
+
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
4227
|
+
}
|
|
4228
|
+
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
4229
|
+
const tempPath = path4.join(DIRS.kernel, asset.name);
|
|
4230
|
+
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
4231
|
+
if (progressCallback) {
|
|
4232
|
+
progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
|
|
4233
|
+
}
|
|
4234
|
+
const curlResult = spawnSync(
|
|
4235
|
+
"curl",
|
|
4236
|
+
["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
|
|
4237
|
+
{ stdio: "inherit" }
|
|
4238
|
+
);
|
|
4239
|
+
if (curlResult.status !== 0) {
|
|
4775
4240
|
try {
|
|
4776
|
-
|
|
4777
|
-
ready = true;
|
|
4778
|
-
break;
|
|
4241
|
+
fs6.unlinkSync(tempPath);
|
|
4779
4242
|
} catch {
|
|
4780
|
-
await sleep(500);
|
|
4781
4243
|
}
|
|
4244
|
+
throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
|
|
4782
4245
|
}
|
|
4783
|
-
if (!
|
|
4784
|
-
|
|
4246
|
+
if (!fs6.existsSync(tempPath)) {
|
|
4247
|
+
throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
|
|
4248
|
+
}
|
|
4249
|
+
if (progressCallback) {
|
|
4250
|
+
progressCallback("\u89E3\u538B\u5185\u6838...");
|
|
4251
|
+
}
|
|
4252
|
+
const extractPath = DIRS.kernel;
|
|
4253
|
+
let extractedBinary = null;
|
|
4254
|
+
try {
|
|
4255
|
+
if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
|
|
4256
|
+
execSync4(`tar -xzf "${tempPath}" -C "${extractPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
4257
|
+
} else if (tempPath.endsWith(".gz")) {
|
|
4258
|
+
const baseName = path4.basename(tempPath, ".gz");
|
|
4259
|
+
const outputPath = path4.join(extractPath, baseName);
|
|
4260
|
+
execSync4(`gzip -dc "${tempPath}" > "${outputPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
4261
|
+
extractedBinary = outputPath;
|
|
4262
|
+
}
|
|
4263
|
+
} catch (e) {
|
|
4785
4264
|
try {
|
|
4786
|
-
|
|
4265
|
+
fs6.unlinkSync(tempPath);
|
|
4787
4266
|
} catch {
|
|
4788
4267
|
}
|
|
4789
|
-
throw new Error(`\
|
|
4790
|
-
${errorDetail}` : ""}`);
|
|
4791
|
-
}
|
|
4792
|
-
if (!ready) {
|
|
4793
|
-
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
4268
|
+
throw new Error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
|
|
4794
4269
|
}
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4270
|
+
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
4271
|
+
if (!foundBinary) {
|
|
4272
|
+
try {
|
|
4273
|
+
fs6.unlinkSync(tempPath);
|
|
4274
|
+
} catch {
|
|
4275
|
+
}
|
|
4276
|
+
throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
|
|
4802
4277
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4278
|
+
const targetPath = PATHS.mihomoBinary;
|
|
4279
|
+
if (foundBinary !== targetPath) {
|
|
4280
|
+
if (fs6.existsSync(targetPath)) {
|
|
4281
|
+
fs6.chmodSync(targetPath, 493);
|
|
4282
|
+
try {
|
|
4283
|
+
fs6.unlinkSync(targetPath);
|
|
4284
|
+
} catch {
|
|
4285
|
+
}
|
|
4808
4286
|
}
|
|
4287
|
+
fs6.renameSync(foundBinary, targetPath);
|
|
4809
4288
|
}
|
|
4289
|
+
fs6.chmodSync(targetPath, 493);
|
|
4810
4290
|
try {
|
|
4811
|
-
|
|
4291
|
+
fs6.unlinkSync(tempPath);
|
|
4812
4292
|
} catch {
|
|
4813
4293
|
}
|
|
4294
|
+
clearKernelVersionCache();
|
|
4295
|
+
return { version: latest.tag_name, path: targetPath };
|
|
4814
4296
|
}
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4297
|
+
|
|
4298
|
+
// src/commands/kernel.ts
|
|
4299
|
+
async function cmdKernel(args) {
|
|
4300
|
+
const mirrorInfo = parseMirrorArg(args);
|
|
4301
|
+
const effectiveMirror = mirrorInfo.mirror;
|
|
4302
|
+
if (effectiveMirror) {
|
|
4303
|
+
const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
|
|
4304
|
+
console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
|
|
4305
|
+
}
|
|
4306
|
+
console.log("\n\u63D0\u793A: \u5982\u679C\u4E0B\u8F7D\u901F\u5EA6\u8FC7\u6162\u6216\u76F4\u8FDE\u5931\u8D25\uFF0C\u53EF\u4F7F\u7528 --mirror \u53C2\u6570\u901A\u8FC7\u955C\u50CF\u4E0B\u8F7D");
|
|
4307
|
+
console.log("\n\u7528\u6CD5:");
|
|
4308
|
+
console.log(" mihomo kernel # \u76F4\u8FDE");
|
|
4309
|
+
console.log(" mihomo kernel --mirror # \u4E0B\u8F7D\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF (v6.gh-proxy.org)");
|
|
4310
|
+
console.log(" mihomo kernel --mirror hk.gh-proxy.org # \u4E0B\u8F7D\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
4311
|
+
console.log(" mihomo kernel --mirror-all # API\u8BF7\u6C42\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF");
|
|
4312
|
+
console.log(" mihomo kernel --mirror-all hk.gh-proxy.org # API\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
4313
|
+
console.log("\n\u53EF\u7528\u955C\u50CF:");
|
|
4314
|
+
for (const m of AVAILABLE_MIRRORS) {
|
|
4315
|
+
const isCurrent = effectiveMirror && (effectiveMirror.includes(`//${m}/`) || effectiveMirror.includes(`//${m}:`) || effectiveMirror.endsWith(`//${m}`));
|
|
4316
|
+
console.log(` ${m}${isCurrent ? " (\u5F53\u524D)" : ""}`);
|
|
4317
|
+
}
|
|
4318
|
+
console.log("");
|
|
4319
|
+
console.log("\u68C0\u67E5\u5185\u6838\u66F4\u65B0...");
|
|
4818
4320
|
try {
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4321
|
+
const apiMirror = mirrorInfo.type === "all" ? effectiveMirror : null;
|
|
4322
|
+
const info = await checkUpdate(apiMirror);
|
|
4323
|
+
console.log(`\u5F53\u524D: ${info.current}`);
|
|
4324
|
+
console.log(`\u6700\u65B0: ${info.latest}`);
|
|
4325
|
+
if (!info.needsUpdate) {
|
|
4326
|
+
console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
4327
|
+
} else {
|
|
4328
|
+
console.log("\n\u6B63\u5728\u4E0B\u8F7D...");
|
|
4329
|
+
const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
|
|
4330
|
+
console.log(`
|
|
4331
|
+
\u5DF2\u66F4\u65B0\u5230 ${result.version}`);
|
|
4332
|
+
}
|
|
4333
|
+
} catch (e) {
|
|
4334
|
+
console.error(`
|
|
4335
|
+
\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4336
|
+
const err = e;
|
|
4337
|
+
if (err.response?.data) {
|
|
4338
|
+
if (err.response.data.message) {
|
|
4339
|
+
console.error(`\u539F\u56E0: ${err.response.data.message}`);
|
|
4340
|
+
}
|
|
4341
|
+
if (err.response.data.documentation_url) {
|
|
4342
|
+
console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
process.exit(1);
|
|
4824
4346
|
}
|
|
4825
4347
|
}
|
|
4826
4348
|
|
|
4827
|
-
// src/commands/
|
|
4828
|
-
function
|
|
4829
|
-
const
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
const activeSub = getActiveSubscription();
|
|
4834
|
-
console.log("");
|
|
4835
|
-
let modeLabel = "";
|
|
4836
|
-
if (info && status.running) {
|
|
4837
|
-
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
4349
|
+
// src/commands/log.ts
|
|
4350
|
+
function cmdLog(args) {
|
|
4351
|
+
const logPath = getLogPath();
|
|
4352
|
+
if (hasFlag(args, "-o", "--open")) {
|
|
4353
|
+
openLogFile(logPath);
|
|
4354
|
+
return;
|
|
4838
4355
|
}
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4356
|
+
viewLogWithTail(logPath, { follow: true, lines: 50 });
|
|
4357
|
+
}
|
|
4358
|
+
function cmdLogs(args) {
|
|
4359
|
+
const targetName = getNonFlagArg(args, 1);
|
|
4360
|
+
const lines = parseIntArg(args, "-n", "--lines", 100);
|
|
4361
|
+
const openInViewer = hasFlag(args, "-o", "--open");
|
|
4362
|
+
if (targetName) {
|
|
4363
|
+
let logPath;
|
|
4364
|
+
if (targetName === "current" || targetName === "0") {
|
|
4365
|
+
logPath = getLogPath();
|
|
4366
|
+
} else {
|
|
4367
|
+
const parsedIdx = parseInt(targetName, 10);
|
|
4368
|
+
if (!Number.isNaN(parsedIdx) && parsedIdx > 0 && String(parsedIdx) === targetName) {
|
|
4369
|
+
const archiveLogs = listLogs();
|
|
4370
|
+
const archive = archiveLogs.archives[parsedIdx - 1];
|
|
4371
|
+
if (!archive) {
|
|
4372
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
4373
|
+
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
4374
|
+
process.exit(1);
|
|
4375
|
+
}
|
|
4376
|
+
logPath = archive.path;
|
|
4377
|
+
} else {
|
|
4378
|
+
logPath = getLogPathByName(targetName);
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
if (!logPath) {
|
|
4382
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
4383
|
+
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
4384
|
+
process.exit(1);
|
|
4846
4385
|
}
|
|
4386
|
+
if (openInViewer) {
|
|
4387
|
+
openLogFile(logPath);
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
viewLogWithTail(logPath, { follow: false, lines });
|
|
4391
|
+
return;
|
|
4847
4392
|
}
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4393
|
+
const logs = listLogs();
|
|
4394
|
+
const all = [];
|
|
4395
|
+
if (logs.current) all.push(logs.current);
|
|
4396
|
+
all.push(...logs.archives);
|
|
4397
|
+
if (all.length === 0) {
|
|
4398
|
+
console.log("\u6682\u65E0\u65E5\u5FD7");
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4401
|
+
console.log("");
|
|
4402
|
+
console.log("\u65E5\u5FD7\u5217\u8868:");
|
|
4403
|
+
console.log("");
|
|
4404
|
+
let archiveCounter = 0;
|
|
4405
|
+
for (const log of all) {
|
|
4406
|
+
let num;
|
|
4407
|
+
if (log.isCurrent) {
|
|
4408
|
+
num = " 0";
|
|
4851
4409
|
} else {
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
|
|
4855
|
-
console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
|
|
4410
|
+
archiveCounter++;
|
|
4411
|
+
num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
|
|
4856
4412
|
}
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4413
|
+
const time = formatDate(log.mtime);
|
|
4414
|
+
const size = formatBytes(log.size);
|
|
4415
|
+
const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
|
|
4416
|
+
console.log(` ${num}. ${name}`);
|
|
4417
|
+
console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
|
|
4418
|
+
if (!log.isCurrent) {
|
|
4419
|
+
console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
|
|
4862
4420
|
}
|
|
4863
|
-
console.log(
|
|
4864
|
-
} else {
|
|
4865
|
-
console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
|
|
4866
|
-
}
|
|
4867
|
-
if (overwriteEnabled && overwriteFiles.length > 0) {
|
|
4868
|
-
const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
|
|
4869
|
-
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
|
|
4870
|
-
} else if (overwriteEnabled) {
|
|
4871
|
-
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
|
|
4872
|
-
} else {
|
|
4873
|
-
console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
|
|
4421
|
+
console.log("");
|
|
4874
4422
|
}
|
|
4423
|
+
console.log("\u7528\u6CD5:");
|
|
4424
|
+
console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
|
|
4425
|
+
console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
|
|
4426
|
+
console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
|
|
4427
|
+
console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
|
|
4875
4428
|
console.log("");
|
|
4876
4429
|
}
|
|
4877
4430
|
|
|
4878
|
-
// src/commands/
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4431
|
+
// src/commands/overwrite.ts
|
|
4432
|
+
import path6 from "path";
|
|
4433
|
+
|
|
4434
|
+
// src/subscription.ts
|
|
4435
|
+
var DEFAULT_UPDATE_INTERVAL_HOURS = 4;
|
|
4436
|
+
var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, noCompatMode: true };
|
|
4437
|
+
var HTTP_CLIENT2 = createHttpClient({ timeout: 6e4 });
|
|
4438
|
+
function isMultiUrl(url) {
|
|
4439
|
+
return url.includes(",");
|
|
4886
4440
|
}
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
if (!sub) {
|
|
4895
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4896
|
-
process.exit(1);
|
|
4897
|
-
}
|
|
4898
|
-
await autoUpdateStaleSubscription();
|
|
4899
|
-
const status = getStatus();
|
|
4900
|
-
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
4901
|
-
if (hasProcess) {
|
|
4902
|
-
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
4903
|
-
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
4904
|
-
}
|
|
4905
|
-
handleStopResult(stop());
|
|
4906
|
-
if (hasProcess) {
|
|
4907
|
-
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
4908
|
-
`);
|
|
4909
|
-
}
|
|
4910
|
-
let configInfo;
|
|
4911
|
-
try {
|
|
4912
|
-
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
4913
|
-
} catch (e) {
|
|
4914
|
-
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
4915
|
-
process.exit(1);
|
|
4916
|
-
}
|
|
4917
|
-
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
4918
|
-
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
4919
|
-
try {
|
|
4920
|
-
const result = await start(targetMode);
|
|
4921
|
-
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
|
|
4922
|
-
} catch (e) {
|
|
4923
|
-
const msg = e.message;
|
|
4924
|
-
const lines = msg.split("\n");
|
|
4925
|
-
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
|
|
4926
|
-
if (lines.length > 1) {
|
|
4927
|
-
for (const line of lines.slice(1)) console.error(line);
|
|
4928
|
-
}
|
|
4929
|
-
process.exit(1);
|
|
4441
|
+
function splitUrls(url) {
|
|
4442
|
+
return url.split(",").map((u) => u.trim()).filter(Boolean);
|
|
4443
|
+
}
|
|
4444
|
+
function loadSubscriptionConfig(subName) {
|
|
4445
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4446
|
+
if (!rawContent) {
|
|
4447
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
4930
4448
|
}
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
}
|
|
4449
|
+
const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4450
|
+
return {
|
|
4451
|
+
raw,
|
|
4452
|
+
proxies: raw.proxies || [],
|
|
4453
|
+
proxyGroups: raw["proxy-groups"] || []
|
|
4454
|
+
};
|
|
4455
|
+
}
|
|
4456
|
+
function saveSubscriptionConfig(subName, parsed) {
|
|
4457
|
+
normalizeProxyNamesBeforeSave(parsed);
|
|
4458
|
+
parsed.raw.proxies = parsed.proxies;
|
|
4459
|
+
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4460
|
+
saveSubscriptionRawConfig(subName, jsYaml.dump(parsed.raw, YAML_DUMP_OPTS));
|
|
4461
|
+
}
|
|
4462
|
+
function parseUserInfo(header) {
|
|
4463
|
+
if (!header) return null;
|
|
4464
|
+
const info = {};
|
|
4465
|
+
const parts = header.split(";").map((p) => p.trim());
|
|
4466
|
+
for (const part of parts) {
|
|
4467
|
+
const [key, val] = part.split("=").map((s) => s.trim());
|
|
4468
|
+
if (key && val !== void 0) {
|
|
4469
|
+
const numVal = parseFloat(val);
|
|
4470
|
+
info[key] = Number.isNaN(numVal) ? 0 : numVal;
|
|
4954
4471
|
}
|
|
4955
4472
|
}
|
|
4956
|
-
|
|
4473
|
+
return info;
|
|
4957
4474
|
}
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
} else {
|
|
4966
|
-
console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
4967
|
-
}
|
|
4475
|
+
function parseUsernameFromContentDisposition(header) {
|
|
4476
|
+
if (!header) return null;
|
|
4477
|
+
const match = header.match(/filename\s*=\s*["']?([^"';\s]+)["']?/i);
|
|
4478
|
+
if (!match) return null;
|
|
4479
|
+
const filename = match[1];
|
|
4480
|
+
const parts = filename.split("/");
|
|
4481
|
+
return parts[parts.length - 1] || null;
|
|
4968
4482
|
}
|
|
4969
|
-
function
|
|
4970
|
-
const parts = [
|
|
4971
|
-
if (
|
|
4972
|
-
|
|
4483
|
+
function formatProxySummary(info) {
|
|
4484
|
+
const parts = [];
|
|
4485
|
+
if (info.proxyGroups && info.proxyGroups > 0) parts.push(`${info.proxyGroups} \u7EC4`);
|
|
4486
|
+
parts.push(`${info.proxies || 0} \u8282\u70B9`);
|
|
4973
4487
|
return parts.join(", ");
|
|
4974
4488
|
}
|
|
4975
|
-
function
|
|
4976
|
-
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
4977
|
-
}
|
|
4978
|
-
function githubRepoUrl(rawUrl) {
|
|
4979
|
-
const match = rawUrl.match(/raw\.githubusercontent\.com\/([^/]+\/[^/]+)/);
|
|
4980
|
-
if (match) return `https://github.com/${match[1]}`;
|
|
4981
|
-
return null;
|
|
4982
|
-
}
|
|
4983
|
-
function resolveTestTarget(args) {
|
|
4489
|
+
function getActiveSubscription() {
|
|
4984
4490
|
const subs = getSubscriptions();
|
|
4985
|
-
if (subs.length === 0)
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
4992
|
-
let target;
|
|
4993
|
-
if (nameArg) {
|
|
4994
|
-
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
4995
|
-
target = pickSingleSubscription(matches, nameArg);
|
|
4996
|
-
} else {
|
|
4997
|
-
const activeSub = getActiveSubscription();
|
|
4998
|
-
if (!activeSub) {
|
|
4999
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
5000
|
-
process.exit(1);
|
|
5001
|
-
}
|
|
5002
|
-
target = activeSub;
|
|
5003
|
-
}
|
|
5004
|
-
return { target, timeout, concurrency };
|
|
5005
|
-
}
|
|
5006
|
-
async function printSubscriptionList(options) {
|
|
5007
|
-
if (options?.autoUpdate !== false) {
|
|
5008
|
-
const updateResult = await autoUpdateStaleSubscription();
|
|
5009
|
-
if (updateResult.total > 0) console.log("");
|
|
5010
|
-
}
|
|
5011
|
-
const subs = getSubscriptionsWithCache();
|
|
5012
|
-
if (subs.length === 0) {
|
|
5013
|
-
console.log("\u6CA1\u6709\u8BA2\u9605");
|
|
5014
|
-
console.log("");
|
|
5015
|
-
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
5016
|
-
console.log("");
|
|
5017
|
-
return;
|
|
4491
|
+
if (subs.length === 0) return null;
|
|
4492
|
+
const settings = readSettings();
|
|
4493
|
+
const activeName = settings.active_subscription;
|
|
4494
|
+
if (activeName) {
|
|
4495
|
+
const found = subs.find((s) => s.name === activeName);
|
|
4496
|
+
if (found) return found;
|
|
5018
4497
|
}
|
|
5019
|
-
|
|
5020
|
-
console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
|
|
5021
|
-
subs.forEach((s, i) => {
|
|
5022
|
-
const time = formatDate(s.updated_at);
|
|
5023
|
-
const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
|
|
5024
|
-
const mergeBadge = isMultiUrl(s.url) ? colors.cyan(` [\u5408\u5E76 ${splitUrls(s.url).length} \u6E90]`) : "";
|
|
5025
|
-
const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
5026
|
-
console.log(` ${i + 1}. ${s.name}${defaultMark}${mergeBadge}`);
|
|
5027
|
-
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
5028
|
-
if (s.username) {
|
|
5029
|
-
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
5030
|
-
}
|
|
5031
|
-
if (s.download !== void 0 || s.total !== void 0) {
|
|
5032
|
-
const used = (s.upload || 0) + (s.download || 0);
|
|
5033
|
-
const usedStr = formatBytes(used);
|
|
5034
|
-
const totalStr = formatBytes(s.total);
|
|
5035
|
-
let percentStr = "";
|
|
5036
|
-
if (s.total && s.total > 0) {
|
|
5037
|
-
const percent = Math.min(used / s.total * 100, 100);
|
|
5038
|
-
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
5039
|
-
}
|
|
5040
|
-
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
5041
|
-
}
|
|
5042
|
-
if (s.expire !== void 0) {
|
|
5043
|
-
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
5044
|
-
}
|
|
5045
|
-
if (s.web_page_url) {
|
|
5046
|
-
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
5047
|
-
}
|
|
5048
|
-
});
|
|
5049
|
-
console.log("");
|
|
5050
|
-
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
5051
|
-
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
5052
|
-
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
5053
|
-
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
5054
|
-
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
5055
|
-
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
5056
|
-
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
5057
|
-
console.log("");
|
|
4498
|
+
return subs[0];
|
|
5058
4499
|
}
|
|
5059
|
-
function
|
|
5060
|
-
const
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
const name2 = "free0";
|
|
5073
|
-
console.log(`\u6DFB\u52A0\u5408\u5E76\u514D\u8D39\u8BA2\u9605: ${name2} (${sources.map((s) => s.name).join(" + ")})`);
|
|
5074
|
-
try {
|
|
5075
|
-
addSubscription(mergedUrl, name2);
|
|
5076
|
-
const info = await downloadMergedSubscription(urls, name2);
|
|
5077
|
-
setDefaultSubscription(name2);
|
|
5078
|
-
const repoUrls = sources.map((s) => githubRepoUrl(s.url)).filter(Boolean);
|
|
5079
|
-
if (repoUrls.length > 0) saveSubscriptionCache(name2, { web_page_url: repoUrls.join(", ") });
|
|
5080
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name2}" (${formatProxySummary(info)}, \u5408\u5E76 ${sources.length} \u6E90)`);
|
|
5081
|
-
} catch (e) {
|
|
5082
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5083
|
-
process.exit(1);
|
|
4500
|
+
function findSubscriptionFuzzy(subs, pattern) {
|
|
4501
|
+
const lowerPattern = pattern.toLowerCase();
|
|
4502
|
+
const exact = [];
|
|
4503
|
+
const prefix = [];
|
|
4504
|
+
const includes = [];
|
|
4505
|
+
for (const s of subs) {
|
|
4506
|
+
const name = s.name.toLowerCase();
|
|
4507
|
+
if (name === lowerPattern) {
|
|
4508
|
+
exact.push(s);
|
|
4509
|
+
} else if (name.startsWith(lowerPattern)) {
|
|
4510
|
+
prefix.push(s);
|
|
4511
|
+
} else if (name.includes(lowerPattern)) {
|
|
4512
|
+
includes.push(s);
|
|
5084
4513
|
}
|
|
5085
|
-
console.log("");
|
|
5086
|
-
await printSubscriptionList();
|
|
5087
|
-
return;
|
|
5088
4514
|
}
|
|
5089
|
-
if (
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
4515
|
+
if (exact.length > 0) return exact;
|
|
4516
|
+
if (prefix.length > 0) return prefix;
|
|
4517
|
+
return includes;
|
|
4518
|
+
}
|
|
4519
|
+
function pickSingleSubscription(subs, pattern) {
|
|
4520
|
+
if (subs.length === 0) {
|
|
4521
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
|
|
5093
4522
|
process.exit(1);
|
|
5094
4523
|
}
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
console.log(
|
|
4524
|
+
if (subs.length === 1) return subs[0];
|
|
4525
|
+
console.error("\u9519\u8BEF: \u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A");
|
|
4526
|
+
console.log("\n\u5339\u914D\u7684\u8BA2\u9605:");
|
|
4527
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4528
|
+
process.exit(1);
|
|
4529
|
+
}
|
|
4530
|
+
async function downloadSubscription(url, subName = "default") {
|
|
4531
|
+
let response;
|
|
5098
4532
|
try {
|
|
5099
|
-
|
|
5100
|
-
const info = await downloadSubscription(source.url, name);
|
|
5101
|
-
setDefaultSubscription(name);
|
|
5102
|
-
const repoUrl = githubRepoUrl(source.url);
|
|
5103
|
-
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
5104
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4533
|
+
response = await HTTP_CLIENT2.get(url, { responseType: "text" });
|
|
5105
4534
|
} catch (e) {
|
|
5106
|
-
|
|
5107
|
-
|
|
4535
|
+
const maskedUrl = maskUrl(url);
|
|
4536
|
+
let errorMsg = `\u83B7\u53D6\u8BA2\u9605\u5931\u8D25: ${e.message}`;
|
|
4537
|
+
const err = e;
|
|
4538
|
+
if (err.response) {
|
|
4539
|
+
errorMsg += ` (HTTP ${err.response.status})`;
|
|
4540
|
+
}
|
|
4541
|
+
errorMsg += `
|
|
4542
|
+
URL: ${maskedUrl}`;
|
|
4543
|
+
throw new Error(errorMsg);
|
|
5108
4544
|
}
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
async function cmdSubscription(args) {
|
|
5113
|
-
const action = args[1];
|
|
5114
|
-
if (!action || action === "list") {
|
|
5115
|
-
await printSubscriptionList();
|
|
5116
|
-
return;
|
|
4545
|
+
const content = response.data;
|
|
4546
|
+
if (!content?.trim()) {
|
|
4547
|
+
throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
5117
4548
|
}
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
4549
|
+
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
4550
|
+
if (!parsed) throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4551
|
+
saveSubscriptionRawConfig(subName, content);
|
|
4552
|
+
const headers = response.headers;
|
|
4553
|
+
const userInfo = parseUserInfo(headers.get("subscription-userinfo"));
|
|
4554
|
+
const updateIntervalHeader = headers.get("profile-update-interval");
|
|
4555
|
+
const updateInterval = updateIntervalHeader ? parseInt(updateIntervalHeader, 10) : null;
|
|
4556
|
+
const webPageUrl = headers.get("profile-web-page-url") || null;
|
|
4557
|
+
const username = parseUsernameFromContentDisposition(headers.get("content-disposition"));
|
|
4558
|
+
const cacheData = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4559
|
+
if (userInfo) {
|
|
4560
|
+
cacheData.upload = userInfo.upload;
|
|
4561
|
+
cacheData.download = userInfo.download;
|
|
4562
|
+
cacheData.total = userInfo.total;
|
|
4563
|
+
cacheData.expire = userInfo.expire;
|
|
5128
4564
|
}
|
|
5129
|
-
if (
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
}
|
|
5148
|
-
}
|
|
5149
|
-
console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
|
|
5150
|
-
try {
|
|
5151
|
-
addSubscription(url, name);
|
|
5152
|
-
setDefaultSubscription(name);
|
|
5153
|
-
const info = await downloadMergedSubscription(urls, name);
|
|
5154
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
|
|
5155
|
-
} catch (e) {
|
|
5156
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5157
|
-
process.exit(1);
|
|
5158
|
-
}
|
|
5159
|
-
} else {
|
|
5160
|
-
if (!url.startsWith("http")) {
|
|
5161
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
5162
|
-
process.exit(1);
|
|
5163
|
-
}
|
|
5164
|
-
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
4565
|
+
if (updateInterval) cacheData.update_interval = updateInterval;
|
|
4566
|
+
if (webPageUrl) cacheData.web_page_url = webPageUrl;
|
|
4567
|
+
if (username) cacheData.username = username;
|
|
4568
|
+
saveSubscriptionCache(subName, cacheData);
|
|
4569
|
+
const proxies = parsed.proxies;
|
|
4570
|
+
const proxyGroups = parsed["proxy-groups"];
|
|
4571
|
+
return {
|
|
4572
|
+
proxies: proxies ? proxies.length : 0,
|
|
4573
|
+
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4574
|
+
userInfo,
|
|
4575
|
+
updateInterval,
|
|
4576
|
+
webPageUrl,
|
|
4577
|
+
username
|
|
4578
|
+
};
|
|
4579
|
+
}
|
|
4580
|
+
async function downloadMergedSubscription(urls, subName) {
|
|
4581
|
+
const responses = await Promise.all(
|
|
4582
|
+
urls.map(async (url, index) => {
|
|
5165
4583
|
try {
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
const info = await downloadSubscription(url, name);
|
|
5169
|
-
const repoUrl = githubRepoUrl(url);
|
|
5170
|
-
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
5171
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4584
|
+
const response = await HTTP_CLIENT2.get(url, { responseType: "text" });
|
|
4585
|
+
return { url, index, response, error: null };
|
|
5172
4586
|
} catch (e) {
|
|
5173
|
-
|
|
5174
|
-
process.exit(1);
|
|
4587
|
+
return { url, index, response: null, error: e };
|
|
5175
4588
|
}
|
|
4589
|
+
})
|
|
4590
|
+
);
|
|
4591
|
+
for (const r of responses) {
|
|
4592
|
+
if (r.error) {
|
|
4593
|
+
const maskedUrl = maskUrl(r.url);
|
|
4594
|
+
throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${r.index + 1} \u4E2A URL \u83B7\u53D6\u5931\u8D25: ${r.error.message}
|
|
4595
|
+
URL: ${maskedUrl}`);
|
|
5176
4596
|
}
|
|
5177
|
-
console.log("");
|
|
5178
|
-
await printSubscriptionList();
|
|
5179
|
-
return;
|
|
5180
4597
|
}
|
|
5181
|
-
|
|
5182
|
-
const
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
5196
|
-
} else {
|
|
5197
|
-
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
5198
|
-
}
|
|
5199
|
-
}
|
|
5200
|
-
if (ok === 0) process.exit(1);
|
|
5201
|
-
console.log("");
|
|
5202
|
-
await printSubscriptionList();
|
|
5203
|
-
return;
|
|
5204
|
-
}
|
|
5205
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
5206
|
-
const target = pickSingleSubscription(matches, name);
|
|
5207
|
-
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
5208
|
-
try {
|
|
5209
|
-
let info;
|
|
5210
|
-
if (isMultiUrl(target.url)) {
|
|
5211
|
-
const urls = splitUrls(target.url);
|
|
5212
|
-
info = await downloadMergedSubscription(urls, target.name);
|
|
5213
|
-
} else {
|
|
5214
|
-
info = await downloadSubscription(target.url, target.name);
|
|
4598
|
+
const parsed = responses.map((r, i) => {
|
|
4599
|
+
const content = r.response?.data;
|
|
4600
|
+
if (!content?.trim()) throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${i + 1} \u4E2A URL \u5185\u5BB9\u4E3A\u7A7A`);
|
|
4601
|
+
return parseYamlOrJson(content, `\u5408\u5E76\u8BA2\u9605\u7B2C ${i + 1} \u4E2A`);
|
|
4602
|
+
});
|
|
4603
|
+
const base = parsed[0];
|
|
4604
|
+
const baseProxies = base.proxies || [];
|
|
4605
|
+
const seenNames = new Set(baseProxies.map((p) => p.name));
|
|
4606
|
+
for (let i = 1; i < parsed.length; i++) {
|
|
4607
|
+
const extraProxies = parsed[i].proxies || [];
|
|
4608
|
+
for (const proxy of extraProxies) {
|
|
4609
|
+
if (!seenNames.has(proxy.name)) {
|
|
4610
|
+
baseProxies.push(proxy);
|
|
4611
|
+
seenNames.add(proxy.name);
|
|
5215
4612
|
}
|
|
5216
|
-
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
|
|
5217
|
-
} catch (e) {
|
|
5218
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5219
|
-
process.exit(1);
|
|
5220
4613
|
}
|
|
5221
|
-
console.log("");
|
|
5222
|
-
await printSubscriptionList();
|
|
5223
|
-
return;
|
|
5224
4614
|
}
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
4615
|
+
base.proxies = baseProxies;
|
|
4616
|
+
const mergedContent = jsYaml.dump(base, YAML_DUMP_OPTS);
|
|
4617
|
+
saveSubscriptionRawConfig(subName, mergedContent);
|
|
4618
|
+
const firstHeaders = responses[0].response?.headers;
|
|
4619
|
+
const userInfo = parseUserInfo(firstHeaders?.get("subscription-userinfo") ?? null);
|
|
4620
|
+
const updateIntervalHeader = firstHeaders?.get("profile-update-interval");
|
|
4621
|
+
const updateInterval = updateIntervalHeader ? parseInt(updateIntervalHeader, 10) : null;
|
|
4622
|
+
const webPageUrl = firstHeaders?.get("profile-web-page-url") || null;
|
|
4623
|
+
const username = parseUsernameFromContentDisposition(firstHeaders?.get("content-disposition") ?? null);
|
|
4624
|
+
const cacheData = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4625
|
+
if (userInfo) {
|
|
4626
|
+
cacheData.upload = userInfo.upload;
|
|
4627
|
+
cacheData.download = userInfo.download;
|
|
4628
|
+
cacheData.total = userInfo.total;
|
|
4629
|
+
cacheData.expire = userInfo.expire;
|
|
4630
|
+
}
|
|
4631
|
+
if (updateInterval) cacheData.update_interval = updateInterval;
|
|
4632
|
+
if (webPageUrl) cacheData.web_page_url = webPageUrl;
|
|
4633
|
+
if (username) cacheData.username = username;
|
|
4634
|
+
saveSubscriptionCache(subName, cacheData);
|
|
4635
|
+
const proxyGroups = base["proxy-groups"];
|
|
4636
|
+
return {
|
|
4637
|
+
proxies: baseProxies.length,
|
|
4638
|
+
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4639
|
+
userInfo,
|
|
4640
|
+
updateInterval,
|
|
4641
|
+
webPageUrl,
|
|
4642
|
+
username
|
|
4643
|
+
};
|
|
4644
|
+
}
|
|
4645
|
+
function prepareConfigForStart(mode, subName = "default") {
|
|
4646
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4647
|
+
if (!rawContent) {
|
|
4648
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
|
|
4649
|
+
}
|
|
4650
|
+
const buildResult = buildConfig(rawContent, mode);
|
|
4651
|
+
if (buildResult.warnings.length > 0) {
|
|
4652
|
+
for (const warning of buildResult.warnings) {
|
|
4653
|
+
console.log(`${colors.yellow("\u81EA\u52A8\u4FEE\u590D:")} ${warning}`);
|
|
5260
4654
|
}
|
|
5261
4655
|
console.log("");
|
|
5262
|
-
await printSubscriptionList();
|
|
5263
|
-
return;
|
|
5264
4656
|
}
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
4657
|
+
writeMihomoConfig(buildResult.config);
|
|
4658
|
+
writeDebugConfig(buildResult);
|
|
4659
|
+
const proxies = buildResult.config.proxies;
|
|
4660
|
+
const proxyGroups = buildResult.config["proxy-groups"];
|
|
4661
|
+
return {
|
|
4662
|
+
proxies: proxies ? proxies.length : 0,
|
|
4663
|
+
proxyGroups: proxyGroups ? proxyGroups.length : 0
|
|
4664
|
+
};
|
|
4665
|
+
}
|
|
4666
|
+
function needsAutoUpdate(sub) {
|
|
4667
|
+
if (!sub.updated_at) return true;
|
|
4668
|
+
const lastUpdate = new Date(sub.updated_at).getTime();
|
|
4669
|
+
if (Number.isNaN(lastUpdate)) return true;
|
|
4670
|
+
const intervalHours = sub.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4671
|
+
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
4672
|
+
return Date.now() - lastUpdate > intervalMs;
|
|
4673
|
+
}
|
|
4674
|
+
async function tryUpdateOne(sub) {
|
|
4675
|
+
try {
|
|
4676
|
+
let info;
|
|
4677
|
+
if (isMultiUrl(sub.url)) {
|
|
4678
|
+
info = await downloadMergedSubscription(splitUrls(sub.url), sub.name);
|
|
5276
4679
|
} else {
|
|
5277
|
-
|
|
5278
|
-
}
|
|
5279
|
-
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
5280
|
-
let webPageUrl = cached?.web_page_url;
|
|
5281
|
-
if (!webPageUrl) {
|
|
5282
|
-
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
5283
|
-
try {
|
|
5284
|
-
await downloadSubscription(target.url, target.name);
|
|
5285
|
-
const cache = readSubscriptionCache();
|
|
5286
|
-
if (cache[target.name]?.web_page_url) {
|
|
5287
|
-
webPageUrl = cache[target.name].web_page_url;
|
|
5288
|
-
} else {
|
|
5289
|
-
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
5290
|
-
process.exit(1);
|
|
5291
|
-
}
|
|
5292
|
-
} catch (e) {
|
|
5293
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5294
|
-
process.exit(1);
|
|
5295
|
-
}
|
|
4680
|
+
info = await downloadSubscription(sub.url, sub.name);
|
|
5296
4681
|
}
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
4682
|
+
return { name: sub.name, success: true, proxies: info.proxies, proxyGroups: info.proxyGroups };
|
|
4683
|
+
} catch (e) {
|
|
4684
|
+
return { name: sub.name, success: false, error: e.message };
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
async function autoUpdateStaleSubscription() {
|
|
4688
|
+
const allSubs = getSubscriptionsWithCache();
|
|
4689
|
+
const staleSubs = allSubs.filter(needsAutoUpdate);
|
|
4690
|
+
if (staleSubs.length === 0) {
|
|
4691
|
+
return { total: 0, updated: 0, failed: 0 };
|
|
4692
|
+
}
|
|
4693
|
+
if (staleSubs.length === 1) {
|
|
4694
|
+
const sub = staleSubs[0];
|
|
4695
|
+
const interval = sub.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4696
|
+
console.log(`\u8BA2\u9605 "${sub.name}" \u8D85\u8FC7 ${interval} \u5C0F\u65F6\u672A\u66F4\u65B0\uFF0C\u6B63\u5728\u66F4\u65B0...`);
|
|
4697
|
+
} else {
|
|
4698
|
+
console.log(`\u68C0\u67E5\u5230 ${staleSubs.length} \u4E2A\u8BA2\u9605\u9700\u8981\u66F4\u65B0\uFF0C\u6B63\u5728\u5E76\u884C\u66F4\u65B0...`);
|
|
4699
|
+
}
|
|
4700
|
+
const results = await Promise.all(staleSubs.map(tryUpdateOne));
|
|
4701
|
+
let updatedCount = 0;
|
|
4702
|
+
for (const r of results) {
|
|
4703
|
+
if (r.success) {
|
|
4704
|
+
updatedCount++;
|
|
4705
|
+
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4706
|
+
} else {
|
|
4707
|
+
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
5301
4708
|
}
|
|
5302
|
-
return;
|
|
5303
4709
|
}
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
4710
|
+
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4711
|
+
}
|
|
4712
|
+
var API_BASE = `http://${BASE_CONFIG["external-controller"]}`;
|
|
4713
|
+
var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
4714
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = API_BASE) {
|
|
4715
|
+
const encodedName = encodeURIComponent(proxyName);
|
|
4716
|
+
const url = `${apiBase}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4717
|
+
try {
|
|
4718
|
+
const response = await client.get(url);
|
|
4719
|
+
const data = JSON.parse(response.data);
|
|
4720
|
+
if (data.delay && data.delay > 0) {
|
|
4721
|
+
return { name: proxyName, delay: data.delay };
|
|
5314
4722
|
}
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
const
|
|
5318
|
-
|
|
5319
|
-
if (
|
|
5320
|
-
|
|
4723
|
+
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
4724
|
+
} catch (e) {
|
|
4725
|
+
const err = e;
|
|
4726
|
+
let errorMsg = "timeout";
|
|
4727
|
+
if (err.response?.data?.message) {
|
|
4728
|
+
errorMsg = String(err.response.data.message);
|
|
4729
|
+
} else if (err.message) {
|
|
4730
|
+
errorMsg = err.message;
|
|
5321
4731
|
}
|
|
5322
|
-
|
|
5323
|
-
await printSubscriptionList({ autoUpdate: false });
|
|
5324
|
-
return;
|
|
4732
|
+
return { name: proxyName, delay: null, error: errorMsg };
|
|
5325
4733
|
}
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
return autoCleanSubscription(target.name, {
|
|
5333
|
-
timeout,
|
|
5334
|
-
concurrency,
|
|
5335
|
-
apiBase,
|
|
5336
|
-
onResult: printTestResult
|
|
5337
|
-
});
|
|
5338
|
-
});
|
|
5339
|
-
console.log("");
|
|
5340
|
-
console.log(formatTestSummary(result.summary));
|
|
5341
|
-
if (result.skipped) {
|
|
5342
|
-
console.log("");
|
|
5343
|
-
console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
|
|
5344
|
-
} else if (result.removedProxies > 0) {
|
|
5345
|
-
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
5346
|
-
const status = getStatus();
|
|
5347
|
-
if (status.running) {
|
|
5348
|
-
console.log("");
|
|
5349
|
-
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
5350
|
-
}
|
|
5351
|
-
}
|
|
5352
|
-
return;
|
|
4734
|
+
}
|
|
4735
|
+
async function testSubscriptionProxies(subName, options = {}) {
|
|
4736
|
+
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, apiBase = API_BASE, onResult } = options;
|
|
4737
|
+
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4738
|
+
if (proxies.length === 0) {
|
|
4739
|
+
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
5353
4740
|
}
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
});
|
|
5367
|
-
console.log("");
|
|
5368
|
-
console.log(formatTestSummary(summary));
|
|
5369
|
-
return;
|
|
4741
|
+
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
4742
|
+
const results = new Array(proxies.length);
|
|
4743
|
+
let completedCount = 0;
|
|
4744
|
+
let nextIndex = 0;
|
|
4745
|
+
async function runNext() {
|
|
4746
|
+
while (nextIndex < proxies.length) {
|
|
4747
|
+
const idx = nextIndex++;
|
|
4748
|
+
const result = await testProxyDelay(proxies[idx].name, timeout, testUrl, client, apiBase);
|
|
4749
|
+
results[idx] = result;
|
|
4750
|
+
onResult?.(result, completedCount, proxies.length);
|
|
4751
|
+
completedCount++;
|
|
4752
|
+
}
|
|
5370
4753
|
}
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
4754
|
+
const workers = Array.from({ length: Math.min(concurrency, proxies.length) }, () => runNext());
|
|
4755
|
+
await Promise.all(workers);
|
|
4756
|
+
const alive = results.filter((r) => r.delay !== null).length;
|
|
4757
|
+
return { total: results.length, alive, dead: results.length - alive, results };
|
|
5374
4758
|
}
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
const
|
|
5379
|
-
|
|
5380
|
-
const
|
|
5381
|
-
if (
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
}
|
|
5388
|
-
console.log(colors.cyan("\u6392\u540D:"));
|
|
5389
|
-
console.log("");
|
|
5390
|
-
const namedResults = valid.map((r) => {
|
|
5391
|
-
const idx = sourceOrder.get(r.name) ?? 0;
|
|
5392
|
-
return { ...r, displayName: `${String(idx + 1).padStart(2, "0")}-${r.name}` };
|
|
5393
|
-
});
|
|
5394
|
-
const nameWidth = Math.max(12, ...namedResults.map((r) => r.displayName.length));
|
|
5395
|
-
const h = (s, w) => s + " ".repeat(Math.max(0, w - displayWidth(s)));
|
|
5396
|
-
console.log(
|
|
5397
|
-
` ${"#".padStart(3)} ${h("\u540D\u79F0", nameWidth)} ${h("\u5B58\u6D3B\u7387", 8)} ${h("\u5B58\u6D3B", 6)} ${h("\u603B\u6570", 6)} ${h("\u5206\u7EC4", 6)} ${h("\u4E2D\u4F4D", 7)} ${h("\u5E73\u5747", 7)}`
|
|
5398
|
-
);
|
|
5399
|
-
console.log(
|
|
5400
|
-
` ${"\u2500".repeat(3)} ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(8)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(7)} ${"\u2500".repeat(7)}`
|
|
5401
|
-
);
|
|
5402
|
-
for (let i = 0; i < namedResults.length; i++) {
|
|
5403
|
-
const r = namedResults[i];
|
|
5404
|
-
const rate = (r.alive / r.totalProxies * 100).toFixed(1);
|
|
5405
|
-
const rateColor = r.alive / r.totalProxies > 0.3 ? colors.green : colors.yellow;
|
|
5406
|
-
const groups = r.proxyGroups > 0 ? String(r.proxyGroups) : "-";
|
|
5407
|
-
console.log(
|
|
5408
|
-
` ${String(i + 1).padStart(3)} ${r.displayName.padEnd(nameWidth)} ${rateColor(h(`${rate}%`, 8))} ${String(r.alive).padEnd(6)} ${String(r.totalProxies).padEnd(6)} ${groups.padEnd(6)} ${h(`${r.medianDelay}ms`, 7)} ${h(`${r.avgDelay}ms`, 7)}`
|
|
5409
|
-
);
|
|
4759
|
+
function normalizeProxyNamesBeforeSave(parsed) {
|
|
4760
|
+
const { proxies, proxyGroups } = parsed;
|
|
4761
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
4762
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
4763
|
+
for (const proxy of proxies) {
|
|
4764
|
+
const shortened = proxy.name.replace(/_github\.com\/[^_]+/, "");
|
|
4765
|
+
if (shortened !== proxy.name && !usedNames.has(shortened)) {
|
|
4766
|
+
renameMap.set(proxy.name, shortened);
|
|
4767
|
+
usedNames.add(shortened);
|
|
4768
|
+
} else {
|
|
4769
|
+
usedNames.add(proxy.name);
|
|
4770
|
+
}
|
|
5410
4771
|
}
|
|
5411
|
-
|
|
5412
|
-
const
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
const names = failed.map((r) => `${String((sourceOrder.get(r.name) ?? 0) + 1).padStart(2, "0")}-${r.name}`);
|
|
5416
|
-
console.log(colors.gray(`\u4E0B\u8F7D\u5931\u8D25: ${names.join(", ")}`));
|
|
4772
|
+
if (renameMap.size === 0) return 0;
|
|
4773
|
+
for (const proxy of proxies) {
|
|
4774
|
+
const newName = renameMap.get(proxy.name);
|
|
4775
|
+
if (newName) proxy.name = newName;
|
|
5417
4776
|
}
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
4777
|
+
for (const group of proxyGroups) {
|
|
4778
|
+
if (Array.isArray(group.proxies)) {
|
|
4779
|
+
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4780
|
+
}
|
|
5421
4781
|
}
|
|
4782
|
+
return renameMap.size;
|
|
5422
4783
|
}
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
const
|
|
5430
|
-
const
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
for (const s of allSources) console.log(` ${s.name}`);
|
|
5441
|
-
process.exit(1);
|
|
4784
|
+
function cleanDeadProxies(parsed, deadNames) {
|
|
4785
|
+
const { proxies, proxyGroups } = parsed;
|
|
4786
|
+
const originalCount = proxies.length;
|
|
4787
|
+
parsed.proxies = proxies.filter((p) => !deadNames.has(p.name));
|
|
4788
|
+
const removedProxies = originalCount - parsed.proxies.length;
|
|
4789
|
+
let updatedGroups = 0;
|
|
4790
|
+
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
4791
|
+
for (const group of proxyGroups) {
|
|
4792
|
+
if (Array.isArray(group.proxies)) {
|
|
4793
|
+
const before = group.proxies.length;
|
|
4794
|
+
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
4795
|
+
if (group.proxies.length < before) {
|
|
4796
|
+
updatedGroups++;
|
|
4797
|
+
}
|
|
4798
|
+
if (group.proxies.length === 0) {
|
|
4799
|
+
removedGroupNames.add(group.name);
|
|
4800
|
+
}
|
|
5442
4801
|
}
|
|
5443
4802
|
}
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
const downloaded = await downloadAllSources(sources, (name, ok, count, groups, error) => {
|
|
5450
|
-
const idx = String((sourceOrder.get(name) ?? 0) + 1).padStart(2, "0");
|
|
5451
|
-
if (ok) {
|
|
5452
|
-
const groupsInfo = groups > 0 ? ` ${groups}\u7EC4` : "";
|
|
5453
|
-
console.log(` ${colors.green("\u2713")} ${idx}-${name}: ${count} \u4E2A\u8282\u70B9${groupsInfo}`);
|
|
5454
|
-
} else {
|
|
5455
|
-
console.log(` ${colors.red("\u2717")} ${idx}-${name}: ${colors.gray(error || "\u5931\u8D25")}`);
|
|
4803
|
+
if (removedGroupNames.size > 0) {
|
|
4804
|
+
parsed.proxyGroups = proxyGroups.filter((g) => !removedGroupNames.has(g.name));
|
|
4805
|
+
for (const group of parsed.proxyGroups) {
|
|
4806
|
+
if (Array.isArray(group.proxies)) {
|
|
4807
|
+
group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
|
|
5456
4808
|
}
|
|
5457
|
-
});
|
|
5458
|
-
const allProxies = downloaded.flatMap((d) => d.proxies);
|
|
5459
|
-
const successSources = downloaded.filter((d) => d.proxies.length > 0);
|
|
5460
|
-
if (allProxies.length === 0) {
|
|
5461
|
-
console.log("");
|
|
5462
|
-
console.log(colors.red("\u6240\u6709\u8BA2\u9605\u6E90\u4E0B\u8F7D\u5931\u8D25\u6216\u65E0\u8282\u70B9"));
|
|
5463
|
-
return;
|
|
5464
4809
|
}
|
|
5465
|
-
console.log("");
|
|
5466
|
-
console.log(`\u5171 ${allProxies.length} \u4E2A\u8282\u70B9\uFF0C\u6765\u81EA ${successSources.length} \u4E2A\u6E90`);
|
|
5467
|
-
console.log("");
|
|
5468
|
-
const filtered = buildMergedBenchConfig(allProxies);
|
|
5469
|
-
if (filtered > 0) {
|
|
5470
|
-
console.log(colors.gray(`\u8FC7\u6EE4 ${filtered} \u4E2A\u65E0\u6548\u8282\u70B9\uFF0C\u5269\u4F59 ${allProxies.length} \u4E2A`));
|
|
5471
|
-
}
|
|
5472
|
-
const survivingSet = new Set(allProxies);
|
|
5473
|
-
for (const d of downloaded) {
|
|
5474
|
-
d.proxies = d.proxies.filter((p) => survivingSet.has(p));
|
|
5475
|
-
}
|
|
5476
|
-
const benchPort = BENCH_CONFIG["mixed-port"];
|
|
5477
|
-
const benchApi = BENCH_CONFIG["external-controller"];
|
|
5478
|
-
console.log(colors.cyan("\u542F\u52A8\u6D4B\u8BD5\u5B9E\u4F8B..."));
|
|
5479
|
-
await startBenchInstance();
|
|
5480
|
-
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (\u7AEF\u53E3 ${benchPort}/${benchApi.split(":")[1]})`);
|
|
5481
|
-
console.log("");
|
|
5482
|
-
console.log(colors.cyan("\u6D4B\u8BD5\u8282\u70B9\u5EF6\u8FDF..."));
|
|
5483
|
-
const allNames = allProxies.map((p) => p.name);
|
|
5484
|
-
const testResults = await testBenchProxies(allNames, {
|
|
5485
|
-
timeout,
|
|
5486
|
-
concurrency,
|
|
5487
|
-
onBatch: (batch, total, alive, tested, median) => {
|
|
5488
|
-
const pct = (tested / allNames.length * 100).toFixed(0);
|
|
5489
|
-
process.stdout.write(`\r \u6279\u6B21 ${batch}/${total} \u8FDB\u5EA6 ${pct}% \u5DF2\u6D4B ${tested} \u5B58\u6D3B ${colors.green(String(alive))} \u4E2D\u4F4D\u5EF6\u8FDF ${median}ms`);
|
|
5490
|
-
}
|
|
5491
|
-
});
|
|
5492
|
-
process.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
5493
|
-
const totalAlive = testResults.filter((r) => r.delay !== null).length;
|
|
5494
|
-
const summary = { alive: totalAlive, dead: testResults.length - totalAlive, total: testResults.length };
|
|
5495
|
-
console.log(formatTestSummary(summary));
|
|
5496
|
-
console.log("");
|
|
5497
|
-
const resultsByName = new Map(testResults.map((r) => [r.name, r]));
|
|
5498
|
-
const results = downloaded.map((source) => computeSourceResult(source, resultsByName));
|
|
5499
|
-
printRanking(results, sourceOrder);
|
|
5500
|
-
} finally {
|
|
5501
|
-
stopBenchInstance();
|
|
5502
|
-
cleanupBenchDir();
|
|
5503
4810
|
}
|
|
4811
|
+
return { removedProxies, updatedGroups, removedGroups: removedGroupNames.size };
|
|
5504
4812
|
}
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
const
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
const
|
|
5524
|
-
|
|
5525
|
-
|
|
4813
|
+
async function autoCleanSubscription(subName, options = {}) {
|
|
4814
|
+
const parsed = loadSubscriptionConfig(subName);
|
|
4815
|
+
const { onResult, onRetryRound, ...testOptions } = options;
|
|
4816
|
+
const wrapOnResult = (round) => onResult ? (r, i, t) => onResult(r, i, t, round) : void 0;
|
|
4817
|
+
const summary = await testSubscriptionProxies(subName, {
|
|
4818
|
+
...testOptions,
|
|
4819
|
+
parsed,
|
|
4820
|
+
onResult: wrapOnResult(1)
|
|
4821
|
+
});
|
|
4822
|
+
let removedProxies = 0;
|
|
4823
|
+
let updatedGroups = 0;
|
|
4824
|
+
let removedGroups = 0;
|
|
4825
|
+
let skipped = false;
|
|
4826
|
+
if (summary.dead > 0) {
|
|
4827
|
+
if (summary.alive === 0 || summary.alive / summary.total < 0.01) {
|
|
4828
|
+
skipped = true;
|
|
4829
|
+
} else {
|
|
4830
|
+
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
4831
|
+
const deadProxies = parsed.proxies.filter((p) => deadNames.has(p.name));
|
|
4832
|
+
for (let retry = 0; retry < 2; retry++) {
|
|
4833
|
+
const round = retry + 2;
|
|
4834
|
+
const retryTargets = deadProxies.filter((p) => deadNames.has(p.name));
|
|
4835
|
+
if (retryTargets.length === 0) break;
|
|
4836
|
+
onRetryRound?.(round, retryTargets.length);
|
|
4837
|
+
const retryParsed = { raw: {}, proxies: retryTargets, proxyGroups: [] };
|
|
4838
|
+
const retrySummary = await testSubscriptionProxies(subName, {
|
|
4839
|
+
...testOptions,
|
|
4840
|
+
parsed: retryParsed,
|
|
4841
|
+
onResult: wrapOnResult(round)
|
|
4842
|
+
});
|
|
4843
|
+
for (const r of retrySummary.results) {
|
|
4844
|
+
if (r.delay !== null) {
|
|
4845
|
+
deadNames.delete(r.name);
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
5526
4848
|
}
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
if (key !== "root") {
|
|
5535
|
-
console.log(` ${key.padEnd(14)}${val.label}`);
|
|
4849
|
+
summary.dead = deadNames.size;
|
|
4850
|
+
summary.alive = summary.total - summary.dead;
|
|
4851
|
+
if (deadNames.size > 0) {
|
|
4852
|
+
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
4853
|
+
removedProxies = cleanResult.removedProxies;
|
|
4854
|
+
updatedGroups = cleanResult.updatedGroups;
|
|
4855
|
+
removedGroups = cleanResult.removedGroups;
|
|
5536
4856
|
}
|
|
5537
4857
|
}
|
|
5538
|
-
console.log("");
|
|
5539
|
-
process.exit(1);
|
|
5540
4858
|
}
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
|
|
5546
|
-
console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
|
|
5547
|
-
console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
|
|
5548
|
-
console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
|
|
5549
|
-
console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
|
|
5550
|
-
console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
|
|
5551
|
-
console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5552
|
-
console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5553
|
-
console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
|
|
5554
|
-
console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
|
|
5555
|
-
console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
|
|
5556
|
-
console.log("");
|
|
5557
|
-
console.log("\u6253\u5F00\u76EE\u5F55:");
|
|
5558
|
-
console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
|
|
5559
|
-
console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
|
|
5560
|
-
console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
|
|
5561
|
-
console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
|
|
5562
|
-
console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
|
|
5563
|
-
console.log("");
|
|
5564
|
-
console.log("\u73AF\u5883\u53D8\u91CF:");
|
|
5565
|
-
console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
|
|
5566
|
-
console.log("");
|
|
5567
|
-
}
|
|
5568
|
-
|
|
5569
|
-
// src/commands/help.ts
|
|
5570
|
-
function printShortHelp() {
|
|
5571
|
-
console.log(`
|
|
5572
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
5573
|
-
`);
|
|
5574
|
-
console.log(
|
|
5575
|
-
`\u5E38\u7528\u547D\u4EE4:
|
|
5576
|
-
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
5577
|
-
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
5578
|
-
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
5579
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
5580
|
-
`
|
|
5581
|
-
);
|
|
5582
|
-
}
|
|
5583
|
-
function printHelp() {
|
|
5584
|
-
console.log(
|
|
5585
|
-
`
|
|
5586
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}
|
|
5587
|
-
|
|
5588
|
-
\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh
|
|
5589
|
-
|
|
5590
|
-
\u7528\u6CD5:
|
|
5591
|
-
mihomo <\u547D\u4EE4> [\u9009\u9879]
|
|
5592
|
-
|
|
5593
|
-
${colors.cyan("\u63A7\u5236:")}
|
|
5594
|
-
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)
|
|
5595
|
-
${colors.bold("stop")} \u505C\u6B62\u4EE3\u7406
|
|
5596
|
-
${colors.bold("status")} \u67E5\u770B\u72B6\u6001
|
|
5597
|
-
|
|
5598
|
-
${colors.cyan("\u754C\u9762:")}
|
|
5599
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)
|
|
5600
|
-
${colors.bold("log")} [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09
|
|
5601
|
-
${colors.bold("logs")} [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09
|
|
5602
|
-
|
|
5603
|
-
${colors.cyan("\u8BA2\u9605:")}
|
|
5604
|
-
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
5605
|
-
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
5606
|
-
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
5607
|
-
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
5608
|
-
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
5609
|
-
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
5610
|
-
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
5611
|
-
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
5612
|
-
${colors.bold("test")} [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027
|
|
5613
|
-
${colors.bold("clean")} [-t ms] [-j N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F
|
|
5614
|
-
${colors.bold("bench")} [name] [-t ms] [-j N] \u6D4B\u8BD5\u514D\u8D39\u8BA2\u9605\u6E90\u8D28\u91CF\u6392\u540D
|
|
5615
|
-
|
|
5616
|
-
${colors.cyan("\u914D\u7F6E:")}
|
|
5617
|
-
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
5618
|
-
${colors.bold("overwrite")} on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E
|
|
5619
|
-
${colors.bold("directory")} \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09
|
|
5620
|
-
${colors.bold("directory")} open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|...
|
|
5621
|
-
|
|
5622
|
-
${colors.cyan("\u7CFB\u7EDF:")}
|
|
5623
|
-
${colors.bold("kernel")} [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09
|
|
5624
|
-
${colors.bold("update")} \u66F4\u65B0 mihomo-cli (npm install -g)
|
|
5625
|
-
${colors.bold("reset")} [\u76EE\u6807...] [--full] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8
|
|
5626
|
-
${colors.bold("help")}, -h \u663E\u793A\u5E2E\u52A9
|
|
5627
|
-
${colors.bold("version")}, -v \u663E\u793A\u7248\u672C
|
|
5628
|
-
|
|
5629
|
-
${colors.cyan("\u793A\u4F8B:")}
|
|
5630
|
-
mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F
|
|
5631
|
-
mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F
|
|
5632
|
-
mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)
|
|
5633
|
-
mihomo bench # \u6D4B\u8BD5\u6240\u6709\u514D\u8D39\u8BA2\u9605\u6E90
|
|
5634
|
-
mihomo ui # \u6253\u5F00 Web UI
|
|
5635
|
-
|
|
5636
|
-
${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}
|
|
5637
|
-
mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)
|
|
5638
|
-
tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo
|
|
5639
|
-
|
|
5640
|
-
${colors.cyan("\u6570\u636E\u76EE\u5F55:")}
|
|
5641
|
-
\u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E
|
|
5642
|
-
\u9ED8\u8BA4: ${USER_DATA_DIR}
|
|
5643
|
-
`
|
|
5644
|
-
);
|
|
5645
|
-
}
|
|
5646
|
-
function printVersion() {
|
|
5647
|
-
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
5648
|
-
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
5649
|
-
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
5650
|
-
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
4859
|
+
if (!skipped && removedProxies > 0) {
|
|
4860
|
+
saveSubscriptionConfig(subName, parsed);
|
|
4861
|
+
}
|
|
4862
|
+
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
5651
4863
|
}
|
|
5652
4864
|
|
|
5653
|
-
// src/
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
4865
|
+
// src/commands/status.ts
|
|
4866
|
+
function printStatus() {
|
|
4867
|
+
const status = getStatus();
|
|
4868
|
+
const info = getConfigInfo();
|
|
4869
|
+
const overwriteEnabled = isOverwriteEnabled();
|
|
4870
|
+
const overwriteFiles = listOverwriteFile().files;
|
|
4871
|
+
const activeSub = getActiveSubscription();
|
|
4872
|
+
console.log("");
|
|
4873
|
+
let modeLabel = "";
|
|
4874
|
+
if (info && status.running) {
|
|
4875
|
+
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
5663
4876
|
}
|
|
5664
|
-
const
|
|
5665
|
-
|
|
5666
|
-
|
|
4877
|
+
const statusText = status.running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
|
|
4878
|
+
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
|
|
4879
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
|
|
4880
|
+
if (status.pid) {
|
|
4881
|
+
console.log(`${colors.gray("PID: ")}${status.pid}`);
|
|
4882
|
+
if (status.processInfo) {
|
|
4883
|
+
console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
|
|
4884
|
+
}
|
|
5667
4885
|
}
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
};
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
};
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
if (
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
}
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
4886
|
+
if (info) {
|
|
4887
|
+
if (info.mixedPort) {
|
|
4888
|
+
console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
|
|
4889
|
+
} else {
|
|
4890
|
+
const ports = [];
|
|
4891
|
+
if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
|
|
4892
|
+
if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
|
|
4893
|
+
console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
if (activeSub) {
|
|
4897
|
+
let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
|
|
4898
|
+
if (info) {
|
|
4899
|
+
subLine += ` (${formatProxySummary(info)})`;
|
|
4900
|
+
}
|
|
4901
|
+
console.log(subLine);
|
|
4902
|
+
} else {
|
|
4903
|
+
console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
|
|
4904
|
+
}
|
|
4905
|
+
if (overwriteEnabled && overwriteFiles.length > 0) {
|
|
4906
|
+
const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
|
|
4907
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
|
|
4908
|
+
} else if (overwriteEnabled) {
|
|
4909
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
|
|
4910
|
+
} else {
|
|
4911
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
|
|
5692
4912
|
}
|
|
5693
|
-
|
|
5694
|
-
}
|
|
4913
|
+
console.log("");
|
|
4914
|
+
}
|
|
5695
4915
|
|
|
5696
|
-
//
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
return r;
|
|
5705
|
-
if (p1 && p2) {
|
|
5706
|
-
return compareSegments(p1.split("."), p2.split("."));
|
|
5707
|
-
} else if (p1 || p2) {
|
|
5708
|
-
return p1 ? -1 : 1;
|
|
5709
|
-
}
|
|
5710
|
-
return 0;
|
|
4916
|
+
// src/test-instance.ts
|
|
4917
|
+
import { spawn as spawn2 } from "child_process";
|
|
4918
|
+
import fs7 from "fs";
|
|
4919
|
+
import path5 from "path";
|
|
4920
|
+
var TEST_DIR = path5.join(USER_DATA_DIR, "test");
|
|
4921
|
+
var TEST_DIRS = {
|
|
4922
|
+
data: path5.join(TEST_DIR, "data"),
|
|
4923
|
+
runtime: path5.join(TEST_DIR, "runtime")
|
|
5711
4924
|
};
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
var
|
|
5718
|
-
function
|
|
5719
|
-
|
|
5720
|
-
|
|
4925
|
+
var TEST_PATHS = {
|
|
4926
|
+
configFile: path5.join(TEST_DIRS.runtime, "config.yaml"),
|
|
4927
|
+
pidFile: path5.join(TEST_DIRS.runtime, "pid"),
|
|
4928
|
+
logFile: path5.join(TEST_DIR, "test.log")
|
|
4929
|
+
};
|
|
4930
|
+
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
4931
|
+
function ensureTestDirs() {
|
|
4932
|
+
for (const dir of Object.values(TEST_DIRS)) {
|
|
4933
|
+
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
5721
4934
|
}
|
|
5722
|
-
return url;
|
|
5723
|
-
}
|
|
5724
|
-
function getArch() {
|
|
5725
|
-
const arch = process.arch;
|
|
5726
|
-
if (arch === "arm64") return "arm64";
|
|
5727
|
-
if (arch === "x64") return "amd64";
|
|
5728
|
-
return arch;
|
|
5729
4935
|
}
|
|
5730
|
-
function
|
|
5731
|
-
|
|
5732
|
-
const matchingAssets = assets.filter(
|
|
5733
|
-
(a) => a.name.startsWith(prefix) && a.name.endsWith(".gz") || a.name.startsWith(`${prefix}-`) && a.name.endsWith(".gz")
|
|
5734
|
-
);
|
|
5735
|
-
if (matchingAssets.length === 0) return null;
|
|
5736
|
-
if (matchingAssets.length === 1) return matchingAssets[0];
|
|
5737
|
-
const standardAsset = matchingAssets.find((a) => {
|
|
5738
|
-
const nameWithoutGz = a.name.slice(0, -3);
|
|
5739
|
-
const parts = nameWithoutGz.split("-");
|
|
5740
|
-
const lastPart = parts[parts.length - 1];
|
|
5741
|
-
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go");
|
|
5742
|
-
});
|
|
5743
|
-
return standardAsset || matchingAssets[0];
|
|
4936
|
+
function cleanupTestDir() {
|
|
4937
|
+
rmrf(TEST_DIR);
|
|
5744
4938
|
}
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
const
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F");
|
|
4939
|
+
function buildTestConfig(subName) {
|
|
4940
|
+
ensureTestDirs();
|
|
4941
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4942
|
+
if (!rawContent) {
|
|
4943
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
5751
4944
|
}
|
|
5752
|
-
const
|
|
5753
|
-
|
|
5754
|
-
)
|
|
5755
|
-
|
|
4945
|
+
const parsed = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4946
|
+
const proxies = (parsed.proxies || []).filter(isProxyValid);
|
|
4947
|
+
if (proxies.length === 0) {
|
|
4948
|
+
throw new Error(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
|
|
4949
|
+
}
|
|
4950
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
4951
|
+
for (const proxy of proxies) {
|
|
4952
|
+
const count = (nameCount.get(proxy.name) || 0) + 1;
|
|
4953
|
+
nameCount.set(proxy.name, count);
|
|
4954
|
+
if (count > 1) {
|
|
4955
|
+
proxy.name = `${proxy.name} #${count}`;
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
const config = {
|
|
4959
|
+
...TEST_CONFIG,
|
|
4960
|
+
proxies,
|
|
4961
|
+
"proxy-groups": [
|
|
4962
|
+
{
|
|
4963
|
+
name: "PROXY",
|
|
4964
|
+
type: "select",
|
|
4965
|
+
proxies: proxies.map((p) => p.name)
|
|
4966
|
+
}
|
|
4967
|
+
],
|
|
4968
|
+
rules: ["MATCH,PROXY"]
|
|
4969
|
+
};
|
|
4970
|
+
const content = jsYaml.dump(config, { indent: 2, lineWidth: -1, noCompatMode: true });
|
|
4971
|
+
fs7.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
5756
4972
|
}
|
|
5757
|
-
async function
|
|
5758
|
-
const
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
const
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
}
|
|
4973
|
+
async function startTestInstance() {
|
|
4974
|
+
const binary2 = PATHS.mihomoBinary;
|
|
4975
|
+
if (!fs7.existsSync(binary2)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
|
|
4976
|
+
stopTestInstance();
|
|
4977
|
+
const logFd = fs7.openSync(TEST_PATHS.logFile, "a");
|
|
4978
|
+
const child = spawn2(binary2, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
|
|
4979
|
+
detached: true,
|
|
4980
|
+
stdio: ["ignore", logFd, logFd]
|
|
4981
|
+
});
|
|
4982
|
+
fs7.closeSync(logFd);
|
|
4983
|
+
child.unref();
|
|
4984
|
+
const pid = child.pid;
|
|
4985
|
+
fs7.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
4986
|
+
const client = createHttpClient({ timeout: 2e3 });
|
|
4987
|
+
let ready = false;
|
|
4988
|
+
for (let i = 0; i < 60; i++) {
|
|
4989
|
+
if (!isProcessRunning(pid)) break;
|
|
5766
4990
|
try {
|
|
5767
|
-
|
|
4991
|
+
await client.get(`${TEST_API}/version`);
|
|
4992
|
+
ready = true;
|
|
4993
|
+
break;
|
|
5768
4994
|
} catch {
|
|
5769
|
-
|
|
4995
|
+
await sleep(500);
|
|
5770
4996
|
}
|
|
5771
4997
|
}
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
4998
|
+
if (!isProcessRunning(pid)) {
|
|
4999
|
+
let errorDetail = "";
|
|
5000
|
+
try {
|
|
5001
|
+
errorDetail = fs7.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
|
|
5002
|
+
} catch {
|
|
5003
|
+
}
|
|
5004
|
+
throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
5005
|
+
${errorDetail}` : ""}`);
|
|
5006
|
+
}
|
|
5007
|
+
if (!ready) {
|
|
5008
|
+
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
5009
|
+
}
|
|
5779
5010
|
}
|
|
5780
|
-
function
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5011
|
+
function stopTestInstance() {
|
|
5012
|
+
let pid;
|
|
5013
|
+
try {
|
|
5014
|
+
pid = parseInt(fs7.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
5015
|
+
} catch {
|
|
5016
|
+
return;
|
|
5017
|
+
}
|
|
5018
|
+
if (pid > 0 && isProcessRunning(pid)) {
|
|
5019
|
+
process.kill(pid, "SIGKILL");
|
|
5020
|
+
for (let i = 0; i < 20; i++) {
|
|
5021
|
+
if (!isProcessRunning(pid)) break;
|
|
5022
|
+
sleepSync(100);
|
|
5789
5023
|
}
|
|
5790
|
-
if (f === "mihomo") return fullPath;
|
|
5791
|
-
if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
|
|
5792
5024
|
}
|
|
5793
|
-
|
|
5025
|
+
try {
|
|
5026
|
+
fs7.unlinkSync(TEST_PATHS.pidFile);
|
|
5027
|
+
} catch {
|
|
5028
|
+
}
|
|
5794
5029
|
}
|
|
5795
|
-
async function
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
if (available) hint = `
|
|
5805
|
-
\u53EF\u7528\u7248\u672C: ${available}`;
|
|
5806
|
-
throw new Error(`\u672A\u627E\u5230\u5339\u914D\u7684\u5185\u6838\u6587\u4EF6
|
|
5807
|
-
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
5030
|
+
async function withTestInstance(subName, fn) {
|
|
5031
|
+
cleanupTestDir();
|
|
5032
|
+
buildTestConfig(subName);
|
|
5033
|
+
try {
|
|
5034
|
+
await startTestInstance();
|
|
5035
|
+
return await fn(TEST_API);
|
|
5036
|
+
} finally {
|
|
5037
|
+
stopTestInstance();
|
|
5038
|
+
cleanupTestDir();
|
|
5808
5039
|
}
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5040
|
+
}
|
|
5041
|
+
|
|
5042
|
+
// src/commands/subscription.ts
|
|
5043
|
+
var IS_TTY = process.stdout.isTTY === true;
|
|
5044
|
+
var BAR_WIDTH = 20;
|
|
5045
|
+
function createProgressPrinter() {
|
|
5046
|
+
let alive = 0;
|
|
5047
|
+
let dead = 0;
|
|
5048
|
+
let hasRetry = false;
|
|
5049
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
5050
|
+
function render(done, total) {
|
|
5051
|
+
if (!IS_TTY) return;
|
|
5052
|
+
const pct = Math.round(done / total * 100);
|
|
5053
|
+
const filled = Math.round(done / total * BAR_WIDTH);
|
|
5054
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
|
|
5055
|
+
process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
|
|
5814
5056
|
}
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5057
|
+
return {
|
|
5058
|
+
onResult(result, index, total, round = 1) {
|
|
5059
|
+
const prev = resultMap.get(result.name);
|
|
5060
|
+
if (prev) {
|
|
5061
|
+
if (prev.result.delay === null && result.delay !== null) {
|
|
5062
|
+
alive++;
|
|
5063
|
+
dead--;
|
|
5064
|
+
}
|
|
5065
|
+
} else {
|
|
5066
|
+
if (result.delay !== null) alive++;
|
|
5067
|
+
else dead++;
|
|
5068
|
+
}
|
|
5069
|
+
resultMap.set(result.name, { result, round });
|
|
5070
|
+
render(index + 1, total);
|
|
5071
|
+
},
|
|
5072
|
+
onRetryRound(round, count) {
|
|
5073
|
+
if (!hasRetry) {
|
|
5074
|
+
hasRetry = true;
|
|
5075
|
+
if (IS_TTY) {
|
|
5076
|
+
process.stdout.write("\n");
|
|
5077
|
+
}
|
|
5078
|
+
console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${resultMap.size} \u4E2A\u8282\u70B9) ---`);
|
|
5079
|
+
}
|
|
5080
|
+
if (IS_TTY) {
|
|
5081
|
+
process.stdout.write("\n");
|
|
5082
|
+
}
|
|
5083
|
+
console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
|
|
5084
|
+
},
|
|
5085
|
+
finish() {
|
|
5086
|
+
if (IS_TTY) {
|
|
5087
|
+
process.stdout.write("\n");
|
|
5088
|
+
}
|
|
5089
|
+
console.log("");
|
|
5090
|
+
const entries = [...resultMap.values()];
|
|
5091
|
+
entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
|
|
5092
|
+
const total = entries.length;
|
|
5093
|
+
console.log("\u8282\u70B9\u6700\u7EC8\u72B6\u6001:");
|
|
5094
|
+
for (let i = 0; i < entries.length; i++) {
|
|
5095
|
+
const { result, round } = entries[i];
|
|
5096
|
+
const prefix = `[${i + 1}/${total}]`;
|
|
5097
|
+
if (result.delay !== null) {
|
|
5098
|
+
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
5099
|
+
const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
|
|
5100
|
+
console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
|
|
5101
|
+
} else {
|
|
5102
|
+
console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
console.log("");
|
|
5106
|
+
}
|
|
5107
|
+
};
|
|
5108
|
+
}
|
|
5109
|
+
function formatCleanSummary(result) {
|
|
5110
|
+
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
5111
|
+
if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
|
|
5112
|
+
if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
|
|
5113
|
+
return parts.join(", ");
|
|
5114
|
+
}
|
|
5115
|
+
function formatTestSummary(summary) {
|
|
5116
|
+
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
5117
|
+
}
|
|
5118
|
+
function githubRepoUrl(rawUrl) {
|
|
5119
|
+
const match = rawUrl.match(/raw\.githubusercontent\.com\/([^/]+\/[^/]+)/);
|
|
5120
|
+
if (match) return `https://github.com/${match[1]}`;
|
|
5121
|
+
return null;
|
|
5122
|
+
}
|
|
5123
|
+
function resolveTestTarget(args) {
|
|
5124
|
+
const subs = getSubscriptions();
|
|
5125
|
+
if (subs.length === 0) {
|
|
5126
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
5127
|
+
process.exit(1);
|
|
5128
|
+
}
|
|
5129
|
+
const nameArg = getNonFlagArg(args, 2);
|
|
5130
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 2e3);
|
|
5131
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
5132
|
+
let target;
|
|
5133
|
+
if (nameArg) {
|
|
5134
|
+
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
5135
|
+
target = pickSingleSubscription(matches, nameArg);
|
|
5136
|
+
} else {
|
|
5137
|
+
const activeSub = getActiveSubscription();
|
|
5138
|
+
if (!activeSub) {
|
|
5139
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
5140
|
+
process.exit(1);
|
|
5824
5141
|
}
|
|
5825
|
-
|
|
5142
|
+
target = activeSub;
|
|
5826
5143
|
}
|
|
5827
|
-
|
|
5828
|
-
|
|
5144
|
+
return { target, timeout, concurrency };
|
|
5145
|
+
}
|
|
5146
|
+
async function printSubscriptionList(options) {
|
|
5147
|
+
if (options?.autoUpdate !== false) {
|
|
5148
|
+
const updateResult = await autoUpdateStaleSubscription();
|
|
5149
|
+
if (updateResult.total > 0) console.log("");
|
|
5829
5150
|
}
|
|
5830
|
-
|
|
5831
|
-
|
|
5151
|
+
const subs = getSubscriptionsWithCache();
|
|
5152
|
+
if (subs.length === 0) {
|
|
5153
|
+
console.log("\u6CA1\u6709\u8BA2\u9605");
|
|
5154
|
+
console.log("");
|
|
5155
|
+
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
5156
|
+
console.log("");
|
|
5157
|
+
return;
|
|
5832
5158
|
}
|
|
5833
|
-
const
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5159
|
+
const activeSub = getActiveSubscription();
|
|
5160
|
+
console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
|
|
5161
|
+
subs.forEach((s, i) => {
|
|
5162
|
+
const time = formatDate(s.updated_at);
|
|
5163
|
+
const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
|
|
5164
|
+
const mergeBadge = isMultiUrl(s.url) ? colors.cyan(` [\u5408\u5E76 ${splitUrls(s.url).length} \u6E90]`) : "";
|
|
5165
|
+
const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
5166
|
+
console.log(` ${i + 1}. ${s.name}${defaultMark}${mergeBadge}`);
|
|
5167
|
+
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
5168
|
+
if (s.username) {
|
|
5169
|
+
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
5843
5170
|
}
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5171
|
+
if (s.download !== void 0 || s.total !== void 0) {
|
|
5172
|
+
const used = (s.upload || 0) + (s.download || 0);
|
|
5173
|
+
const usedStr = formatBytes(used);
|
|
5174
|
+
const totalStr = formatBytes(s.total);
|
|
5175
|
+
let percentStr = "";
|
|
5176
|
+
if (s.total && s.total > 0) {
|
|
5177
|
+
const percent = Math.min(used / s.total * 100, 100);
|
|
5178
|
+
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
5179
|
+
}
|
|
5180
|
+
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
5848
5181
|
}
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
5852
|
-
if (!foundBinary) {
|
|
5853
|
-
try {
|
|
5854
|
-
fs8.unlinkSync(tempPath);
|
|
5855
|
-
} catch {
|
|
5182
|
+
if (s.expire !== void 0) {
|
|
5183
|
+
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
5856
5184
|
}
|
|
5857
|
-
|
|
5185
|
+
if (s.web_page_url) {
|
|
5186
|
+
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
5187
|
+
}
|
|
5188
|
+
});
|
|
5189
|
+
console.log("");
|
|
5190
|
+
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
5191
|
+
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
5192
|
+
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
5193
|
+
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
5194
|
+
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
5195
|
+
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
5196
|
+
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
5197
|
+
console.log("");
|
|
5198
|
+
}
|
|
5199
|
+
async function cmdSubscription(args) {
|
|
5200
|
+
const action = args[1];
|
|
5201
|
+
if (!action || action === "list") {
|
|
5202
|
+
await printSubscriptionList();
|
|
5203
|
+
return;
|
|
5858
5204
|
}
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5205
|
+
if (action === "add") {
|
|
5206
|
+
const url = args[2];
|
|
5207
|
+
const name = args[3] || "default";
|
|
5208
|
+
if (!url) {
|
|
5209
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
5210
|
+
process.exit(1);
|
|
5211
|
+
}
|
|
5212
|
+
if (isMultiUrl(url)) {
|
|
5213
|
+
const urls = splitUrls(url);
|
|
5214
|
+
for (const u of urls) {
|
|
5215
|
+
if (!u.startsWith("http")) {
|
|
5216
|
+
console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
|
|
5217
|
+
process.exit(1);
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
|
|
5863
5221
|
try {
|
|
5864
|
-
|
|
5865
|
-
|
|
5222
|
+
addSubscription(url, name);
|
|
5223
|
+
setDefaultSubscription(name);
|
|
5224
|
+
const info = await downloadMergedSubscription(urls, name);
|
|
5225
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
|
|
5226
|
+
} catch (e) {
|
|
5227
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5228
|
+
process.exit(1);
|
|
5229
|
+
}
|
|
5230
|
+
} else {
|
|
5231
|
+
if (!url.startsWith("http")) {
|
|
5232
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
5233
|
+
process.exit(1);
|
|
5234
|
+
}
|
|
5235
|
+
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
5236
|
+
try {
|
|
5237
|
+
addSubscription(url, name);
|
|
5238
|
+
setDefaultSubscription(name);
|
|
5239
|
+
const info = await downloadSubscription(url, name);
|
|
5240
|
+
const repoUrl = githubRepoUrl(url);
|
|
5241
|
+
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
5242
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
5243
|
+
} catch (e) {
|
|
5244
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5245
|
+
process.exit(1);
|
|
5866
5246
|
}
|
|
5867
5247
|
}
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
try {
|
|
5872
|
-
fs8.unlinkSync(tempPath);
|
|
5873
|
-
} catch {
|
|
5874
|
-
}
|
|
5875
|
-
clearKernelVersionCache();
|
|
5876
|
-
return { version: latest.tag_name, path: targetPath };
|
|
5877
|
-
}
|
|
5878
|
-
|
|
5879
|
-
// src/commands/kernel.ts
|
|
5880
|
-
async function cmdKernel(args) {
|
|
5881
|
-
const mirrorInfo = parseMirrorArg(args);
|
|
5882
|
-
const effectiveMirror = mirrorInfo.mirror;
|
|
5883
|
-
if (effectiveMirror) {
|
|
5884
|
-
const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
|
|
5885
|
-
console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
|
|
5886
|
-
}
|
|
5887
|
-
console.log("\n\u63D0\u793A: \u5982\u679C\u4E0B\u8F7D\u901F\u5EA6\u8FC7\u6162\u6216\u76F4\u8FDE\u5931\u8D25\uFF0C\u53EF\u4F7F\u7528 --mirror \u53C2\u6570\u901A\u8FC7\u955C\u50CF\u4E0B\u8F7D");
|
|
5888
|
-
console.log("\n\u7528\u6CD5:");
|
|
5889
|
-
console.log(" mihomo kernel # \u76F4\u8FDE");
|
|
5890
|
-
console.log(" mihomo kernel --mirror # \u4E0B\u8F7D\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF (v6.gh-proxy.org)");
|
|
5891
|
-
console.log(" mihomo kernel --mirror hk.gh-proxy.org # \u4E0B\u8F7D\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5892
|
-
console.log(" mihomo kernel --mirror-all # API\u8BF7\u6C42\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF");
|
|
5893
|
-
console.log(" mihomo kernel --mirror-all hk.gh-proxy.org # API\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5894
|
-
console.log("\n\u53EF\u7528\u955C\u50CF:");
|
|
5895
|
-
for (const m of AVAILABLE_MIRRORS) {
|
|
5896
|
-
const isCurrent = effectiveMirror && (effectiveMirror.includes(`//${m}/`) || effectiveMirror.includes(`//${m}:`) || effectiveMirror.endsWith(`//${m}`));
|
|
5897
|
-
console.log(` ${m}${isCurrent ? " (\u5F53\u524D)" : ""}`);
|
|
5248
|
+
console.log("");
|
|
5249
|
+
await printSubscriptionList();
|
|
5250
|
+
return;
|
|
5898
5251
|
}
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
console.log(`\u6700\u65B0: ${info.latest}`);
|
|
5906
|
-
if (!info.needsUpdate) {
|
|
5907
|
-
console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
5908
|
-
} else {
|
|
5909
|
-
console.log("\n\u6B63\u5728\u4E0B\u8F7D...");
|
|
5910
|
-
const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
|
|
5911
|
-
console.log(`
|
|
5912
|
-
\u5DF2\u66F4\u65B0\u5230 ${result.version}`);
|
|
5252
|
+
if (action === "update") {
|
|
5253
|
+
const name = args[2];
|
|
5254
|
+
const subs = getSubscriptions();
|
|
5255
|
+
if (subs.length === 0) {
|
|
5256
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
5257
|
+
process.exit(1);
|
|
5913
5258
|
}
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5259
|
+
if (!name) {
|
|
5260
|
+
console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
|
|
5261
|
+
const results = await Promise.all(subs.map(tryUpdateOne));
|
|
5262
|
+
let ok = 0;
|
|
5263
|
+
for (const r of results) {
|
|
5264
|
+
if (r.success) {
|
|
5265
|
+
ok++;
|
|
5266
|
+
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
5267
|
+
} else {
|
|
5268
|
+
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
5269
|
+
}
|
|
5921
5270
|
}
|
|
5922
|
-
if (
|
|
5923
|
-
|
|
5271
|
+
if (ok === 0) process.exit(1);
|
|
5272
|
+
console.log("");
|
|
5273
|
+
await printSubscriptionList();
|
|
5274
|
+
return;
|
|
5275
|
+
}
|
|
5276
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
5277
|
+
const target = pickSingleSubscription(matches, name);
|
|
5278
|
+
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
5279
|
+
try {
|
|
5280
|
+
let info;
|
|
5281
|
+
if (isMultiUrl(target.url)) {
|
|
5282
|
+
const urls = splitUrls(target.url);
|
|
5283
|
+
info = await downloadMergedSubscription(urls, target.name);
|
|
5284
|
+
} else {
|
|
5285
|
+
info = await downloadSubscription(target.url, target.name);
|
|
5286
|
+
}
|
|
5287
|
+
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
|
|
5288
|
+
} catch (e) {
|
|
5289
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5290
|
+
process.exit(1);
|
|
5291
|
+
}
|
|
5292
|
+
console.log("");
|
|
5293
|
+
await printSubscriptionList();
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5296
|
+
if (action === "use") {
|
|
5297
|
+
const name = args[2];
|
|
5298
|
+
const subs = getSubscriptions();
|
|
5299
|
+
if (!name) {
|
|
5300
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
5301
|
+
if (subs.length > 0) {
|
|
5302
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
5303
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
5924
5304
|
}
|
|
5305
|
+
process.exit(1);
|
|
5306
|
+
}
|
|
5307
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
5308
|
+
const target = pickSingleSubscription(matches, name);
|
|
5309
|
+
const currentDefault = getActiveSubscription();
|
|
5310
|
+
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
5311
|
+
if (isAlreadyDefault) {
|
|
5312
|
+
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
5313
|
+
console.log("");
|
|
5314
|
+
await printSubscriptionList();
|
|
5315
|
+
return;
|
|
5316
|
+
}
|
|
5317
|
+
const status = getStatus();
|
|
5318
|
+
const configInfo = getConfigInfo();
|
|
5319
|
+
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
5320
|
+
const success = setDefaultSubscription(target.name);
|
|
5321
|
+
if (success) {
|
|
5322
|
+
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
5323
|
+
} else {
|
|
5324
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
5325
|
+
process.exit(1);
|
|
5925
5326
|
}
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
if (hasFlag(args, "-o", "--open")) {
|
|
5934
|
-
openLogFile(logPath);
|
|
5327
|
+
if (status.running) {
|
|
5328
|
+
console.log("");
|
|
5329
|
+
await cmdStart(["start", currentMode]);
|
|
5330
|
+
return;
|
|
5331
|
+
}
|
|
5332
|
+
console.log("");
|
|
5333
|
+
await printSubscriptionList();
|
|
5935
5334
|
return;
|
|
5936
5335
|
}
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
let
|
|
5945
|
-
if (
|
|
5946
|
-
|
|
5336
|
+
if (action === "web" || action === "open") {
|
|
5337
|
+
const name = args[2];
|
|
5338
|
+
const subs = getSubscriptionsWithCache();
|
|
5339
|
+
if (subs.length === 0) {
|
|
5340
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
5341
|
+
process.exit(1);
|
|
5342
|
+
}
|
|
5343
|
+
let target;
|
|
5344
|
+
if (name) {
|
|
5345
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
5346
|
+
target = pickSingleSubscription(matches, name);
|
|
5947
5347
|
} else {
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5348
|
+
target = subs[0];
|
|
5349
|
+
}
|
|
5350
|
+
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
5351
|
+
let webPageUrl = cached?.web_page_url;
|
|
5352
|
+
if (!webPageUrl) {
|
|
5353
|
+
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
5354
|
+
try {
|
|
5355
|
+
await downloadSubscription(target.url, target.name);
|
|
5356
|
+
const cache = readSubscriptionCache();
|
|
5357
|
+
if (cache[target.name]?.web_page_url) {
|
|
5358
|
+
webPageUrl = cache[target.name].web_page_url;
|
|
5359
|
+
} else {
|
|
5360
|
+
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
5955
5361
|
process.exit(1);
|
|
5956
5362
|
}
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5363
|
+
} catch (e) {
|
|
5364
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5365
|
+
process.exit(1);
|
|
5960
5366
|
}
|
|
5961
5367
|
}
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5368
|
+
console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
|
|
5369
|
+
const opened = openUrl(webPageUrl);
|
|
5370
|
+
if (!opened) {
|
|
5371
|
+
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
5372
|
+
}
|
|
5373
|
+
return;
|
|
5374
|
+
}
|
|
5375
|
+
if (action === "remove" || action === "rm" || action === "delete") {
|
|
5376
|
+
const name = args[2];
|
|
5377
|
+
const subs = getSubscriptions();
|
|
5378
|
+
if (!name) {
|
|
5379
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
|
|
5380
|
+
if (subs.length > 0) {
|
|
5381
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
5382
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
5383
|
+
}
|
|
5965
5384
|
process.exit(1);
|
|
5966
5385
|
}
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5386
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
5387
|
+
const target = pickSingleSubscription(matches, name);
|
|
5388
|
+
const switchedTo = removeSubscription(target.name);
|
|
5389
|
+
console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
|
|
5390
|
+
if (switchedTo) {
|
|
5391
|
+
console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
|
|
5970
5392
|
}
|
|
5971
|
-
|
|
5393
|
+
console.log("");
|
|
5394
|
+
await printSubscriptionList({ autoUpdate: false });
|
|
5972
5395
|
return;
|
|
5973
5396
|
}
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5397
|
+
if (action === "clean") {
|
|
5398
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
5399
|
+
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
5400
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5401
|
+
console.log("");
|
|
5402
|
+
const progress = createProgressPrinter();
|
|
5403
|
+
const result = await withTestInstance(target.name, async (apiBase) => {
|
|
5404
|
+
return autoCleanSubscription(target.name, {
|
|
5405
|
+
timeout,
|
|
5406
|
+
concurrency,
|
|
5407
|
+
apiBase,
|
|
5408
|
+
onResult: progress.onResult,
|
|
5409
|
+
onRetryRound: progress.onRetryRound
|
|
5410
|
+
});
|
|
5411
|
+
});
|
|
5412
|
+
progress.finish();
|
|
5413
|
+
console.log(formatTestSummary(result.summary));
|
|
5414
|
+
if (result.skipped) {
|
|
5415
|
+
console.log("");
|
|
5416
|
+
console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
|
|
5417
|
+
} else if (result.removedProxies > 0) {
|
|
5418
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
5419
|
+
const status = getStatus();
|
|
5420
|
+
if (status.running) {
|
|
5421
|
+
console.log("");
|
|
5422
|
+
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5980
5425
|
return;
|
|
5981
5426
|
}
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
console.log(
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
5427
|
+
if (action === "test") {
|
|
5428
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
5429
|
+
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
5430
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5431
|
+
console.log("");
|
|
5432
|
+
const progress = createProgressPrinter();
|
|
5433
|
+
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
5434
|
+
return testSubscriptionProxies(target.name, {
|
|
5435
|
+
timeout,
|
|
5436
|
+
concurrency,
|
|
5437
|
+
apiBase,
|
|
5438
|
+
onResult: progress.onResult
|
|
5439
|
+
});
|
|
5440
|
+
});
|
|
5441
|
+
progress.finish();
|
|
5442
|
+
console.log(formatTestSummary(summary));
|
|
5443
|
+
return;
|
|
5444
|
+
}
|
|
5445
|
+
console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
|
|
5446
|
+
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
|
|
5447
|
+
process.exit(1);
|
|
5448
|
+
}
|
|
5449
|
+
|
|
5450
|
+
// src/commands/start.ts
|
|
5451
|
+
var AUTO_CLEAN_THRESHOLD = 50;
|
|
5452
|
+
function handleStopResult(result) {
|
|
5453
|
+
if (result.remaining && result.remaining.length > 0) {
|
|
5454
|
+
console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
|
|
5455
|
+
console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
|
|
5456
|
+
process.exit(1);
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
async function cmdStart(args) {
|
|
5460
|
+
if (!hasKernel()) {
|
|
5461
|
+
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
5462
|
+
process.exit(1);
|
|
5463
|
+
}
|
|
5464
|
+
const targetMode = args[1] === "tun" ? "tun" : "mixed";
|
|
5465
|
+
const sub = getActiveSubscription();
|
|
5466
|
+
if (!sub) {
|
|
5467
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
5468
|
+
process.exit(1);
|
|
5469
|
+
}
|
|
5470
|
+
await autoUpdateStaleSubscription();
|
|
5471
|
+
const status = getStatus();
|
|
5472
|
+
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
5473
|
+
if (hasProcess) {
|
|
5474
|
+
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
5475
|
+
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
5476
|
+
}
|
|
5477
|
+
handleStopResult(stop());
|
|
5478
|
+
if (hasProcess) {
|
|
5479
|
+
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
5480
|
+
`);
|
|
5481
|
+
}
|
|
5482
|
+
let configInfo;
|
|
5483
|
+
try {
|
|
5484
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
5485
|
+
} catch (e) {
|
|
5486
|
+
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
5487
|
+
process.exit(1);
|
|
5488
|
+
}
|
|
5489
|
+
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
5490
|
+
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
5491
|
+
try {
|
|
5492
|
+
const result = await start(targetMode);
|
|
5493
|
+
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
|
|
5494
|
+
} catch (e) {
|
|
5495
|
+
const msg = e.message;
|
|
5496
|
+
const lines = msg.split("\n");
|
|
5497
|
+
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
|
|
5498
|
+
if (lines.length > 1) {
|
|
5499
|
+
for (const line of lines.slice(1)) console.error(line);
|
|
6001
5500
|
}
|
|
5501
|
+
process.exit(1);
|
|
5502
|
+
}
|
|
5503
|
+
if (configInfo.proxies > AUTO_CLEAN_THRESHOLD) {
|
|
6002
5504
|
console.log("");
|
|
5505
|
+
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${AUTO_CLEAN_THRESHOLD}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
|
|
5506
|
+
console.log("");
|
|
5507
|
+
await sleep(1e3);
|
|
5508
|
+
const progress = createProgressPrinter();
|
|
5509
|
+
const cleanResult = await autoCleanSubscription(sub.name, {
|
|
5510
|
+
onResult: progress.onResult,
|
|
5511
|
+
onRetryRound: progress.onRetryRound
|
|
5512
|
+
});
|
|
5513
|
+
progress.finish();
|
|
5514
|
+
console.log(formatTestSummary(cleanResult.summary));
|
|
5515
|
+
if (cleanResult.skipped) {
|
|
5516
|
+
console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
|
|
5517
|
+
} else if (cleanResult.removedProxies > 0) {
|
|
5518
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
5519
|
+
console.log("");
|
|
5520
|
+
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
5521
|
+
handleStopResult(stop());
|
|
5522
|
+
try {
|
|
5523
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
5524
|
+
const result = await start(targetMode);
|
|
5525
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${result.pid}) \xB7 ${formatProxySummary(configInfo)}`);
|
|
5526
|
+
} catch (e) {
|
|
5527
|
+
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
5528
|
+
process.exit(1);
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
6003
5531
|
}
|
|
6004
|
-
|
|
6005
|
-
console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
|
|
6006
|
-
console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
|
|
6007
|
-
console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
|
|
6008
|
-
console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
|
|
6009
|
-
console.log("");
|
|
5532
|
+
printStatus();
|
|
6010
5533
|
}
|
|
6011
5534
|
|
|
6012
5535
|
// src/commands/overwrite.ts
|
|
6013
|
-
import path7 from "path";
|
|
6014
5536
|
function printOverwriteList() {
|
|
6015
5537
|
const info = listOverwriteFile();
|
|
6016
5538
|
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
@@ -6020,8 +5542,8 @@ function printOverwriteList() {
|
|
|
6020
5542
|
if (info.files.length === 0) {
|
|
6021
5543
|
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
6022
5544
|
console.log("");
|
|
6023
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${
|
|
6024
|
-
console.log(` \u6216 ${
|
|
5545
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path6.join(info.dir, "overwrite.yaml")}`);
|
|
5546
|
+
console.log(` \u6216 ${path6.join(info.dir, "overwrite.dns.yaml")}`);
|
|
6025
5547
|
console.log("");
|
|
6026
5548
|
} else {
|
|
6027
5549
|
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
@@ -6085,7 +5607,7 @@ async function cmdOverwrite(args) {
|
|
|
6085
5607
|
}
|
|
6086
5608
|
|
|
6087
5609
|
// src/commands/reset.ts
|
|
6088
|
-
import
|
|
5610
|
+
import fs8 from "fs";
|
|
6089
5611
|
import readline from "readline";
|
|
6090
5612
|
var RESET_TARGETS = [
|
|
6091
5613
|
{
|
|
@@ -6140,8 +5662,8 @@ var RESET_TARGETS = [
|
|
|
6140
5662
|
label: "\u8986\u5199",
|
|
6141
5663
|
paths: () => {
|
|
6142
5664
|
const dir = USER_DATA_DIR;
|
|
6143
|
-
if (!
|
|
6144
|
-
return
|
|
5665
|
+
if (!fs8.existsSync(dir)) return [];
|
|
5666
|
+
return fs8.readdirSync(dir).filter((f) => f === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(f)).map((f) => `${dir}/${f}`);
|
|
6145
5667
|
},
|
|
6146
5668
|
needsStop: false
|
|
6147
5669
|
}
|
|
@@ -6281,12 +5803,13 @@ async function cmdTest(args) {
|
|
|
6281
5803
|
console.log(`\u6D4B\u8BD5 "${activeSub.name}" \u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
6282
5804
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6283
5805
|
console.log("");
|
|
5806
|
+
const progress = createProgressPrinter();
|
|
6284
5807
|
const summary = await testSubscriptionProxies(activeSub.name, {
|
|
6285
5808
|
timeout,
|
|
6286
5809
|
concurrency,
|
|
6287
|
-
onResult:
|
|
5810
|
+
onResult: progress.onResult
|
|
6288
5811
|
});
|
|
6289
|
-
|
|
5812
|
+
progress.finish();
|
|
6290
5813
|
console.log(formatTestSummary(summary));
|
|
6291
5814
|
}
|
|
6292
5815
|
async function cmdClean(args) {
|
|
@@ -6297,12 +5820,14 @@ async function cmdClean(args) {
|
|
|
6297
5820
|
console.log(`\u6E05\u7406 "${activeSub.name}" \u5931\u8D25\u8282\u70B9...`);
|
|
6298
5821
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6299
5822
|
console.log("");
|
|
5823
|
+
const progress = createProgressPrinter();
|
|
6300
5824
|
const result = await autoCleanSubscription(activeSub.name, {
|
|
6301
5825
|
timeout,
|
|
6302
5826
|
concurrency,
|
|
6303
|
-
onResult:
|
|
5827
|
+
onResult: progress.onResult,
|
|
5828
|
+
onRetryRound: progress.onRetryRound
|
|
6304
5829
|
});
|
|
6305
|
-
|
|
5830
|
+
progress.finish();
|
|
6306
5831
|
console.log(formatTestSummary(result.summary));
|
|
6307
5832
|
if (result.skipped) {
|
|
6308
5833
|
console.log("");
|
|
@@ -6338,7 +5863,7 @@ function cmdUI(args) {
|
|
|
6338
5863
|
}
|
|
6339
5864
|
|
|
6340
5865
|
// src/commands/update.ts
|
|
6341
|
-
import { exec, spawn as
|
|
5866
|
+
import { exec, spawn as spawn3 } from "child_process";
|
|
6342
5867
|
import { promisify } from "util";
|
|
6343
5868
|
var execAsync = promisify(exec);
|
|
6344
5869
|
async function cmdUpdate() {
|
|
@@ -6347,7 +5872,7 @@ async function cmdUpdate() {
|
|
|
6347
5872
|
console.log("\u6B63\u5728\u66F4\u65B0 mihomo-cli...");
|
|
6348
5873
|
console.log("");
|
|
6349
5874
|
await new Promise((resolve) => {
|
|
6350
|
-
const npm =
|
|
5875
|
+
const npm = spawn3("npm", ["install", "-g", "mihomo-cli"], { stdio: "inherit" });
|
|
6351
5876
|
npm.on("close", (code) => {
|
|
6352
5877
|
if (code === 0) {
|
|
6353
5878
|
resolve();
|
|
@@ -6492,9 +6017,6 @@ async function main() {
|
|
|
6492
6017
|
case "overwrite":
|
|
6493
6018
|
await cmdOverwrite(args);
|
|
6494
6019
|
break;
|
|
6495
|
-
case "bench":
|
|
6496
|
-
await cmdBench(args);
|
|
6497
|
-
break;
|
|
6498
6020
|
case "test":
|
|
6499
6021
|
await cmdTest(args);
|
|
6500
6022
|
break;
|