@napuketto/loader 0.0.7 → 0.0.8

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.
@@ -287,8 +287,135 @@ function createIpcActions(ctx) {
287
287
  return ctx.groupCache.listGroupsRefreshed();
288
288
  });
289
289
  actions.set("friend.getFriendList", async () => ctx.friendApi.getFriendList());
290
+ actions.set("diag.richMediaTmpPaths", async () => runRichMediaDiag(ctx.session));
291
+ actions.set("diag.flashCall", async (params) => {
292
+ const sess = ctx.session;
293
+ if (sess === void 0) return { error: "session 未注入" };
294
+ const flash = typeof sess.getFlashTransferService === "function" ? sess.getFlashTransferService() : null;
295
+ if (flash === null || flash === void 0) return { error: "flash 服务为 null" };
296
+ return callDiagnosticMethod(flash, params);
297
+ });
298
+ actions.set("diag.sessionCall", async (params) => {
299
+ if (ctx.session === void 0) return { error: "session 未注入" };
300
+ return callDiagnosticMethod(ctx.session, params);
301
+ });
302
+ actions.set("diag.engineCall", async (params) => {
303
+ if (ctx.engine === void 0) return { error: "engine 未注入" };
304
+ return callDiagnosticMethod(ctx.engine, params);
305
+ });
306
+ actions.set("diag.richMediaCall", async (params) => {
307
+ const sess = ctx.session;
308
+ if (sess === void 0) return { error: "session 未注入" };
309
+ const svc = typeof sess.getRichMediaService === "function" ? sess.getRichMediaService() : null;
310
+ if (svc === null || svc === void 0) return { error: "richMedia 服务为 null" };
311
+ return callDiagnosticMethod(svc, params);
312
+ });
313
+ actions.set("diag.msgServiceCall", async (params) => {
314
+ const sess = ctx.session;
315
+ if (sess === void 0) return { error: "session 未注入" };
316
+ const svc = typeof sess.getMsgService === "function" ? sess.getMsgService() : null;
317
+ if (svc === null || svc === void 0) return { error: "msgService 为 null" };
318
+ return callDiagnosticMethod(svc, params);
319
+ });
320
+ actions.set("diag.utilCall", async (params) => {
321
+ const util = ctx.util;
322
+ if (util === void 0) return { error: "util 未注入" };
323
+ const instance = typeof util.get === "function" ? util.get() : util;
324
+ if (instance === null || instance === void 0) return { error: "util 实例为 null" };
325
+ return callDiagnosticMethod(instance, params);
326
+ });
290
327
  return actions;
291
328
  }
329
+ /** 诊断:按方法名 + 参数数组调用对象任意方法(返回结构化结果,不抛)。
330
+ * ⚠️ NAPI 对象方法挂在实例自身(非原型),__methods 须枚举实例 ownKeys。 */
331
+ async function callDiagnosticMethod(obj, params) {
332
+ const method = typeof params["method"] === "string" ? params["method"] : "";
333
+ const args = Array.isArray(params["args"]) ? params["args"] : [];
334
+ const target = obj;
335
+ if (method === "__methods") {
336
+ const own = Object.getOwnPropertyNames(obj);
337
+ const proto = Object.getOwnPropertyNames(Object.getPrototypeOf(obj) ?? {});
338
+ return { methods: [.../* @__PURE__ */ new Set([...own, ...proto])] };
339
+ }
340
+ const fn = target[method];
341
+ if (typeof fn !== "function") return { error: `方法不存在: ${method}` };
342
+ try {
343
+ return {
344
+ ok: true,
345
+ ret: await fn.apply(obj, args)
346
+ };
347
+ } catch (e) {
348
+ return {
349
+ ok: false,
350
+ err: e instanceof Error ? e.message : String(e)
351
+ };
352
+ }
353
+ }
354
+ /** 诊断:枚举富媒体临时路径(getPicTmpPath 等 0 参方法)。 */
355
+ async function enumerateTmpPaths(svc) {
356
+ const tmp = {};
357
+ for (const name of [
358
+ "getPicTmpPath",
359
+ "getRichMeidaTmpPath",
360
+ "getPttTmpPath",
361
+ "getVideoTmpPath",
362
+ "getFileTmpPath",
363
+ "getTransferingTmpPath"
364
+ ]) {
365
+ const fn = svc[name];
366
+ if (typeof fn === "function") try {
367
+ tmp[name] = await fn.call(svc);
368
+ } catch (e) {
369
+ tmp[name] = `调用失败: ${e instanceof Error ? e.message : String(e)}`;
370
+ }
371
+ else tmp[name] = "方法缺失";
372
+ }
373
+ return tmp;
374
+ }
375
+ /** 诊断:闪传服务候选 side-effect 触发(§5.3 候选动作 1,参数宽松猜测)。 */
376
+ async function probeFlashSideEffects(flash) {
377
+ const flashObj = flash;
378
+ const sideEffects = {};
379
+ const candidates = [
380
+ ["addFileSetSimpleStatusListener", [() => void 0]],
381
+ ["setFlashTransferDir", [""]],
382
+ ["setFileSetDownloadDir", [""]],
383
+ ["getFileSetIdByCode", [""]]
384
+ ];
385
+ for (const [name, args] of candidates) {
386
+ const fn = flashObj[name];
387
+ if (typeof fn === "function") try {
388
+ sideEffects[name] = {
389
+ ok: true,
390
+ ret: await fn.apply(flash, args)
391
+ };
392
+ } catch (e) {
393
+ sideEffects[name] = {
394
+ ok: false,
395
+ err: e instanceof Error ? e.message : String(e)
396
+ };
397
+ }
398
+ else sideEffects[name] = { missing: true };
399
+ }
400
+ return sideEffects;
401
+ }
402
+ /** 诊断主流程:session → 富媒体服务 + 闪传服务的方法面与触发结果。 */
403
+ async function runRichMediaDiag(session) {
404
+ const sess = session;
405
+ if (sess === void 0) return { error: "session 未注入(IPC 上下文缺 session)" };
406
+ const out = {};
407
+ const svc = typeof sess.getRichMediaService === "function" ? sess.getRichMediaService() : null;
408
+ if (svc !== null && svc !== void 0) {
409
+ out["richMediaTmpPaths"] = await enumerateTmpPaths(svc);
410
+ out["richMediaMethods"] = Object.getOwnPropertyNames(Object.getPrototypeOf(svc));
411
+ } else out["richMediaService"] = "null";
412
+ const flash = typeof sess.getFlashTransferService === "function" ? sess.getFlashTransferService() : null;
413
+ if (flash !== null && flash !== void 0) {
414
+ out["flashMethods"] = Object.getOwnPropertyNames(Object.getPrototypeOf(flash));
415
+ out["flashSideEffects"] = await probeFlashSideEffects(flash);
416
+ } else out["flashService"] = "null";
417
+ return out;
418
+ }
292
419
  /** 统一动作调用:查找动作表 → 执行 → 返回结果或错误(不抛,调用方转 result)。 */
293
420
  async function callIpcAction(actions, action, params) {
294
421
  const handler = actions.get(action);
@@ -510,6 +637,9 @@ function startIpcMode(services) {
510
637
  groupCache: services.groupCache,
511
638
  friendApi: services.friendApi,
512
639
  self: services.self,
640
+ session: services.session,
641
+ engine: services.engine,
642
+ util: services.util,
513
643
  ...groupApi.uinToUid !== void 0 ? { uinToUid: groupApi.uinToUid } : {}
514
644
  }) });
515
645
  const stops = [];
@@ -612,6 +742,10 @@ function loadProtocolSections(kernel, uin) {
612
742
  * assemble-protocols.ts:OB11 / Satori 协议装配(非 IPC 模式,cli pnpm start)。
613
743
  * 2026-08-08:从 protocols.ts 拆分——protocols.ts 只留入口 + IPC 分支。
614
744
  */
745
+ /** Windows 反斜杠(file:// URL 化时替换)。 */
746
+ const BACKSLASH_RE = /\\/g;
747
+ /** adapter 入口 index.mjs 后缀(子路径导出替换用)。 */
748
+ const INDEX_MJS_RE = /index\.mjs$/;
615
749
  /** 装配 OB11 + Satori 适配器(登录成功后,非 IPC 模式)。 */
616
750
  async function assembleOb11AndSatori(kernel, services, loginResult) {
617
751
  const adapterEntry = env.NAPUTO_ADAPTER_ENTRY;
@@ -621,13 +755,13 @@ async function assembleOb11AndSatori(kernel, services, loginResult) {
621
755
  return;
622
756
  }
623
757
  try {
624
- const network = await import(`file://${networkEntry.replace(/\\/g, "/")}`);
625
- const onebot11Entry = adapterEntry.replace(/index\.mjs$/, "onebot11/index.mjs");
626
- const satoriEntry = adapterEntry.replace(/index\.mjs$/, "satori/index.mjs");
627
- const coreEntry = adapterEntry.replace(/index\.mjs$/, "core/index.mjs");
628
- const adapter = await import(`file://${onebot11Entry.replace(/\\/g, "/")}`);
629
- const satoriAdapter = await import(`file://${satoriEntry.replace(/\\/g, "/")}`);
630
- const adapterCore = await import(`file://${coreEntry.replace(/\\/g, "/")}`);
758
+ const network = await import(`file://${networkEntry.replace(BACKSLASH_RE, "/")}`);
759
+ const onebot11Entry = adapterEntry.replace(INDEX_MJS_RE, "onebot11/index.mjs");
760
+ const satoriEntry = adapterEntry.replace(INDEX_MJS_RE, "satori/index.mjs");
761
+ const coreEntry = adapterEntry.replace(INDEX_MJS_RE, "core/index.mjs");
762
+ const adapter = await import(`file://${onebot11Entry.replace(BACKSLASH_RE, "/")}`);
763
+ const satoriAdapter = await import(`file://${satoriEntry.replace(BACKSLASH_RE, "/")}`);
764
+ const adapterCore = await import(`file://${coreEntry.replace(BACKSLASH_RE, "/")}`);
631
765
  const { channel, groupCache } = services;
632
766
  const broadcaster = new network.EventBroadcaster();
633
767
  const { cfgFile, ob11Section, satoriSection } = loadProtocolSections(kernel, loginResult.uin);
@@ -772,7 +906,8 @@ async function createKernelServices(kernel, ctx, loginResult) {
772
906
  new kernel.MsgBridge(session, channel).register();
773
907
  setupMsgLogging(kernel, channel, logger);
774
908
  const groupApi = new kernel.GroupApi(session);
775
- const msgApi = new kernel.MsgApi(session);
909
+ const util = ctx.exports?.NodeQQNTWrapperUtil;
910
+ const msgApi = new kernel.MsgApi(session, channel, util);
776
911
  const friendApi = new kernel.FriendApi(session, { uidToUin: (uids) => groupApi.uidToUin(uids) });
777
912
  const groupChannel = new kernel.NTEventChannel("Group");
778
913
  new kernel.GroupBridge(session, groupChannel).register();
@@ -802,7 +937,10 @@ async function createKernelServices(kernel, ctx, loginResult) {
802
937
  self: {
803
938
  uin: loginResult.uin,
804
939
  nickname: loginResult.nick ?? ""
805
- }
940
+ },
941
+ session,
942
+ engine: ctx.engine,
943
+ util: ctx.exports?.NodeQQNTWrapperUtil
806
944
  };
807
945
  }
808
946
  //#endregion
@@ -855,32 +993,32 @@ function isSessionUsable(s) {
855
993
  return false;
856
994
  }
857
995
  }
996
+ /** 描述单个 session 对象(探测日志用;无效对象返回带断言信息的占位)。 */
997
+ function describeSession(s) {
998
+ if (!s) return "null";
999
+ try {
1000
+ const sess = s;
1001
+ const id = typeof sess.getSessionId === "function" ? String(sess.getSessionId()) : "?";
1002
+ const svc = typeof sess.getMsgService === "function" ? sess.getMsgService() : null;
1003
+ return `id=${id} msgSvc=${svc !== null && svc !== void 0 ? "READY" : "null"}`;
1004
+ } catch (e) {
1005
+ return `id=? msgSvc=断言(${errMsg(e).slice(0, 60)})`;
1006
+ }
1007
+ }
1008
+ /** 非空候选才追加探测描述。 */
1009
+ function pushProbe(out, label, candidate) {
1010
+ if (candidate) out.push(`${label}[${describeSession(candidate)}]`);
1011
+ }
858
1012
  /** session 就绪探测(5s 间隔,60s 上限)——观察 qqSession / get() 状态。 */
859
1013
  function startSessionProbe(state, _ctx, durationMs = 6e4) {
860
1014
  const sessionProbe = setInterval(() => {
861
1015
  try {
862
1016
  const S2 = state.wrapperExports?.["NodeIQQNTWrapperSession"];
863
1017
  const out = [];
864
- const describe = (s) => {
865
- if (!s) return "null";
866
- try {
867
- const sess = s;
868
- const id = typeof sess.getSessionId === "function" ? String(sess.getSessionId()) : "?";
869
- const svc = typeof sess.getMsgService === "function" ? sess.getMsgService() : null;
870
- return `id=${id} msgSvc=${svc !== null && svc !== void 0 ? "READY" : "null"}`;
871
- } catch (e) {
872
- return `id=? msgSvc=断言(${errMsg(e).slice(0, 60)})`;
873
- }
874
- };
875
- if (state.qqSession && typeof state.qqSession.getMsgService === "function") out.push(`qqSession[${describe(state.qqSession)}]`);
876
- if (S2 && typeof S2.get === "function") {
877
- const got = S2.get();
878
- out.push(`get()[${describe(got)}]`);
879
- }
880
- if (S2 && typeof S2.getNTWrapperSession === "function") {
881
- const gotNT = S2.getNTWrapperSession("Session");
882
- if (gotNT) out.push(`getNT("Session")[${describe(gotNT)}]`);
883
- }
1018
+ const qqSession = state.qqSession;
1019
+ if (qqSession && typeof qqSession.getMsgService === "function") pushProbe(out, "qqSession", qqSession);
1020
+ if (S2 && typeof S2.get === "function") pushProbe(out, "get()", S2.get());
1021
+ if (S2 && typeof S2.getNTWrapperSession === "function") pushProbe(out, "getNT(\"Session\")", S2.getNTWrapperSession("Session"));
884
1022
  log(`BOOT: session 探测: ${out.join(" | ")}`);
885
1023
  } catch (e) {
886
1024
  log(`BOOT: session 探测失败: ${errMsg(e)}`);
@@ -1129,11 +1267,13 @@ function buildLoginOpts(Appid, forcedUin, ref) {
1129
1267
  };
1130
1268
  const ipcMode = env.NAPUTO_IPC === "1";
1131
1269
  opts["onLoginProgress"] = (progress) => {
1132
- if (progress.qr !== void 0) if (ipcMode) sendQr(progress.qr.pngBase64, progress.qr.qrcodeUrl);
1133
- else node_process.default.stdout.write(`${QR_LINE_PREFIX}${JSON.stringify({
1134
- pngBase64: progress.qr.pngBase64,
1135
- qrcodeUrl: progress.qr.qrcodeUrl
1136
- })}\n`);
1270
+ if (progress.qr !== void 0) {
1271
+ if (ipcMode) sendQr(progress.qr.pngBase64, progress.qr.qrcodeUrl);
1272
+ else node_process.default.stdout.write(`${QR_LINE_PREFIX}${JSON.stringify({
1273
+ pngBase64: progress.qr.pngBase64,
1274
+ qrcodeUrl: progress.qr.qrcodeUrl
1275
+ })}\n`);
1276
+ }
1137
1277
  if (ipcMode && isLoginState(progress.state)) sendLogin(progress.state, progress.selfInfo);
1138
1278
  };
1139
1279
  return opts;
@@ -1284,6 +1424,43 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
1284
1424
  function resolveAppid(kernel, bootEnv) {
1285
1425
  return typeof kernel.parseAppidFromMajor === "function" && bootEnv.wrapperPath && kernel.parseAppidFromMajor((0, node_path.join)((0, node_path.dirname)(bootEnv.wrapperPath), "major.node")) || typeof kernel.resolveAppidQua === "function" && kernel.resolveAppidQua(bootEnv.qqVersion || "").appid || "537237765";
1286
1426
  }
1427
+ /** 快速登录 + session 初始化(kernel lifecycle 路径;生命周期方法缺失则跳过)。 */
1428
+ async function quickLoginAndStartSession(kernel, ctx, Appid, bootEnv) {
1429
+ if (typeof kernel.quickLogin !== "function" || typeof kernel.initAndStartSession !== "function") {
1430
+ log("bootstrap: kernel missing lifecycle fns (quickLogin/initAndStartSession)");
1431
+ return;
1432
+ }
1433
+ if (typeof kernel.buildLoginConfig === "function" && ctx.loginService) {
1434
+ const loginCfg = kernel.buildLoginConfig(Appid, bootEnv.qqVersion || "", bootEnv.dataDir || ".");
1435
+ if (typeof ctx.loginService.initConfig === "function") {
1436
+ ctx.loginService.initConfig(loginCfg);
1437
+ log("bootstrap: loginService.initConfig OK");
1438
+ }
1439
+ }
1440
+ const loginResult = await kernel.quickLogin(ctx, {});
1441
+ log(`bootstrap: quickLogin OK, uin=${loginResult.uin}, uid=${loginResult.uid}`);
1442
+ const sessionConfig = kernel.buildSessionConfig({
1443
+ appid: Appid,
1444
+ fullVersion: bootEnv.qqVersion || "",
1445
+ selfUin: loginResult.uin,
1446
+ selfUid: loginResult.uid,
1447
+ accountPath: bootEnv.dataDir || ".",
1448
+ downloadPath: (0, node_path.join)(bootEnv.dataDir || ".", "temp")
1449
+ });
1450
+ const listener = kernel.createLifecycleSessionListener();
1451
+ await kernel.initAndStartSession(ctx, sessionConfig, listener, { timeoutMs: 2e4 });
1452
+ log("bootstrap: session init + startNT OK!");
1453
+ }
1454
+ /** 冒烟探测(NAPUTO_PROBE=1 时运行)。 */
1455
+ async function probeRuntime(kernel, ctx) {
1456
+ if (env.NAPUTO_PROBE !== "1" || typeof kernel.probeRuntime !== "function") return;
1457
+ try {
1458
+ const probe = kernel.probeRuntime(ctx);
1459
+ log(`bootstrap: probe done, session=${probe.session ? "ok" : "null"}, services=${Object.keys(probe.services ?? {}).length}`);
1460
+ } catch (e) {
1461
+ log(`bootstrap: probe error: ${errMsg(e)}`);
1462
+ }
1463
+ }
1287
1464
  /** 回退:旧装配路径(startNapuketto + 手工 lifecycle)。 */
1288
1465
  async function bootstrapFallback(kernel, state, bootEnv, Appid) {
1289
1466
  log("bootstrap: kernel has no NapukettoCore, falling back");
@@ -1296,34 +1473,8 @@ async function bootstrapFallback(kernel, state, bootEnv, Appid) {
1296
1473
  return;
1297
1474
  }
1298
1475
  log(`bootstrap: startNapuketto OK, engine=${typeof ctx.engine}, session=${ctx.session !== null}`);
1299
- if (typeof kernel.quickLogin === "function" && typeof kernel.initAndStartSession === "function") {
1300
- if (typeof kernel.buildLoginConfig === "function" && ctx.loginService) {
1301
- const loginCfg = kernel.buildLoginConfig(Appid, bootEnv.qqVersion || "", bootEnv.dataDir || ".");
1302
- if (typeof ctx.loginService.initConfig === "function") {
1303
- ctx.loginService.initConfig(loginCfg);
1304
- log("bootstrap: loginService.initConfig OK");
1305
- }
1306
- }
1307
- const loginResult = await kernel.quickLogin(ctx, {});
1308
- log(`bootstrap: quickLogin OK, uin=${loginResult.uin}, uid=${loginResult.uid}`);
1309
- const sessionConfig = kernel.buildSessionConfig({
1310
- appid: Appid,
1311
- fullVersion: bootEnv.qqVersion || "",
1312
- selfUin: loginResult.uin,
1313
- selfUid: loginResult.uid,
1314
- accountPath: bootEnv.dataDir || ".",
1315
- downloadPath: (0, node_path.join)(bootEnv.dataDir || ".", "temp")
1316
- });
1317
- const listener = kernel.createLifecycleSessionListener();
1318
- await kernel.initAndStartSession(ctx, sessionConfig, listener, { timeoutMs: 2e4 });
1319
- log("bootstrap: session init + startNT OK!");
1320
- } else log("bootstrap: kernel missing lifecycle fns (quickLogin/initAndStartSession)");
1321
- if (env.NAPUTO_PROBE === "1" && typeof kernel.probeRuntime === "function") try {
1322
- const probe = kernel.probeRuntime(ctx);
1323
- log(`bootstrap: probe done, session=${probe.session ? "ok" : "null"}, services=${Object.keys(probe.services ?? {}).length}`);
1324
- } catch (e) {
1325
- log(`bootstrap: probe error: ${errMsg(e)}`);
1326
- }
1476
+ await quickLoginAndStartSession(kernel, ctx, Appid, bootEnv);
1477
+ await probeRuntime(kernel, ctx);
1327
1478
  }
1328
1479
  /** 核心引导:import kernel → 装配 → 登录 → 替换 session → 等待就绪 → 协议装配。 */
1329
1480
  async function bootstrap(state) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napuketto/loader",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "NapukettoQQ 引导组件:自建宿主(标准 Node + stub QQNT.dll)dlopen wrapper.node 并启动 kernel,不拉起 QQ / 不注入",
5
5
  "type": "module",
6
6
  "files": [