@naplink/naplink 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,1572 @@
1
+ // src/naplink.ts
2
+ import EventEmitter2 from "events";
3
+
4
+ // src/utils/logger.ts
5
+ var LOG_LEVEL_MAP = {
6
+ debug: 0 /* DEBUG */,
7
+ info: 1 /* INFO */,
8
+ warn: 2 /* WARN */,
9
+ error: 3 /* ERROR */,
10
+ off: 4 /* OFF */
11
+ };
12
+ var DefaultLogger = class {
13
+ level;
14
+ constructor(level = "info") {
15
+ this.level = LOG_LEVEL_MAP[level];
16
+ }
17
+ /**
18
+ * 设置日志等级
19
+ */
20
+ setLevel(level) {
21
+ this.level = LOG_LEVEL_MAP[level];
22
+ }
23
+ debug(message, ...meta) {
24
+ if (this.shouldLog(0 /* DEBUG */)) {
25
+ console.debug(this.format("DEBUG", message), ...meta);
26
+ }
27
+ }
28
+ info(message, ...meta) {
29
+ if (this.shouldLog(1 /* INFO */)) {
30
+ console.info(this.format("INFO", message), ...meta);
31
+ }
32
+ }
33
+ warn(message, ...meta) {
34
+ if (this.shouldLog(2 /* WARN */)) {
35
+ console.warn(this.format("WARN", message), ...meta);
36
+ }
37
+ }
38
+ error(message, error, ...meta) {
39
+ if (this.shouldLog(3 /* ERROR */)) {
40
+ console.error(this.format("ERROR", message), error || "", ...meta);
41
+ }
42
+ }
43
+ /**
44
+ * 检查是否应该输出日志
45
+ */
46
+ shouldLog(level) {
47
+ return level >= this.level;
48
+ }
49
+ /**
50
+ * 格式化日志消息
51
+ */
52
+ format(level, message) {
53
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
54
+ return `[${timestamp}] [NapLink] ${level}: ${message}`;
55
+ }
56
+ };
57
+
58
+ // src/core/connection/manager.ts
59
+ import WebSocket2 from "isomorphic-ws";
60
+
61
+ // src/types/errors.ts
62
+ var NapLinkError = class extends Error {
63
+ constructor(message, code, details) {
64
+ super(message);
65
+ this.code = code;
66
+ this.details = details;
67
+ this.name = this.constructor.name;
68
+ Object.setPrototypeOf(this, new.target.prototype);
69
+ }
70
+ /**
71
+ * 转换为JSON格式
72
+ */
73
+ toJSON() {
74
+ return {
75
+ name: this.name,
76
+ message: this.message,
77
+ code: this.code,
78
+ details: this.details
79
+ };
80
+ }
81
+ };
82
+ var ConnectionError = class extends NapLinkError {
83
+ constructor(message, cause) {
84
+ super(message, "E_CONNECTION", cause);
85
+ }
86
+ };
87
+ var ApiTimeoutError = class extends NapLinkError {
88
+ constructor(method, timeout) {
89
+ super(
90
+ `API\u8C03\u7528 ${method} \u8D85\u65F6 (${timeout}ms)`,
91
+ "E_API_TIMEOUT",
92
+ { method, timeout }
93
+ );
94
+ }
95
+ };
96
+ var ApiError = class extends NapLinkError {
97
+ constructor(method, retcode, message, wording) {
98
+ super(
99
+ wording || message || `API\u8C03\u7528\u5931\u8D25: ${method}`,
100
+ "E_API_FAILED",
101
+ { method, retcode, message, wording }
102
+ );
103
+ }
104
+ };
105
+ var MaxReconnectAttemptsError = class extends NapLinkError {
106
+ constructor(attempts) {
107
+ super(
108
+ `\u8FBE\u5230\u6700\u5927\u91CD\u8FDE\u6B21\u6570 (${attempts})`,
109
+ "E_MAX_RECONNECT",
110
+ { attempts }
111
+ );
112
+ }
113
+ };
114
+ var ConnectionClosedError = class extends NapLinkError {
115
+ constructor(code, reason) {
116
+ super(
117
+ `\u8FDE\u63A5\u5DF2\u5173\u95ED: ${reason} (code: ${code})`,
118
+ "E_CONNECTION_CLOSED",
119
+ { code, reason }
120
+ );
121
+ }
122
+ };
123
+ var InvalidConfigError = class extends NapLinkError {
124
+ constructor(field, reason) {
125
+ super(
126
+ `\u65E0\u6548\u7684\u914D\u7F6E: ${field} - ${reason}`,
127
+ "E_INVALID_CONFIG",
128
+ { field, reason }
129
+ );
130
+ }
131
+ };
132
+
133
+ // src/core/reconnect.ts
134
+ var ReconnectService = class {
135
+ constructor(config, logger) {
136
+ this.config = config;
137
+ this.logger = logger;
138
+ this.backoffMs = config.backoff.initial;
139
+ }
140
+ currentAttempt = 0;
141
+ backoffMs;
142
+ reconnectTimer;
143
+ hasRemainingAttempts() {
144
+ return this.currentAttempt < this.config.maxAttempts;
145
+ }
146
+ getMaxAttempts() {
147
+ return this.config.maxAttempts;
148
+ }
149
+ /**
150
+ * 调度重连
151
+ * @param reconnectFn 重连函数
152
+ * @returns 是否调度成功
153
+ */
154
+ schedule(reconnectFn) {
155
+ if (!this.config.enabled) {
156
+ this.logger.warn("\u81EA\u52A8\u91CD\u8FDE\u672A\u542F\u7528");
157
+ return false;
158
+ }
159
+ if (this.currentAttempt >= this.config.maxAttempts) {
160
+ this.logger.error(
161
+ `\u8FBE\u5230\u6700\u5927\u91CD\u8FDE\u6B21\u6570 (${this.config.maxAttempts})`,
162
+ void 0,
163
+ { attempts: this.currentAttempt }
164
+ );
165
+ return false;
166
+ }
167
+ this.currentAttempt++;
168
+ this.logger.info(
169
+ `\u5C06\u5728 ${this.backoffMs}ms \u540E\u8FDB\u884C\u7B2C ${this.currentAttempt} \u6B21\u91CD\u8FDE...`
170
+ );
171
+ this.reconnectTimer = setTimeout(async () => {
172
+ try {
173
+ await reconnectFn();
174
+ this.reset();
175
+ } catch (error) {
176
+ this.logger.error("\u91CD\u8FDE\u5931\u8D25", error);
177
+ this.backoffMs = Math.min(
178
+ this.backoffMs * this.config.backoff.multiplier,
179
+ this.config.backoff.max
180
+ );
181
+ this.schedule(reconnectFn);
182
+ }
183
+ }, this.backoffMs);
184
+ return true;
185
+ }
186
+ /**
187
+ * 重置重连状态
188
+ * 在连接成功后调用
189
+ */
190
+ reset() {
191
+ this.currentAttempt = 0;
192
+ this.backoffMs = this.config.backoff.initial;
193
+ this.cancel();
194
+ }
195
+ /**
196
+ * 取消待定的重连
197
+ */
198
+ cancel() {
199
+ if (this.reconnectTimer) {
200
+ clearTimeout(this.reconnectTimer);
201
+ this.reconnectTimer = void 0;
202
+ }
203
+ }
204
+ /**
205
+ * 获取当前重连尝试次数
206
+ */
207
+ getCurrentAttempt() {
208
+ return this.currentAttempt;
209
+ }
210
+ };
211
+
212
+ // src/core/heartbeat.ts
213
+ var HeartbeatService = class _HeartbeatService {
214
+ constructor(interval, sendPing, onTimeout, logger) {
215
+ this.interval = interval;
216
+ this.sendPing = sendPing;
217
+ this.onTimeout = onTimeout;
218
+ this.logger = logger;
219
+ }
220
+ timer;
221
+ lastPongTime = 0;
222
+ missedPings = 0;
223
+ static MAX_MISSED_PINGS = 3;
224
+ /**
225
+ * 启动心跳
226
+ */
227
+ start() {
228
+ if (this.interval <= 0) {
229
+ this.logger.debug("\u5FC3\u8DF3\u5DF2\u7981\u7528 (interval: 0)");
230
+ return;
231
+ }
232
+ this.logger.debug(`\u542F\u52A8\u5FC3\u8DF3\u670D\u52A1 (\u95F4\u9694: ${this.interval}ms)`);
233
+ this.lastPongTime = Date.now();
234
+ this.missedPings = 0;
235
+ this.timer = setInterval(() => {
236
+ const now = Date.now();
237
+ const elapsed = now - this.lastPongTime;
238
+ if (elapsed > this.interval * _HeartbeatService.MAX_MISSED_PINGS) {
239
+ this.missedPings++;
240
+ this.logger.warn(
241
+ `\u5FC3\u8DF3\u8D85\u65F6 (${this.missedPings}/${_HeartbeatService.MAX_MISSED_PINGS})`,
242
+ { elapsed }
243
+ );
244
+ if (this.missedPings >= _HeartbeatService.MAX_MISSED_PINGS) {
245
+ this.logger.error("\u5FC3\u8DF3\u8FDE\u7EED\u8D85\u65F6\uFF0C\u89E6\u53D1\u91CD\u8FDE");
246
+ this.stop();
247
+ this.onTimeout();
248
+ return;
249
+ }
250
+ }
251
+ this.logger.debug("\u53D1\u9001\u5FC3\u8DF3 ping");
252
+ this.sendPing();
253
+ }, this.interval);
254
+ }
255
+ /**
256
+ * 停止心跳
257
+ */
258
+ stop() {
259
+ if (this.timer) {
260
+ clearInterval(this.timer);
261
+ this.timer = void 0;
262
+ this.logger.debug("\u5FC3\u8DF3\u670D\u52A1\u5DF2\u505C\u6B62");
263
+ }
264
+ }
265
+ /**
266
+ * 记录收到pong
267
+ */
268
+ recordPong() {
269
+ this.lastPongTime = Date.now();
270
+ this.missedPings = 0;
271
+ this.logger.debug("\u6536\u5230\u5FC3\u8DF3 pong");
272
+ }
273
+ /**
274
+ * 检查心跳是否活跃
275
+ */
276
+ isActive() {
277
+ return this.timer !== void 0;
278
+ }
279
+ };
280
+
281
+ // src/core/connection/state.ts
282
+ var ConnectionState = /* @__PURE__ */ ((ConnectionState3) => {
283
+ ConnectionState3["DISCONNECTED"] = "disconnected";
284
+ ConnectionState3["CONNECTING"] = "connecting";
285
+ ConnectionState3["CONNECTED"] = "connected";
286
+ ConnectionState3["RECONNECTING"] = "reconnecting";
287
+ return ConnectionState3;
288
+ })(ConnectionState || {});
289
+
290
+ // src/core/connection/url.ts
291
+ function buildWebSocketUrl(config) {
292
+ const { url, token } = config.connection;
293
+ if (token) {
294
+ const separator = url.includes("?") ? "&" : "?";
295
+ return `${url}${separator}access_token=${token}`;
296
+ }
297
+ return url;
298
+ }
299
+
300
+ // src/core/connection/heartbeat-runner.ts
301
+ function startHeartbeat(deps) {
302
+ const { config, logger, send, onTimeout, createService } = deps;
303
+ const interval = config.connection.pingInterval || 0;
304
+ const heartbeatAction = config.connection.heartbeatAction;
305
+ if (interval <= 0 || !heartbeatAction?.action) {
306
+ return void 0;
307
+ }
308
+ const service = createService(
309
+ interval,
310
+ () => {
311
+ try {
312
+ const payload = {
313
+ action: heartbeatAction.action,
314
+ params: heartbeatAction.params ?? {},
315
+ echo: `heartbeat_${Date.now()}`
316
+ };
317
+ send(JSON.stringify(payload));
318
+ } catch (error) {
319
+ logger.error("\u53D1\u9001\u5FC3\u8DF3\u5931\u8D25", error);
320
+ }
321
+ },
322
+ onTimeout,
323
+ logger
324
+ );
325
+ service.start();
326
+ return service;
327
+ }
328
+
329
+ // src/core/connection/retry-handler.ts
330
+ function handleReconnect(deps) {
331
+ const { config, logger, reconnectService, setState, connect } = deps;
332
+ if (!reconnectService.hasRemainingAttempts()) {
333
+ setState("disconnected" /* DISCONNECTED */);
334
+ const err = new MaxReconnectAttemptsError(reconnectService.getMaxAttempts());
335
+ logger.error("\u81EA\u52A8\u91CD\u8FDE\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u505C\u6B62\u91CD\u8FDE", err);
336
+ return false;
337
+ }
338
+ setState("reconnecting" /* RECONNECTING */);
339
+ const scheduled = reconnectService.schedule(() => connect());
340
+ if (!scheduled) {
341
+ setState("disconnected" /* DISCONNECTED */);
342
+ const err = new MaxReconnectAttemptsError(config.reconnect.maxAttempts);
343
+ logger.error("\u81EA\u52A8\u91CD\u8FDE\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u505C\u6B62\u91CD\u8FDE", err);
344
+ return false;
345
+ }
346
+ return true;
347
+ }
348
+
349
+ // src/core/connection/close-handler.ts
350
+ function handleCloseEvent(deps, event) {
351
+ const { getState, setState, stopHeartbeat, logger, config, reconnectService, reconnect } = deps;
352
+ stopHeartbeat();
353
+ logger.info(`\u8FDE\u63A5\u5173\u95ED (code: ${event.code}, reason: ${event.reason})`);
354
+ if (event.code === 1e3) {
355
+ setState("disconnected" /* DISCONNECTED */);
356
+ return;
357
+ }
358
+ const state = getState();
359
+ if (state === "connected" /* CONNECTED */ || state === "reconnecting" /* RECONNECTING */ || state === "connecting" /* CONNECTING */) {
360
+ handleReconnect({
361
+ config,
362
+ logger,
363
+ reconnectService,
364
+ setState,
365
+ connect: reconnect
366
+ });
367
+ } else {
368
+ setState("disconnected" /* DISCONNECTED */);
369
+ }
370
+ }
371
+
372
+ // src/core/connection/handlers.ts
373
+ import WebSocket from "isomorphic-ws";
374
+ function attachWebSocketHandlers(ws, deps, resolve, reject) {
375
+ const { config, logger } = deps;
376
+ const timeoutMs = config.connection.timeout || 3e4;
377
+ const connectTimeout = setTimeout(() => {
378
+ if (ws && ws.readyState !== WebSocket.OPEN) {
379
+ logger.error(`\u8FDE\u63A5\u8D85\u65F6 (${timeoutMs}ms)`);
380
+ ws.close();
381
+ reject(new ConnectionError(`\u8FDE\u63A5\u8D85\u65F6 (${timeoutMs}ms)`));
382
+ }
383
+ }, timeoutMs);
384
+ ws.onopen = () => {
385
+ deps.clearConnectTimeout();
386
+ deps.setState("connected" /* CONNECTED */);
387
+ deps.resetReconnect();
388
+ deps.startHeartbeat();
389
+ deps.logger.info("WebSocket \u8FDE\u63A5\u5DF2\u5EFA\u7ACB");
390
+ resolve();
391
+ };
392
+ ws.onerror = (event) => {
393
+ deps.clearConnectTimeout();
394
+ const error = new ConnectionError("WebSocket \u9519\u8BEF", event);
395
+ deps.logger.error("\u8FDE\u63A5\u9519\u8BEF", error);
396
+ reject(error);
397
+ };
398
+ ws.onclose = (event) => {
399
+ deps.clearConnectTimeout();
400
+ deps.onClose(event);
401
+ };
402
+ ws.onmessage = (event) => {
403
+ try {
404
+ deps.recordPong();
405
+ deps.onMessage(event.data.toString());
406
+ } catch (error) {
407
+ deps.logger.error("\u6D88\u606F\u5904\u7406\u5931\u8D25", error);
408
+ }
409
+ };
410
+ return connectTimeout;
411
+ }
412
+
413
+ // src/core/connection/manager.ts
414
+ var ConnectionManager = class {
415
+ constructor(config, logger, onMessage, onStateChange) {
416
+ this.config = config;
417
+ this.logger = logger;
418
+ this.onMessage = onMessage;
419
+ this.onStateChange = onStateChange;
420
+ this.reconnectService = new ReconnectService(config.reconnect, logger);
421
+ }
422
+ ws;
423
+ state = "disconnected" /* DISCONNECTED */;
424
+ reconnectService;
425
+ heartbeatService;
426
+ connectPromise;
427
+ connectTimeout;
428
+ /**
429
+ * 连接到WebSocket服务器
430
+ */
431
+ async connect() {
432
+ if (this.connectPromise) {
433
+ return this.connectPromise;
434
+ }
435
+ this.setState("connecting" /* CONNECTING */);
436
+ this.connectPromise = this.performConnect();
437
+ try {
438
+ await this.connectPromise;
439
+ } finally {
440
+ this.connectPromise = void 0;
441
+ }
442
+ }
443
+ /**
444
+ * 执行实际的连接逻辑
445
+ */
446
+ performConnect() {
447
+ return new Promise((resolve, reject) => {
448
+ const url = buildWebSocketUrl(this.config);
449
+ this.logger.info(`\u8FDE\u63A5\u5230 ${url}`);
450
+ try {
451
+ this.ws = new WebSocket2(url);
452
+ } catch (error) {
453
+ const err = new ConnectionError("WebSocket \u521B\u5EFA\u5931\u8D25", error);
454
+ this.logger.error("\u8FDE\u63A5\u5931\u8D25", err);
455
+ reject(err);
456
+ return;
457
+ }
458
+ const timeoutHandle = attachWebSocketHandlers(
459
+ this.ws,
460
+ {
461
+ config: this.config,
462
+ logger: this.logger,
463
+ setState: (s) => this.setState(s),
464
+ resetReconnect: () => this.reconnectService.reset(),
465
+ startHeartbeat: () => {
466
+ this.heartbeatService = startHeartbeat({
467
+ config: this.config,
468
+ logger: this.logger,
469
+ send: (payload) => this.send(payload),
470
+ onTimeout: () => this.handleHeartbeatTimeout(),
471
+ createService: (interval, onPing, onTimeout, logger) => new HeartbeatService(interval, onPing, onTimeout, logger)
472
+ });
473
+ },
474
+ recordPong: () => this.heartbeatService?.recordPong(),
475
+ onMessage: (data) => this.onMessage(data),
476
+ onClose: (event) => handleCloseEvent({
477
+ getState: () => this.state,
478
+ setState: (s) => this.setState(s),
479
+ stopHeartbeat: () => this.stopHeartbeat(),
480
+ logger: this.logger,
481
+ config: this.config,
482
+ reconnectService: this.reconnectService,
483
+ reconnect: () => this.connect()
484
+ }, event),
485
+ clearConnectTimeout: () => this.clearConnectTimeout()
486
+ },
487
+ resolve,
488
+ reject
489
+ );
490
+ this.connectTimeout = timeoutHandle;
491
+ });
492
+ }
493
+ /**
494
+ * 断开连接
495
+ * @param code 关闭代码
496
+ * @param reason 关闭原因
497
+ */
498
+ disconnect(code = 1e3, reason = "\u6B63\u5E38\u5173\u95ED") {
499
+ this.logger.info(`\u65AD\u5F00\u8FDE\u63A5: ${reason}`);
500
+ this.reconnectService.cancel();
501
+ this.stopHeartbeat();
502
+ if (this.ws) {
503
+ try {
504
+ this.ws.close(code, reason);
505
+ } catch (error) {
506
+ this.logger.error("\u5173\u95ED\u8FDE\u63A5\u5931\u8D25", error);
507
+ }
508
+ this.ws = void 0;
509
+ }
510
+ this.setState("disconnected" /* DISCONNECTED */);
511
+ }
512
+ /**
513
+ * 发送数据
514
+ */
515
+ send(data) {
516
+ if (!this.ws || this.ws.readyState !== WebSocket2.OPEN) {
517
+ throw new ConnectionClosedError(
518
+ this.ws?.readyState || -1,
519
+ "\u8FDE\u63A5\u672A\u5EFA\u7ACB\u6216\u5DF2\u5173\u95ED"
520
+ );
521
+ }
522
+ this.ws.send(data);
523
+ }
524
+ /**
525
+ * 获取当前状态
526
+ */
527
+ getState() {
528
+ return this.state;
529
+ }
530
+ /**
531
+ * 检查是否已连接
532
+ */
533
+ isConnected() {
534
+ return this.state === "connected" /* CONNECTED */ && this.ws?.readyState === WebSocket2.OPEN;
535
+ }
536
+ /**
537
+ * 设置状态并通知
538
+ */
539
+ setState(state) {
540
+ if (this.state !== state) {
541
+ this.state = state;
542
+ this.logger.debug(`\u72B6\u6001\u53D8\u66F4: ${state}`);
543
+ this.onStateChange(state);
544
+ }
545
+ }
546
+ /**
547
+ * 停止心跳
548
+ */
549
+ stopHeartbeat() {
550
+ if (this.heartbeatService) {
551
+ this.heartbeatService.stop();
552
+ this.heartbeatService = void 0;
553
+ }
554
+ }
555
+ /**
556
+ * 处理心跳超时
557
+ */
558
+ handleHeartbeatTimeout() {
559
+ this.logger.warn("\u5FC3\u8DF3\u8D85\u65F6\uFF0C\u4E3B\u52A8\u65AD\u5F00\u8FDE\u63A5");
560
+ this.disconnect(4e3, "\u5FC3\u8DF3\u8D85\u65F6");
561
+ }
562
+ /**
563
+ * 处理连接关闭并尝试重连
564
+ */
565
+ clearConnectTimeout() {
566
+ if (this.connectTimeout) {
567
+ clearTimeout(this.connectTimeout);
568
+ this.connectTimeout = void 0;
569
+ }
570
+ }
571
+ };
572
+
573
+ // src/core/api/request-builder.ts
574
+ function buildRequestPayload(method, params, echo) {
575
+ return {
576
+ echo,
577
+ payload: JSON.stringify({
578
+ action: method,
579
+ params,
580
+ echo
581
+ })
582
+ };
583
+ }
584
+
585
+ // src/core/api/response-registry.ts
586
+ var ResponseRegistry = class {
587
+ pending = /* @__PURE__ */ new Map();
588
+ add(echo, request, timeout) {
589
+ const timer = setTimeout(() => {
590
+ this.pending.delete(echo);
591
+ request.reject(new ApiTimeoutError(request.method, timeout));
592
+ }, timeout);
593
+ const entry = { ...request, timer };
594
+ this.pending.set(echo, entry);
595
+ return entry;
596
+ }
597
+ resolve(echo, data) {
598
+ const req = this.pending.get(echo);
599
+ if (!req) return false;
600
+ this.pending.delete(echo);
601
+ clearTimeout(req.timer);
602
+ req.resolve(data);
603
+ return true;
604
+ }
605
+ reject(echo, error) {
606
+ const req = this.pending.get(echo);
607
+ if (!req) return false;
608
+ this.pending.delete(echo);
609
+ clearTimeout(req.timer);
610
+ req.reject(error);
611
+ return true;
612
+ }
613
+ take(echo) {
614
+ const req = this.pending.get(echo);
615
+ if (req) {
616
+ this.pending.delete(echo);
617
+ clearTimeout(req.timer);
618
+ }
619
+ return req;
620
+ }
621
+ clearAll(reason) {
622
+ for (const [, req] of this.pending) {
623
+ clearTimeout(req.timer);
624
+ req.reject(new Error(reason));
625
+ }
626
+ this.pending.clear();
627
+ }
628
+ cleanupStale(now, maxAge, onTimeout) {
629
+ for (const [echo, req] of this.pending) {
630
+ if (now - req.createdAt > maxAge) {
631
+ onTimeout(req.method, echo);
632
+ this.reject(echo, new ApiTimeoutError(req.method, maxAge / 2));
633
+ }
634
+ }
635
+ }
636
+ };
637
+
638
+ // src/core/api/retry.ts
639
+ async function withRetry(fn, retries, method, logger, delayFn) {
640
+ let lastError;
641
+ for (let attempt = 0; attempt <= retries; attempt++) {
642
+ try {
643
+ return await fn();
644
+ } catch (error) {
645
+ lastError = error;
646
+ if (attempt === retries) {
647
+ break;
648
+ }
649
+ if (error instanceof ApiTimeoutError || error instanceof ApiError) {
650
+ logger.warn(
651
+ `API\u8C03\u7528\u5931\u8D25\uFF0C\u91CD\u8BD5 ${attempt + 1}/${retries}: ${method}`,
652
+ error
653
+ );
654
+ await delayFn(Math.min(1e3 * (attempt + 1), 5e3));
655
+ } else {
656
+ throw error;
657
+ }
658
+ }
659
+ }
660
+ throw lastError;
661
+ }
662
+
663
+ // src/core/api-client.ts
664
+ var ApiClient = class {
665
+ constructor(connection, config, logger) {
666
+ this.connection = connection;
667
+ this.config = config;
668
+ this.logger = logger;
669
+ this.startCleanupTimer();
670
+ }
671
+ registry = new ResponseRegistry();
672
+ cleanupTimer;
673
+ requestIdCounter = 0;
674
+ /**
675
+ * 调用API
676
+ * @param method API方法名
677
+ * @param params API参数
678
+ * @param options 调用选项
679
+ */
680
+ async call(method, params = {}, options) {
681
+ const timeout = options?.timeout ?? this.config.api.timeout;
682
+ const retries = options?.retries ?? this.config.api.retries;
683
+ return withRetry(
684
+ () => this.sendRequest(method, params, timeout),
685
+ retries,
686
+ method,
687
+ this.logger,
688
+ this.delay.bind(this)
689
+ );
690
+ }
691
+ /**
692
+ * 处理API响应
693
+ * 由连接管理器调用
694
+ */
695
+ handleResponse(echo, response) {
696
+ const request = this.registry.take(echo);
697
+ if (!request) {
698
+ this.logger.warn(`\u6536\u5230\u672A\u77E5\u8BF7\u6C42\u7684\u54CD\u5E94: ${echo}`);
699
+ return;
700
+ }
701
+ if (response.status === "ok" || response.retcode === 0) {
702
+ this.logger.debug(`API\u6210\u529F: ${request.method}`);
703
+ request.resolve(response.data);
704
+ } else {
705
+ this.logger.warn(`API\u5931\u8D25: ${request.method}`, {
706
+ retcode: response.retcode,
707
+ message: response.message
708
+ });
709
+ request.reject(
710
+ new ApiError(
711
+ request.method,
712
+ response.retcode,
713
+ response.message,
714
+ response.wording
715
+ )
716
+ );
717
+ }
718
+ }
719
+ /**
720
+ * 销毁API客户端
721
+ */
722
+ destroy() {
723
+ this.registry.clearAll("API\u5BA2\u6237\u7AEF\u5DF2\u9500\u6BC1");
724
+ if (this.cleanupTimer) {
725
+ clearInterval(this.cleanupTimer);
726
+ this.cleanupTimer = void 0;
727
+ }
728
+ }
729
+ /**
730
+ * 发送API请求
731
+ */
732
+ sendRequest(method, params, timeout) {
733
+ return new Promise((resolve, reject) => {
734
+ const echo = this.generateRequestId();
735
+ this.logger.debug(`\u53D1\u9001API\u8BF7\u6C42: ${method}`, { echo, params });
736
+ this.registry.add(
737
+ echo,
738
+ {
739
+ resolve,
740
+ reject,
741
+ createdAt: Date.now(),
742
+ method
743
+ },
744
+ timeout
745
+ );
746
+ try {
747
+ if (!this.connection.isConnected()) {
748
+ throw new ConnectionClosedError(-1, "\u8FDE\u63A5\u672A\u5EFA\u7ACB\uFF0C\u8BF7\u5148\u8C03\u7528 connect()");
749
+ }
750
+ const { payload } = buildRequestPayload(method, params, echo);
751
+ this.connection.send(payload);
752
+ } catch (error) {
753
+ this.registry.reject(echo, error);
754
+ reject(error);
755
+ }
756
+ });
757
+ }
758
+ /**
759
+ * 延迟
760
+ */
761
+ delay(ms) {
762
+ return new Promise((resolve) => setTimeout(resolve, ms));
763
+ }
764
+ /**
765
+ * 生成请求ID
766
+ */
767
+ generateRequestId() {
768
+ return `naplink_${Date.now()}_${this.requestIdCounter++}`;
769
+ }
770
+ /**
771
+ * 启动清理定时器
772
+ * 定期清理超时的待处理请求
773
+ */
774
+ startCleanupTimer() {
775
+ this.cleanupTimer = setInterval(() => {
776
+ const now = Date.now();
777
+ const timeout = this.config.api.timeout;
778
+ this.registry.cleanupStale(now, timeout * 2, (method, echo) => {
779
+ this.logger.warn(`\u6E05\u7406\u8D85\u65F6\u8BF7\u6C42: ${method}`, { echo });
780
+ });
781
+ }, 6e4);
782
+ }
783
+ };
784
+
785
+ // src/core/event-router.ts
786
+ import EventEmitter from "events";
787
+ var EventRouter = class extends EventEmitter {
788
+ constructor(logger) {
789
+ super();
790
+ this.logger = logger;
791
+ }
792
+ anyListeners = [];
793
+ /**
794
+ * 监听所有事件
795
+ */
796
+ onAny(listener) {
797
+ this.anyListeners.push(listener);
798
+ return this;
799
+ }
800
+ /**
801
+ * 重写emit以支持onAny
802
+ */
803
+ emit(event, ...args) {
804
+ this.anyListeners.forEach((listener) => {
805
+ try {
806
+ listener(event, ...args);
807
+ } catch (error) {
808
+ this.logger.error("onAny listener error", error);
809
+ }
810
+ });
811
+ return super.emit(event, ...args);
812
+ }
813
+ /**
814
+ * 路由消息到相应的事件
815
+ * @param data 原始消息数据
816
+ */
817
+ route(data) {
818
+ try {
819
+ const postType = data.post_type;
820
+ if (!postType) {
821
+ this.logger.warn("\u6536\u5230\u65E0\u6548\u6D88\u606F: \u7F3A\u5C11 post_type", data);
822
+ return;
823
+ }
824
+ this.logger.debug(`\u8DEF\u7531\u4E8B\u4EF6: ${postType}`, {
825
+ messageType: data.message_type,
826
+ noticeType: data.notice_type
827
+ });
828
+ switch (postType) {
829
+ case "meta_event":
830
+ this.routeMetaEvent(data);
831
+ break;
832
+ case "message":
833
+ this.routeMessage(data);
834
+ break;
835
+ case "message_sent":
836
+ this.routeMessageSent(data);
837
+ break;
838
+ case "notice":
839
+ this.routeNotice(data);
840
+ break;
841
+ case "request":
842
+ this.routeRequest(data);
843
+ break;
844
+ default:
845
+ this.logger.warn(`\u672A\u77E5\u7684 post_type: ${postType}`);
846
+ this.emit("unknown", data);
847
+ }
848
+ this.emit("raw", data);
849
+ } catch (error) {
850
+ this.logger.error("\u4E8B\u4EF6\u8DEF\u7531\u5931\u8D25", error, data);
851
+ }
852
+ }
853
+ /**
854
+ * 路由元事件
855
+ */
856
+ routeMetaEvent(data) {
857
+ const type = data.meta_event_type;
858
+ const events = [`meta_event`, `meta_event.${type}`];
859
+ if (type === "lifecycle") {
860
+ events.push(`meta_event.lifecycle.${data.sub_type}`);
861
+ }
862
+ this.emitEvents(events, data);
863
+ }
864
+ /**
865
+ * 路由消息事件
866
+ */
867
+ routeMessage(data) {
868
+ const messageType = data.message_type;
869
+ const subType = data.sub_type;
870
+ const events = [
871
+ "message",
872
+ `message.${messageType}`,
873
+ `message.${messageType}.${subType}`
874
+ ];
875
+ this.emitEvents(events, data);
876
+ }
877
+ /**
878
+ * 路由发送消息事件
879
+ */
880
+ routeMessageSent(data) {
881
+ const messageType = data.message_type;
882
+ const subType = data.sub_type;
883
+ const events = [
884
+ "message_sent",
885
+ `message_sent.${messageType}`,
886
+ `message_sent.${messageType}.${subType}`
887
+ ];
888
+ this.emitEvents(events, data);
889
+ }
890
+ /**
891
+ * 路由通知事件
892
+ */
893
+ routeNotice(data) {
894
+ const noticeType = data.notice_type;
895
+ const subType = data.sub_type;
896
+ const events = ["notice", `notice.${noticeType}`];
897
+ if (subType) {
898
+ events.push(`notice.${noticeType}.${subType}`);
899
+ }
900
+ this.emitEvents(events, data);
901
+ }
902
+ /**
903
+ * 路由请求事件
904
+ */
905
+ routeRequest(data) {
906
+ const requestType = data.request_type;
907
+ const subType = data.sub_type;
908
+ const events = ["request", `request.${requestType}`];
909
+ if (subType) {
910
+ events.push(`request.${requestType}.${subType}`);
911
+ }
912
+ this.emitEvents(events, data);
913
+ }
914
+ /**
915
+ * 触发多个事件
916
+ */
917
+ emitEvents(events, data) {
918
+ for (const event of events) {
919
+ this.logger.debug(`\u89E6\u53D1\u4E8B\u4EF6: ${event}`);
920
+ this.emit(event, data);
921
+ }
922
+ }
923
+ };
924
+
925
+ // src/core/dispatcher.ts
926
+ var MessageDispatcher = class {
927
+ constructor(apiClient, eventRouter, logger) {
928
+ this.apiClient = apiClient;
929
+ this.eventRouter = eventRouter;
930
+ this.logger = logger;
931
+ }
932
+ /**
933
+ * 分发消息
934
+ * @param message WebSocket 接收到的原始字符串消息
935
+ */
936
+ dispatch(message) {
937
+ try {
938
+ const data = JSON.parse(message);
939
+ if (data.echo) {
940
+ if (typeof data.echo === "string" && data.echo.startsWith("heartbeat_")) {
941
+ return;
942
+ }
943
+ this.apiClient.handleResponse(data.echo, data);
944
+ } else {
945
+ this.eventRouter.route(data);
946
+ }
947
+ } catch (error) {
948
+ this.logger.error("\u6D88\u606F\u89E3\u6790\u5931\u8D25", error, { message });
949
+ }
950
+ }
951
+ };
952
+
953
+ // src/core/connection/state-handler.ts
954
+ function handleConnectionStateChange(emitter, state) {
955
+ emitter.emit("state_change", state);
956
+ switch (state) {
957
+ case "connected" /* CONNECTED */:
958
+ emitter.emit("connect");
959
+ break;
960
+ case "disconnected" /* DISCONNECTED */:
961
+ emitter.emit("disconnect");
962
+ break;
963
+ case "reconnecting" /* RECONNECTING */:
964
+ emitter.emit("reconnecting");
965
+ break;
966
+ }
967
+ }
968
+
969
+ // src/api/onebot/message.ts
970
+ function createMessageApi(client) {
971
+ return {
972
+ sendMessage(params) {
973
+ return client.call("send_msg", params);
974
+ },
975
+ sendPrivateMessage(userId, message) {
976
+ return client.call("send_private_msg", { user_id: userId, message });
977
+ },
978
+ sendGroupMessage(groupId, message) {
979
+ return client.call("send_group_msg", { group_id: groupId, message });
980
+ },
981
+ deleteMessage(messageId) {
982
+ return client.call("delete_msg", { message_id: messageId });
983
+ },
984
+ getMessage(messageId) {
985
+ return client.call("get_msg", { message_id: messageId });
986
+ },
987
+ getForwardMessage(id) {
988
+ return client.call("get_forward_msg", { id });
989
+ },
990
+ sendGroupForwardMessage(groupId, messages) {
991
+ return client.call("send_group_forward_msg", { group_id: groupId, messages });
992
+ },
993
+ setEssenceMessage(messageId) {
994
+ return client.call("set_essence_msg", { message_id: messageId });
995
+ },
996
+ deleteEssenceMessage(messageId) {
997
+ return client.call("delete_essence_msg", { message_id: messageId });
998
+ },
999
+ getEssenceMessageList(groupId) {
1000
+ return client.call("get_essence_msg_list", { group_id: groupId });
1001
+ },
1002
+ markMessageAsRead(messageId) {
1003
+ return client.call("mark_msg_as_read", { message_id: messageId });
1004
+ },
1005
+ getGroupAtAllRemain(groupId) {
1006
+ return client.call("get_group_at_all_remain", { group_id: groupId });
1007
+ },
1008
+ getGroupSystemMsg() {
1009
+ return client.call("get_group_system_msg");
1010
+ },
1011
+ getGroupHonorInfo(groupId, type) {
1012
+ return client.call("get_group_honor_info", { group_id: groupId, type });
1013
+ }
1014
+ };
1015
+ }
1016
+
1017
+ // src/api/onebot/media.ts
1018
+ function createMediaApi(client, logger) {
1019
+ const api = {
1020
+ getImage(file) {
1021
+ return client.call("get_image", { file });
1022
+ },
1023
+ getRecord(file, outFormat) {
1024
+ return client.call("get_record", { file, out_format: outFormat });
1025
+ },
1026
+ getFile(file) {
1027
+ return client.call("get_file", { file });
1028
+ },
1029
+ async hydrateMedia(message) {
1030
+ if (!Array.isArray(message)) return;
1031
+ await Promise.all(message.map(async (segment) => {
1032
+ const type = segment?.type;
1033
+ const data = segment?.data;
1034
+ if (!type || !data) return;
1035
+ if (["image", "video", "record", "audio", "file"].includes(type)) {
1036
+ const fileId = data.file;
1037
+ if (typeof fileId === "string" && !/^https?:\/\//.test(fileId) && !fileId.startsWith("file://")) {
1038
+ try {
1039
+ const res = await api.getFile(fileId);
1040
+ if (res?.file) {
1041
+ data.url = res.file;
1042
+ data.file = res.file;
1043
+ return;
1044
+ }
1045
+ if (type === "record" || type === "audio") {
1046
+ const rec = await api.getRecord(fileId, "mp3");
1047
+ if (rec?.file) {
1048
+ data.url = rec.file;
1049
+ data.file = rec.file;
1050
+ }
1051
+ } else if (type === "image") {
1052
+ const img = await api.getImage(fileId);
1053
+ if (img?.file) {
1054
+ data.url = img.file;
1055
+ data.file = img.file;
1056
+ }
1057
+ }
1058
+ } catch (e) {
1059
+ logger.debug(`Failed to hydrate media for ${type}: ${fileId}`, e);
1060
+ }
1061
+ }
1062
+ }
1063
+ }));
1064
+ }
1065
+ };
1066
+ return api;
1067
+ }
1068
+
1069
+ // src/api/onebot/account.ts
1070
+ function createAccountApi(client) {
1071
+ return {
1072
+ getLoginInfo() {
1073
+ return client.call("get_login_info");
1074
+ },
1075
+ getStatus() {
1076
+ return client.call("get_status");
1077
+ },
1078
+ getFriendList() {
1079
+ return client.call("get_friend_list");
1080
+ },
1081
+ getGroupList() {
1082
+ return client.call("get_group_list");
1083
+ },
1084
+ getGroupInfo(groupId, noCache = false) {
1085
+ return client.call("get_group_info", { group_id: groupId, no_cache: noCache });
1086
+ },
1087
+ getGroupMemberList(groupId) {
1088
+ return client.call("get_group_member_list", { group_id: groupId });
1089
+ },
1090
+ getGroupMemberInfo(groupId, userId, noCache = false) {
1091
+ return client.call("get_group_member_info", {
1092
+ group_id: groupId,
1093
+ user_id: userId,
1094
+ no_cache: noCache
1095
+ });
1096
+ },
1097
+ getStrangerInfo(userId, noCache = false) {
1098
+ return client.call("get_stranger_info", { user_id: userId, no_cache: noCache });
1099
+ },
1100
+ getVersionInfo() {
1101
+ return client.call("get_version_info");
1102
+ }
1103
+ };
1104
+ }
1105
+
1106
+ // src/api/onebot/group.ts
1107
+ function createGroupApi(client) {
1108
+ return {
1109
+ setGroupBan(groupId, userId, duration = 30 * 60) {
1110
+ return client.call("set_group_ban", { group_id: groupId, user_id: userId, duration });
1111
+ },
1112
+ unsetGroupBan(groupId, userId) {
1113
+ return client.call("set_group_ban", { group_id: groupId, user_id: userId, duration: 0 });
1114
+ },
1115
+ setGroupWholeBan(groupId, enable = true) {
1116
+ return client.call("set_group_whole_ban", { group_id: groupId, enable });
1117
+ },
1118
+ setGroupKick(groupId, userId, rejectAddRequest = false) {
1119
+ return client.call("set_group_kick", {
1120
+ group_id: groupId,
1121
+ user_id: userId,
1122
+ reject_add_request: rejectAddRequest
1123
+ });
1124
+ },
1125
+ setGroupLeave(groupId, isDismiss = false) {
1126
+ return client.call("set_group_leave", { group_id: groupId, is_dismiss: isDismiss });
1127
+ },
1128
+ setGroupCard(groupId, userId, card) {
1129
+ return client.call("set_group_card", { group_id: groupId, user_id: userId, card });
1130
+ },
1131
+ setGroupName(groupId, groupName) {
1132
+ return client.call("set_group_name", { group_id: groupId, group_name: groupName });
1133
+ },
1134
+ setGroupAdmin(groupId, userId, enable = true) {
1135
+ return client.call("set_group_admin", { group_id: groupId, user_id: userId, enable });
1136
+ },
1137
+ setGroupAnonymousBan(groupId, anonymousFlag, duration = 30 * 60) {
1138
+ return client.call("set_group_anonymous_ban", {
1139
+ group_id: groupId,
1140
+ anonymous_flag: anonymousFlag,
1141
+ duration
1142
+ });
1143
+ },
1144
+ setGroupSpecialTitle(groupId, userId, specialTitle, duration = -1) {
1145
+ return client.call("set_group_special_title", {
1146
+ group_id: groupId,
1147
+ user_id: userId,
1148
+ special_title: specialTitle,
1149
+ duration
1150
+ });
1151
+ },
1152
+ sendLike(userId, times = 1) {
1153
+ return client.call("send_like", { user_id: userId, times });
1154
+ }
1155
+ };
1156
+ }
1157
+
1158
+ // src/api/onebot/files.ts
1159
+ import { promises as fs, createWriteStream } from "fs";
1160
+ import { tmpdir } from "os";
1161
+ import { join } from "path";
1162
+ import { randomUUID } from "crypto";
1163
+ import { pipeline } from "stream/promises";
1164
+ function createFileApi(client) {
1165
+ const normalizeFileInput = async (file, name) => {
1166
+ if (typeof file === "string") return file;
1167
+ const tempPath = join(tmpdir(), `${randomUUID()}-${name || "naplink.tmp"}`);
1168
+ if (file instanceof Buffer || file instanceof Uint8Array) {
1169
+ await fs.writeFile(tempPath, file);
1170
+ return tempPath;
1171
+ }
1172
+ await pipeline(file, createWriteStream(tempPath));
1173
+ return tempPath;
1174
+ };
1175
+ return {
1176
+ async uploadGroupFile(groupId, file, name) {
1177
+ const normalized = await normalizeFileInput(file, name);
1178
+ return client.call("upload_group_file", { group_id: groupId, file: normalized, name });
1179
+ },
1180
+ async uploadPrivateFile(userId, file, name) {
1181
+ const normalized = await normalizeFileInput(file, name);
1182
+ return client.call("upload_private_file", { user_id: userId, file: normalized, name });
1183
+ },
1184
+ async setGroupPortrait(groupId, file) {
1185
+ return this.uploadGroupFile(groupId, file, "portrait");
1186
+ },
1187
+ getGroupFileSystemInfo(groupId) {
1188
+ return client.call("get_group_file_system_info", { group_id: groupId });
1189
+ },
1190
+ getGroupRootFiles(groupId) {
1191
+ return client.call("get_group_root_files", { group_id: groupId });
1192
+ },
1193
+ getGroupFilesByFolder(groupId, folderId) {
1194
+ return client.call("get_group_files_by_folder", { group_id: groupId, folder_id: folderId });
1195
+ },
1196
+ getGroupFileUrl(groupId, fileId, busid) {
1197
+ return client.call("get_group_file_url", { group_id: groupId, file_id: fileId, busid });
1198
+ },
1199
+ deleteGroupFile(groupId, fileId, busid) {
1200
+ return client.call("delete_group_file", { group_id: groupId, file_id: fileId, busid });
1201
+ },
1202
+ createGroupFileFolder(groupId, name, parentId) {
1203
+ return client.call("create_group_file_folder", { group_id: groupId, name, parent_id: parentId });
1204
+ },
1205
+ deleteGroupFolder(groupId, folderId) {
1206
+ return client.call("delete_group_folder", { group_id: groupId, folder_id: folderId });
1207
+ },
1208
+ downloadFile(url, threadCount = 3, headers) {
1209
+ return client.call("download_file", { url, thread_count: threadCount, headers });
1210
+ }
1211
+ };
1212
+ }
1213
+
1214
+ // src/api/onebot/stream.ts
1215
+ import { randomUUID as randomUUID3 } from "crypto";
1216
+
1217
+ // src/core/upload/stream.ts
1218
+ import { promises as fs2, createWriteStream as createWriteStream2, createReadStream } from "fs";
1219
+ import { tmpdir as tmpdir2 } from "os";
1220
+ import { join as join2, basename } from "path";
1221
+ import { randomUUID as randomUUID2, createHash } from "crypto";
1222
+ import { pipeline as pipeline2 } from "stream/promises";
1223
+ async function prepareStreamSource(file, overrideName) {
1224
+ if (typeof file === "string") {
1225
+ const stats2 = await fs2.stat(file);
1226
+ return { source: file, size: stats2.size, filename: overrideName || basename(file) };
1227
+ }
1228
+ if (file instanceof Buffer || file instanceof Uint8Array) {
1229
+ return { source: Buffer.from(file), size: file.length, filename: overrideName || "upload.bin" };
1230
+ }
1231
+ const tempPath = join2(tmpdir2(), `${randomUUID2()}-upload.tmp`);
1232
+ await pipeline2(file, createWriteStream2(tempPath));
1233
+ const stats = await fs2.stat(tempPath);
1234
+ return {
1235
+ source: tempPath,
1236
+ size: stats.size,
1237
+ filename: overrideName || basename(tempPath),
1238
+ cleanupTemp: async () => {
1239
+ try {
1240
+ await fs2.unlink(tempPath);
1241
+ } catch {
1242
+ }
1243
+ }
1244
+ };
1245
+ }
1246
+ async function* iterateChunks(source, size, chunkSize) {
1247
+ if (typeof source === "string") {
1248
+ const stream = createReadStream(source, { highWaterMark: chunkSize });
1249
+ for await (const chunk of stream) {
1250
+ yield chunk;
1251
+ }
1252
+ } else {
1253
+ let offset = 0;
1254
+ while (offset < size) {
1255
+ const end = Math.min(offset + chunkSize, size);
1256
+ yield source.slice(offset, end);
1257
+ offset = end;
1258
+ }
1259
+ }
1260
+ }
1261
+ async function computeSha256(source) {
1262
+ const hash = createHash("sha256");
1263
+ if (typeof source === "string") {
1264
+ const stream = createReadStream(source);
1265
+ for await (const chunk of stream) {
1266
+ hash.update(chunk);
1267
+ }
1268
+ } else {
1269
+ hash.update(source);
1270
+ }
1271
+ return hash.digest("hex");
1272
+ }
1273
+
1274
+ // src/api/onebot/stream.ts
1275
+ function createStreamApi(client) {
1276
+ return {
1277
+ async uploadFileStream(file, options) {
1278
+ const chunkSize = options?.chunkSize ?? 256 * 1024;
1279
+ const streamId = options?.streamId ?? randomUUID3();
1280
+ const { source, size, filename, cleanupTemp } = await prepareStreamSource(file, options?.filename);
1281
+ const expectedSha256 = options?.expectedSha256 ?? await computeSha256(source);
1282
+ const totalChunks = Math.ceil(size / chunkSize);
1283
+ if (options?.reset) {
1284
+ await client.call("upload_file_stream", { stream_id: streamId, reset: true });
1285
+ }
1286
+ let chunkIndex = 0;
1287
+ for await (const chunk of iterateChunks(source, size, chunkSize)) {
1288
+ const payload = {
1289
+ stream_id: streamId,
1290
+ chunk_data: chunk.toString("base64"),
1291
+ chunk_index: chunkIndex,
1292
+ total_chunks: totalChunks,
1293
+ file_size: size,
1294
+ expected_sha256: expectedSha256,
1295
+ filename
1296
+ };
1297
+ if (options?.fileRetention != null) {
1298
+ payload.file_retention = options.fileRetention;
1299
+ }
1300
+ if (options?.verifyOnly) {
1301
+ payload.verify_only = true;
1302
+ }
1303
+ await client.call("upload_file_stream", payload);
1304
+ chunkIndex++;
1305
+ }
1306
+ const completion = await client.call("upload_file_stream", {
1307
+ stream_id: streamId,
1308
+ is_complete: true
1309
+ });
1310
+ await cleanupTemp?.();
1311
+ return completion;
1312
+ },
1313
+ getUploadStreamStatus(streamId) {
1314
+ return client.call("upload_file_stream", { stream_id: streamId });
1315
+ }
1316
+ };
1317
+ }
1318
+
1319
+ // src/api/onebot/requests.ts
1320
+ function createRequestApi(client) {
1321
+ return {
1322
+ handleFriendRequest(flag, approve = true, remark) {
1323
+ return client.call("set_friend_add_request", { flag, approve, remark });
1324
+ },
1325
+ handleGroupRequest(flag, subType, approve = true, reason) {
1326
+ return client.call("set_group_add_request", { flag, sub_type: subType, approve, reason });
1327
+ }
1328
+ };
1329
+ }
1330
+
1331
+ // src/api/onebot/index.ts
1332
+ var OneBotApi = class {
1333
+ messageApi;
1334
+ mediaApi;
1335
+ accountApi;
1336
+ groupApi;
1337
+ fileApi;
1338
+ streamApi;
1339
+ requestApi;
1340
+ constructor(client, logger) {
1341
+ this.messageApi = createMessageApi(client);
1342
+ this.mediaApi = createMediaApi(client, logger);
1343
+ this.accountApi = createAccountApi(client);
1344
+ this.groupApi = createGroupApi(client);
1345
+ this.fileApi = createFileApi(client);
1346
+ this.streamApi = createStreamApi(client);
1347
+ this.requestApi = createRequestApi(client);
1348
+ Object.assign(
1349
+ this,
1350
+ this.messageApi,
1351
+ this.mediaApi,
1352
+ this.accountApi,
1353
+ this.groupApi,
1354
+ this.fileApi,
1355
+ this.streamApi,
1356
+ this.requestApi
1357
+ );
1358
+ }
1359
+ };
1360
+
1361
+ // src/api/delegates.ts
1362
+ function bindOneBotApiMethods(api, target) {
1363
+ const bindings = {
1364
+ getLoginInfo: () => api.getLoginInfo(),
1365
+ getStatus: () => api.getStatus(),
1366
+ sendMessage: (params) => api.sendMessage(params),
1367
+ sendPrivateMessage: (userId, message) => api.sendPrivateMessage(userId, message),
1368
+ sendGroupMessage: (groupId, message) => api.sendGroupMessage(groupId, message),
1369
+ deleteMessage: (messageId) => api.deleteMessage(messageId),
1370
+ getMessage: (messageId) => api.getMessage(messageId),
1371
+ getForwardMessage: (id) => api.getForwardMessage(id),
1372
+ getEssenceMessageList: (groupId) => api.getEssenceMessageList(groupId),
1373
+ markMessageAsRead: (messageId) => api.markMessageAsRead(messageId),
1374
+ getGroupAtAllRemain: (groupId) => api.getGroupAtAllRemain(groupId),
1375
+ getGroupSystemMsg: () => api.getGroupSystemMsg(),
1376
+ getGroupHonorInfo: (groupId, type) => api.getGroupHonorInfo(groupId, type),
1377
+ getGroupFileSystemInfo: (groupId) => api.getGroupFileSystemInfo(groupId),
1378
+ getGroupRootFiles: (groupId) => api.getGroupRootFiles(groupId),
1379
+ getGroupFilesByFolder: (groupId, folderId) => api.getGroupFilesByFolder(groupId, folderId),
1380
+ getGroupFileUrl: (groupId, fileId, busid) => api.getGroupFileUrl(groupId, fileId, busid),
1381
+ deleteGroupFile: (groupId, fileId, busid) => api.deleteGroupFile(groupId, fileId, busid),
1382
+ createGroupFileFolder: (groupId, name, parentId) => api.createGroupFileFolder(groupId, name, parentId),
1383
+ deleteGroupFolder: (groupId, folderId) => api.deleteGroupFolder(groupId, folderId),
1384
+ downloadFile: (url, threadCount, headers) => api.downloadFile(url, threadCount, headers),
1385
+ uploadFileStream: (file, options) => api.uploadFileStream(file, options),
1386
+ getUploadStreamStatus: (streamId) => api.getUploadStreamStatus(streamId),
1387
+ sendGroupForwardMessage: (groupId, messages) => api.sendGroupForwardMessage(groupId, messages),
1388
+ getImage: (file) => api.getImage(file),
1389
+ getRecord: (file, outFormat) => api.getRecord(file, outFormat),
1390
+ getFile: (file) => api.getFile(file),
1391
+ getFriendList: () => api.getFriendList(),
1392
+ getGroupList: () => api.getGroupList(),
1393
+ getGroupInfo: (groupId, noCache = false) => api.getGroupInfo(groupId, noCache),
1394
+ getGroupMemberList: (groupId) => api.getGroupMemberList(groupId),
1395
+ getGroupMemberInfo: (groupId, userId, noCache = false) => api.getGroupMemberInfo(groupId, userId, noCache),
1396
+ setGroupBan: (groupId, userId, duration = 30 * 60) => api.setGroupBan(groupId, userId, duration),
1397
+ unsetGroupBan: (groupId, userId) => api.unsetGroupBan(groupId, userId),
1398
+ setGroupWholeBan: (groupId, enable = true) => api.setGroupWholeBan(groupId, enable),
1399
+ setGroupKick: (groupId, userId, rejectAddRequest = false) => api.setGroupKick(groupId, userId, rejectAddRequest),
1400
+ setGroupLeave: (groupId, isDismiss = false) => api.setGroupLeave(groupId, isDismiss),
1401
+ setGroupCard: (groupId, userId, card) => api.setGroupCard(groupId, userId, card),
1402
+ setGroupName: (groupId, groupName) => api.setGroupName(groupId, groupName),
1403
+ setGroupPortrait: (groupId, file) => api.setGroupPortrait(groupId, file),
1404
+ setGroupAdmin: (groupId, userId, enable = true) => api.setGroupAdmin(groupId, userId, enable),
1405
+ setGroupAnonymousBan: (groupId, anonymousFlag, duration = 30 * 60) => api.setGroupAnonymousBan(groupId, anonymousFlag, duration),
1406
+ setEssenceMessage: (messageId) => api.setEssenceMessage(messageId),
1407
+ deleteEssenceMessage: (messageId) => api.deleteEssenceMessage(messageId),
1408
+ setGroupSpecialTitle: (groupId, userId, specialTitle, duration = -1) => api.setGroupSpecialTitle(groupId, userId, specialTitle, duration),
1409
+ sendLike: (userId, times = 1) => api.sendLike(userId, times),
1410
+ uploadGroupFile: (groupId, file, name) => api.uploadGroupFile(groupId, file, name),
1411
+ uploadPrivateFile: (userId, file, name) => api.uploadPrivateFile(userId, file, name),
1412
+ getStrangerInfo: (userId, noCache = false) => api.getStrangerInfo(userId, noCache),
1413
+ getVersionInfo: () => api.getVersionInfo(),
1414
+ handleFriendRequest: (flag, approve = true, remark) => api.handleFriendRequest(flag, approve, remark),
1415
+ handleGroupRequest: (flag, subType, approve = true, reason) => api.handleGroupRequest(flag, subType, approve, reason)
1416
+ };
1417
+ Object.assign(target, bindings);
1418
+ }
1419
+
1420
+ // src/types/config.ts
1421
+ var DEFAULT_CONFIG = {
1422
+ reconnect: {
1423
+ enabled: true,
1424
+ maxAttempts: 10,
1425
+ backoff: {
1426
+ initial: 1e3,
1427
+ max: 6e4,
1428
+ multiplier: 2
1429
+ }
1430
+ },
1431
+ logging: {
1432
+ level: "info"
1433
+ },
1434
+ api: {
1435
+ timeout: 3e4,
1436
+ retries: 3
1437
+ }
1438
+ };
1439
+
1440
+ // src/utils/merge-config.ts
1441
+ function mergeConfig(userConfig) {
1442
+ if (!userConfig.connection?.url) {
1443
+ throw new InvalidConfigError("connection.url", "\u5FC5\u987B\u63D0\u4F9B\u8FDE\u63A5URL");
1444
+ }
1445
+ return {
1446
+ connection: {
1447
+ url: userConfig.connection.url,
1448
+ token: userConfig.connection.token,
1449
+ timeout: userConfig.connection.timeout ?? 3e4,
1450
+ pingInterval: userConfig.connection.pingInterval ?? 3e4,
1451
+ heartbeatAction: userConfig.connection.heartbeatAction ?? {
1452
+ action: "get_status",
1453
+ params: {}
1454
+ }
1455
+ },
1456
+ reconnect: {
1457
+ enabled: userConfig.reconnect?.enabled ?? DEFAULT_CONFIG.reconnect.enabled,
1458
+ maxAttempts: userConfig.reconnect?.maxAttempts ?? DEFAULT_CONFIG.reconnect.maxAttempts,
1459
+ backoff: {
1460
+ initial: userConfig.reconnect?.backoff?.initial ?? DEFAULT_CONFIG.reconnect.backoff.initial,
1461
+ max: userConfig.reconnect?.backoff?.max ?? DEFAULT_CONFIG.reconnect.backoff.max,
1462
+ multiplier: userConfig.reconnect?.backoff?.multiplier ?? DEFAULT_CONFIG.reconnect.backoff.multiplier
1463
+ }
1464
+ },
1465
+ logging: {
1466
+ level: userConfig.logging?.level ?? DEFAULT_CONFIG.logging.level,
1467
+ logger: userConfig.logging?.logger
1468
+ },
1469
+ api: {
1470
+ timeout: userConfig.api?.timeout ?? DEFAULT_CONFIG.api.timeout,
1471
+ retries: userConfig.api?.retries ?? DEFAULT_CONFIG.api.retries
1472
+ }
1473
+ };
1474
+ }
1475
+
1476
+ // src/naplink.ts
1477
+ var NapLink = class extends EventEmitter2 {
1478
+ config;
1479
+ logger;
1480
+ connection;
1481
+ apiClient;
1482
+ eventRouter;
1483
+ dispatcher;
1484
+ oneBotApi;
1485
+ constructor(config) {
1486
+ super();
1487
+ this.config = mergeConfig(config);
1488
+ this.logger = this.config.logging.logger || new DefaultLogger(this.config.logging.level);
1489
+ this.eventRouter = new EventRouter(this.logger);
1490
+ this.setupEventForwarding();
1491
+ this.connection = new ConnectionManager(
1492
+ this.config,
1493
+ this.logger,
1494
+ (message) => this.dispatcher.dispatch(message),
1495
+ (state) => handleConnectionStateChange(this, state)
1496
+ );
1497
+ this.apiClient = new ApiClient(this.connection, this.config, this.logger);
1498
+ this.dispatcher = new MessageDispatcher(this.apiClient, this.eventRouter, this.logger);
1499
+ this.oneBotApi = new OneBotApi(this.apiClient, this.logger);
1500
+ bindOneBotApiMethods(this.oneBotApi, this);
1501
+ this.logger.info("NapLink \u5BA2\u6237\u7AEF\u5DF2\u521D\u59CB\u5316");
1502
+ }
1503
+ /**
1504
+ * 连接到NapCat服务器
1505
+ */
1506
+ async connect() {
1507
+ this.logger.info("\u5F00\u59CB\u8FDE\u63A5...");
1508
+ await this.connection.connect();
1509
+ }
1510
+ /**
1511
+ * 断开连接
1512
+ */
1513
+ disconnect() {
1514
+ this.logger.info("\u65AD\u5F00\u8FDE\u63A5...");
1515
+ this.connection.disconnect();
1516
+ this.apiClient.destroy();
1517
+ }
1518
+ /**
1519
+ * 获取连接状态
1520
+ */
1521
+ getState() {
1522
+ return this.connection.getState();
1523
+ }
1524
+ /**
1525
+ * 检查是否已连接
1526
+ */
1527
+ isConnected() {
1528
+ return this.connection.isConnected();
1529
+ }
1530
+ /**
1531
+ * 补充消息中的媒体直链
1532
+ * 自动通过 get_file / get_image / get_record 获取真实下载链接
1533
+ */
1534
+ async hydrateMessage(message) {
1535
+ return this.api.hydrateMedia(message);
1536
+ }
1537
+ /**
1538
+ * 调用自定义API
1539
+ */
1540
+ async callApi(method, params = {}) {
1541
+ return this.apiClient.call(method, params);
1542
+ }
1543
+ /**
1544
+ * 暴露 OneBot API 便于直接使用
1545
+ */
1546
+ get api() {
1547
+ return this.oneBotApi;
1548
+ }
1549
+ // ============ 内部方法 ============
1550
+ /**
1551
+ * 设置事件转发
1552
+ */
1553
+ setupEventForwarding() {
1554
+ this.eventRouter.onAny((event, data) => {
1555
+ this.emit(event, data);
1556
+ });
1557
+ }
1558
+ };
1559
+ export {
1560
+ ApiError,
1561
+ ApiTimeoutError,
1562
+ ConnectionClosedError,
1563
+ ConnectionError,
1564
+ ConnectionState,
1565
+ InvalidConfigError,
1566
+ MaxReconnectAttemptsError,
1567
+ NapLink,
1568
+ NapLinkError,
1569
+ OneBotApi,
1570
+ NapLink as default
1571
+ };
1572
+ //# sourceMappingURL=index.js.map