mihomo-cli 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +9 -4
- package/dist/index.js +509 -199
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.2.0] - 2026-05-01
|
|
4
|
+
|
|
5
|
+
### 新增
|
|
6
|
+
|
|
7
|
+
- **节点测速**:`sub test [name]` 测试订阅节点连通性,支持 `-t` 超时和 `-j` 并发参数
|
|
8
|
+
- **节点清理**:`sub clean [name]` 测速后自动清理不可用节点,移除空分组
|
|
9
|
+
- **启动自动清理**:`start` / `start tun` 启动时,节点数超过 100 自动执行清理
|
|
10
|
+
|
|
11
|
+
### 安全
|
|
12
|
+
|
|
13
|
+
- **强制端口配置**:HTTP 端口固定 7890,SOCKS5 端口固定 7891,忽略订阅中的 `mixed-port` 配置
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [2.1.0] - 2026-05-01
|
|
18
|
+
|
|
19
|
+
### 新增
|
|
20
|
+
|
|
21
|
+
- **删除订阅**:`sub remove <name>` 删除订阅(别名 `rm`/`delete`),同时清理缓存和配置文件
|
|
22
|
+
- 删除当前使用中的订阅时自动切换到第一个剩余订阅
|
|
23
|
+
- **添加即切换**:`sub add` 添加订阅后自动切换为当前使用的订阅
|
|
24
|
+
|
|
25
|
+
### 安全
|
|
26
|
+
|
|
27
|
+
- **强制 `allow-lan: false`**:无论订阅配置如何,始终禁止局域网访问
|
|
28
|
+
- **强制 `external-controller: 127.0.0.1:9090`**:控制面板仅监听本地,防止不可信订阅暴露控制接口
|
|
29
|
+
- **剥离 `external-ui` 相关字段**:构建配置时强制删除 `external-ui`/`external-ui-name`/`external-ui-url`,防止订阅触发额外下载
|
|
30
|
+
|
|
31
|
+
### 优化
|
|
32
|
+
|
|
33
|
+
- **TUN DNS 劫持**:`dns-hijack` 从 `['0.0.0.0:53']` 改为 `['any:53', 'tcp://any:53']`,同时劫持 UDP 和 TCP DNS,覆盖 IPv4/IPv6
|
|
34
|
+
- **帮助顺序统一**:订阅子命令统一为 use → add → update → remove → web 顺序
|
|
35
|
+
- **`removeSubscription` 返回切换信息**:返回自动切换到的订阅名,避免调用方重复读取状态
|
|
36
|
+
- **`setDefaultSubscription` 跳过冗余写入**:已是同值时直接返回
|
|
37
|
+
- **删除后跳过自动更新**:`sub remove` 后列出订阅时不触发网络更新
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
3
41
|
## [2.0.1] - 2026-04-22
|
|
4
42
|
|
|
5
43
|
### 修复
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
- 🌐 **订阅管理** - 添加/更新订阅,支持流量统计和到期时间显示
|
|
8
8
|
- 🔄 **自动更新** - 启动时自动检查并更新过期订阅
|
|
9
9
|
- 🔍 **模糊匹配** - `sub use` / `sub web` 支持订阅名称模糊匹配
|
|
10
|
+
- 🧹 **节点测速清理** - `sub test` 测试连通性,`sub clean` 自动清理失败节点,启动时超过 100 节点自动清理
|
|
10
11
|
- 📝 **覆写配置** - 在订阅基础上进行自定义覆写,支持强制覆盖、数组合并
|
|
11
12
|
- 🔄 **智能重启** - `sub use` 切换订阅、`ow on/off` 切换覆写后自动重启
|
|
12
13
|
- 🚀 **进程管理** - 启动/停止/切换模式,自动清理残留进程
|
|
@@ -94,11 +95,14 @@ mihomo ui yacd # YACD
|
|
|
94
95
|
| 命令 | 说明 |
|
|
95
96
|
| ----------------------------- | -------------------------------------- |
|
|
96
97
|
| `mihomo sub` | 列出所有订阅(含流量、到期时间) |
|
|
97
|
-
| `mihomo sub
|
|
98
|
+
| `mihomo sub use <name>` | 切换当前订阅(支持模糊匹配,自动重启) |
|
|
99
|
+
| `mihomo sub add <url> [name]` | 添加订阅并自动切换 |
|
|
98
100
|
| `mihomo sub update` | 更新所有订阅 |
|
|
99
101
|
| `mihomo sub update <name>` | 更新指定订阅(支持模糊匹配) |
|
|
100
|
-
| `mihomo sub
|
|
102
|
+
| `mihomo sub remove <name>` | 删除订阅(支持模糊匹配) |
|
|
101
103
|
| `mihomo sub web [name]` | 打开订阅页面(无参打开默认) |
|
|
104
|
+
| `mihomo sub test [name]` | 测试节点连通性(`-t` 超时,`-j` 并发) |
|
|
105
|
+
| `mihomo sub clean [name]` | 测速并清理失败节点 |
|
|
102
106
|
|
|
103
107
|
### 覆写配置
|
|
104
108
|
|
|
@@ -289,9 +293,10 @@ sudo pkill -9 mihomo
|
|
|
289
293
|
|
|
290
294
|
### 端口被占用
|
|
291
295
|
|
|
292
|
-
|
|
296
|
+
默认端口(系统强制,不受订阅配置影响):
|
|
293
297
|
|
|
294
|
-
-
|
|
298
|
+
- HTTP 端口: `7890`
|
|
299
|
+
- SOCKS5 端口: `7891`
|
|
295
300
|
- 外部控制器: `127.0.0.1:9090`
|
|
296
301
|
|
|
297
302
|
## 安全特性
|
package/dist/index.js
CHANGED
|
@@ -2695,13 +2695,17 @@ var TUN_CONFIG = {
|
|
|
2695
2695
|
tun: {
|
|
2696
2696
|
enable: true,
|
|
2697
2697
|
stack: "mixed",
|
|
2698
|
-
"dns-hijack": ["
|
|
2698
|
+
"dns-hijack": ["any:53", "tcp://any:53"],
|
|
2699
2699
|
"auto-route": true,
|
|
2700
2700
|
"auto-detect-interface": true,
|
|
2701
2701
|
"strict-route": true
|
|
2702
2702
|
}
|
|
2703
2703
|
};
|
|
2704
2704
|
var BASE_CONFIG = {
|
|
2705
|
+
"allow-lan": false,
|
|
2706
|
+
"external-controller": "127.0.0.1:9090",
|
|
2707
|
+
port: 7890,
|
|
2708
|
+
"socks-port": 7891,
|
|
2705
2709
|
"log-level": "warning",
|
|
2706
2710
|
"geodata-mode": true,
|
|
2707
2711
|
"geo-update-interval": 24,
|
|
@@ -2819,11 +2823,33 @@ function addSubscription(url, name = "default") {
|
|
|
2819
2823
|
}
|
|
2820
2824
|
writeSettings(updates);
|
|
2821
2825
|
}
|
|
2826
|
+
function removeSubscription(name) {
|
|
2827
|
+
const settings = readSettings();
|
|
2828
|
+
const subs = settings.subscriptions || [];
|
|
2829
|
+
const idx = subs.findIndex((s) => s.name === name);
|
|
2830
|
+
if (idx < 0) return null;
|
|
2831
|
+
subs.splice(idx, 1);
|
|
2832
|
+
const updates = { subscriptions: subs };
|
|
2833
|
+
let switchedTo = null;
|
|
2834
|
+
if (settings.active_subscription === name) {
|
|
2835
|
+
switchedTo = subs.length > 0 ? subs[0].name : null;
|
|
2836
|
+
updates.active_subscription = switchedTo ?? void 0;
|
|
2837
|
+
}
|
|
2838
|
+
writeSettings(updates);
|
|
2839
|
+
const cache = readSubscriptionCache();
|
|
2840
|
+
if (cache[name]) {
|
|
2841
|
+
delete cache[name];
|
|
2842
|
+
writeSubscriptionCache(cache);
|
|
2843
|
+
}
|
|
2844
|
+
fs2.rmSync(getSubscriptionRawConfigPath(name), { force: true });
|
|
2845
|
+
return switchedTo;
|
|
2846
|
+
}
|
|
2822
2847
|
function setDefaultSubscription(name) {
|
|
2823
2848
|
const settings = readSettings();
|
|
2824
2849
|
const subs = settings.subscriptions || [];
|
|
2825
2850
|
const idx = subs.findIndex((s) => s.name === name);
|
|
2826
2851
|
if (idx < 0) return false;
|
|
2852
|
+
if (settings.active_subscription === name) return true;
|
|
2827
2853
|
writeSettings({ active_subscription: name });
|
|
2828
2854
|
return true;
|
|
2829
2855
|
}
|
|
@@ -3003,6 +3029,14 @@ function buildConfig(subRawContent, mode) {
|
|
|
3003
3029
|
systemConfig[key] = value;
|
|
3004
3030
|
}
|
|
3005
3031
|
}
|
|
3032
|
+
systemConfig["allow-lan"] = false;
|
|
3033
|
+
systemConfig["external-controller"] = BASE_CONFIG["external-controller"];
|
|
3034
|
+
systemConfig.port = BASE_CONFIG.port;
|
|
3035
|
+
systemConfig["socks-port"] = BASE_CONFIG["socks-port"];
|
|
3036
|
+
delete withOverwrites["mixed-port"];
|
|
3037
|
+
delete withOverwrites["external-ui"];
|
|
3038
|
+
delete withOverwrites["external-ui-name"];
|
|
3039
|
+
delete withOverwrites["external-ui-url"];
|
|
3006
3040
|
if (mode === "tun") {
|
|
3007
3041
|
systemConfig.tun = TUN_CONFIG.tun;
|
|
3008
3042
|
const subDns = withOverwrites.dns || {};
|
|
@@ -3116,6 +3150,9 @@ var colors = {
|
|
|
3116
3150
|
function sleepSync(ms) {
|
|
3117
3151
|
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
3118
3152
|
}
|
|
3153
|
+
function sleep(ms) {
|
|
3154
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3155
|
+
}
|
|
3119
3156
|
function formatBytes(bytes) {
|
|
3120
3157
|
if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
|
|
3121
3158
|
const num = Number(bytes);
|
|
@@ -3833,10 +3870,13 @@ ${colors.cyan("\u754C\u9762:")}
|
|
|
3833
3870
|
|
|
3834
3871
|
${colors.cyan("\u8BA2\u9605:")}
|
|
3835
3872
|
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
3873
|
+
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
3836
3874
|
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
3837
3875
|
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
3838
|
-
${colors.bold("subscription")}
|
|
3876
|
+
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
3839
3877
|
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
3878
|
+
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
3879
|
+
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
3840
3880
|
|
|
3841
3881
|
${colors.cyan("\u914D\u7F6E:")}
|
|
3842
3882
|
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
@@ -4238,7 +4278,25 @@ import path5 from "path";
|
|
|
4238
4278
|
|
|
4239
4279
|
// src/subscription.ts
|
|
4240
4280
|
var DEFAULT_UPDATE_INTERVAL_HOURS = 12;
|
|
4281
|
+
var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, noCompatMode: true };
|
|
4241
4282
|
var HTTP_CLIENT2 = createHttpClient({ timeout: 6e4 });
|
|
4283
|
+
function loadSubscriptionConfig(subName) {
|
|
4284
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4285
|
+
if (!rawContent) {
|
|
4286
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
4287
|
+
}
|
|
4288
|
+
const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4289
|
+
return {
|
|
4290
|
+
raw,
|
|
4291
|
+
proxies: raw.proxies || [],
|
|
4292
|
+
proxyGroups: raw["proxy-groups"] || []
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
4295
|
+
function saveSubscriptionConfig(subName, parsed) {
|
|
4296
|
+
parsed.raw.proxies = parsed.proxies;
|
|
4297
|
+
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4298
|
+
saveSubscriptionRawConfig(subName, jsYaml.dump(parsed.raw, YAML_DUMP_OPTS));
|
|
4299
|
+
}
|
|
4242
4300
|
function parseUserInfo(header) {
|
|
4243
4301
|
if (!header) return null;
|
|
4244
4302
|
const info = {};
|
|
@@ -4413,6 +4471,122 @@ async function autoUpdateStaleSubscription() {
|
|
|
4413
4471
|
}
|
|
4414
4472
|
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4415
4473
|
}
|
|
4474
|
+
var API_BASE = `http://${BASE_CONFIG["external-controller"]}`;
|
|
4475
|
+
var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
4476
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client) {
|
|
4477
|
+
const encodedName = encodeURIComponent(proxyName);
|
|
4478
|
+
const url = `${API_BASE}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4479
|
+
try {
|
|
4480
|
+
const response = await client.get(url);
|
|
4481
|
+
const data = JSON.parse(response.data);
|
|
4482
|
+
if (data.delay && data.delay > 0) {
|
|
4483
|
+
return { name: proxyName, delay: data.delay };
|
|
4484
|
+
}
|
|
4485
|
+
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
4486
|
+
} catch (e) {
|
|
4487
|
+
const err = e;
|
|
4488
|
+
let errorMsg = "timeout";
|
|
4489
|
+
if (err.response?.data?.message) {
|
|
4490
|
+
errorMsg = String(err.response.data.message);
|
|
4491
|
+
} else if (err.message) {
|
|
4492
|
+
errorMsg = err.message;
|
|
4493
|
+
}
|
|
4494
|
+
return { name: proxyName, delay: null, error: errorMsg };
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
async function testSubscriptionProxies(subName, options = {}) {
|
|
4498
|
+
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, onResult } = options;
|
|
4499
|
+
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4500
|
+
if (proxies.length === 0) {
|
|
4501
|
+
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
4502
|
+
}
|
|
4503
|
+
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
4504
|
+
const results = [];
|
|
4505
|
+
let completedCount = 0;
|
|
4506
|
+
for (let i = 0; i < proxies.length; i += concurrency) {
|
|
4507
|
+
const batch = proxies.slice(i, i + concurrency);
|
|
4508
|
+
const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client)));
|
|
4509
|
+
for (const result of batchResults) {
|
|
4510
|
+
results.push(result);
|
|
4511
|
+
onResult?.(result, completedCount, proxies.length);
|
|
4512
|
+
completedCount++;
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
const alive = results.filter((r) => r.delay !== null).length;
|
|
4516
|
+
return { total: results.length, alive, dead: results.length - alive, results };
|
|
4517
|
+
}
|
|
4518
|
+
function shortenProxyNames(parsed) {
|
|
4519
|
+
const { proxies, proxyGroups } = parsed;
|
|
4520
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
4521
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
4522
|
+
for (const proxy of proxies) {
|
|
4523
|
+
const shortened = proxy.name.replace(/_github\.com\/[^_]+/, "");
|
|
4524
|
+
if (shortened !== proxy.name && !usedNames.has(shortened)) {
|
|
4525
|
+
renameMap.set(proxy.name, shortened);
|
|
4526
|
+
usedNames.add(shortened);
|
|
4527
|
+
} else {
|
|
4528
|
+
usedNames.add(proxy.name);
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
if (renameMap.size === 0) return 0;
|
|
4532
|
+
for (const proxy of proxies) {
|
|
4533
|
+
const newName = renameMap.get(proxy.name);
|
|
4534
|
+
if (newName) proxy.name = newName;
|
|
4535
|
+
}
|
|
4536
|
+
for (const group of proxyGroups) {
|
|
4537
|
+
if (Array.isArray(group.proxies)) {
|
|
4538
|
+
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
return renameMap.size;
|
|
4542
|
+
}
|
|
4543
|
+
function cleanDeadProxies(parsed, deadNames) {
|
|
4544
|
+
const { proxies, proxyGroups } = parsed;
|
|
4545
|
+
const originalCount = proxies.length;
|
|
4546
|
+
parsed.proxies = proxies.filter((p) => !deadNames.has(p.name));
|
|
4547
|
+
const removedProxies = originalCount - parsed.proxies.length;
|
|
4548
|
+
let updatedGroups = 0;
|
|
4549
|
+
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
4550
|
+
for (const group of proxyGroups) {
|
|
4551
|
+
if (Array.isArray(group.proxies)) {
|
|
4552
|
+
const before = group.proxies.length;
|
|
4553
|
+
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
4554
|
+
if (group.proxies.length < before) {
|
|
4555
|
+
updatedGroups++;
|
|
4556
|
+
}
|
|
4557
|
+
if (group.proxies.length === 0) {
|
|
4558
|
+
removedGroupNames.add(group.name);
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
if (removedGroupNames.size > 0) {
|
|
4563
|
+
parsed.proxyGroups = proxyGroups.filter((g) => !removedGroupNames.has(g.name));
|
|
4564
|
+
for (const group of parsed.proxyGroups) {
|
|
4565
|
+
if (Array.isArray(group.proxies)) {
|
|
4566
|
+
group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
return { removedProxies, updatedGroups, removedGroups: removedGroupNames.size };
|
|
4571
|
+
}
|
|
4572
|
+
async function autoCleanSubscription(subName, options = {}) {
|
|
4573
|
+
const parsed = loadSubscriptionConfig(subName);
|
|
4574
|
+
shortenProxyNames(parsed);
|
|
4575
|
+
saveSubscriptionConfig(subName, parsed);
|
|
4576
|
+
const summary = await testSubscriptionProxies(subName, { ...options, parsed });
|
|
4577
|
+
let removedProxies = 0;
|
|
4578
|
+
let updatedGroups = 0;
|
|
4579
|
+
let removedGroups = 0;
|
|
4580
|
+
if (summary.dead > 0) {
|
|
4581
|
+
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
4582
|
+
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
4583
|
+
removedProxies = cleanResult.removedProxies;
|
|
4584
|
+
updatedGroups = cleanResult.updatedGroups;
|
|
4585
|
+
removedGroups = cleanResult.removedGroups;
|
|
4586
|
+
saveSubscriptionConfig(subName, parsed);
|
|
4587
|
+
}
|
|
4588
|
+
return { summary, removedProxies, updatedGroups, removedGroups };
|
|
4589
|
+
}
|
|
4416
4590
|
|
|
4417
4591
|
// src/commands/status.ts
|
|
4418
4592
|
function printStatus() {
|
|
@@ -4465,7 +4639,316 @@ function printStatus() {
|
|
|
4465
4639
|
console.log("");
|
|
4466
4640
|
}
|
|
4467
4641
|
|
|
4642
|
+
// src/commands/subscription.ts
|
|
4643
|
+
function printTestResult(result, index, total) {
|
|
4644
|
+
const prefix = `[${index + 1}/${total}]`;
|
|
4645
|
+
if (result.delay !== null) {
|
|
4646
|
+
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
4647
|
+
console.log(` ${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}`);
|
|
4648
|
+
} else {
|
|
4649
|
+
console.log(` ${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
function formatCleanSummary(result) {
|
|
4653
|
+
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
4654
|
+
if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
|
|
4655
|
+
if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
|
|
4656
|
+
return parts.join(", ");
|
|
4657
|
+
}
|
|
4658
|
+
function formatTestSummary(summary) {
|
|
4659
|
+
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
4660
|
+
}
|
|
4661
|
+
function resolveActiveTestTarget(args) {
|
|
4662
|
+
const subs = getSubscriptions();
|
|
4663
|
+
if (subs.length === 0) {
|
|
4664
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4665
|
+
process.exit(1);
|
|
4666
|
+
}
|
|
4667
|
+
const nameArg = getNonFlagArg(args, 2);
|
|
4668
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 2e3);
|
|
4669
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
4670
|
+
const activeSub = getActiveSubscription();
|
|
4671
|
+
let target;
|
|
4672
|
+
if (nameArg) {
|
|
4673
|
+
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
4674
|
+
target = pickSingleSubscription(matches, nameArg);
|
|
4675
|
+
} else {
|
|
4676
|
+
if (!activeSub) {
|
|
4677
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
4678
|
+
process.exit(1);
|
|
4679
|
+
}
|
|
4680
|
+
target = activeSub;
|
|
4681
|
+
}
|
|
4682
|
+
const status = getStatus();
|
|
4683
|
+
if (!status.running) {
|
|
4684
|
+
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
4685
|
+
process.exit(1);
|
|
4686
|
+
}
|
|
4687
|
+
if (!activeSub || activeSub.name !== target.name) {
|
|
4688
|
+
console.error(`\u9519\u8BEF: \u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605\u662F "${activeSub?.name}"\uFF0C\u4E0D\u662F "${target.name}"`);
|
|
4689
|
+
console.log(`\u8BF7\u5148\u5207\u6362: mihomo sub use ${target.name}`);
|
|
4690
|
+
process.exit(1);
|
|
4691
|
+
}
|
|
4692
|
+
return { target, timeout, concurrency };
|
|
4693
|
+
}
|
|
4694
|
+
async function printSubscriptionList(options) {
|
|
4695
|
+
if (options?.autoUpdate !== false) {
|
|
4696
|
+
const updateResult = await autoUpdateStaleSubscription();
|
|
4697
|
+
if (updateResult.total > 0) console.log("");
|
|
4698
|
+
}
|
|
4699
|
+
const subs = getSubscriptionsWithCache();
|
|
4700
|
+
if (subs.length === 0) {
|
|
4701
|
+
console.log("\u6CA1\u6709\u8BA2\u9605");
|
|
4702
|
+
console.log("");
|
|
4703
|
+
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4704
|
+
console.log("");
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
const activeSub = getActiveSubscription();
|
|
4708
|
+
console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
|
|
4709
|
+
subs.forEach((s, i) => {
|
|
4710
|
+
const time = formatDate(s.updated_at);
|
|
4711
|
+
const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
|
|
4712
|
+
const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4713
|
+
console.log(` ${i + 1}. ${s.name}${defaultMark}`);
|
|
4714
|
+
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
4715
|
+
if (s.username) {
|
|
4716
|
+
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
4717
|
+
}
|
|
4718
|
+
if (s.download !== void 0 || s.total !== void 0) {
|
|
4719
|
+
const used = (s.upload || 0) + (s.download || 0);
|
|
4720
|
+
const usedStr = formatBytes(used);
|
|
4721
|
+
const totalStr = formatBytes(s.total);
|
|
4722
|
+
let percentStr = "";
|
|
4723
|
+
if (s.total && s.total > 0) {
|
|
4724
|
+
const percent = Math.min(used / s.total * 100, 100);
|
|
4725
|
+
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
4726
|
+
}
|
|
4727
|
+
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
4728
|
+
}
|
|
4729
|
+
if (s.expire !== void 0) {
|
|
4730
|
+
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
4731
|
+
}
|
|
4732
|
+
if (s.web_page_url) {
|
|
4733
|
+
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
4734
|
+
}
|
|
4735
|
+
});
|
|
4736
|
+
console.log("");
|
|
4737
|
+
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
4738
|
+
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4739
|
+
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
4740
|
+
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
4741
|
+
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
4742
|
+
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
4743
|
+
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
4744
|
+
console.log("");
|
|
4745
|
+
}
|
|
4746
|
+
async function cmdSubscription(args) {
|
|
4747
|
+
const action = args[1];
|
|
4748
|
+
if (!action || action === "list") {
|
|
4749
|
+
await printSubscriptionList();
|
|
4750
|
+
return;
|
|
4751
|
+
}
|
|
4752
|
+
if (action === "add") {
|
|
4753
|
+
const url = args[2];
|
|
4754
|
+
const name = args[3] || "default";
|
|
4755
|
+
if (!url?.startsWith("http")) {
|
|
4756
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
4757
|
+
process.exit(1);
|
|
4758
|
+
}
|
|
4759
|
+
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
4760
|
+
try {
|
|
4761
|
+
addSubscription(url, name);
|
|
4762
|
+
setDefaultSubscription(name);
|
|
4763
|
+
const info = await downloadSubscription(url, name);
|
|
4764
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4765
|
+
} catch (e) {
|
|
4766
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4767
|
+
process.exit(1);
|
|
4768
|
+
}
|
|
4769
|
+
console.log("");
|
|
4770
|
+
await printSubscriptionList();
|
|
4771
|
+
return;
|
|
4772
|
+
}
|
|
4773
|
+
if (action === "update") {
|
|
4774
|
+
const name = args[2];
|
|
4775
|
+
const subs = getSubscriptions();
|
|
4776
|
+
if (subs.length === 0) {
|
|
4777
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4778
|
+
process.exit(1);
|
|
4779
|
+
}
|
|
4780
|
+
if (!name) {
|
|
4781
|
+
console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
|
|
4782
|
+
const results = await Promise.all(subs.map(tryUpdateOne));
|
|
4783
|
+
let ok = 0;
|
|
4784
|
+
for (const r of results) {
|
|
4785
|
+
if (r.success) {
|
|
4786
|
+
ok++;
|
|
4787
|
+
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4788
|
+
} else {
|
|
4789
|
+
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
if (ok === 0) process.exit(1);
|
|
4793
|
+
console.log("");
|
|
4794
|
+
await printSubscriptionList();
|
|
4795
|
+
return;
|
|
4796
|
+
}
|
|
4797
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4798
|
+
const target = pickSingleSubscription(matches, name);
|
|
4799
|
+
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
4800
|
+
try {
|
|
4801
|
+
const info = await downloadSubscription(target.url, target.name);
|
|
4802
|
+
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
|
|
4803
|
+
} catch (e) {
|
|
4804
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4805
|
+
process.exit(1);
|
|
4806
|
+
}
|
|
4807
|
+
console.log("");
|
|
4808
|
+
await printSubscriptionList();
|
|
4809
|
+
return;
|
|
4810
|
+
}
|
|
4811
|
+
if (action === "use") {
|
|
4812
|
+
const name = args[2];
|
|
4813
|
+
const subs = getSubscriptions();
|
|
4814
|
+
if (!name) {
|
|
4815
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
4816
|
+
if (subs.length > 0) {
|
|
4817
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4818
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4819
|
+
}
|
|
4820
|
+
process.exit(1);
|
|
4821
|
+
}
|
|
4822
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4823
|
+
const target = pickSingleSubscription(matches, name);
|
|
4824
|
+
const currentDefault = getActiveSubscription();
|
|
4825
|
+
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
4826
|
+
if (isAlreadyDefault) {
|
|
4827
|
+
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
4828
|
+
console.log("");
|
|
4829
|
+
await printSubscriptionList();
|
|
4830
|
+
return;
|
|
4831
|
+
}
|
|
4832
|
+
const status = getStatus();
|
|
4833
|
+
const configInfo = getConfigInfo();
|
|
4834
|
+
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
4835
|
+
const success = setDefaultSubscription(target.name);
|
|
4836
|
+
if (success) {
|
|
4837
|
+
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
4838
|
+
} else {
|
|
4839
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
4840
|
+
process.exit(1);
|
|
4841
|
+
}
|
|
4842
|
+
if (status.running) {
|
|
4843
|
+
console.log("");
|
|
4844
|
+
await cmdStart(["start", currentMode]);
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4847
|
+
console.log("");
|
|
4848
|
+
await printSubscriptionList();
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4851
|
+
if (action === "web" || action === "open") {
|
|
4852
|
+
const name = args[2];
|
|
4853
|
+
const subs = getSubscriptionsWithCache();
|
|
4854
|
+
if (subs.length === 0) {
|
|
4855
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4856
|
+
process.exit(1);
|
|
4857
|
+
}
|
|
4858
|
+
let target;
|
|
4859
|
+
if (name) {
|
|
4860
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4861
|
+
target = pickSingleSubscription(matches, name);
|
|
4862
|
+
} else {
|
|
4863
|
+
target = subs[0];
|
|
4864
|
+
}
|
|
4865
|
+
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
4866
|
+
let webPageUrl = cached?.web_page_url;
|
|
4867
|
+
if (!webPageUrl) {
|
|
4868
|
+
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
4869
|
+
try {
|
|
4870
|
+
await downloadSubscription(target.url, target.name);
|
|
4871
|
+
const cache = readSubscriptionCache();
|
|
4872
|
+
if (cache[target.name]?.web_page_url) {
|
|
4873
|
+
webPageUrl = cache[target.name].web_page_url;
|
|
4874
|
+
} else {
|
|
4875
|
+
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
4876
|
+
process.exit(1);
|
|
4877
|
+
}
|
|
4878
|
+
} catch (e) {
|
|
4879
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4880
|
+
process.exit(1);
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
|
|
4884
|
+
const opened = openUrl(webPageUrl);
|
|
4885
|
+
if (!opened) {
|
|
4886
|
+
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
4887
|
+
}
|
|
4888
|
+
return;
|
|
4889
|
+
}
|
|
4890
|
+
if (action === "remove" || action === "rm" || action === "delete") {
|
|
4891
|
+
const name = args[2];
|
|
4892
|
+
const subs = getSubscriptions();
|
|
4893
|
+
if (!name) {
|
|
4894
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
|
|
4895
|
+
if (subs.length > 0) {
|
|
4896
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4897
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4898
|
+
}
|
|
4899
|
+
process.exit(1);
|
|
4900
|
+
}
|
|
4901
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4902
|
+
const target = pickSingleSubscription(matches, name);
|
|
4903
|
+
const switchedTo = removeSubscription(target.name);
|
|
4904
|
+
console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
|
|
4905
|
+
if (switchedTo) {
|
|
4906
|
+
console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
|
|
4907
|
+
}
|
|
4908
|
+
console.log("");
|
|
4909
|
+
await printSubscriptionList({ autoUpdate: false });
|
|
4910
|
+
return;
|
|
4911
|
+
}
|
|
4912
|
+
if (action === "clean") {
|
|
4913
|
+
const { target, timeout, concurrency } = resolveActiveTestTarget(args);
|
|
4914
|
+
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
4915
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
4916
|
+
console.log("");
|
|
4917
|
+
const result = await autoCleanSubscription(target.name, {
|
|
4918
|
+
timeout,
|
|
4919
|
+
concurrency,
|
|
4920
|
+
onResult: printTestResult
|
|
4921
|
+
});
|
|
4922
|
+
console.log("");
|
|
4923
|
+
console.log(formatTestSummary(result.summary));
|
|
4924
|
+
if (result.removedProxies > 0) {
|
|
4925
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
4926
|
+
}
|
|
4927
|
+
console.log("");
|
|
4928
|
+
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
4929
|
+
return;
|
|
4930
|
+
}
|
|
4931
|
+
if (action === "test") {
|
|
4932
|
+
const { target, timeout, concurrency } = resolveActiveTestTarget(args);
|
|
4933
|
+
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
4934
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
4935
|
+
console.log("");
|
|
4936
|
+
const summary = await testSubscriptionProxies(target.name, {
|
|
4937
|
+
timeout,
|
|
4938
|
+
concurrency,
|
|
4939
|
+
onResult: printTestResult
|
|
4940
|
+
});
|
|
4941
|
+
console.log("");
|
|
4942
|
+
console.log(formatTestSummary(summary));
|
|
4943
|
+
return;
|
|
4944
|
+
}
|
|
4945
|
+
console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
|
|
4946
|
+
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
|
|
4947
|
+
process.exit(1);
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4468
4950
|
// src/commands/start.ts
|
|
4951
|
+
var AUTO_CLEAN_THRESHOLD = 100;
|
|
4469
4952
|
function handleStopResult(result) {
|
|
4470
4953
|
if (result.remaining && result.remaining.length > 0) {
|
|
4471
4954
|
console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
|
|
@@ -4508,11 +4991,34 @@ async function cmdStart(args) {
|
|
|
4508
4991
|
try {
|
|
4509
4992
|
const result = await start(targetMode);
|
|
4510
4993
|
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
|
|
4511
|
-
printStatus();
|
|
4512
4994
|
} catch (e) {
|
|
4513
4995
|
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
4514
4996
|
process.exit(1);
|
|
4515
4997
|
}
|
|
4998
|
+
if (configInfo.proxies > AUTO_CLEAN_THRESHOLD) {
|
|
4999
|
+
console.log("");
|
|
5000
|
+
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${AUTO_CLEAN_THRESHOLD}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
|
|
5001
|
+
console.log("");
|
|
5002
|
+
await sleep(1e3);
|
|
5003
|
+
const cleanResult = await autoCleanSubscription(sub.name, { onResult: printTestResult });
|
|
5004
|
+
console.log("");
|
|
5005
|
+
console.log(formatTestSummary(cleanResult.summary));
|
|
5006
|
+
if (cleanResult.removedProxies > 0) {
|
|
5007
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
5008
|
+
console.log("");
|
|
5009
|
+
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
5010
|
+
handleStopResult(stop(true));
|
|
5011
|
+
try {
|
|
5012
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
5013
|
+
const result = await start(targetMode);
|
|
5014
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${result.pid}) \xB7 ${formatProxySummary(configInfo)}`);
|
|
5015
|
+
} catch (e) {
|
|
5016
|
+
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
5017
|
+
process.exit(1);
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
printStatus();
|
|
4516
5022
|
}
|
|
4517
5023
|
|
|
4518
5024
|
// src/commands/overwrite.ts
|
|
@@ -4762,202 +5268,6 @@ async function cmdStop() {
|
|
|
4762
5268
|
console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
|
|
4763
5269
|
}
|
|
4764
5270
|
|
|
4765
|
-
// src/commands/subscription.ts
|
|
4766
|
-
async function printSubscriptionList() {
|
|
4767
|
-
const updateResult = await autoUpdateStaleSubscription();
|
|
4768
|
-
if (updateResult.total > 0) console.log("");
|
|
4769
|
-
const subs = getSubscriptionsWithCache();
|
|
4770
|
-
if (subs.length === 0) {
|
|
4771
|
-
console.log("\u6CA1\u6709\u8BA2\u9605");
|
|
4772
|
-
console.log("");
|
|
4773
|
-
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4774
|
-
console.log("");
|
|
4775
|
-
return;
|
|
4776
|
-
}
|
|
4777
|
-
const activeSub = getActiveSubscription();
|
|
4778
|
-
console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
|
|
4779
|
-
subs.forEach((s, i) => {
|
|
4780
|
-
const time = formatDate(s.updated_at);
|
|
4781
|
-
const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
|
|
4782
|
-
const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4783
|
-
console.log(` ${i + 1}. ${s.name}${defaultMark}`);
|
|
4784
|
-
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
4785
|
-
if (s.username) {
|
|
4786
|
-
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
4787
|
-
}
|
|
4788
|
-
if (s.download !== void 0 || s.total !== void 0) {
|
|
4789
|
-
const used = (s.upload || 0) + (s.download || 0);
|
|
4790
|
-
const usedStr = formatBytes(used);
|
|
4791
|
-
const totalStr = formatBytes(s.total);
|
|
4792
|
-
let percentStr = "";
|
|
4793
|
-
if (s.total && s.total > 0) {
|
|
4794
|
-
const percent = Math.min(used / s.total * 100, 100);
|
|
4795
|
-
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
4796
|
-
}
|
|
4797
|
-
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
4798
|
-
}
|
|
4799
|
-
if (s.expire !== void 0) {
|
|
4800
|
-
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
4801
|
-
}
|
|
4802
|
-
if (s.web_page_url) {
|
|
4803
|
-
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
4804
|
-
}
|
|
4805
|
-
});
|
|
4806
|
-
console.log("");
|
|
4807
|
-
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
4808
|
-
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
4809
|
-
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
4810
|
-
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4811
|
-
console.log("");
|
|
4812
|
-
}
|
|
4813
|
-
async function cmdSubscription(args) {
|
|
4814
|
-
const action = args[1];
|
|
4815
|
-
if (!action || action === "list") {
|
|
4816
|
-
await printSubscriptionList();
|
|
4817
|
-
return;
|
|
4818
|
-
}
|
|
4819
|
-
if (action === "add") {
|
|
4820
|
-
const url = args[2];
|
|
4821
|
-
const name = args[3] || "default";
|
|
4822
|
-
if (!url?.startsWith("http")) {
|
|
4823
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
4824
|
-
process.exit(1);
|
|
4825
|
-
}
|
|
4826
|
-
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
4827
|
-
try {
|
|
4828
|
-
addSubscription(url, name);
|
|
4829
|
-
const info = await downloadSubscription(url, name);
|
|
4830
|
-
console.log(`\u5DF2\u6DFB\u52A0 (${formatProxySummary(info)})`);
|
|
4831
|
-
} catch (e) {
|
|
4832
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4833
|
-
process.exit(1);
|
|
4834
|
-
}
|
|
4835
|
-
console.log("");
|
|
4836
|
-
await printSubscriptionList();
|
|
4837
|
-
return;
|
|
4838
|
-
}
|
|
4839
|
-
if (action === "update") {
|
|
4840
|
-
const name = args[2];
|
|
4841
|
-
const subs = getSubscriptions();
|
|
4842
|
-
if (subs.length === 0) {
|
|
4843
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4844
|
-
process.exit(1);
|
|
4845
|
-
}
|
|
4846
|
-
if (!name) {
|
|
4847
|
-
console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
|
|
4848
|
-
const results = await Promise.all(subs.map(tryUpdateOne));
|
|
4849
|
-
let ok = 0;
|
|
4850
|
-
for (const r of results) {
|
|
4851
|
-
if (r.success) {
|
|
4852
|
-
ok++;
|
|
4853
|
-
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4854
|
-
} else {
|
|
4855
|
-
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
4856
|
-
}
|
|
4857
|
-
}
|
|
4858
|
-
if (ok === 0) process.exit(1);
|
|
4859
|
-
console.log("");
|
|
4860
|
-
await printSubscriptionList();
|
|
4861
|
-
return;
|
|
4862
|
-
}
|
|
4863
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
4864
|
-
const target = pickSingleSubscription(matches, name);
|
|
4865
|
-
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
4866
|
-
try {
|
|
4867
|
-
const info = await downloadSubscription(target.url, target.name);
|
|
4868
|
-
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
|
|
4869
|
-
} catch (e) {
|
|
4870
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4871
|
-
process.exit(1);
|
|
4872
|
-
}
|
|
4873
|
-
console.log("");
|
|
4874
|
-
await printSubscriptionList();
|
|
4875
|
-
return;
|
|
4876
|
-
}
|
|
4877
|
-
if (action === "use") {
|
|
4878
|
-
const name = args[2];
|
|
4879
|
-
const subs = getSubscriptions();
|
|
4880
|
-
if (!name) {
|
|
4881
|
-
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
4882
|
-
if (subs.length > 0) {
|
|
4883
|
-
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4884
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
4885
|
-
}
|
|
4886
|
-
process.exit(1);
|
|
4887
|
-
}
|
|
4888
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
4889
|
-
const target = pickSingleSubscription(matches, name);
|
|
4890
|
-
const currentDefault = getActiveSubscription();
|
|
4891
|
-
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
4892
|
-
if (isAlreadyDefault) {
|
|
4893
|
-
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
4894
|
-
console.log("");
|
|
4895
|
-
await printSubscriptionList();
|
|
4896
|
-
return;
|
|
4897
|
-
}
|
|
4898
|
-
const status = getStatus();
|
|
4899
|
-
const configInfo = getConfigInfo();
|
|
4900
|
-
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
4901
|
-
const success = setDefaultSubscription(target.name);
|
|
4902
|
-
if (success) {
|
|
4903
|
-
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
4904
|
-
} else {
|
|
4905
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
4906
|
-
process.exit(1);
|
|
4907
|
-
}
|
|
4908
|
-
if (status.running) {
|
|
4909
|
-
console.log("");
|
|
4910
|
-
await cmdStart(["start", currentMode]);
|
|
4911
|
-
return;
|
|
4912
|
-
}
|
|
4913
|
-
console.log("");
|
|
4914
|
-
await printSubscriptionList();
|
|
4915
|
-
return;
|
|
4916
|
-
}
|
|
4917
|
-
if (action === "web" || action === "open") {
|
|
4918
|
-
const name = args[2];
|
|
4919
|
-
const subs = getSubscriptionsWithCache();
|
|
4920
|
-
if (subs.length === 0) {
|
|
4921
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4922
|
-
process.exit(1);
|
|
4923
|
-
}
|
|
4924
|
-
let target;
|
|
4925
|
-
if (name) {
|
|
4926
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
4927
|
-
target = pickSingleSubscription(matches, name);
|
|
4928
|
-
} else {
|
|
4929
|
-
target = subs[0];
|
|
4930
|
-
}
|
|
4931
|
-
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
4932
|
-
let webPageUrl = cached?.web_page_url;
|
|
4933
|
-
if (!webPageUrl) {
|
|
4934
|
-
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
4935
|
-
try {
|
|
4936
|
-
await downloadSubscription(target.url, target.name);
|
|
4937
|
-
const cache = readSubscriptionCache();
|
|
4938
|
-
if (cache[target.name]?.web_page_url) {
|
|
4939
|
-
webPageUrl = cache[target.name].web_page_url;
|
|
4940
|
-
} else {
|
|
4941
|
-
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
4942
|
-
process.exit(1);
|
|
4943
|
-
}
|
|
4944
|
-
} catch (e) {
|
|
4945
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4946
|
-
process.exit(1);
|
|
4947
|
-
}
|
|
4948
|
-
}
|
|
4949
|
-
console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
|
|
4950
|
-
const opened = openUrl(webPageUrl);
|
|
4951
|
-
if (!opened) {
|
|
4952
|
-
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
4953
|
-
}
|
|
4954
|
-
return;
|
|
4955
|
-
}
|
|
4956
|
-
console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
|
|
4957
|
-
console.log("\u7528\u6CD5: mihomo sub [list|add|update|use|web]");
|
|
4958
|
-
process.exit(1);
|
|
4959
|
-
}
|
|
4960
|
-
|
|
4961
5271
|
// src/commands/ui.ts
|
|
4962
5272
|
function cmdUI(args) {
|
|
4963
5273
|
const uiName = args[1] || "zash";
|