mihomo-cli 2.6.3 → 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 +9 -0
- package/README.md +0 -4
- package/dist/index.js +1559 -2135
- 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,1814 +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 };
|
|
4455
|
-
}
|
|
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}`);
|
|
4463
|
-
}
|
|
4464
|
-
}
|
|
4465
|
-
const parsed = responses.map((r, i) => {
|
|
4466
|
-
const content = r.response?.data;
|
|
4467
|
-
if (!content?.trim()) throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${i + 1} \u4E2A URL \u5185\u5BB9\u4E3A\u7A7A`);
|
|
4468
|
-
return parseYamlOrJson(content, `\u5408\u5E76\u8BA2\u9605\u7B2C ${i + 1} \u4E2A`);
|
|
4469
|
-
});
|
|
4470
|
-
const base = parsed[0];
|
|
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);
|
|
4479
|
-
}
|
|
4480
|
-
}
|
|
4481
|
-
}
|
|
4482
|
-
base.proxies = baseProxies;
|
|
4483
|
-
const mergedContent = jsYaml.dump(base, YAML_DUMP_OPTS);
|
|
4484
|
-
saveSubscriptionRawConfig(subName, mergedContent);
|
|
4485
|
-
const firstHeaders = responses[0].response?.headers;
|
|
4486
|
-
const userInfo = parseUserInfo(firstHeaders?.get("subscription-userinfo") ?? null);
|
|
4487
|
-
const updateIntervalHeader = firstHeaders?.get("profile-update-interval");
|
|
4488
|
-
const updateInterval = updateIntervalHeader ? parseInt(updateIntervalHeader, 10) : null;
|
|
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}`);
|
|
4521
|
-
}
|
|
4522
|
-
console.log("");
|
|
4523
|
-
}
|
|
4524
|
-
writeMihomoConfig(buildResult.config);
|
|
4525
|
-
writeDebugConfig(buildResult);
|
|
4526
|
-
const proxies = buildResult.config.proxies;
|
|
4527
|
-
const proxyGroups = buildResult.config["proxy-groups"];
|
|
4528
|
-
return {
|
|
4529
|
-
proxies: proxies ? proxies.length : 0,
|
|
4530
|
-
proxyGroups: proxyGroups ? proxyGroups.length : 0
|
|
4531
|
-
};
|
|
4532
|
-
}
|
|
4533
|
-
function needsAutoUpdate(sub) {
|
|
4534
|
-
if (!sub.updated_at) return true;
|
|
4535
|
-
const lastUpdate = new Date(sub.updated_at).getTime();
|
|
4536
|
-
if (Number.isNaN(lastUpdate)) return true;
|
|
4537
|
-
const intervalHours = sub.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4538
|
-
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
4539
|
-
return Date.now() - lastUpdate > intervalMs;
|
|
4540
|
-
}
|
|
4541
|
-
async function tryUpdateOne(sub) {
|
|
4542
|
-
try {
|
|
4543
|
-
let info;
|
|
4544
|
-
if (isMultiUrl(sub.url)) {
|
|
4545
|
-
info = await downloadMergedSubscription(splitUrls(sub.url), sub.name);
|
|
4546
|
-
} else {
|
|
4547
|
-
info = await downloadSubscription(sub.url, sub.name);
|
|
4548
|
-
}
|
|
4549
|
-
return { name: sub.name, success: true, proxies: info.proxies, proxyGroups: info.proxyGroups };
|
|
4550
|
-
} catch (e) {
|
|
4551
|
-
return { name: sub.name, success: false, error: e.message };
|
|
4552
|
-
}
|
|
4553
|
-
}
|
|
4554
|
-
async function autoUpdateStaleSubscription() {
|
|
4555
|
-
const allSubs = getSubscriptionsWithCache();
|
|
4556
|
-
const staleSubs = allSubs.filter(needsAutoUpdate);
|
|
4557
|
-
if (staleSubs.length === 0) {
|
|
4558
|
-
return { total: 0, updated: 0, failed: 0 };
|
|
4559
|
-
}
|
|
4560
|
-
if (staleSubs.length === 1) {
|
|
4561
|
-
const sub = staleSubs[0];
|
|
4562
|
-
const interval = sub.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4563
|
-
console.log(`\u8BA2\u9605 "${sub.name}" \u8D85\u8FC7 ${interval} \u5C0F\u65F6\u672A\u66F4\u65B0\uFF0C\u6B63\u5728\u66F4\u65B0...`);
|
|
4564
|
-
} else {
|
|
4565
|
-
console.log(`\u68C0\u67E5\u5230 ${staleSubs.length} \u4E2A\u8BA2\u9605\u9700\u8981\u66F4\u65B0\uFF0C\u6B63\u5728\u5E76\u884C\u66F4\u65B0...`);
|
|
4566
|
-
}
|
|
4567
|
-
const results = await Promise.all(staleSubs.map(tryUpdateOne));
|
|
4568
|
-
let updatedCount = 0;
|
|
4569
|
-
for (const r of results) {
|
|
4570
|
-
if (r.success) {
|
|
4571
|
-
updatedCount++;
|
|
4572
|
-
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4573
|
-
} else {
|
|
4574
|
-
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
4575
|
-
}
|
|
4576
|
-
}
|
|
4577
|
-
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4578
|
-
}
|
|
4579
|
-
var API_BASE = `http://${BASE_CONFIG["external-controller"]}`;
|
|
4580
|
-
var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
4581
|
-
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = API_BASE) {
|
|
4582
|
-
const encodedName = encodeURIComponent(proxyName);
|
|
4583
|
-
const url = `${apiBase}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
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
|
-
}
|
|
4601
|
-
}
|
|
4602
|
-
async function testSubscriptionProxies(subName, options = {}) {
|
|
4603
|
-
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, apiBase = API_BASE, onResult } = options;
|
|
4604
|
-
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4605
|
-
if (proxies.length === 0) {
|
|
4606
|
-
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
4607
|
-
}
|
|
4608
|
-
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
4609
|
-
const results = new Array(proxies.length);
|
|
4610
|
-
let completedCount = 0;
|
|
4611
|
-
let nextIndex = 0;
|
|
4612
|
-
async function runNext() {
|
|
4613
|
-
while (nextIndex < proxies.length) {
|
|
4614
|
-
const idx = nextIndex++;
|
|
4615
|
-
const result = await testProxyDelay(proxies[idx].name, timeout, testUrl, client, apiBase);
|
|
4616
|
-
results[idx] = result;
|
|
4617
|
-
onResult?.(result, completedCount, proxies.length);
|
|
4618
|
-
completedCount++;
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
const workers = Array.from({ length: Math.min(concurrency, proxies.length) }, () => runNext());
|
|
4622
|
-
await Promise.all(workers);
|
|
4623
|
-
const alive = results.filter((r) => r.delay !== null).length;
|
|
4624
|
-
return { total: results.length, alive, dead: results.length - alive, results };
|
|
4625
|
-
}
|
|
4626
|
-
function normalizeProxyNamesBeforeSave(parsed) {
|
|
4627
|
-
const { proxies, proxyGroups } = parsed;
|
|
4628
|
-
const renameMap = /* @__PURE__ */ new Map();
|
|
4629
|
-
const usedNames = /* @__PURE__ */ new Set();
|
|
4630
|
-
for (const proxy of proxies) {
|
|
4631
|
-
const shortened = proxy.name.replace(/_github\.com\/[^_]+/, "");
|
|
4632
|
-
if (shortened !== proxy.name && !usedNames.has(shortened)) {
|
|
4633
|
-
renameMap.set(proxy.name, shortened);
|
|
4634
|
-
usedNames.add(shortened);
|
|
4635
|
-
} else {
|
|
4636
|
-
usedNames.add(proxy.name);
|
|
4637
|
-
}
|
|
4638
|
-
}
|
|
4639
|
-
if (renameMap.size === 0) return 0;
|
|
4640
|
-
for (const proxy of proxies) {
|
|
4641
|
-
const newName = renameMap.get(proxy.name);
|
|
4642
|
-
if (newName) proxy.name = newName;
|
|
4643
|
-
}
|
|
4644
|
-
for (const group of proxyGroups) {
|
|
4645
|
-
if (Array.isArray(group.proxies)) {
|
|
4646
|
-
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4647
|
-
}
|
|
4648
|
-
}
|
|
4649
|
-
return renameMap.size;
|
|
4650
|
-
}
|
|
4651
|
-
function cleanDeadProxies(parsed, deadNames) {
|
|
4652
|
-
const { proxies, proxyGroups } = parsed;
|
|
4653
|
-
const originalCount = proxies.length;
|
|
4654
|
-
parsed.proxies = proxies.filter((p) => !deadNames.has(p.name));
|
|
4655
|
-
const removedProxies = originalCount - parsed.proxies.length;
|
|
4656
|
-
let updatedGroups = 0;
|
|
4657
|
-
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
4658
|
-
for (const group of proxyGroups) {
|
|
4659
|
-
if (Array.isArray(group.proxies)) {
|
|
4660
|
-
const before = group.proxies.length;
|
|
4661
|
-
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
4662
|
-
if (group.proxies.length < before) {
|
|
4663
|
-
updatedGroups++;
|
|
4664
|
-
}
|
|
4665
|
-
if (group.proxies.length === 0) {
|
|
4666
|
-
removedGroupNames.add(group.name);
|
|
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}`);
|
|
4667
3937
|
}
|
|
3938
|
+
return;
|
|
4668
3939
|
}
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
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}`);
|
|
4675
3947
|
}
|
|
3948
|
+
return;
|
|
4676
3949
|
}
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
const summary = await testSubscriptionProxies(subName, {
|
|
4685
|
-
...testOptions,
|
|
4686
|
-
parsed,
|
|
4687
|
-
onResult: wrapOnResult(1)
|
|
4688
|
-
});
|
|
4689
|
-
let removedProxies = 0;
|
|
4690
|
-
let updatedGroups = 0;
|
|
4691
|
-
let removedGroups = 0;
|
|
4692
|
-
let skipped = false;
|
|
4693
|
-
if (summary.dead > 0) {
|
|
4694
|
-
if (summary.alive === 0 || summary.alive / summary.total < 0.01) {
|
|
4695
|
-
skipped = true;
|
|
4696
|
-
} else {
|
|
4697
|
-
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
4698
|
-
const deadProxies = parsed.proxies.filter((p) => deadNames.has(p.name));
|
|
4699
|
-
for (let retry = 0; retry < 2; retry++) {
|
|
4700
|
-
const round = retry + 2;
|
|
4701
|
-
const retryTargets = deadProxies.filter((p) => deadNames.has(p.name));
|
|
4702
|
-
if (retryTargets.length === 0) break;
|
|
4703
|
-
onRetryRound?.(round, retryTargets.length);
|
|
4704
|
-
const retryParsed = { raw: {}, proxies: retryTargets, proxyGroups: [] };
|
|
4705
|
-
const retrySummary = await testSubscriptionProxies(subName, {
|
|
4706
|
-
...testOptions,
|
|
4707
|
-
parsed: retryParsed,
|
|
4708
|
-
onResult: wrapOnResult(round)
|
|
4709
|
-
});
|
|
4710
|
-
for (const r of retrySummary.results) {
|
|
4711
|
-
if (r.delay !== null) {
|
|
4712
|
-
deadNames.delete(r.name);
|
|
4713
|
-
}
|
|
4714
|
-
}
|
|
4715
|
-
}
|
|
4716
|
-
summary.dead = deadNames.size;
|
|
4717
|
-
summary.alive = summary.total - summary.dead;
|
|
4718
|
-
if (deadNames.size > 0) {
|
|
4719
|
-
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
4720
|
-
removedProxies = cleanResult.removedProxies;
|
|
4721
|
-
updatedGroups = cleanResult.updatedGroups;
|
|
4722
|
-
removedGroups = cleanResult.removedGroups;
|
|
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}`);
|
|
4723
3957
|
}
|
|
4724
3958
|
}
|
|
3959
|
+
console.log("");
|
|
3960
|
+
process.exit(1);
|
|
4725
3961
|
}
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
}
|
|
4729
|
-
|
|
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("");
|
|
4730
3988
|
}
|
|
4731
3989
|
|
|
4732
|
-
// src/
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
}
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
};
|
|
4746
|
-
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
4747
|
-
function ensureTestDirs() {
|
|
4748
|
-
for (const dir of Object.values(TEST_DIRS)) {
|
|
4749
|
-
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4750
|
-
}
|
|
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
|
+
);
|
|
4751
4003
|
}
|
|
4752
|
-
function
|
|
4753
|
-
|
|
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
|
+
);
|
|
4754
4064
|
}
|
|
4755
|
-
function
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
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}`);
|
|
4070
|
+
}
|
|
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");
|
|
4082
|
+
}
|
|
4083
|
+
const match = version.match(semver);
|
|
4084
|
+
if (!match) {
|
|
4085
|
+
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
|
4086
|
+
}
|
|
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;
|
|
4760
4111
|
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
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;
|
|
4765
4128
|
}
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
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;
|
|
4773
4140
|
}
|
|
4774
|
-
|
|
4775
|
-
...TEST_CONFIG,
|
|
4776
|
-
proxies,
|
|
4777
|
-
"proxy-groups": [
|
|
4778
|
-
{
|
|
4779
|
-
name: "PROXY",
|
|
4780
|
-
type: "select",
|
|
4781
|
-
proxies: proxies.map((p) => p.name)
|
|
4782
|
-
}
|
|
4783
|
-
],
|
|
4784
|
-
rules: ["MATCH,PROXY"]
|
|
4785
|
-
};
|
|
4786
|
-
const content = jsYaml.dump(config, { indent: 2, lineWidth: -1, noCompatMode: true });
|
|
4787
|
-
fs7.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
4141
|
+
return url;
|
|
4788
4142
|
}
|
|
4789
|
-
|
|
4790
|
-
const
|
|
4791
|
-
if (
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4143
|
+
function getArch() {
|
|
4144
|
+
const arch = process.arch;
|
|
4145
|
+
if (arch === "arm64") return "arm64";
|
|
4146
|
+
if (arch === "x64") return "amd64";
|
|
4147
|
+
return arch;
|
|
4148
|
+
}
|
|
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");
|
|
4797
4161
|
});
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
const
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
try {
|
|
4807
|
-
await client.get(`${TEST_API}/version`);
|
|
4808
|
-
ready = true;
|
|
4809
|
-
break;
|
|
4810
|
-
} catch {
|
|
4811
|
-
await sleep(500);
|
|
4812
|
-
}
|
|
4162
|
+
return standardAsset || matchingAssets[0];
|
|
4163
|
+
}
|
|
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");
|
|
4813
4170
|
}
|
|
4814
|
-
|
|
4815
|
-
|
|
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];
|
|
4175
|
+
}
|
|
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 {
|
|
4816
4185
|
try {
|
|
4817
|
-
|
|
4186
|
+
needsUpdate = compareVersions(latestVersion.replace(/^v/, ""), currentVersion.replace(/^v/, "")) > 0;
|
|
4818
4187
|
} catch {
|
|
4188
|
+
needsUpdate = latestVersion !== currentVersion;
|
|
4819
4189
|
}
|
|
4820
|
-
throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
4821
|
-
${errorDetail}` : ""}`);
|
|
4822
|
-
}
|
|
4823
|
-
if (!ready) {
|
|
4824
|
-
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
4825
|
-
}
|
|
4826
|
-
}
|
|
4827
|
-
function stopTestInstance() {
|
|
4828
|
-
let pid;
|
|
4829
|
-
try {
|
|
4830
|
-
pid = parseInt(fs7.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
4831
|
-
} catch {
|
|
4832
|
-
return;
|
|
4833
|
-
}
|
|
4834
|
-
if (pid > 0 && isProcessRunning(pid)) {
|
|
4835
|
-
process.kill(pid, "SIGKILL");
|
|
4836
|
-
for (let i = 0; i < 20; i++) {
|
|
4837
|
-
if (!isProcessRunning(pid)) break;
|
|
4838
|
-
sleepSync(100);
|
|
4839
|
-
}
|
|
4840
|
-
}
|
|
4841
|
-
try {
|
|
4842
|
-
fs7.unlinkSync(TEST_PATHS.pidFile);
|
|
4843
|
-
} catch {
|
|
4844
|
-
}
|
|
4845
|
-
}
|
|
4846
|
-
async function withTestInstance(subName, fn) {
|
|
4847
|
-
cleanupTestDir();
|
|
4848
|
-
buildTestConfig(subName);
|
|
4849
|
-
try {
|
|
4850
|
-
await startTestInstance();
|
|
4851
|
-
return await fn(TEST_API);
|
|
4852
|
-
} finally {
|
|
4853
|
-
stopTestInstance();
|
|
4854
|
-
cleanupTestDir();
|
|
4855
4190
|
}
|
|
4191
|
+
return {
|
|
4192
|
+
current: currentDisplay,
|
|
4193
|
+
latest: latestVersion,
|
|
4194
|
+
needsUpdate,
|
|
4195
|
+
assets: latest.assets,
|
|
4196
|
+
release: latest
|
|
4197
|
+
};
|
|
4856
4198
|
}
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
let modeLabel = "";
|
|
4867
|
-
if (info && status.running) {
|
|
4868
|
-
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
4869
|
-
}
|
|
4870
|
-
const statusText = status.running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
|
|
4871
|
-
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
|
|
4872
|
-
console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
|
|
4873
|
-
if (status.pid) {
|
|
4874
|
-
console.log(`${colors.gray("PID: ")}${status.pid}`);
|
|
4875
|
-
if (status.processInfo) {
|
|
4876
|
-
console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
|
|
4877
|
-
}
|
|
4878
|
-
}
|
|
4879
|
-
if (info) {
|
|
4880
|
-
if (info.mixedPort) {
|
|
4881
|
-
console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
|
|
4882
|
-
} else {
|
|
4883
|
-
const ports = [];
|
|
4884
|
-
if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
|
|
4885
|
-
if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
|
|
4886
|
-
console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
|
|
4887
|
-
}
|
|
4888
|
-
}
|
|
4889
|
-
if (activeSub) {
|
|
4890
|
-
let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
|
|
4891
|
-
if (info) {
|
|
4892
|
-
subLine += ` (${formatProxySummary(info)})`;
|
|
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;
|
|
4893
4208
|
}
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
|
|
4897
|
-
}
|
|
4898
|
-
if (overwriteEnabled && overwriteFiles.length > 0) {
|
|
4899
|
-
const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
|
|
4900
|
-
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
|
|
4901
|
-
} else if (overwriteEnabled) {
|
|
4902
|
-
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
|
|
4903
|
-
} else {
|
|
4904
|
-
console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
|
|
4209
|
+
if (f === "mihomo") return fullPath;
|
|
4210
|
+
if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
|
|
4905
4211
|
}
|
|
4906
|
-
|
|
4212
|
+
return null;
|
|
4907
4213
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
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}`);
|
|
4916
4227
|
}
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
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)`);
|
|
4922
4233
|
}
|
|
4923
|
-
const
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
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) {
|
|
4240
|
+
try {
|
|
4241
|
+
fs6.unlinkSync(tempPath);
|
|
4242
|
+
} catch {
|
|
4243
|
+
}
|
|
4244
|
+
throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
|
|
4928
4245
|
}
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
4932
|
-
if (hasProcess) {
|
|
4933
|
-
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
4934
|
-
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
4246
|
+
if (!fs6.existsSync(tempPath)) {
|
|
4247
|
+
throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
|
|
4935
4248
|
}
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
4939
|
-
`);
|
|
4249
|
+
if (progressCallback) {
|
|
4250
|
+
progressCallback("\u89E3\u538B\u5185\u6838...");
|
|
4940
4251
|
}
|
|
4941
|
-
|
|
4252
|
+
const extractPath = DIRS.kernel;
|
|
4253
|
+
let extractedBinary = null;
|
|
4942
4254
|
try {
|
|
4943
|
-
|
|
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
|
+
}
|
|
4944
4263
|
} catch (e) {
|
|
4945
|
-
|
|
4946
|
-
|
|
4264
|
+
try {
|
|
4265
|
+
fs6.unlinkSync(tempPath);
|
|
4266
|
+
} catch {
|
|
4267
|
+
}
|
|
4268
|
+
throw new Error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
|
|
4947
4269
|
}
|
|
4948
|
-
const
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
} catch (e) {
|
|
4954
|
-
const msg = e.message;
|
|
4955
|
-
const lines = msg.split("\n");
|
|
4956
|
-
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
|
|
4957
|
-
if (lines.length > 1) {
|
|
4958
|
-
for (const line of lines.slice(1)) console.error(line);
|
|
4270
|
+
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
4271
|
+
if (!foundBinary) {
|
|
4272
|
+
try {
|
|
4273
|
+
fs6.unlinkSync(tempPath);
|
|
4274
|
+
} catch {
|
|
4959
4275
|
}
|
|
4960
|
-
|
|
4276
|
+
throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
|
|
4961
4277
|
}
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
await sleep(1e3);
|
|
4967
|
-
const progress = createProgressPrinter();
|
|
4968
|
-
const cleanResult = await autoCleanSubscription(sub.name, {
|
|
4969
|
-
onResult: progress.onResult,
|
|
4970
|
-
onRetryRound: progress.onRetryRound
|
|
4971
|
-
});
|
|
4972
|
-
progress.finish();
|
|
4973
|
-
console.log(formatTestSummary(cleanResult.summary));
|
|
4974
|
-
if (cleanResult.skipped) {
|
|
4975
|
-
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"));
|
|
4976
|
-
} else if (cleanResult.removedProxies > 0) {
|
|
4977
|
-
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
4978
|
-
console.log("");
|
|
4979
|
-
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
4980
|
-
handleStopResult(stop());
|
|
4278
|
+
const targetPath = PATHS.mihomoBinary;
|
|
4279
|
+
if (foundBinary !== targetPath) {
|
|
4280
|
+
if (fs6.existsSync(targetPath)) {
|
|
4281
|
+
fs6.chmodSync(targetPath, 493);
|
|
4981
4282
|
try {
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${result.pid}) \xB7 ${formatProxySummary(configInfo)}`);
|
|
4985
|
-
} catch (e) {
|
|
4986
|
-
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
4987
|
-
process.exit(1);
|
|
4283
|
+
fs6.unlinkSync(targetPath);
|
|
4284
|
+
} catch {
|
|
4988
4285
|
}
|
|
4989
4286
|
}
|
|
4287
|
+
fs6.renameSync(foundBinary, targetPath);
|
|
4990
4288
|
}
|
|
4991
|
-
|
|
4289
|
+
fs6.chmodSync(targetPath, 493);
|
|
4290
|
+
try {
|
|
4291
|
+
fs6.unlinkSync(tempPath);
|
|
4292
|
+
} catch {
|
|
4293
|
+
}
|
|
4294
|
+
clearKernelVersionCache();
|
|
4295
|
+
return { version: latest.tag_name, path: targetPath };
|
|
4992
4296
|
}
|
|
4993
4297
|
|
|
4994
|
-
// src/commands/
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
const resultMap = /* @__PURE__ */ new Map();
|
|
5002
|
-
function render(done, total) {
|
|
5003
|
-
if (!IS_TTY) return;
|
|
5004
|
-
const pct = Math.round(done / total * 100);
|
|
5005
|
-
const filled = Math.round(done / total * BAR_WIDTH);
|
|
5006
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
|
|
5007
|
-
process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
|
|
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}`);
|
|
5008
4305
|
}
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
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...");
|
|
4320
|
+
try {
|
|
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}`);
|
|
5034
4340
|
}
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
finish() {
|
|
5038
|
-
if (IS_TTY) {
|
|
5039
|
-
process.stdout.write("\n");
|
|
4341
|
+
if (err.response.data.documentation_url) {
|
|
4342
|
+
console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
|
|
5040
4343
|
}
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
4344
|
+
}
|
|
4345
|
+
process.exit(1);
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
// src/commands/log.ts
|
|
4350
|
+
function cmdLog(args) {
|
|
4351
|
+
const logPath = getLogPath();
|
|
4352
|
+
if (hasFlag(args, "-o", "--open")) {
|
|
4353
|
+
openLogFile(logPath);
|
|
4354
|
+
return;
|
|
4355
|
+
}
|
|
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);
|
|
5055
4375
|
}
|
|
4376
|
+
logPath = archive.path;
|
|
4377
|
+
} else {
|
|
4378
|
+
logPath = getLogPathByName(targetName);
|
|
5056
4379
|
}
|
|
5057
|
-
console.log("");
|
|
5058
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);
|
|
4385
|
+
}
|
|
4386
|
+
if (openInViewer) {
|
|
4387
|
+
openLogFile(logPath);
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
viewLogWithTail(logPath, { follow: false, lines });
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
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";
|
|
4409
|
+
} else {
|
|
4410
|
+
archiveCounter++;
|
|
4411
|
+
num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
|
|
4412
|
+
}
|
|
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`);
|
|
4420
|
+
}
|
|
4421
|
+
console.log("");
|
|
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");
|
|
4428
|
+
console.log("");
|
|
4429
|
+
}
|
|
4430
|
+
|
|
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(",");
|
|
4440
|
+
}
|
|
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}"`);
|
|
4448
|
+
}
|
|
4449
|
+
const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4450
|
+
return {
|
|
4451
|
+
raw,
|
|
4452
|
+
proxies: raw.proxies || [],
|
|
4453
|
+
proxyGroups: raw["proxy-groups"] || []
|
|
5059
4454
|
};
|
|
5060
4455
|
}
|
|
5061
|
-
function
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
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));
|
|
5066
4461
|
}
|
|
5067
|
-
function
|
|
5068
|
-
|
|
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;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
return info;
|
|
5069
4474
|
}
|
|
5070
|
-
function
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
return null;
|
|
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;
|
|
5074
4482
|
}
|
|
5075
|
-
function
|
|
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`);
|
|
4487
|
+
return parts.join(", ");
|
|
4488
|
+
}
|
|
4489
|
+
function getActiveSubscription() {
|
|
5076
4490
|
const subs = getSubscriptions();
|
|
5077
|
-
if (subs.length === 0)
|
|
5078
|
-
|
|
5079
|
-
|
|
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;
|
|
5080
4497
|
}
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
const
|
|
5090
|
-
if (
|
|
5091
|
-
|
|
5092
|
-
|
|
4498
|
+
return subs[0];
|
|
4499
|
+
}
|
|
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);
|
|
5093
4513
|
}
|
|
5094
|
-
target = activeSub;
|
|
5095
4514
|
}
|
|
5096
|
-
|
|
4515
|
+
if (exact.length > 0) return exact;
|
|
4516
|
+
if (prefix.length > 0) return prefix;
|
|
4517
|
+
return includes;
|
|
5097
4518
|
}
|
|
5098
|
-
|
|
5099
|
-
if (options?.autoUpdate !== false) {
|
|
5100
|
-
const updateResult = await autoUpdateStaleSubscription();
|
|
5101
|
-
if (updateResult.total > 0) console.log("");
|
|
5102
|
-
}
|
|
5103
|
-
const subs = getSubscriptionsWithCache();
|
|
4519
|
+
function pickSingleSubscription(subs, pattern) {
|
|
5104
4520
|
if (subs.length === 0) {
|
|
5105
|
-
console.
|
|
5106
|
-
|
|
5107
|
-
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
5108
|
-
console.log("");
|
|
5109
|
-
return;
|
|
4521
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
|
|
4522
|
+
process.exit(1);
|
|
5110
4523
|
}
|
|
5111
|
-
|
|
5112
|
-
console.
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
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;
|
|
4532
|
+
try {
|
|
4533
|
+
response = await HTTP_CLIENT2.get(url, { responseType: "text" });
|
|
4534
|
+
} catch (e) {
|
|
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})`;
|
|
5122
4540
|
}
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
4541
|
+
errorMsg += `
|
|
4542
|
+
URL: ${maskedUrl}`;
|
|
4543
|
+
throw new Error(errorMsg);
|
|
4544
|
+
}
|
|
4545
|
+
const content = response.data;
|
|
4546
|
+
if (!content?.trim()) {
|
|
4547
|
+
throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4548
|
+
}
|
|
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;
|
|
4564
|
+
}
|
|
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) => {
|
|
4583
|
+
try {
|
|
4584
|
+
const response = await HTTP_CLIENT2.get(url, { responseType: "text" });
|
|
4585
|
+
return { url, index, response, error: null };
|
|
4586
|
+
} catch (e) {
|
|
4587
|
+
return { url, index, response: null, error: e };
|
|
5131
4588
|
}
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
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}`);
|
|
5139
4596
|
}
|
|
4597
|
+
}
|
|
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`);
|
|
5140
4602
|
});
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
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);
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
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
|
+
};
|
|
5150
4644
|
}
|
|
5151
|
-
function
|
|
5152
|
-
const
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
if (freeId === 0) {
|
|
5161
|
-
const sources = [freeSources[0], freeSources[1]];
|
|
5162
|
-
const urls = sources.map((s) => s.url);
|
|
5163
|
-
const mergedUrl = urls.join(",");
|
|
5164
|
-
const name2 = "free0";
|
|
5165
|
-
console.log(`\u6DFB\u52A0\u5408\u5E76\u514D\u8D39\u8BA2\u9605: ${name2} (${sources.map((s) => s.name).join(" + ")})`);
|
|
5166
|
-
try {
|
|
5167
|
-
addSubscription(mergedUrl, name2);
|
|
5168
|
-
const info = await downloadMergedSubscription(urls, name2);
|
|
5169
|
-
setDefaultSubscription(name2);
|
|
5170
|
-
const repoUrls = sources.map((s) => githubRepoUrl(s.url)).filter(Boolean);
|
|
5171
|
-
if (repoUrls.length > 0) saveSubscriptionCache(name2, { web_page_url: repoUrls.join(", ") });
|
|
5172
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name2}" (${formatProxySummary(info)}, \u5408\u5E76 ${sources.length} \u6E90)`);
|
|
5173
|
-
} catch (e) {
|
|
5174
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5175
|
-
process.exit(1);
|
|
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}`);
|
|
5176
4654
|
}
|
|
5177
4655
|
console.log("");
|
|
5178
|
-
await printSubscriptionList();
|
|
5179
|
-
return;
|
|
5180
|
-
}
|
|
5181
|
-
if (freeId < 1 || freeId > freeSources.length) {
|
|
5182
|
-
console.error(`\u9519\u8BEF: \u514D\u8D39\u8BA2\u9605 ID \u8303\u56F4 0-${freeSources.length}`);
|
|
5183
|
-
console.log("\n\u53EF\u7528\u6E90:");
|
|
5184
|
-
printFreeSourceList();
|
|
5185
|
-
process.exit(1);
|
|
5186
4656
|
}
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
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) {
|
|
5190
4675
|
try {
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
4676
|
+
let info;
|
|
4677
|
+
if (isMultiUrl(sub.url)) {
|
|
4678
|
+
info = await downloadMergedSubscription(splitUrls(sub.url), sub.name);
|
|
4679
|
+
} else {
|
|
4680
|
+
info = await downloadSubscription(sub.url, sub.name);
|
|
4681
|
+
}
|
|
4682
|
+
return { name: sub.name, success: true, proxies: info.proxies, proxyGroups: info.proxyGroups };
|
|
5197
4683
|
} catch (e) {
|
|
5198
|
-
|
|
5199
|
-
process.exit(1);
|
|
4684
|
+
return { name: sub.name, success: false, error: e.message };
|
|
5200
4685
|
}
|
|
5201
|
-
console.log("");
|
|
5202
|
-
await printSubscriptionList();
|
|
5203
4686
|
}
|
|
5204
|
-
async function
|
|
5205
|
-
const
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
return;
|
|
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 };
|
|
5209
4692
|
}
|
|
5210
|
-
if (
|
|
5211
|
-
const
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
process.exit(1);
|
|
5217
|
-
}
|
|
5218
|
-
await addFreeSubscription(id);
|
|
5219
|
-
return;
|
|
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...`);
|
|
5220
4699
|
}
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
const url = args[2];
|
|
5228
|
-
const name = args[3] || "default";
|
|
5229
|
-
if (!url) {
|
|
5230
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
5231
|
-
process.exit(1);
|
|
5232
|
-
}
|
|
5233
|
-
if (isMultiUrl(url)) {
|
|
5234
|
-
const urls = splitUrls(url);
|
|
5235
|
-
for (const u of urls) {
|
|
5236
|
-
if (!u.startsWith("http")) {
|
|
5237
|
-
console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
|
|
5238
|
-
process.exit(1);
|
|
5239
|
-
}
|
|
5240
|
-
}
|
|
5241
|
-
console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
|
|
5242
|
-
try {
|
|
5243
|
-
addSubscription(url, name);
|
|
5244
|
-
setDefaultSubscription(name);
|
|
5245
|
-
const info = await downloadMergedSubscription(urls, name);
|
|
5246
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
|
|
5247
|
-
} catch (e) {
|
|
5248
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5249
|
-
process.exit(1);
|
|
5250
|
-
}
|
|
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)})`);
|
|
5251
4706
|
} else {
|
|
5252
|
-
|
|
5253
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
5254
|
-
process.exit(1);
|
|
5255
|
-
}
|
|
5256
|
-
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
5257
|
-
try {
|
|
5258
|
-
addSubscription(url, name);
|
|
5259
|
-
setDefaultSubscription(name);
|
|
5260
|
-
const info = await downloadSubscription(url, name);
|
|
5261
|
-
const repoUrl = githubRepoUrl(url);
|
|
5262
|
-
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
5263
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
5264
|
-
} catch (e) {
|
|
5265
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5266
|
-
process.exit(1);
|
|
5267
|
-
}
|
|
4707
|
+
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
5268
4708
|
}
|
|
5269
|
-
console.log("");
|
|
5270
|
-
await printSubscriptionList();
|
|
5271
|
-
return;
|
|
5272
4709
|
}
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
if (r.success) {
|
|
5286
|
-
ok++;
|
|
5287
|
-
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
5288
|
-
} else {
|
|
5289
|
-
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
5290
|
-
}
|
|
5291
|
-
}
|
|
5292
|
-
if (ok === 0) process.exit(1);
|
|
5293
|
-
console.log("");
|
|
5294
|
-
await printSubscriptionList();
|
|
5295
|
-
return;
|
|
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 };
|
|
5296
4722
|
}
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
} else {
|
|
5306
|
-
info = await downloadSubscription(target.url, target.name);
|
|
5307
|
-
}
|
|
5308
|
-
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
|
|
5309
|
-
} catch (e) {
|
|
5310
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5311
|
-
process.exit(1);
|
|
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;
|
|
5312
4731
|
}
|
|
5313
|
-
|
|
5314
|
-
await printSubscriptionList();
|
|
5315
|
-
return;
|
|
4732
|
+
return { name: proxyName, delay: null, error: errorMsg };
|
|
5316
4733
|
}
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
await printSubscriptionList();
|
|
5336
|
-
return;
|
|
5337
|
-
}
|
|
5338
|
-
const status = getStatus();
|
|
5339
|
-
const configInfo = getConfigInfo();
|
|
5340
|
-
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
5341
|
-
const success = setDefaultSubscription(target.name);
|
|
5342
|
-
if (success) {
|
|
5343
|
-
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
5344
|
-
} else {
|
|
5345
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
5346
|
-
process.exit(1);
|
|
5347
|
-
}
|
|
5348
|
-
if (status.running) {
|
|
5349
|
-
console.log("");
|
|
5350
|
-
await cmdStart(["start", currentMode]);
|
|
5351
|
-
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: [] };
|
|
4740
|
+
}
|
|
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++;
|
|
5352
4752
|
}
|
|
5353
|
-
console.log("");
|
|
5354
|
-
await printSubscriptionList();
|
|
5355
|
-
return;
|
|
5356
4753
|
}
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
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 };
|
|
4758
|
+
}
|
|
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);
|
|
5368
4768
|
} else {
|
|
5369
|
-
|
|
5370
|
-
}
|
|
5371
|
-
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
5372
|
-
let webPageUrl = cached?.web_page_url;
|
|
5373
|
-
if (!webPageUrl) {
|
|
5374
|
-
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
5375
|
-
try {
|
|
5376
|
-
await downloadSubscription(target.url, target.name);
|
|
5377
|
-
const cache = readSubscriptionCache();
|
|
5378
|
-
if (cache[target.name]?.web_page_url) {
|
|
5379
|
-
webPageUrl = cache[target.name].web_page_url;
|
|
5380
|
-
} else {
|
|
5381
|
-
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
5382
|
-
process.exit(1);
|
|
5383
|
-
}
|
|
5384
|
-
} catch (e) {
|
|
5385
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5386
|
-
process.exit(1);
|
|
5387
|
-
}
|
|
4769
|
+
usedNames.add(proxy.name);
|
|
5388
4770
|
}
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
4771
|
+
}
|
|
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;
|
|
4776
|
+
}
|
|
4777
|
+
for (const group of proxyGroups) {
|
|
4778
|
+
if (Array.isArray(group.proxies)) {
|
|
4779
|
+
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
5393
4780
|
}
|
|
5394
|
-
return;
|
|
5395
4781
|
}
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
4782
|
+
return renameMap.size;
|
|
4783
|
+
}
|
|
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);
|
|
5404
4800
|
}
|
|
5405
|
-
process.exit(1);
|
|
5406
4801
|
}
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
4802
|
+
}
|
|
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));
|
|
4808
|
+
}
|
|
5413
4809
|
}
|
|
5414
|
-
console.log("");
|
|
5415
|
-
await printSubscriptionList({ autoUpdate: false });
|
|
5416
|
-
return;
|
|
5417
4810
|
}
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
4811
|
+
return { removedProxies, updatedGroups, removedGroups: removedGroupNames.size };
|
|
4812
|
+
}
|
|
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
|
+
}
|
|
4848
|
+
}
|
|
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;
|
|
5444
4856
|
}
|
|
5445
4857
|
}
|
|
5446
|
-
return;
|
|
5447
4858
|
}
|
|
5448
|
-
if (
|
|
5449
|
-
|
|
5450
|
-
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
5451
|
-
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5452
|
-
console.log("");
|
|
5453
|
-
const progress = createProgressPrinter();
|
|
5454
|
-
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
5455
|
-
return testSubscriptionProxies(target.name, {
|
|
5456
|
-
timeout,
|
|
5457
|
-
concurrency,
|
|
5458
|
-
apiBase,
|
|
5459
|
-
onResult: progress.onResult
|
|
5460
|
-
});
|
|
5461
|
-
});
|
|
5462
|
-
progress.finish();
|
|
5463
|
-
console.log(formatTestSummary(summary));
|
|
5464
|
-
return;
|
|
4859
|
+
if (!skipped && removedProxies > 0) {
|
|
4860
|
+
saveSubscriptionConfig(subName, parsed);
|
|
5465
4861
|
}
|
|
5466
|
-
|
|
5467
|
-
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
|
|
5468
|
-
process.exit(1);
|
|
4862
|
+
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
5469
4863
|
}
|
|
5470
4864
|
|
|
5471
|
-
// src/commands/
|
|
5472
|
-
function
|
|
5473
|
-
const
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
});
|
|
5479
|
-
if (valid.length === 0) {
|
|
5480
|
-
console.log(colors.yellow("\u6CA1\u6709\u53EF\u7528\u7684\u8BA2\u9605\u6E90"));
|
|
5481
|
-
return;
|
|
5482
|
-
}
|
|
5483
|
-
console.log(colors.cyan("\u6392\u540D:"));
|
|
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();
|
|
5484
4872
|
console.log("");
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
}
|
|
5489
|
-
const
|
|
5490
|
-
|
|
5491
|
-
console.log(
|
|
5492
|
-
|
|
5493
|
-
);
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
4873
|
+
let modeLabel = "";
|
|
4874
|
+
if (info && status.running) {
|
|
4875
|
+
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
4876
|
+
}
|
|
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
|
+
}
|
|
4885
|
+
}
|
|
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")}`);
|
|
5505
4912
|
}
|
|
5506
4913
|
console.log("");
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
4914
|
+
}
|
|
4915
|
+
|
|
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")
|
|
4924
|
+
};
|
|
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 });
|
|
5516
4934
|
}
|
|
5517
4935
|
}
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
4936
|
+
function cleanupTestDir() {
|
|
4937
|
+
rmrf(TEST_DIR);
|
|
4938
|
+
}
|
|
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}"`);
|
|
5522
4944
|
}
|
|
5523
|
-
const
|
|
5524
|
-
const
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
const sourceOrder = new Map(allSources.map((s, i) => [s.name, i]));
|
|
5528
|
-
let sources = allSources;
|
|
5529
|
-
if (nameFilter) {
|
|
5530
|
-
const lower = nameFilter.toLowerCase();
|
|
5531
|
-
sources = sources.filter((s) => s.name.toLowerCase().includes(lower));
|
|
5532
|
-
if (sources.length === 0) {
|
|
5533
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${nameFilter}" \u7684\u8BA2\u9605\u6E90`);
|
|
5534
|
-
console.log("\n\u53EF\u7528\u6E90:");
|
|
5535
|
-
for (const s of allSources) console.log(` ${s.name}`);
|
|
5536
|
-
process.exit(1);
|
|
5537
|
-
}
|
|
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`);
|
|
5538
4949
|
}
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
const idx = String((sourceOrder.get(name) ?? 0) + 1).padStart(2, "0");
|
|
5546
|
-
if (ok) {
|
|
5547
|
-
const groupsInfo = groups > 0 ? ` ${groups}\u7EC4` : "";
|
|
5548
|
-
console.log(` ${colors.green("\u2713")} ${idx}-${name}: ${count} \u4E2A\u8282\u70B9${groupsInfo}`);
|
|
5549
|
-
} else {
|
|
5550
|
-
console.log(` ${colors.red("\u2717")} ${idx}-${name}: ${colors.gray(error || "\u5931\u8D25")}`);
|
|
5551
|
-
}
|
|
5552
|
-
});
|
|
5553
|
-
const allProxies = downloaded.flatMap((d) => d.proxies);
|
|
5554
|
-
const successSources = downloaded.filter((d) => d.proxies.length > 0);
|
|
5555
|
-
if (allProxies.length === 0) {
|
|
5556
|
-
console.log("");
|
|
5557
|
-
console.log(colors.red("\u6240\u6709\u8BA2\u9605\u6E90\u4E0B\u8F7D\u5931\u8D25\u6216\u65E0\u8282\u70B9"));
|
|
5558
|
-
return;
|
|
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}`;
|
|
5559
4956
|
}
|
|
5560
|
-
console.log("");
|
|
5561
|
-
console.log(`\u5171 ${allProxies.length} \u4E2A\u8282\u70B9\uFF0C\u6765\u81EA ${successSources.length} \u4E2A\u6E90`);
|
|
5562
|
-
console.log("");
|
|
5563
|
-
const filtered = buildMergedBenchConfig(allProxies);
|
|
5564
|
-
if (filtered > 0) {
|
|
5565
|
-
console.log(colors.gray(`\u8FC7\u6EE4 ${filtered} \u4E2A\u65E0\u6548\u8282\u70B9\uFF0C\u5269\u4F59 ${allProxies.length} \u4E2A`));
|
|
5566
|
-
}
|
|
5567
|
-
const survivingSet = new Set(allProxies);
|
|
5568
|
-
for (const d of downloaded) {
|
|
5569
|
-
d.proxies = d.proxies.filter((p) => survivingSet.has(p));
|
|
5570
|
-
}
|
|
5571
|
-
const benchPort = BENCH_CONFIG["mixed-port"];
|
|
5572
|
-
const benchApi = BENCH_CONFIG["external-controller"];
|
|
5573
|
-
console.log(colors.cyan("\u542F\u52A8\u6D4B\u8BD5\u5B9E\u4F8B..."));
|
|
5574
|
-
await startBenchInstance();
|
|
5575
|
-
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (\u7AEF\u53E3 ${benchPort}/${benchApi.split(":")[1]})`);
|
|
5576
|
-
console.log("");
|
|
5577
|
-
console.log(colors.cyan("\u6D4B\u8BD5\u8282\u70B9\u5EF6\u8FDF..."));
|
|
5578
|
-
const allNames = allProxies.map((p) => p.name);
|
|
5579
|
-
const testResults = await testBenchProxies(allNames, {
|
|
5580
|
-
timeout,
|
|
5581
|
-
concurrency,
|
|
5582
|
-
onBatch: (batch, total, alive, tested, median) => {
|
|
5583
|
-
const pct = (tested / allNames.length * 100).toFixed(0);
|
|
5584
|
-
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`);
|
|
5585
|
-
}
|
|
5586
|
-
});
|
|
5587
|
-
process.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
5588
|
-
const totalAlive = testResults.filter((r) => r.delay !== null).length;
|
|
5589
|
-
const summary = { alive: totalAlive, dead: testResults.length - totalAlive, total: testResults.length };
|
|
5590
|
-
console.log(formatTestSummary(summary));
|
|
5591
|
-
console.log("");
|
|
5592
|
-
const resultsByName = new Map(testResults.map((r) => [r.name, r]));
|
|
5593
|
-
const results = downloaded.map((source) => computeSourceResult(source, resultsByName));
|
|
5594
|
-
printRanking(results, sourceOrder);
|
|
5595
|
-
} finally {
|
|
5596
|
-
stopBenchInstance();
|
|
5597
|
-
cleanupBenchDir();
|
|
5598
4957
|
}
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
|
|
5608
|
-
const success = openUrl(USER_DATA_DIR);
|
|
5609
|
-
if (!success) {
|
|
5610
|
-
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
|
|
5611
|
-
}
|
|
5612
|
-
return;
|
|
5613
|
-
}
|
|
5614
|
-
const targetInfo = DIRECTORY_TARGETS[target.toLowerCase()];
|
|
5615
|
-
if (targetInfo) {
|
|
5616
|
-
const targetPath = targetInfo.path || USER_DATA_DIR;
|
|
5617
|
-
console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
|
|
5618
|
-
const success = openUrl(targetPath);
|
|
5619
|
-
if (!success) {
|
|
5620
|
-
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
|
|
5621
|
-
}
|
|
5622
|
-
return;
|
|
5623
|
-
}
|
|
5624
|
-
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`);
|
|
5625
|
-
console.log("");
|
|
5626
|
-
console.log("\u53EF\u7528\u76EE\u6807:");
|
|
5627
|
-
console.log(" root (\u9ED8\u8BA4) \u6839\u76EE\u5F55");
|
|
5628
|
-
for (const [key, val] of Object.entries(DIRECTORY_TARGETS)) {
|
|
5629
|
-
if (key !== "root") {
|
|
5630
|
-
console.log(` ${key.padEnd(14)}${val.label}`);
|
|
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)
|
|
5631
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 });
|
|
4972
|
+
}
|
|
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;
|
|
4990
|
+
try {
|
|
4991
|
+
await client.get(`${TEST_API}/version`);
|
|
4992
|
+
ready = true;
|
|
4993
|
+
break;
|
|
4994
|
+
} catch {
|
|
4995
|
+
await sleep(500);
|
|
5632
4996
|
}
|
|
5633
|
-
console.log("");
|
|
5634
|
-
process.exit(1);
|
|
5635
4997
|
}
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
|
|
5645
|
-
console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
|
|
5646
|
-
console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5647
|
-
console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5648
|
-
console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
|
|
5649
|
-
console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
|
|
5650
|
-
console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
|
|
5651
|
-
console.log("");
|
|
5652
|
-
console.log("\u6253\u5F00\u76EE\u5F55:");
|
|
5653
|
-
console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
|
|
5654
|
-
console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
|
|
5655
|
-
console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
|
|
5656
|
-
console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
|
|
5657
|
-
console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
|
|
5658
|
-
console.log("");
|
|
5659
|
-
console.log("\u73AF\u5883\u53D8\u91CF:");
|
|
5660
|
-
console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
|
|
5661
|
-
console.log("");
|
|
5662
|
-
}
|
|
5663
|
-
|
|
5664
|
-
// src/commands/help.ts
|
|
5665
|
-
function printShortHelp() {
|
|
5666
|
-
console.log(`
|
|
5667
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
5668
|
-
`);
|
|
5669
|
-
console.log(
|
|
5670
|
-
`\u5E38\u7528\u547D\u4EE4:
|
|
5671
|
-
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
5672
|
-
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
5673
|
-
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
5674
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
5675
|
-
`
|
|
5676
|
-
);
|
|
5677
|
-
}
|
|
5678
|
-
function printHelp() {
|
|
5679
|
-
console.log(
|
|
5680
|
-
`
|
|
5681
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}
|
|
5682
|
-
|
|
5683
|
-
\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh
|
|
5684
|
-
|
|
5685
|
-
\u7528\u6CD5:
|
|
5686
|
-
mihomo <\u547D\u4EE4> [\u9009\u9879]
|
|
5687
|
-
|
|
5688
|
-
${colors.cyan("\u63A7\u5236:")}
|
|
5689
|
-
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)
|
|
5690
|
-
${colors.bold("stop")} \u505C\u6B62\u4EE3\u7406
|
|
5691
|
-
${colors.bold("status")} \u67E5\u770B\u72B6\u6001
|
|
5692
|
-
|
|
5693
|
-
${colors.cyan("\u754C\u9762:")}
|
|
5694
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)
|
|
5695
|
-
${colors.bold("log")} [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09
|
|
5696
|
-
${colors.bold("logs")} [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09
|
|
5697
|
-
|
|
5698
|
-
${colors.cyan("\u8BA2\u9605:")}
|
|
5699
|
-
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
5700
|
-
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
5701
|
-
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
5702
|
-
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
5703
|
-
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
5704
|
-
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
5705
|
-
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
5706
|
-
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
5707
|
-
${colors.bold("test")} [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027
|
|
5708
|
-
${colors.bold("clean")} [-t ms] [-j N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F
|
|
5709
|
-
${colors.bold("bench")} [name] [-t ms] [-j N] \u6D4B\u8BD5\u514D\u8D39\u8BA2\u9605\u6E90\u8D28\u91CF\u6392\u540D
|
|
5710
|
-
|
|
5711
|
-
${colors.cyan("\u914D\u7F6E:")}
|
|
5712
|
-
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
5713
|
-
${colors.bold("overwrite")} on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E
|
|
5714
|
-
${colors.bold("directory")} \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09
|
|
5715
|
-
${colors.bold("directory")} open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|...
|
|
5716
|
-
|
|
5717
|
-
${colors.cyan("\u7CFB\u7EDF:")}
|
|
5718
|
-
${colors.bold("kernel")} [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09
|
|
5719
|
-
${colors.bold("update")} \u66F4\u65B0 mihomo-cli (npm install -g)
|
|
5720
|
-
${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
|
|
5721
|
-
${colors.bold("help")}, -h \u663E\u793A\u5E2E\u52A9
|
|
5722
|
-
${colors.bold("version")}, -v \u663E\u793A\u7248\u672C
|
|
5723
|
-
|
|
5724
|
-
${colors.cyan("\u793A\u4F8B:")}
|
|
5725
|
-
mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F
|
|
5726
|
-
mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F
|
|
5727
|
-
mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)
|
|
5728
|
-
mihomo bench # \u6D4B\u8BD5\u6240\u6709\u514D\u8D39\u8BA2\u9605\u6E90
|
|
5729
|
-
mihomo ui # \u6253\u5F00 Web UI
|
|
5730
|
-
|
|
5731
|
-
${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}
|
|
5732
|
-
mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)
|
|
5733
|
-
tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo
|
|
5734
|
-
|
|
5735
|
-
${colors.cyan("\u6570\u636E\u76EE\u5F55:")}
|
|
5736
|
-
\u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E
|
|
5737
|
-
\u9ED8\u8BA4: ${USER_DATA_DIR}
|
|
5738
|
-
`
|
|
5739
|
-
);
|
|
5740
|
-
}
|
|
5741
|
-
function printVersion() {
|
|
5742
|
-
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
5743
|
-
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
5744
|
-
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
5745
|
-
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
5746
|
-
}
|
|
5747
|
-
|
|
5748
|
-
// src/kernel.ts
|
|
5749
|
-
import { execSync as execSync4, spawnSync } from "child_process";
|
|
5750
|
-
import fs8 from "fs";
|
|
5751
|
-
import path6 from "path";
|
|
5752
|
-
|
|
5753
|
-
// node_modules/compare-versions/lib/esm/utils.js
|
|
5754
|
-
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
5755
|
-
var validateAndParse = (version) => {
|
|
5756
|
-
if (typeof version !== "string") {
|
|
5757
|
-
throw new TypeError("Invalid argument expected string");
|
|
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}` : ""}`);
|
|
5758
5006
|
}
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
|
5007
|
+
if (!ready) {
|
|
5008
|
+
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
5762
5009
|
}
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
};
|
|
5771
|
-
var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
|
|
5772
|
-
var compareStrings = (a, b) => {
|
|
5773
|
-
if (isWildcard(a) || isWildcard(b))
|
|
5774
|
-
return 0;
|
|
5775
|
-
const [ap, bp] = forceType(tryParse(a), tryParse(b));
|
|
5776
|
-
if (ap > bp)
|
|
5777
|
-
return 1;
|
|
5778
|
-
if (ap < bp)
|
|
5779
|
-
return -1;
|
|
5780
|
-
return 0;
|
|
5781
|
-
};
|
|
5782
|
-
var compareSegments = (a, b) => {
|
|
5783
|
-
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
5784
|
-
const r = compareStrings(a[i] || "0", b[i] || "0");
|
|
5785
|
-
if (r !== 0)
|
|
5786
|
-
return r;
|
|
5010
|
+
}
|
|
5011
|
+
function stopTestInstance() {
|
|
5012
|
+
let pid;
|
|
5013
|
+
try {
|
|
5014
|
+
pid = parseInt(fs7.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
5015
|
+
} catch {
|
|
5016
|
+
return;
|
|
5787
5017
|
}
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
const n2 = validateAndParse(v2);
|
|
5795
|
-
const p1 = n1.pop();
|
|
5796
|
-
const p2 = n2.pop();
|
|
5797
|
-
const r = compareSegments(n1, n2);
|
|
5798
|
-
if (r !== 0)
|
|
5799
|
-
return r;
|
|
5800
|
-
if (p1 && p2) {
|
|
5801
|
-
return compareSegments(p1.split("."), p2.split("."));
|
|
5802
|
-
} else if (p1 || p2) {
|
|
5803
|
-
return p1 ? -1 : 1;
|
|
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);
|
|
5023
|
+
}
|
|
5804
5024
|
}
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
// src/kernel.ts
|
|
5809
|
-
var GITHUB_REPO = "MetaCubeX/mihomo";
|
|
5810
|
-
var KERNEL_HTTP_TIMEOUT = 12e4;
|
|
5811
|
-
var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
|
|
5812
|
-
var HTTP_CLIENT2 = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
|
|
5813
|
-
function withMirror(url, mirror) {
|
|
5814
|
-
if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
|
|
5815
|
-
return mirror + url;
|
|
5025
|
+
try {
|
|
5026
|
+
fs7.unlinkSync(TEST_PATHS.pidFile);
|
|
5027
|
+
} catch {
|
|
5816
5028
|
}
|
|
5817
|
-
return url;
|
|
5818
|
-
}
|
|
5819
|
-
function getArch() {
|
|
5820
|
-
const arch = process.arch;
|
|
5821
|
-
if (arch === "arm64") return "arm64";
|
|
5822
|
-
if (arch === "x64") return "amd64";
|
|
5823
|
-
return arch;
|
|
5824
|
-
}
|
|
5825
|
-
function findMatchingAsset(assets, platform, arch) {
|
|
5826
|
-
const prefix = `mihomo-${platform}-${arch}`;
|
|
5827
|
-
const matchingAssets = assets.filter(
|
|
5828
|
-
(a) => a.name.startsWith(prefix) && a.name.endsWith(".gz") || a.name.startsWith(`${prefix}-`) && a.name.endsWith(".gz")
|
|
5829
|
-
);
|
|
5830
|
-
if (matchingAssets.length === 0) return null;
|
|
5831
|
-
if (matchingAssets.length === 1) return matchingAssets[0];
|
|
5832
|
-
const standardAsset = matchingAssets.find((a) => {
|
|
5833
|
-
const nameWithoutGz = a.name.slice(0, -3);
|
|
5834
|
-
const parts = nameWithoutGz.split("-");
|
|
5835
|
-
const lastPart = parts[parts.length - 1];
|
|
5836
|
-
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go");
|
|
5837
|
-
});
|
|
5838
|
-
return standardAsset || matchingAssets[0];
|
|
5839
5029
|
}
|
|
5840
|
-
async function
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
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();
|
|
5846
5039
|
}
|
|
5847
|
-
const stableReleases = releases.filter(
|
|
5848
|
-
(r) => !r.prerelease && !r.tag_name.toLowerCase().includes("alpha") && !r.tag_name.toLowerCase().includes("beta") && !r.tag_name.toLowerCase().includes("prerelease")
|
|
5849
|
-
);
|
|
5850
|
-
return stableReleases.length > 0 ? stableReleases[0] : releases[0];
|
|
5851
5040
|
}
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
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}`)}`);
|
|
5866
5056
|
}
|
|
5867
5057
|
return {
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
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
|
+
}
|
|
5873
5107
|
};
|
|
5874
5108
|
}
|
|
5875
|
-
function
|
|
5876
|
-
const
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
}
|
|
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]}`;
|
|
5888
5121
|
return null;
|
|
5889
5122
|
}
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
const asset = findMatchingAsset(latest.assets, platform, arch);
|
|
5896
|
-
if (!asset) {
|
|
5897
|
-
const available = latest.assets.map((a) => a.name).join(", ");
|
|
5898
|
-
let hint = "";
|
|
5899
|
-
if (available) hint = `
|
|
5900
|
-
\u53EF\u7528\u7248\u672C: ${available}`;
|
|
5901
|
-
throw new Error(`\u672A\u627E\u5230\u5339\u914D\u7684\u5185\u6838\u6587\u4EF6
|
|
5902
|
-
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
5903
|
-
}
|
|
5904
|
-
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
5905
|
-
const tempPath = path6.join(DIRS.kernel, asset.name);
|
|
5906
|
-
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
5907
|
-
if (progressCallback) {
|
|
5908
|
-
progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
|
|
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);
|
|
5909
5128
|
}
|
|
5910
|
-
const
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
)
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
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);
|
|
5919
5141
|
}
|
|
5920
|
-
|
|
5142
|
+
target = activeSub;
|
|
5921
5143
|
}
|
|
5922
|
-
|
|
5923
|
-
|
|
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("");
|
|
5924
5150
|
}
|
|
5925
|
-
|
|
5926
|
-
|
|
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;
|
|
5927
5158
|
}
|
|
5928
|
-
const
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
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}`);
|
|
5938
5170
|
}
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
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}`);
|
|
5943
5181
|
}
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
5947
|
-
if (!foundBinary) {
|
|
5948
|
-
try {
|
|
5949
|
-
fs8.unlinkSync(tempPath);
|
|
5950
|
-
} catch {
|
|
5182
|
+
if (s.expire !== void 0) {
|
|
5183
|
+
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
5951
5184
|
}
|
|
5952
|
-
|
|
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;
|
|
5953
5204
|
}
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
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)`);
|
|
5958
5221
|
try {
|
|
5959
|
-
|
|
5960
|
-
|
|
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);
|
|
5961
5246
|
}
|
|
5962
5247
|
}
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
try {
|
|
5967
|
-
fs8.unlinkSync(tempPath);
|
|
5968
|
-
} catch {
|
|
5969
|
-
}
|
|
5970
|
-
clearKernelVersionCache();
|
|
5971
|
-
return { version: latest.tag_name, path: targetPath };
|
|
5972
|
-
}
|
|
5973
|
-
|
|
5974
|
-
// src/commands/kernel.ts
|
|
5975
|
-
async function cmdKernel(args) {
|
|
5976
|
-
const mirrorInfo = parseMirrorArg(args);
|
|
5977
|
-
const effectiveMirror = mirrorInfo.mirror;
|
|
5978
|
-
if (effectiveMirror) {
|
|
5979
|
-
const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
|
|
5980
|
-
console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
|
|
5981
|
-
}
|
|
5982
|
-
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");
|
|
5983
|
-
console.log("\n\u7528\u6CD5:");
|
|
5984
|
-
console.log(" mihomo kernel # \u76F4\u8FDE");
|
|
5985
|
-
console.log(" mihomo kernel --mirror # \u4E0B\u8F7D\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF (v6.gh-proxy.org)");
|
|
5986
|
-
console.log(" mihomo kernel --mirror hk.gh-proxy.org # \u4E0B\u8F7D\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5987
|
-
console.log(" mihomo kernel --mirror-all # API\u8BF7\u6C42\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF");
|
|
5988
|
-
console.log(" mihomo kernel --mirror-all hk.gh-proxy.org # API\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5989
|
-
console.log("\n\u53EF\u7528\u955C\u50CF:");
|
|
5990
|
-
for (const m of AVAILABLE_MIRRORS) {
|
|
5991
|
-
const isCurrent = effectiveMirror && (effectiveMirror.includes(`//${m}/`) || effectiveMirror.includes(`//${m}:`) || effectiveMirror.endsWith(`//${m}`));
|
|
5992
|
-
console.log(` ${m}${isCurrent ? " (\u5F53\u524D)" : ""}`);
|
|
5248
|
+
console.log("");
|
|
5249
|
+
await printSubscriptionList();
|
|
5250
|
+
return;
|
|
5993
5251
|
}
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
console.log(`\u6700\u65B0: ${info.latest}`);
|
|
6001
|
-
if (!info.needsUpdate) {
|
|
6002
|
-
console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
6003
|
-
} else {
|
|
6004
|
-
console.log("\n\u6B63\u5728\u4E0B\u8F7D...");
|
|
6005
|
-
const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
|
|
6006
|
-
console.log(`
|
|
6007
|
-
\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);
|
|
6008
5258
|
}
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
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
|
+
}
|
|
6016
5270
|
}
|
|
6017
|
-
if (
|
|
6018
|
-
|
|
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}`);
|
|
6019
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);
|
|
5326
|
+
}
|
|
5327
|
+
if (status.running) {
|
|
5328
|
+
console.log("");
|
|
5329
|
+
await cmdStart(["start", currentMode]);
|
|
5330
|
+
return;
|
|
6020
5331
|
}
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
}
|
|
6024
|
-
|
|
6025
|
-
// src/commands/log.ts
|
|
6026
|
-
function cmdLog(args) {
|
|
6027
|
-
const logPath = getLogPath();
|
|
6028
|
-
if (hasFlag(args, "-o", "--open")) {
|
|
6029
|
-
openLogFile(logPath);
|
|
5332
|
+
console.log("");
|
|
5333
|
+
await printSubscriptionList();
|
|
6030
5334
|
return;
|
|
6031
5335
|
}
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
let
|
|
6040
|
-
if (
|
|
6041
|
-
|
|
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);
|
|
6042
5347
|
} else {
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
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");
|
|
6050
5361
|
process.exit(1);
|
|
6051
5362
|
}
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
5363
|
+
} catch (e) {
|
|
5364
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5365
|
+
process.exit(1);
|
|
6055
5366
|
}
|
|
6056
5367
|
}
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
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
|
+
}
|
|
6060
5384
|
process.exit(1);
|
|
6061
5385
|
}
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
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}"`);
|
|
6065
5392
|
}
|
|
6066
|
-
|
|
5393
|
+
console.log("");
|
|
5394
|
+
await printSubscriptionList({ autoUpdate: false });
|
|
6067
5395
|
return;
|
|
6068
5396
|
}
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
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
|
+
}
|
|
6075
5425
|
return;
|
|
6076
5426
|
}
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
console.log(
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
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);
|
|
6096
5500
|
}
|
|
5501
|
+
process.exit(1);
|
|
5502
|
+
}
|
|
5503
|
+
if (configInfo.proxies > AUTO_CLEAN_THRESHOLD) {
|
|
5504
|
+
console.log("");
|
|
5505
|
+
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${AUTO_CLEAN_THRESHOLD}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
|
|
6097
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
|
+
}
|
|
6098
5531
|
}
|
|
6099
|
-
|
|
6100
|
-
console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
|
|
6101
|
-
console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
|
|
6102
|
-
console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
|
|
6103
|
-
console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
|
|
6104
|
-
console.log("");
|
|
5532
|
+
printStatus();
|
|
6105
5533
|
}
|
|
6106
5534
|
|
|
6107
5535
|
// src/commands/overwrite.ts
|
|
6108
|
-
import path7 from "path";
|
|
6109
5536
|
function printOverwriteList() {
|
|
6110
5537
|
const info = listOverwriteFile();
|
|
6111
5538
|
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
@@ -6115,8 +5542,8 @@ function printOverwriteList() {
|
|
|
6115
5542
|
if (info.files.length === 0) {
|
|
6116
5543
|
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
6117
5544
|
console.log("");
|
|
6118
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${
|
|
6119
|
-
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")}`);
|
|
6120
5547
|
console.log("");
|
|
6121
5548
|
} else {
|
|
6122
5549
|
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
@@ -6180,7 +5607,7 @@ async function cmdOverwrite(args) {
|
|
|
6180
5607
|
}
|
|
6181
5608
|
|
|
6182
5609
|
// src/commands/reset.ts
|
|
6183
|
-
import
|
|
5610
|
+
import fs8 from "fs";
|
|
6184
5611
|
import readline from "readline";
|
|
6185
5612
|
var RESET_TARGETS = [
|
|
6186
5613
|
{
|
|
@@ -6235,8 +5662,8 @@ var RESET_TARGETS = [
|
|
|
6235
5662
|
label: "\u8986\u5199",
|
|
6236
5663
|
paths: () => {
|
|
6237
5664
|
const dir = USER_DATA_DIR;
|
|
6238
|
-
if (!
|
|
6239
|
-
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}`);
|
|
6240
5667
|
},
|
|
6241
5668
|
needsStop: false
|
|
6242
5669
|
}
|
|
@@ -6436,7 +5863,7 @@ function cmdUI(args) {
|
|
|
6436
5863
|
}
|
|
6437
5864
|
|
|
6438
5865
|
// src/commands/update.ts
|
|
6439
|
-
import { exec, spawn as
|
|
5866
|
+
import { exec, spawn as spawn3 } from "child_process";
|
|
6440
5867
|
import { promisify } from "util";
|
|
6441
5868
|
var execAsync = promisify(exec);
|
|
6442
5869
|
async function cmdUpdate() {
|
|
@@ -6445,7 +5872,7 @@ async function cmdUpdate() {
|
|
|
6445
5872
|
console.log("\u6B63\u5728\u66F4\u65B0 mihomo-cli...");
|
|
6446
5873
|
console.log("");
|
|
6447
5874
|
await new Promise((resolve) => {
|
|
6448
|
-
const npm =
|
|
5875
|
+
const npm = spawn3("npm", ["install", "-g", "mihomo-cli"], { stdio: "inherit" });
|
|
6449
5876
|
npm.on("close", (code) => {
|
|
6450
5877
|
if (code === 0) {
|
|
6451
5878
|
resolve();
|
|
@@ -6590,9 +6017,6 @@ async function main() {
|
|
|
6590
6017
|
case "overwrite":
|
|
6591
6018
|
await cmdOverwrite(args);
|
|
6592
6019
|
break;
|
|
6593
|
-
case "bench":
|
|
6594
|
-
await cmdBench(args);
|
|
6595
|
-
break;
|
|
6596
6020
|
case "test":
|
|
6597
6021
|
await cmdTest(args);
|
|
6598
6022
|
break;
|