@naplink/naplink 0.0.6 → 0.0.10

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/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  - ⏱️ **超时控制** - API 调用和连接超时保护
16
16
  - 🎯 **事件驱动** - 完整的 OneBot 11 事件支持
17
17
  - 📊 **完善日志** - 分级日志系统,支持自定义 logger
18
- - 💪 **稳定可靠** - 自动心跳检测,零运行时依赖
18
+ - 💪 **稳定可靠** - 自动心跳检测,轻量运行时依赖
19
19
 
20
20
  ## 📚 文档
21
21
 
@@ -32,7 +32,9 @@
32
32
  ## 📦 安装
33
33
 
34
34
  ```bash
35
- pnpm add naplink
35
+ pnpm add @naplink/naplink
36
+ npm install @naplink/naplink
37
+ yarn add @naplink/naplink
36
38
  ```
37
39
 
38
40
  ## 🔧 配置示例
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ interface NapLinkConfig {
18
18
  /** 自定义心跳动作(默认 get_status) */
19
19
  heartbeatAction?: {
20
20
  action: string;
21
- params?: Record<string, any>;
21
+ params?: Record<string, unknown>;
22
22
  };
23
23
  };
24
24
  /** 重连配置 */
@@ -57,10 +57,10 @@ interface NapLinkConfig {
57
57
  * 允许用户提供自定义logger实现
58
58
  */
59
59
  interface Logger {
60
- debug(message: string, ...meta: any[]): void;
61
- info(message: string, ...meta: any[]): void;
62
- warn(message: string, ...meta: any[]): void;
63
- error(message: string, error?: Error, ...meta: any[]): void;
60
+ debug(message: string, ...meta: unknown[]): void;
61
+ info(message: string, ...meta: unknown[]): void;
62
+ warn(message: string, ...meta: unknown[]): void;
63
+ error(message: string, error?: Error, ...meta: unknown[]): void;
64
64
  }
65
65
  /**
66
66
  * 部分配置类型(用于构造函数)
@@ -74,7 +74,7 @@ type PartialNapLinkConfig = {
74
74
  pingInterval?: number;
75
75
  heartbeatAction?: {
76
76
  action: string;
77
- params?: Record<string, any>;
77
+ params?: Record<string, unknown>;
78
78
  };
79
79
  };
80
80
  reconnect?: Partial<NapLinkConfig['reconnect']>;
@@ -109,7 +109,7 @@ declare class ConnectionManager {
109
109
  private connectPromise?;
110
110
  private connectTimeout?;
111
111
  private wasReconnecting;
112
- constructor(config: NapLinkConfig, logger: Logger, onMessage: (data: string) => void, onStateChange: (state: ConnectionState, wasReconnecting?: boolean) => void, emitter?: any | undefined);
112
+ constructor(config: NapLinkConfig, logger: Logger, onMessage: (data: string) => void, onStateChange: (state: ConnectionState, wasReconnecting?: boolean) => void, emitter?: Pick<EventEmitter, "emit"> | undefined);
113
113
  /**
114
114
  * 连接到WebSocket服务器
115
115
  */
@@ -154,6 +154,14 @@ declare class ConnectionManager {
154
154
  private clearConnectTimeout;
155
155
  }
156
156
 
157
+ type ApiResponseEnvelope = {
158
+ stream?: string;
159
+ status?: string;
160
+ retcode?: number;
161
+ message?: string;
162
+ wording?: string;
163
+ data?: Record<string, unknown>;
164
+ };
157
165
  /**
158
166
  * API客户端
159
167
  * 负责发送API请求并处理响应
@@ -173,7 +181,7 @@ declare class ApiClient {
173
181
  * @param params API参数
174
182
  * @param options 调用选项
175
183
  */
176
- call<T = any>(method: string, params?: any, options?: {
184
+ call<T = unknown>(method: string, params?: Record<string, unknown>, options?: {
177
185
  timeout?: number;
178
186
  retries?: number;
179
187
  }): Promise<T>;
@@ -181,7 +189,7 @@ declare class ApiClient {
181
189
  * 调用流式 API(NapCat stream-action)
182
190
  * 会持续产出 data.type=stream 的分片包,并在 data.type=response 时结束。
183
191
  */
184
- callStream<TPacket = any, TFinal = any>(method: string, params?: any, options?: {
192
+ callStream<TPacket = unknown, TFinal = unknown>(method: string, params?: Record<string, unknown>, options?: {
185
193
  timeout?: number;
186
194
  }): {
187
195
  packets: AsyncIterable<TPacket>;
@@ -191,11 +199,16 @@ declare class ApiClient {
191
199
  * 处理API响应
192
200
  * 由连接管理器调用
193
201
  */
194
- handleResponse(echo: string, response: any): void;
202
+ handleResponse(echo: string, response: ApiResponseEnvelope): void;
195
203
  /**
196
204
  * 销毁API客户端
197
205
  */
198
206
  destroy(): void;
207
+ /**
208
+ * 清理所有待处理请求,但保留客户端可继续使用。
209
+ * 适用于临时断线、主动 disconnect 后再次 connect 的场景。
210
+ */
211
+ clearPendingRequests(reason: string): void;
199
212
  /**
200
213
  * 发送API请求
201
214
  */
@@ -215,6 +228,93 @@ declare class ApiClient {
215
228
  private startCleanupTimer;
216
229
  }
217
230
 
231
+ type TextSegment = {
232
+ type: 'text';
233
+ data: {
234
+ text: string;
235
+ };
236
+ };
237
+ type AtSegment = {
238
+ type: 'at';
239
+ data: {
240
+ qq: string | 'all';
241
+ };
242
+ };
243
+ type FaceSegment = {
244
+ type: 'face';
245
+ data: {
246
+ id: string;
247
+ };
248
+ };
249
+ type ReplySegment = {
250
+ type: 'reply';
251
+ data: {
252
+ id: string;
253
+ };
254
+ };
255
+ type ImageSegment = {
256
+ type: 'image';
257
+ data: {
258
+ file: string;
259
+ file_id?: string;
260
+ url?: string;
261
+ summary?: string;
262
+ sub_type?: string;
263
+ };
264
+ };
265
+ type RecordSegment = {
266
+ type: 'record';
267
+ data: {
268
+ file: string;
269
+ file_id?: string;
270
+ url?: string;
271
+ };
272
+ };
273
+ type AudioSegment = {
274
+ type: 'audio';
275
+ data: {
276
+ file: string;
277
+ file_id?: string;
278
+ url?: string;
279
+ };
280
+ };
281
+ type VideoSegment = {
282
+ type: 'video';
283
+ data: {
284
+ file: string;
285
+ file_id?: string;
286
+ url?: string;
287
+ };
288
+ };
289
+ type FileSegment = {
290
+ type: 'file';
291
+ data: {
292
+ file: string;
293
+ file_id?: string;
294
+ url?: string;
295
+ name?: string;
296
+ };
297
+ };
298
+ type JsonSegment = {
299
+ type: 'json';
300
+ data: {
301
+ data: string;
302
+ };
303
+ };
304
+ type XmlSegment = {
305
+ type: 'xml';
306
+ data: {
307
+ data: string;
308
+ };
309
+ };
310
+ type MarkdownSegment = {
311
+ type: 'markdown';
312
+ data: {
313
+ content: string;
314
+ };
315
+ };
316
+ type OneBotMessageSegment = TextSegment | AtSegment | FaceSegment | ReplySegment | ImageSegment | RecordSegment | AudioSegment | VideoSegment | FileSegment | JsonSegment | XmlSegment | MarkdownSegment;
317
+
218
318
  /**
219
319
  * OneBot 11 Protocol Base Types
220
320
  */
@@ -224,7 +324,7 @@ interface BaseEvent {
224
324
  self_id: number;
225
325
  post_type: PostType;
226
326
  }
227
- type MessageSegment = any;
327
+ type MessageSegment = OneBotMessageSegment;
228
328
 
229
329
  interface MessageEvent extends BaseEvent {
230
330
  post_type: 'message';
@@ -344,7 +444,7 @@ interface FriendAddNotice extends NoticeEvent {
344
444
  interface NotifyNotice extends NoticeEvent {
345
445
  notice_type: 'notify';
346
446
  sub_type: string;
347
- [key: string]: any;
447
+ [key: string]: unknown;
348
448
  }
349
449
  interface PokeNotice extends NotifyNotice {
350
450
  sub_type: 'poke';
@@ -391,7 +491,7 @@ interface LifecycleMetaEvent extends MetaEvent {
391
491
  }
392
492
  interface HeartbeatMetaEvent extends MetaEvent {
393
493
  meta_event_type: 'heartbeat';
394
- status: any;
494
+ status: Record<string, unknown>;
395
495
  interval: number;
396
496
  }
397
497
 
@@ -405,6 +505,8 @@ interface GroupHonorInfo {
405
505
  emotion_list?: any[];
406
506
  }
407
507
 
508
+ type OneBotEvent = MessageEvent | PrivateMessageEvent | GroupMessageEvent | NoticeEvent | GroupRecallNotice | FriendRecallNotice | GroupUploadNotice | GroupAdminNotice | GroupDecreaseNotice | GroupIncreaseNotice | FriendAddNotice | PokeNotice | GroupGrayTipNotice | RequestEvent | FriendRequest | GroupRequest | MetaEvent | LifecycleMetaEvent | HeartbeatMetaEvent;
509
+
408
510
  type MessageApi = {
409
511
  sendMessage(params: {
410
512
  message_type?: 'private' | 'group';
@@ -457,11 +559,15 @@ type MessageApi = {
457
559
  sendPoke(targetId: number | string, groupId?: number | string): Promise<any>;
458
560
  };
459
561
 
562
+ type MediaFileResponse = {
563
+ file?: string;
564
+ url?: string;
565
+ };
460
566
  type MediaApi = {
461
- getImage(file: string): Promise<any>;
462
- getRecord(file: string, outFormat?: string): Promise<any>;
463
- getFile(file: string): Promise<any>;
464
- hydrateMedia(message: any[]): Promise<void>;
567
+ getImage(file: string): Promise<MediaFileResponse>;
568
+ getRecord(file: string, outFormat?: string): Promise<MediaFileResponse>;
569
+ getFile(file: string): Promise<MediaFileResponse>;
570
+ hydrateMedia(message: OneBotMessageSegment[]): Promise<void>;
465
571
  };
466
572
 
467
573
  type AccountApi = {
@@ -979,15 +1085,20 @@ declare class NapLink extends EventEmitter {
979
1085
  * 补充消息中的媒体直链
980
1086
  * 自动通过 get_file / get_image / get_record 获取真实下载链接
981
1087
  */
982
- hydrateMessage(message: any[]): Promise<void>;
1088
+ hydrateMessage(message: OneBotMessageSegment[]): Promise<void>;
983
1089
  /**
984
1090
  * 调用自定义API
985
1091
  */
986
- callApi<T = any>(method: string, params?: any): Promise<T>;
1092
+ callApi<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
987
1093
  /**
988
1094
  * 暴露 OneBot API 便于直接使用
989
1095
  */
990
1096
  get api(): OneBotApi;
1097
+ /**
1098
+ * 销毁客户端实例
1099
+ * 调用后不应再复用当前实例。
1100
+ */
1101
+ dispose(): void;
991
1102
  /**
992
1103
  * 设置事件转发
993
1104
  */
@@ -1002,8 +1113,8 @@ interface NapLink extends OneBotApiMethods {
1002
1113
  */
1003
1114
  declare class NapLinkError extends Error {
1004
1115
  code: string;
1005
- details?: any | undefined;
1006
- constructor(message: string, code: string, details?: any | undefined);
1116
+ details?: unknown | undefined;
1117
+ constructor(message: string, code: string, details?: unknown | undefined);
1007
1118
  /**
1008
1119
  * 转换为JSON格式
1009
1120
  */
@@ -1011,7 +1122,7 @@ declare class NapLinkError extends Error {
1011
1122
  name: string;
1012
1123
  message: string;
1013
1124
  code: string;
1014
- details: any;
1125
+ details: unknown;
1015
1126
  };
1016
1127
  }
1017
1128
  /**
@@ -1019,7 +1130,7 @@ declare class NapLinkError extends Error {
1019
1130
  * 当WebSocket连接失败时抛出
1020
1131
  */
1021
1132
  declare class ConnectionError extends NapLinkError {
1022
- constructor(message: string, cause?: any);
1133
+ constructor(message: string, cause?: unknown);
1023
1134
  }
1024
1135
  /**
1025
1136
  * API超时错误
@@ -1054,75 +1165,4 @@ declare class InvalidConfigError extends NapLinkError {
1054
1165
  constructor(field: string, reason: string);
1055
1166
  }
1056
1167
 
1057
- type TextSegment = {
1058
- type: 'text';
1059
- data: {
1060
- text: string;
1061
- };
1062
- };
1063
- type AtSegment = {
1064
- type: 'at';
1065
- data: {
1066
- qq: string | 'all';
1067
- };
1068
- };
1069
- type FaceSegment = {
1070
- type: 'face';
1071
- data: {
1072
- id: string;
1073
- };
1074
- };
1075
- type ReplySegment = {
1076
- type: 'reply';
1077
- data: {
1078
- id: string;
1079
- };
1080
- };
1081
- type ImageSegment = {
1082
- type: 'image';
1083
- data: {
1084
- file: string;
1085
- summary?: string;
1086
- sub_type?: string;
1087
- };
1088
- };
1089
- type RecordSegment = {
1090
- type: 'record';
1091
- data: {
1092
- file: string;
1093
- };
1094
- };
1095
- type VideoSegment = {
1096
- type: 'video';
1097
- data: {
1098
- file: string;
1099
- };
1100
- };
1101
- type FileSegment = {
1102
- type: 'file';
1103
- data: {
1104
- file: string;
1105
- name?: string;
1106
- };
1107
- };
1108
- type JsonSegment = {
1109
- type: 'json';
1110
- data: {
1111
- data: string;
1112
- };
1113
- };
1114
- type XmlSegment = {
1115
- type: 'xml';
1116
- data: {
1117
- data: string;
1118
- };
1119
- };
1120
- type MarkdownSegment = {
1121
- type: 'markdown';
1122
- data: {
1123
- content: string;
1124
- };
1125
- };
1126
- type OneBotMessageSegment = TextSegment | AtSegment | FaceSegment | ReplySegment | ImageSegment | RecordSegment | VideoSegment | FileSegment | JsonSegment | XmlSegment | MarkdownSegment;
1127
-
1128
- export { type Anonymous, ApiError, ApiTimeoutError, type AtSegment, type BaseEvent, ConnectionClosedError, ConnectionError, ConnectionState, type FaceSegment, type FileInfo, type FileSegment, type FriendAddNotice, type FriendRecallNotice, type FriendRequest, type GroupAdminNotice, type GroupDecreaseNotice, type GroupGrayTipNotice, type GroupHonorInfo, type GroupIncreaseNotice, type GroupInviteRequest, type GroupJoinRequest, type GroupMessageEvent, type GroupRecallNotice, type GroupRequest, type GroupSystemMessages, type GroupUploadNotice, type HeartbeatMetaEvent, type ImageSegment, InvalidConfigError, type JsonSegment, type LifecycleMetaEvent, type Logger, type MarkdownSegment, MaxReconnectAttemptsError, type MessageEvent, type MessageSegment, type MetaEvent, NapLink, type NapLinkConfig, NapLinkError, type NoticeEvent, type NotifyNotice, OneBotApi, type OneBotMessageSegment, type PartialNapLinkConfig, type PokeNotice, type PostType, type PrivateMessageEvent, type RecordSegment, type ReplySegment, type RequestEvent, type Sender, type TextSegment, type VideoSegment, type XmlSegment, NapLink as default };
1168
+ export { type Anonymous, ApiError, ApiTimeoutError, type AtSegment, type AudioSegment, type BaseEvent, ConnectionClosedError, ConnectionError, ConnectionState, type FaceSegment, type FileInfo, type FileSegment, type FriendAddNotice, type FriendRecallNotice, type FriendRequest, type GroupAdminNotice, type GroupDecreaseNotice, type GroupGrayTipNotice, type GroupHonorInfo, type GroupIncreaseNotice, type GroupInviteRequest, type GroupJoinRequest, type GroupMessageEvent, type GroupRecallNotice, type GroupRequest, type GroupSystemMessages, type GroupUploadNotice, type HeartbeatMetaEvent, type ImageSegment, InvalidConfigError, type JsonSegment, type LifecycleMetaEvent, type Logger, type MarkdownSegment, MaxReconnectAttemptsError, type MessageEvent, type MessageSegment, type MetaEvent, NapLink, type NapLinkConfig, NapLinkError, type NoticeEvent, type NotifyNotice, OneBotApi, type OneBotEvent, type OneBotMessageSegment, type PartialNapLinkConfig, type PokeNotice, type PostType, type PrivateMessageEvent, type RecordSegment, type ReplySegment, type RequestEvent, type Sender, type TextSegment, type VideoSegment, type XmlSegment, NapLink as default };
package/dist/index.js CHANGED
@@ -293,8 +293,14 @@ var ConnectionState = /* @__PURE__ */ ((ConnectionState3) => {
293
293
  function buildWebSocketUrl(config) {
294
294
  const { url, token } = config.connection;
295
295
  if (token) {
296
- const separator = url.includes("?") ? "&" : "?";
297
- return `${url}${separator}access_token=${token}`;
296
+ try {
297
+ const parsed = new URL(url);
298
+ parsed.searchParams.set("access_token", token);
299
+ return parsed.toString();
300
+ } catch {
301
+ const separator = url.includes("?") ? "&" : "?";
302
+ return `${url}${separator}access_token=${encodeURIComponent(token)}`;
303
+ }
298
304
  }
299
305
  return url;
300
306
  }
@@ -462,6 +468,17 @@ var ConnectionManager = class {
462
468
  const url = buildWebSocketUrl(this.config);
463
469
  this.logger.info(`\u8FDE\u63A5\u5230 ${url}`);
464
470
  try {
471
+ if (this.ws) {
472
+ this.logger.debug("\u6E05\u7406\u65E7\u7684 WebSocket \u8FDE\u63A5");
473
+ this.ws.onopen = null;
474
+ this.ws.onclose = null;
475
+ this.ws.onerror = null;
476
+ this.ws.onmessage = null;
477
+ if (this.ws.readyState === WebSocket2.OPEN || this.ws.readyState === WebSocket2.CONNECTING) {
478
+ this.ws.close(1001, "\u6B63\u5728\u91CD\u65B0\u8FDE\u63A5");
479
+ }
480
+ this.ws = void 0;
481
+ }
465
482
  this.ws = new WebSocket2(url);
466
483
  } catch (error) {
467
484
  const err = new ConnectionError("WebSocket \u521B\u5EFA\u5931\u8D25", error);
@@ -521,15 +538,23 @@ var ConnectionManager = class {
521
538
  this.logger.info(`\u65AD\u5F00\u8FDE\u63A5: ${reason}`);
522
539
  this.reconnectService.cancel();
523
540
  this.stopHeartbeat();
541
+ this.clearConnectTimeout();
524
542
  if (this.ws) {
525
543
  try {
526
- this.ws.close(code, reason);
544
+ this.ws.onopen = null;
545
+ this.ws.onclose = null;
546
+ this.ws.onerror = null;
547
+ this.ws.onmessage = null;
548
+ if (this.ws.readyState === WebSocket2.OPEN || this.ws.readyState === WebSocket2.CONNECTING) {
549
+ this.ws.close(code, reason);
550
+ }
527
551
  } catch (error) {
528
552
  this.logger.error("\u5173\u95ED\u8FDE\u63A5\u5931\u8D25", error);
529
553
  }
530
554
  this.ws = void 0;
531
555
  }
532
556
  this.setState("disconnected" /* DISCONNECTED */);
557
+ this.wasReconnecting = false;
533
558
  }
534
559
  /**
535
560
  * 发送数据
@@ -833,8 +858,8 @@ var ApiClient = class {
833
858
  });
834
859
  const error = new ApiError(
835
860
  request.method,
836
- response.retcode,
837
- response.message,
861
+ typeof response.retcode === "number" ? response.retcode : -1,
862
+ typeof response.message === "string" ? response.message : "Unknown API error",
838
863
  response.wording
839
864
  );
840
865
  request.onError?.(error);
@@ -845,12 +870,19 @@ var ApiClient = class {
845
870
  * 销毁API客户端
846
871
  */
847
872
  destroy() {
848
- this.registry.clearAll("API\u5BA2\u6237\u7AEF\u5DF2\u9500\u6BC1");
873
+ this.clearPendingRequests("API\u5BA2\u6237\u7AEF\u5DF2\u9500\u6BC1");
849
874
  if (this.cleanupTimer) {
850
875
  clearInterval(this.cleanupTimer);
851
876
  this.cleanupTimer = void 0;
852
877
  }
853
878
  }
879
+ /**
880
+ * 清理所有待处理请求,但保留客户端可继续使用。
881
+ * 适用于临时断线、主动 disconnect 后再次 connect 的场景。
882
+ */
883
+ clearPendingRequests(reason) {
884
+ this.registry.clearAll(reason);
885
+ }
854
886
  /**
855
887
  * 发送API请求
856
888
  */
@@ -931,7 +963,7 @@ var EventRouter = class extends EventEmitter {
931
963
  emit(event, ...args) {
932
964
  this.anyListeners.forEach((listener) => {
933
965
  try {
934
- listener(event, ...args);
966
+ listener(event, args[0]);
935
967
  } catch (error) {
936
968
  this.logger.error("onAny listener error", error);
937
969
  }
@@ -945,13 +977,15 @@ var EventRouter = class extends EventEmitter {
945
977
  route(data) {
946
978
  try {
947
979
  const postType = data.post_type;
980
+ const messageType = "message_type" in data ? data.message_type : void 0;
981
+ const noticeType = "notice_type" in data ? data.notice_type : void 0;
948
982
  if (!postType) {
949
983
  this.logger.warn("\u6536\u5230\u65E0\u6548\u6D88\u606F: \u7F3A\u5C11 post_type", data);
950
984
  return;
951
985
  }
952
986
  this.logger.debug(`\u8DEF\u7531\u4E8B\u4EF6: ${postType}`, {
953
- messageType: data.message_type,
954
- noticeType: data.notice_type
987
+ messageType,
988
+ noticeType
955
989
  });
956
990
  switch (postType) {
957
991
  case "meta_event":
@@ -982,10 +1016,13 @@ var EventRouter = class extends EventEmitter {
982
1016
  * 路由元事件
983
1017
  */
984
1018
  routeMetaEvent(data) {
1019
+ if (!isMetaEventPayload(data))
1020
+ return;
985
1021
  const type = data.meta_event_type;
986
1022
  const events = [`meta_event`, `meta_event.${type}`];
987
- if (type === "lifecycle") {
988
- events.push(`meta_event.lifecycle.${data.sub_type}`);
1023
+ const subType = "sub_type" in data ? data.sub_type : void 0;
1024
+ if (type === "lifecycle" && subType) {
1025
+ events.push(`meta_event.lifecycle.${subType}`);
989
1026
  }
990
1027
  this.emitEvents(events, data);
991
1028
  }
@@ -993,6 +1030,8 @@ var EventRouter = class extends EventEmitter {
993
1030
  * 路由消息事件
994
1031
  */
995
1032
  routeMessage(data) {
1033
+ if (!isMessageEventPayload(data))
1034
+ return;
996
1035
  const messageType = data.message_type;
997
1036
  const subType = data.sub_type;
998
1037
  const events = [
@@ -1006,6 +1045,8 @@ var EventRouter = class extends EventEmitter {
1006
1045
  * 路由发送消息事件
1007
1046
  */
1008
1047
  routeMessageSent(data) {
1048
+ if (!isMessageEventPayload(data))
1049
+ return;
1009
1050
  const messageType = data.message_type;
1010
1051
  const subType = data.sub_type;
1011
1052
  const events = [
@@ -1019,8 +1060,10 @@ var EventRouter = class extends EventEmitter {
1019
1060
  * 路由通知事件
1020
1061
  */
1021
1062
  routeNotice(data) {
1063
+ if (!isNoticeEventPayload(data))
1064
+ return;
1022
1065
  const noticeType = data.notice_type;
1023
- const subType = data.sub_type;
1066
+ const subType = "sub_type" in data ? data.sub_type : void 0;
1024
1067
  const events = ["notice", `notice.${noticeType}`];
1025
1068
  if (subType) {
1026
1069
  events.push(`notice.${noticeType}.${subType}`);
@@ -1031,8 +1074,10 @@ var EventRouter = class extends EventEmitter {
1031
1074
  * 路由请求事件
1032
1075
  */
1033
1076
  routeRequest(data) {
1077
+ if (!isRequestEventPayload(data))
1078
+ return;
1034
1079
  const requestType = data.request_type;
1035
- const subType = data.sub_type;
1080
+ const subType = "sub_type" in data ? data.sub_type : void 0;
1036
1081
  const events = ["request", `request.${requestType}`];
1037
1082
  if (subType) {
1038
1083
  events.push(`request.${requestType}.${subType}`);
@@ -1049,6 +1094,18 @@ var EventRouter = class extends EventEmitter {
1049
1094
  }
1050
1095
  }
1051
1096
  };
1097
+ function isMessageEventPayload(data) {
1098
+ return data.post_type === "message" || data.post_type === "message_sent";
1099
+ }
1100
+ function isNoticeEventPayload(data) {
1101
+ return data.post_type === "notice";
1102
+ }
1103
+ function isRequestEventPayload(data) {
1104
+ return data.post_type === "request";
1105
+ }
1106
+ function isMetaEventPayload(data) {
1107
+ return data.post_type === "meta_event";
1108
+ }
1052
1109
 
1053
1110
  // src/core/dispatcher.ts
1054
1111
  var MessageDispatcher = class {
@@ -1064,7 +1121,8 @@ var MessageDispatcher = class {
1064
1121
  dispatch(message) {
1065
1122
  try {
1066
1123
  const data = JSON.parse(message);
1067
- if (data.echo) {
1124
+ const isApiResponse = data && typeof data === "object" && ("echo" in data || "status" in data || "retcode" in data);
1125
+ if (isApiResponse) {
1068
1126
  if (typeof data.echo === "string" && data.echo.startsWith("heartbeat_")) {
1069
1127
  return;
1070
1128
  }
@@ -1193,38 +1251,37 @@ function createMediaApi(client, logger) {
1193
1251
  async hydrateMedia(message) {
1194
1252
  if (!Array.isArray(message)) return;
1195
1253
  await Promise.all(message.map(async (segment) => {
1254
+ if (!isHydratableSegment(segment)) return;
1196
1255
  const type = segment?.type;
1197
1256
  const data = segment?.data;
1198
1257
  if (!type || !data) return;
1199
- if (["image", "video", "record", "audio", "file"].includes(type)) {
1200
- const fileId = data.file ?? data.file_id;
1201
- if (typeof fileId === "string" && !/^https?:\/\//.test(fileId) && !fileId.startsWith("file://")) {
1202
- try {
1203
- const res = await api.getFile(fileId);
1204
- const hydratedUrl = res?.file ?? res?.url;
1205
- if (hydratedUrl) {
1206
- data.url = hydratedUrl;
1207
- data.file = hydratedUrl;
1208
- return;
1258
+ const fileId = data.file ?? data.file_id;
1259
+ if (typeof fileId === "string" && !/^https?:\/\//.test(fileId) && !fileId.startsWith("file://")) {
1260
+ try {
1261
+ const res = await api.getFile(fileId);
1262
+ const hydratedUrl = res?.file ?? res?.url;
1263
+ if (hydratedUrl) {
1264
+ data.url = hydratedUrl;
1265
+ data.file = hydratedUrl;
1266
+ return;
1267
+ }
1268
+ if (type === "record" || type === "audio") {
1269
+ const rec = await api.getRecord(fileId, "mp3");
1270
+ const recUrl = rec?.file ?? rec?.url;
1271
+ if (recUrl) {
1272
+ data.url = recUrl;
1273
+ data.file = recUrl;
1209
1274
  }
1210
- if (type === "record" || type === "audio") {
1211
- const rec = await api.getRecord(fileId, "mp3");
1212
- const recUrl = rec?.file ?? rec?.url;
1213
- if (recUrl) {
1214
- data.url = recUrl;
1215
- data.file = recUrl;
1216
- }
1217
- } else if (type === "image") {
1218
- const img = await api.getImage(fileId);
1219
- const imgUrl = img?.file ?? img?.url;
1220
- if (imgUrl) {
1221
- data.url = imgUrl;
1222
- data.file = imgUrl;
1223
- }
1275
+ } else if (type === "image") {
1276
+ const img = await api.getImage(fileId);
1277
+ const imgUrl = img?.file ?? img?.url;
1278
+ if (imgUrl) {
1279
+ data.url = imgUrl;
1280
+ data.file = imgUrl;
1224
1281
  }
1225
- } catch (e) {
1226
- logger.debug(`Failed to hydrate media for ${type}: ${fileId}`, e);
1227
1282
  }
1283
+ } catch (e) {
1284
+ logger.debug(`Failed to hydrate media for ${type}: ${fileId}`, e);
1228
1285
  }
1229
1286
  }
1230
1287
  }));
@@ -1232,6 +1289,9 @@ function createMediaApi(client, logger) {
1232
1289
  };
1233
1290
  return api;
1234
1291
  }
1292
+ function isHydratableSegment(segment) {
1293
+ return ["image", "video", "record", "audio", "file"].includes(segment.type);
1294
+ }
1235
1295
 
1236
1296
  // src/api/onebot/account.ts
1237
1297
  function createAccountApi(client) {
@@ -2156,7 +2216,7 @@ var NapLink = class extends EventEmitter2 {
2156
2216
  disconnect() {
2157
2217
  this.logger.info("\u65AD\u5F00\u8FDE\u63A5...");
2158
2218
  this.connection.disconnect();
2159
- this.apiClient.destroy();
2219
+ this.apiClient.clearPendingRequests("\u8FDE\u63A5\u5DF2\u65AD\u5F00");
2160
2220
  }
2161
2221
  /**
2162
2222
  * 获取连接状态
@@ -2189,6 +2249,15 @@ var NapLink = class extends EventEmitter2 {
2189
2249
  get api() {
2190
2250
  return this.oneBotApi;
2191
2251
  }
2252
+ /**
2253
+ * 销毁客户端实例
2254
+ * 调用后不应再复用当前实例。
2255
+ */
2256
+ dispose() {
2257
+ this.logger.info("\u9500\u6BC1\u5BA2\u6237\u7AEF...");
2258
+ this.connection.disconnect();
2259
+ this.apiClient.destroy();
2260
+ }
2192
2261
  // ============ 内部方法 ============
2193
2262
  /**
2194
2263
  * 设置事件转发