@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 +4 -2
- package/dist/index.d.ts +135 -95
- package/dist/index.js +91 -40
- package/dist/index.js.map +1 -1
- package/package.json +66 -57
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,
|
|
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:
|
|
61
|
-
info(message: string, ...meta:
|
|
62
|
-
warn(message: string, ...meta:
|
|
63
|
-
error(message: string, error?: Error, ...meta:
|
|
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,
|
|
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?:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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]:
|
|
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:
|
|
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<
|
|
462
|
-
getRecord(file: string, outFormat?: string): Promise<
|
|
463
|
-
getFile(file: string): Promise<
|
|
464
|
-
hydrateMedia(message:
|
|
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:
|
|
1088
|
+
hydrateMessage(message: OneBotMessageSegment[]): Promise<void>;
|
|
983
1089
|
/**
|
|
984
1090
|
* 调用自定义API
|
|
985
1091
|
*/
|
|
986
|
-
callApi<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?:
|
|
1006
|
-
constructor(message: string, code: string, details?:
|
|
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:
|
|
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?:
|
|
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
|
-
|
|
297
|
-
|
|
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.
|
|
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,
|
|
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
|
|
972
|
-
noticeType
|
|
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
|
-
|
|
1006
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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.
|
|
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
|
* 设置事件转发
|