@napuketto/loader 0.0.20 → 0.0.21

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.
@@ -271,6 +271,81 @@ async function toPeer(params, uinToUid) {
271
271
  }
272
272
  throw new Error("缺 peerUid(或 peerUin 且未注入 uinToUid)");
273
273
  }
274
+ /** at 元素判断(宽松结构;排除 @全体——target="all" 走 atType=1,无需转换)。 */
275
+ function isLooseAtElement(value) {
276
+ if (typeof value !== "object" || value === null) return false;
277
+ const el = value;
278
+ return el["type"] === "at" && typeof el["target"] === "string" && el["target"] !== "all";
279
+ }
280
+ /** 收集元素中的 at(非 @全体)及其索引。 */
281
+ function collectAtItems(elements) {
282
+ const items = [];
283
+ for (let i = 0; i < elements.length; i++) {
284
+ const el = elements[i];
285
+ if (isLooseAtElement(el)) items.push({
286
+ index: i,
287
+ uin: el.target
288
+ });
289
+ }
290
+ return items;
291
+ }
292
+ /** 群成员昵称 → display(可选依赖 groupCache.getMember;无昵称返回 undefined)。 */
293
+ async function memberDisplay(ctx, groupCode, uid) {
294
+ if (ctx.groupCache.getMember === void 0) return;
295
+ const member = await ctx.groupCache.getMember(groupCode, uid);
296
+ if (member === void 0 || member.nick === void 0 || member.nick === "") return;
297
+ return member.nick;
298
+ }
299
+ /**
300
+ * 单个 at 元素归一化:target uin → uid(u_ 前缀直通)+ display 补全。
301
+ * 返回 null 表示目标未解析(调用方保留原元素,保底发送)。
302
+ */
303
+ async function normalizeAtElement(ctx, groupCode, prev, uidMap) {
304
+ const uid = prev.target.startsWith("u_") ? prev.target : uidMap.get(prev.target) ?? "";
305
+ if (uid === "") return null;
306
+ const next = {
307
+ type: "at",
308
+ target: uid
309
+ };
310
+ if (prev.display !== void 0 && prev.display !== "") {
311
+ next.display = prev.display;
312
+ return next;
313
+ }
314
+ const nick = await memberDisplay(ctx, groupCode, uid);
315
+ if (nick !== void 0) next.display = nick;
316
+ return next;
317
+ }
318
+ /**
319
+ * 发送前 at 目标归一化(P2-19 同构:OB11 协议层 at.qq uin → uid 后才进 kernel)。
320
+ *
321
+ * 背景(koishi-plugin-adapter-napuketto issue #1「无法 @ 指定群成员」):
322
+ * koishi 调用方给的 at.target 是 QQ 号(uin——session.userId 即 senderUin),
323
+ * 而 NT 的 atUid 契约是 u_ 开头的 uid。不转则 QQ 解析 at 失败,只能回退显示
324
+ * content 兜底 "@" → 表现为「@号后面为空」。群聊才有 at 语义,私聊/临时会话跳过。
325
+ *
326
+ * 归一化:uin 批量 uinToUid → uid(u_ 前缀直通不转);有 groupCache.getMember
327
+ * 时补 display(群成员昵称,kernel toSendElements 用它作 TEXT content,
328
+ * 避免 @ 后无显示名)。转换失败/缺注入:保留原元素(不阻塞发送,保底)。
329
+ */
330
+ async function resolveAtElements(chatType, peerUid, elements, ctx) {
331
+ if (chatType !== 2 || ctx.uinToUid === void 0) return elements;
332
+ const atItems = collectAtItems(elements);
333
+ if (atItems.length === 0) return elements;
334
+ const uins = atItems.filter((item) => !item.uin.startsWith("u_"));
335
+ let uidMap = /* @__PURE__ */ new Map();
336
+ if (uins.length > 0) try {
337
+ uidMap = await ctx.uinToUid(uins.map((item) => item.uin));
338
+ } catch {
339
+ uidMap = /* @__PURE__ */ new Map();
340
+ }
341
+ const out = [...elements];
342
+ for (const item of atItems) {
343
+ const prev = out[item.index];
344
+ const next = await normalizeAtElement(ctx, peerUid, prev, uidMap);
345
+ if (next !== null) out[item.index] = next;
346
+ }
347
+ return out;
348
+ }
274
349
  /** 构造动作表(map 顺序即注册顺序,重复动作名后注册覆盖)。 */
275
350
  function createIpcActions(ctx) {
276
351
  const actions = /* @__PURE__ */ new Map();
@@ -278,7 +353,8 @@ function createIpcActions(ctx) {
278
353
  actions.set("msg.sendMessage", async (params) => {
279
354
  const peer = await toPeer(params, ctx.uinToUid);
280
355
  const elements = Array.isArray(params["elements"]) ? params["elements"] : [];
281
- return ctx.msgApi.sendMessage(peer, elements);
356
+ const resolved = await resolveAtElements(peer.chatType, peer.peerUid, elements, ctx);
357
+ return ctx.msgApi.sendMessage(peer, resolved);
282
358
  });
283
359
  actions.set("msg.recallMessage", async (params) => {
284
360
  const peer = await toPeer(params, ctx.uinToUid);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napuketto/loader",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "NapukettoQQ 引导组件:自建宿主(标准 Node + stub QQNT.dll)dlopen wrapper.node 并启动 kernel,不拉起 QQ / 不注入",
5
5
  "type": "module",
6
6
  "files": [