mihomo-cli 2.2.3 → 2.3.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 +28 -0
- package/README.md +5 -1
- package/dist/index.js +1839 -1224
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,65 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
import
|
|
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";
|
|
3
|
+
// src/bench.ts
|
|
4
|
+
import { spawn } from "child_process";
|
|
56
5
|
import fs5 from "fs";
|
|
57
6
|
import path3 from "path";
|
|
58
7
|
|
|
59
|
-
// src/config.ts
|
|
60
|
-
import { execSync } from "child_process";
|
|
61
|
-
import fs4 from "fs";
|
|
62
|
-
|
|
63
8
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
64
9
|
function isNothing(subject) {
|
|
65
10
|
return typeof subject === "undefined" || subject === null;
|
|
@@ -2684,6 +2629,10 @@ var jsYaml = {
|
|
|
2684
2629
|
safeDump
|
|
2685
2630
|
};
|
|
2686
2631
|
|
|
2632
|
+
// src/config.ts
|
|
2633
|
+
import { execSync } from "child_process";
|
|
2634
|
+
import fs4 from "fs";
|
|
2635
|
+
|
|
2687
2636
|
// src/constants.ts
|
|
2688
2637
|
var AVAILABLE_MIRRORS = ["gh-proxy.org", "v6.gh-proxy.org", "hk.gh-proxy.org", "cdn.gh-proxy.org"];
|
|
2689
2638
|
var UI_URLS = {
|
|
@@ -2701,6 +2650,44 @@ var TUN_CONFIG = {
|
|
|
2701
2650
|
"strict-route": true
|
|
2702
2651
|
}
|
|
2703
2652
|
};
|
|
2653
|
+
function getFreeSubscriptionSources() {
|
|
2654
|
+
return [
|
|
2655
|
+
// 完整配置(DNS + 分组 + rule-provider, 29 组)
|
|
2656
|
+
{ name: "FreeSubsCheck", url: "https://gh-proxy.org/raw.githubusercontent.com/kooker/FreeSubsCheck/main/mihomo.yaml" },
|
|
2657
|
+
{ name: "shaoyouvip", url: "https://gh-proxy.org/raw.githubusercontent.com/shaoyouvip/free/main/mihomo.yaml" },
|
|
2658
|
+
{ name: "freeSub", url: "https://gh-proxy.org/raw.githubusercontent.com/Ruk1ng001/freeSub/main/clash.yaml" },
|
|
2659
|
+
// 完整配置(13 组)
|
|
2660
|
+
{ name: "PuddinCat", url: "https://gh-proxy.org/raw.githubusercontent.com/PuddinCat/BestClash/refs/heads/main/proxies.yaml" },
|
|
2661
|
+
{ name: "cn-news", url: "https://gh-proxy.org/raw.githubusercontent.com/hello-world-1989/cn-news/refs/heads/main/clash.yaml" },
|
|
2662
|
+
// 基础分组(10-11 组)
|
|
2663
|
+
{ name: "naidounode", url: "https://gh-proxy.org/raw.githubusercontent.com/xiaoji235/airport-free/main/clash/naidounode.txt" },
|
|
2664
|
+
{ name: "v2rayshare", url: "https://gh-proxy.org/raw.githubusercontent.com/xiaoji235/airport-free/main/clash/v2rayshare.txt" },
|
|
2665
|
+
// 简单配置(2 组)
|
|
2666
|
+
{ name: "proxypool", url: "https://gh-proxy.org/raw.githubusercontent.com/snakem982/proxypool/main/source/clash-meta.yaml" },
|
|
2667
|
+
{ name: "chromego", url: "https://gh-proxy.org/raw.githubusercontent.com/Misaka-blog/chromego_merge/main/sub/merged_proxies_new.yaml" },
|
|
2668
|
+
// 纯节点列表
|
|
2669
|
+
{ name: "awesome-vpn", url: "https://gh-proxy.org/raw.githubusercontent.com/awesome-vpn/awesome-vpn/master/clash.yaml" },
|
|
2670
|
+
{ name: "V2RayAggregator", url: "https://gh-proxy.org/raw.githubusercontent.com/mahdibland/V2RayAggregator/master/Eternity.yml" },
|
|
2671
|
+
{ name: "Pawdroid", url: "https://gh-proxy.org/raw.githubusercontent.com/Pawdroid/Free-servers/main/sub" },
|
|
2672
|
+
{ name: "ermaozi", url: "https://gh-proxy.org/raw.githubusercontent.com/ermaozi/get_subscribe/main/subscribe/clash.yml" },
|
|
2673
|
+
{ name: "v2rayfree", url: "https://gh-proxy.org/raw.githubusercontent.com/v2raynnodes/v2rayfree/main/nodes/clashmeta.yaml" },
|
|
2674
|
+
{ name: "yudou66", url: "https://gh-proxy.org/raw.githubusercontent.com/Barabama/FreeNodes/main/nodes/yudou66.yaml" },
|
|
2675
|
+
{ name: "wenode", url: "https://gh-proxy.org/raw.githubusercontent.com/Barabama/FreeNodes/main/nodes/wenode.yaml" },
|
|
2676
|
+
{ name: "dongtai-sub", url: "https://gh-proxy.org/raw.githubusercontent.com/wenxig/dongtai-sub/refs/heads/main/data/sub.yaml" },
|
|
2677
|
+
{ name: "kasesm", url: "https://gh-proxy.org/raw.githubusercontent.com/kasesm/Free-Config/refs/heads/main/all_raw.txt" },
|
|
2678
|
+
{ name: "Au1rxx", url: "https://gh-proxy.org/raw.githubusercontent.com/Au1rxx/free-vpn-subscriptions/main/output/clash.yaml" },
|
|
2679
|
+
// 完整配置但需要完整版 GeoSite.dat(geosite-lite 不兼容, 22 组)
|
|
2680
|
+
{ name: "NoMoreWalls", url: "https://gh-proxy.org/raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.meta.yml" }
|
|
2681
|
+
];
|
|
2682
|
+
}
|
|
2683
|
+
var BENCH_CONFIG = {
|
|
2684
|
+
"allow-lan": false,
|
|
2685
|
+
"external-controller": "127.0.0.1:19090",
|
|
2686
|
+
port: 17890,
|
|
2687
|
+
"socks-port": 17891,
|
|
2688
|
+
"log-level": "error",
|
|
2689
|
+
"geodata-mode": true
|
|
2690
|
+
};
|
|
2704
2691
|
var BASE_CONFIG = {
|
|
2705
2692
|
"allow-lan": false,
|
|
2706
2693
|
"external-controller": "127.0.0.1:9090",
|
|
@@ -2721,6 +2708,57 @@ var BASE_CONFIG = {
|
|
|
2721
2708
|
import fs3 from "fs";
|
|
2722
2709
|
import path2 from "path";
|
|
2723
2710
|
|
|
2711
|
+
// src/paths.ts
|
|
2712
|
+
import fs from "fs";
|
|
2713
|
+
import os from "os";
|
|
2714
|
+
import path from "path";
|
|
2715
|
+
function getUserDataDir() {
|
|
2716
|
+
if (process.env.MIHOMO_CLI_DIR) {
|
|
2717
|
+
return process.env.MIHOMO_CLI_DIR;
|
|
2718
|
+
}
|
|
2719
|
+
return path.join(os.homedir(), ".mihomo-cli");
|
|
2720
|
+
}
|
|
2721
|
+
var USER_DATA_DIR = getUserDataDir();
|
|
2722
|
+
var DIRS = {
|
|
2723
|
+
kernel: path.join(USER_DATA_DIR, "kernel"),
|
|
2724
|
+
subscriptions: path.join(USER_DATA_DIR, "subscriptions"),
|
|
2725
|
+
logs: path.join(USER_DATA_DIR, "logs"),
|
|
2726
|
+
data: path.join(USER_DATA_DIR, "data"),
|
|
2727
|
+
runtime: path.join(USER_DATA_DIR, "runtime")
|
|
2728
|
+
};
|
|
2729
|
+
var PATHS = {
|
|
2730
|
+
mihomoBinary: path.join(DIRS.kernel, "mihomo"),
|
|
2731
|
+
settingsFile: path.join(USER_DATA_DIR, "settings.json"),
|
|
2732
|
+
subscriptionsCacheFile: path.join(DIRS.subscriptions, "cache.json"),
|
|
2733
|
+
configFile: path.join(DIRS.runtime, "config.yaml"),
|
|
2734
|
+
logFile: path.join(DIRS.logs, "mihomo.log"),
|
|
2735
|
+
pidFile: path.join(DIRS.runtime, "pid"),
|
|
2736
|
+
configStage1Subscription: path.join(DIRS.runtime, "1.subscription.yaml"),
|
|
2737
|
+
configStage2Overwrite: path.join(DIRS.runtime, "2.overwrite.yaml"),
|
|
2738
|
+
configStage3System: path.join(DIRS.runtime, "3.system.yaml")
|
|
2739
|
+
};
|
|
2740
|
+
var DIRECTORY_TARGETS = {
|
|
2741
|
+
root: { path: null, label: "\u6839\u76EE\u5F55" },
|
|
2742
|
+
subs: { path: DIRS.subscriptions, label: "\u8BA2\u9605\u76EE\u5F55" },
|
|
2743
|
+
logs: { path: DIRS.logs, label: "\u65E5\u5FD7\u76EE\u5F55" },
|
|
2744
|
+
data: { path: DIRS.data, label: "mihomo \u6570\u636E\u76EE\u5F55" },
|
|
2745
|
+
runtime: { path: DIRS.runtime, label: "\u8FD0\u884C\u65F6\u76EE\u5F55" },
|
|
2746
|
+
kernel: { path: DIRS.kernel, label: "\u5185\u6838\u76EE\u5F55" }
|
|
2747
|
+
};
|
|
2748
|
+
function ensureDirs() {
|
|
2749
|
+
for (const dir of Object.values(DIRS)) {
|
|
2750
|
+
if (!fs.existsSync(dir)) {
|
|
2751
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
function fsExistsSync(p) {
|
|
2756
|
+
return fs.existsSync(p);
|
|
2757
|
+
}
|
|
2758
|
+
function rmrf(dir) {
|
|
2759
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2724
2762
|
// src/settings.ts
|
|
2725
2763
|
import fs2 from "fs";
|
|
2726
2764
|
var settingsCache = null;
|
|
@@ -3022,6 +3060,37 @@ function parseYamlOrJson(content, errorMsg) {
|
|
|
3022
3060
|
throw new Error(`${errorMsg || "\u5185\u5BB9"}\u683C\u5F0F\u9519\u8BEF\uFF0C\u65E0\u6CD5\u89E3\u6790\u4E3A YAML \u6216 JSON`);
|
|
3023
3061
|
}
|
|
3024
3062
|
}
|
|
3063
|
+
function collectOverwriteProxyNames(overwriteFiles) {
|
|
3064
|
+
const names = [];
|
|
3065
|
+
for (const file of overwriteFiles) {
|
|
3066
|
+
for (const [key, value] of Object.entries(file.config)) {
|
|
3067
|
+
if ((key === "+proxies" || key === "proxies+") && Array.isArray(value)) {
|
|
3068
|
+
for (const proxy of value) {
|
|
3069
|
+
if (proxy && typeof proxy === "object" && "name" in proxy) {
|
|
3070
|
+
names.push(proxy.name);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
return names;
|
|
3077
|
+
}
|
|
3078
|
+
function excludeOverwriteProxiesFromIncludeAll(config, overwriteFiles) {
|
|
3079
|
+
const injectedNames = collectOverwriteProxyNames(overwriteFiles);
|
|
3080
|
+
if (injectedNames.length === 0) return;
|
|
3081
|
+
const groups = config["proxy-groups"];
|
|
3082
|
+
if (!groups) return;
|
|
3083
|
+
const excludePattern = injectedNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
3084
|
+
for (const group of groups) {
|
|
3085
|
+
if (!group["include-all"] && !group["include-all-proxies"]) continue;
|
|
3086
|
+
const existing = group["exclude-filter"];
|
|
3087
|
+
if (existing) {
|
|
3088
|
+
group["exclude-filter"] = `${existing}|${excludePattern}`;
|
|
3089
|
+
} else {
|
|
3090
|
+
group["exclude-filter"] = excludePattern;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3025
3094
|
function buildConfig(subRawContent, mode) {
|
|
3026
3095
|
const subscriptionConfig = parseYamlOrJson(subRawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
3027
3096
|
if (!subscriptionConfig) {
|
|
@@ -3030,6 +3099,9 @@ function buildConfig(subRawContent, mode) {
|
|
|
3030
3099
|
const overwriteEnabled = isOverwriteEnabled();
|
|
3031
3100
|
const overwriteFiles = overwriteEnabled ? loadOverwriteFile() : [];
|
|
3032
3101
|
const withOverwrites = applyOverwrite(subscriptionConfig, overwriteFiles);
|
|
3102
|
+
if (overwriteFiles.length > 0) {
|
|
3103
|
+
excludeOverwriteProxiesFromIncludeAll(withOverwrites, overwriteFiles);
|
|
3104
|
+
}
|
|
3033
3105
|
const systemConfig = {};
|
|
3034
3106
|
for (const [key, value] of Object.entries(BASE_CONFIG)) {
|
|
3035
3107
|
if (!(key in withOverwrites)) {
|
|
@@ -3188,6 +3260,18 @@ function formatDate(dateOrIso) {
|
|
|
3188
3260
|
return "\u672A\u77E5";
|
|
3189
3261
|
}
|
|
3190
3262
|
}
|
|
3263
|
+
function displayWidth(str2) {
|
|
3264
|
+
let w = 0;
|
|
3265
|
+
for (const ch of str2) {
|
|
3266
|
+
const code = ch.codePointAt(0);
|
|
3267
|
+
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)) {
|
|
3268
|
+
w += 2;
|
|
3269
|
+
} else {
|
|
3270
|
+
w += 1;
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
return w;
|
|
3274
|
+
}
|
|
3191
3275
|
function hasFlag(args, short, long) {
|
|
3192
3276
|
return !!args && (args.includes(short) || args.includes(long));
|
|
3193
3277
|
}
|
|
@@ -3296,7 +3380,345 @@ function parseMirrorArg(args) {
|
|
|
3296
3380
|
return { mirror: null, isOverride: false, type: "download" };
|
|
3297
3381
|
}
|
|
3298
3382
|
|
|
3383
|
+
// src/bench.ts
|
|
3384
|
+
var BENCH_DIR = path3.join(USER_DATA_DIR, "bench");
|
|
3385
|
+
var BENCH_DIRS = {
|
|
3386
|
+
data: path3.join(BENCH_DIR, "data"),
|
|
3387
|
+
runtime: path3.join(BENCH_DIR, "runtime")
|
|
3388
|
+
};
|
|
3389
|
+
var BENCH_PATHS = {
|
|
3390
|
+
configFile: path3.join(BENCH_DIRS.runtime, "config.yaml"),
|
|
3391
|
+
pidFile: path3.join(BENCH_DIRS.runtime, "pid"),
|
|
3392
|
+
logFile: path3.join(BENCH_DIR, "bench.log")
|
|
3393
|
+
};
|
|
3394
|
+
var BENCH_API = `http://${BENCH_CONFIG["external-controller"]}`;
|
|
3395
|
+
var BENCH_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
3396
|
+
function ensureBenchDirs() {
|
|
3397
|
+
for (const dir of Object.values(BENCH_DIRS)) {
|
|
3398
|
+
if (!fs5.existsSync(dir)) {
|
|
3399
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
function cleanupBenchDir() {
|
|
3404
|
+
if (fs5.existsSync(BENCH_DIR)) {
|
|
3405
|
+
fs5.rmSync(BENCH_DIR, { recursive: true, force: true });
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
function tryDecodeBase64Content(content) {
|
|
3409
|
+
const trimmed = content.trim();
|
|
3410
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("proxies") || trimmed.includes("proxy-groups")) return null;
|
|
3411
|
+
try {
|
|
3412
|
+
const decoded = Buffer.from(trimmed, "base64").toString("utf8");
|
|
3413
|
+
if (decoded.includes("://")) return decoded;
|
|
3414
|
+
} catch {
|
|
3415
|
+
}
|
|
3416
|
+
return null;
|
|
3417
|
+
}
|
|
3418
|
+
function parseVmessUri(uri) {
|
|
3419
|
+
try {
|
|
3420
|
+
const b64 = uri.slice("vmess://".length);
|
|
3421
|
+
const json2 = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
3422
|
+
return {
|
|
3423
|
+
name: json2.ps || json2.add || "vmess",
|
|
3424
|
+
type: "vmess",
|
|
3425
|
+
server: json2.add,
|
|
3426
|
+
port: Number(json2.port),
|
|
3427
|
+
uuid: json2.id,
|
|
3428
|
+
alterId: Number(json2.aid) || 0,
|
|
3429
|
+
cipher: json2.security || "auto",
|
|
3430
|
+
tls: json2.tls === "tls",
|
|
3431
|
+
network: json2.net || "tcp",
|
|
3432
|
+
...json2.net === "ws" && { "ws-opts": { path: json2.path || "/", headers: json2.host ? { Host: json2.host } : void 0 } }
|
|
3433
|
+
};
|
|
3434
|
+
} catch {
|
|
3435
|
+
return null;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
function parseSsUri(uri) {
|
|
3439
|
+
try {
|
|
3440
|
+
const hashIdx = uri.indexOf("#");
|
|
3441
|
+
const name = hashIdx >= 0 ? decodeURIComponent(uri.slice(hashIdx + 1)) : "ss";
|
|
3442
|
+
const main2 = uri.slice("ss://".length, hashIdx >= 0 ? hashIdx : void 0);
|
|
3443
|
+
let decoded;
|
|
3444
|
+
const atIdx = main2.indexOf("@");
|
|
3445
|
+
if (atIdx >= 0) {
|
|
3446
|
+
const methodPassword2 = Buffer.from(main2.slice(0, atIdx), "base64").toString("utf8");
|
|
3447
|
+
decoded = `${methodPassword2}@${main2.slice(atIdx + 1)}`;
|
|
3448
|
+
} else {
|
|
3449
|
+
decoded = Buffer.from(main2, "base64").toString("utf8");
|
|
3450
|
+
}
|
|
3451
|
+
const [methodPassword, serverPort] = decoded.split("@");
|
|
3452
|
+
if (!methodPassword || !serverPort) return null;
|
|
3453
|
+
const colonIdx = methodPassword.indexOf(":");
|
|
3454
|
+
const method = methodPassword.slice(0, colonIdx);
|
|
3455
|
+
const password = methodPassword.slice(colonIdx + 1);
|
|
3456
|
+
const lastColon = serverPort.lastIndexOf(":");
|
|
3457
|
+
const server = serverPort.slice(0, lastColon);
|
|
3458
|
+
const port = Number(serverPort.slice(lastColon + 1));
|
|
3459
|
+
return { name, type: "ss", server, port, cipher: method, password };
|
|
3460
|
+
} catch {
|
|
3461
|
+
return null;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
function parseTrojanUri(uri) {
|
|
3465
|
+
try {
|
|
3466
|
+
const hashIdx = uri.indexOf("#");
|
|
3467
|
+
const name = hashIdx >= 0 ? decodeURIComponent(uri.slice(hashIdx + 1)) : "trojan";
|
|
3468
|
+
const main2 = uri.slice("trojan://".length, hashIdx >= 0 ? hashIdx : void 0);
|
|
3469
|
+
const atIdx = main2.indexOf("@");
|
|
3470
|
+
if (atIdx < 0) return null;
|
|
3471
|
+
const password = main2.slice(0, atIdx);
|
|
3472
|
+
const rest = main2.slice(atIdx + 1).split("?")[0];
|
|
3473
|
+
const lastColon = rest.lastIndexOf(":");
|
|
3474
|
+
const server = rest.slice(0, lastColon);
|
|
3475
|
+
const port = Number(rest.slice(lastColon + 1));
|
|
3476
|
+
return { name, type: "trojan", server, port, password, sni: server };
|
|
3477
|
+
} catch {
|
|
3478
|
+
return null;
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
function parseProxyUris(content) {
|
|
3482
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3483
|
+
const proxies = [];
|
|
3484
|
+
for (const line of lines) {
|
|
3485
|
+
let proxy = null;
|
|
3486
|
+
if (line.startsWith("vmess://")) proxy = parseVmessUri(line);
|
|
3487
|
+
else if (line.startsWith("ss://")) proxy = parseSsUri(line);
|
|
3488
|
+
else if (line.startsWith("trojan://")) proxy = parseTrojanUri(line);
|
|
3489
|
+
if (proxy?.name && proxy?.server) proxies.push(proxy);
|
|
3490
|
+
}
|
|
3491
|
+
return proxies;
|
|
3492
|
+
}
|
|
3493
|
+
async function downloadAllSources(sources, onProgress) {
|
|
3494
|
+
const savedProxy = { http: process.env.http_proxy, https: process.env.https_proxy, HTTP: process.env.HTTP_PROXY, HTTPS: process.env.HTTPS_PROXY };
|
|
3495
|
+
delete process.env.http_proxy;
|
|
3496
|
+
delete process.env.https_proxy;
|
|
3497
|
+
delete process.env.HTTP_PROXY;
|
|
3498
|
+
delete process.env.HTTPS_PROXY;
|
|
3499
|
+
try {
|
|
3500
|
+
const client = createHttpClient({ timeout: 3e4 });
|
|
3501
|
+
const tasks = sources.map(async (source) => {
|
|
3502
|
+
const entry = { name: source.name, url: source.url, proxies: [], proxyGroups: 0 };
|
|
3503
|
+
try {
|
|
3504
|
+
const response = await client.get(source.url, { responseType: "text" });
|
|
3505
|
+
const content = response.data;
|
|
3506
|
+
if (!content?.trim()) throw new Error("\u5185\u5BB9\u4E3A\u7A7A");
|
|
3507
|
+
let proxies;
|
|
3508
|
+
try {
|
|
3509
|
+
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
3510
|
+
proxies = parsed.proxies || [];
|
|
3511
|
+
const groups = parsed["proxy-groups"];
|
|
3512
|
+
if (groups) entry.proxyGroups = groups.length;
|
|
3513
|
+
} catch {
|
|
3514
|
+
const decoded = tryDecodeBase64Content(content);
|
|
3515
|
+
if (decoded) {
|
|
3516
|
+
proxies = parseProxyUris(decoded);
|
|
3517
|
+
} else {
|
|
3518
|
+
proxies = parseProxyUris(content);
|
|
3519
|
+
}
|
|
3520
|
+
if (proxies.length === 0) throw new Error("\u65E0\u6CD5\u89E3\u6790\u8BA2\u9605\u5185\u5BB9\uFF08\u975E YAML/JSON/Base64\uFF09");
|
|
3521
|
+
}
|
|
3522
|
+
entry.proxies = proxies.map((p) => ({ ...p, name: `[${source.name}] ${p.name}` }));
|
|
3523
|
+
onProgress?.(source.name, true, proxies.length, entry.proxyGroups);
|
|
3524
|
+
} catch (e) {
|
|
3525
|
+
entry.error = e.message;
|
|
3526
|
+
onProgress?.(source.name, false, 0, 0, entry.error);
|
|
3527
|
+
}
|
|
3528
|
+
return entry;
|
|
3529
|
+
});
|
|
3530
|
+
return await Promise.all(tasks);
|
|
3531
|
+
} finally {
|
|
3532
|
+
for (const [key, val] of Object.entries(savedProxy)) {
|
|
3533
|
+
if (val !== void 0) process.env[key] = val;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
function isProxyValid(proxy) {
|
|
3538
|
+
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3539
|
+
if (!proxy.type) return false;
|
|
3540
|
+
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3541
|
+
const pw = String(proxy.password || "");
|
|
3542
|
+
if (!/^[A-Za-z0-9+/]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3543
|
+
}
|
|
3544
|
+
return true;
|
|
3545
|
+
}
|
|
3546
|
+
function buildMergedBenchConfig(allProxies) {
|
|
3547
|
+
ensureBenchDirs();
|
|
3548
|
+
const validProxies = allProxies.filter(isProxyValid);
|
|
3549
|
+
const removed = allProxies.length - validProxies.length;
|
|
3550
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
3551
|
+
for (const proxy of validProxies) {
|
|
3552
|
+
const originalName = proxy.name;
|
|
3553
|
+
const count = (nameCount.get(originalName) || 0) + 1;
|
|
3554
|
+
nameCount.set(originalName, count);
|
|
3555
|
+
if (count > 1) {
|
|
3556
|
+
proxy.name = `${originalName} #${count}`;
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
const config = {
|
|
3560
|
+
...BENCH_CONFIG,
|
|
3561
|
+
proxies: validProxies,
|
|
3562
|
+
"proxy-groups": [
|
|
3563
|
+
{
|
|
3564
|
+
name: "PROXY",
|
|
3565
|
+
type: "select",
|
|
3566
|
+
proxies: validProxies.map((p) => p.name)
|
|
3567
|
+
}
|
|
3568
|
+
],
|
|
3569
|
+
rules: ["MATCH,PROXY"]
|
|
3570
|
+
};
|
|
3571
|
+
const content = jsYaml.dump(config, { indent: 2, lineWidth: -1, noCompatMode: true });
|
|
3572
|
+
fs5.writeFileSync(BENCH_PATHS.configFile, content, { mode: 384 });
|
|
3573
|
+
allProxies.length = 0;
|
|
3574
|
+
allProxies.push(...validProxies);
|
|
3575
|
+
return removed;
|
|
3576
|
+
}
|
|
3577
|
+
async function startBenchInstance() {
|
|
3578
|
+
const binary2 = PATHS.mihomoBinary;
|
|
3579
|
+
if (!fs5.existsSync(binary2)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
|
|
3580
|
+
const logFd = fs5.openSync(BENCH_PATHS.logFile, "a");
|
|
3581
|
+
const child = spawn(binary2, ["-d", BENCH_DIRS.data, "-f", BENCH_PATHS.configFile], {
|
|
3582
|
+
detached: true,
|
|
3583
|
+
stdio: ["ignore", logFd, logFd]
|
|
3584
|
+
});
|
|
3585
|
+
fs5.closeSync(logFd);
|
|
3586
|
+
child.unref();
|
|
3587
|
+
const pid = child.pid;
|
|
3588
|
+
fs5.writeFileSync(BENCH_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
3589
|
+
const client = createHttpClient({ timeout: 2e3 });
|
|
3590
|
+
let ready = false;
|
|
3591
|
+
for (let i = 0; i < 60; i++) {
|
|
3592
|
+
await sleep(500);
|
|
3593
|
+
if (!isProcessRunning(pid)) break;
|
|
3594
|
+
try {
|
|
3595
|
+
await client.get(`${BENCH_API}/version`);
|
|
3596
|
+
ready = true;
|
|
3597
|
+
break;
|
|
3598
|
+
} catch {
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
if (!isProcessRunning(pid)) {
|
|
3602
|
+
let errorDetail = "";
|
|
3603
|
+
if (fs5.existsSync(BENCH_PATHS.logFile)) {
|
|
3604
|
+
try {
|
|
3605
|
+
errorDetail = fs5.readFileSync(BENCH_PATHS.logFile, "utf8").slice(-1e3);
|
|
3606
|
+
} catch {
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
throw new Error(`bench \u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
3610
|
+
${errorDetail}` : ""}`);
|
|
3611
|
+
}
|
|
3612
|
+
if (!ready) {
|
|
3613
|
+
throw new Error("bench \u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
3614
|
+
}
|
|
3615
|
+
return pid;
|
|
3616
|
+
}
|
|
3617
|
+
function stopBenchInstance() {
|
|
3618
|
+
if (!fs5.existsSync(BENCH_PATHS.pidFile)) return;
|
|
3619
|
+
try {
|
|
3620
|
+
const pid = parseInt(fs5.readFileSync(BENCH_PATHS.pidFile, "utf8").trim(), 10);
|
|
3621
|
+
if (pid > 0 && isProcessRunning(pid)) {
|
|
3622
|
+
process.kill(pid, "SIGKILL");
|
|
3623
|
+
for (let i = 0; i < 20; i++) {
|
|
3624
|
+
if (!isProcessRunning(pid)) break;
|
|
3625
|
+
sleepSync(100);
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
} catch {
|
|
3629
|
+
}
|
|
3630
|
+
try {
|
|
3631
|
+
fs5.unlinkSync(BENCH_PATHS.pidFile);
|
|
3632
|
+
} catch {
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
async function testBenchProxy(proxyName, timeout, client) {
|
|
3636
|
+
const encodedName = encodeURIComponent(proxyName);
|
|
3637
|
+
const url = `${BENCH_API}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(BENCH_TEST_URL)}`;
|
|
3638
|
+
try {
|
|
3639
|
+
const response = await client.get(url);
|
|
3640
|
+
const data = JSON.parse(response.data);
|
|
3641
|
+
if (data.delay && data.delay > 0) {
|
|
3642
|
+
return { name: proxyName, delay: data.delay };
|
|
3643
|
+
}
|
|
3644
|
+
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
3645
|
+
} catch (e) {
|
|
3646
|
+
const err = e;
|
|
3647
|
+
let errorMsg = "timeout";
|
|
3648
|
+
if (err.response?.data?.message) {
|
|
3649
|
+
errorMsg = String(err.response.data.message);
|
|
3650
|
+
} else if (err.message) {
|
|
3651
|
+
errorMsg = err.message;
|
|
3652
|
+
}
|
|
3653
|
+
return { name: proxyName, delay: null, error: errorMsg };
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
async function testBenchProxies(proxyNames, options = {}) {
|
|
3657
|
+
const { timeout = 3e3, concurrency = 100, onResult, onBatch } = options;
|
|
3658
|
+
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
3659
|
+
const results = [];
|
|
3660
|
+
let completedCount = 0;
|
|
3661
|
+
let aliveCount = 0;
|
|
3662
|
+
const delays = [];
|
|
3663
|
+
const totalBatches = Math.ceil(proxyNames.length / concurrency);
|
|
3664
|
+
for (let i = 0; i < proxyNames.length; i += concurrency) {
|
|
3665
|
+
const batch = proxyNames.slice(i, i + concurrency);
|
|
3666
|
+
const batchResults = await Promise.all(batch.map((name) => testBenchProxy(name, timeout, client)));
|
|
3667
|
+
for (const result of batchResults) {
|
|
3668
|
+
results.push(result);
|
|
3669
|
+
if (result.delay !== null) {
|
|
3670
|
+
aliveCount++;
|
|
3671
|
+
delays.push(result.delay);
|
|
3672
|
+
}
|
|
3673
|
+
onResult?.(result, completedCount, proxyNames.length);
|
|
3674
|
+
completedCount++;
|
|
3675
|
+
}
|
|
3676
|
+
delays.sort((a, b) => a - b);
|
|
3677
|
+
const median = delays.length > 0 ? delays[Math.floor(delays.length / 2)] : 0;
|
|
3678
|
+
onBatch?.(Math.floor(i / concurrency) + 1, totalBatches, aliveCount, completedCount, median);
|
|
3679
|
+
}
|
|
3680
|
+
return results;
|
|
3681
|
+
}
|
|
3682
|
+
function computeSourceResult(source, resultsByName) {
|
|
3683
|
+
const proxyNames = source.proxies.map((p) => p.name);
|
|
3684
|
+
const sourceResults = proxyNames.map((n) => resultsByName.get(n)).filter((r) => r !== void 0);
|
|
3685
|
+
const delays = sourceResults.filter((r) => r.delay !== null).map((r) => r.delay);
|
|
3686
|
+
const alive = delays.length;
|
|
3687
|
+
const dead = sourceResults.length - alive;
|
|
3688
|
+
if (alive === 0) {
|
|
3689
|
+
return {
|
|
3690
|
+
name: source.name,
|
|
3691
|
+
url: source.url,
|
|
3692
|
+
downloadOk: !source.error,
|
|
3693
|
+
downloadError: source.error,
|
|
3694
|
+
totalProxies: source.proxies.length,
|
|
3695
|
+
proxyGroups: source.proxyGroups,
|
|
3696
|
+
alive: 0,
|
|
3697
|
+
dead,
|
|
3698
|
+
avgDelay: 0,
|
|
3699
|
+
minDelay: 0,
|
|
3700
|
+
medianDelay: 0
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
delays.sort((a, b) => a - b);
|
|
3704
|
+
return {
|
|
3705
|
+
name: source.name,
|
|
3706
|
+
url: source.url,
|
|
3707
|
+
downloadOk: true,
|
|
3708
|
+
totalProxies: source.proxies.length,
|
|
3709
|
+
proxyGroups: source.proxyGroups,
|
|
3710
|
+
alive,
|
|
3711
|
+
dead,
|
|
3712
|
+
avgDelay: Math.round(delays.reduce((sum, d) => sum + d, 0) / alive),
|
|
3713
|
+
minDelay: delays[0],
|
|
3714
|
+
medianDelay: delays[Math.floor(delays.length / 2)]
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3299
3718
|
// src/process.ts
|
|
3719
|
+
import { execSync as execSync3, spawn as spawn2 } from "child_process";
|
|
3720
|
+
import fs6 from "fs";
|
|
3721
|
+
import path4 from "path";
|
|
3300
3722
|
var PROCESS_WAIT_ATTEMPTS = 50;
|
|
3301
3723
|
var PROCESS_WAIT_INTERVAL = 100;
|
|
3302
3724
|
var STARTUP_WAIT_MS = 800;
|
|
@@ -3305,15 +3727,15 @@ var TUN_MODE_POST_WAIT_MS = 500;
|
|
|
3305
3727
|
var BATCH_KILL_THRESHOLD = 3;
|
|
3306
3728
|
var DEFAULT_LOG_RETENTION_DAYS = 7;
|
|
3307
3729
|
function clearRuntime() {
|
|
3308
|
-
if (
|
|
3730
|
+
if (fs6.existsSync(DIRS.runtime)) {
|
|
3309
3731
|
rmrf(DIRS.runtime);
|
|
3310
3732
|
}
|
|
3311
3733
|
ensureDirs();
|
|
3312
3734
|
}
|
|
3313
3735
|
function getPid() {
|
|
3314
|
-
if (!
|
|
3736
|
+
if (!fs6.existsSync(PATHS.pidFile)) return null;
|
|
3315
3737
|
try {
|
|
3316
|
-
const pid = parseInt(
|
|
3738
|
+
const pid = parseInt(fs6.readFileSync(PATHS.pidFile, "utf8").trim(), 10);
|
|
3317
3739
|
return pid > 0 ? pid : null;
|
|
3318
3740
|
} catch {
|
|
3319
3741
|
return null;
|
|
@@ -3334,9 +3756,9 @@ function getAllMihomoPids() {
|
|
|
3334
3756
|
}
|
|
3335
3757
|
}
|
|
3336
3758
|
function isPidFileOwnedByRoot() {
|
|
3337
|
-
if (!
|
|
3759
|
+
if (!fs6.existsSync(PATHS.pidFile)) return false;
|
|
3338
3760
|
try {
|
|
3339
|
-
const stat =
|
|
3761
|
+
const stat = fs6.statSync(PATHS.pidFile);
|
|
3340
3762
|
return stat.uid === 0;
|
|
3341
3763
|
} catch {
|
|
3342
3764
|
return false;
|
|
@@ -3356,10 +3778,10 @@ function checkStaleState() {
|
|
|
3356
3778
|
}
|
|
3357
3779
|
function savePid(pid) {
|
|
3358
3780
|
ensureDirs();
|
|
3359
|
-
|
|
3781
|
+
fs6.writeFileSync(PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
3360
3782
|
}
|
|
3361
3783
|
function clearPid() {
|
|
3362
|
-
if (!
|
|
3784
|
+
if (!fs6.existsSync(PATHS.pidFile)) return;
|
|
3363
3785
|
if (isPidFileOwnedByRoot()) {
|
|
3364
3786
|
try {
|
|
3365
3787
|
execSync3(`sudo rm -f "${PATHS.pidFile}" 2>/dev/null`, { stdio: "inherit", timeout: 1e4 });
|
|
@@ -3367,7 +3789,7 @@ function clearPid() {
|
|
|
3367
3789
|
}
|
|
3368
3790
|
} else {
|
|
3369
3791
|
try {
|
|
3370
|
-
|
|
3792
|
+
fs6.unlinkSync(PATHS.pidFile);
|
|
3371
3793
|
} catch {
|
|
3372
3794
|
}
|
|
3373
3795
|
}
|
|
@@ -3492,8 +3914,8 @@ echo "--- \u65E5\u5FD7 ---"
|
|
|
3492
3914
|
tail -25 "\${LOG_FILE}" 2>/dev/null
|
|
3493
3915
|
exit 1
|
|
3494
3916
|
`;
|
|
3495
|
-
const scriptPath =
|
|
3496
|
-
|
|
3917
|
+
const scriptPath = path4.join(DIRS.runtime, "launch-tun.sh");
|
|
3918
|
+
fs6.writeFileSync(scriptPath, scriptContent, { mode: 448 });
|
|
3497
3919
|
return scriptPath;
|
|
3498
3920
|
}
|
|
3499
3921
|
function getProcessInfo(pid) {
|
|
@@ -3534,11 +3956,11 @@ async function start(mode = "mixed") {
|
|
|
3534
3956
|
ensureDirs();
|
|
3535
3957
|
rotateAndCleanupLogs();
|
|
3536
3958
|
const binary2 = PATHS.mihomoBinary;
|
|
3537
|
-
if (!
|
|
3959
|
+
if (!fs6.existsSync(binary2)) {
|
|
3538
3960
|
throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838\uFF0C\u8BF7\u5148\u4E0B\u8F7D\u5185\u6838");
|
|
3539
3961
|
}
|
|
3540
3962
|
const configFile = PATHS.configFile;
|
|
3541
|
-
if (!
|
|
3963
|
+
if (!fs6.existsSync(configFile)) {
|
|
3542
3964
|
throw new Error("\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605\u5E76\u542F\u52A8");
|
|
3543
3965
|
}
|
|
3544
3966
|
const staleState = checkStaleState();
|
|
@@ -3567,12 +3989,12 @@ async function startMixedMode(staleState) {
|
|
|
3567
3989
|
const configFile = PATHS.configFile;
|
|
3568
3990
|
const logFile = PATHS.logFile;
|
|
3569
3991
|
const args = ["-d", DIRS.data, "-f", configFile];
|
|
3570
|
-
const logFd =
|
|
3571
|
-
const child =
|
|
3992
|
+
const logFd = fs6.openSync(logFile, "a");
|
|
3993
|
+
const child = spawn2(PATHS.mihomoBinary, args, {
|
|
3572
3994
|
detached: true,
|
|
3573
3995
|
stdio: ["ignore", logFd, logFd]
|
|
3574
3996
|
});
|
|
3575
|
-
|
|
3997
|
+
fs6.closeSync(logFd);
|
|
3576
3998
|
child.unref();
|
|
3577
3999
|
const pid = child.pid;
|
|
3578
4000
|
savePid(pid);
|
|
@@ -3580,9 +4002,9 @@ async function startMixedMode(staleState) {
|
|
|
3580
4002
|
if (!isRunning()) {
|
|
3581
4003
|
clearPid();
|
|
3582
4004
|
let errorMsg = "\u542F\u52A8\u5931\u8D25";
|
|
3583
|
-
if (
|
|
4005
|
+
if (fs6.existsSync(logFile)) {
|
|
3584
4006
|
try {
|
|
3585
|
-
const logs =
|
|
4007
|
+
const logs = fs6.readFileSync(logFile, "utf8").slice(-3e3);
|
|
3586
4008
|
if (logs.trim()) {
|
|
3587
4009
|
errorMsg += "\n\u6700\u8FD1\u7684\u65E5\u5FD7:\n" + logs.split("\n").map((l) => ` ${l}`).join("\n");
|
|
3588
4010
|
}
|
|
@@ -3603,7 +4025,7 @@ async function startTunMode(staleState) {
|
|
|
3603
4025
|
execSync3(`sudo "${launchScript}"`, { stdio: "inherit", timeout: SUDO_TIMEOUT_MS });
|
|
3604
4026
|
} catch (e) {
|
|
3605
4027
|
try {
|
|
3606
|
-
|
|
4028
|
+
fs6.unlinkSync(launchScript);
|
|
3607
4029
|
} catch {
|
|
3608
4030
|
}
|
|
3609
4031
|
if (e.status === 1) {
|
|
@@ -3612,7 +4034,7 @@ async function startTunMode(staleState) {
|
|
|
3612
4034
|
throw new Error(e.message);
|
|
3613
4035
|
}
|
|
3614
4036
|
try {
|
|
3615
|
-
|
|
4037
|
+
fs6.unlinkSync(launchScript);
|
|
3616
4038
|
} catch {
|
|
3617
4039
|
}
|
|
3618
4040
|
await new Promise((resolve) => setTimeout(resolve, TUN_MODE_POST_WAIT_MS));
|
|
@@ -3651,19 +4073,19 @@ function getLogPath() {
|
|
|
3651
4073
|
}
|
|
3652
4074
|
function rotateLog() {
|
|
3653
4075
|
const logFile = PATHS.logFile;
|
|
3654
|
-
if (!
|
|
3655
|
-
const stat =
|
|
4076
|
+
if (!fs6.existsSync(logFile)) return null;
|
|
4077
|
+
const stat = fs6.statSync(logFile);
|
|
3656
4078
|
if (stat.size === 0) return null;
|
|
3657
4079
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/T/, "_").replace(/:/g, "-").replace(/\..+/, "");
|
|
3658
4080
|
const rotatedName = `mihomo.${timestamp2}.log`;
|
|
3659
|
-
const rotatedPath =
|
|
3660
|
-
|
|
4081
|
+
const rotatedPath = path4.join(DIRS.logs, rotatedName);
|
|
4082
|
+
fs6.renameSync(logFile, rotatedPath);
|
|
3661
4083
|
return rotatedPath;
|
|
3662
4084
|
}
|
|
3663
4085
|
function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
3664
4086
|
const logsDir = DIRS.logs;
|
|
3665
|
-
if (!
|
|
3666
|
-
const files =
|
|
4087
|
+
if (!fs6.existsSync(logsDir)) return { deleted: 0, errors: 0 };
|
|
4088
|
+
const files = fs6.readdirSync(logsDir);
|
|
3667
4089
|
const now = Date.now();
|
|
3668
4090
|
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
3669
4091
|
let deleted = 0;
|
|
@@ -3671,10 +4093,10 @@ function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
|
3671
4093
|
for (const file of files) {
|
|
3672
4094
|
if (!file.match(/^mihomo\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.log$/)) continue;
|
|
3673
4095
|
try {
|
|
3674
|
-
const filePath =
|
|
3675
|
-
const stat =
|
|
4096
|
+
const filePath = path4.join(logsDir, file);
|
|
4097
|
+
const stat = fs6.statSync(filePath);
|
|
3676
4098
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
3677
|
-
|
|
4099
|
+
fs6.unlinkSync(filePath);
|
|
3678
4100
|
deleted++;
|
|
3679
4101
|
}
|
|
3680
4102
|
} catch {
|
|
@@ -3686,8 +4108,8 @@ function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
|
3686
4108
|
function listLogs() {
|
|
3687
4109
|
const logsDir = DIRS.logs;
|
|
3688
4110
|
const result = { current: null, archives: [] };
|
|
3689
|
-
if (
|
|
3690
|
-
const stat =
|
|
4111
|
+
if (fs6.existsSync(PATHS.logFile)) {
|
|
4112
|
+
const stat = fs6.statSync(PATHS.logFile);
|
|
3691
4113
|
result.current = {
|
|
3692
4114
|
name: "mihomo.log (\u5F53\u524D)",
|
|
3693
4115
|
path: PATHS.logFile,
|
|
@@ -3696,14 +4118,14 @@ function listLogs() {
|
|
|
3696
4118
|
isCurrent: true
|
|
3697
4119
|
};
|
|
3698
4120
|
}
|
|
3699
|
-
if (!
|
|
3700
|
-
const files =
|
|
4121
|
+
if (!fs6.existsSync(logsDir)) return result;
|
|
4122
|
+
const files = fs6.readdirSync(logsDir);
|
|
3701
4123
|
for (const file of files) {
|
|
3702
4124
|
const match = file.match(/^mihomo\.(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.log$/);
|
|
3703
4125
|
if (!match) continue;
|
|
3704
4126
|
try {
|
|
3705
|
-
const filePath =
|
|
3706
|
-
const stat =
|
|
4127
|
+
const filePath = path4.join(logsDir, file);
|
|
4128
|
+
const stat = fs6.statSync(filePath);
|
|
3707
4129
|
result.archives.push({
|
|
3708
4130
|
name: file,
|
|
3709
4131
|
path: filePath,
|
|
@@ -3718,22 +4140,22 @@ function listLogs() {
|
|
|
3718
4140
|
return result;
|
|
3719
4141
|
}
|
|
3720
4142
|
function isPathUnderDir(filePath, baseDir) {
|
|
3721
|
-
const resolvedPath =
|
|
3722
|
-
const resolvedBase =
|
|
3723
|
-
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase +
|
|
4143
|
+
const resolvedPath = path4.resolve(filePath);
|
|
4144
|
+
const resolvedBase = path4.resolve(baseDir);
|
|
4145
|
+
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + path4.sep);
|
|
3724
4146
|
}
|
|
3725
4147
|
function getLogPathByName(name) {
|
|
3726
4148
|
const logsDir = DIRS.logs;
|
|
3727
4149
|
let targetName = name;
|
|
3728
4150
|
if (!name.endsWith(".log")) targetName = `mihomo.${name}.log`;
|
|
3729
4151
|
if (!targetName.startsWith("mihomo.")) targetName = `mihomo.${targetName}`;
|
|
3730
|
-
const filePath =
|
|
3731
|
-
if (
|
|
3732
|
-
if (
|
|
3733
|
-
const files =
|
|
4152
|
+
const filePath = path4.join(logsDir, targetName);
|
|
4153
|
+
if (fs6.existsSync(filePath) && isPathUnderDir(filePath, logsDir)) return filePath;
|
|
4154
|
+
if (fs6.existsSync(logsDir)) {
|
|
4155
|
+
const files = fs6.readdirSync(logsDir);
|
|
3734
4156
|
for (const file of files) {
|
|
3735
4157
|
if (file.includes(name)) {
|
|
3736
|
-
const candidatePath =
|
|
4158
|
+
const candidatePath = path4.join(logsDir, file);
|
|
3737
4159
|
if (isPathUnderDir(candidatePath, logsDir)) return candidatePath;
|
|
3738
4160
|
}
|
|
3739
4161
|
}
|
|
@@ -3742,7 +4164,7 @@ function getLogPathByName(name) {
|
|
|
3742
4164
|
}
|
|
3743
4165
|
function openUrl(url) {
|
|
3744
4166
|
try {
|
|
3745
|
-
const child =
|
|
4167
|
+
const child = spawn2("open", [url], { stdio: "ignore", detached: true });
|
|
3746
4168
|
child.unref();
|
|
3747
4169
|
child.on("error", () => {
|
|
3748
4170
|
});
|
|
@@ -3773,7 +4195,7 @@ function viewLogWithTail(logPath, options) {
|
|
|
3773
4195
|
if (follow) tailArgs.push("-f");
|
|
3774
4196
|
tailArgs.push("-n", lines.toString());
|
|
3775
4197
|
tailArgs.push(logPath);
|
|
3776
|
-
const tail =
|
|
4198
|
+
const tail = spawn2("tail", tailArgs, { stdio: "inherit" });
|
|
3777
4199
|
tail.on("close", () => process.exit(0));
|
|
3778
4200
|
tail.on("error", (e) => {
|
|
3779
4201
|
console.error(`\u65E0\u6CD5\u8BFB\u53D6\u65E5\u5FD7: ${e.message}`);
|
|
@@ -3781,1268 +4203,1458 @@ function viewLogWithTail(logPath, options) {
|
|
|
3781
4203
|
});
|
|
3782
4204
|
}
|
|
3783
4205
|
|
|
3784
|
-
// src/
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
if (!success) {
|
|
3793
|
-
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
|
|
3794
|
-
}
|
|
3795
|
-
return;
|
|
3796
|
-
}
|
|
3797
|
-
const targetInfo = DIRECTORY_TARGETS[target.toLowerCase()];
|
|
3798
|
-
if (targetInfo) {
|
|
3799
|
-
const targetPath = targetInfo.path || USER_DATA_DIR;
|
|
3800
|
-
console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
|
|
3801
|
-
const success = openUrl(targetPath);
|
|
3802
|
-
if (!success) {
|
|
3803
|
-
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
|
|
3804
|
-
}
|
|
3805
|
-
return;
|
|
3806
|
-
}
|
|
3807
|
-
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`);
|
|
3808
|
-
console.log("");
|
|
3809
|
-
console.log("\u53EF\u7528\u76EE\u6807:");
|
|
3810
|
-
console.log(" root (\u9ED8\u8BA4) \u6839\u76EE\u5F55");
|
|
3811
|
-
for (const [key, val] of Object.entries(DIRECTORY_TARGETS)) {
|
|
3812
|
-
if (key !== "root") {
|
|
3813
|
-
console.log(` ${key.padEnd(14)}${val.label}`);
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
console.log("");
|
|
3817
|
-
process.exit(1);
|
|
4206
|
+
// src/subscription.ts
|
|
4207
|
+
var DEFAULT_UPDATE_INTERVAL_HOURS = 12;
|
|
4208
|
+
var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, noCompatMode: true };
|
|
4209
|
+
var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
|
|
4210
|
+
function loadSubscriptionConfig(subName) {
|
|
4211
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4212
|
+
if (!rawContent) {
|
|
4213
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
3818
4214
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
|
|
3826
|
-
console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
|
|
3827
|
-
console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
|
|
3828
|
-
console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
|
|
3829
|
-
console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
3830
|
-
console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
3831
|
-
console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
|
|
3832
|
-
console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
|
|
3833
|
-
console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
|
|
3834
|
-
console.log("");
|
|
3835
|
-
console.log("\u6253\u5F00\u76EE\u5F55:");
|
|
3836
|
-
console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
|
|
3837
|
-
console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
|
|
3838
|
-
console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
|
|
3839
|
-
console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
|
|
3840
|
-
console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
|
|
3841
|
-
console.log("");
|
|
3842
|
-
console.log("\u73AF\u5883\u53D8\u91CF:");
|
|
3843
|
-
console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
|
|
3844
|
-
console.log("");
|
|
4215
|
+
const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4216
|
+
return {
|
|
4217
|
+
raw,
|
|
4218
|
+
proxies: raw.proxies || [],
|
|
4219
|
+
proxyGroups: raw["proxy-groups"] || []
|
|
4220
|
+
};
|
|
3845
4221
|
}
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
`);
|
|
3852
|
-
console.log(
|
|
3853
|
-
`\u5E38\u7528\u547D\u4EE4:
|
|
3854
|
-
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
3855
|
-
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
3856
|
-
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
3857
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
3858
|
-
`
|
|
3859
|
-
);
|
|
4222
|
+
function saveSubscriptionConfig(subName, parsed) {
|
|
4223
|
+
normalizeProxyNamesBeforeSave(parsed);
|
|
4224
|
+
parsed.raw.proxies = parsed.proxies;
|
|
4225
|
+
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4226
|
+
saveSubscriptionRawConfig(subName, jsYaml.dump(parsed.raw, YAML_DUMP_OPTS));
|
|
3860
4227
|
}
|
|
3861
|
-
function
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
${colors.bold("stop")} \u505C\u6B62\u4EE3\u7406
|
|
3874
|
-
${colors.bold("status")} \u67E5\u770B\u72B6\u6001
|
|
3875
|
-
|
|
3876
|
-
${colors.cyan("\u754C\u9762:")}
|
|
3877
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)
|
|
3878
|
-
${colors.bold("log")} [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09
|
|
3879
|
-
${colors.bold("logs")} [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09
|
|
3880
|
-
|
|
3881
|
-
${colors.cyan("\u8BA2\u9605:")}
|
|
3882
|
-
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
3883
|
-
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
3884
|
-
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
3885
|
-
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
3886
|
-
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
3887
|
-
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
3888
|
-
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
3889
|
-
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
3890
|
-
|
|
3891
|
-
${colors.cyan("\u914D\u7F6E:")}
|
|
3892
|
-
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
3893
|
-
${colors.bold("overwrite")} on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E
|
|
3894
|
-
${colors.bold("directory")} \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09
|
|
3895
|
-
${colors.bold("directory")} open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|...
|
|
3896
|
-
|
|
3897
|
-
${colors.cyan("\u7CFB\u7EDF:")}
|
|
3898
|
-
${colors.bold("kernel")} [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09
|
|
3899
|
-
${colors.bold("update")} \u66F4\u65B0 mihomo-cli (npm install -g)
|
|
3900
|
-
${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
|
|
3901
|
-
${colors.bold("help")}, -h \u663E\u793A\u5E2E\u52A9
|
|
3902
|
-
${colors.bold("version")}, -v \u663E\u793A\u7248\u672C
|
|
3903
|
-
|
|
3904
|
-
${colors.cyan("\u793A\u4F8B:")}
|
|
3905
|
-
mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F
|
|
3906
|
-
mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F
|
|
3907
|
-
mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)
|
|
3908
|
-
mihomo ui # \u6253\u5F00 Web UI
|
|
3909
|
-
|
|
3910
|
-
${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}
|
|
3911
|
-
mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)
|
|
3912
|
-
tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo
|
|
3913
|
-
|
|
3914
|
-
${colors.cyan("\u6570\u636E\u76EE\u5F55:")}
|
|
3915
|
-
\u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E
|
|
3916
|
-
\u9ED8\u8BA4: ${USER_DATA_DIR}
|
|
3917
|
-
`
|
|
3918
|
-
);
|
|
4228
|
+
function parseUserInfo(header) {
|
|
4229
|
+
if (!header) return null;
|
|
4230
|
+
const info = {};
|
|
4231
|
+
const parts = header.split(";").map((p) => p.trim());
|
|
4232
|
+
for (const part of parts) {
|
|
4233
|
+
const [key, val] = part.split("=").map((s) => s.trim());
|
|
4234
|
+
if (key && val !== void 0) {
|
|
4235
|
+
const numVal = parseFloat(val);
|
|
4236
|
+
info[key] = Number.isNaN(numVal) ? 0 : numVal;
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
return info;
|
|
3919
4240
|
}
|
|
3920
|
-
function
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
4241
|
+
function parseUsernameFromContentDisposition(header) {
|
|
4242
|
+
if (!header) return null;
|
|
4243
|
+
const match = header.match(/filename\s*=\s*["']?([^"';\s]+)["']?/i);
|
|
4244
|
+
if (!match) return null;
|
|
4245
|
+
const filename = match[1];
|
|
4246
|
+
const parts = filename.split("/");
|
|
4247
|
+
return parts[parts.length - 1] || null;
|
|
3925
4248
|
}
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
// node_modules/compare-versions/lib/esm/utils.js
|
|
3933
|
-
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
3934
|
-
var validateAndParse = (version) => {
|
|
3935
|
-
if (typeof version !== "string") {
|
|
3936
|
-
throw new TypeError("Invalid argument expected string");
|
|
3937
|
-
}
|
|
3938
|
-
const match = version.match(semver);
|
|
3939
|
-
if (!match) {
|
|
3940
|
-
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
|
3941
|
-
}
|
|
3942
|
-
match.shift();
|
|
3943
|
-
return match;
|
|
3944
|
-
};
|
|
3945
|
-
var isWildcard = (s) => s === "*" || s === "x" || s === "X";
|
|
3946
|
-
var tryParse = (v) => {
|
|
3947
|
-
const n = parseInt(v, 10);
|
|
3948
|
-
return isNaN(n) ? v : n;
|
|
3949
|
-
};
|
|
3950
|
-
var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
|
|
3951
|
-
var compareStrings = (a, b) => {
|
|
3952
|
-
if (isWildcard(a) || isWildcard(b))
|
|
3953
|
-
return 0;
|
|
3954
|
-
const [ap, bp] = forceType(tryParse(a), tryParse(b));
|
|
3955
|
-
if (ap > bp)
|
|
3956
|
-
return 1;
|
|
3957
|
-
if (ap < bp)
|
|
3958
|
-
return -1;
|
|
3959
|
-
return 0;
|
|
3960
|
-
};
|
|
3961
|
-
var compareSegments = (a, b) => {
|
|
3962
|
-
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
3963
|
-
const r = compareStrings(a[i] || "0", b[i] || "0");
|
|
3964
|
-
if (r !== 0)
|
|
3965
|
-
return r;
|
|
3966
|
-
}
|
|
3967
|
-
return 0;
|
|
3968
|
-
};
|
|
3969
|
-
|
|
3970
|
-
// node_modules/compare-versions/lib/esm/compareVersions.js
|
|
3971
|
-
var compareVersions = (v1, v2) => {
|
|
3972
|
-
const n1 = validateAndParse(v1);
|
|
3973
|
-
const n2 = validateAndParse(v2);
|
|
3974
|
-
const p1 = n1.pop();
|
|
3975
|
-
const p2 = n2.pop();
|
|
3976
|
-
const r = compareSegments(n1, n2);
|
|
3977
|
-
if (r !== 0)
|
|
3978
|
-
return r;
|
|
3979
|
-
if (p1 && p2) {
|
|
3980
|
-
return compareSegments(p1.split("."), p2.split("."));
|
|
3981
|
-
} else if (p1 || p2) {
|
|
3982
|
-
return p1 ? -1 : 1;
|
|
3983
|
-
}
|
|
3984
|
-
return 0;
|
|
3985
|
-
};
|
|
3986
|
-
|
|
3987
|
-
// src/kernel.ts
|
|
3988
|
-
var GITHUB_REPO = "MetaCubeX/mihomo";
|
|
3989
|
-
var KERNEL_HTTP_TIMEOUT = 12e4;
|
|
3990
|
-
var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
|
|
3991
|
-
var HTTP_CLIENT = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
|
|
3992
|
-
function withMirror(url, mirror) {
|
|
3993
|
-
if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
|
|
3994
|
-
return mirror + url;
|
|
3995
|
-
}
|
|
3996
|
-
return url;
|
|
4249
|
+
function formatProxySummary(info) {
|
|
4250
|
+
const parts = [];
|
|
4251
|
+
if (info.proxyGroups && info.proxyGroups > 0) parts.push(`${info.proxyGroups} \u7EC4`);
|
|
4252
|
+
parts.push(`${info.proxies || 0} \u8282\u70B9`);
|
|
4253
|
+
return parts.join(", ");
|
|
3997
4254
|
}
|
|
3998
|
-
function
|
|
3999
|
-
const
|
|
4000
|
-
if (
|
|
4001
|
-
|
|
4002
|
-
|
|
4255
|
+
function getActiveSubscription() {
|
|
4256
|
+
const subs = getSubscriptions();
|
|
4257
|
+
if (subs.length === 0) return null;
|
|
4258
|
+
const settings = readSettings();
|
|
4259
|
+
const activeName = settings.active_subscription;
|
|
4260
|
+
if (activeName) {
|
|
4261
|
+
const found = subs.find((s) => s.name === activeName);
|
|
4262
|
+
if (found) return found;
|
|
4263
|
+
}
|
|
4264
|
+
return subs[0];
|
|
4003
4265
|
}
|
|
4004
|
-
function
|
|
4005
|
-
const
|
|
4006
|
-
const
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4266
|
+
function findSubscriptionFuzzy(subs, pattern) {
|
|
4267
|
+
const lowerPattern = pattern.toLowerCase();
|
|
4268
|
+
const exact = [];
|
|
4269
|
+
const prefix = [];
|
|
4270
|
+
const includes = [];
|
|
4271
|
+
for (const s of subs) {
|
|
4272
|
+
const name = s.name.toLowerCase();
|
|
4273
|
+
if (name === lowerPattern) {
|
|
4274
|
+
exact.push(s);
|
|
4275
|
+
} else if (name.startsWith(lowerPattern)) {
|
|
4276
|
+
prefix.push(s);
|
|
4277
|
+
} else if (name.includes(lowerPattern)) {
|
|
4278
|
+
includes.push(s);
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
if (exact.length > 0) return exact;
|
|
4282
|
+
if (prefix.length > 0) return prefix;
|
|
4283
|
+
return includes;
|
|
4018
4284
|
}
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
if (!Array.isArray(releases) || releases.length === 0) {
|
|
4024
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F");
|
|
4285
|
+
function pickSingleSubscription(subs, pattern) {
|
|
4286
|
+
if (subs.length === 0) {
|
|
4287
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
|
|
4288
|
+
process.exit(1);
|
|
4025
4289
|
}
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
);
|
|
4029
|
-
|
|
4290
|
+
if (subs.length === 1) return subs[0];
|
|
4291
|
+
console.error("\u9519\u8BEF: \u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A");
|
|
4292
|
+
console.log("\n\u5339\u914D\u7684\u8BA2\u9605:");
|
|
4293
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4294
|
+
process.exit(1);
|
|
4030
4295
|
}
|
|
4031
|
-
async function
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
needsUpdate = compareVersions(latestVersion.replace(/^v/, ""), currentVersion.replace(/^v/, "")) > 0;
|
|
4042
|
-
} catch {
|
|
4043
|
-
needsUpdate = latestVersion !== currentVersion;
|
|
4296
|
+
async function downloadSubscription(url, subName = "default") {
|
|
4297
|
+
let response;
|
|
4298
|
+
try {
|
|
4299
|
+
response = await HTTP_CLIENT.get(url, { responseType: "text" });
|
|
4300
|
+
} catch (e) {
|
|
4301
|
+
const maskedUrl = maskUrl(url);
|
|
4302
|
+
let errorMsg = `\u83B7\u53D6\u8BA2\u9605\u5931\u8D25: ${e.message}`;
|
|
4303
|
+
const err = e;
|
|
4304
|
+
if (err.response) {
|
|
4305
|
+
errorMsg += ` (HTTP ${err.response.status})`;
|
|
4044
4306
|
}
|
|
4307
|
+
errorMsg += `
|
|
4308
|
+
URL: ${maskedUrl}`;
|
|
4309
|
+
throw new Error(errorMsg);
|
|
4310
|
+
}
|
|
4311
|
+
const content = response.data;
|
|
4312
|
+
if (!content?.trim()) {
|
|
4313
|
+
throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4314
|
+
}
|
|
4315
|
+
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
4316
|
+
if (!parsed) throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4317
|
+
saveSubscriptionRawConfig(subName, content);
|
|
4318
|
+
const headers = response.headers;
|
|
4319
|
+
const userInfo = parseUserInfo(headers.get("subscription-userinfo"));
|
|
4320
|
+
const updateIntervalHeader = headers.get("profile-update-interval");
|
|
4321
|
+
const updateInterval = updateIntervalHeader ? parseInt(updateIntervalHeader, 10) : null;
|
|
4322
|
+
const webPageUrl = headers.get("profile-web-page-url") || null;
|
|
4323
|
+
const username = parseUsernameFromContentDisposition(headers.get("content-disposition"));
|
|
4324
|
+
const cacheData = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4325
|
+
if (userInfo) {
|
|
4326
|
+
cacheData.upload = userInfo.upload;
|
|
4327
|
+
cacheData.download = userInfo.download;
|
|
4328
|
+
cacheData.total = userInfo.total;
|
|
4329
|
+
cacheData.expire = userInfo.expire;
|
|
4045
4330
|
}
|
|
4331
|
+
if (updateInterval) cacheData.update_interval = updateInterval;
|
|
4332
|
+
if (webPageUrl) cacheData.web_page_url = webPageUrl;
|
|
4333
|
+
if (username) cacheData.username = username;
|
|
4334
|
+
saveSubscriptionCache(subName, cacheData);
|
|
4335
|
+
const proxies = parsed.proxies;
|
|
4336
|
+
const proxyGroups = parsed["proxy-groups"];
|
|
4046
4337
|
return {
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4338
|
+
proxies: proxies ? proxies.length : 0,
|
|
4339
|
+
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4340
|
+
userInfo,
|
|
4341
|
+
updateInterval,
|
|
4342
|
+
webPageUrl,
|
|
4343
|
+
username
|
|
4052
4344
|
};
|
|
4053
4345
|
}
|
|
4054
|
-
function
|
|
4055
|
-
const
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
const stat = fs6.statSync(fullPath);
|
|
4059
|
-
if (stat.isDirectory()) {
|
|
4060
|
-
const found = findBinaryInDir(fullPath);
|
|
4061
|
-
if (found) return found;
|
|
4062
|
-
continue;
|
|
4063
|
-
}
|
|
4064
|
-
if (f === "mihomo") return fullPath;
|
|
4065
|
-
if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
|
|
4346
|
+
function prepareConfigForStart(mode, subName = "default") {
|
|
4347
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4348
|
+
if (!rawContent) {
|
|
4349
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
|
|
4066
4350
|
}
|
|
4067
|
-
|
|
4351
|
+
const buildResult = buildConfig(rawContent, mode);
|
|
4352
|
+
writeMihomoConfig(buildResult.config);
|
|
4353
|
+
writeDebugConfig(buildResult);
|
|
4354
|
+
const proxies = buildResult.config.proxies;
|
|
4355
|
+
const proxyGroups = buildResult.config["proxy-groups"];
|
|
4356
|
+
return {
|
|
4357
|
+
proxies: proxies ? proxies.length : 0,
|
|
4358
|
+
proxyGroups: proxyGroups ? proxyGroups.length : 0
|
|
4359
|
+
};
|
|
4068
4360
|
}
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
const
|
|
4072
|
-
|
|
4073
|
-
const
|
|
4074
|
-
const
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
4084
|
-
const tempPath = path4.join(DIRS.kernel, asset.name);
|
|
4085
|
-
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
4086
|
-
if (progressCallback) {
|
|
4087
|
-
progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
|
|
4361
|
+
function needsAutoUpdate(sub) {
|
|
4362
|
+
if (!sub.updated_at) return true;
|
|
4363
|
+
const lastUpdate = new Date(sub.updated_at).getTime();
|
|
4364
|
+
if (Number.isNaN(lastUpdate)) return true;
|
|
4365
|
+
const intervalHours = sub.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4366
|
+
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
4367
|
+
return Date.now() - lastUpdate > intervalMs;
|
|
4368
|
+
}
|
|
4369
|
+
async function tryUpdateOne(sub) {
|
|
4370
|
+
try {
|
|
4371
|
+
const info = await downloadSubscription(sub.url, sub.name);
|
|
4372
|
+
return { name: sub.name, success: true, proxies: info.proxies, proxyGroups: info.proxyGroups };
|
|
4373
|
+
} catch (e) {
|
|
4374
|
+
return { name: sub.name, success: false, error: e.message };
|
|
4088
4375
|
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
)
|
|
4094
|
-
|
|
4095
|
-
try {
|
|
4096
|
-
fs6.unlinkSync(tempPath);
|
|
4097
|
-
} catch {
|
|
4098
|
-
}
|
|
4099
|
-
throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
|
|
4376
|
+
}
|
|
4377
|
+
async function autoUpdateStaleSubscription() {
|
|
4378
|
+
const allSubs = getSubscriptionsWithCache();
|
|
4379
|
+
const staleSubs = allSubs.filter(needsAutoUpdate);
|
|
4380
|
+
if (staleSubs.length === 0) {
|
|
4381
|
+
return { total: 0, updated: 0, failed: 0 };
|
|
4100
4382
|
}
|
|
4101
|
-
if (
|
|
4102
|
-
|
|
4383
|
+
if (staleSubs.length === 1) {
|
|
4384
|
+
const sub = staleSubs[0];
|
|
4385
|
+
const interval = sub.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4386
|
+
console.log(`\u8BA2\u9605 "${sub.name}" \u8D85\u8FC7 ${interval} \u5C0F\u65F6\u672A\u66F4\u65B0\uFF0C\u6B63\u5728\u66F4\u65B0...`);
|
|
4387
|
+
} else {
|
|
4388
|
+
console.log(`\u68C0\u67E5\u5230 ${staleSubs.length} \u4E2A\u8BA2\u9605\u9700\u8981\u66F4\u65B0\uFF0C\u6B63\u5728\u5E76\u884C\u66F4\u65B0...`);
|
|
4103
4389
|
}
|
|
4104
|
-
|
|
4105
|
-
|
|
4390
|
+
const results = await Promise.all(staleSubs.map(tryUpdateOne));
|
|
4391
|
+
let updatedCount = 0;
|
|
4392
|
+
for (const r of results) {
|
|
4393
|
+
if (r.success) {
|
|
4394
|
+
updatedCount++;
|
|
4395
|
+
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4396
|
+
} else {
|
|
4397
|
+
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
4398
|
+
}
|
|
4106
4399
|
}
|
|
4107
|
-
|
|
4108
|
-
|
|
4400
|
+
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4401
|
+
}
|
|
4402
|
+
var API_BASE = `http://${BASE_CONFIG["external-controller"]}`;
|
|
4403
|
+
var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
4404
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client) {
|
|
4405
|
+
const encodedName = encodeURIComponent(proxyName);
|
|
4406
|
+
const url = `${API_BASE}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4109
4407
|
try {
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
const outputPath = path4.join(extractPath, baseName);
|
|
4115
|
-
execSync4(`gzip -dc "${tempPath}" > "${outputPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
4116
|
-
extractedBinary = outputPath;
|
|
4408
|
+
const response = await client.get(url);
|
|
4409
|
+
const data = JSON.parse(response.data);
|
|
4410
|
+
if (data.delay && data.delay > 0) {
|
|
4411
|
+
return { name: proxyName, delay: data.delay };
|
|
4117
4412
|
}
|
|
4413
|
+
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
4118
4414
|
} catch (e) {
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4415
|
+
const err = e;
|
|
4416
|
+
let errorMsg = "timeout";
|
|
4417
|
+
if (err.response?.data?.message) {
|
|
4418
|
+
errorMsg = String(err.response.data.message);
|
|
4419
|
+
} else if (err.message) {
|
|
4420
|
+
errorMsg = err.message;
|
|
4122
4421
|
}
|
|
4123
|
-
|
|
4422
|
+
return { name: proxyName, delay: null, error: errorMsg };
|
|
4124
4423
|
}
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
}
|
|
4131
|
-
throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
|
|
4424
|
+
}
|
|
4425
|
+
async function testSubscriptionProxies(subName, options = {}) {
|
|
4426
|
+
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, onResult } = options;
|
|
4427
|
+
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4428
|
+
if (proxies.length === 0) {
|
|
4429
|
+
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
4132
4430
|
}
|
|
4133
|
-
const
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4431
|
+
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
4432
|
+
const results = [];
|
|
4433
|
+
let completedCount = 0;
|
|
4434
|
+
for (let i = 0; i < proxies.length; i += concurrency) {
|
|
4435
|
+
const batch = proxies.slice(i, i + concurrency);
|
|
4436
|
+
const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client)));
|
|
4437
|
+
for (const result of batchResults) {
|
|
4438
|
+
results.push(result);
|
|
4439
|
+
onResult?.(result, completedCount, proxies.length);
|
|
4440
|
+
completedCount++;
|
|
4141
4441
|
}
|
|
4142
|
-
fs6.renameSync(foundBinary, targetPath);
|
|
4143
|
-
}
|
|
4144
|
-
fs6.chmodSync(targetPath, 493);
|
|
4145
|
-
try {
|
|
4146
|
-
fs6.unlinkSync(tempPath);
|
|
4147
|
-
} catch {
|
|
4148
4442
|
}
|
|
4149
|
-
|
|
4150
|
-
return {
|
|
4443
|
+
const alive = results.filter((r) => r.delay !== null).length;
|
|
4444
|
+
return { total: results.length, alive, dead: results.length - alive, results };
|
|
4151
4445
|
}
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
const
|
|
4156
|
-
const
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4446
|
+
function normalizeProxyNamesBeforeSave(parsed) {
|
|
4447
|
+
const { proxies, proxyGroups } = parsed;
|
|
4448
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
4449
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
4450
|
+
for (const proxy of proxies) {
|
|
4451
|
+
const shortened = proxy.name.replace(/_github\.com\/[^_]+/, "");
|
|
4452
|
+
if (shortened !== proxy.name && !usedNames.has(shortened)) {
|
|
4453
|
+
renameMap.set(proxy.name, shortened);
|
|
4454
|
+
usedNames.add(shortened);
|
|
4455
|
+
} else {
|
|
4456
|
+
usedNames.add(proxy.name);
|
|
4457
|
+
}
|
|
4160
4458
|
}
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
console.log(" mihomo kernel --mirror hk.gh-proxy.org # \u4E0B\u8F7D\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
4166
|
-
console.log(" mihomo kernel --mirror-all # API\u8BF7\u6C42\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF");
|
|
4167
|
-
console.log(" mihomo kernel --mirror-all hk.gh-proxy.org # API\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
4168
|
-
console.log("\n\u53EF\u7528\u955C\u50CF:");
|
|
4169
|
-
for (const m of AVAILABLE_MIRRORS) {
|
|
4170
|
-
const isCurrent = effectiveMirror && (effectiveMirror.includes(`//${m}/`) || effectiveMirror.includes(`//${m}:`) || effectiveMirror.endsWith(`//${m}`));
|
|
4171
|
-
console.log(` ${m}${isCurrent ? " (\u5F53\u524D)" : ""}`);
|
|
4459
|
+
if (renameMap.size === 0) return 0;
|
|
4460
|
+
for (const proxy of proxies) {
|
|
4461
|
+
const newName = renameMap.get(proxy.name);
|
|
4462
|
+
if (newName) proxy.name = newName;
|
|
4172
4463
|
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
const apiMirror = mirrorInfo.type === "all" ? effectiveMirror : null;
|
|
4177
|
-
const info = await checkUpdate(apiMirror);
|
|
4178
|
-
console.log(`\u5F53\u524D: ${info.current}`);
|
|
4179
|
-
console.log(`\u6700\u65B0: ${info.latest}`);
|
|
4180
|
-
if (!info.needsUpdate) {
|
|
4181
|
-
console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
4182
|
-
} else {
|
|
4183
|
-
console.log("\n\u6B63\u5728\u4E0B\u8F7D...");
|
|
4184
|
-
const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
|
|
4185
|
-
console.log(`
|
|
4186
|
-
\u5DF2\u66F4\u65B0\u5230 ${result.version}`);
|
|
4464
|
+
for (const group of proxyGroups) {
|
|
4465
|
+
if (Array.isArray(group.proxies)) {
|
|
4466
|
+
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4187
4467
|
}
|
|
4188
|
-
}
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4468
|
+
}
|
|
4469
|
+
return renameMap.size;
|
|
4470
|
+
}
|
|
4471
|
+
function cleanDeadProxies(parsed, deadNames) {
|
|
4472
|
+
const { proxies, proxyGroups } = parsed;
|
|
4473
|
+
const originalCount = proxies.length;
|
|
4474
|
+
parsed.proxies = proxies.filter((p) => !deadNames.has(p.name));
|
|
4475
|
+
const removedProxies = originalCount - parsed.proxies.length;
|
|
4476
|
+
let updatedGroups = 0;
|
|
4477
|
+
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
4478
|
+
for (const group of proxyGroups) {
|
|
4479
|
+
if (Array.isArray(group.proxies)) {
|
|
4480
|
+
const before = group.proxies.length;
|
|
4481
|
+
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
4482
|
+
if (group.proxies.length < before) {
|
|
4483
|
+
updatedGroups++;
|
|
4195
4484
|
}
|
|
4196
|
-
if (
|
|
4197
|
-
|
|
4485
|
+
if (group.proxies.length === 0) {
|
|
4486
|
+
removedGroupNames.add(group.name);
|
|
4198
4487
|
}
|
|
4199
4488
|
}
|
|
4200
|
-
process.exit(1);
|
|
4201
4489
|
}
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
return;
|
|
4490
|
+
if (removedGroupNames.size > 0) {
|
|
4491
|
+
parsed.proxyGroups = proxyGroups.filter((g) => !removedGroupNames.has(g.name));
|
|
4492
|
+
for (const group of parsed.proxyGroups) {
|
|
4493
|
+
if (Array.isArray(group.proxies)) {
|
|
4494
|
+
group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4210
4497
|
}
|
|
4211
|
-
|
|
4498
|
+
return { removedProxies, updatedGroups, removedGroups: removedGroupNames.size };
|
|
4212
4499
|
}
|
|
4213
|
-
function
|
|
4214
|
-
const
|
|
4215
|
-
const
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4500
|
+
async function autoCleanSubscription(subName, options = {}) {
|
|
4501
|
+
const parsed = loadSubscriptionConfig(subName);
|
|
4502
|
+
const summary = await testSubscriptionProxies(subName, { ...options, parsed });
|
|
4503
|
+
let removedProxies = 0;
|
|
4504
|
+
let updatedGroups = 0;
|
|
4505
|
+
let removedGroups = 0;
|
|
4506
|
+
let skipped = false;
|
|
4507
|
+
if (summary.dead > 0) {
|
|
4508
|
+
if (summary.alive === 0 || summary.alive / summary.total < 0.01) {
|
|
4509
|
+
skipped = true;
|
|
4221
4510
|
} else {
|
|
4222
|
-
const
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
4228
|
-
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
4229
|
-
process.exit(1);
|
|
4230
|
-
}
|
|
4231
|
-
logPath = archive.path;
|
|
4232
|
-
} else {
|
|
4233
|
-
logPath = getLogPathByName(targetName);
|
|
4234
|
-
}
|
|
4235
|
-
}
|
|
4236
|
-
if (!logPath) {
|
|
4237
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
4238
|
-
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
4239
|
-
process.exit(1);
|
|
4240
|
-
}
|
|
4241
|
-
if (openInViewer) {
|
|
4242
|
-
openLogFile(logPath);
|
|
4243
|
-
return;
|
|
4511
|
+
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
4512
|
+
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
4513
|
+
removedProxies = cleanResult.removedProxies;
|
|
4514
|
+
updatedGroups = cleanResult.updatedGroups;
|
|
4515
|
+
removedGroups = cleanResult.removedGroups;
|
|
4244
4516
|
}
|
|
4245
|
-
viewLogWithTail(logPath, { follow: false, lines });
|
|
4246
|
-
return;
|
|
4247
4517
|
}
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
if (logs.current) all.push(logs.current);
|
|
4251
|
-
all.push(...logs.archives);
|
|
4252
|
-
if (all.length === 0) {
|
|
4253
|
-
console.log("\u6682\u65E0\u65E5\u5FD7");
|
|
4254
|
-
return;
|
|
4518
|
+
if (!skipped) {
|
|
4519
|
+
saveSubscriptionConfig(subName, parsed);
|
|
4255
4520
|
}
|
|
4521
|
+
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
// src/commands/status.ts
|
|
4525
|
+
function printStatus() {
|
|
4526
|
+
const status = getStatus();
|
|
4527
|
+
const info = getConfigInfo();
|
|
4528
|
+
const overwriteEnabled = isOverwriteEnabled();
|
|
4529
|
+
const overwriteFiles = listOverwriteFile().files;
|
|
4530
|
+
const activeSub = getActiveSubscription();
|
|
4256
4531
|
console.log("");
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4532
|
+
let modeLabel = "";
|
|
4533
|
+
if (info && status.running) {
|
|
4534
|
+
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
4535
|
+
}
|
|
4536
|
+
const statusText = status.running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
|
|
4537
|
+
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
|
|
4538
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
|
|
4539
|
+
if (status.pid) {
|
|
4540
|
+
console.log(`${colors.gray("PID: ")}${status.pid}`);
|
|
4541
|
+
if (status.processInfo) {
|
|
4542
|
+
console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
if (info) {
|
|
4546
|
+
if (info.mixedPort) {
|
|
4547
|
+
console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
|
|
4264
4548
|
} else {
|
|
4265
|
-
|
|
4266
|
-
|
|
4549
|
+
const ports = [];
|
|
4550
|
+
if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
|
|
4551
|
+
if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
|
|
4552
|
+
console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
|
|
4267
4553
|
}
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
if (!log.isCurrent) {
|
|
4274
|
-
console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
|
|
4554
|
+
}
|
|
4555
|
+
if (activeSub) {
|
|
4556
|
+
let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
|
|
4557
|
+
if (info) {
|
|
4558
|
+
subLine += ` (${formatProxySummary(info)})`;
|
|
4275
4559
|
}
|
|
4276
|
-
console.log(
|
|
4560
|
+
console.log(subLine);
|
|
4561
|
+
} else {
|
|
4562
|
+
console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
|
|
4563
|
+
}
|
|
4564
|
+
if (overwriteEnabled && overwriteFiles.length > 0) {
|
|
4565
|
+
const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
|
|
4566
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
|
|
4567
|
+
} else if (overwriteEnabled) {
|
|
4568
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
|
|
4569
|
+
} else {
|
|
4570
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
|
|
4277
4571
|
}
|
|
4278
|
-
console.log("\u7528\u6CD5:");
|
|
4279
|
-
console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
|
|
4280
|
-
console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
|
|
4281
|
-
console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
|
|
4282
|
-
console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
|
|
4283
4572
|
console.log("");
|
|
4284
4573
|
}
|
|
4285
4574
|
|
|
4286
|
-
// src/commands/
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
const rawContent = readSubscriptionRawConfig(subName);
|
|
4295
|
-
if (!rawContent) {
|
|
4296
|
-
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
4575
|
+
// src/commands/start.ts
|
|
4576
|
+
var AUTO_CLEAN_THRESHOLD = 100;
|
|
4577
|
+
var AUTO_CLEAN_THRESHOLD_FREE = 50;
|
|
4578
|
+
function handleStopResult(result) {
|
|
4579
|
+
if (result.remaining && result.remaining.length > 0) {
|
|
4580
|
+
console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
|
|
4581
|
+
console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
|
|
4582
|
+
process.exit(1);
|
|
4297
4583
|
}
|
|
4298
|
-
const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4299
|
-
return {
|
|
4300
|
-
raw,
|
|
4301
|
-
proxies: raw.proxies || [],
|
|
4302
|
-
proxyGroups: raw["proxy-groups"] || []
|
|
4303
|
-
};
|
|
4304
|
-
}
|
|
4305
|
-
function saveSubscriptionConfig(subName, parsed) {
|
|
4306
|
-
shortenProxyNames(parsed);
|
|
4307
|
-
parsed.raw.proxies = parsed.proxies;
|
|
4308
|
-
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4309
|
-
saveSubscriptionRawConfig(subName, jsYaml.dump(parsed.raw, YAML_DUMP_OPTS));
|
|
4310
4584
|
}
|
|
4311
|
-
function
|
|
4312
|
-
if (!
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4585
|
+
async function cmdStart(args) {
|
|
4586
|
+
if (!hasKernel()) {
|
|
4587
|
+
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
4588
|
+
process.exit(1);
|
|
4589
|
+
}
|
|
4590
|
+
const targetMode = args[1] === "tun" ? "tun" : "mixed";
|
|
4591
|
+
const sub = getActiveSubscription();
|
|
4592
|
+
if (!sub) {
|
|
4593
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4594
|
+
process.exit(1);
|
|
4595
|
+
}
|
|
4596
|
+
await autoUpdateStaleSubscription();
|
|
4597
|
+
const status = getStatus();
|
|
4598
|
+
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
4599
|
+
if (hasProcess) {
|
|
4600
|
+
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
4601
|
+
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
4602
|
+
}
|
|
4603
|
+
handleStopResult(stop());
|
|
4604
|
+
if (hasProcess) {
|
|
4605
|
+
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
4606
|
+
`);
|
|
4607
|
+
}
|
|
4608
|
+
let configInfo;
|
|
4609
|
+
try {
|
|
4610
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
4611
|
+
} catch (e) {
|
|
4612
|
+
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
4613
|
+
process.exit(1);
|
|
4614
|
+
}
|
|
4615
|
+
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
4616
|
+
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
4617
|
+
try {
|
|
4618
|
+
const result = await start(targetMode);
|
|
4619
|
+
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
|
|
4620
|
+
} catch (e) {
|
|
4621
|
+
const msg = e.message;
|
|
4622
|
+
const lines = msg.split("\n");
|
|
4623
|
+
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
|
|
4624
|
+
if (lines.length > 1) {
|
|
4625
|
+
for (const line of lines.slice(1)) console.error(line);
|
|
4320
4626
|
}
|
|
4627
|
+
process.exit(1);
|
|
4321
4628
|
}
|
|
4322
|
-
|
|
4629
|
+
const cleanThreshold = sub.name.startsWith("free") ? AUTO_CLEAN_THRESHOLD_FREE : AUTO_CLEAN_THRESHOLD;
|
|
4630
|
+
if (configInfo.proxies > cleanThreshold) {
|
|
4631
|
+
console.log("");
|
|
4632
|
+
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${cleanThreshold}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
|
|
4633
|
+
console.log("");
|
|
4634
|
+
await sleep(1e3);
|
|
4635
|
+
const cleanResult = await autoCleanSubscription(sub.name, { onResult: printTestResult });
|
|
4636
|
+
console.log("");
|
|
4637
|
+
console.log(formatTestSummary(cleanResult.summary));
|
|
4638
|
+
if (cleanResult.skipped) {
|
|
4639
|
+
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"));
|
|
4640
|
+
} else if (cleanResult.removedProxies > 0) {
|
|
4641
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
4642
|
+
console.log("");
|
|
4643
|
+
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
4644
|
+
handleStopResult(stop());
|
|
4645
|
+
try {
|
|
4646
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
4647
|
+
const result = await start(targetMode);
|
|
4648
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${result.pid}) \xB7 ${formatProxySummary(configInfo)}`);
|
|
4649
|
+
} catch (e) {
|
|
4650
|
+
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
4651
|
+
process.exit(1);
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4655
|
+
printStatus();
|
|
4323
4656
|
}
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4657
|
+
|
|
4658
|
+
// src/commands/subscription.ts
|
|
4659
|
+
function printTestResult(result, index, total) {
|
|
4660
|
+
const prefix = `[${index + 1}/${total}]`;
|
|
4661
|
+
if (result.delay !== null) {
|
|
4662
|
+
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
4663
|
+
console.log(` ${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}`);
|
|
4664
|
+
} else {
|
|
4665
|
+
console.log(` ${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
4666
|
+
}
|
|
4331
4667
|
}
|
|
4332
|
-
function
|
|
4333
|
-
const parts = [];
|
|
4334
|
-
if (
|
|
4335
|
-
parts.push(
|
|
4668
|
+
function formatCleanSummary(result) {
|
|
4669
|
+
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
4670
|
+
if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
|
|
4671
|
+
if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
|
|
4336
4672
|
return parts.join(", ");
|
|
4337
4673
|
}
|
|
4338
|
-
function
|
|
4339
|
-
|
|
4340
|
-
if (subs.length === 0) return null;
|
|
4341
|
-
const settings = readSettings();
|
|
4342
|
-
const activeName = settings.active_subscription;
|
|
4343
|
-
if (activeName) {
|
|
4344
|
-
const found = subs.find((s) => s.name === activeName);
|
|
4345
|
-
if (found) return found;
|
|
4346
|
-
}
|
|
4347
|
-
return subs[0];
|
|
4674
|
+
function formatTestSummary(summary) {
|
|
4675
|
+
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
4348
4676
|
}
|
|
4349
|
-
function
|
|
4350
|
-
const
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
const includes = [];
|
|
4354
|
-
for (const s of subs) {
|
|
4355
|
-
const name = s.name.toLowerCase();
|
|
4356
|
-
if (name === lowerPattern) {
|
|
4357
|
-
exact.push(s);
|
|
4358
|
-
} else if (name.startsWith(lowerPattern)) {
|
|
4359
|
-
prefix.push(s);
|
|
4360
|
-
} else if (name.includes(lowerPattern)) {
|
|
4361
|
-
includes.push(s);
|
|
4362
|
-
}
|
|
4363
|
-
}
|
|
4364
|
-
if (exact.length > 0) return exact;
|
|
4365
|
-
if (prefix.length > 0) return prefix;
|
|
4366
|
-
return includes;
|
|
4677
|
+
function githubRepoUrl(rawUrl) {
|
|
4678
|
+
const match = rawUrl.match(/raw\.githubusercontent\.com\/([^/]+\/[^/]+)/);
|
|
4679
|
+
if (match) return `https://github.com/${match[1]}`;
|
|
4680
|
+
return null;
|
|
4367
4681
|
}
|
|
4368
|
-
function
|
|
4682
|
+
function resolveActiveTestTarget(args) {
|
|
4683
|
+
const subs = getSubscriptions();
|
|
4369
4684
|
if (subs.length === 0) {
|
|
4370
|
-
console.error(
|
|
4685
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4371
4686
|
process.exit(1);
|
|
4372
4687
|
}
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
let errorMsg = `\u83B7\u53D6\u8BA2\u9605\u5931\u8D25: ${e.message}`;
|
|
4386
|
-
const err = e;
|
|
4387
|
-
if (err.response) {
|
|
4388
|
-
errorMsg += ` (HTTP ${err.response.status})`;
|
|
4688
|
+
const nameArg = getNonFlagArg(args, 2);
|
|
4689
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 2e3);
|
|
4690
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
4691
|
+
const activeSub = getActiveSubscription();
|
|
4692
|
+
let target;
|
|
4693
|
+
if (nameArg) {
|
|
4694
|
+
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
4695
|
+
target = pickSingleSubscription(matches, nameArg);
|
|
4696
|
+
} else {
|
|
4697
|
+
if (!activeSub) {
|
|
4698
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
4699
|
+
process.exit(1);
|
|
4389
4700
|
}
|
|
4390
|
-
|
|
4391
|
-
URL: ${maskedUrl}`;
|
|
4392
|
-
throw new Error(errorMsg);
|
|
4393
|
-
}
|
|
4394
|
-
const content = response.data;
|
|
4395
|
-
if (!content?.trim()) {
|
|
4396
|
-
throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4701
|
+
target = activeSub;
|
|
4397
4702
|
}
|
|
4398
|
-
const
|
|
4399
|
-
if (!
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
const userInfo = parseUserInfo(headers.get("subscription-userinfo"));
|
|
4403
|
-
const updateIntervalHeader = headers.get("profile-update-interval");
|
|
4404
|
-
const updateInterval = updateIntervalHeader ? parseInt(updateIntervalHeader, 10) : null;
|
|
4405
|
-
const webPageUrl = headers.get("profile-web-page-url") || null;
|
|
4406
|
-
const username = parseUsernameFromContentDisposition(headers.get("content-disposition"));
|
|
4407
|
-
const cacheData = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4408
|
-
if (userInfo) {
|
|
4409
|
-
cacheData.upload = userInfo.upload;
|
|
4410
|
-
cacheData.download = userInfo.download;
|
|
4411
|
-
cacheData.total = userInfo.total;
|
|
4412
|
-
cacheData.expire = userInfo.expire;
|
|
4703
|
+
const status = getStatus();
|
|
4704
|
+
if (!status.running) {
|
|
4705
|
+
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
4706
|
+
process.exit(1);
|
|
4413
4707
|
}
|
|
4414
|
-
if (
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
const proxies = parsed.proxies;
|
|
4419
|
-
const proxyGroups = parsed["proxy-groups"];
|
|
4420
|
-
return {
|
|
4421
|
-
proxies: proxies ? proxies.length : 0,
|
|
4422
|
-
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4423
|
-
userInfo,
|
|
4424
|
-
updateInterval,
|
|
4425
|
-
webPageUrl,
|
|
4426
|
-
username
|
|
4427
|
-
};
|
|
4428
|
-
}
|
|
4429
|
-
function prepareConfigForStart(mode, subName = "default") {
|
|
4430
|
-
const rawContent = readSubscriptionRawConfig(subName);
|
|
4431
|
-
if (!rawContent) {
|
|
4432
|
-
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
|
|
4708
|
+
if (!activeSub || activeSub.name !== target.name) {
|
|
4709
|
+
console.error(`\u9519\u8BEF: \u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605\u662F "${activeSub?.name}"\uFF0C\u4E0D\u662F "${target.name}"`);
|
|
4710
|
+
console.log(`\u8BF7\u5148\u5207\u6362: mihomo sub use ${target.name}`);
|
|
4711
|
+
process.exit(1);
|
|
4433
4712
|
}
|
|
4434
|
-
|
|
4435
|
-
writeMihomoConfig(buildResult.config);
|
|
4436
|
-
writeDebugConfig(buildResult);
|
|
4437
|
-
const proxies = buildResult.config.proxies;
|
|
4438
|
-
const proxyGroups = buildResult.config["proxy-groups"];
|
|
4439
|
-
return {
|
|
4440
|
-
proxies: proxies ? proxies.length : 0,
|
|
4441
|
-
proxyGroups: proxyGroups ? proxyGroups.length : 0
|
|
4442
|
-
};
|
|
4713
|
+
return { target, timeout, concurrency };
|
|
4443
4714
|
}
|
|
4444
|
-
function
|
|
4445
|
-
if (
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
const
|
|
4450
|
-
|
|
4715
|
+
async function printSubscriptionList(options) {
|
|
4716
|
+
if (options?.autoUpdate !== false) {
|
|
4717
|
+
const updateResult = await autoUpdateStaleSubscription();
|
|
4718
|
+
if (updateResult.total > 0) console.log("");
|
|
4719
|
+
}
|
|
4720
|
+
const subs = getSubscriptionsWithCache();
|
|
4721
|
+
if (subs.length === 0) {
|
|
4722
|
+
console.log("\u6CA1\u6709\u8BA2\u9605");
|
|
4723
|
+
console.log("");
|
|
4724
|
+
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4725
|
+
console.log("");
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4728
|
+
const activeSub = getActiveSubscription();
|
|
4729
|
+
console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
|
|
4730
|
+
subs.forEach((s, i) => {
|
|
4731
|
+
const time = formatDate(s.updated_at);
|
|
4732
|
+
const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
|
|
4733
|
+
const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4734
|
+
console.log(` ${i + 1}. ${s.name}${defaultMark}`);
|
|
4735
|
+
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
4736
|
+
if (s.username) {
|
|
4737
|
+
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
4738
|
+
}
|
|
4739
|
+
if (s.download !== void 0 || s.total !== void 0) {
|
|
4740
|
+
const used = (s.upload || 0) + (s.download || 0);
|
|
4741
|
+
const usedStr = formatBytes(used);
|
|
4742
|
+
const totalStr = formatBytes(s.total);
|
|
4743
|
+
let percentStr = "";
|
|
4744
|
+
if (s.total && s.total > 0) {
|
|
4745
|
+
const percent = Math.min(used / s.total * 100, 100);
|
|
4746
|
+
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
4747
|
+
}
|
|
4748
|
+
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
4749
|
+
}
|
|
4750
|
+
if (s.expire !== void 0) {
|
|
4751
|
+
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
4752
|
+
}
|
|
4753
|
+
if (s.web_page_url) {
|
|
4754
|
+
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
4755
|
+
}
|
|
4756
|
+
});
|
|
4757
|
+
console.log("");
|
|
4758
|
+
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
4759
|
+
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4760
|
+
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
4761
|
+
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
4762
|
+
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
4763
|
+
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
4764
|
+
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
4765
|
+
console.log("");
|
|
4451
4766
|
}
|
|
4452
|
-
async function
|
|
4767
|
+
async function addFreeSubscription(freeId) {
|
|
4768
|
+
const freeSources = getFreeSubscriptionSources();
|
|
4769
|
+
if (freeId < 1 || freeId > freeSources.length) {
|
|
4770
|
+
console.error(`\u9519\u8BEF: \u514D\u8D39\u8BA2\u9605 ID \u8303\u56F4 1-${freeSources.length}`);
|
|
4771
|
+
console.log("\n\u53EF\u7528\u6E90:");
|
|
4772
|
+
for (let i = 0; i < freeSources.length; i++) {
|
|
4773
|
+
console.log(` ${String(i + 1).padStart(2, "0")} ${freeSources[i].name}`);
|
|
4774
|
+
}
|
|
4775
|
+
process.exit(1);
|
|
4776
|
+
}
|
|
4777
|
+
const source = freeSources[freeId - 1];
|
|
4778
|
+
const name = `free${freeId}`;
|
|
4779
|
+
console.log(`\u6DFB\u52A0\u514D\u8D39\u8BA2\u9605: ${name}`);
|
|
4453
4780
|
try {
|
|
4454
|
-
|
|
4455
|
-
|
|
4781
|
+
addSubscription(source.url, name);
|
|
4782
|
+
setDefaultSubscription(name);
|
|
4783
|
+
const info = await downloadSubscription(source.url, name);
|
|
4784
|
+
const repoUrl = githubRepoUrl(source.url);
|
|
4785
|
+
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
4786
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4456
4787
|
} catch (e) {
|
|
4457
|
-
|
|
4788
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4789
|
+
process.exit(1);
|
|
4458
4790
|
}
|
|
4791
|
+
console.log("");
|
|
4792
|
+
await printSubscriptionList();
|
|
4459
4793
|
}
|
|
4460
|
-
async function
|
|
4461
|
-
const
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
return
|
|
4465
|
-
}
|
|
4466
|
-
if (staleSubs.length === 1) {
|
|
4467
|
-
const sub = staleSubs[0];
|
|
4468
|
-
const interval = sub.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4469
|
-
console.log(`\u8BA2\u9605 "${sub.name}" \u8D85\u8FC7 ${interval} \u5C0F\u65F6\u672A\u66F4\u65B0\uFF0C\u6B63\u5728\u66F4\u65B0...`);
|
|
4470
|
-
} else {
|
|
4471
|
-
console.log(`\u68C0\u67E5\u5230 ${staleSubs.length} \u4E2A\u8BA2\u9605\u9700\u8981\u66F4\u65B0\uFF0C\u6B63\u5728\u5E76\u884C\u66F4\u65B0...`);
|
|
4794
|
+
async function cmdSubscription(args) {
|
|
4795
|
+
const action = args[1];
|
|
4796
|
+
if (!action || action === "list") {
|
|
4797
|
+
await printSubscriptionList();
|
|
4798
|
+
return;
|
|
4472
4799
|
}
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
console.log(
|
|
4479
|
-
|
|
4480
|
-
|
|
4800
|
+
if (action === "free") {
|
|
4801
|
+
const id = parseInt(args[2], 10);
|
|
4802
|
+
if (!id || Number.isNaN(id)) {
|
|
4803
|
+
const freeSources = getFreeSubscriptionSources();
|
|
4804
|
+
console.log("\u7528\u6CD5: mihomo sub free <id>\n");
|
|
4805
|
+
console.log("\u53EF\u7528\u6E90:");
|
|
4806
|
+
for (let i = 0; i < freeSources.length; i++) {
|
|
4807
|
+
console.log(` ${String(i + 1).padStart(2, "0")} ${freeSources[i].name}`);
|
|
4808
|
+
}
|
|
4809
|
+
process.exit(1);
|
|
4481
4810
|
}
|
|
4811
|
+
await addFreeSubscription(id);
|
|
4812
|
+
return;
|
|
4482
4813
|
}
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
const encodedName = encodeURIComponent(proxyName);
|
|
4489
|
-
const url = `${API_BASE}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4490
|
-
try {
|
|
4491
|
-
const response = await client.get(url);
|
|
4492
|
-
const data = JSON.parse(response.data);
|
|
4493
|
-
if (data.delay && data.delay > 0) {
|
|
4494
|
-
return { name: proxyName, delay: data.delay };
|
|
4814
|
+
if (action === "add") {
|
|
4815
|
+
const freeId = parseIntArg(args, "--free", "--free", -1);
|
|
4816
|
+
if (freeId > 0) {
|
|
4817
|
+
await addFreeSubscription(freeId);
|
|
4818
|
+
return;
|
|
4495
4819
|
}
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
errorMsg = String(err.response.data.message);
|
|
4502
|
-
} else if (err.message) {
|
|
4503
|
-
errorMsg = err.message;
|
|
4820
|
+
const url = args[2];
|
|
4821
|
+
const name = args[3] || "default";
|
|
4822
|
+
if (!url?.startsWith("http")) {
|
|
4823
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
4824
|
+
process.exit(1);
|
|
4504
4825
|
}
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4826
|
+
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
4827
|
+
try {
|
|
4828
|
+
addSubscription(url, name);
|
|
4829
|
+
setDefaultSubscription(name);
|
|
4830
|
+
const info = await downloadSubscription(url, name);
|
|
4831
|
+
const repoUrl = githubRepoUrl(url);
|
|
4832
|
+
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
4833
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4834
|
+
} catch (e) {
|
|
4835
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4836
|
+
process.exit(1);
|
|
4837
|
+
}
|
|
4838
|
+
console.log("");
|
|
4839
|
+
await printSubscriptionList();
|
|
4840
|
+
return;
|
|
4513
4841
|
}
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
for (const result of batchResults) {
|
|
4521
|
-
results.push(result);
|
|
4522
|
-
onResult?.(result, completedCount, proxies.length);
|
|
4523
|
-
completedCount++;
|
|
4842
|
+
if (action === "update") {
|
|
4843
|
+
const name = args[2];
|
|
4844
|
+
const subs = getSubscriptions();
|
|
4845
|
+
if (subs.length === 0) {
|
|
4846
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4847
|
+
process.exit(1);
|
|
4524
4848
|
}
|
|
4849
|
+
if (!name) {
|
|
4850
|
+
console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
|
|
4851
|
+
const results = await Promise.all(subs.map(tryUpdateOne));
|
|
4852
|
+
let ok = 0;
|
|
4853
|
+
for (const r of results) {
|
|
4854
|
+
if (r.success) {
|
|
4855
|
+
ok++;
|
|
4856
|
+
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4857
|
+
} else {
|
|
4858
|
+
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
if (ok === 0) process.exit(1);
|
|
4862
|
+
console.log("");
|
|
4863
|
+
await printSubscriptionList();
|
|
4864
|
+
return;
|
|
4865
|
+
}
|
|
4866
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4867
|
+
const target = pickSingleSubscription(matches, name);
|
|
4868
|
+
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
4869
|
+
try {
|
|
4870
|
+
const info = await downloadSubscription(target.url, target.name);
|
|
4871
|
+
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
|
|
4872
|
+
} catch (e) {
|
|
4873
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4874
|
+
process.exit(1);
|
|
4875
|
+
}
|
|
4876
|
+
console.log("");
|
|
4877
|
+
await printSubscriptionList();
|
|
4878
|
+
return;
|
|
4525
4879
|
}
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4880
|
+
if (action === "use") {
|
|
4881
|
+
const name = args[2];
|
|
4882
|
+
const subs = getSubscriptions();
|
|
4883
|
+
if (!name) {
|
|
4884
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
4885
|
+
if (subs.length > 0) {
|
|
4886
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4887
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4888
|
+
}
|
|
4889
|
+
process.exit(1);
|
|
4890
|
+
}
|
|
4891
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4892
|
+
const target = pickSingleSubscription(matches, name);
|
|
4893
|
+
const currentDefault = getActiveSubscription();
|
|
4894
|
+
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
4895
|
+
if (isAlreadyDefault) {
|
|
4896
|
+
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
4897
|
+
console.log("");
|
|
4898
|
+
await printSubscriptionList();
|
|
4899
|
+
return;
|
|
4900
|
+
}
|
|
4901
|
+
const status = getStatus();
|
|
4902
|
+
const configInfo = getConfigInfo();
|
|
4903
|
+
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
4904
|
+
const success = setDefaultSubscription(target.name);
|
|
4905
|
+
if (success) {
|
|
4906
|
+
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
4538
4907
|
} else {
|
|
4539
|
-
|
|
4908
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
4909
|
+
process.exit(1);
|
|
4540
4910
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
if (newName) proxy.name = newName;
|
|
4546
|
-
}
|
|
4547
|
-
for (const group of proxyGroups) {
|
|
4548
|
-
if (Array.isArray(group.proxies)) {
|
|
4549
|
-
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4911
|
+
if (status.running) {
|
|
4912
|
+
console.log("");
|
|
4913
|
+
await cmdStart(["start", currentMode]);
|
|
4914
|
+
return;
|
|
4550
4915
|
}
|
|
4916
|
+
console.log("");
|
|
4917
|
+
await printSubscriptionList();
|
|
4918
|
+
return;
|
|
4551
4919
|
}
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
const removedProxies = originalCount - parsed.proxies.length;
|
|
4559
|
-
let updatedGroups = 0;
|
|
4560
|
-
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
4561
|
-
for (const group of proxyGroups) {
|
|
4562
|
-
if (Array.isArray(group.proxies)) {
|
|
4563
|
-
const before = group.proxies.length;
|
|
4564
|
-
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
4565
|
-
if (group.proxies.length < before) {
|
|
4566
|
-
updatedGroups++;
|
|
4567
|
-
}
|
|
4568
|
-
if (group.proxies.length === 0) {
|
|
4569
|
-
removedGroupNames.add(group.name);
|
|
4570
|
-
}
|
|
4920
|
+
if (action === "web" || action === "open") {
|
|
4921
|
+
const name = args[2];
|
|
4922
|
+
const subs = getSubscriptionsWithCache();
|
|
4923
|
+
if (subs.length === 0) {
|
|
4924
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4925
|
+
process.exit(1);
|
|
4571
4926
|
}
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4927
|
+
let target;
|
|
4928
|
+
if (name) {
|
|
4929
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4930
|
+
target = pickSingleSubscription(matches, name);
|
|
4931
|
+
} else {
|
|
4932
|
+
target = subs[0];
|
|
4933
|
+
}
|
|
4934
|
+
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
4935
|
+
let webPageUrl = cached?.web_page_url;
|
|
4936
|
+
if (!webPageUrl) {
|
|
4937
|
+
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
4938
|
+
try {
|
|
4939
|
+
await downloadSubscription(target.url, target.name);
|
|
4940
|
+
const cache = readSubscriptionCache();
|
|
4941
|
+
if (cache[target.name]?.web_page_url) {
|
|
4942
|
+
webPageUrl = cache[target.name].web_page_url;
|
|
4943
|
+
} else {
|
|
4944
|
+
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
4945
|
+
process.exit(1);
|
|
4946
|
+
}
|
|
4947
|
+
} catch (e) {
|
|
4948
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4949
|
+
process.exit(1);
|
|
4578
4950
|
}
|
|
4579
4951
|
}
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
const parsed = loadSubscriptionConfig(subName);
|
|
4585
|
-
const summary = await testSubscriptionProxies(subName, { ...options, parsed });
|
|
4586
|
-
let removedProxies = 0;
|
|
4587
|
-
let updatedGroups = 0;
|
|
4588
|
-
let removedGroups = 0;
|
|
4589
|
-
let skipped = false;
|
|
4590
|
-
if (summary.dead > 0) {
|
|
4591
|
-
if (summary.alive === 0 || summary.alive / summary.total < 0.01) {
|
|
4592
|
-
skipped = true;
|
|
4593
|
-
} else {
|
|
4594
|
-
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
4595
|
-
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
4596
|
-
removedProxies = cleanResult.removedProxies;
|
|
4597
|
-
updatedGroups = cleanResult.updatedGroups;
|
|
4598
|
-
removedGroups = cleanResult.removedGroups;
|
|
4952
|
+
console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
|
|
4953
|
+
const opened = openUrl(webPageUrl);
|
|
4954
|
+
if (!opened) {
|
|
4955
|
+
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
4599
4956
|
}
|
|
4957
|
+
return;
|
|
4600
4958
|
}
|
|
4601
|
-
if (
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
const overwriteEnabled = isOverwriteEnabled();
|
|
4612
|
-
const overwriteFiles = listOverwriteFile().files;
|
|
4613
|
-
const activeSub = getActiveSubscription();
|
|
4614
|
-
console.log("");
|
|
4615
|
-
let modeLabel = "";
|
|
4616
|
-
if (info && status.running) {
|
|
4617
|
-
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
4618
|
-
}
|
|
4619
|
-
const statusText = status.running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
|
|
4620
|
-
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
|
|
4621
|
-
console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
|
|
4622
|
-
if (status.pid) {
|
|
4623
|
-
console.log(`${colors.gray("PID: ")}${status.pid}`);
|
|
4624
|
-
if (status.processInfo) {
|
|
4625
|
-
console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
|
|
4959
|
+
if (action === "remove" || action === "rm" || action === "delete") {
|
|
4960
|
+
const name = args[2];
|
|
4961
|
+
const subs = getSubscriptions();
|
|
4962
|
+
if (!name) {
|
|
4963
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
|
|
4964
|
+
if (subs.length > 0) {
|
|
4965
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4966
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4967
|
+
}
|
|
4968
|
+
process.exit(1);
|
|
4626
4969
|
}
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
|
|
4634
|
-
if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
|
|
4635
|
-
console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
|
|
4970
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4971
|
+
const target = pickSingleSubscription(matches, name);
|
|
4972
|
+
const switchedTo = removeSubscription(target.name);
|
|
4973
|
+
console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
|
|
4974
|
+
if (switchedTo) {
|
|
4975
|
+
console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
|
|
4636
4976
|
}
|
|
4977
|
+
console.log("");
|
|
4978
|
+
await printSubscriptionList({ autoUpdate: false });
|
|
4979
|
+
return;
|
|
4637
4980
|
}
|
|
4638
|
-
if (
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4981
|
+
if (action === "clean") {
|
|
4982
|
+
const { target, timeout, concurrency } = resolveActiveTestTarget(args);
|
|
4983
|
+
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
4984
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
4985
|
+
console.log("");
|
|
4986
|
+
const result = await autoCleanSubscription(target.name, {
|
|
4987
|
+
timeout,
|
|
4988
|
+
concurrency,
|
|
4989
|
+
onResult: printTestResult
|
|
4990
|
+
});
|
|
4991
|
+
console.log("");
|
|
4992
|
+
console.log(formatTestSummary(result.summary));
|
|
4993
|
+
if (result.skipped) {
|
|
4994
|
+
console.log("");
|
|
4995
|
+
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"));
|
|
4996
|
+
} else if (result.removedProxies > 0) {
|
|
4997
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
4998
|
+
console.log("");
|
|
4999
|
+
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
4642
5000
|
}
|
|
4643
|
-
|
|
4644
|
-
} else {
|
|
4645
|
-
console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
|
|
5001
|
+
return;
|
|
4646
5002
|
}
|
|
4647
|
-
if (
|
|
4648
|
-
const
|
|
4649
|
-
console.log(
|
|
4650
|
-
}
|
|
4651
|
-
console.log(
|
|
4652
|
-
|
|
4653
|
-
|
|
5003
|
+
if (action === "test") {
|
|
5004
|
+
const { target, timeout, concurrency } = resolveActiveTestTarget(args);
|
|
5005
|
+
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
5006
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5007
|
+
console.log("");
|
|
5008
|
+
const summary = await testSubscriptionProxies(target.name, {
|
|
5009
|
+
timeout,
|
|
5010
|
+
concurrency,
|
|
5011
|
+
onResult: printTestResult
|
|
5012
|
+
});
|
|
5013
|
+
console.log("");
|
|
5014
|
+
console.log(formatTestSummary(summary));
|
|
5015
|
+
return;
|
|
4654
5016
|
}
|
|
4655
|
-
console.
|
|
5017
|
+
console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
|
|
5018
|
+
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
|
|
5019
|
+
process.exit(1);
|
|
4656
5020
|
}
|
|
4657
5021
|
|
|
4658
|
-
// src/commands/
|
|
4659
|
-
function
|
|
4660
|
-
const
|
|
4661
|
-
|
|
4662
|
-
const
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
5022
|
+
// src/commands/bench.ts
|
|
5023
|
+
function printRanking(results, sourceOrder) {
|
|
5024
|
+
const valid = results.filter((r) => r.downloadOk && r.alive > 0).sort((a, b) => {
|
|
5025
|
+
const rateA = a.alive / a.totalProxies;
|
|
5026
|
+
const rateB = b.alive / b.totalProxies;
|
|
5027
|
+
if (Math.abs(rateA - rateB) > 0.1) return rateB - rateA;
|
|
5028
|
+
return a.medianDelay - b.medianDelay;
|
|
5029
|
+
});
|
|
5030
|
+
if (valid.length === 0) {
|
|
5031
|
+
console.log(colors.yellow("\u6CA1\u6709\u53EF\u7528\u7684\u8BA2\u9605\u6E90"));
|
|
5032
|
+
return;
|
|
4666
5033
|
}
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
const
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
}
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
5034
|
+
console.log(colors.cyan("\u6392\u540D:"));
|
|
5035
|
+
console.log("");
|
|
5036
|
+
const namedResults = valid.map((r) => {
|
|
5037
|
+
const idx = sourceOrder.get(r.name) ?? 0;
|
|
5038
|
+
return { ...r, displayName: `${String(idx + 1).padStart(2, "0")}-${r.name}` };
|
|
5039
|
+
});
|
|
5040
|
+
const nameWidth = Math.max(12, ...namedResults.map((r) => r.displayName.length));
|
|
5041
|
+
const h = (s, w) => s + " ".repeat(Math.max(0, w - displayWidth(s)));
|
|
5042
|
+
console.log(
|
|
5043
|
+
` ${"#".padStart(3)} ${h("\u540D\u79F0", nameWidth)} ${h("\u5B58\u6D3B\u7387", 8)} ${h("\u5B58\u6D3B", 6)} ${h("\u603B\u6570", 6)} ${h("\u5206\u7EC4", 6)} ${h("\u4E2D\u4F4D", 7)} ${h("\u5E73\u5747", 7)}`
|
|
5044
|
+
);
|
|
5045
|
+
console.log(
|
|
5046
|
+
` ${"\u2500".repeat(3)} ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(8)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(7)} ${"\u2500".repeat(7)}`
|
|
5047
|
+
);
|
|
5048
|
+
for (let i = 0; i < namedResults.length; i++) {
|
|
5049
|
+
const r = namedResults[i];
|
|
5050
|
+
const rate = (r.alive / r.totalProxies * 100).toFixed(1);
|
|
5051
|
+
const rateColor = r.alive / r.totalProxies > 0.3 ? colors.green : colors.yellow;
|
|
5052
|
+
const groups = r.proxyGroups > 0 ? String(r.proxyGroups) : "-";
|
|
5053
|
+
console.log(
|
|
5054
|
+
` ${String(i + 1).padStart(3)} ${r.displayName.padEnd(nameWidth)} ${rateColor(h(`${rate}%`, 8))} ${String(r.alive).padEnd(6)} ${String(r.totalProxies).padEnd(6)} ${groups.padEnd(6)} ${h(`${r.medianDelay}ms`, 7)} ${h(`${r.avgDelay}ms`, 7)}`
|
|
5055
|
+
);
|
|
4682
5056
|
}
|
|
4683
|
-
|
|
4684
|
-
const
|
|
4685
|
-
const
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
4690
|
-
target = pickSingleSubscription(matches, nameArg);
|
|
4691
|
-
} else {
|
|
4692
|
-
if (!activeSub) {
|
|
4693
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
4694
|
-
process.exit(1);
|
|
4695
|
-
}
|
|
4696
|
-
target = activeSub;
|
|
5057
|
+
console.log("");
|
|
5058
|
+
const failed = results.filter((r) => !r.downloadOk);
|
|
5059
|
+
const noAlive = results.filter((r) => r.downloadOk && r.alive === 0);
|
|
5060
|
+
if (failed.length > 0) {
|
|
5061
|
+
const names = failed.map((r) => `${String((sourceOrder.get(r.name) ?? 0) + 1).padStart(2, "0")}-${r.name}`);
|
|
5062
|
+
console.log(colors.gray(`\u4E0B\u8F7D\u5931\u8D25: ${names.join(", ")}`));
|
|
4697
5063
|
}
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
console.
|
|
4701
|
-
process.exit(1);
|
|
5064
|
+
if (noAlive.length > 0) {
|
|
5065
|
+
const names = noAlive.map((r) => `${String((sourceOrder.get(r.name) ?? 0) + 1).padStart(2, "0")}-${r.name}`);
|
|
5066
|
+
console.log(colors.gray(`\u65E0\u5B58\u6D3B\u8282\u70B9: ${names.join(", ")}`));
|
|
4702
5067
|
}
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
5068
|
+
}
|
|
5069
|
+
async function cmdBench(args) {
|
|
5070
|
+
if (!hasKernel()) {
|
|
5071
|
+
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
4706
5072
|
process.exit(1);
|
|
4707
5073
|
}
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
5074
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 1500);
|
|
5075
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
5076
|
+
const nameFilter = getNonFlagArg(args, 1);
|
|
5077
|
+
const allSources = getFreeSubscriptionSources();
|
|
5078
|
+
const sourceOrder = new Map(allSources.map((s, i) => [s.name, i]));
|
|
5079
|
+
let sources = allSources;
|
|
5080
|
+
if (nameFilter) {
|
|
5081
|
+
const lower = nameFilter.toLowerCase();
|
|
5082
|
+
sources = sources.filter((s) => s.name.toLowerCase().includes(lower));
|
|
5083
|
+
if (sources.length === 0) {
|
|
5084
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${nameFilter}" \u7684\u8BA2\u9605\u6E90`);
|
|
5085
|
+
console.log("\n\u53EF\u7528\u6E90:");
|
|
5086
|
+
for (const s of allSources) console.log(` ${s.name}`);
|
|
5087
|
+
process.exit(1);
|
|
5088
|
+
}
|
|
4714
5089
|
}
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
5090
|
+
console.log(colors.cyan(`\u57FA\u51C6\u6D4B\u8BD5 ${sources.length} \u4E2A\u514D\u8D39\u8BA2\u9605\u6E90`));
|
|
5091
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5092
|
+
console.log("");
|
|
5093
|
+
try {
|
|
5094
|
+
console.log(colors.cyan("\u4E0B\u8F7D\u8BA2\u9605..."));
|
|
5095
|
+
const downloaded = await downloadAllSources(sources, (name, ok, count, groups, error) => {
|
|
5096
|
+
const idx = String((sourceOrder.get(name) ?? 0) + 1).padStart(2, "0");
|
|
5097
|
+
if (ok) {
|
|
5098
|
+
const groupsInfo = groups > 0 ? ` ${groups}\u7EC4` : "";
|
|
5099
|
+
console.log(` ${colors.green("\u2713")} ${idx}-${name}: ${count} \u4E2A\u8282\u70B9${groupsInfo}`);
|
|
5100
|
+
} else {
|
|
5101
|
+
console.log(` ${colors.red("\u2717")} ${idx}-${name}: ${colors.gray(error || "\u5931\u8D25")}`);
|
|
5102
|
+
}
|
|
5103
|
+
});
|
|
5104
|
+
const allProxies = downloaded.flatMap((d) => d.proxies);
|
|
5105
|
+
const successSources = downloaded.filter((d) => d.proxies.length > 0);
|
|
5106
|
+
if (allProxies.length === 0) {
|
|
5107
|
+
console.log("");
|
|
5108
|
+
console.log(colors.red("\u6240\u6709\u8BA2\u9605\u6E90\u4E0B\u8F7D\u5931\u8D25\u6216\u65E0\u8282\u70B9"));
|
|
5109
|
+
return;
|
|
5110
|
+
}
|
|
4718
5111
|
console.log("");
|
|
4719
|
-
console.log(
|
|
5112
|
+
console.log(`\u5171 ${allProxies.length} \u4E2A\u8282\u70B9\uFF0C\u6765\u81EA ${successSources.length} \u4E2A\u6E90`);
|
|
4720
5113
|
console.log("");
|
|
4721
|
-
|
|
5114
|
+
const filtered = buildMergedBenchConfig(allProxies);
|
|
5115
|
+
if (filtered > 0) {
|
|
5116
|
+
console.log(colors.gray(`\u8FC7\u6EE4 ${filtered} \u4E2A\u65E0\u6548\u8282\u70B9\uFF0C\u5269\u4F59 ${allProxies.length} \u4E2A`));
|
|
5117
|
+
}
|
|
5118
|
+
const survivingSet = new Set(allProxies);
|
|
5119
|
+
for (const d of downloaded) {
|
|
5120
|
+
d.proxies = d.proxies.filter((p) => survivingSet.has(p));
|
|
5121
|
+
}
|
|
5122
|
+
const benchPort = BENCH_CONFIG.port;
|
|
5123
|
+
const benchApi = BENCH_CONFIG["external-controller"];
|
|
5124
|
+
console.log(colors.cyan("\u542F\u52A8\u6D4B\u8BD5\u5B9E\u4F8B..."));
|
|
5125
|
+
await startBenchInstance();
|
|
5126
|
+
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (\u7AEF\u53E3 ${benchPort}/${benchApi.split(":")[1]})`);
|
|
5127
|
+
console.log("");
|
|
5128
|
+
console.log(colors.cyan("\u6D4B\u8BD5\u8282\u70B9\u5EF6\u8FDF..."));
|
|
5129
|
+
const allNames = allProxies.map((p) => p.name);
|
|
5130
|
+
const testResults = await testBenchProxies(allNames, {
|
|
5131
|
+
timeout,
|
|
5132
|
+
concurrency,
|
|
5133
|
+
onBatch: (batch, total, alive, tested, median) => {
|
|
5134
|
+
const pct = (tested / allNames.length * 100).toFixed(0);
|
|
5135
|
+
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`);
|
|
5136
|
+
}
|
|
5137
|
+
});
|
|
5138
|
+
process.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
5139
|
+
const totalAlive = testResults.filter((r) => r.delay !== null).length;
|
|
5140
|
+
const summary = { alive: totalAlive, dead: testResults.length - totalAlive, total: testResults.length };
|
|
5141
|
+
console.log(formatTestSummary(summary));
|
|
5142
|
+
console.log("");
|
|
5143
|
+
const resultsByName = new Map(testResults.map((r) => [r.name, r]));
|
|
5144
|
+
const results = downloaded.map((source) => computeSourceResult(source, resultsByName));
|
|
5145
|
+
printRanking(results, sourceOrder);
|
|
5146
|
+
} finally {
|
|
5147
|
+
stopBenchInstance();
|
|
5148
|
+
cleanupBenchDir();
|
|
4722
5149
|
}
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
const used = (s.upload || 0) + (s.download || 0);
|
|
4736
|
-
const usedStr = formatBytes(used);
|
|
4737
|
-
const totalStr = formatBytes(s.total);
|
|
4738
|
-
let percentStr = "";
|
|
4739
|
-
if (s.total && s.total > 0) {
|
|
4740
|
-
const percent = Math.min(used / s.total * 100, 100);
|
|
4741
|
-
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
// src/commands/directory.ts
|
|
5153
|
+
function cmdDirectory(args) {
|
|
5154
|
+
const action = args?.[1];
|
|
5155
|
+
if (action === "open") {
|
|
5156
|
+
const target = args[2];
|
|
5157
|
+
if (!target || target === "root") {
|
|
5158
|
+
console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
|
|
5159
|
+
const success = openUrl(USER_DATA_DIR);
|
|
5160
|
+
if (!success) {
|
|
5161
|
+
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
|
|
4742
5162
|
}
|
|
4743
|
-
|
|
5163
|
+
return;
|
|
4744
5164
|
}
|
|
4745
|
-
|
|
4746
|
-
|
|
5165
|
+
const targetInfo = DIRECTORY_TARGETS[target.toLowerCase()];
|
|
5166
|
+
if (targetInfo) {
|
|
5167
|
+
const targetPath = targetInfo.path || USER_DATA_DIR;
|
|
5168
|
+
console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
|
|
5169
|
+
const success = openUrl(targetPath);
|
|
5170
|
+
if (!success) {
|
|
5171
|
+
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
|
|
5172
|
+
}
|
|
5173
|
+
return;
|
|
4747
5174
|
}
|
|
4748
|
-
|
|
4749
|
-
|
|
5175
|
+
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`);
|
|
5176
|
+
console.log("");
|
|
5177
|
+
console.log("\u53EF\u7528\u76EE\u6807:");
|
|
5178
|
+
console.log(" root (\u9ED8\u8BA4) \u6839\u76EE\u5F55");
|
|
5179
|
+
for (const [key, val] of Object.entries(DIRECTORY_TARGETS)) {
|
|
5180
|
+
if (key !== "root") {
|
|
5181
|
+
console.log(` ${key.padEnd(14)}${val.label}`);
|
|
5182
|
+
}
|
|
4750
5183
|
}
|
|
5184
|
+
console.log("");
|
|
5185
|
+
process.exit(1);
|
|
5186
|
+
}
|
|
5187
|
+
console.log("");
|
|
5188
|
+
console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
|
|
5189
|
+
console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
|
|
5190
|
+
console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
|
|
5191
|
+
console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
|
|
5192
|
+
console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
|
|
5193
|
+
console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
|
|
5194
|
+
console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
|
|
5195
|
+
console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
|
|
5196
|
+
console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
|
|
5197
|
+
console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5198
|
+
console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5199
|
+
console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
|
|
5200
|
+
console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
|
|
5201
|
+
console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
|
|
5202
|
+
console.log("");
|
|
5203
|
+
console.log("\u6253\u5F00\u76EE\u5F55:");
|
|
5204
|
+
console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
|
|
5205
|
+
console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
|
|
5206
|
+
console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
|
|
5207
|
+
console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
|
|
5208
|
+
console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
|
|
5209
|
+
console.log("");
|
|
5210
|
+
console.log("\u73AF\u5883\u53D8\u91CF:");
|
|
5211
|
+
console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
|
|
5212
|
+
console.log("");
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
// src/commands/help.ts
|
|
5216
|
+
function printShortHelp() {
|
|
5217
|
+
console.log(`
|
|
5218
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
5219
|
+
`);
|
|
5220
|
+
console.log(
|
|
5221
|
+
`\u5E38\u7528\u547D\u4EE4:
|
|
5222
|
+
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
5223
|
+
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
5224
|
+
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
5225
|
+
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
5226
|
+
`
|
|
5227
|
+
);
|
|
5228
|
+
}
|
|
5229
|
+
function printHelp() {
|
|
5230
|
+
console.log(
|
|
5231
|
+
`
|
|
5232
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}
|
|
5233
|
+
|
|
5234
|
+
\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh
|
|
5235
|
+
|
|
5236
|
+
\u7528\u6CD5:
|
|
5237
|
+
mihomo <\u547D\u4EE4> [\u9009\u9879]
|
|
5238
|
+
|
|
5239
|
+
${colors.cyan("\u63A7\u5236:")}
|
|
5240
|
+
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)
|
|
5241
|
+
${colors.bold("stop")} \u505C\u6B62\u4EE3\u7406
|
|
5242
|
+
${colors.bold("status")} \u67E5\u770B\u72B6\u6001
|
|
5243
|
+
|
|
5244
|
+
${colors.cyan("\u754C\u9762:")}
|
|
5245
|
+
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)
|
|
5246
|
+
${colors.bold("log")} [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09
|
|
5247
|
+
${colors.bold("logs")} [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09
|
|
5248
|
+
|
|
5249
|
+
${colors.cyan("\u8BA2\u9605:")}
|
|
5250
|
+
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
5251
|
+
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
5252
|
+
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
5253
|
+
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
5254
|
+
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
5255
|
+
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
5256
|
+
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
5257
|
+
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
5258
|
+
${colors.bold("bench")} [name] [-t ms] [-j N] \u6D4B\u8BD5\u514D\u8D39\u8BA2\u9605\u6E90\u8D28\u91CF\u6392\u540D
|
|
5259
|
+
|
|
5260
|
+
${colors.cyan("\u914D\u7F6E:")}
|
|
5261
|
+
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
5262
|
+
${colors.bold("overwrite")} on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E
|
|
5263
|
+
${colors.bold("directory")} \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09
|
|
5264
|
+
${colors.bold("directory")} open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|...
|
|
5265
|
+
|
|
5266
|
+
${colors.cyan("\u7CFB\u7EDF:")}
|
|
5267
|
+
${colors.bold("kernel")} [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09
|
|
5268
|
+
${colors.bold("update")} \u66F4\u65B0 mihomo-cli (npm install -g)
|
|
5269
|
+
${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
|
|
5270
|
+
${colors.bold("help")}, -h \u663E\u793A\u5E2E\u52A9
|
|
5271
|
+
${colors.bold("version")}, -v \u663E\u793A\u7248\u672C
|
|
5272
|
+
|
|
5273
|
+
${colors.cyan("\u793A\u4F8B:")}
|
|
5274
|
+
mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F
|
|
5275
|
+
mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F
|
|
5276
|
+
mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)
|
|
5277
|
+
mihomo bench # \u6D4B\u8BD5\u6240\u6709\u514D\u8D39\u8BA2\u9605\u6E90
|
|
5278
|
+
mihomo ui # \u6253\u5F00 Web UI
|
|
5279
|
+
|
|
5280
|
+
${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}
|
|
5281
|
+
mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)
|
|
5282
|
+
tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo
|
|
5283
|
+
|
|
5284
|
+
${colors.cyan("\u6570\u636E\u76EE\u5F55:")}
|
|
5285
|
+
\u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E
|
|
5286
|
+
\u9ED8\u8BA4: ${USER_DATA_DIR}
|
|
5287
|
+
`
|
|
5288
|
+
);
|
|
5289
|
+
}
|
|
5290
|
+
function printVersion() {
|
|
5291
|
+
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
5292
|
+
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
5293
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
5294
|
+
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
5295
|
+
}
|
|
5296
|
+
|
|
5297
|
+
// src/kernel.ts
|
|
5298
|
+
import { execSync as execSync4, spawnSync } from "child_process";
|
|
5299
|
+
import fs7 from "fs";
|
|
5300
|
+
import path5 from "path";
|
|
5301
|
+
|
|
5302
|
+
// node_modules/compare-versions/lib/esm/utils.js
|
|
5303
|
+
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
5304
|
+
var validateAndParse = (version) => {
|
|
5305
|
+
if (typeof version !== "string") {
|
|
5306
|
+
throw new TypeError("Invalid argument expected string");
|
|
5307
|
+
}
|
|
5308
|
+
const match = version.match(semver);
|
|
5309
|
+
if (!match) {
|
|
5310
|
+
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
|
5311
|
+
}
|
|
5312
|
+
match.shift();
|
|
5313
|
+
return match;
|
|
5314
|
+
};
|
|
5315
|
+
var isWildcard = (s) => s === "*" || s === "x" || s === "X";
|
|
5316
|
+
var tryParse = (v) => {
|
|
5317
|
+
const n = parseInt(v, 10);
|
|
5318
|
+
return isNaN(n) ? v : n;
|
|
5319
|
+
};
|
|
5320
|
+
var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
|
|
5321
|
+
var compareStrings = (a, b) => {
|
|
5322
|
+
if (isWildcard(a) || isWildcard(b))
|
|
5323
|
+
return 0;
|
|
5324
|
+
const [ap, bp] = forceType(tryParse(a), tryParse(b));
|
|
5325
|
+
if (ap > bp)
|
|
5326
|
+
return 1;
|
|
5327
|
+
if (ap < bp)
|
|
5328
|
+
return -1;
|
|
5329
|
+
return 0;
|
|
5330
|
+
};
|
|
5331
|
+
var compareSegments = (a, b) => {
|
|
5332
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
5333
|
+
const r = compareStrings(a[i] || "0", b[i] || "0");
|
|
5334
|
+
if (r !== 0)
|
|
5335
|
+
return r;
|
|
5336
|
+
}
|
|
5337
|
+
return 0;
|
|
5338
|
+
};
|
|
5339
|
+
|
|
5340
|
+
// node_modules/compare-versions/lib/esm/compareVersions.js
|
|
5341
|
+
var compareVersions = (v1, v2) => {
|
|
5342
|
+
const n1 = validateAndParse(v1);
|
|
5343
|
+
const n2 = validateAndParse(v2);
|
|
5344
|
+
const p1 = n1.pop();
|
|
5345
|
+
const p2 = n2.pop();
|
|
5346
|
+
const r = compareSegments(n1, n2);
|
|
5347
|
+
if (r !== 0)
|
|
5348
|
+
return r;
|
|
5349
|
+
if (p1 && p2) {
|
|
5350
|
+
return compareSegments(p1.split("."), p2.split("."));
|
|
5351
|
+
} else if (p1 || p2) {
|
|
5352
|
+
return p1 ? -1 : 1;
|
|
5353
|
+
}
|
|
5354
|
+
return 0;
|
|
5355
|
+
};
|
|
5356
|
+
|
|
5357
|
+
// src/kernel.ts
|
|
5358
|
+
var GITHUB_REPO = "MetaCubeX/mihomo";
|
|
5359
|
+
var KERNEL_HTTP_TIMEOUT = 12e4;
|
|
5360
|
+
var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
|
|
5361
|
+
var HTTP_CLIENT2 = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
|
|
5362
|
+
function withMirror(url, mirror) {
|
|
5363
|
+
if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
|
|
5364
|
+
return mirror + url;
|
|
5365
|
+
}
|
|
5366
|
+
return url;
|
|
5367
|
+
}
|
|
5368
|
+
function getArch() {
|
|
5369
|
+
const arch = process.arch;
|
|
5370
|
+
if (arch === "arm64") return "arm64";
|
|
5371
|
+
if (arch === "x64") return "amd64";
|
|
5372
|
+
return arch;
|
|
5373
|
+
}
|
|
5374
|
+
function findMatchingAsset(assets, platform, arch) {
|
|
5375
|
+
const prefix = `mihomo-${platform}-${arch}`;
|
|
5376
|
+
const matchingAssets = assets.filter(
|
|
5377
|
+
(a) => a.name.startsWith(prefix) && a.name.endsWith(".gz") || a.name.startsWith(`${prefix}-`) && a.name.endsWith(".gz")
|
|
5378
|
+
);
|
|
5379
|
+
if (matchingAssets.length === 0) return null;
|
|
5380
|
+
if (matchingAssets.length === 1) return matchingAssets[0];
|
|
5381
|
+
const standardAsset = matchingAssets.find((a) => {
|
|
5382
|
+
const nameWithoutGz = a.name.slice(0, -3);
|
|
5383
|
+
const parts = nameWithoutGz.split("-");
|
|
5384
|
+
const lastPart = parts[parts.length - 1];
|
|
5385
|
+
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go");
|
|
4751
5386
|
});
|
|
4752
|
-
|
|
4753
|
-
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
4754
|
-
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4755
|
-
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
4756
|
-
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
4757
|
-
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
4758
|
-
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
4759
|
-
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
4760
|
-
console.log("");
|
|
5387
|
+
return standardAsset || matchingAssets[0];
|
|
4761
5388
|
}
|
|
4762
|
-
async function
|
|
4763
|
-
const
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
5389
|
+
async function getLatestRelease(repo, mirror) {
|
|
5390
|
+
const url = withMirror(`https://api.github.com/repos/${repo}/releases`, mirror);
|
|
5391
|
+
const response = await HTTP_CLIENT2.get(url, { responseType: "json" });
|
|
5392
|
+
const releases = response.data;
|
|
5393
|
+
if (!Array.isArray(releases) || releases.length === 0) {
|
|
5394
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F");
|
|
4767
5395
|
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
5396
|
+
const stableReleases = releases.filter(
|
|
5397
|
+
(r) => !r.prerelease && !r.tag_name.toLowerCase().includes("alpha") && !r.tag_name.toLowerCase().includes("beta") && !r.tag_name.toLowerCase().includes("prerelease")
|
|
5398
|
+
);
|
|
5399
|
+
return stableReleases.length > 0 ? stableReleases[0] : releases[0];
|
|
5400
|
+
}
|
|
5401
|
+
async function checkUpdate(mirror) {
|
|
5402
|
+
const currentVersion = getKernelVersion();
|
|
5403
|
+
const latest = await getLatestRelease(GITHUB_REPO, mirror);
|
|
5404
|
+
const latestVersion = latest.tag_name;
|
|
5405
|
+
let needsUpdate = false;
|
|
5406
|
+
const currentDisplay = currentVersion || "\u672A\u5B89\u88C5";
|
|
5407
|
+
if (!currentVersion) {
|
|
5408
|
+
needsUpdate = true;
|
|
5409
|
+
} else {
|
|
4776
5410
|
try {
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4781
|
-
} catch (e) {
|
|
4782
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4783
|
-
process.exit(1);
|
|
5411
|
+
needsUpdate = compareVersions(latestVersion.replace(/^v/, ""), currentVersion.replace(/^v/, "")) > 0;
|
|
5412
|
+
} catch {
|
|
5413
|
+
needsUpdate = latestVersion !== currentVersion;
|
|
4784
5414
|
}
|
|
4785
|
-
console.log("");
|
|
4786
|
-
await printSubscriptionList();
|
|
4787
|
-
return;
|
|
4788
5415
|
}
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
}
|
|
4807
|
-
}
|
|
4808
|
-
if (ok === 0) process.exit(1);
|
|
4809
|
-
console.log("");
|
|
4810
|
-
await printSubscriptionList();
|
|
4811
|
-
return;
|
|
5416
|
+
return {
|
|
5417
|
+
current: currentDisplay,
|
|
5418
|
+
latest: latestVersion,
|
|
5419
|
+
needsUpdate,
|
|
5420
|
+
assets: latest.assets,
|
|
5421
|
+
release: latest
|
|
5422
|
+
};
|
|
5423
|
+
}
|
|
5424
|
+
function findBinaryInDir(dir) {
|
|
5425
|
+
const files = fs7.readdirSync(dir);
|
|
5426
|
+
for (const f of files) {
|
|
5427
|
+
const fullPath = path5.join(dir, f);
|
|
5428
|
+
const stat = fs7.statSync(fullPath);
|
|
5429
|
+
if (stat.isDirectory()) {
|
|
5430
|
+
const found = findBinaryInDir(fullPath);
|
|
5431
|
+
if (found) return found;
|
|
5432
|
+
continue;
|
|
4812
5433
|
}
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
5434
|
+
if (f === "mihomo") return fullPath;
|
|
5435
|
+
if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
|
|
5436
|
+
}
|
|
5437
|
+
return null;
|
|
5438
|
+
}
|
|
5439
|
+
async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
5440
|
+
ensureDirs();
|
|
5441
|
+
const latest = releaseInfo || await getLatestRelease(GITHUB_REPO, mirror);
|
|
5442
|
+
const arch = getArch();
|
|
5443
|
+
const platform = process.platform;
|
|
5444
|
+
const asset = findMatchingAsset(latest.assets, platform, arch);
|
|
5445
|
+
if (!asset) {
|
|
5446
|
+
const available = latest.assets.map((a) => a.name).join(", ");
|
|
5447
|
+
let hint = "";
|
|
5448
|
+
if (available) hint = `
|
|
5449
|
+
\u53EF\u7528\u7248\u672C: ${available}`;
|
|
5450
|
+
throw new Error(`\u672A\u627E\u5230\u5339\u914D\u7684\u5185\u6838\u6587\u4EF6
|
|
5451
|
+
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
5452
|
+
}
|
|
5453
|
+
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
5454
|
+
const tempPath = path5.join(DIRS.kernel, asset.name);
|
|
5455
|
+
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
5456
|
+
if (progressCallback) {
|
|
5457
|
+
progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
|
|
5458
|
+
}
|
|
5459
|
+
const curlResult = spawnSync(
|
|
5460
|
+
"curl",
|
|
5461
|
+
["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
|
|
5462
|
+
{ stdio: "inherit" }
|
|
5463
|
+
);
|
|
5464
|
+
if (curlResult.status !== 0) {
|
|
4816
5465
|
try {
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
} catch (e) {
|
|
4820
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4821
|
-
process.exit(1);
|
|
5466
|
+
fs7.unlinkSync(tempPath);
|
|
5467
|
+
} catch {
|
|
4822
5468
|
}
|
|
4823
|
-
|
|
4824
|
-
await printSubscriptionList();
|
|
4825
|
-
return;
|
|
5469
|
+
throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
|
|
4826
5470
|
}
|
|
4827
|
-
if (
|
|
4828
|
-
|
|
4829
|
-
const subs = getSubscriptions();
|
|
4830
|
-
if (!name) {
|
|
4831
|
-
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
4832
|
-
if (subs.length > 0) {
|
|
4833
|
-
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4834
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
4835
|
-
}
|
|
4836
|
-
process.exit(1);
|
|
4837
|
-
}
|
|
4838
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
4839
|
-
const target = pickSingleSubscription(matches, name);
|
|
4840
|
-
const currentDefault = getActiveSubscription();
|
|
4841
|
-
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
4842
|
-
if (isAlreadyDefault) {
|
|
4843
|
-
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
4844
|
-
console.log("");
|
|
4845
|
-
await printSubscriptionList();
|
|
4846
|
-
return;
|
|
4847
|
-
}
|
|
4848
|
-
const status = getStatus();
|
|
4849
|
-
const configInfo = getConfigInfo();
|
|
4850
|
-
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
4851
|
-
const success = setDefaultSubscription(target.name);
|
|
4852
|
-
if (success) {
|
|
4853
|
-
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
4854
|
-
} else {
|
|
4855
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
4856
|
-
process.exit(1);
|
|
4857
|
-
}
|
|
4858
|
-
if (status.running) {
|
|
4859
|
-
console.log("");
|
|
4860
|
-
await cmdStart(["start", currentMode]);
|
|
4861
|
-
return;
|
|
4862
|
-
}
|
|
4863
|
-
console.log("");
|
|
4864
|
-
await printSubscriptionList();
|
|
4865
|
-
return;
|
|
5471
|
+
if (!fs7.existsSync(tempPath)) {
|
|
5472
|
+
throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
|
|
4866
5473
|
}
|
|
4867
|
-
if (
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
if (
|
|
4876
|
-
const
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
}
|
|
4881
|
-
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
4882
|
-
let webPageUrl = cached?.web_page_url;
|
|
4883
|
-
if (!webPageUrl) {
|
|
4884
|
-
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
4885
|
-
try {
|
|
4886
|
-
await downloadSubscription(target.url, target.name);
|
|
4887
|
-
const cache = readSubscriptionCache();
|
|
4888
|
-
if (cache[target.name]?.web_page_url) {
|
|
4889
|
-
webPageUrl = cache[target.name].web_page_url;
|
|
4890
|
-
} else {
|
|
4891
|
-
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
4892
|
-
process.exit(1);
|
|
4893
|
-
}
|
|
4894
|
-
} catch (e) {
|
|
4895
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4896
|
-
process.exit(1);
|
|
4897
|
-
}
|
|
5474
|
+
if (progressCallback) {
|
|
5475
|
+
progressCallback("\u89E3\u538B\u5185\u6838...");
|
|
5476
|
+
}
|
|
5477
|
+
const extractPath = DIRS.kernel;
|
|
5478
|
+
let extractedBinary = null;
|
|
5479
|
+
try {
|
|
5480
|
+
if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
|
|
5481
|
+
execSync4(`tar -xzf "${tempPath}" -C "${extractPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
5482
|
+
} else if (tempPath.endsWith(".gz")) {
|
|
5483
|
+
const baseName = path5.basename(tempPath, ".gz");
|
|
5484
|
+
const outputPath = path5.join(extractPath, baseName);
|
|
5485
|
+
execSync4(`gzip -dc "${tempPath}" > "${outputPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
5486
|
+
extractedBinary = outputPath;
|
|
4898
5487
|
}
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
5488
|
+
} catch (e) {
|
|
5489
|
+
try {
|
|
5490
|
+
fs7.unlinkSync(tempPath);
|
|
5491
|
+
} catch {
|
|
4903
5492
|
}
|
|
4904
|
-
|
|
5493
|
+
throw new Error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
|
|
4905
5494
|
}
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
if (subs.length > 0) {
|
|
4912
|
-
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4913
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
4914
|
-
}
|
|
4915
|
-
process.exit(1);
|
|
4916
|
-
}
|
|
4917
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
4918
|
-
const target = pickSingleSubscription(matches, name);
|
|
4919
|
-
const switchedTo = removeSubscription(target.name);
|
|
4920
|
-
console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
|
|
4921
|
-
if (switchedTo) {
|
|
4922
|
-
console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
|
|
5495
|
+
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
5496
|
+
if (!foundBinary) {
|
|
5497
|
+
try {
|
|
5498
|
+
fs7.unlinkSync(tempPath);
|
|
5499
|
+
} catch {
|
|
4923
5500
|
}
|
|
4924
|
-
|
|
4925
|
-
await printSubscriptionList({ autoUpdate: false });
|
|
4926
|
-
return;
|
|
5501
|
+
throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
|
|
4927
5502
|
}
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
onResult: printTestResult
|
|
4937
|
-
});
|
|
4938
|
-
console.log("");
|
|
4939
|
-
console.log(formatTestSummary(result.summary));
|
|
4940
|
-
if (result.skipped) {
|
|
4941
|
-
console.log("");
|
|
4942
|
-
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"));
|
|
4943
|
-
} else if (result.removedProxies > 0) {
|
|
4944
|
-
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
4945
|
-
console.log("");
|
|
4946
|
-
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
5503
|
+
const targetPath = PATHS.mihomoBinary;
|
|
5504
|
+
if (foundBinary !== targetPath) {
|
|
5505
|
+
if (fs7.existsSync(targetPath)) {
|
|
5506
|
+
fs7.chmodSync(targetPath, 493);
|
|
5507
|
+
try {
|
|
5508
|
+
fs7.unlinkSync(targetPath);
|
|
5509
|
+
} catch {
|
|
5510
|
+
}
|
|
4947
5511
|
}
|
|
4948
|
-
|
|
5512
|
+
fs7.renameSync(foundBinary, targetPath);
|
|
4949
5513
|
}
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
console.log("");
|
|
4955
|
-
const summary = await testSubscriptionProxies(target.name, {
|
|
4956
|
-
timeout,
|
|
4957
|
-
concurrency,
|
|
4958
|
-
onResult: printTestResult
|
|
4959
|
-
});
|
|
4960
|
-
console.log("");
|
|
4961
|
-
console.log(formatTestSummary(summary));
|
|
4962
|
-
return;
|
|
5514
|
+
fs7.chmodSync(targetPath, 493);
|
|
5515
|
+
try {
|
|
5516
|
+
fs7.unlinkSync(tempPath);
|
|
5517
|
+
} catch {
|
|
4963
5518
|
}
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
process.exit(1);
|
|
5519
|
+
clearKernelVersionCache();
|
|
5520
|
+
return { version: latest.tag_name, path: targetPath };
|
|
4967
5521
|
}
|
|
4968
5522
|
|
|
4969
|
-
// src/commands/
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
}
|
|
4977
|
-
}
|
|
4978
|
-
async function cmdStart(args) {
|
|
4979
|
-
if (!hasKernel()) {
|
|
4980
|
-
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
4981
|
-
process.exit(1);
|
|
4982
|
-
}
|
|
4983
|
-
const targetMode = args[1] === "tun" ? "tun" : "mixed";
|
|
4984
|
-
const sub = getActiveSubscription();
|
|
4985
|
-
if (!sub) {
|
|
4986
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4987
|
-
process.exit(1);
|
|
4988
|
-
}
|
|
4989
|
-
await autoUpdateStaleSubscription();
|
|
4990
|
-
const status = getStatus();
|
|
4991
|
-
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
4992
|
-
if (hasProcess) {
|
|
4993
|
-
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
4994
|
-
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
5523
|
+
// src/commands/kernel.ts
|
|
5524
|
+
async function cmdKernel(args) {
|
|
5525
|
+
const mirrorInfo = parseMirrorArg(args);
|
|
5526
|
+
const effectiveMirror = mirrorInfo.mirror;
|
|
5527
|
+
if (effectiveMirror) {
|
|
5528
|
+
const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
|
|
5529
|
+
console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
|
|
4995
5530
|
}
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5531
|
+
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");
|
|
5532
|
+
console.log("\n\u7528\u6CD5:");
|
|
5533
|
+
console.log(" mihomo kernel # \u76F4\u8FDE");
|
|
5534
|
+
console.log(" mihomo kernel --mirror # \u4E0B\u8F7D\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF (v6.gh-proxy.org)");
|
|
5535
|
+
console.log(" mihomo kernel --mirror hk.gh-proxy.org # \u4E0B\u8F7D\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5536
|
+
console.log(" mihomo kernel --mirror-all # API\u8BF7\u6C42\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF");
|
|
5537
|
+
console.log(" mihomo kernel --mirror-all hk.gh-proxy.org # API\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5538
|
+
console.log("\n\u53EF\u7528\u955C\u50CF:");
|
|
5539
|
+
for (const m of AVAILABLE_MIRRORS) {
|
|
5540
|
+
const isCurrent = effectiveMirror && (effectiveMirror.includes(`//${m}/`) || effectiveMirror.includes(`//${m}:`) || effectiveMirror.endsWith(`//${m}`));
|
|
5541
|
+
console.log(` ${m}${isCurrent ? " (\u5F53\u524D)" : ""}`);
|
|
5000
5542
|
}
|
|
5001
|
-
|
|
5543
|
+
console.log("");
|
|
5544
|
+
console.log("\u68C0\u67E5\u5185\u6838\u66F4\u65B0...");
|
|
5002
5545
|
try {
|
|
5003
|
-
|
|
5546
|
+
const apiMirror = mirrorInfo.type === "all" ? effectiveMirror : null;
|
|
5547
|
+
const info = await checkUpdate(apiMirror);
|
|
5548
|
+
console.log(`\u5F53\u524D: ${info.current}`);
|
|
5549
|
+
console.log(`\u6700\u65B0: ${info.latest}`);
|
|
5550
|
+
if (!info.needsUpdate) {
|
|
5551
|
+
console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
5552
|
+
} else {
|
|
5553
|
+
console.log("\n\u6B63\u5728\u4E0B\u8F7D...");
|
|
5554
|
+
const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
|
|
5555
|
+
console.log(`
|
|
5556
|
+
\u5DF2\u66F4\u65B0\u5230 ${result.version}`);
|
|
5557
|
+
}
|
|
5004
5558
|
} catch (e) {
|
|
5005
|
-
console.error(
|
|
5559
|
+
console.error(`
|
|
5560
|
+
\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5561
|
+
const err = e;
|
|
5562
|
+
if (err.response?.data) {
|
|
5563
|
+
if (err.response.data.message) {
|
|
5564
|
+
console.error(`\u539F\u56E0: ${err.response.data.message}`);
|
|
5565
|
+
}
|
|
5566
|
+
if (err.response.data.documentation_url) {
|
|
5567
|
+
console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5006
5570
|
process.exit(1);
|
|
5007
5571
|
}
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5572
|
+
}
|
|
5573
|
+
|
|
5574
|
+
// src/commands/log.ts
|
|
5575
|
+
function cmdLog(args) {
|
|
5576
|
+
const logPath = getLogPath();
|
|
5577
|
+
if (hasFlag(args, "-o", "--open")) {
|
|
5578
|
+
openLogFile(logPath);
|
|
5579
|
+
return;
|
|
5016
5580
|
}
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
if (
|
|
5026
|
-
|
|
5027
|
-
} else
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5581
|
+
viewLogWithTail(logPath, { follow: true, lines: 50 });
|
|
5582
|
+
}
|
|
5583
|
+
function cmdLogs(args) {
|
|
5584
|
+
const targetName = getNonFlagArg(args, 1);
|
|
5585
|
+
const lines = parseIntArg(args, "-n", "--lines", 100);
|
|
5586
|
+
const openInViewer = hasFlag(args, "-o", "--open");
|
|
5587
|
+
if (targetName) {
|
|
5588
|
+
let logPath;
|
|
5589
|
+
if (targetName === "current" || targetName === "0") {
|
|
5590
|
+
logPath = getLogPath();
|
|
5591
|
+
} else {
|
|
5592
|
+
const parsedIdx = parseInt(targetName, 10);
|
|
5593
|
+
if (!Number.isNaN(parsedIdx) && parsedIdx > 0 && String(parsedIdx) === targetName) {
|
|
5594
|
+
const archiveLogs = listLogs();
|
|
5595
|
+
const archive = archiveLogs.archives[parsedIdx - 1];
|
|
5596
|
+
if (!archive) {
|
|
5597
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
5598
|
+
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
5599
|
+
process.exit(1);
|
|
5600
|
+
}
|
|
5601
|
+
logPath = archive.path;
|
|
5602
|
+
} else {
|
|
5603
|
+
logPath = getLogPathByName(targetName);
|
|
5039
5604
|
}
|
|
5040
5605
|
}
|
|
5606
|
+
if (!logPath) {
|
|
5607
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
5608
|
+
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
5609
|
+
process.exit(1);
|
|
5610
|
+
}
|
|
5611
|
+
if (openInViewer) {
|
|
5612
|
+
openLogFile(logPath);
|
|
5613
|
+
return;
|
|
5614
|
+
}
|
|
5615
|
+
viewLogWithTail(logPath, { follow: false, lines });
|
|
5616
|
+
return;
|
|
5041
5617
|
}
|
|
5042
|
-
|
|
5618
|
+
const logs = listLogs();
|
|
5619
|
+
const all = [];
|
|
5620
|
+
if (logs.current) all.push(logs.current);
|
|
5621
|
+
all.push(...logs.archives);
|
|
5622
|
+
if (all.length === 0) {
|
|
5623
|
+
console.log("\u6682\u65E0\u65E5\u5FD7");
|
|
5624
|
+
return;
|
|
5625
|
+
}
|
|
5626
|
+
console.log("");
|
|
5627
|
+
console.log("\u65E5\u5FD7\u5217\u8868:");
|
|
5628
|
+
console.log("");
|
|
5629
|
+
let archiveCounter = 0;
|
|
5630
|
+
for (const log of all) {
|
|
5631
|
+
let num;
|
|
5632
|
+
if (log.isCurrent) {
|
|
5633
|
+
num = " 0";
|
|
5634
|
+
} else {
|
|
5635
|
+
archiveCounter++;
|
|
5636
|
+
num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
|
|
5637
|
+
}
|
|
5638
|
+
const time = formatDate(log.mtime);
|
|
5639
|
+
const size = formatBytes(log.size);
|
|
5640
|
+
const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
|
|
5641
|
+
console.log(` ${num}. ${name}`);
|
|
5642
|
+
console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
|
|
5643
|
+
if (!log.isCurrent) {
|
|
5644
|
+
console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
|
|
5645
|
+
}
|
|
5646
|
+
console.log("");
|
|
5647
|
+
}
|
|
5648
|
+
console.log("\u7528\u6CD5:");
|
|
5649
|
+
console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
|
|
5650
|
+
console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
|
|
5651
|
+
console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
|
|
5652
|
+
console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
|
|
5653
|
+
console.log("");
|
|
5043
5654
|
}
|
|
5044
5655
|
|
|
5045
5656
|
// src/commands/overwrite.ts
|
|
5657
|
+
import path6 from "path";
|
|
5046
5658
|
function printOverwriteList() {
|
|
5047
5659
|
const info = listOverwriteFile();
|
|
5048
5660
|
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
@@ -5052,8 +5664,8 @@ function printOverwriteList() {
|
|
|
5052
5664
|
if (info.files.length === 0) {
|
|
5053
5665
|
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
5054
5666
|
console.log("");
|
|
5055
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${
|
|
5056
|
-
console.log(` \u6216 ${
|
|
5667
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path6.join(info.dir, "overwrite.yaml")}`);
|
|
5668
|
+
console.log(` \u6216 ${path6.join(info.dir, "overwrite.dns.yaml")}`);
|
|
5057
5669
|
console.log("");
|
|
5058
5670
|
} else {
|
|
5059
5671
|
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
@@ -5117,7 +5729,7 @@ async function cmdOverwrite(args) {
|
|
|
5117
5729
|
}
|
|
5118
5730
|
|
|
5119
5731
|
// src/commands/reset.ts
|
|
5120
|
-
import
|
|
5732
|
+
import fs8 from "fs";
|
|
5121
5733
|
import readline from "readline";
|
|
5122
5734
|
var RESET_TARGETS = [
|
|
5123
5735
|
{
|
|
@@ -5172,8 +5784,8 @@ var RESET_TARGETS = [
|
|
|
5172
5784
|
label: "\u8986\u5199",
|
|
5173
5785
|
paths: () => {
|
|
5174
5786
|
const dir = USER_DATA_DIR;
|
|
5175
|
-
if (!
|
|
5176
|
-
return
|
|
5787
|
+
if (!fs8.existsSync(dir)) return [];
|
|
5788
|
+
return fs8.readdirSync(dir).filter((f) => f === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(f)).map((f) => `${dir}/${f}`);
|
|
5177
5789
|
},
|
|
5178
5790
|
needsStop: false
|
|
5179
5791
|
}
|
|
@@ -5240,7 +5852,7 @@ async function cmdReset(args) {
|
|
|
5240
5852
|
const pids = needsStop || warnRunning ? getAllMihomoPids() : [];
|
|
5241
5853
|
if (needsStop && pids.length > 0) {
|
|
5242
5854
|
console.log(`\u505C\u6B62 ${pids.length} \u4E2A\u8FDB\u7A0B...`);
|
|
5243
|
-
cleanupAll(
|
|
5855
|
+
cleanupAll();
|
|
5244
5856
|
for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
|
|
5245
5857
|
if (getAllMihomoPids().length === 0) break;
|
|
5246
5858
|
await new Promise((r) => setTimeout(r, PROCESS_WAIT_INTERVAL));
|
|
@@ -5307,7 +5919,7 @@ function cmdUI(args) {
|
|
|
5307
5919
|
}
|
|
5308
5920
|
|
|
5309
5921
|
// src/commands/update.ts
|
|
5310
|
-
import { exec, spawn as
|
|
5922
|
+
import { exec, spawn as spawn3 } from "child_process";
|
|
5311
5923
|
import { promisify } from "util";
|
|
5312
5924
|
var execAsync = promisify(exec);
|
|
5313
5925
|
async function cmdUpdate() {
|
|
@@ -5316,7 +5928,7 @@ async function cmdUpdate() {
|
|
|
5316
5928
|
console.log("\u6B63\u5728\u66F4\u65B0 mihomo-cli...");
|
|
5317
5929
|
console.log("");
|
|
5318
5930
|
await new Promise((resolve) => {
|
|
5319
|
-
const npm =
|
|
5931
|
+
const npm = spawn3("npm", ["install", "-g", "mihomo-cli"], { stdio: "inherit" });
|
|
5320
5932
|
npm.on("close", (code) => {
|
|
5321
5933
|
if (code === 0) {
|
|
5322
5934
|
resolve();
|
|
@@ -5452,6 +6064,9 @@ async function main() {
|
|
|
5452
6064
|
case "overwrite":
|
|
5453
6065
|
await cmdOverwrite(args);
|
|
5454
6066
|
break;
|
|
6067
|
+
case "bench":
|
|
6068
|
+
await cmdBench(args);
|
|
6069
|
+
break;
|
|
5455
6070
|
default:
|
|
5456
6071
|
console.error(`\u672A\u77E5\u547D\u4EE4: ${cmd}`);
|
|
5457
6072
|
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|