@naplink/naplink 0.0.7 → 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
  }
@@ -532,6 +538,7 @@ var ConnectionManager = class {
532
538
  this.logger.info(`\u65AD\u5F00\u8FDE\u63A5: ${reason}`);
533
539
  this.reconnectService.cancel();
534
540
  this.stopHeartbeat();
541
+ this.clearConnectTimeout();
535
542
  if (this.ws) {
536
543
  try {
537
544
  this.ws.onopen = null;
@@ -851,8 +858,8 @@ var ApiClient = class {
851
858
  });
852
859
  const error = new ApiError(
853
860
  request.method,
854
- response.retcode,
855
- response.message,
861
+ typeof response.retcode === "number" ? response.retcode : -1,
862
+ typeof response.message === "string" ? response.message : "Unknown API error",
856
863
  response.wording
857
864
  );
858
865
  request.onError?.(error);
@@ -863,12 +870,19 @@ var ApiClient = class {
863
870
  * 销毁API客户端
864
871
  */
865
872
  destroy() {
866
- this.registry.clearAll("API\u5BA2\u6237\u7AEF\u5DF2\u9500\u6BC1");
873
+ this.clearPendingRequests("API\u5BA2\u6237\u7AEF\u5DF2\u9500\u6BC1");
867
874
  if (this.cleanupTimer) {
868
875
  clearInterval(this.cleanupTimer);
869
876
  this.cleanupTimer = void 0;
870
877
  }
871
878
  }
879
+ /**
880
+ * 清理所有待处理请求,但保留客户端可继续使用。
881
+ * 适用于临时断线、主动 disconnect 后再次 connect 的场景。
882
+ */
883
+ clearPendingRequests(reason) {
884
+ this.registry.clearAll(reason);
885
+ }
872
886
  /**
873
887
  * 发送API请求
874
888
  */
@@ -949,7 +963,7 @@ var EventRouter = class extends EventEmitter {
949
963
  emit(event, ...args) {
950
964
  this.anyListeners.forEach((listener) => {
951
965
  try {
952
- listener(event, ...args);
966
+ listener(event, args[0]);
953
967
  } catch (error) {
954
968
  this.logger.error("onAny listener error", error);
955
969
  }
@@ -963,13 +977,15 @@ var EventRouter = class extends EventEmitter {
963
977
  route(data) {
964
978
  try {
965
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;
966
982
  if (!postType) {
967
983
  this.logger.warn("\u6536\u5230\u65E0\u6548\u6D88\u606F: \u7F3A\u5C11 post_type", data);
968
984
  return;
969
985
  }
970
986
  this.logger.debug(`\u8DEF\u7531\u4E8B\u4EF6: ${postType}`, {
971
- messageType: data.message_type,
972
- noticeType: data.notice_type
987
+ messageType,
988
+ noticeType
973
989
  });
974
990
  switch (postType) {
975
991
  case "meta_event":
@@ -1000,10 +1016,13 @@ var EventRouter = class extends EventEmitter {
1000
1016
  * 路由元事件
1001
1017
  */
1002
1018
  routeMetaEvent(data) {
1019
+ if (!isMetaEventPayload(data))
1020
+ return;
1003
1021
  const type = data.meta_event_type;
1004
1022
  const events = [`meta_event`, `meta_event.${type}`];
1005
- if (type === "lifecycle") {
1006
- 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}`);
1007
1026
  }
1008
1027
  this.emitEvents(events, data);
1009
1028
  }
@@ -1011,6 +1030,8 @@ var EventRouter = class extends EventEmitter {
1011
1030
  * 路由消息事件
1012
1031
  */
1013
1032
  routeMessage(data) {
1033
+ if (!isMessageEventPayload(data))
1034
+ return;
1014
1035
  const messageType = data.message_type;
1015
1036
  const subType = data.sub_type;
1016
1037
  const events = [
@@ -1024,6 +1045,8 @@ var EventRouter = class extends EventEmitter {
1024
1045
  * 路由发送消息事件
1025
1046
  */
1026
1047
  routeMessageSent(data) {
1048
+ if (!isMessageEventPayload(data))
1049
+ return;
1027
1050
  const messageType = data.message_type;
1028
1051
  const subType = data.sub_type;
1029
1052
  const events = [
@@ -1037,8 +1060,10 @@ var EventRouter = class extends EventEmitter {
1037
1060
  * 路由通知事件
1038
1061
  */
1039
1062
  routeNotice(data) {
1063
+ if (!isNoticeEventPayload(data))
1064
+ return;
1040
1065
  const noticeType = data.notice_type;
1041
- const subType = data.sub_type;
1066
+ const subType = "sub_type" in data ? data.sub_type : void 0;
1042
1067
  const events = ["notice", `notice.${noticeType}`];
1043
1068
  if (subType) {
1044
1069
  events.push(`notice.${noticeType}.${subType}`);
@@ -1049,8 +1074,10 @@ var EventRouter = class extends EventEmitter {
1049
1074
  * 路由请求事件
1050
1075
  */
1051
1076
  routeRequest(data) {
1077
+ if (!isRequestEventPayload(data))
1078
+ return;
1052
1079
  const requestType = data.request_type;
1053
- const subType = data.sub_type;
1080
+ const subType = "sub_type" in data ? data.sub_type : void 0;
1054
1081
  const events = ["request", `request.${requestType}`];
1055
1082
  if (subType) {
1056
1083
  events.push(`request.${requestType}.${subType}`);
@@ -1067,6 +1094,18 @@ var EventRouter = class extends EventEmitter {
1067
1094
  }
1068
1095
  }
1069
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
+ }
1070
1109
 
1071
1110
  // src/core/dispatcher.ts
1072
1111
  var MessageDispatcher = class {
@@ -1082,7 +1121,8 @@ var MessageDispatcher = class {
1082
1121
  dispatch(message) {
1083
1122
  try {
1084
1123
  const data = JSON.parse(message);
1085
- if (data.echo) {
1124
+ const isApiResponse = data && typeof data === "object" && ("echo" in data || "status" in data || "retcode" in data);
1125
+ if (isApiResponse) {
1086
1126
  if (typeof data.echo === "string" && data.echo.startsWith("heartbeat_")) {
1087
1127
  return;
1088
1128
  }
@@ -1211,38 +1251,37 @@ function createMediaApi(client, logger) {
1211
1251
  async hydrateMedia(message) {
1212
1252
  if (!Array.isArray(message)) return;
1213
1253
  await Promise.all(message.map(async (segment) => {
1254
+ if (!isHydratableSegment(segment)) return;
1214
1255
  const type = segment?.type;
1215
1256
  const data = segment?.data;
1216
1257
  if (!type || !data) return;
1217
- if (["image", "video", "record", "audio", "file"].includes(type)) {
1218
- const fileId = data.file ?? data.file_id;
1219
- if (typeof fileId === "string" && !/^https?:\/\//.test(fileId) && !fileId.startsWith("file://")) {
1220
- try {
1221
- const res = await api.getFile(fileId);
1222
- const hydratedUrl = res?.file ?? res?.url;
1223
- if (hydratedUrl) {
1224
- data.url = hydratedUrl;
1225
- data.file = hydratedUrl;
1226
- 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;
1227
1274
  }
1228
- if (type === "record" || type === "audio") {
1229
- const rec = await api.getRecord(fileId, "mp3");
1230
- const recUrl = rec?.file ?? rec?.url;
1231
- if (recUrl) {
1232
- data.url = recUrl;
1233
- data.file = recUrl;
1234
- }
1235
- } else if (type === "image") {
1236
- const img = await api.getImage(fileId);
1237
- const imgUrl = img?.file ?? img?.url;
1238
- if (imgUrl) {
1239
- data.url = imgUrl;
1240
- data.file = imgUrl;
1241
- }
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;
1242
1281
  }
1243
- } catch (e) {
1244
- logger.debug(`Failed to hydrate media for ${type}: ${fileId}`, e);
1245
1282
  }
1283
+ } catch (e) {
1284
+ logger.debug(`Failed to hydrate media for ${type}: ${fileId}`, e);
1246
1285
  }
1247
1286
  }
1248
1287
  }));
@@ -1250,6 +1289,9 @@ function createMediaApi(client, logger) {
1250
1289
  };
1251
1290
  return api;
1252
1291
  }
1292
+ function isHydratableSegment(segment) {
1293
+ return ["image", "video", "record", "audio", "file"].includes(segment.type);
1294
+ }
1253
1295
 
1254
1296
  // src/api/onebot/account.ts
1255
1297
  function createAccountApi(client) {
@@ -2174,7 +2216,7 @@ var NapLink = class extends EventEmitter2 {
2174
2216
  disconnect() {
2175
2217
  this.logger.info("\u65AD\u5F00\u8FDE\u63A5...");
2176
2218
  this.connection.disconnect();
2177
- this.apiClient.destroy();
2219
+ this.apiClient.clearPendingRequests("\u8FDE\u63A5\u5DF2\u65AD\u5F00");
2178
2220
  }
2179
2221
  /**
2180
2222
  * 获取连接状态
@@ -2207,6 +2249,15 @@ var NapLink = class extends EventEmitter2 {
2207
2249
  get api() {
2208
2250
  return this.oneBotApi;
2209
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
+ }
2210
2261
  // ============ 内部方法 ============
2211
2262
  /**
2212
2263
  * 设置事件转发