@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 +4 -2
- package/dist/index.d.ts +135 -95
- package/dist/index.js +110 -41
- 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
|
}
|
|
@@ -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.
|
|
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.
|
|
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,
|
|
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
|
|
954
|
-
noticeType
|
|
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
|
-
|
|
988
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
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.
|
|
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
|
* 设置事件转发
|