mihomo-cli 2.1.0 → 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 +14 -0
- package/README.md +6 -2
- package/dist/index.js +562 -308
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
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
|
+
|
|
3
17
|
## [2.1.0] - 2026-05-01
|
|
4
18
|
|
|
5
19
|
### 新增
|
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,25 @@ 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
|
+
parsed.raw.proxies = parsed.proxies;
|
|
4297
|
+
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4298
|
+
saveSubscriptionRawConfig(subName, jsYaml.dump(parsed.raw, YAML_DUMP_OPTS));
|
|
4299
|
+
}
|
|
4272
4300
|
function parseUserInfo(header) {
|
|
4273
4301
|
if (!header) return null;
|
|
4274
4302
|
const info = {};
|
|
@@ -4443,6 +4471,122 @@ async function autoUpdateStaleSubscription() {
|
|
|
4443
4471
|
}
|
|
4444
4472
|
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4445
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
|
+
}
|
|
4446
4590
|
|
|
4447
4591
|
// src/commands/status.ts
|
|
4448
4592
|
function printStatus() {
|
|
@@ -4495,124 +4639,456 @@ function printStatus() {
|
|
|
4495
4639
|
console.log("");
|
|
4496
4640
|
}
|
|
4497
4641
|
|
|
4498
|
-
// src/commands/
|
|
4499
|
-
function
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
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")}`);
|
|
4504
4650
|
}
|
|
4505
4651
|
}
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
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");
|
|
4509
4665
|
process.exit(1);
|
|
4510
4666
|
}
|
|
4511
|
-
const
|
|
4512
|
-
const
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
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;
|
|
4516
4681
|
}
|
|
4517
|
-
await autoUpdateStaleSubscription();
|
|
4518
4682
|
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}`);
|
|
4683
|
+
if (!status.running) {
|
|
4684
|
+
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
4534
4685
|
process.exit(1);
|
|
4535
4686
|
}
|
|
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]}`);
|
|
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}`);
|
|
4544
4690
|
process.exit(1);
|
|
4545
4691
|
}
|
|
4692
|
+
return { target, timeout, concurrency };
|
|
4546
4693
|
}
|
|
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):`);
|
|
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");
|
|
4563
4702
|
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
|
-
});
|
|
4703
|
+
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
4571
4704
|
console.log("");
|
|
4705
|
+
return;
|
|
4572
4706
|
}
|
|
4573
|
-
|
|
4574
|
-
console.log("\
|
|
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]");
|
|
4575
4744
|
console.log("");
|
|
4576
4745
|
}
|
|
4577
|
-
async function
|
|
4578
|
-
const action = args
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
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);
|
|
4588
4758
|
}
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
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);
|
|
4592
4793
|
console.log("");
|
|
4593
|
-
await
|
|
4794
|
+
await printSubscriptionList();
|
|
4594
4795
|
return;
|
|
4595
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
|
+
}
|
|
4596
4807
|
console.log("");
|
|
4597
|
-
|
|
4808
|
+
await printSubscriptionList();
|
|
4598
4809
|
return;
|
|
4599
4810
|
}
|
|
4600
|
-
if (action === "
|
|
4601
|
-
|
|
4602
|
-
|
|
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`);
|
|
4603
4828
|
console.log("");
|
|
4604
|
-
|
|
4829
|
+
await printSubscriptionList();
|
|
4605
4830
|
return;
|
|
4606
4831
|
}
|
|
4607
|
-
|
|
4608
|
-
|
|
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
|
+
}
|
|
4609
4842
|
if (status.running) {
|
|
4610
4843
|
console.log("");
|
|
4611
4844
|
await cmdStart(["start", currentMode]);
|
|
4612
4845
|
return;
|
|
4613
4846
|
}
|
|
4614
4847
|
console.log("");
|
|
4615
|
-
|
|
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
|
+
|
|
4950
|
+
// src/commands/start.ts
|
|
4951
|
+
var AUTO_CLEAN_THRESHOLD = 100;
|
|
4952
|
+
function handleStopResult(result) {
|
|
4953
|
+
if (result.remaining && result.remaining.length > 0) {
|
|
4954
|
+
console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
|
|
4955
|
+
console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
|
|
4956
|
+
process.exit(1);
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
async function cmdStart(args) {
|
|
4960
|
+
if (!hasKernel()) {
|
|
4961
|
+
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
4962
|
+
process.exit(1);
|
|
4963
|
+
}
|
|
4964
|
+
const targetMode = args[1] === "tun" ? "tun" : "mixed";
|
|
4965
|
+
const sub = getActiveSubscription();
|
|
4966
|
+
if (!sub) {
|
|
4967
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4968
|
+
process.exit(1);
|
|
4969
|
+
}
|
|
4970
|
+
await autoUpdateStaleSubscription();
|
|
4971
|
+
const status = getStatus();
|
|
4972
|
+
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
4973
|
+
if (hasProcess) {
|
|
4974
|
+
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
4975
|
+
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
4976
|
+
}
|
|
4977
|
+
handleStopResult(stop(true));
|
|
4978
|
+
if (hasProcess) {
|
|
4979
|
+
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
4980
|
+
`);
|
|
4981
|
+
}
|
|
4982
|
+
let configInfo;
|
|
4983
|
+
try {
|
|
4984
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
4985
|
+
} catch (e) {
|
|
4986
|
+
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
4987
|
+
process.exit(1);
|
|
4988
|
+
}
|
|
4989
|
+
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
4990
|
+
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
4991
|
+
try {
|
|
4992
|
+
const result = await start(targetMode);
|
|
4993
|
+
console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
|
|
4994
|
+
} catch (e) {
|
|
4995
|
+
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
4996
|
+
process.exit(1);
|
|
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();
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
// src/commands/overwrite.ts
|
|
5025
|
+
function printOverwriteList() {
|
|
5026
|
+
const info = listOverwriteFile();
|
|
5027
|
+
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
5028
|
+
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}`);
|
|
5029
|
+
console.log(`${colors.gray("\u4F4D\u7F6E: ")}${info.dir}`);
|
|
5030
|
+
console.log("");
|
|
5031
|
+
if (info.files.length === 0) {
|
|
5032
|
+
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
5033
|
+
console.log("");
|
|
5034
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path5.join(info.dir, "overwrite.yaml")}`);
|
|
5035
|
+
console.log(` \u6216 ${path5.join(info.dir, "overwrite.dns.yaml")}`);
|
|
5036
|
+
console.log("");
|
|
5037
|
+
} else {
|
|
5038
|
+
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
5039
|
+
console.log("");
|
|
5040
|
+
info.files.forEach((f, i) => {
|
|
5041
|
+
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
5042
|
+
console.log(` ${num}. ${f.name}`);
|
|
5043
|
+
if (f.keys.length > 0) {
|
|
5044
|
+
console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
|
|
5045
|
+
}
|
|
5046
|
+
});
|
|
5047
|
+
console.log("");
|
|
5048
|
+
}
|
|
5049
|
+
console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
|
|
5050
|
+
console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
|
|
5051
|
+
console.log("");
|
|
5052
|
+
}
|
|
5053
|
+
async function cmdOverwrite(args) {
|
|
5054
|
+
const action = args?.[1];
|
|
5055
|
+
const status = getStatus();
|
|
5056
|
+
const configInfo = getConfigInfo();
|
|
5057
|
+
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
5058
|
+
if (action === "on" || action === "enable") {
|
|
5059
|
+
if (isOverwriteEnabled()) {
|
|
5060
|
+
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
|
|
5061
|
+
console.log("");
|
|
5062
|
+
printOverwriteList();
|
|
5063
|
+
return;
|
|
5064
|
+
}
|
|
5065
|
+
setOverwriteEnabled(true);
|
|
5066
|
+
console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
|
|
5067
|
+
if (status.running) {
|
|
5068
|
+
console.log("");
|
|
5069
|
+
await cmdStart(["start", currentMode]);
|
|
5070
|
+
return;
|
|
5071
|
+
}
|
|
5072
|
+
console.log("");
|
|
5073
|
+
printOverwriteList();
|
|
5074
|
+
return;
|
|
5075
|
+
}
|
|
5076
|
+
if (action === "off" || action === "disable") {
|
|
5077
|
+
if (!isOverwriteEnabled()) {
|
|
5078
|
+
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
|
|
5079
|
+
console.log("");
|
|
5080
|
+
printOverwriteList();
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
setOverwriteEnabled(false);
|
|
5084
|
+
console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
|
|
5085
|
+
if (status.running) {
|
|
5086
|
+
console.log("");
|
|
5087
|
+
await cmdStart(["start", currentMode]);
|
|
5088
|
+
return;
|
|
5089
|
+
}
|
|
5090
|
+
console.log("");
|
|
5091
|
+
printOverwriteList();
|
|
4616
5092
|
return;
|
|
4617
5093
|
}
|
|
4618
5094
|
console.log("");
|
|
@@ -4792,228 +5268,6 @@ async function cmdStop() {
|
|
|
4792
5268
|
console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
|
|
4793
5269
|
}
|
|
4794
5270
|
|
|
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
5271
|
// src/commands/ui.ts
|
|
5018
5272
|
function cmdUI(args) {
|
|
5019
5273
|
const uiName = args[1] || "zash";
|