mihomo-cli 2.1.0 → 2.2.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 +23 -0
- package/README.md +6 -2
- package/dist/index.js +603 -338
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.2.1] - 2026-05-01
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- **节点名称精简时序**:修复 `shortenProxyNames` 在测速前执行导致 API 返回 "Resource not found" 的问题,改为测速完成后再精简
|
|
8
|
+
- **清理安全阈值**:存活节点不足 1% 时跳过清理,提示用户检查原始订阅
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## [2.2.0] - 2026-05-01
|
|
13
|
+
|
|
14
|
+
### 新增
|
|
15
|
+
|
|
16
|
+
- **节点测速**:`sub test [name]` 测试订阅节点连通性,支持 `-t` 超时和 `-j` 并发参数
|
|
17
|
+
- **节点清理**:`sub clean [name]` 测速后自动清理不可用节点,移除空分组
|
|
18
|
+
- **启动自动清理**:`start` / `start tun` 启动时,节点数超过 100 自动执行清理
|
|
19
|
+
|
|
20
|
+
### 安全
|
|
21
|
+
|
|
22
|
+
- **强制端口配置**:HTTP 端口固定 7890,SOCKS5 端口固定 7891,忽略订阅中的 `mixed-port` 配置
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
3
26
|
## [2.1.0] - 2026-05-01
|
|
4
27
|
|
|
5
28
|
### 新增
|
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
|
- 🚀 **进程管理** - 启动/停止/切换模式,自动清理残留进程
|
|
@@ -100,6 +101,8 @@ mihomo ui yacd # YACD
|
|
|
100
101
|
| `mihomo sub update <name>` | 更新指定订阅(支持模糊匹配) |
|
|
101
102
|
| `mihomo sub remove <name>` | 删除订阅(支持模糊匹配) |
|
|
102
103
|
| `mihomo sub web [name]` | 打开订阅页面(无参打开默认) |
|
|
104
|
+
| `mihomo sub test [name]` | 测试节点连通性(`-t` 超时,`-j` 并发) |
|
|
105
|
+
| `mihomo sub clean [name]` | 测速并清理失败节点 |
|
|
103
106
|
|
|
104
107
|
### 覆写配置
|
|
105
108
|
|
|
@@ -290,9 +293,10 @@ sudo pkill -9 mihomo
|
|
|
290
293
|
|
|
291
294
|
### 端口被占用
|
|
292
295
|
|
|
293
|
-
|
|
296
|
+
默认端口(系统强制,不受订阅配置影响):
|
|
294
297
|
|
|
295
|
-
-
|
|
298
|
+
- HTTP 端口: `7890`
|
|
299
|
+
- SOCKS5 端口: `7891`
|
|
296
300
|
- 外部控制器: `127.0.0.1:9090`
|
|
297
301
|
|
|
298
302
|
## 安全特性
|
package/dist/index.js
CHANGED
|
@@ -2704,6 +2704,8 @@ var TUN_CONFIG = {
|
|
|
2704
2704
|
var BASE_CONFIG = {
|
|
2705
2705
|
"allow-lan": false,
|
|
2706
2706
|
"external-controller": "127.0.0.1:9090",
|
|
2707
|
+
port: 7890,
|
|
2708
|
+
"socks-port": 7891,
|
|
2707
2709
|
"log-level": "warning",
|
|
2708
2710
|
"geodata-mode": true,
|
|
2709
2711
|
"geo-update-interval": 24,
|
|
@@ -3029,6 +3031,9 @@ function buildConfig(subRawContent, mode) {
|
|
|
3029
3031
|
}
|
|
3030
3032
|
systemConfig["allow-lan"] = false;
|
|
3031
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"];
|
|
3032
3037
|
delete withOverwrites["external-ui"];
|
|
3033
3038
|
delete withOverwrites["external-ui-name"];
|
|
3034
3039
|
delete withOverwrites["external-ui-url"];
|
|
@@ -3145,6 +3150,9 @@ var colors = {
|
|
|
3145
3150
|
function sleepSync(ms) {
|
|
3146
3151
|
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
3147
3152
|
}
|
|
3153
|
+
function sleep(ms) {
|
|
3154
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3155
|
+
}
|
|
3148
3156
|
function formatBytes(bytes) {
|
|
3149
3157
|
if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
|
|
3150
3158
|
const num = Number(bytes);
|
|
@@ -3867,6 +3875,8 @@ ${colors.cyan("\u8BA2\u9605:")}
|
|
|
3867
3875
|
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
3868
3876
|
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
3869
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
|
|
3870
3880
|
|
|
3871
3881
|
${colors.cyan("\u914D\u7F6E:")}
|
|
3872
3882
|
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
@@ -4268,7 +4278,26 @@ import path5 from "path";
|
|
|
4268
4278
|
|
|
4269
4279
|
// src/subscription.ts
|
|
4270
4280
|
var DEFAULT_UPDATE_INTERVAL_HOURS = 12;
|
|
4281
|
+
var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, noCompatMode: true };
|
|
4271
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
|
+
shortenProxyNames(parsed);
|
|
4297
|
+
parsed.raw.proxies = parsed.proxies;
|
|
4298
|
+
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4299
|
+
saveSubscriptionRawConfig(subName, jsYaml.dump(parsed.raw, YAML_DUMP_OPTS));
|
|
4300
|
+
}
|
|
4272
4301
|
function parseUserInfo(header) {
|
|
4273
4302
|
if (!header) return null;
|
|
4274
4303
|
const info = {};
|
|
@@ -4443,6 +4472,127 @@ async function autoUpdateStaleSubscription() {
|
|
|
4443
4472
|
}
|
|
4444
4473
|
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4445
4474
|
}
|
|
4475
|
+
var API_BASE = `http://${BASE_CONFIG["external-controller"]}`;
|
|
4476
|
+
var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
4477
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client) {
|
|
4478
|
+
const encodedName = encodeURIComponent(proxyName);
|
|
4479
|
+
const url = `${API_BASE}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4480
|
+
try {
|
|
4481
|
+
const response = await client.get(url);
|
|
4482
|
+
const data = JSON.parse(response.data);
|
|
4483
|
+
if (data.delay && data.delay > 0) {
|
|
4484
|
+
return { name: proxyName, delay: data.delay };
|
|
4485
|
+
}
|
|
4486
|
+
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
4487
|
+
} catch (e) {
|
|
4488
|
+
const err = e;
|
|
4489
|
+
let errorMsg = "timeout";
|
|
4490
|
+
if (err.response?.data?.message) {
|
|
4491
|
+
errorMsg = String(err.response.data.message);
|
|
4492
|
+
} else if (err.message) {
|
|
4493
|
+
errorMsg = err.message;
|
|
4494
|
+
}
|
|
4495
|
+
return { name: proxyName, delay: null, error: errorMsg };
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
async function testSubscriptionProxies(subName, options = {}) {
|
|
4499
|
+
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, onResult } = options;
|
|
4500
|
+
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4501
|
+
if (proxies.length === 0) {
|
|
4502
|
+
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
4503
|
+
}
|
|
4504
|
+
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
4505
|
+
const results = [];
|
|
4506
|
+
let completedCount = 0;
|
|
4507
|
+
for (let i = 0; i < proxies.length; i += concurrency) {
|
|
4508
|
+
const batch = proxies.slice(i, i + concurrency);
|
|
4509
|
+
const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client)));
|
|
4510
|
+
for (const result of batchResults) {
|
|
4511
|
+
results.push(result);
|
|
4512
|
+
onResult?.(result, completedCount, proxies.length);
|
|
4513
|
+
completedCount++;
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
const alive = results.filter((r) => r.delay !== null).length;
|
|
4517
|
+
return { total: results.length, alive, dead: results.length - alive, results };
|
|
4518
|
+
}
|
|
4519
|
+
function shortenProxyNames(parsed) {
|
|
4520
|
+
const { proxies, proxyGroups } = parsed;
|
|
4521
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
4522
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
4523
|
+
for (const proxy of proxies) {
|
|
4524
|
+
const shortened = proxy.name.replace(/_github\.com\/[^_]+/, "");
|
|
4525
|
+
if (shortened !== proxy.name && !usedNames.has(shortened)) {
|
|
4526
|
+
renameMap.set(proxy.name, shortened);
|
|
4527
|
+
usedNames.add(shortened);
|
|
4528
|
+
} else {
|
|
4529
|
+
usedNames.add(proxy.name);
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
if (renameMap.size === 0) return 0;
|
|
4533
|
+
for (const proxy of proxies) {
|
|
4534
|
+
const newName = renameMap.get(proxy.name);
|
|
4535
|
+
if (newName) proxy.name = newName;
|
|
4536
|
+
}
|
|
4537
|
+
for (const group of proxyGroups) {
|
|
4538
|
+
if (Array.isArray(group.proxies)) {
|
|
4539
|
+
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
return renameMap.size;
|
|
4543
|
+
}
|
|
4544
|
+
function cleanDeadProxies(parsed, deadNames) {
|
|
4545
|
+
const { proxies, proxyGroups } = parsed;
|
|
4546
|
+
const originalCount = proxies.length;
|
|
4547
|
+
parsed.proxies = proxies.filter((p) => !deadNames.has(p.name));
|
|
4548
|
+
const removedProxies = originalCount - parsed.proxies.length;
|
|
4549
|
+
let updatedGroups = 0;
|
|
4550
|
+
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
4551
|
+
for (const group of proxyGroups) {
|
|
4552
|
+
if (Array.isArray(group.proxies)) {
|
|
4553
|
+
const before = group.proxies.length;
|
|
4554
|
+
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
4555
|
+
if (group.proxies.length < before) {
|
|
4556
|
+
updatedGroups++;
|
|
4557
|
+
}
|
|
4558
|
+
if (group.proxies.length === 0) {
|
|
4559
|
+
removedGroupNames.add(group.name);
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
if (removedGroupNames.size > 0) {
|
|
4564
|
+
parsed.proxyGroups = proxyGroups.filter((g) => !removedGroupNames.has(g.name));
|
|
4565
|
+
for (const group of parsed.proxyGroups) {
|
|
4566
|
+
if (Array.isArray(group.proxies)) {
|
|
4567
|
+
group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
return { removedProxies, updatedGroups, removedGroups: removedGroupNames.size };
|
|
4572
|
+
}
|
|
4573
|
+
async function autoCleanSubscription(subName, options = {}) {
|
|
4574
|
+
const parsed = loadSubscriptionConfig(subName);
|
|
4575
|
+
const summary = await testSubscriptionProxies(subName, { ...options, parsed });
|
|
4576
|
+
let removedProxies = 0;
|
|
4577
|
+
let updatedGroups = 0;
|
|
4578
|
+
let removedGroups = 0;
|
|
4579
|
+
let skipped = false;
|
|
4580
|
+
if (summary.dead > 0) {
|
|
4581
|
+
if (summary.alive === 0 || summary.alive / summary.total < 0.01) {
|
|
4582
|
+
skipped = true;
|
|
4583
|
+
} else {
|
|
4584
|
+
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
4585
|
+
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
4586
|
+
removedProxies = cleanResult.removedProxies;
|
|
4587
|
+
updatedGroups = cleanResult.updatedGroups;
|
|
4588
|
+
removedGroups = cleanResult.removedGroups;
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
if (!skipped) {
|
|
4592
|
+
saveSubscriptionConfig(subName, parsed);
|
|
4593
|
+
}
|
|
4594
|
+
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
4595
|
+
}
|
|
4446
4596
|
|
|
4447
4597
|
// src/commands/status.ts
|
|
4448
4598
|
function printStatus() {
|
|
@@ -4495,150 +4645,487 @@ function printStatus() {
|
|
|
4495
4645
|
console.log("");
|
|
4496
4646
|
}
|
|
4497
4647
|
|
|
4498
|
-
// src/commands/
|
|
4499
|
-
function
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4648
|
+
// src/commands/subscription.ts
|
|
4649
|
+
function printTestResult(result, index, total) {
|
|
4650
|
+
const prefix = `[${index + 1}/${total}]`;
|
|
4651
|
+
if (result.delay !== null) {
|
|
4652
|
+
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
4653
|
+
console.log(` ${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}`);
|
|
4654
|
+
} else {
|
|
4655
|
+
console.log(` ${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
4504
4656
|
}
|
|
4505
4657
|
}
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4658
|
+
function formatCleanSummary(result) {
|
|
4659
|
+
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
4660
|
+
if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
|
|
4661
|
+
if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
|
|
4662
|
+
return parts.join(", ");
|
|
4663
|
+
}
|
|
4664
|
+
function formatTestSummary(summary) {
|
|
4665
|
+
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
4666
|
+
}
|
|
4667
|
+
function resolveActiveTestTarget(args) {
|
|
4668
|
+
const subs = getSubscriptions();
|
|
4669
|
+
if (subs.length === 0) {
|
|
4670
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4509
4671
|
process.exit(1);
|
|
4510
4672
|
}
|
|
4511
|
-
const
|
|
4512
|
-
const
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4673
|
+
const nameArg = getNonFlagArg(args, 2);
|
|
4674
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 2e3);
|
|
4675
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
4676
|
+
const activeSub = getActiveSubscription();
|
|
4677
|
+
let target;
|
|
4678
|
+
if (nameArg) {
|
|
4679
|
+
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
4680
|
+
target = pickSingleSubscription(matches, nameArg);
|
|
4681
|
+
} else {
|
|
4682
|
+
if (!activeSub) {
|
|
4683
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
4684
|
+
process.exit(1);
|
|
4685
|
+
}
|
|
4686
|
+
target = activeSub;
|
|
4516
4687
|
}
|
|
4517
|
-
await autoUpdateStaleSubscription();
|
|
4518
4688
|
const status = getStatus();
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
4522
|
-
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
4523
|
-
}
|
|
4524
|
-
handleStopResult(stop(true));
|
|
4525
|
-
if (hasProcess) {
|
|
4526
|
-
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
4527
|
-
`);
|
|
4528
|
-
}
|
|
4529
|
-
let configInfo;
|
|
4530
|
-
try {
|
|
4531
|
-
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
4532
|
-
} catch (e) {
|
|
4533
|
-
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
4689
|
+
if (!status.running) {
|
|
4690
|
+
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
4534
4691
|
process.exit(1);
|
|
4535
4692
|
}
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
const result = await start(targetMode);
|
|
4540
|
-
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
|
|
4541
|
-
printStatus();
|
|
4542
|
-
} catch (e) {
|
|
4543
|
-
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
4693
|
+
if (!activeSub || activeSub.name !== target.name) {
|
|
4694
|
+
console.error(`\u9519\u8BEF: \u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605\u662F "${activeSub?.name}"\uFF0C\u4E0D\u662F "${target.name}"`);
|
|
4695
|
+
console.log(`\u8BF7\u5148\u5207\u6362: mihomo sub use ${target.name}`);
|
|
4544
4696
|
process.exit(1);
|
|
4545
4697
|
}
|
|
4698
|
+
return { target, timeout, concurrency };
|
|
4546
4699
|
}
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
if (info.files.length === 0) {
|
|
4556
|
-
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
4557
|
-
console.log("");
|
|
4558
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path5.join(info.dir, "overwrite.yaml")}`);
|
|
4559
|
-
console.log(` \u6216 ${path5.join(info.dir, "overwrite.dns.yaml")}`);
|
|
4560
|
-
console.log("");
|
|
4561
|
-
} else {
|
|
4562
|
-
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
4700
|
+
async function printSubscriptionList(options) {
|
|
4701
|
+
if (options?.autoUpdate !== false) {
|
|
4702
|
+
const updateResult = await autoUpdateStaleSubscription();
|
|
4703
|
+
if (updateResult.total > 0) console.log("");
|
|
4704
|
+
}
|
|
4705
|
+
const subs = getSubscriptionsWithCache();
|
|
4706
|
+
if (subs.length === 0) {
|
|
4707
|
+
console.log("\u6CA1\u6709\u8BA2\u9605");
|
|
4563
4708
|
console.log("");
|
|
4564
|
-
|
|
4565
|
-
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
4566
|
-
console.log(` ${num}. ${f.name}`);
|
|
4567
|
-
if (f.keys.length > 0) {
|
|
4568
|
-
console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
|
|
4569
|
-
}
|
|
4570
|
-
});
|
|
4709
|
+
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4571
4710
|
console.log("");
|
|
4711
|
+
return;
|
|
4572
4712
|
}
|
|
4573
|
-
|
|
4574
|
-
console.log("\
|
|
4713
|
+
const activeSub = getActiveSubscription();
|
|
4714
|
+
console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
|
|
4715
|
+
subs.forEach((s, i) => {
|
|
4716
|
+
const time = formatDate(s.updated_at);
|
|
4717
|
+
const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
|
|
4718
|
+
const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4719
|
+
console.log(` ${i + 1}. ${s.name}${defaultMark}`);
|
|
4720
|
+
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
4721
|
+
if (s.username) {
|
|
4722
|
+
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
4723
|
+
}
|
|
4724
|
+
if (s.download !== void 0 || s.total !== void 0) {
|
|
4725
|
+
const used = (s.upload || 0) + (s.download || 0);
|
|
4726
|
+
const usedStr = formatBytes(used);
|
|
4727
|
+
const totalStr = formatBytes(s.total);
|
|
4728
|
+
let percentStr = "";
|
|
4729
|
+
if (s.total && s.total > 0) {
|
|
4730
|
+
const percent = Math.min(used / s.total * 100, 100);
|
|
4731
|
+
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
4732
|
+
}
|
|
4733
|
+
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
4734
|
+
}
|
|
4735
|
+
if (s.expire !== void 0) {
|
|
4736
|
+
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
4737
|
+
}
|
|
4738
|
+
if (s.web_page_url) {
|
|
4739
|
+
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
4740
|
+
}
|
|
4741
|
+
});
|
|
4742
|
+
console.log("");
|
|
4743
|
+
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
4744
|
+
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4745
|
+
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
4746
|
+
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
4747
|
+
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
4748
|
+
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
4749
|
+
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
4575
4750
|
console.log("");
|
|
4576
4751
|
}
|
|
4577
|
-
async function
|
|
4578
|
-
const action = args
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4752
|
+
async function cmdSubscription(args) {
|
|
4753
|
+
const action = args[1];
|
|
4754
|
+
if (!action || action === "list") {
|
|
4755
|
+
await printSubscriptionList();
|
|
4756
|
+
return;
|
|
4757
|
+
}
|
|
4758
|
+
if (action === "add") {
|
|
4759
|
+
const url = args[2];
|
|
4760
|
+
const name = args[3] || "default";
|
|
4761
|
+
if (!url?.startsWith("http")) {
|
|
4762
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
4763
|
+
process.exit(1);
|
|
4588
4764
|
}
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
await
|
|
4594
|
-
|
|
4765
|
+
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
4766
|
+
try {
|
|
4767
|
+
addSubscription(url, name);
|
|
4768
|
+
setDefaultSubscription(name);
|
|
4769
|
+
const info = await downloadSubscription(url, name);
|
|
4770
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4771
|
+
} catch (e) {
|
|
4772
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4773
|
+
process.exit(1);
|
|
4595
4774
|
}
|
|
4596
4775
|
console.log("");
|
|
4597
|
-
|
|
4776
|
+
await printSubscriptionList();
|
|
4598
4777
|
return;
|
|
4599
4778
|
}
|
|
4600
|
-
if (action === "
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4779
|
+
if (action === "update") {
|
|
4780
|
+
const name = args[2];
|
|
4781
|
+
const subs = getSubscriptions();
|
|
4782
|
+
if (subs.length === 0) {
|
|
4783
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4784
|
+
process.exit(1);
|
|
4606
4785
|
}
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4786
|
+
if (!name) {
|
|
4787
|
+
console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
|
|
4788
|
+
const results = await Promise.all(subs.map(tryUpdateOne));
|
|
4789
|
+
let ok = 0;
|
|
4790
|
+
for (const r of results) {
|
|
4791
|
+
if (r.success) {
|
|
4792
|
+
ok++;
|
|
4793
|
+
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4794
|
+
} else {
|
|
4795
|
+
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
if (ok === 0) process.exit(1);
|
|
4610
4799
|
console.log("");
|
|
4611
|
-
await
|
|
4800
|
+
await printSubscriptionList();
|
|
4612
4801
|
return;
|
|
4613
4802
|
}
|
|
4803
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4804
|
+
const target = pickSingleSubscription(matches, name);
|
|
4805
|
+
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
4806
|
+
try {
|
|
4807
|
+
const info = await downloadSubscription(target.url, target.name);
|
|
4808
|
+
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
|
|
4809
|
+
} catch (e) {
|
|
4810
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4811
|
+
process.exit(1);
|
|
4812
|
+
}
|
|
4614
4813
|
console.log("");
|
|
4615
|
-
|
|
4814
|
+
await printSubscriptionList();
|
|
4616
4815
|
return;
|
|
4617
4816
|
}
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4817
|
+
if (action === "use") {
|
|
4818
|
+
const name = args[2];
|
|
4819
|
+
const subs = getSubscriptions();
|
|
4820
|
+
if (!name) {
|
|
4821
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
4822
|
+
if (subs.length > 0) {
|
|
4823
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4824
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4825
|
+
}
|
|
4826
|
+
process.exit(1);
|
|
4827
|
+
}
|
|
4828
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4829
|
+
const target = pickSingleSubscription(matches, name);
|
|
4830
|
+
const currentDefault = getActiveSubscription();
|
|
4831
|
+
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
4832
|
+
if (isAlreadyDefault) {
|
|
4833
|
+
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
4834
|
+
console.log("");
|
|
4835
|
+
await printSubscriptionList();
|
|
4836
|
+
return;
|
|
4837
|
+
}
|
|
4838
|
+
const status = getStatus();
|
|
4839
|
+
const configInfo = getConfigInfo();
|
|
4840
|
+
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
4841
|
+
const success = setDefaultSubscription(target.name);
|
|
4842
|
+
if (success) {
|
|
4843
|
+
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
4844
|
+
} else {
|
|
4845
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
4846
|
+
process.exit(1);
|
|
4847
|
+
}
|
|
4848
|
+
if (status.running) {
|
|
4849
|
+
console.log("");
|
|
4850
|
+
await cmdStart(["start", currentMode]);
|
|
4851
|
+
return;
|
|
4852
|
+
}
|
|
4853
|
+
console.log("");
|
|
4854
|
+
await printSubscriptionList();
|
|
4855
|
+
return;
|
|
4856
|
+
}
|
|
4857
|
+
if (action === "web" || action === "open") {
|
|
4858
|
+
const name = args[2];
|
|
4859
|
+
const subs = getSubscriptionsWithCache();
|
|
4860
|
+
if (subs.length === 0) {
|
|
4861
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4862
|
+
process.exit(1);
|
|
4863
|
+
}
|
|
4864
|
+
let target;
|
|
4865
|
+
if (name) {
|
|
4866
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4867
|
+
target = pickSingleSubscription(matches, name);
|
|
4868
|
+
} else {
|
|
4869
|
+
target = subs[0];
|
|
4870
|
+
}
|
|
4871
|
+
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
4872
|
+
let webPageUrl = cached?.web_page_url;
|
|
4873
|
+
if (!webPageUrl) {
|
|
4874
|
+
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
4875
|
+
try {
|
|
4876
|
+
await downloadSubscription(target.url, target.name);
|
|
4877
|
+
const cache = readSubscriptionCache();
|
|
4878
|
+
if (cache[target.name]?.web_page_url) {
|
|
4879
|
+
webPageUrl = cache[target.name].web_page_url;
|
|
4880
|
+
} else {
|
|
4881
|
+
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
4882
|
+
process.exit(1);
|
|
4883
|
+
}
|
|
4884
|
+
} catch (e) {
|
|
4885
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4886
|
+
process.exit(1);
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
|
|
4890
|
+
const opened = openUrl(webPageUrl);
|
|
4891
|
+
if (!opened) {
|
|
4892
|
+
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
4893
|
+
}
|
|
4894
|
+
return;
|
|
4895
|
+
}
|
|
4896
|
+
if (action === "remove" || action === "rm" || action === "delete") {
|
|
4897
|
+
const name = args[2];
|
|
4898
|
+
const subs = getSubscriptions();
|
|
4899
|
+
if (!name) {
|
|
4900
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
|
|
4901
|
+
if (subs.length > 0) {
|
|
4902
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4903
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4904
|
+
}
|
|
4905
|
+
process.exit(1);
|
|
4906
|
+
}
|
|
4907
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
4908
|
+
const target = pickSingleSubscription(matches, name);
|
|
4909
|
+
const switchedTo = removeSubscription(target.name);
|
|
4910
|
+
console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
|
|
4911
|
+
if (switchedTo) {
|
|
4912
|
+
console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
|
|
4913
|
+
}
|
|
4914
|
+
console.log("");
|
|
4915
|
+
await printSubscriptionList({ autoUpdate: false });
|
|
4916
|
+
return;
|
|
4917
|
+
}
|
|
4918
|
+
if (action === "clean") {
|
|
4919
|
+
const { target, timeout, concurrency } = resolveActiveTestTarget(args);
|
|
4920
|
+
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
4921
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
4922
|
+
console.log("");
|
|
4923
|
+
const result = await autoCleanSubscription(target.name, {
|
|
4924
|
+
timeout,
|
|
4925
|
+
concurrency,
|
|
4926
|
+
onResult: printTestResult
|
|
4927
|
+
});
|
|
4928
|
+
console.log("");
|
|
4929
|
+
console.log(formatTestSummary(result.summary));
|
|
4930
|
+
if (result.skipped) {
|
|
4931
|
+
console.log("");
|
|
4932
|
+
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"));
|
|
4933
|
+
} else if (result.removedProxies > 0) {
|
|
4934
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
4935
|
+
console.log("");
|
|
4936
|
+
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
4937
|
+
}
|
|
4938
|
+
return;
|
|
4939
|
+
}
|
|
4940
|
+
if (action === "test") {
|
|
4941
|
+
const { target, timeout, concurrency } = resolveActiveTestTarget(args);
|
|
4942
|
+
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
4943
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
4944
|
+
console.log("");
|
|
4945
|
+
const summary = await testSubscriptionProxies(target.name, {
|
|
4946
|
+
timeout,
|
|
4947
|
+
concurrency,
|
|
4948
|
+
onResult: printTestResult
|
|
4949
|
+
});
|
|
4950
|
+
console.log("");
|
|
4951
|
+
console.log(formatTestSummary(summary));
|
|
4952
|
+
return;
|
|
4953
|
+
}
|
|
4954
|
+
console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
|
|
4955
|
+
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
|
|
4956
|
+
process.exit(1);
|
|
4957
|
+
}
|
|
4958
|
+
|
|
4959
|
+
// src/commands/start.ts
|
|
4960
|
+
var AUTO_CLEAN_THRESHOLD = 100;
|
|
4961
|
+
function handleStopResult(result) {
|
|
4962
|
+
if (result.remaining && result.remaining.length > 0) {
|
|
4963
|
+
console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
|
|
4964
|
+
console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
|
|
4965
|
+
process.exit(1);
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
async function cmdStart(args) {
|
|
4969
|
+
if (!hasKernel()) {
|
|
4970
|
+
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
4971
|
+
process.exit(1);
|
|
4972
|
+
}
|
|
4973
|
+
const targetMode = args[1] === "tun" ? "tun" : "mixed";
|
|
4974
|
+
const sub = getActiveSubscription();
|
|
4975
|
+
if (!sub) {
|
|
4976
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4977
|
+
process.exit(1);
|
|
4978
|
+
}
|
|
4979
|
+
await autoUpdateStaleSubscription();
|
|
4980
|
+
const status = getStatus();
|
|
4981
|
+
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
4982
|
+
if (hasProcess) {
|
|
4983
|
+
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
4984
|
+
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
4985
|
+
}
|
|
4986
|
+
handleStopResult(stop(true));
|
|
4987
|
+
if (hasProcess) {
|
|
4988
|
+
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
4989
|
+
`);
|
|
4990
|
+
}
|
|
4991
|
+
let configInfo;
|
|
4992
|
+
try {
|
|
4993
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
4994
|
+
} catch (e) {
|
|
4995
|
+
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
4996
|
+
process.exit(1);
|
|
4997
|
+
}
|
|
4998
|
+
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
4999
|
+
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
5000
|
+
try {
|
|
5001
|
+
const result = await start(targetMode);
|
|
5002
|
+
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
|
|
5003
|
+
} catch (e) {
|
|
5004
|
+
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
5005
|
+
process.exit(1);
|
|
5006
|
+
}
|
|
5007
|
+
if (configInfo.proxies > AUTO_CLEAN_THRESHOLD) {
|
|
5008
|
+
console.log("");
|
|
5009
|
+
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${AUTO_CLEAN_THRESHOLD}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
|
|
5010
|
+
console.log("");
|
|
5011
|
+
await sleep(1e3);
|
|
5012
|
+
const cleanResult = await autoCleanSubscription(sub.name, { onResult: printTestResult });
|
|
5013
|
+
console.log("");
|
|
5014
|
+
console.log(formatTestSummary(cleanResult.summary));
|
|
5015
|
+
if (cleanResult.skipped) {
|
|
5016
|
+
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"));
|
|
5017
|
+
} else if (cleanResult.removedProxies > 0) {
|
|
5018
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
5019
|
+
console.log("");
|
|
5020
|
+
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
5021
|
+
handleStopResult(stop(true));
|
|
5022
|
+
try {
|
|
5023
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
5024
|
+
const result = await start(targetMode);
|
|
5025
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${result.pid}) \xB7 ${formatProxySummary(configInfo)}`);
|
|
5026
|
+
} catch (e) {
|
|
5027
|
+
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
5028
|
+
process.exit(1);
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
printStatus();
|
|
5033
|
+
}
|
|
5034
|
+
|
|
5035
|
+
// src/commands/overwrite.ts
|
|
5036
|
+
function printOverwriteList() {
|
|
5037
|
+
const info = listOverwriteFile();
|
|
5038
|
+
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
5039
|
+
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}`);
|
|
5040
|
+
console.log(`${colors.gray("\u4F4D\u7F6E: ")}${info.dir}`);
|
|
5041
|
+
console.log("");
|
|
5042
|
+
if (info.files.length === 0) {
|
|
5043
|
+
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
5044
|
+
console.log("");
|
|
5045
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path5.join(info.dir, "overwrite.yaml")}`);
|
|
5046
|
+
console.log(` \u6216 ${path5.join(info.dir, "overwrite.dns.yaml")}`);
|
|
5047
|
+
console.log("");
|
|
5048
|
+
} else {
|
|
5049
|
+
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
5050
|
+
console.log("");
|
|
5051
|
+
info.files.forEach((f, i) => {
|
|
5052
|
+
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
5053
|
+
console.log(` ${num}. ${f.name}`);
|
|
5054
|
+
if (f.keys.length > 0) {
|
|
5055
|
+
console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
|
|
5056
|
+
}
|
|
5057
|
+
});
|
|
5058
|
+
console.log("");
|
|
5059
|
+
}
|
|
5060
|
+
console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
|
|
5061
|
+
console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
|
|
5062
|
+
console.log("");
|
|
5063
|
+
}
|
|
5064
|
+
async function cmdOverwrite(args) {
|
|
5065
|
+
const action = args?.[1];
|
|
5066
|
+
const status = getStatus();
|
|
5067
|
+
const configInfo = getConfigInfo();
|
|
5068
|
+
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
5069
|
+
if (action === "on" || action === "enable") {
|
|
5070
|
+
if (isOverwriteEnabled()) {
|
|
5071
|
+
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
|
|
5072
|
+
console.log("");
|
|
5073
|
+
printOverwriteList();
|
|
5074
|
+
return;
|
|
5075
|
+
}
|
|
5076
|
+
setOverwriteEnabled(true);
|
|
5077
|
+
console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
|
|
5078
|
+
if (status.running) {
|
|
5079
|
+
console.log("");
|
|
5080
|
+
await cmdStart(["start", currentMode]);
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
console.log("");
|
|
5084
|
+
printOverwriteList();
|
|
5085
|
+
return;
|
|
5086
|
+
}
|
|
5087
|
+
if (action === "off" || action === "disable") {
|
|
5088
|
+
if (!isOverwriteEnabled()) {
|
|
5089
|
+
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
|
|
5090
|
+
console.log("");
|
|
5091
|
+
printOverwriteList();
|
|
5092
|
+
return;
|
|
5093
|
+
}
|
|
5094
|
+
setOverwriteEnabled(false);
|
|
5095
|
+
console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
|
|
5096
|
+
if (status.running) {
|
|
5097
|
+
console.log("");
|
|
5098
|
+
await cmdStart(["start", currentMode]);
|
|
5099
|
+
return;
|
|
5100
|
+
}
|
|
5101
|
+
console.log("");
|
|
5102
|
+
printOverwriteList();
|
|
5103
|
+
return;
|
|
5104
|
+
}
|
|
5105
|
+
console.log("");
|
|
5106
|
+
printOverwriteList();
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5109
|
+
// src/commands/reset.ts
|
|
5110
|
+
import fs7 from "fs";
|
|
5111
|
+
import readline from "readline";
|
|
5112
|
+
var RESET_TARGETS = [
|
|
5113
|
+
{
|
|
5114
|
+
id: "subs",
|
|
5115
|
+
aliases: ["sub", "subs", "subscription", "subscriptions"],
|
|
5116
|
+
label: "\u8BA2\u9605",
|
|
5117
|
+
paths: () => [DIRS.subscriptions],
|
|
5118
|
+
needsStop: true
|
|
5119
|
+
},
|
|
5120
|
+
{
|
|
5121
|
+
id: "logs",
|
|
5122
|
+
aliases: ["log", "logs"],
|
|
5123
|
+
label: "\u65E5\u5FD7",
|
|
5124
|
+
paths: () => [DIRS.logs],
|
|
5125
|
+
needsStop: false
|
|
5126
|
+
},
|
|
5127
|
+
{
|
|
5128
|
+
id: "data",
|
|
4642
5129
|
aliases: ["data"],
|
|
4643
5130
|
label: "\u8FD0\u884C\u6570\u636E",
|
|
4644
5131
|
paths: () => [DIRS.data],
|
|
@@ -4792,228 +5279,6 @@ async function cmdStop() {
|
|
|
4792
5279
|
console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
|
|
4793
5280
|
}
|
|
4794
5281
|
|
|
4795
|
-
// src/commands/subscription.ts
|
|
4796
|
-
async function printSubscriptionList(options) {
|
|
4797
|
-
if (options?.autoUpdate !== false) {
|
|
4798
|
-
const updateResult = await autoUpdateStaleSubscription();
|
|
4799
|
-
if (updateResult.total > 0) console.log("");
|
|
4800
|
-
}
|
|
4801
|
-
const subs = getSubscriptionsWithCache();
|
|
4802
|
-
if (subs.length === 0) {
|
|
4803
|
-
console.log("\u6CA1\u6709\u8BA2\u9605");
|
|
4804
|
-
console.log("");
|
|
4805
|
-
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4806
|
-
console.log("");
|
|
4807
|
-
return;
|
|
4808
|
-
}
|
|
4809
|
-
const activeSub = getActiveSubscription();
|
|
4810
|
-
console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
|
|
4811
|
-
subs.forEach((s, i) => {
|
|
4812
|
-
const time = formatDate(s.updated_at);
|
|
4813
|
-
const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
|
|
4814
|
-
const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4815
|
-
console.log(` ${i + 1}. ${s.name}${defaultMark}`);
|
|
4816
|
-
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
4817
|
-
if (s.username) {
|
|
4818
|
-
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
4819
|
-
}
|
|
4820
|
-
if (s.download !== void 0 || s.total !== void 0) {
|
|
4821
|
-
const used = (s.upload || 0) + (s.download || 0);
|
|
4822
|
-
const usedStr = formatBytes(used);
|
|
4823
|
-
const totalStr = formatBytes(s.total);
|
|
4824
|
-
let percentStr = "";
|
|
4825
|
-
if (s.total && s.total > 0) {
|
|
4826
|
-
const percent = Math.min(used / s.total * 100, 100);
|
|
4827
|
-
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
4828
|
-
}
|
|
4829
|
-
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
4830
|
-
}
|
|
4831
|
-
if (s.expire !== void 0) {
|
|
4832
|
-
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
4833
|
-
}
|
|
4834
|
-
if (s.web_page_url) {
|
|
4835
|
-
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
4836
|
-
}
|
|
4837
|
-
});
|
|
4838
|
-
console.log("");
|
|
4839
|
-
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
4840
|
-
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4841
|
-
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
4842
|
-
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
4843
|
-
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
4844
|
-
console.log("");
|
|
4845
|
-
}
|
|
4846
|
-
async function cmdSubscription(args) {
|
|
4847
|
-
const action = args[1];
|
|
4848
|
-
if (!action || action === "list") {
|
|
4849
|
-
await printSubscriptionList();
|
|
4850
|
-
return;
|
|
4851
|
-
}
|
|
4852
|
-
if (action === "add") {
|
|
4853
|
-
const url = args[2];
|
|
4854
|
-
const name = args[3] || "default";
|
|
4855
|
-
if (!url?.startsWith("http")) {
|
|
4856
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
4857
|
-
process.exit(1);
|
|
4858
|
-
}
|
|
4859
|
-
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
4860
|
-
try {
|
|
4861
|
-
addSubscription(url, name);
|
|
4862
|
-
setDefaultSubscription(name);
|
|
4863
|
-
const info = await downloadSubscription(url, name);
|
|
4864
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4865
|
-
} catch (e) {
|
|
4866
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4867
|
-
process.exit(1);
|
|
4868
|
-
}
|
|
4869
|
-
console.log("");
|
|
4870
|
-
await printSubscriptionList();
|
|
4871
|
-
return;
|
|
4872
|
-
}
|
|
4873
|
-
if (action === "update") {
|
|
4874
|
-
const name = args[2];
|
|
4875
|
-
const subs = getSubscriptions();
|
|
4876
|
-
if (subs.length === 0) {
|
|
4877
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4878
|
-
process.exit(1);
|
|
4879
|
-
}
|
|
4880
|
-
if (!name) {
|
|
4881
|
-
console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
|
|
4882
|
-
const results = await Promise.all(subs.map(tryUpdateOne));
|
|
4883
|
-
let ok = 0;
|
|
4884
|
-
for (const r of results) {
|
|
4885
|
-
if (r.success) {
|
|
4886
|
-
ok++;
|
|
4887
|
-
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4888
|
-
} else {
|
|
4889
|
-
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
4890
|
-
}
|
|
4891
|
-
}
|
|
4892
|
-
if (ok === 0) process.exit(1);
|
|
4893
|
-
console.log("");
|
|
4894
|
-
await printSubscriptionList();
|
|
4895
|
-
return;
|
|
4896
|
-
}
|
|
4897
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
4898
|
-
const target = pickSingleSubscription(matches, name);
|
|
4899
|
-
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
4900
|
-
try {
|
|
4901
|
-
const info = await downloadSubscription(target.url, target.name);
|
|
4902
|
-
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
|
|
4903
|
-
} catch (e) {
|
|
4904
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4905
|
-
process.exit(1);
|
|
4906
|
-
}
|
|
4907
|
-
console.log("");
|
|
4908
|
-
await printSubscriptionList();
|
|
4909
|
-
return;
|
|
4910
|
-
}
|
|
4911
|
-
if (action === "use") {
|
|
4912
|
-
const name = args[2];
|
|
4913
|
-
const subs = getSubscriptions();
|
|
4914
|
-
if (!name) {
|
|
4915
|
-
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
4916
|
-
if (subs.length > 0) {
|
|
4917
|
-
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4918
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
4919
|
-
}
|
|
4920
|
-
process.exit(1);
|
|
4921
|
-
}
|
|
4922
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
4923
|
-
const target = pickSingleSubscription(matches, name);
|
|
4924
|
-
const currentDefault = getActiveSubscription();
|
|
4925
|
-
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
4926
|
-
if (isAlreadyDefault) {
|
|
4927
|
-
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
4928
|
-
console.log("");
|
|
4929
|
-
await printSubscriptionList();
|
|
4930
|
-
return;
|
|
4931
|
-
}
|
|
4932
|
-
const status = getStatus();
|
|
4933
|
-
const configInfo = getConfigInfo();
|
|
4934
|
-
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
4935
|
-
const success = setDefaultSubscription(target.name);
|
|
4936
|
-
if (success) {
|
|
4937
|
-
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
4938
|
-
} else {
|
|
4939
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
4940
|
-
process.exit(1);
|
|
4941
|
-
}
|
|
4942
|
-
if (status.running) {
|
|
4943
|
-
console.log("");
|
|
4944
|
-
await cmdStart(["start", currentMode]);
|
|
4945
|
-
return;
|
|
4946
|
-
}
|
|
4947
|
-
console.log("");
|
|
4948
|
-
await printSubscriptionList();
|
|
4949
|
-
return;
|
|
4950
|
-
}
|
|
4951
|
-
if (action === "web" || action === "open") {
|
|
4952
|
-
const name = args[2];
|
|
4953
|
-
const subs = getSubscriptionsWithCache();
|
|
4954
|
-
if (subs.length === 0) {
|
|
4955
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
4956
|
-
process.exit(1);
|
|
4957
|
-
}
|
|
4958
|
-
let target;
|
|
4959
|
-
if (name) {
|
|
4960
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
4961
|
-
target = pickSingleSubscription(matches, name);
|
|
4962
|
-
} else {
|
|
4963
|
-
target = subs[0];
|
|
4964
|
-
}
|
|
4965
|
-
const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
|
|
4966
|
-
let webPageUrl = cached?.web_page_url;
|
|
4967
|
-
if (!webPageUrl) {
|
|
4968
|
-
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
4969
|
-
try {
|
|
4970
|
-
await downloadSubscription(target.url, target.name);
|
|
4971
|
-
const cache = readSubscriptionCache();
|
|
4972
|
-
if (cache[target.name]?.web_page_url) {
|
|
4973
|
-
webPageUrl = cache[target.name].web_page_url;
|
|
4974
|
-
} else {
|
|
4975
|
-
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
4976
|
-
process.exit(1);
|
|
4977
|
-
}
|
|
4978
|
-
} catch (e) {
|
|
4979
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4980
|
-
process.exit(1);
|
|
4981
|
-
}
|
|
4982
|
-
}
|
|
4983
|
-
console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
|
|
4984
|
-
const opened = openUrl(webPageUrl);
|
|
4985
|
-
if (!opened) {
|
|
4986
|
-
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
4987
|
-
}
|
|
4988
|
-
return;
|
|
4989
|
-
}
|
|
4990
|
-
if (action === "remove" || action === "rm" || action === "delete") {
|
|
4991
|
-
const name = args[2];
|
|
4992
|
-
const subs = getSubscriptions();
|
|
4993
|
-
if (!name) {
|
|
4994
|
-
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
|
|
4995
|
-
if (subs.length > 0) {
|
|
4996
|
-
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
4997
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
4998
|
-
}
|
|
4999
|
-
process.exit(1);
|
|
5000
|
-
}
|
|
5001
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
5002
|
-
const target = pickSingleSubscription(matches, name);
|
|
5003
|
-
const switchedTo = removeSubscription(target.name);
|
|
5004
|
-
console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
|
|
5005
|
-
if (switchedTo) {
|
|
5006
|
-
console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
|
|
5007
|
-
}
|
|
5008
|
-
console.log("");
|
|
5009
|
-
await printSubscriptionList({ autoUpdate: false });
|
|
5010
|
-
return;
|
|
5011
|
-
}
|
|
5012
|
-
console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
|
|
5013
|
-
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web]");
|
|
5014
|
-
process.exit(1);
|
|
5015
|
-
}
|
|
5016
|
-
|
|
5017
5282
|
// src/commands/ui.ts
|
|
5018
5283
|
function cmdUI(args) {
|
|
5019
5284
|
const uiName = args[1] || "zash";
|