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