@napuketto/loader 0.0.14 → 0.0.17
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/dist/host/self-host.cjs +218 -35
- package/dist/index.cjs +185 -2
- package/dist/index.d.cts +343 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +343 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +175 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
- package/qq-releases.json +8 -0
package/dist/host/self-host.cjs
CHANGED
|
@@ -25,6 +25,7 @@ let node_path = require("node:path");
|
|
|
25
25
|
let node_process = require("node:process");
|
|
26
26
|
node_process = __toESM(node_process, 1);
|
|
27
27
|
let node_os = require("node:os");
|
|
28
|
+
let zod = require("zod");
|
|
28
29
|
let node_readline = require("node:readline");
|
|
29
30
|
//#region src/instance-lock.ts
|
|
30
31
|
/**
|
|
@@ -459,20 +460,156 @@ function errorCodeOf(err) {
|
|
|
459
460
|
}
|
|
460
461
|
return "UNKNOWN";
|
|
461
462
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
"
|
|
467
|
-
"
|
|
468
|
-
"
|
|
469
|
-
"
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
"
|
|
474
|
-
"
|
|
475
|
-
"
|
|
463
|
+
/** 引导阶段(status 消息)。 */
|
|
464
|
+
const IpcBootPhaseSchema = zod.z.enum([
|
|
465
|
+
"booting",
|
|
466
|
+
"dlopening",
|
|
467
|
+
"logging",
|
|
468
|
+
"sessioning",
|
|
469
|
+
"ready",
|
|
470
|
+
"failed"
|
|
471
|
+
]);
|
|
472
|
+
/** 结构化日志级别(pino level)。 */
|
|
473
|
+
const IpcLogLevelSchema = zod.z.enum([
|
|
474
|
+
"trace",
|
|
475
|
+
"debug",
|
|
476
|
+
"info",
|
|
477
|
+
"warn",
|
|
478
|
+
"error",
|
|
479
|
+
"fatal"
|
|
480
|
+
]);
|
|
481
|
+
/** 登录状态(与 kernel LoginState 结构等价)。 */
|
|
482
|
+
const LoginStateSchema = zod.z.enum([
|
|
483
|
+
"idle",
|
|
484
|
+
"waiting_scan",
|
|
485
|
+
"scanned",
|
|
486
|
+
"logged_in",
|
|
487
|
+
"failed"
|
|
488
|
+
]);
|
|
489
|
+
/** selfInfo(与 kernel SelfInfo 结构等价)。 */
|
|
490
|
+
const SelfInfoSchema = zod.z.object({
|
|
491
|
+
uin: zod.z.string(),
|
|
492
|
+
uid: zod.z.string(),
|
|
493
|
+
nick: zod.z.string()
|
|
494
|
+
});
|
|
495
|
+
/** status 消息 payload。 */
|
|
496
|
+
const IpcStatusPayloadSchema = zod.z.object({
|
|
497
|
+
phase: IpcBootPhaseSchema,
|
|
498
|
+
message: zod.z.string().optional(),
|
|
499
|
+
error: zod.z.object({
|
|
500
|
+
code: zod.z.string(),
|
|
501
|
+
message: zod.z.string()
|
|
502
|
+
}).optional()
|
|
503
|
+
});
|
|
504
|
+
/** login 消息 payload。 */
|
|
505
|
+
const IpcLoginPayloadSchema = zod.z.object({
|
|
506
|
+
state: LoginStateSchema,
|
|
507
|
+
selfInfo: SelfInfoSchema.optional(),
|
|
508
|
+
/** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */
|
|
509
|
+
message: zod.z.string().optional()
|
|
510
|
+
});
|
|
511
|
+
/** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */
|
|
512
|
+
const IpcEventPayloadSchema = zod.z.object({
|
|
513
|
+
service: zod.z.string(),
|
|
514
|
+
name: zod.z.string(),
|
|
515
|
+
args: zod.z.array(zod.z.unknown())
|
|
516
|
+
});
|
|
517
|
+
/** log 消息 payload。 */
|
|
518
|
+
const IpcLogPayloadSchema = zod.z.object({
|
|
519
|
+
level: IpcLogLevelSchema,
|
|
520
|
+
message: zod.z.string()
|
|
521
|
+
});
|
|
522
|
+
/** 动作响应 payload(result 消息)。 */
|
|
523
|
+
const IpcResultPayloadSchema = zod.z.union([zod.z.object({
|
|
524
|
+
ok: zod.z.literal(true),
|
|
525
|
+
value: zod.z.unknown().optional()
|
|
526
|
+
}), zod.z.object({
|
|
527
|
+
ok: zod.z.literal(false),
|
|
528
|
+
error: zod.z.object({
|
|
529
|
+
code: zod.z.string(),
|
|
530
|
+
message: zod.z.string()
|
|
531
|
+
})
|
|
532
|
+
})]);
|
|
533
|
+
/** 动作请求 payload(action 消息)。 */
|
|
534
|
+
const IpcActionPayloadSchema = zod.z.object({
|
|
535
|
+
action: zod.z.string(),
|
|
536
|
+
params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
537
|
+
});
|
|
538
|
+
/** 控制指令 payload(control 消息,父→子)。 */
|
|
539
|
+
const IpcControlPayloadSchema = zod.z.union([
|
|
540
|
+
zod.z.object({ command: zod.z.literal("stop") }),
|
|
541
|
+
zod.z.object({ command: zod.z.literal("restart") }),
|
|
542
|
+
zod.z.object({
|
|
543
|
+
command: zod.z.literal("login"),
|
|
544
|
+
uin: zod.z.string().optional(),
|
|
545
|
+
qr: zod.z.boolean().optional()
|
|
546
|
+
})
|
|
547
|
+
]);
|
|
548
|
+
const IpcStatusMessageSchema = zod.z.object({
|
|
549
|
+
v: zod.z.literal(1),
|
|
550
|
+
type: zod.z.literal("status"),
|
|
551
|
+
payload: IpcStatusPayloadSchema
|
|
552
|
+
});
|
|
553
|
+
const IpcLoginMessageSchema = zod.z.object({
|
|
554
|
+
v: zod.z.literal(1),
|
|
555
|
+
type: zod.z.literal("login"),
|
|
556
|
+
payload: IpcLoginPayloadSchema
|
|
557
|
+
});
|
|
558
|
+
const IpcQrMessageSchema = zod.z.object({
|
|
559
|
+
v: zod.z.literal(1),
|
|
560
|
+
type: zod.z.literal("qr"),
|
|
561
|
+
payload: zod.z.object({
|
|
562
|
+
pngBase64: zod.z.string(),
|
|
563
|
+
qrcodeUrl: zod.z.string()
|
|
564
|
+
})
|
|
565
|
+
});
|
|
566
|
+
const IpcEventMessageSchema = zod.z.object({
|
|
567
|
+
v: zod.z.literal(1),
|
|
568
|
+
type: zod.z.literal("event"),
|
|
569
|
+
payload: IpcEventPayloadSchema
|
|
570
|
+
});
|
|
571
|
+
const IpcResultMessageSchema = zod.z.object({
|
|
572
|
+
v: zod.z.literal(1),
|
|
573
|
+
type: zod.z.literal("result"),
|
|
574
|
+
id: zod.z.number(),
|
|
575
|
+
payload: IpcResultPayloadSchema
|
|
576
|
+
});
|
|
577
|
+
const IpcLogMessageSchema = zod.z.object({
|
|
578
|
+
v: zod.z.literal(1),
|
|
579
|
+
type: zod.z.literal("log"),
|
|
580
|
+
payload: IpcLogPayloadSchema
|
|
581
|
+
});
|
|
582
|
+
const IpcPingMessageSchema = zod.z.object({
|
|
583
|
+
v: zod.z.literal(1),
|
|
584
|
+
type: zod.z.literal("ping")
|
|
585
|
+
});
|
|
586
|
+
const IpcPongMessageSchema = zod.z.object({
|
|
587
|
+
v: zod.z.literal(1),
|
|
588
|
+
type: zod.z.literal("pong")
|
|
589
|
+
});
|
|
590
|
+
const IpcActionMessageSchema = zod.z.object({
|
|
591
|
+
v: zod.z.literal(1),
|
|
592
|
+
type: zod.z.literal("action"),
|
|
593
|
+
id: zod.z.number(),
|
|
594
|
+
payload: IpcActionPayloadSchema
|
|
595
|
+
});
|
|
596
|
+
const IpcControlMessageSchema = zod.z.object({
|
|
597
|
+
v: zod.z.literal(1),
|
|
598
|
+
type: zod.z.literal("control"),
|
|
599
|
+
payload: IpcControlPayloadSchema
|
|
600
|
+
});
|
|
601
|
+
/** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */
|
|
602
|
+
const IpcMessageSchema = zod.z.discriminatedUnion("type", [
|
|
603
|
+
IpcStatusMessageSchema,
|
|
604
|
+
IpcLoginMessageSchema,
|
|
605
|
+
IpcQrMessageSchema,
|
|
606
|
+
IpcEventMessageSchema,
|
|
607
|
+
IpcResultMessageSchema,
|
|
608
|
+
IpcLogMessageSchema,
|
|
609
|
+
IpcPingMessageSchema,
|
|
610
|
+
IpcPongMessageSchema,
|
|
611
|
+
IpcActionMessageSchema,
|
|
612
|
+
IpcControlMessageSchema
|
|
476
613
|
]);
|
|
477
614
|
//#endregion
|
|
478
615
|
//#region src/host/ipc/ipc-sender.ts
|
|
@@ -615,22 +752,18 @@ function attachIpcServices(actions, services) {
|
|
|
615
752
|
/**
|
|
616
753
|
* ipc-codec.ts:IPC 消息编解码(JSON 行协议,与 koishi 插件侧 src/ipc/codec.ts 对齐)。
|
|
617
754
|
*
|
|
618
|
-
* 每行一条 JSON 消息,`\n`
|
|
619
|
-
*
|
|
755
|
+
* 每行一条 JSON 消息,`\n` 结尾。解码用 IpcMessageSchema.safeParse 校验:
|
|
756
|
+
* 非法行 / 空行 / v 不匹配 / type 未知 / payload 形状非法 → null,调用方忽略,
|
|
757
|
+
* 不崩通道。
|
|
620
758
|
*/
|
|
621
|
-
/**
|
|
622
|
-
function isIpcMessage(value) {
|
|
623
|
-
if (typeof value !== "object" || value === null) return false;
|
|
624
|
-
const record = value;
|
|
625
|
-
return record["v"] === 1 && typeof record["type"] === "string" && IPC_MESSAGE_TYPES.has(record["type"]);
|
|
626
|
-
}
|
|
627
|
-
/** 解码一行 JSON;非法行/空行返回 null。 */
|
|
759
|
+
/** 解码一行 JSON;非法行/空行/形状不合法返回 null。 */
|
|
628
760
|
function decodeIpcMessage(line) {
|
|
629
761
|
const trimmed = line.trim();
|
|
630
762
|
if (trimmed === "") return null;
|
|
631
763
|
try {
|
|
632
764
|
const parsed = JSON.parse(trimmed);
|
|
633
|
-
|
|
765
|
+
const result = IpcMessageSchema.safeParse(parsed);
|
|
766
|
+
return result.success ? result.data : null;
|
|
634
767
|
} catch {
|
|
635
768
|
return null;
|
|
636
769
|
}
|
|
@@ -644,6 +777,7 @@ function decodeIpcMessage(line) {
|
|
|
644
777
|
* - stdin readline 收 action / control / ping
|
|
645
778
|
* - action → 动作表执行 → result 响应(请求 id 匹配)
|
|
646
779
|
* - control stop/restart → 退出回调(默认 process.exit(0),由驱动层重启)
|
|
780
|
+
* - control login → onLogin 回调(触发重新登录 / 强制扫码)
|
|
647
781
|
* - ping → 自动回 pong
|
|
648
782
|
* - 心跳:定期发 ping(koishi 插件探活)
|
|
649
783
|
*/
|
|
@@ -665,7 +799,7 @@ function startIpcServer(options) {
|
|
|
665
799
|
handleAction(message.id, message.payload.action, message.payload.params, options.actions);
|
|
666
800
|
break;
|
|
667
801
|
case "control":
|
|
668
|
-
handleControl(message.payload, onExit);
|
|
802
|
+
handleControl(message.payload, onExit, options.onLogin);
|
|
669
803
|
break;
|
|
670
804
|
case "ping": sendPong();
|
|
671
805
|
}
|
|
@@ -680,9 +814,16 @@ function startIpcServer(options) {
|
|
|
680
814
|
async function handleAction(id, action, params, actions) {
|
|
681
815
|
sendResult(id, await callIpcAction(actions, action, params));
|
|
682
816
|
}
|
|
683
|
-
/** 控制指令处理(stop/restart 退出;login
|
|
684
|
-
function handleControl(payload, onExit) {
|
|
685
|
-
if (payload.command === "stop" || payload.command === "restart")
|
|
817
|
+
/** 控制指令处理(stop/restart 退出;login 触发重新登录 / 强制扫码)。 */
|
|
818
|
+
function handleControl(payload, onExit, onLogin) {
|
|
819
|
+
if (payload.command === "stop" || payload.command === "restart") {
|
|
820
|
+
onExit();
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (payload.command === "login") onLogin?.({
|
|
824
|
+
...payload.uin !== void 0 ? { uin: payload.uin } : {},
|
|
825
|
+
...payload.qr !== void 0 ? { qr: payload.qr } : {}
|
|
826
|
+
});
|
|
686
827
|
}
|
|
687
828
|
//#endregion
|
|
688
829
|
//#region src/host/core/login.ts
|
|
@@ -1312,6 +1453,33 @@ function buildLoginOpts(Appid, forcedUin, ref) {
|
|
|
1312
1453
|
};
|
|
1313
1454
|
return opts;
|
|
1314
1455
|
}
|
|
1456
|
+
/** control login 指令 → 重新登录(qr=true 强制扫码跳过快速登录,uin 指定账号)。 */
|
|
1457
|
+
function createLoginControlHandler(core, Appid) {
|
|
1458
|
+
return (payload) => {
|
|
1459
|
+
const ipcMode = env.NAPUTO_IPC === "1";
|
|
1460
|
+
const opts = {
|
|
1461
|
+
appid: String(Appid),
|
|
1462
|
+
initTimeoutMs: 2e4,
|
|
1463
|
+
qrFallback: true,
|
|
1464
|
+
...payload.uin !== void 0 ? { quickUin: payload.uin } : {},
|
|
1465
|
+
...payload.qr === true ? { qrOnly: true } : {},
|
|
1466
|
+
onLoginProgress: (progress) => {
|
|
1467
|
+
if (ipcMode && progress.qr !== void 0) sendQr(progress.qr.pngBase64, progress.qr.qrcodeUrl);
|
|
1468
|
+
if (ipcMode && isLoginState(progress.state)) sendLogin(progress.state, progress.selfInfo, progress.message);
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
core.login(opts).then((result) => {
|
|
1472
|
+
if (ipcMode && result !== null) sendLogin("logged_in", {
|
|
1473
|
+
uin: result.uin,
|
|
1474
|
+
uid: result.uid,
|
|
1475
|
+
nick: result.nick ?? ""
|
|
1476
|
+
});
|
|
1477
|
+
}).catch((err) => {
|
|
1478
|
+
log(`bootstrap: control login 失败: ${errMsg(err)}`);
|
|
1479
|
+
if (ipcMode) sendLogin("failed", void 0, errMsg(err));
|
|
1480
|
+
});
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1315
1483
|
/** V2 登录后替换 session:优先 QQ 主 session,其次 vehicle 单例表;自建宿主例外。 */
|
|
1316
1484
|
function replaceSession(kernel, core, state, ctx) {
|
|
1317
1485
|
if (env.NAPUTO_SELF_HOST === "1") return null;
|
|
@@ -1337,13 +1505,15 @@ async function activateSession(kernel, state, ctx, chosen, loginResult, Appid, b
|
|
|
1337
1505
|
try {
|
|
1338
1506
|
const qqDataRoot = typeof kernel.resolveQqUserDataRoot === "function" ? kernel.resolveQqUserDataRoot(state.wrapperExports) : null;
|
|
1339
1507
|
const accountPath = qqDataRoot && typeof kernel.resolveQqGlobalPath === "function" ? kernel.resolveQqGlobalPath(qqDataRoot) : bootEnv.dataDir || ".";
|
|
1508
|
+
const machineGuid = typeof kernel.readMachineGuid === "function" ? kernel.readMachineGuid(ctx.loginService) : "";
|
|
1340
1509
|
const sessionConfig = kernel.buildSessionConfig({
|
|
1341
1510
|
appid: Appid,
|
|
1342
1511
|
fullVersion: bootEnv.qqVersion || "",
|
|
1343
1512
|
selfUin: loginResult.uin,
|
|
1344
1513
|
selfUid: loginResult.uid,
|
|
1345
1514
|
accountPath,
|
|
1346
|
-
downloadPath: (0, node_path.join)(accountPath, "NapCat", "temp")
|
|
1515
|
+
downloadPath: (0, node_path.join)(accountPath, "NapCat", "temp"),
|
|
1516
|
+
machineGuid
|
|
1347
1517
|
});
|
|
1348
1518
|
const listener = kernel.createLifecycleSessionListener();
|
|
1349
1519
|
await kernel.initAndStartSession(ctx, sessionConfig, listener, { timeoutMs: 2e4 });
|
|
@@ -1400,7 +1570,10 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
|
|
|
1400
1570
|
let ipcActions = null;
|
|
1401
1571
|
if (env.NAPUTO_IPC === "1") {
|
|
1402
1572
|
ipcActions = createIpcActionsForCore(core);
|
|
1403
|
-
startIpcServer({
|
|
1573
|
+
startIpcServer({
|
|
1574
|
+
actions: ipcActions,
|
|
1575
|
+
onLogin: createLoginControlHandler(core, Appid)
|
|
1576
|
+
});
|
|
1404
1577
|
}
|
|
1405
1578
|
const ctx = core.attachWrapper(state.wrapperExports, { ...bootEnv });
|
|
1406
1579
|
log(`bootstrap: attachWrapper OK, engine=${typeof ctx.engine}, session=${ctx.session !== null}`);
|
|
@@ -1459,9 +1632,17 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
|
|
|
1459
1632
|
* 取到后 core.setSession 替换 → waitSessionReady 确认 getMsgService READY。
|
|
1460
1633
|
*/
|
|
1461
1634
|
/** ⭐ appid 动态解析(2026-08-06 P2-0 实测:硬编码 537237765 在 9.9.33 扫码失败
|
|
1462
|
-
* 「请下载最新版」;major.node 解析的 537376818 成功)。自研,参考 NapCat 思路。
|
|
1635
|
+
* 「请下载最新版」;major.node 解析的 537376818 成功)。自研,参考 NapCat 思路。
|
|
1636
|
+
* 2026-08 硬编码审计:删除第三层字面量兜底 537237765——解析失败复用 kernel 的
|
|
1637
|
+
* resolveAppidQua(已改为失败抛 KernelError),旧 kernel 则显式抛错,不再静默回退。 */
|
|
1463
1638
|
function resolveAppid(kernel, bootEnv) {
|
|
1464
|
-
|
|
1639
|
+
const majorPath = bootEnv.wrapperPath ? (0, node_path.join)((0, node_path.dirname)(bootEnv.wrapperPath), "major.node") : void 0;
|
|
1640
|
+
if (typeof kernel.resolveAppidQua === "function") return kernel.resolveAppidQua(bootEnv.qqVersion || "", majorPath).appid;
|
|
1641
|
+
if (typeof kernel.parseAppidFromMajor === "function" && majorPath !== void 0) {
|
|
1642
|
+
const appid = kernel.parseAppidFromMajor(majorPath);
|
|
1643
|
+
if (appid !== null) return appid;
|
|
1644
|
+
}
|
|
1645
|
+
throw new Error("无法从 major.node 解析 appid,请确认 wrapper.node/major.node 完整,或更新 qq-releases.json");
|
|
1465
1646
|
}
|
|
1466
1647
|
/** 快速登录 + session 初始化(kernel lifecycle 路径;生命周期方法缺失则跳过)。 */
|
|
1467
1648
|
async function quickLoginAndStartSession(kernel, ctx, Appid, bootEnv) {
|
|
@@ -1478,13 +1659,15 @@ async function quickLoginAndStartSession(kernel, ctx, Appid, bootEnv) {
|
|
|
1478
1659
|
}
|
|
1479
1660
|
const loginResult = await kernel.quickLogin(ctx, {});
|
|
1480
1661
|
log(`bootstrap: quickLogin OK, uin=${loginResult.uin}, uid=${loginResult.uid}`);
|
|
1662
|
+
const machineGuid = typeof kernel.readMachineGuid === "function" ? kernel.readMachineGuid(ctx.loginService) : "";
|
|
1481
1663
|
const sessionConfig = kernel.buildSessionConfig({
|
|
1482
1664
|
appid: Appid,
|
|
1483
1665
|
fullVersion: bootEnv.qqVersion || "",
|
|
1484
1666
|
selfUin: loginResult.uin,
|
|
1485
1667
|
selfUid: loginResult.uid,
|
|
1486
1668
|
accountPath: bootEnv.dataDir || ".",
|
|
1487
|
-
downloadPath: (0, node_path.join)(bootEnv.dataDir || ".", "temp")
|
|
1669
|
+
downloadPath: (0, node_path.join)(bootEnv.dataDir || ".", "temp"),
|
|
1670
|
+
machineGuid
|
|
1488
1671
|
});
|
|
1489
1672
|
const listener = kernel.createLifecycleSessionListener();
|
|
1490
1673
|
await kernel.initAndStartSession(ctx, sessionConfig, listener, { timeoutMs: 2e4 });
|
|
@@ -1534,9 +1717,9 @@ async function bootstrap(state) {
|
|
|
1534
1717
|
dataDir: env.NAPUTO_CFG_DIR || "",
|
|
1535
1718
|
wrapperPath: env.NAPUTO_WRAPPER_PATH || ""
|
|
1536
1719
|
};
|
|
1537
|
-
const Appid = resolveAppid(kernel, bootEnv);
|
|
1538
|
-
log(`bootstrap: appid=${Appid}(wrapper=${bootEnv.wrapperPath})`);
|
|
1539
1720
|
try {
|
|
1721
|
+
const Appid = resolveAppid(kernel, bootEnv);
|
|
1722
|
+
log(`bootstrap: appid=${Appid}(wrapper=${bootEnv.wrapperPath})`);
|
|
1540
1723
|
if (kernel.NapukettoCore !== void 0) await bootstrapWithCore(kernel, state, bootEnv, Appid);
|
|
1541
1724
|
else await bootstrapFallback(kernel, state, bootEnv, Appid);
|
|
1542
1725
|
} catch (e) {
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
+
let zod = require("zod");
|
|
24
25
|
let node_fs = require("node:fs");
|
|
25
26
|
let node_path = require("node:path");
|
|
26
27
|
let node_process = require("node:process");
|
|
@@ -30,6 +31,171 @@ let node_url = require("node:url");
|
|
|
30
31
|
let node_fs_promises = require("node:fs/promises");
|
|
31
32
|
let node_crypto = require("node:crypto");
|
|
32
33
|
let node_https = require("node:https");
|
|
34
|
+
//#region src/host/ipc/ipc-types.ts
|
|
35
|
+
/**
|
|
36
|
+
* ipc-types.ts:IPC 协议消息(koishi 插件 ↔ self-host 子进程,单一来源)。
|
|
37
|
+
*
|
|
38
|
+
* 协议契约(koishi 插件 design.md §5.6):`{ v, type, id?, payload }` JSON 行。
|
|
39
|
+
* 用 zod v4 定义单一 schema,两侧(loader host + koishi 插件)import 同一份:
|
|
40
|
+
* 类型经 z.infer 派生、运行时经 safeParse 校验,消除「两份手写 types.ts」漂移。
|
|
41
|
+
*
|
|
42
|
+
* 领域字段(LoginState / SelfInfo / QrCodeData)与 kernel 同名类型结构等价,
|
|
43
|
+
* 在协议边界内联为 zod schema(kernel 不依赖 zod,故不在 kernel 定义)。
|
|
44
|
+
*/
|
|
45
|
+
/** IPC 协议版本(解码校验)。 */
|
|
46
|
+
const IPC_VERSION = 1;
|
|
47
|
+
/** 引导阶段(status 消息)。 */
|
|
48
|
+
const IpcBootPhaseSchema = zod.z.enum([
|
|
49
|
+
"booting",
|
|
50
|
+
"dlopening",
|
|
51
|
+
"logging",
|
|
52
|
+
"sessioning",
|
|
53
|
+
"ready",
|
|
54
|
+
"failed"
|
|
55
|
+
]);
|
|
56
|
+
/** 结构化日志级别(pino level)。 */
|
|
57
|
+
const IpcLogLevelSchema = zod.z.enum([
|
|
58
|
+
"trace",
|
|
59
|
+
"debug",
|
|
60
|
+
"info",
|
|
61
|
+
"warn",
|
|
62
|
+
"error",
|
|
63
|
+
"fatal"
|
|
64
|
+
]);
|
|
65
|
+
/** 登录状态(与 kernel LoginState 结构等价)。 */
|
|
66
|
+
const LoginStateSchema = zod.z.enum([
|
|
67
|
+
"idle",
|
|
68
|
+
"waiting_scan",
|
|
69
|
+
"scanned",
|
|
70
|
+
"logged_in",
|
|
71
|
+
"failed"
|
|
72
|
+
]);
|
|
73
|
+
/** selfInfo(与 kernel SelfInfo 结构等价)。 */
|
|
74
|
+
const SelfInfoSchema = zod.z.object({
|
|
75
|
+
uin: zod.z.string(),
|
|
76
|
+
uid: zod.z.string(),
|
|
77
|
+
nick: zod.z.string()
|
|
78
|
+
});
|
|
79
|
+
/** status 消息 payload。 */
|
|
80
|
+
const IpcStatusPayloadSchema = zod.z.object({
|
|
81
|
+
phase: IpcBootPhaseSchema,
|
|
82
|
+
message: zod.z.string().optional(),
|
|
83
|
+
error: zod.z.object({
|
|
84
|
+
code: zod.z.string(),
|
|
85
|
+
message: zod.z.string()
|
|
86
|
+
}).optional()
|
|
87
|
+
});
|
|
88
|
+
/** login 消息 payload。 */
|
|
89
|
+
const IpcLoginPayloadSchema = zod.z.object({
|
|
90
|
+
state: LoginStateSchema,
|
|
91
|
+
selfInfo: SelfInfoSchema.optional(),
|
|
92
|
+
/** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */
|
|
93
|
+
message: zod.z.string().optional()
|
|
94
|
+
});
|
|
95
|
+
/** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */
|
|
96
|
+
const IpcEventPayloadSchema = zod.z.object({
|
|
97
|
+
service: zod.z.string(),
|
|
98
|
+
name: zod.z.string(),
|
|
99
|
+
args: zod.z.array(zod.z.unknown())
|
|
100
|
+
});
|
|
101
|
+
/** log 消息 payload。 */
|
|
102
|
+
const IpcLogPayloadSchema = zod.z.object({
|
|
103
|
+
level: IpcLogLevelSchema,
|
|
104
|
+
message: zod.z.string()
|
|
105
|
+
});
|
|
106
|
+
/** 动作响应 payload(result 消息)。 */
|
|
107
|
+
const IpcResultPayloadSchema = zod.z.union([zod.z.object({
|
|
108
|
+
ok: zod.z.literal(true),
|
|
109
|
+
value: zod.z.unknown().optional()
|
|
110
|
+
}), zod.z.object({
|
|
111
|
+
ok: zod.z.literal(false),
|
|
112
|
+
error: zod.z.object({
|
|
113
|
+
code: zod.z.string(),
|
|
114
|
+
message: zod.z.string()
|
|
115
|
+
})
|
|
116
|
+
})]);
|
|
117
|
+
/** 动作请求 payload(action 消息)。 */
|
|
118
|
+
const IpcActionPayloadSchema = zod.z.object({
|
|
119
|
+
action: zod.z.string(),
|
|
120
|
+
params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
121
|
+
});
|
|
122
|
+
/** 控制指令 payload(control 消息,父→子)。 */
|
|
123
|
+
const IpcControlPayloadSchema = zod.z.union([
|
|
124
|
+
zod.z.object({ command: zod.z.literal("stop") }),
|
|
125
|
+
zod.z.object({ command: zod.z.literal("restart") }),
|
|
126
|
+
zod.z.object({
|
|
127
|
+
command: zod.z.literal("login"),
|
|
128
|
+
uin: zod.z.string().optional(),
|
|
129
|
+
qr: zod.z.boolean().optional()
|
|
130
|
+
})
|
|
131
|
+
]);
|
|
132
|
+
const IpcStatusMessageSchema = zod.z.object({
|
|
133
|
+
v: zod.z.literal(1),
|
|
134
|
+
type: zod.z.literal("status"),
|
|
135
|
+
payload: IpcStatusPayloadSchema
|
|
136
|
+
});
|
|
137
|
+
const IpcLoginMessageSchema = zod.z.object({
|
|
138
|
+
v: zod.z.literal(1),
|
|
139
|
+
type: zod.z.literal("login"),
|
|
140
|
+
payload: IpcLoginPayloadSchema
|
|
141
|
+
});
|
|
142
|
+
const IpcQrMessageSchema = zod.z.object({
|
|
143
|
+
v: zod.z.literal(1),
|
|
144
|
+
type: zod.z.literal("qr"),
|
|
145
|
+
payload: zod.z.object({
|
|
146
|
+
pngBase64: zod.z.string(),
|
|
147
|
+
qrcodeUrl: zod.z.string()
|
|
148
|
+
})
|
|
149
|
+
});
|
|
150
|
+
const IpcEventMessageSchema = zod.z.object({
|
|
151
|
+
v: zod.z.literal(1),
|
|
152
|
+
type: zod.z.literal("event"),
|
|
153
|
+
payload: IpcEventPayloadSchema
|
|
154
|
+
});
|
|
155
|
+
const IpcResultMessageSchema = zod.z.object({
|
|
156
|
+
v: zod.z.literal(1),
|
|
157
|
+
type: zod.z.literal("result"),
|
|
158
|
+
id: zod.z.number(),
|
|
159
|
+
payload: IpcResultPayloadSchema
|
|
160
|
+
});
|
|
161
|
+
const IpcLogMessageSchema = zod.z.object({
|
|
162
|
+
v: zod.z.literal(1),
|
|
163
|
+
type: zod.z.literal("log"),
|
|
164
|
+
payload: IpcLogPayloadSchema
|
|
165
|
+
});
|
|
166
|
+
const IpcPingMessageSchema = zod.z.object({
|
|
167
|
+
v: zod.z.literal(1),
|
|
168
|
+
type: zod.z.literal("ping")
|
|
169
|
+
});
|
|
170
|
+
const IpcPongMessageSchema = zod.z.object({
|
|
171
|
+
v: zod.z.literal(1),
|
|
172
|
+
type: zod.z.literal("pong")
|
|
173
|
+
});
|
|
174
|
+
const IpcActionMessageSchema = zod.z.object({
|
|
175
|
+
v: zod.z.literal(1),
|
|
176
|
+
type: zod.z.literal("action"),
|
|
177
|
+
id: zod.z.number(),
|
|
178
|
+
payload: IpcActionPayloadSchema
|
|
179
|
+
});
|
|
180
|
+
const IpcControlMessageSchema = zod.z.object({
|
|
181
|
+
v: zod.z.literal(1),
|
|
182
|
+
type: zod.z.literal("control"),
|
|
183
|
+
payload: IpcControlPayloadSchema
|
|
184
|
+
});
|
|
185
|
+
/** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */
|
|
186
|
+
const IpcMessageSchema = zod.z.discriminatedUnion("type", [
|
|
187
|
+
IpcStatusMessageSchema,
|
|
188
|
+
IpcLoginMessageSchema,
|
|
189
|
+
IpcQrMessageSchema,
|
|
190
|
+
IpcEventMessageSchema,
|
|
191
|
+
IpcResultMessageSchema,
|
|
192
|
+
IpcLogMessageSchema,
|
|
193
|
+
IpcPingMessageSchema,
|
|
194
|
+
IpcPongMessageSchema,
|
|
195
|
+
IpcActionMessageSchema,
|
|
196
|
+
IpcControlMessageSchema
|
|
197
|
+
]);
|
|
198
|
+
//#endregion
|
|
33
199
|
//#region src/instance-lock.ts
|
|
34
200
|
/**
|
|
35
201
|
* instance-lock.ts:单实例锁(2026-08-07 根治「多实例抢数据目录锁挂起」)
|
|
@@ -635,8 +801,14 @@ const ENV = {
|
|
|
635
801
|
* qq-releases.ts:版本清单读取(P1)。
|
|
636
802
|
*
|
|
637
803
|
* 运行时从包根 qq-releases.json 读取(发布时 files 含它,安装后位于
|
|
638
|
-
* node_modules/@napuketto/loader/qq-releases.json
|
|
639
|
-
*
|
|
804
|
+
* node_modules/@napuketto/loader/qq-releases.json)。
|
|
805
|
+
*
|
|
806
|
+
* 维护机制(真实存在,非「声称」):清单由 CI 自动维护——
|
|
807
|
+
* .github/workflows/update-qq-releases.yml(每日 cron + 手动触发)调用
|
|
808
|
+
* scripts/qq-releases/update-qq-releases.ts(纯 Node,与本地共用),抓取 QQ 官方下载配置
|
|
809
|
+
* 解析最新版 + 下载安装包计算 sha256 + 解析安装包内部版本目录后更新本清单
|
|
810
|
+
* (保留历史条目、known 按版本递增)。社区 PR 仍可手动补充。NAPUTO_QQ_URL
|
|
811
|
+
* 可运行时覆盖下载地址(用户拿到新链接时用)。
|
|
640
812
|
*/
|
|
641
813
|
/** 清单解析失败。 */
|
|
642
814
|
var QqReleasesError = class extends Error {
|
|
@@ -909,6 +1081,17 @@ async function ensureQqFiles(options = {}) {
|
|
|
909
1081
|
exports.DownloadError = DownloadError;
|
|
910
1082
|
exports.ENV = ENV;
|
|
911
1083
|
exports.INSTANCE_LOCK_FILE = INSTANCE_LOCK_FILE;
|
|
1084
|
+
exports.IPC_VERSION = IPC_VERSION;
|
|
1085
|
+
exports.IpcActionPayloadSchema = IpcActionPayloadSchema;
|
|
1086
|
+
exports.IpcBootPhaseSchema = IpcBootPhaseSchema;
|
|
1087
|
+
exports.IpcControlPayloadSchema = IpcControlPayloadSchema;
|
|
1088
|
+
exports.IpcEventPayloadSchema = IpcEventPayloadSchema;
|
|
1089
|
+
exports.IpcLogLevelSchema = IpcLogLevelSchema;
|
|
1090
|
+
exports.IpcLogPayloadSchema = IpcLogPayloadSchema;
|
|
1091
|
+
exports.IpcLoginPayloadSchema = IpcLoginPayloadSchema;
|
|
1092
|
+
exports.IpcMessageSchema = IpcMessageSchema;
|
|
1093
|
+
exports.IpcResultPayloadSchema = IpcResultPayloadSchema;
|
|
1094
|
+
exports.IpcStatusPayloadSchema = IpcStatusPayloadSchema;
|
|
912
1095
|
exports.QQ_FILES_DIR_NAME = QQ_FILES_DIR_NAME;
|
|
913
1096
|
exports.QqReleasesError = QqReleasesError;
|
|
914
1097
|
exports.WIN_NODE_DIR_NAME = WIN_NODE_DIR_NAME;
|