@napuketto/adapter 0.0.1 → 0.0.3

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.
@@ -1,4 +1,5 @@
1
1
  import { a as ActionRegistry, n as BaseProtocolAdapter, r as BaseAction } from "../core-Bj5L8WeZ.mjs";
2
+ import { i as createWsServerSchema, n as collectGrayTipUids, r as createHttpServerSchema, t as forEachRawMessage } from "../raw-message-Du7tvR1D.mjs";
2
3
  import { ChatType, GrayTipSubType, GroupApi, GroupNotifyMsgType, NTGroupMemberRole, NTGroupRequestOperateTypes, TicketApi, TipGroupElementType, WebHonorType, kernelError, toCanonicalElements } from "@napuketto/kernel";
3
4
  import { z } from "zod";
4
5
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
@@ -17,6 +18,17 @@ const ob11ErrorCodeMap = {
17
18
  UNKNOWN: 999
18
19
  };
19
20
  //#endregion
21
+ //#region src/onebot11/action/resolve-uid.ts
22
+ /**
23
+ * OB11 用户 ID 解析(uin → uid,多个动作共用,2026-08-08 克隆合并)
24
+ */
25
+ /** 解析单个 uin → uid(失败抛 KernelError INVALID_PARAM)。 */
26
+ async function resolveUid(uin, uinToUid) {
27
+ const uid = (await uinToUid([uin])).get(uin);
28
+ if (uid === void 0) throw kernelError(`用户 ${uin} 的 uid 解析失败`, "INVALID_PARAM");
29
+ return uid;
30
+ }
31
+ //#endregion
20
32
  //#region src/onebot11/action/friend/delete-friend.ts
21
33
  /**
22
34
  * delete_friend 动作:删除好友(P2-11 接 kernel FriendApi.deleteFriend)
@@ -33,8 +45,7 @@ var DeleteFriendAction = class extends BaseAction {
33
45
  this.deps = deps;
34
46
  }
35
47
  async _handle(payload) {
36
- const uid = (await this.deps.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
37
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
48
+ const uid = await resolveUid(String(payload.user_id), this.deps.uinToUid);
38
49
  await this.deps.friendApi.deleteFriend(uid);
39
50
  return null;
40
51
  }
@@ -144,8 +155,7 @@ var SendLikeAction = class extends BaseAction {
144
155
  this.deps = deps;
145
156
  }
146
157
  async _handle(payload) {
147
- const uid = (await this.deps.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
148
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
158
+ const uid = await resolveUid(String(payload.user_id), this.deps.uinToUid);
149
159
  await this.deps.profileLikeApi.sendLike(uid, payload.times ?? 1);
150
160
  return null;
151
161
  }
@@ -222,16 +232,10 @@ var SetFriendRemarkAction = class extends BaseAction {
222
232
  this.deps = deps;
223
233
  }
224
234
  async _handle(payload) {
225
- const uid = await this.resolveUid(payload.user_id);
235
+ const uid = await resolveUid(String(payload.user_id), this.deps.uinToUid);
226
236
  this.deps.friendApi.setFriendRemark(uid, payload.remark);
227
237
  return null;
228
238
  }
229
- /** user_id(uin)→ uid。 */
230
- async resolveUid(userId) {
231
- const uid = (await this.deps.uinToUid([String(userId)])).get(String(userId));
232
- if (uid === void 0) throw kernelError(`用户 ${userId} 的 uid 解析失败`, "INVALID_PARAM");
233
- return uid;
234
- }
235
239
  };
236
240
  //#endregion
237
241
  //#region src/onebot11/helper/message-unique.ts
@@ -322,15 +326,15 @@ function resolveMsgIdAndPeer(messageId, unique) {
322
326
  };
323
327
  }
324
328
  //#endregion
325
- //#region src/onebot11/action/group/delete-essence-msg.ts
329
+ //#region src/onebot11/action/group/set-essence-msg.ts
326
330
  /**
327
- * delete_essence_msg 动作:取消精华消息(P2-10 接 kernel GroupApi.removeGroupEssence)
331
+ * set_essence_msg 动作:设置精华消息(P2-10 接 kernel GroupApi.addGroupEssence)
332
+ *
333
+ * message_id → msgId + peer(MessageUnique 反查,需群消息)→ addGroupEssence。
328
334
  */
329
- const deleteEssenceMsgSchema = z.object({ message_id: z.union([z.number(), z.string()]) });
330
- /** 取消精华消息(P2-10 接 kernel removeGroupEssence)。 */
331
- var DeleteEssenceMsgAction = class extends BaseAction {
332
- name = "delete_essence_msg";
333
- schema = deleteEssenceMsgSchema;
335
+ const setEssenceMsgSchema = z.object({ message_id: z.union([z.number(), z.string()]) });
336
+ /** 精华消息基类(set/delete 共用实现,2026-08-08 克隆合并)。 */
337
+ var EssenceMsgBase = class extends BaseAction {
334
338
  errorCodeMap = ob11ErrorCodeMap;
335
339
  deps;
336
340
  constructor(deps) {
@@ -340,10 +344,32 @@ var DeleteEssenceMsgAction = class extends BaseAction {
340
344
  async _handle(payload) {
341
345
  const { msgId, peer } = resolveMsgIdAndPeer(payload.message_id, this.deps.messageUnique);
342
346
  if (peer.chatType !== ChatType.GROUP) throw kernelError("精华消息仅支持群聊消息", "INVALID_PARAM");
343
- await this.deps.groupApi.removeGroupEssence(peer.peerUid, msgId);
347
+ await this.operate(peer.peerUid, msgId);
344
348
  return null;
345
349
  }
346
350
  };
351
+ /** 设置精华消息(P2-10 接 kernel addGroupEssence)。 */
352
+ var SetEssenceMsgAction = class extends EssenceMsgBase {
353
+ name = "set_essence_msg";
354
+ schema = setEssenceMsgSchema;
355
+ async operate(groupCode, msgId) {
356
+ await this.deps.groupApi.addGroupEssence(groupCode, msgId);
357
+ }
358
+ };
359
+ //#endregion
360
+ //#region src/onebot11/action/group/delete-essence-msg.ts
361
+ /**
362
+ * delete_essence_msg 动作:取消精华消息(P2-10 接 kernel GroupApi.removeGroupEssence)
363
+ */
364
+ const deleteEssenceMsgSchema = z.object({ message_id: z.union([z.number(), z.string()]) });
365
+ /** 取消精华消息(P2-10 接 kernel removeGroupEssence)。 */
366
+ var DeleteEssenceMsgAction = class extends EssenceMsgBase {
367
+ name = "delete_essence_msg";
368
+ schema = deleteEssenceMsgSchema;
369
+ async operate(groupCode, msgId) {
370
+ await this.deps.groupApi.removeGroupEssence(groupCode, msgId);
371
+ }
372
+ };
347
373
  //#endregion
348
374
  //#region src/onebot11/action/group/get-essence-msg-list.ts
349
375
  const getEssenceMsgListSchema = z.object({ group_id: z.number() });
@@ -1020,31 +1046,6 @@ function toGroupFolderInfo(folder) {
1020
1046
  };
1021
1047
  }
1022
1048
  //#endregion
1023
- //#region src/onebot11/action/group/set-essence-msg.ts
1024
- /**
1025
- * set_essence_msg 动作:设置精华消息(P2-10 接 kernel GroupApi.addGroupEssence)
1026
- *
1027
- * message_id → msgId + peer(MessageUnique 反查,需群消息)→ addGroupEssence。
1028
- */
1029
- const setEssenceMsgSchema = z.object({ message_id: z.union([z.number(), z.string()]) });
1030
- /** 设置精华消息(P2-10 接 kernel addGroupEssence)。 */
1031
- var SetEssenceMsgAction = class extends BaseAction {
1032
- name = "set_essence_msg";
1033
- schema = setEssenceMsgSchema;
1034
- errorCodeMap = ob11ErrorCodeMap;
1035
- deps;
1036
- constructor(deps) {
1037
- super();
1038
- this.deps = deps;
1039
- }
1040
- async _handle(payload) {
1041
- const { msgId, peer } = resolveMsgIdAndPeer(payload.message_id, this.deps.messageUnique);
1042
- if (peer.chatType !== ChatType.GROUP) throw kernelError("精华消息仅支持群聊消息", "INVALID_PARAM");
1043
- await this.deps.groupApi.addGroupEssence(peer.peerUid, msgId);
1044
- return null;
1045
- }
1046
- };
1047
- //#endregion
1048
1049
  //#region src/onebot11/action/group/set-group-add-request.ts
1049
1050
  /**
1050
1051
  * set_group_add_request 动作:处理加群请求(P2-13 接 kernel GroupNotifyApi)
@@ -1110,8 +1111,7 @@ var SetGroupAdminAction = class extends BaseAction {
1110
1111
  this.groupApi = groupApi;
1111
1112
  }
1112
1113
  async _handle(payload) {
1113
- const uid = (await this.groupApi.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
1114
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
1114
+ const uid = await resolveUid(String(payload.user_id), (uins) => this.groupApi.uinToUid(uins));
1115
1115
  let role = NTGroupMemberRole.MEMBER;
1116
1116
  if (payload.enable) role = NTGroupMemberRole.ADMIN;
1117
1117
  this.groupApi.setMemberRole(String(payload.group_id), uid, role);
@@ -1120,11 +1120,6 @@ var SetGroupAdminAction = class extends BaseAction {
1120
1120
  };
1121
1121
  //#endregion
1122
1122
  //#region src/onebot11/action/group/set-group-ban.ts
1123
- /**
1124
- * set_group_ban 动作:群组单人禁言(P2-10 接 kernel GroupApi.setMemberShutUp)
1125
- *
1126
- * duration 秒(0 解除禁言);user_id → uinToUid。
1127
- */
1128
1123
  const setGroupBanSchema = z.object({
1129
1124
  group_id: z.number(),
1130
1125
  user_id: z.number(),
@@ -1142,8 +1137,7 @@ var SetGroupBanAction = class extends BaseAction {
1142
1137
  this.groupApi = groupApi;
1143
1138
  }
1144
1139
  async _handle(payload) {
1145
- const uid = (await this.groupApi.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
1146
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
1140
+ const uid = await resolveUid(String(payload.user_id), (uins) => this.groupApi.uinToUid(uins));
1147
1141
  await this.groupApi.setMemberShutUp(String(payload.group_id), [{
1148
1142
  uid,
1149
1143
  duration: payload.duration
@@ -1153,9 +1147,6 @@ var SetGroupBanAction = class extends BaseAction {
1153
1147
  };
1154
1148
  //#endregion
1155
1149
  //#region src/onebot11/action/group/set-group-card.ts
1156
- /**
1157
- * set_group_card 动作:设置群名片(P2-10 接 kernel GroupApi.setMemberCardName)
1158
- */
1159
1150
  const setGroupCardSchema = z.object({
1160
1151
  group_id: z.number(),
1161
1152
  user_id: z.number(),
@@ -1173,8 +1164,7 @@ var SetGroupCardAction = class extends BaseAction {
1173
1164
  this.groupApi = groupApi;
1174
1165
  }
1175
1166
  async _handle(payload) {
1176
- const uid = (await this.groupApi.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
1177
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
1167
+ const uid = await resolveUid(String(payload.user_id), (uins) => this.groupApi.uinToUid(uins));
1178
1168
  this.groupApi.setMemberCardName(String(payload.group_id), uid, payload.card);
1179
1169
  return null;
1180
1170
  }
@@ -1331,20 +1321,14 @@ var FetchPttTextAction = class extends BaseAction {
1331
1321
  }
1332
1322
  };
1333
1323
  //#endregion
1334
- //#region src/onebot11/action/message/forward-single-msg.ts
1324
+ //#region src/onebot11/action/message/resolve-peer.ts
1335
1325
  /**
1336
- * forward_group_single_msg / forward_friend_single_msg 动作:单条转发(P2-12)
1326
+ * OB11 目标 Peer 解析 / 单条消息拉取(多个动作共用,2026-08-08 克隆合并)
1337
1327
  *
1338
- * message_id 反查源 msgId + peer;group_id/user_id 为目标 →
1339
- * MsgApi.forwardSingleMessage(srcPeer, [msgId], dstPeer)。
1328
+ * group_id 直通(群消息 peerUid = 群号);user_id 经 uin→uid 解析。
1340
1329
  */
1341
- const forwardSingleMsgSchema = z.object({
1342
- message_id: z.union([z.number(), z.string()]),
1343
- group_id: z.number().optional(),
1344
- user_id: z.number().optional()
1345
- });
1346
1330
  /** 解析目标 Peer(group_id 直通;user_id 经 uin→uid)。 */
1347
- async function resolveTargetPeer(payload, deps) {
1331
+ async function resolveTargetPeer(payload, deps, missingHint) {
1348
1332
  if (payload.group_id !== void 0) return {
1349
1333
  chatType: ChatType.GROUP,
1350
1334
  peerUid: String(payload.group_id)
@@ -1357,8 +1341,35 @@ async function resolveTargetPeer(payload, deps) {
1357
1341
  peerUid: uid
1358
1342
  };
1359
1343
  }
1360
- throw kernelError("单条转发需要 group_id 或 user_id", "INVALID_PARAM");
1344
+ throw kernelError(missingHint, "INVALID_PARAM");
1345
+ }
1346
+ /**
1347
+ * 按 message_id(OB11 int32 或 NT msgId)拉取单条消息。
1348
+ * 消息不存在/已被撤回 → 抛 KernelError NOT_FOUND。
1349
+ */
1350
+ async function fetchMsgById(messageId, messageUnique, msgApi) {
1351
+ const { msgId, peer } = resolveMsgIdAndPeer(messageId, messageUnique);
1352
+ const [msg] = await msgApi.fetchMsgsByMsgId(peer, [msgId]);
1353
+ if (msg === void 0) throw kernelError(`消息 ${messageId} 不存在或已被撤回`, "NOT_FOUND");
1354
+ return {
1355
+ msg,
1356
+ peer,
1357
+ msgId
1358
+ };
1361
1359
  }
1360
+ //#endregion
1361
+ //#region src/onebot11/action/message/forward-single-msg.ts
1362
+ /**
1363
+ * forward_group_single_msg / forward_friend_single_msg 动作:单条转发(P2-12)
1364
+ *
1365
+ * message_id 反查源 msgId + peer;group_id/user_id 为目标 →
1366
+ * MsgApi.forwardSingleMessage(srcPeer, [msgId], dstPeer)。
1367
+ */
1368
+ const forwardSingleMsgSchema = z.object({
1369
+ message_id: z.union([z.number(), z.string()]),
1370
+ group_id: z.number().optional(),
1371
+ user_id: z.number().optional()
1372
+ });
1362
1373
  /** 单条转发基类(两个动作共用实现)。 */
1363
1374
  var ForwardSingleMsgBase = class extends BaseAction {
1364
1375
  errorCodeMap = ob11ErrorCodeMap;
@@ -1369,7 +1380,7 @@ var ForwardSingleMsgBase = class extends BaseAction {
1369
1380
  }
1370
1381
  async _handle(payload) {
1371
1382
  const { msgId, peer: sourcePeer } = resolveMsgIdAndPeer(payload.message_id, this.deps.messageUnique);
1372
- const dstPeer = await resolveTargetPeer(payload, this.deps);
1383
+ const dstPeer = await resolveTargetPeer(payload, this.deps, "单条转发需要 group_id 或 user_id");
1373
1384
  await this.deps.msgApi.forwardSingleMessage(sourcePeer, [msgId], dstPeer);
1374
1385
  return null;
1375
1386
  }
@@ -1385,90 +1396,7 @@ var ForwardFriendSingleMsgAction = class extends ForwardSingleMsgBase {
1385
1396
  schema = forwardSingleMsgSchema;
1386
1397
  };
1387
1398
  //#endregion
1388
- //#region src/onebot11/helper/config.ts
1389
- /**
1390
- * OneBot 11 协议配置(zod schema,归属本包,ADR-012)
1391
- * 协议配置 schema 在各自协议包,kernel 只提供 ConfigBase 基类。
1392
- *
1393
- * 2026-08-07 多实例化(P2-18):对齐 NapCat——httpServers / httpPostUrls / wsServers /
1394
- * wsReverseUrls 四个数组实例(每实例可覆盖全局 token);旧单对象字段 http/httpPost/ws/
1395
- * wsReverse 废弃(zod strip 静默忽略,不兼容迁移)。
1396
- */
1397
- /** HTTP 反向服务器默认端口。 */
1398
- const DEFAULT_HTTP_PORT = 3e3;
1399
- /** 反向 WS server 默认端口。 */
1400
- const DEFAULT_WS_PORT = 3001;
1401
- /** 心跳 meta 事件默认间隔(毫秒)。 */
1402
- const DEFAULT_HEARTBEAT_INTERVAL_MS = 3e3;
1403
- /** WS 传输 ping 默认间隔(毫秒)。 */
1404
- const DEFAULT_WS_HEARTBEAT_MS = 3e4;
1405
- /** WS 反向断线重连默认延迟(毫秒)。 */
1406
- const DEFAULT_RECONNECT_DELAY_MS = 5e3;
1407
- /** 消息内容格式(NapCat messagePostFormat)。 */
1408
- const MESSAGE_FORMATS = ["array", "string"];
1409
- /** HTTP 反向服务器实例(第三方 POST 指令入口)。 */
1410
- const httpServerSchema = z.object({
1411
- enabled: z.boolean().default(false),
1412
- host: z.string().default("127.0.0.1"),
1413
- port: z.number().int().default(DEFAULT_HTTP_PORT),
1414
- /** 实例级 token,覆盖全局 token(缺省继承全局)。 */
1415
- token: z.string().optional()
1416
- });
1417
- /** HTTP 正向上报实例(Webhook 事件推送)。 */
1418
- const httpPostSchema = z.object({
1419
- enabled: z.boolean().default(false),
1420
- url: z.string().optional(),
1421
- /** 实例级 token,覆盖全局 token(缺省继承全局)。 */
1422
- token: z.string().optional(),
1423
- /** 上报超时(毫秒),缺省不超时。 */
1424
- timeoutMs: z.number().int().optional()
1425
- });
1426
- /** 反向 WS server 实例(第三方 WS 连入)。 */
1427
- const wsServerSchema = z.object({
1428
- enabled: z.boolean().default(false),
1429
- host: z.string().default("127.0.0.1"),
1430
- port: z.number().int().default(DEFAULT_WS_PORT),
1431
- /** 实例级 token,覆盖全局 token(缺省继承全局)。 */
1432
- token: z.string().optional(),
1433
- /** WS ping 间隔(毫秒)。 */
1434
- heartbeatInterval: z.number().int().default(DEFAULT_WS_HEARTBEAT_MS)
1435
- });
1436
- /** 正向 WS client 实例(主动连接第三方,双向)。 */
1437
- const wsReverseSchema = z.object({
1438
- enabled: z.boolean().default(false),
1439
- url: z.string().optional(),
1440
- /** 实例级 token,覆盖全局 token(缺省继承全局)。 */
1441
- token: z.string().optional(),
1442
- /** 断线重连延迟(毫秒)。 */
1443
- reconnectDelayMs: z.number().int().default(DEFAULT_RECONNECT_DELAY_MS),
1444
- /** 最大重连次数(缺省无限)。 */
1445
- maxReconnectAttempts: z.number().int().optional(),
1446
- /** wss:// 是否校验证书(自签证书场景设 false,对齐 NapCat enableSelfSigned)。 */
1447
- rejectUnauthorized: z.boolean().default(true),
1448
- /** WS ping 间隔(毫秒)。 */
1449
- heartbeatInterval: z.number().int().default(DEFAULT_WS_HEARTBEAT_MS)
1450
- });
1451
- /** OB11 配置 schema。 */
1452
- const ob11ConfigSchema = z.object({
1453
- /** 鉴权 token(全局默认,实例未指定时继承)。 */
1454
- token: z.string().optional(),
1455
- /** 心跳 meta 事件间隔(毫秒),0 关闭心跳。 */
1456
- heartbeatInterval: z.number().int().default(DEFAULT_HEARTBEAT_INTERVAL_MS),
1457
- /** 是否上报机器人自己发的消息(缺省 false = OB11 规范行为)。 */
1458
- reportSelfMessage: z.boolean().default(false),
1459
- /** 消息内容格式:array = 消息段数组(标准),string = CQ 码字符串。 */
1460
- messagePostFormat: z.enum(MESSAGE_FORMATS).default("array"),
1461
- /** HTTP 反向服务器列表。 */
1462
- httpServers: z.array(httpServerSchema).default([]),
1463
- /** HTTP 正向上报列表。 */
1464
- httpPostUrls: z.array(httpPostSchema).default([]),
1465
- /** 反向 WS server 列表。 */
1466
- wsServers: z.array(wsServerSchema).default([]),
1467
- /** 正向 WS client 列表。 */
1468
- wsReverseUrls: z.array(wsReverseSchema).default([])
1469
- });
1470
- //#endregion
1471
- //#region src/onebot11/helper/cqcode.ts
1399
+ //#region src/onebot11/helper/codec/cqcode.ts
1472
1400
  /**
1473
1401
  * CQ 码编解码(OneBot 11 消息段序列化格式)
1474
1402
  *
@@ -1537,7 +1465,7 @@ function serializeCqParts(parts) {
1537
1465
  return out;
1538
1466
  }
1539
1467
  //#endregion
1540
- //#region src/onebot11/helper/data.ts
1468
+ //#region src/onebot11/helper/codec/segment.ts
1541
1469
  /** OB11 媒体段 data(image/record/video 共用,url 可选)。 */
1542
1470
  function mediaData(file, url) {
1543
1471
  const data = { file };
@@ -1573,80 +1501,95 @@ function rawToString(raw) {
1573
1501
  if (typeof raw === "string") return raw;
1574
1502
  return JSON.stringify(raw);
1575
1503
  }
1576
- /** canonical 元素 → OB11 segment(null = OB11 无法表达,跳过)。 */
1577
- function canonicalToSegment(el) {
1578
- if (el.type === "text") return {
1504
+ /** canonical 元素 → OB11 segment 转换器(键 = canonical type,判别字段收窄)。 */
1505
+ const CANONICAL_TO_SEGMENT = {
1506
+ text: (el) => ({
1579
1507
  type: "text",
1580
1508
  data: { text: el.text }
1581
- };
1582
- if (el.type === "at") return {
1509
+ }),
1510
+ at: (el) => ({
1583
1511
  type: "at",
1584
1512
  data: atData(el.target, el.display)
1585
- };
1586
- if (el.type === "image") return {
1513
+ }),
1514
+ image: (el) => ({
1587
1515
  type: "image",
1588
1516
  data: mediaData(el.path, el.url)
1589
- };
1590
- if (el.type === "face") return {
1517
+ }),
1518
+ face: (el) => ({
1591
1519
  type: "face",
1592
1520
  data: { id: el.id }
1593
- };
1594
- if (el.type === "voice") return {
1521
+ }),
1522
+ voice: (el) => ({
1595
1523
  type: "record",
1596
1524
  data: mediaData(el.path, el.url)
1597
- };
1598
- if (el.type === "video") return {
1525
+ }),
1526
+ video: (el) => ({
1599
1527
  type: "video",
1600
1528
  data: mediaData(el.path, el.url)
1601
- };
1602
- if (el.type === "reply") return {
1529
+ }),
1530
+ reply: (el) => ({
1603
1531
  type: "reply",
1604
1532
  data: { id: el.messageId }
1605
- };
1606
- if (el.type === "forward") return {
1533
+ }),
1534
+ forward: (el) => ({
1607
1535
  type: "forward",
1608
1536
  data: { id: el.messageIds[0] ?? "" }
1609
- };
1610
- if (el.type === "json") return {
1537
+ }),
1538
+ json: (el) => ({
1611
1539
  type: "json",
1612
1540
  data: { data: rawToString(el.raw) }
1613
- };
1614
- if (el.type === "xml") return {
1541
+ }),
1542
+ xml: (el) => ({
1615
1543
  type: "xml",
1616
1544
  data: { data: rawToString(el.raw) }
1617
- };
1618
- return null;
1545
+ })
1546
+ };
1547
+ /** canonical 元素 → OB11 segment(null = OB11 无法表达,跳过)。 */
1548
+ function canonicalToSegment(el) {
1549
+ const builder = CANONICAL_TO_SEGMENT[el.type];
1550
+ return builder === void 0 ? null : builder(el);
1619
1551
  }
1620
- /** OB11 segment → canonical 元素。 */
1621
- function segmentToCanonical(seg) {
1622
- if (seg.type === "text" || seg.type === "string") return {
1552
+ /** OB11 segment → canonical 元素转换器(键 = segment type)。 */
1553
+ const SEGMENT_TO_CANONICAL = {
1554
+ text: (seg) => ({
1623
1555
  type: "text",
1624
1556
  text: seg.data.text
1625
- };
1626
- if (seg.type === "at") return canonicalAt(seg.data.qq, seg.data.name);
1627
- if (seg.type === "face") return {
1557
+ }),
1558
+ string: (seg) => ({
1559
+ type: "text",
1560
+ text: seg.data.text
1561
+ }),
1562
+ at: (seg) => canonicalAt(seg.data.qq, seg.data.name),
1563
+ face: (seg) => ({
1628
1564
  type: "face",
1629
1565
  id: seg.data.id
1630
- };
1631
- if (seg.type === "image") return canonicalMedia("image", seg.data.file, seg.data.url);
1632
- if (seg.type === "record") return canonicalMedia("voice", seg.data.file, seg.data.url);
1633
- if (seg.type === "video") return canonicalMedia("video", seg.data.file, seg.data.url);
1634
- if (seg.type === "reply") return {
1566
+ }),
1567
+ image: (seg) => canonicalMedia("image", seg.data.file, seg.data.url),
1568
+ record: (seg) => canonicalMedia("voice", seg.data.file, seg.data.url),
1569
+ video: (seg) => canonicalMedia("video", seg.data.file, seg.data.url),
1570
+ reply: (seg) => ({
1635
1571
  type: "reply",
1636
1572
  messageId: seg.data.id
1637
- };
1638
- if (seg.type === "forward") return {
1573
+ }),
1574
+ forward: (seg) => ({
1639
1575
  type: "forward",
1640
1576
  messageIds: [seg.data.id]
1641
- };
1642
- if (seg.type === "json") return {
1577
+ }),
1578
+ json: (seg) => ({
1643
1579
  type: "json",
1644
1580
  raw: seg.data.data
1645
- };
1646
- return {
1581
+ }),
1582
+ xml: (seg) => ({
1647
1583
  type: "xml",
1648
1584
  raw: seg.data.data
1649
- };
1585
+ })
1586
+ };
1587
+ /** OB11 segment → canonical 元素(未知 type 兜底按 xml 处理,保持既有行为)。 */
1588
+ function segmentToCanonical(seg) {
1589
+ const builder = SEGMENT_TO_CANONICAL[seg.type];
1590
+ if (builder !== void 0) return builder(seg);
1591
+ const xmlBuilder = SEGMENT_TO_CANONICAL["xml"];
1592
+ return xmlBuilder === void 0 ? null : xmlBuilder(seg);
1650
1593
  }
1651
1594
  /** 已知 CQ 类型 → segment 构造器。 */
1652
1595
  const CQ_SEGMENT_BUILDERS = {
@@ -1700,6 +1643,86 @@ function cqCodeToSegment(code) {
1700
1643
  };
1701
1644
  return builder(code.params);
1702
1645
  }
1646
+ /** OB11 segment 数组 → CQ 码字符串(text 段转义拼接,特殊段编码)。 */
1647
+ function segmentsToCqMessage(segments) {
1648
+ let out = "";
1649
+ for (const seg of segments) if (seg.type === "text" || seg.type === "string") out += escapeCqText(seg.data.text);
1650
+ else out += encodeCqCode(seg.type, seg.data);
1651
+ return out;
1652
+ }
1653
+ //#endregion
1654
+ //#region src/onebot11/helper/config.ts
1655
+ /**
1656
+ * OneBot 11 协议配置(zod schema,归属本包,ADR-012)
1657
+ * 协议配置 schema 在各自协议包,kernel 只提供 ConfigBase 基类。
1658
+ *
1659
+ * 2026-08-07 多实例化(P2-18):对齐 NapCat——httpServers / httpPostUrls / wsServers /
1660
+ * wsReverseUrls 四个数组实例(每实例可覆盖全局 token);旧单对象字段 http/httpPost/ws/
1661
+ * wsReverse 废弃(zod strip 静默忽略,不兼容迁移)。
1662
+ */
1663
+ /** HTTP 反向服务器默认端口。 */
1664
+ const DEFAULT_HTTP_PORT = 3e3;
1665
+ /** 反向 WS server 默认端口。 */
1666
+ const DEFAULT_WS_PORT = 3001;
1667
+ /** 心跳 meta 事件默认间隔(毫秒)。 */
1668
+ const DEFAULT_HEARTBEAT_INTERVAL_MS = 3e3;
1669
+ /** WS 传输 ping 默认间隔(毫秒)。 */
1670
+ const DEFAULT_WS_HEARTBEAT_MS = 3e4;
1671
+ /** WS 反向断线重连默认延迟(毫秒)。 */
1672
+ const DEFAULT_RECONNECT_DELAY_MS = 5e3;
1673
+ /** 消息内容格式(NapCat messagePostFormat)。 */
1674
+ const MESSAGE_FORMATS = ["array", "string"];
1675
+ /** HTTP 反向服务器实例(第三方 POST 指令入口)。 */
1676
+ const httpServerSchema = createHttpServerSchema(DEFAULT_HTTP_PORT);
1677
+ /** HTTP 正向上报实例(Webhook 事件推送)。 */
1678
+ const httpPostSchema = z.object({
1679
+ enabled: z.boolean().default(false),
1680
+ url: z.string().optional(),
1681
+ /** 实例级 token,覆盖全局 token(缺省继承全局)。 */
1682
+ token: z.string().optional(),
1683
+ /** 上报超时(毫秒),缺省不超时。 */
1684
+ timeoutMs: z.number().int().optional()
1685
+ });
1686
+ /** 反向 WS server 实例(第三方 WS 连入,带 WS ping 间隔扩展字段)。 */
1687
+ const wsServerSchema = createWsServerSchema(DEFAULT_WS_PORT).extend({
1688
+ /** WS ping 间隔(毫秒)。 */
1689
+ heartbeatInterval: z.number().int().default(DEFAULT_WS_HEARTBEAT_MS) });
1690
+ /** 正向 WS client 实例(主动连接第三方,双向)。 */
1691
+ const wsReverseSchema = z.object({
1692
+ enabled: z.boolean().default(false),
1693
+ url: z.string().optional(),
1694
+ /** 实例级 token,覆盖全局 token(缺省继承全局)。 */
1695
+ token: z.string().optional(),
1696
+ /** 断线重连延迟(毫秒)。 */
1697
+ reconnectDelayMs: z.number().int().default(DEFAULT_RECONNECT_DELAY_MS),
1698
+ /** 最大重连次数(缺省无限)。 */
1699
+ maxReconnectAttempts: z.number().int().optional(),
1700
+ /** wss:// 是否校验证书(自签证书场景设 false,对齐 NapCat enableSelfSigned)。 */
1701
+ rejectUnauthorized: z.boolean().default(true),
1702
+ /** WS ping 间隔(毫秒)。 */
1703
+ heartbeatInterval: z.number().int().default(DEFAULT_WS_HEARTBEAT_MS)
1704
+ });
1705
+ /** OB11 配置 schema。 */
1706
+ const ob11ConfigSchema = z.object({
1707
+ /** 鉴权 token(全局默认,实例未指定时继承)。 */
1708
+ token: z.string().optional(),
1709
+ /** 心跳 meta 事件间隔(毫秒),0 关闭心跳。 */
1710
+ heartbeatInterval: z.number().int().default(DEFAULT_HEARTBEAT_INTERVAL_MS),
1711
+ /** 是否上报机器人自己发的消息(缺省 false = OB11 规范行为)。 */
1712
+ reportSelfMessage: z.boolean().default(false),
1713
+ /** 消息内容格式:array = 消息段数组(标准),string = CQ 码字符串。 */
1714
+ messagePostFormat: z.enum(MESSAGE_FORMATS).default("array"),
1715
+ /** HTTP 反向服务器列表。 */
1716
+ httpServers: z.array(httpServerSchema).default([]),
1717
+ /** HTTP 正向上报列表。 */
1718
+ httpPostUrls: z.array(httpPostSchema).default([]),
1719
+ /** 反向 WS server 列表。 */
1720
+ wsServers: z.array(wsServerSchema).default([]),
1721
+ /** 正向 WS client 列表。 */
1722
+ wsReverseUrls: z.array(wsReverseSchema).default([])
1723
+ });
1724
+ //#endregion
1725
+ //#region src/onebot11/helper/data.ts
1703
1726
  /** canonical 元素数组 → OB11 segment 数组(无法表达的元素静默跳过)。 */
1704
1727
  function canonicalToSegments(elements) {
1705
1728
  const segments = [];
@@ -1718,13 +1741,6 @@ function segmentsToCanonical(segments) {
1718
1741
  }
1719
1742
  return elements;
1720
1743
  }
1721
- /** OB11 segment 数组 → CQ 码字符串(text 段转义拼接,特殊段编码)。 */
1722
- function segmentsToCqMessage(segments) {
1723
- let out = "";
1724
- for (const seg of segments) if (seg.type === "text" || seg.type === "string") out += escapeCqText(seg.data.text);
1725
- else out += encodeCqCode(seg.type, seg.data);
1726
- return out;
1727
- }
1728
1744
  /**
1729
1745
  * 收集 canonical 元素里需要转换的 at uid / reply NT msgId(接收方向)。
1730
1746
  * 调用方据此批量 uidToUin(一次异步调用),避免逐元素查询。
@@ -1878,23 +1894,6 @@ const MS_TO_SEC$2 = 1e3;
1878
1894
  function hasGrayTip(msg) {
1879
1895
  return msg.elements.some((el) => el.grayTipElement !== void 0);
1880
1896
  }
1881
- /** 收集 grayTip 涉及的 uid(批量 uidToUin 用)。 */
1882
- function collectGrayTipUids(msg) {
1883
- const uids = /* @__PURE__ */ new Set();
1884
- for (const el of msg.elements) {
1885
- const g = el.grayTipElement;
1886
- if (g === void 0) continue;
1887
- const revoke = g.revokeElement;
1888
- if (revoke?.operatorUid !== void 0 && revoke.operatorUid !== "") uids.add(revoke.operatorUid);
1889
- const grp = g.groupElement;
1890
- if (grp === void 0) continue;
1891
- if (grp.memberUid !== void 0 && grp.memberUid !== "") uids.add(grp.memberUid);
1892
- if (grp.adminUid !== void 0 && grp.adminUid !== "") uids.add(grp.adminUid);
1893
- if (grp.shutUp?.admin?.uid !== void 0 && grp.shutUp.admin.uid !== "") uids.add(grp.shutUp.admin.uid);
1894
- if (grp.shutUp?.member?.uid !== void 0 && grp.shutUp.member.uid !== "") uids.add(grp.shutUp.member.uid);
1895
- }
1896
- return [...uids];
1897
- }
1898
1897
  /** uid → uin(上下文映射缺省退化为 uid)。 */
1899
1898
  function toUin(uid, ctx) {
1900
1899
  const uin = ctx.uidToUin.get(uid);
@@ -1925,59 +1924,79 @@ function toRecall(msg, g, ctx) {
1925
1924
  function toGroupChange(msg, g, ctx) {
1926
1925
  const grp = g?.groupElement;
1927
1926
  if (grp === void 0 || grp.type === void 0) return null;
1928
- const groupId = Number(msg.peerUid);
1929
- const time = Math.floor(Number(msg.msgTime) / MS_TO_SEC$2);
1930
- const selfId = Number(ctx.selfUin);
1931
- const memberUid = grp.memberUid ?? "";
1932
- const adminUid = grp.adminUid ?? "";
1927
+ const common = {
1928
+ time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$2),
1929
+ selfId: Number(ctx.selfUin),
1930
+ groupId: Number(msg.peerUid),
1931
+ memberUid: grp.memberUid ?? "",
1932
+ adminUid: grp.adminUid ?? ""
1933
+ };
1933
1934
  switch (grp.type) {
1934
- case TipGroupElementType.MEMBER_ADD: return {
1935
- time,
1936
- self_id: selfId,
1937
- post_type: "notice",
1938
- notice_type: "group_increase",
1939
- sub_type: adminUid === memberUid ? "approve" : "invite",
1940
- group_id: groupId,
1941
- operator_id: toUin(adminUid, ctx),
1942
- user_id: toUin(memberUid, ctx)
1943
- };
1944
- case TipGroupElementType.QUIT: return {
1945
- time,
1946
- self_id: selfId,
1947
- post_type: "notice",
1948
- notice_type: "group_decrease",
1949
- sub_type: "leave",
1950
- group_id: groupId,
1951
- operator_id: toUin(adminUid, ctx),
1952
- user_id: toUin(memberUid, ctx)
1953
- };
1935
+ case TipGroupElementType.MEMBER_ADD: return toMemberAdd(common, ctx);
1936
+ case TipGroupElementType.QUIT: return toQuit(common, ctx);
1954
1937
  case TipGroupElementType.BLOCK:
1955
- case TipGroupElementType.UNBLOCK: return {
1956
- time,
1957
- self_id: selfId,
1958
- post_type: "notice",
1959
- notice_type: "group_admin",
1960
- sub_type: grp.type === TipGroupElementType.BLOCK ? "set" : "unset",
1961
- group_id: groupId,
1962
- user_id: toUin(memberUid === "" ? adminUid : memberUid, ctx)
1963
- };
1964
- case TipGroupElementType.SHUT_UP: {
1965
- const shutUp = grp.shutUp;
1966
- return {
1967
- time,
1968
- self_id: selfId,
1969
- post_type: "notice",
1970
- notice_type: "group_ban",
1971
- sub_type: Number(shutUp?.duration ?? 0) > 0 ? "ban" : "lift_ban",
1972
- group_id: groupId,
1973
- operator_id: toUin(shutUp?.admin?.uid ?? adminUid, ctx),
1974
- user_id: toUin(shutUp?.member?.uid ?? memberUid, ctx),
1975
- duration: Number(shutUp?.duration ?? 0)
1976
- };
1977
- }
1938
+ case TipGroupElementType.UNBLOCK: return toAdminChange(common, grp, ctx);
1939
+ case TipGroupElementType.SHUT_UP: return toBan(common, grp, ctx);
1978
1940
  default: return null;
1979
1941
  }
1980
1942
  }
1943
+ /** 群成员增加(approve=管理员操作 / invite=普通邀请)。 */
1944
+ function toMemberAdd(common, ctx) {
1945
+ const { time, selfId, groupId, memberUid, adminUid } = common;
1946
+ return {
1947
+ time,
1948
+ self_id: selfId,
1949
+ post_type: "notice",
1950
+ notice_type: "group_increase",
1951
+ sub_type: adminUid === memberUid ? "approve" : "invite",
1952
+ group_id: groupId,
1953
+ operator_id: toUin(adminUid, ctx),
1954
+ user_id: toUin(memberUid, ctx)
1955
+ };
1956
+ }
1957
+ /** 群成员退出(leave)。 */
1958
+ function toQuit(common, ctx) {
1959
+ const { time, selfId, groupId, memberUid, adminUid } = common;
1960
+ return {
1961
+ time,
1962
+ self_id: selfId,
1963
+ post_type: "notice",
1964
+ notice_type: "group_decrease",
1965
+ sub_type: "leave",
1966
+ group_id: groupId,
1967
+ operator_id: toUin(adminUid, ctx),
1968
+ user_id: toUin(memberUid, ctx)
1969
+ };
1970
+ }
1971
+ /** 管理员变更(BLOCK=set / UNBLOCK=unset)。 */
1972
+ function toAdminChange(common, grp, ctx) {
1973
+ const { time, selfId, groupId, memberUid, adminUid } = common;
1974
+ return {
1975
+ time,
1976
+ self_id: selfId,
1977
+ post_type: "notice",
1978
+ notice_type: "group_admin",
1979
+ sub_type: grp.type === TipGroupElementType.BLOCK ? "set" : "unset",
1980
+ group_id: groupId,
1981
+ user_id: toUin(memberUid === "" ? adminUid : memberUid, ctx)
1982
+ };
1983
+ }
1984
+ /** 禁言(duration>0=ban / 否则 lift_ban)。 */
1985
+ function toBan(common, grp, ctx) {
1986
+ const { time, selfId, groupId, adminUid, memberUid } = common;
1987
+ const shutUp = grp.shutUp;
1988
+ return {
1989
+ time,
1990
+ self_id: selfId,
1991
+ post_type: "notice",
1992
+ notice_type: "group_ban",
1993
+ sub_type: Number(shutUp?.duration ?? 0) > 0 ? "ban" : "lift_ban",
1994
+ group_id: groupId,
1995
+ operator_id: toUin(shutUp?.admin?.uid ?? adminUid, ctx),
1996
+ user_id: toUin(shutUp?.member?.uid ?? memberUid, ctx),
1997
+ duration: Number(shutUp?.duration ?? 0)
1998
+ };
1999
+ }
1981
2000
  /** RawMessage → OB11 notice 事件(无 grayTip 返回 null)。 */
1982
2001
  function toOb11NoticeEvent(msg, ctx) {
1983
2002
  for (const el of msg.elements) {
@@ -2024,6 +2043,48 @@ var GetForwardMsgAction = class extends BaseAction {
2024
2043
  return { messages: (await this.deps.msgApi.fetchForwardMessage(peer, msgId)).map((msg) => toOb11MessageInfo(msg, this.deps.messageUnique)) };
2025
2044
  }
2026
2045
  };
2046
+ /** 历史消息公共参数(message_seq / count / 反向排序)。 */
2047
+ const msgHistoryParamsSchema = {
2048
+ /** 起始消息序号(message_seq,缺省从最新拉)。 */
2049
+ message_seq: z.union([z.number(), z.string()]).optional(),
2050
+ count: z.number().default(20),
2051
+ /** 是否反向排序(NapCat 扩展)。 */
2052
+ reverse_order: z.boolean().optional(),
2053
+ reverseOrder: z.boolean().optional()
2054
+ };
2055
+ /** 解析 message_seq → 起始 msgId(int32 经 MessageUnique 反查,否则原样透传)。 */
2056
+ function resolveStartMsgId(messageSeq, messageUnique) {
2057
+ if (messageSeq === void 0) return;
2058
+ const raw = String(messageSeq);
2059
+ const shortId = Number(raw);
2060
+ if (Number.isFinite(shortId)) return messageUnique.getMsgId(shortId) ?? raw;
2061
+ return raw;
2062
+ }
2063
+ /**
2064
+ * 拉取历史消息并翻译为 OB11 消息信息数组。
2065
+ * 收集全部消息 at uid → 一次批量 uidToUin → 上下文注入(P2-19)。
2066
+ */
2067
+ async function fetchAndTranslateHistory(peer, messageSeq, count, deps) {
2068
+ const startMsgId = resolveStartMsgId(messageSeq, deps.messageUnique);
2069
+ const opts = { count };
2070
+ if (startMsgId !== void 0) opts.msgId = startMsgId;
2071
+ const msgs = await deps.msgApi.fetchMessages(peer, opts);
2072
+ if (msgs.length === 0) throw kernelError(`消息 ${messageSeq ?? "0"} 不存在`, "NOT_FOUND");
2073
+ const atUids = /* @__PURE__ */ new Set();
2074
+ for (const m of msgs) {
2075
+ const { atUids: uids } = collectReceiveNeeds(toCanonicalElements(m));
2076
+ for (const u of uids) atUids.add(u);
2077
+ }
2078
+ let uidToUin;
2079
+ if (atUids.size > 0) try {
2080
+ uidToUin = await deps.uidToUin([...atUids]);
2081
+ } catch {}
2082
+ const ctx = {
2083
+ ...uidToUin !== void 0 ? { uidToUin } : {},
2084
+ msgIdToOb11Id: (m) => deps.messageUnique.getMessageId(m)
2085
+ };
2086
+ return { messages: msgs.map((msg) => toOb11MessageInfo(msg, deps.messageUnique, ctx)) };
2087
+ }
2027
2088
  //#endregion
2028
2089
  //#region src/onebot11/action/message/get-friend-msg-history.ts
2029
2090
  /**
@@ -2033,10 +2094,7 @@ var GetForwardMsgAction = class extends BaseAction {
2033
2094
  */
2034
2095
  const getFriendMsgHistorySchema = z.object({
2035
2096
  user_id: z.number(),
2036
- message_seq: z.union([z.number(), z.string()]).optional(),
2037
- count: z.number().default(20),
2038
- reverse_order: z.boolean().optional(),
2039
- reverseOrder: z.boolean().optional()
2097
+ ...msgHistoryParamsSchema
2040
2098
  });
2041
2099
  /** 获取私聊历史消息(P2-10 接 kernel fetchMessages)。 */
2042
2100
  var GetFriendMsgHistoryAction = class extends BaseAction {
@@ -2049,37 +2107,11 @@ var GetFriendMsgHistoryAction = class extends BaseAction {
2049
2107
  this.deps = deps;
2050
2108
  }
2051
2109
  async _handle(payload) {
2052
- const uid = (await this.deps.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
2053
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
2054
- const peer = {
2110
+ const uid = await resolveUid(String(payload.user_id), this.deps.uinToUid);
2111
+ return await fetchAndTranslateHistory({
2055
2112
  chatType: ChatType.C2C,
2056
2113
  peerUid: uid
2057
- };
2058
- let startMsgId;
2059
- if (payload.message_seq !== void 0) {
2060
- const raw = String(payload.message_seq);
2061
- const shortId = Number(raw);
2062
- if (Number.isFinite(shortId)) startMsgId = this.deps.messageUnique.getMsgId(shortId);
2063
- startMsgId ??= raw;
2064
- }
2065
- const opts = { count: payload.count };
2066
- if (startMsgId !== void 0) opts.msgId = startMsgId;
2067
- const msgs = await this.deps.msgApi.fetchMessages(peer, opts);
2068
- if (msgs.length === 0) throw kernelError(`消息 ${payload.message_seq ?? "0"} 不存在`, "NOT_FOUND");
2069
- const atUids = /* @__PURE__ */ new Set();
2070
- for (const m of msgs) {
2071
- const { atUids: uids } = collectReceiveNeeds(toCanonicalElements(m));
2072
- for (const u of uids) atUids.add(u);
2073
- }
2074
- let uidToUin;
2075
- if (atUids.size > 0) try {
2076
- uidToUin = await this.deps.uidToUin([...atUids]);
2077
- } catch {}
2078
- const ctx = {
2079
- ...uidToUin !== void 0 ? { uidToUin } : {},
2080
- msgIdToOb11Id: (m) => this.deps.messageUnique.getMessageId(m)
2081
- };
2082
- return { messages: msgs.map((msg) => toOb11MessageInfo(msg, this.deps.messageUnique, ctx)) };
2114
+ }, payload.message_seq, payload.count, this.deps);
2083
2115
  }
2084
2116
  };
2085
2117
  //#endregion
@@ -2092,12 +2124,7 @@ var GetFriendMsgHistoryAction = class extends BaseAction {
2092
2124
  */
2093
2125
  const getGroupMsgHistorySchema = z.object({
2094
2126
  group_id: z.number(),
2095
- /** 起始消息序号(message_seq,缺省从最新拉)。 */
2096
- message_seq: z.union([z.number(), z.string()]).optional(),
2097
- count: z.number().default(20),
2098
- /** 是否反向排序(NapCat 扩展)。 */
2099
- reverse_order: z.boolean().optional(),
2100
- reverseOrder: z.boolean().optional()
2127
+ ...msgHistoryParamsSchema
2101
2128
  });
2102
2129
  /** 获取群历史消息(P2-10 接 kernel fetchMessages)。 */
2103
2130
  var GetGroupMsgHistoryAction = class extends BaseAction {
@@ -2110,35 +2137,10 @@ var GetGroupMsgHistoryAction = class extends BaseAction {
2110
2137
  this.deps = deps;
2111
2138
  }
2112
2139
  async _handle(payload) {
2113
- const peer = {
2140
+ return await fetchAndTranslateHistory({
2114
2141
  chatType: ChatType.GROUP,
2115
2142
  peerUid: String(payload.group_id)
2116
- };
2117
- let startMsgId;
2118
- if (payload.message_seq !== void 0) {
2119
- const raw = String(payload.message_seq);
2120
- const shortId = Number(raw);
2121
- if (Number.isFinite(shortId)) startMsgId = this.deps.messageUnique.getMsgId(shortId);
2122
- startMsgId ??= raw;
2123
- }
2124
- const opts = { count: payload.count };
2125
- if (startMsgId !== void 0) opts.msgId = startMsgId;
2126
- const msgs = await this.deps.msgApi.fetchMessages(peer, opts);
2127
- if (msgs.length === 0) throw kernelError(`消息 ${payload.message_seq ?? "0"} 不存在`, "NOT_FOUND");
2128
- const atUids = /* @__PURE__ */ new Set();
2129
- for (const m of msgs) {
2130
- const { atUids: uids } = collectReceiveNeeds(toCanonicalElements(m));
2131
- for (const u of uids) atUids.add(u);
2132
- }
2133
- let uidToUin;
2134
- if (atUids.size > 0) try {
2135
- uidToUin = await this.deps.uidToUin([...atUids]);
2136
- } catch {}
2137
- const ctx = {
2138
- ...uidToUin !== void 0 ? { uidToUin } : {},
2139
- msgIdToOb11Id: (m) => this.deps.messageUnique.getMessageId(m)
2140
- };
2141
- return { messages: msgs.map((msg) => toOb11MessageInfo(msg, this.deps.messageUnique, ctx)) };
2143
+ }, payload.message_seq, payload.count, this.deps);
2142
2144
  }
2143
2145
  };
2144
2146
  //#endregion
@@ -2154,19 +2156,38 @@ const getMediaSchema = z.object({
2154
2156
  file: z.string().optional(),
2155
2157
  message_id: z.union([z.number(), z.string()]).optional()
2156
2158
  });
2159
+ /**
2160
+ * 反查消息并提取媒体元素路径。
2161
+ * 返回 null = 消息不存在;extract 返回 undefined = 无匹配元素。
2162
+ */
2163
+ async function resolveMedia(id, label, deps, extract) {
2164
+ const { msgId, peer } = resolveMsgIdAndPeer(id, deps.messageUnique);
2165
+ const [first] = await deps.msgApi.fetchMsgsByMsgId(peer, [msgId]);
2166
+ if (first === void 0) throw kernelError(`消息 ${id} 不存在或已被撤回`, "NOT_FOUND");
2167
+ for (const el of first.elements) {
2168
+ const found = extract(el);
2169
+ if (found !== void 0) return found;
2170
+ }
2171
+ throw kernelError(`消息 ${id} 不包含${label}`, "NOT_FOUND");
2172
+ }
2157
2173
  /** 从消息元素提取媒体路径与 URL(PIC/PTT,纯函数)。 */
2158
2174
  function extractMediaPath(element) {
2159
2175
  const pic = element.picElement;
2160
2176
  if (pic !== void 0) {
2161
2177
  const out = {};
2162
2178
  const path = pic.sourcePath ?? pic.picPath;
2163
- if (path !== void 0) out.path = path;
2179
+ if (path !== void 0) out.file = path;
2164
2180
  if (pic.picUrl !== void 0) out.url = pic.picUrl;
2181
+ if (Object.keys(out).length === 0) return;
2165
2182
  return out;
2166
2183
  }
2167
2184
  const ptt = element.pttElement;
2168
- if (ptt !== void 0 && ptt.filePath !== void 0) return { path: ptt.filePath };
2169
- return {};
2185
+ if (ptt !== void 0 && ptt.filePath !== void 0) return { file: ptt.filePath };
2186
+ }
2187
+ /** 提取语音元素(get_record 用)。 */
2188
+ function extractPttPath(element) {
2189
+ const ptt = element.pttElement;
2190
+ if (ptt !== void 0 && ptt.filePath !== void 0) return { file: ptt.filePath };
2170
2191
  }
2171
2192
  /** 获取图片信息(P2-14)。 */
2172
2193
  var GetImageAction = class extends BaseAction {
@@ -2179,28 +2200,12 @@ var GetImageAction = class extends BaseAction {
2179
2200
  this.deps = deps;
2180
2201
  }
2181
2202
  _handle(payload) {
2182
- return this.resolveMedia(payload, "图片", "get_image");
2203
+ return this.resolve(payload, "图片", "get_image");
2183
2204
  }
2184
- /** 反查消息并提取媒体元素路径。 */
2185
- async resolveMedia(payload, label, actionName) {
2205
+ resolve(payload, label, actionName) {
2186
2206
  const id = payload.message_id ?? payload.file;
2187
2207
  if (id === void 0) throw kernelError(`${actionName} 需要 message_id 或 file`, "INVALID_PARAM");
2188
- const { msgId, peer } = resolveMsgIdAndPeer(id, this.deps.messageUnique);
2189
- const [first] = await this.deps.msgApi.fetchMsgsByMsgId(peer, [msgId]);
2190
- if (first === void 0) throw kernelError(`消息 ${id} 不存在或已被撤回`, "NOT_FOUND");
2191
- let found;
2192
- for (const el of first.elements) {
2193
- const media = extractMediaPath(el);
2194
- if (media.path !== void 0 || media.url !== void 0) {
2195
- found = media;
2196
- break;
2197
- }
2198
- }
2199
- if (found === void 0) throw kernelError(`消息 ${id} 不包含${label}`, "NOT_FOUND");
2200
- const result = {};
2201
- if (found.path !== void 0) result.file = found.path;
2202
- if (found.url !== void 0) result.url = found.url;
2203
- return result;
2208
+ return resolveMedia(id, label, this.deps, extractMediaPath);
2204
2209
  }
2205
2210
  };
2206
2211
  /** 获取语音信息(P2-14)。 */
@@ -2216,14 +2221,7 @@ var GetRecordAction = class extends BaseAction {
2216
2221
  async _handle(payload) {
2217
2222
  const id = payload.message_id ?? payload.file;
2218
2223
  if (id === void 0) throw kernelError("get_record 需要 message_id 或 file", "INVALID_PARAM");
2219
- const { msgId, peer } = resolveMsgIdAndPeer(id, this.deps.messageUnique);
2220
- const [first] = await this.deps.msgApi.fetchMsgsByMsgId(peer, [msgId]);
2221
- if (first === void 0) throw kernelError(`消息 ${id} 不存在或已被撤回`, "NOT_FOUND");
2222
- for (const el of first.elements) {
2223
- const ptt = el.pttElement;
2224
- if (ptt !== void 0 && ptt.filePath !== void 0) return { file: ptt.filePath };
2225
- }
2226
- throw kernelError(`消息 ${id} 不包含语音`, "NOT_FOUND");
2224
+ return await resolveMedia(id, "语音", this.deps, extractPttPath);
2227
2225
  }
2228
2226
  };
2229
2227
  //#endregion
@@ -2245,9 +2243,7 @@ var GetMsgAction = class extends BaseAction {
2245
2243
  this.deps = deps;
2246
2244
  }
2247
2245
  async _handle(payload) {
2248
- const { msgId, peer } = resolveMsgIdAndPeer(payload.message_id, this.deps.messageUnique);
2249
- const [msg] = await this.deps.msgApi.fetchMsgsByMsgId(peer, [msgId]);
2250
- if (msg === void 0) throw kernelError(`消息 ${payload.message_id} 不存在或已被撤回`, "NOT_FOUND");
2246
+ const { msg } = await fetchMsgById(payload.message_id, this.deps.messageUnique, this.deps.msgApi);
2251
2247
  const { atUids } = collectReceiveNeeds(toCanonicalElements(msg));
2252
2248
  let uidToUin;
2253
2249
  if (atUids.length > 0) try {
@@ -2292,8 +2288,7 @@ var MarkMsgAsReadAction = class extends BaseAction {
2292
2288
  return null;
2293
2289
  }
2294
2290
  if (payload.user_id !== void 0) {
2295
- const uid = (await this.deps.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
2296
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
2291
+ const uid = await resolveUid(String(payload.user_id), this.deps.uinToUid);
2297
2292
  await this.deps.msgApi.markRead({
2298
2293
  chatType: ChatType.C2C,
2299
2294
  peerUid: uid
@@ -2324,8 +2319,7 @@ var MarkPrivateMsgAsReadAction = class extends BaseAction {
2324
2319
  this.deps = deps;
2325
2320
  }
2326
2321
  async _handle(payload) {
2327
- const uid = (await this.deps.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
2328
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
2322
+ const uid = await resolveUid(String(payload.user_id), this.deps.uinToUid);
2329
2323
  await this.deps.msgApi.markRead({
2330
2324
  chatType: ChatType.C2C,
2331
2325
  peerUid: uid
@@ -2393,6 +2387,14 @@ function resolveSourceMessages(messages, unique) {
2393
2387
  }
2394
2388
  return out;
2395
2389
  }
2390
+ /** 合并转发发送(群/私聊共用:解析源消息 → 发送 → 映射 message_id)。 */
2391
+ async function sendForward(messages, target, deps) {
2392
+ const sources = resolveSourceMessages(messages, deps.messageUnique);
2393
+ const [firstSource] = sources;
2394
+ if (firstSource === void 0) throw kernelError("合并转发需要至少一条可解析的源消息(node.id)", "INVALID_PARAM");
2395
+ const { msgId } = await deps.msgApi.sendForwardMessage(target, firstSource.peer, sources.map((s) => s.msgId));
2396
+ return { message_id: deps.messageUnique.alloc(msgId, target) };
2397
+ }
2396
2398
  /** 发送群合并转发(P2-12 接 kernel sendForwardMessage)。 */
2397
2399
  var SendGroupForwardMsgAction = class extends BaseAction {
2398
2400
  name = "send_group_forward_msg";
@@ -2404,15 +2406,11 @@ var SendGroupForwardMsgAction = class extends BaseAction {
2404
2406
  this.deps = deps;
2405
2407
  }
2406
2408
  async _handle(payload) {
2407
- const sources = resolveSourceMessages(payload.messages, this.deps.messageUnique);
2408
- const [firstSource] = sources;
2409
- if (firstSource === void 0) throw kernelError("合并转发需要至少一条可解析的源消息(node.id)", "INVALID_PARAM");
2410
2409
  const target = {
2411
2410
  chatType: ChatType.GROUP,
2412
2411
  peerUid: String(payload.group_id)
2413
2412
  };
2414
- const { msgId } = await this.deps.msgApi.sendForwardMessage(target, firstSource.peer, sources.map((s) => s.msgId));
2415
- return { message_id: this.deps.messageUnique.alloc(msgId, target) };
2413
+ return await sendForward(payload.messages, target, this.deps);
2416
2414
  }
2417
2415
  };
2418
2416
  /** 发送私聊合并转发(P2-12)。 */
@@ -2426,31 +2424,16 @@ var SendPrivateForwardMsgAction = class extends BaseAction {
2426
2424
  this.deps = deps;
2427
2425
  }
2428
2426
  async _handle(payload) {
2429
- const sources = resolveSourceMessages(payload.messages, this.deps.messageUnique);
2430
- const [firstSource] = sources;
2431
- if (firstSource === void 0) throw kernelError("合并转发需要至少一条可解析的源消息(node.id)", "INVALID_PARAM");
2432
- const uid = (await this.deps.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
2433
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
2427
+ const uid = await resolveUid(String(payload.user_id), this.deps.uinToUid);
2434
2428
  const target = {
2435
2429
  chatType: ChatType.C2C,
2436
2430
  peerUid: uid
2437
2431
  };
2438
- const { msgId } = await this.deps.msgApi.sendForwardMessage(target, firstSource.peer, sources.map((s) => s.msgId));
2439
- return { message_id: this.deps.messageUnique.alloc(msgId, target) };
2432
+ return await sendForward(payload.messages, target, this.deps);
2440
2433
  }
2441
2434
  };
2442
2435
  //#endregion
2443
2436
  //#region src/onebot11/action/message/send-msg.ts
2444
- /**
2445
- * send_msg 动作:发送消息(群聊/私聊)+ 共享发送核心 sendOb11Message
2446
- *
2447
- * P2-3 真实化:注入 kernel MsgApi,message 参数(CQ 码字符串或 segment 数组)
2448
- * 翻译为 canonical 元素 → MsgApi.sendMessage → 返回真实 message_id。
2449
- * send_private_msg / send_group_msg 复用 sendOb11Message(P2-10)。
2450
- *
2451
- * 群聊:group_id → Peer{ chatType: GROUP, peerUid: String(group_id) }(群消息 peerUid=群号)。
2452
- * 私聊:user_id 经 uin→uid → Peer{ chatType: C2C, peerUid: uid }。
2453
- */
2454
2437
  const sendMsgSchema = z.object({
2455
2438
  message_type: z.enum(["group", "private"]).optional(),
2456
2439
  user_id: z.number().optional(),
@@ -2458,25 +2441,9 @@ const sendMsgSchema = z.object({
2458
2441
  message: z.union([z.string(), z.array(z.unknown())]),
2459
2442
  auto_escape: z.boolean().optional()
2460
2443
  });
2461
- /** 解析目标 Peer(group_id 直通;user_id 经 uin→uid)。 */
2462
- async function resolvePeer(payload, deps) {
2463
- if (payload.group_id !== void 0) return {
2464
- chatType: ChatType.GROUP,
2465
- peerUid: String(payload.group_id)
2466
- };
2467
- if (payload.user_id !== void 0) {
2468
- const uid = (await deps.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
2469
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
2470
- return {
2471
- chatType: ChatType.C2C,
2472
- peerUid: uid
2473
- };
2474
- }
2475
- throw kernelError("send_msg 需要 group_id 或 user_id", "INVALID_PARAM");
2476
- }
2477
2444
  /** 共享发送核心:解析 Peer → canonical 翻译(含 auto_escape)→ sendMessage → 映射 message_id。 */
2478
2445
  async function sendOb11Message(payload, deps) {
2479
- const peer = await resolvePeer(payload, deps);
2446
+ const peer = await resolveTargetPeer(payload, deps, "send_msg 需要 group_id 或 user_id");
2480
2447
  let canonical;
2481
2448
  if (Array.isArray(payload.message)) canonical = segmentsToCanonical(payload.message);
2482
2449
  else canonical = cqMessageToCanonical(payload.message);
@@ -2592,8 +2559,7 @@ var SetInputStatusAction = class extends BaseAction {
2592
2559
  this.deps = deps;
2593
2560
  }
2594
2561
  async _handle(payload) {
2595
- const uid = (await this.deps.uinToUid([String(payload.user_id)])).get(String(payload.user_id));
2596
- if (uid === void 0) throw kernelError(`用户 ${payload.user_id} 的 uid 解析失败`, "INVALID_PARAM");
2562
+ const uid = await resolveUid(String(payload.user_id), this.deps.uinToUid);
2597
2563
  await this.deps.msgApi.setInputStatus({
2598
2564
  chatType: ChatType.C2C,
2599
2565
  peerUid: uid
@@ -2628,9 +2594,7 @@ var SetMsgEmojiLikeAction = class extends BaseAction {
2628
2594
  this.deps = deps;
2629
2595
  }
2630
2596
  async _handle(payload) {
2631
- const { msgId, peer } = resolveMsgIdAndPeer(payload.message_id, this.deps.messageUnique);
2632
- const [msg] = await this.deps.msgApi.fetchMsgsByMsgId(peer, [msgId]);
2633
- if (msg === void 0) throw kernelError(`消息 ${payload.message_id} 不存在或已被撤回`, "NOT_FOUND");
2597
+ const { msg, peer } = await fetchMsgById(payload.message_id, this.deps.messageUnique, this.deps.msgApi);
2634
2598
  await this.deps.msgApi.setMsgEmojiLike(peer, {
2635
2599
  msgSeq: msg.msgSeq,
2636
2600
  emojiId: String(payload.emoji_id),
@@ -3131,6 +3095,9 @@ var TranslateEn2ZhAction = class extends BaseAction {
3131
3095
  * OneBot 11 动作注册表(ADR-013 延伸)
3132
3096
  * 各协议维护自己的 ActionRegistry,由协议 adapter 挂到请求分发。
3133
3097
  * P2-16:deps 收敛为 { api: OneBotApi } 单聚合对象(基础设施第一项)。
3098
+ *
3099
+ * 同时是 action 域 barrel:re-export 全部动作类 + error-map + resolve-uid,
3100
+ * 协议外部只从本入口消费动作面。
3134
3101
  */
3135
3102
  /** 构建 OB11 动作注册表(所有 OB11 动作在此注册,按组拆分控制行数)。 */
3136
3103
  function createOb11ActionRegistry(deps) {
@@ -3424,15 +3391,27 @@ function makeAuthorize(token) {
3424
3391
  return hasAccessTokenQuery(ctx.url, token);
3425
3392
  };
3426
3393
  }
3394
+ /**
3395
+ * OneBot 客户端标识头:koishi 的 onebot 适配器按
3396
+ * x-client-role === "Universal" 与 x-self-id 校验 WS 连接(NapCat 同款头),
3397
+ * 缺失会被 close(1008) 拒绝——连接虽握手成功但应用层不认。
3398
+ */
3399
+ function oneBotClientHeaders(selfUin) {
3400
+ return {
3401
+ "X-Self-ID": selfUin,
3402
+ "x-client-role": "Universal",
3403
+ "User-Agent": "OneBot/11"
3404
+ };
3405
+ }
3427
3406
  /** 按配置装配 OB11 传输。 */
3428
3407
  function assembleOb11Transports(opts) {
3429
- const { config, broadcaster, handleRequest } = opts;
3408
+ const { config, broadcaster, handleRequest, selfUin } = opts;
3430
3409
  const servers = [];
3431
3410
  const transports = [];
3432
3411
  assembleHttpServers(config, handleRequest, servers);
3433
3412
  assembleWsServers(config, handleRequest, servers);
3434
- assembleHttpPosts(config, transports);
3435
- assembleWsReverses(config, handleRequest, transports);
3413
+ assembleHttpPosts(config, selfUin, transports);
3414
+ assembleWsReverses(config, selfUin, handleRequest, transports);
3436
3415
  for (const t of transports) broadcaster.register(t);
3437
3416
  for (const s of servers) broadcaster.register(s);
3438
3417
  return {
@@ -3474,19 +3453,27 @@ function assembleWsServers(config, handleRequest, servers) {
3474
3453
  }
3475
3454
  }
3476
3455
  /** 装配 HTTP 正向上报 client 列表(fire-and-forget)。 */
3477
- function assembleHttpPosts(config, transports) {
3456
+ function assembleHttpPosts(config, selfUin, transports) {
3478
3457
  for (const item of config.httpPostUrls) if (item.enabled && item.url !== void 0) {
3479
- const headers = authorizeHeader(item.token ?? config.token);
3480
- const options = { url: item.url };
3481
- if (headers !== void 0) options.headers = headers;
3458
+ const headers = {
3459
+ "x-self-id": selfUin,
3460
+ ...authorizeHeader(item.token ?? config.token) ?? {}
3461
+ };
3462
+ const options = {
3463
+ url: item.url,
3464
+ headers
3465
+ };
3482
3466
  if (item.timeoutMs !== void 0) options.timeoutMs = item.timeoutMs;
3483
3467
  transports.push(new HttpClient(options));
3484
3468
  }
3485
3469
  }
3486
3470
  /** 装配正向 WS client 列表(双向,心跳 + 重连 + 证书选项)。 */
3487
- function assembleWsReverses(config, handleRequest, transports) {
3471
+ function assembleWsReverses(config, selfUin, handleRequest, transports) {
3488
3472
  for (const item of config.wsReverseUrls) if (item.enabled && item.url !== void 0) {
3489
- const headers = authorizeHeader(item.token ?? config.token);
3473
+ const headers = {
3474
+ ...oneBotClientHeaders(selfUin),
3475
+ ...authorizeHeader(item.token ?? config.token) ?? {}
3476
+ };
3490
3477
  const reconnect = {
3491
3478
  enabled: true,
3492
3479
  delayMs: item.reconnectDelayMs
@@ -3494,11 +3481,11 @@ function assembleWsReverses(config, handleRequest, transports) {
3494
3481
  if (item.maxReconnectAttempts !== void 0) reconnect.maxAttempts = item.maxReconnectAttempts;
3495
3482
  const options = {
3496
3483
  url: item.url,
3484
+ headers,
3497
3485
  heartbeat: { intervalMs: item.heartbeatInterval },
3498
3486
  reconnect,
3499
3487
  rejectUnauthorized: item.rejectUnauthorized
3500
3488
  };
3501
- if (headers !== void 0) options.headers = headers;
3502
3489
  const client = new WsClient(options);
3503
3490
  client.onRequest = handleRequest;
3504
3491
  transports.push(client);
@@ -3552,6 +3539,7 @@ var NapukettoOneBot11Adapter = class extends BaseProtocolAdapter {
3552
3539
  this.transports = assembleOb11Transports({
3553
3540
  config,
3554
3541
  broadcaster,
3542
+ selfUin: this.selfUin,
3555
3543
  handleRequest: (req, respond) => {
3556
3544
  this.handleRequest(req, respond).catch((err) => {
3557
3545
  let message = String(err);
@@ -3611,16 +3599,14 @@ var NapukettoOneBot11Adapter = class extends BaseProtocolAdapter {
3611
3599
  subscribe() {
3612
3600
  if (this.unsubscribe !== null) return;
3613
3601
  this.unsubscribe = this.msgChannel.on("Msg/onRecvMsg", (msgs) => {
3614
- const list = Array.isArray(msgs) ? msgs : [msgs];
3615
- for (const msg of list) {
3616
- if (!msg || typeof msg !== "object") continue;
3602
+ forEachRawMessage(msgs, (msg) => {
3617
3603
  if (hasGrayTip(msg)) {
3618
3604
  this.broadcastNotice(msg).catch(() => {});
3619
- continue;
3605
+ return;
3620
3606
  }
3621
- if (!this.reportSelfMessage && String(msg.senderUin) === this.selfUin) continue;
3607
+ if (!this.reportSelfMessage && String(msg.senderUin) === this.selfUin) return;
3622
3608
  this.broadcastMessageEvent(msg);
3623
- }
3609
+ });
3624
3610
  });
3625
3611
  }
3626
3612
  /**