koishi-plugin-chat-patch 1.2.0 → 1.3.0
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/client/icons/activity.vue +9 -9
- package/client/icons/index.ts +4 -4
- package/client/index.scss +4 -4
- package/client/index.ts +22 -22
- package/client/vue/chat-logic.ts +3784 -3784
- package/client/vue/index.vue +361 -361
- package/client/vue/style.css +1997 -1973
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/lib/index.js +20 -3
- package/package.json +40 -40
- package/readme.md +25 -25
- package/src/api-handlers.ts +657 -657
- package/src/config.ts +49 -49
- package/src/file-manager.ts +168 -168
- package/src/index.ts +125 -125
- package/src/message-handler.ts +304 -282
- package/src/types.ts +59 -59
- package/src/utils.ts +99 -99
- package/lib/config.d.ts +0 -14
package/src/api-handlers.ts
CHANGED
|
@@ -1,657 +1,657 @@
|
|
|
1
|
-
import { FileManager } from './file-manager'
|
|
2
|
-
import { MessageHandler } from './message-handler'
|
|
3
|
-
import { Context, h, Logger } from 'koishi'
|
|
4
|
-
import { Config } from './config'
|
|
5
|
-
import { } from '@koishijs/plugin-console'
|
|
6
|
-
import { URL, pathToFileURL } from 'node:url'
|
|
7
|
-
|
|
8
|
-
export class ApiHandlers {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
1
|
+
import { FileManager } from './file-manager'
|
|
2
|
+
import { MessageHandler } from './message-handler'
|
|
3
|
+
import { Context, h, Logger } from 'koishi'
|
|
4
|
+
import { Config } from './config'
|
|
5
|
+
import { } from '@koishijs/plugin-console'
|
|
6
|
+
import { URL, pathToFileURL } from 'node:url'
|
|
7
|
+
|
|
8
|
+
export class ApiHandlers {
|
|
9
|
+
private logger: Logger
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private ctx: Context,
|
|
13
|
+
private config: Config,
|
|
14
|
+
private fileManager: FileManager,
|
|
15
|
+
private messageHandler: MessageHandler
|
|
16
|
+
) {
|
|
17
|
+
this.logger = ctx.logger('chat-patch')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
registerApiHandlers() {
|
|
21
|
+
this.ctx.console.addListener('clear-all-indexeddb-data' as any, async () => {
|
|
22
|
+
try {
|
|
23
|
+
this.logInfo('收到清空 IndexedDB 数据请求')
|
|
24
|
+
// 这个 API 主要用于前端调用,后端不直接操作 IndexedDB
|
|
25
|
+
return { success: true, message: '可以清空 IndexedDB' }
|
|
26
|
+
} catch (error: any) {
|
|
27
|
+
this.logger.error('清空 IndexedDB 数据失败:', error)
|
|
28
|
+
return { success: false, error: error?.message || String(error) }
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// 获取所有聊天数据的 API
|
|
33
|
+
this.ctx.console.addListener('get-chat-data' as any, async () => {
|
|
34
|
+
try {
|
|
35
|
+
const data = this.fileManager.readChatDataFromFile()
|
|
36
|
+
const cleanedData = this.fileManager.cleanExcessMessages(data)
|
|
37
|
+
|
|
38
|
+
this.logInfo('获取聊天数据:', {
|
|
39
|
+
机器人数量: Object.keys(cleanedData.bots).length,
|
|
40
|
+
频道数量: Object.keys(cleanedData.channels).reduce((total, botId) =>
|
|
41
|
+
total + Object.keys(cleanedData.channels[botId] || {}).length, 0),
|
|
42
|
+
消息频道数: Object.keys(cleanedData.messages).length,
|
|
43
|
+
总消息数: Object.values(cleanedData.messages).reduce((total, msgs) => total + msgs.length, 0)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
data: {
|
|
49
|
+
...cleanedData,
|
|
50
|
+
pinnedBots: cleanedData.pinnedBots,
|
|
51
|
+
pinnedChannels: cleanedData.pinnedChannels
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (error: any) {
|
|
55
|
+
this.logger.error('获取聊天数据失败:', error)
|
|
56
|
+
return { success: false, error: error?.message || String(error) }
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// 获取历史消息的 API
|
|
61
|
+
this.ctx.console.addListener('get-history-messages' as any, async (requestData: {
|
|
62
|
+
selfId: string
|
|
63
|
+
channelId: string
|
|
64
|
+
limit?: number
|
|
65
|
+
offset?: number
|
|
66
|
+
}) => {
|
|
67
|
+
try {
|
|
68
|
+
const data = this.fileManager.readChatDataFromFile()
|
|
69
|
+
const channelKey = `${requestData.selfId}:${requestData.channelId}`
|
|
70
|
+
let messages = data.messages[channelKey] || []
|
|
71
|
+
|
|
72
|
+
// 按时间戳降序排列(最新的消息在前面)
|
|
73
|
+
const sortedMessages = messages.sort((a, b) => b.timestamp - a.timestamp)
|
|
74
|
+
|
|
75
|
+
// 如果提供了分页参数,则进行分页处理
|
|
76
|
+
if (requestData.limit !== undefined) {
|
|
77
|
+
const limit = requestData.limit
|
|
78
|
+
const offset = requestData.offset || 0
|
|
79
|
+
messages = sortedMessages.slice(offset, offset + limit)
|
|
80
|
+
// 重新按时间戳升序排列,以保持消息显示顺序
|
|
81
|
+
messages = messages.sort((a, b) => a.timestamp - b.timestamp)
|
|
82
|
+
} else {
|
|
83
|
+
// 默认返回所有消息,按时间戳升序排列
|
|
84
|
+
messages = sortedMessages.sort((a, b) => a.timestamp - b.timestamp)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.logInfo('获取历史消息:', channelKey, '共', messages.length, '条消息')
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
messages: messages,
|
|
92
|
+
total: sortedMessages.length
|
|
93
|
+
}
|
|
94
|
+
} catch (error: any) {
|
|
95
|
+
this.logger.error('获取历史消息失败:', error)
|
|
96
|
+
return { success: false, error: error?.message || String(error), messages: [], total: 0 }
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// 获取所有频道消息数量的 API
|
|
101
|
+
this.ctx.console.addListener('get-all-channel-message-counts' as any, async () => {
|
|
102
|
+
try {
|
|
103
|
+
const data = this.fileManager.readChatDataFromFile()
|
|
104
|
+
const counts: Record<string, number> = {}
|
|
105
|
+
|
|
106
|
+
for (const [channelKey, messages] of Object.entries(data.messages)) {
|
|
107
|
+
counts[channelKey] = messages.length
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.logInfo('获取所有频道消息数量:', {
|
|
111
|
+
频道数: Object.keys(counts).length,
|
|
112
|
+
总消息数: Object.values(counts).reduce((total, count) => total + count, 0)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
counts: counts
|
|
118
|
+
}
|
|
119
|
+
} catch (error: any) {
|
|
120
|
+
this.logger.error('获取频道消息数量失败:', error)
|
|
121
|
+
return { success: false, error: error?.message || String(error), counts: {} }
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// 图片获取 API
|
|
126
|
+
this.ctx.console.addListener('fetch-image' as any, async (data: { url: string }) => {
|
|
127
|
+
try {
|
|
128
|
+
// 检查是否是本地文件路径
|
|
129
|
+
if (this.isFileUrl(data.url)) {
|
|
130
|
+
this.logInfo('处理本地文件请求:', data.url)
|
|
131
|
+
return await this.handleLocalFileRequest(data.url)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 处理网络图片
|
|
135
|
+
const response = await fetch(data.url, {
|
|
136
|
+
headers: {
|
|
137
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
|
138
|
+
'Referer': ''
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const buffer = await response.arrayBuffer()
|
|
147
|
+
const base64 = Buffer.from(buffer).toString('base64')
|
|
148
|
+
const contentType = response.headers.get('content-type') || 'image/jpeg'
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
base64: base64,
|
|
152
|
+
contentType: contentType,
|
|
153
|
+
dataUrl: `data:${contentType};base64,${base64}`
|
|
154
|
+
}
|
|
155
|
+
} catch (error: any) {
|
|
156
|
+
return { success: false, error: error?.message || String(error) }
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// 清理频道历史记录的 API
|
|
161
|
+
this.ctx.console.addListener('clear-channel-history' as any, async (data: {
|
|
162
|
+
selfId: string
|
|
163
|
+
channelId: string
|
|
164
|
+
keepCount?: number
|
|
165
|
+
}) => {
|
|
166
|
+
try {
|
|
167
|
+
this.logInfo('收到清理历史记录请求:', data)
|
|
168
|
+
|
|
169
|
+
const chatData = this.fileManager.readChatDataFromFile()
|
|
170
|
+
const channelKey = `${data.selfId}:${data.channelId}`
|
|
171
|
+
|
|
172
|
+
if (!chatData.messages[channelKey]) {
|
|
173
|
+
return { success: true, message: '频道没有历史消息' }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const messages = chatData.messages[channelKey]
|
|
177
|
+
const originalCount = messages.length
|
|
178
|
+
|
|
179
|
+
const keepCount = data.keepCount || this.config.keepMessagesOnClear
|
|
180
|
+
|
|
181
|
+
if (keepCount > 0 && originalCount <= keepCount) {
|
|
182
|
+
return { success: true, message: `消息数量(${originalCount})未超过保留数量(${keepCount}),无需清理` }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp)
|
|
186
|
+
const keptMessages = keepCount > 0 ? sortedMessages.slice(-keepCount) : []
|
|
187
|
+
const clearedCount = originalCount - keptMessages.length
|
|
188
|
+
|
|
189
|
+
chatData.messages[channelKey] = keptMessages
|
|
190
|
+
this.fileManager.writeChatDataToFile(chatData)
|
|
191
|
+
|
|
192
|
+
this.logInfo(`频道 ${channelKey} 历史记录清理完成:`, {
|
|
193
|
+
原始消息数: originalCount,
|
|
194
|
+
保留消息数: keptMessages.length,
|
|
195
|
+
清理消息数: clearedCount
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
message: `成功清理 ${clearedCount} 条历史消息,保留最新 ${keptMessages.length} 条`,
|
|
201
|
+
clearedCount: clearedCount,
|
|
202
|
+
keptCount: keptMessages.length
|
|
203
|
+
}
|
|
204
|
+
} catch (error: any) {
|
|
205
|
+
this.logger.error('清理频道历史记录失败:', error)
|
|
206
|
+
return { success: false, error: error?.message || String(error) }
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// 发送消息的 API
|
|
211
|
+
this.ctx.console.addListener('send-message' as any, async (data: {
|
|
212
|
+
selfId: string
|
|
213
|
+
channelId: string
|
|
214
|
+
content: string
|
|
215
|
+
images?: Array<{
|
|
216
|
+
tempId: string
|
|
217
|
+
filename: string
|
|
218
|
+
}>
|
|
219
|
+
}) => {
|
|
220
|
+
try {
|
|
221
|
+
this.logInfo('收到发送消息请求:', data)
|
|
222
|
+
|
|
223
|
+
const bot = this.ctx.bots.find((bot: any) => bot.selfId === data.selfId)
|
|
224
|
+
if (!bot) {
|
|
225
|
+
this.logger.error('未找到机器人:', data.selfId)
|
|
226
|
+
return { success: false, error: '未找到指定的机器人' }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let messageContent = data.content
|
|
230
|
+
|
|
231
|
+
// 如果有图片,添加图片元素
|
|
232
|
+
if (data.images && data.images.length > 0) {
|
|
233
|
+
const tempDir = this.ctx.baseDir + '/data/chat-patch/temp'
|
|
234
|
+
|
|
235
|
+
for (const image of data.images) {
|
|
236
|
+
const files = require('fs').readdirSync(tempDir).filter((file: string) =>
|
|
237
|
+
file.includes(`temp_${image.tempId}`)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if (files.length > 0) {
|
|
241
|
+
const imagePath = `${tempDir}/${files[0]}`
|
|
242
|
+
// 使用 pathToFileURL 创建正确的文件 URL
|
|
243
|
+
const fileUrl = this.createFileUrl(imagePath)
|
|
244
|
+
messageContent += h.image(fileUrl).toString()
|
|
245
|
+
this.logInfo('添加图片到消息:', { imagePath, fileUrl })
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const parsedContent = h.parse(messageContent)
|
|
251
|
+
|
|
252
|
+
// 在发送消息前,通知 MessageHandler 正确的 channelId
|
|
253
|
+
this.messageHandler.setCorrectChannelId(data.selfId, data.channelId)
|
|
254
|
+
|
|
255
|
+
const result = await bot.sendMessage(data.channelId, parsedContent)
|
|
256
|
+
this.logInfo('消息发送成功:', result)
|
|
257
|
+
|
|
258
|
+
// 广播机器人消息发送成功事件,包含真实的 messageId
|
|
259
|
+
const messageId = Array.isArray(result) ? result[0] : result;
|
|
260
|
+
if (messageId) {
|
|
261
|
+
const bot = this.ctx.bots.find((b: any) => b.selfId === data.selfId);
|
|
262
|
+
const botMessageEvent = {
|
|
263
|
+
type: 'bot-message-sent',
|
|
264
|
+
selfId: data.selfId,
|
|
265
|
+
platform: bot?.platform || 'unknown',
|
|
266
|
+
channelId: data.channelId,
|
|
267
|
+
messageId: messageId,
|
|
268
|
+
content: messageContent,
|
|
269
|
+
userId: data.selfId,
|
|
270
|
+
botUsername: bot?.user?.name || `Bot-${data.selfId}`,
|
|
271
|
+
botAvatar: bot?.user?.avatar,
|
|
272
|
+
timestamp: Date.now(),
|
|
273
|
+
guildName: '', // 这个信息在前端会补充
|
|
274
|
+
channelType: 0, // 这个信息在前端会补充
|
|
275
|
+
elements: [], // 这个信息在前端会补充
|
|
276
|
+
isDirect: false // 这个信息在前端会补充
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
this.ctx.console.broadcast('bot-message-sent-event', botMessageEvent);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
success: true,
|
|
284
|
+
messageId: messageId,
|
|
285
|
+
tempImageIds: data.images?.map(img => img.tempId) || []
|
|
286
|
+
}
|
|
287
|
+
} catch (error: any) {
|
|
288
|
+
this.logger.error('发送消息失败:', error)
|
|
289
|
+
return { success: false, error: error?.message || String(error) }
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// 清理临时图片的 API(由前端在消息发送成功后调用)
|
|
294
|
+
this.ctx.console.addListener('cleanup-temp-images' as any, async (data: {
|
|
295
|
+
tempImageIds: string[]
|
|
296
|
+
}) => {
|
|
297
|
+
try {
|
|
298
|
+
this.logInfo('收到清理临时图片请求:', data.tempImageIds)
|
|
299
|
+
|
|
300
|
+
// 不立即删除,只是记录日志,让定时任务处理清理
|
|
301
|
+
this.logInfo('临时图片将由定时任务清理,保持文件可用性')
|
|
302
|
+
|
|
303
|
+
return { success: true, cleanedCount: 0 }
|
|
304
|
+
} catch (error: any) {
|
|
305
|
+
this.logger.error('清理临时图片失败:', error)
|
|
306
|
+
return { success: false, error: error?.message || String(error) }
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// 删除机器人所有数据的 API
|
|
311
|
+
this.ctx.console.addListener('delete-bot-data' as any, async (data: {
|
|
312
|
+
selfId: string
|
|
313
|
+
}) => {
|
|
314
|
+
try {
|
|
315
|
+
this.logInfo('收到删除机器人数据请求:', data)
|
|
316
|
+
|
|
317
|
+
const chatData = this.fileManager.readChatDataFromFile()
|
|
318
|
+
let deletedChannels = 0
|
|
319
|
+
let deletedMessages = 0
|
|
320
|
+
|
|
321
|
+
if (chatData.channels[data.selfId]) {
|
|
322
|
+
deletedChannels = Object.keys(chatData.channels[data.selfId]).length
|
|
323
|
+
delete chatData.channels[data.selfId]
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const channelsToDelete = Object.keys(chatData.messages).filter(key => key.startsWith(`${data.selfId}:`))
|
|
327
|
+
for (const channelKey of channelsToDelete) {
|
|
328
|
+
deletedMessages += chatData.messages[channelKey].length
|
|
329
|
+
delete chatData.messages[channelKey]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
delete chatData.bots[data.selfId]
|
|
333
|
+
this.fileManager.writeChatDataToFile(chatData)
|
|
334
|
+
|
|
335
|
+
this.logInfo(`机器人 ${data.selfId} 数据删除完成:`, {
|
|
336
|
+
删除频道数: deletedChannels,
|
|
337
|
+
删除消息数: deletedMessages
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
success: true,
|
|
342
|
+
message: `成功删除机器人数据:${deletedChannels} 个频道,${deletedMessages} 条消息`,
|
|
343
|
+
deletedChannels,
|
|
344
|
+
deletedMessages
|
|
345
|
+
}
|
|
346
|
+
} catch (error: any) {
|
|
347
|
+
this.logger.error('删除机器人数据失败:', error)
|
|
348
|
+
return { success: false, error: error?.message || String(error) }
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
// 删除频道所有数据的 API
|
|
353
|
+
this.ctx.console.addListener('delete-channel-data' as any, async (data: {
|
|
354
|
+
selfId: string
|
|
355
|
+
channelId: string
|
|
356
|
+
}) => {
|
|
357
|
+
try {
|
|
358
|
+
this.logInfo('收到删除频道数据请求:', data)
|
|
359
|
+
|
|
360
|
+
const chatData = this.fileManager.readChatDataFromFile()
|
|
361
|
+
const channelKey = `${data.selfId}:${data.channelId}`
|
|
362
|
+
let deletedMessages = 0
|
|
363
|
+
|
|
364
|
+
if (chatData.messages[channelKey]) {
|
|
365
|
+
deletedMessages = chatData.messages[channelKey].length
|
|
366
|
+
delete chatData.messages[channelKey]
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (chatData.channels[data.selfId] && chatData.channels[data.selfId][data.channelId]) {
|
|
370
|
+
delete chatData.channels[data.selfId][data.channelId]
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
this.fileManager.writeChatDataToFile(chatData)
|
|
374
|
+
|
|
375
|
+
this.logInfo(`频道 ${channelKey} 数据删除完成:`, {
|
|
376
|
+
删除消息数: deletedMessages
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
success: true,
|
|
381
|
+
message: `成功删除频道数据:${deletedMessages} 条消息`,
|
|
382
|
+
deletedMessages
|
|
383
|
+
}
|
|
384
|
+
} catch (error: any) {
|
|
385
|
+
this.logger.error('删除频道数据失败:', error)
|
|
386
|
+
return { success: false, error: error?.message || String(error) }
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
// 设置置顶机器人列表的 API
|
|
391
|
+
this.ctx.console.addListener('set-pinned-bots' as any, async (data: {
|
|
392
|
+
pinnedBots: string[]
|
|
393
|
+
}) => {
|
|
394
|
+
try {
|
|
395
|
+
this.logInfo('收到设置置顶机器人请求:', data.pinnedBots)
|
|
396
|
+
const chatData = this.fileManager.readChatDataFromFile()
|
|
397
|
+
chatData.pinnedBots = data.pinnedBots
|
|
398
|
+
this.fileManager.writeChatDataToFile(chatData)
|
|
399
|
+
return { success: true }
|
|
400
|
+
} catch (error: any) {
|
|
401
|
+
this.logger.error('设置置顶机器人失败:', error)
|
|
402
|
+
return { success: false, error: error?.message || String(error) }
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
// 设置置顶频道列表的 API
|
|
407
|
+
this.ctx.console.addListener('set-pinned-channels' as any, async (data: {
|
|
408
|
+
pinnedChannels: string[]
|
|
409
|
+
}) => {
|
|
410
|
+
try {
|
|
411
|
+
this.logInfo('收到设置置顶频道请求:', data.pinnedChannels)
|
|
412
|
+
const chatData = this.fileManager.readChatDataFromFile()
|
|
413
|
+
chatData.pinnedChannels = data.pinnedChannels
|
|
414
|
+
this.fileManager.writeChatDataToFile(chatData)
|
|
415
|
+
return { success: true }
|
|
416
|
+
} catch (error: any) {
|
|
417
|
+
this.logger.error('设置置顶频道失败:', error)
|
|
418
|
+
return { success: false, error: error?.message || String(error) }
|
|
419
|
+
}
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
// 上传图片的 API
|
|
423
|
+
this.ctx.console.addListener('upload-image' as any, async (data: {
|
|
424
|
+
file: string // base64 encoded image
|
|
425
|
+
filename: string
|
|
426
|
+
mimeType: string
|
|
427
|
+
isGif?: boolean // 是否为GIF图片
|
|
428
|
+
}) => {
|
|
429
|
+
try {
|
|
430
|
+
this.logInfo('收到图片上传请求:', { filename: data.filename, mimeType: data.mimeType, isGif: data.isGif })
|
|
431
|
+
|
|
432
|
+
// 将base64转换为Buffer
|
|
433
|
+
const base64Data = data.file.replace(/^data:image\/\w+;base64,/, '')
|
|
434
|
+
const buffer = Buffer.from(base64Data, 'base64')
|
|
435
|
+
|
|
436
|
+
// 生成临时文件路径,保持原始扩展名以保持GIF格式
|
|
437
|
+
const tempId = Date.now() + '_' + Math.random().toString(36).substring(2, 11)
|
|
438
|
+
const extension = data.filename.split('.').pop()?.toLowerCase() || (data.isGif ? 'gif' : 'jpg')
|
|
439
|
+
const tempFilename = `temp_${tempId}.${extension}`
|
|
440
|
+
|
|
441
|
+
// 保存到临时目录
|
|
442
|
+
const tempDir = this.ctx.baseDir + '/data/chat-patch/temp'
|
|
443
|
+
if (!require('fs').existsSync(tempDir)) {
|
|
444
|
+
require('fs').mkdirSync(tempDir, { recursive: true })
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const tempPath = `${tempDir}/${tempFilename}`
|
|
448
|
+
|
|
449
|
+
// 对于GIF文件,直接保存原始数据以保持动画
|
|
450
|
+
require('fs').writeFileSync(tempPath, buffer)
|
|
451
|
+
|
|
452
|
+
this.logInfo('图片上传成功:', { tempPath, size: buffer.length, isGif: data.isGif })
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
success: true,
|
|
456
|
+
tempId: tempId,
|
|
457
|
+
tempPath: tempPath,
|
|
458
|
+
filename: data.filename,
|
|
459
|
+
size: buffer.length,
|
|
460
|
+
isGif: data.isGif
|
|
461
|
+
}
|
|
462
|
+
} catch (error: any) {
|
|
463
|
+
this.logger.error('图片上传失败:', error)
|
|
464
|
+
return { success: false, error: error?.message || String(error) }
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
// 删除临时图片的 API
|
|
469
|
+
this.ctx.console.addListener('delete-temp-image' as any, async (data: {
|
|
470
|
+
tempId: string
|
|
471
|
+
}) => {
|
|
472
|
+
try {
|
|
473
|
+
const tempDir = this.ctx.baseDir + '/data/chat-patch/temp'
|
|
474
|
+
const files = require('fs').readdirSync(tempDir).filter((file: string) =>
|
|
475
|
+
file.includes(`temp_${data.tempId}`)
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
for (const file of files) {
|
|
479
|
+
const filePath = `${tempDir}/${file}`
|
|
480
|
+
if (require('fs').existsSync(filePath)) {
|
|
481
|
+
require('fs').unlinkSync(filePath)
|
|
482
|
+
this.logInfo('删除临时图片:', filePath)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return { success: true }
|
|
487
|
+
} catch (error: any) {
|
|
488
|
+
this.logger.error('删除临时图片失败:', error)
|
|
489
|
+
return { success: false, error: error?.message || String(error) }
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
// 定时清理过期临时文件(备用机制)
|
|
494
|
+
this.setupTempFileCleanup()
|
|
495
|
+
|
|
496
|
+
// 获取插件配置的 API
|
|
497
|
+
this.ctx.console.addListener('get-plugin-config' as any, async () => {
|
|
498
|
+
try {
|
|
499
|
+
return {
|
|
500
|
+
success: true,
|
|
501
|
+
config: {
|
|
502
|
+
maxMessagesPerChannel: this.config.maxMessagesPerChannel,
|
|
503
|
+
keepMessagesOnClear: this.config.keepMessagesOnClear,
|
|
504
|
+
keepTempImages: this.config.keepTempImages,
|
|
505
|
+
loggerinfo: this.config.loggerinfo,
|
|
506
|
+
blockedPlatforms: this.config.blockedPlatforms || [],
|
|
507
|
+
chatContainerHeight: this.config.chatContainerHeight,
|
|
508
|
+
clearIndexedDBOnStart: this.config.clearIndexedDBOnStart
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
} catch (error: any) {
|
|
512
|
+
this.logger.error('获取插件配置失败:', error)
|
|
513
|
+
return { success: false, error: error?.message || String(error) }
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
// 调试API:获取原始文件数据
|
|
518
|
+
this.ctx.console.addListener('debug-get-raw-data' as any, async () => {
|
|
519
|
+
try {
|
|
520
|
+
const data = this.fileManager.readChatDataFromFile()
|
|
521
|
+
return {
|
|
522
|
+
success: true,
|
|
523
|
+
data: data
|
|
524
|
+
}
|
|
525
|
+
} catch (error: any) {
|
|
526
|
+
this.logger.error('获取原始数据失败:', error)
|
|
527
|
+
return { success: false, error: error?.message || String(error) }
|
|
528
|
+
}
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// 检查是否为文件 URL
|
|
533
|
+
private isFileUrl(url: string): boolean {
|
|
534
|
+
try {
|
|
535
|
+
const parsedUrl = new URL(url)
|
|
536
|
+
return parsedUrl.protocol === 'file:'
|
|
537
|
+
} catch {
|
|
538
|
+
return false
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 创建文件 URL
|
|
543
|
+
private createFileUrl(filePath: string): string {
|
|
544
|
+
try {
|
|
545
|
+
return pathToFileURL(filePath).href
|
|
546
|
+
} catch (error) {
|
|
547
|
+
this.logger.error('创建文件URL失败:', { filePath, error })
|
|
548
|
+
// 回退到简单的字符串拼接
|
|
549
|
+
return `file://${filePath}`
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// 处理本地文件请求
|
|
554
|
+
private async handleLocalFileRequest(fileUrl: string) {
|
|
555
|
+
try {
|
|
556
|
+
const fileresponse = await this.ctx.http.file(fileUrl)
|
|
557
|
+
const fileresponsebase64 = Buffer.from(fileresponse.data).toString('base64')
|
|
558
|
+
let contentType = fileresponse.type
|
|
559
|
+
|
|
560
|
+
this.logInfo('成功读取本地文件:', { fileUrl, contentType })
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
success: true,
|
|
564
|
+
base64: fileresponsebase64,
|
|
565
|
+
contentType: contentType,
|
|
566
|
+
dataUrl: `data:${contentType};base64,${fileresponsebase64}`
|
|
567
|
+
}
|
|
568
|
+
} catch (error: any) {
|
|
569
|
+
this.logger.error('读取本地文件失败:', { fileUrl, error: error.message })
|
|
570
|
+
return {
|
|
571
|
+
success: false,
|
|
572
|
+
error: `读取本地文件失败: ${error.message}`
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// 设置定时清理临时文件
|
|
578
|
+
private setupTempFileCleanup() {
|
|
579
|
+
// 每5分钟执行一次基于数量的清理
|
|
580
|
+
setInterval(() => {
|
|
581
|
+
this.cleanupTempImagesByCount()
|
|
582
|
+
}, 5 * 60 * 1000) // 5分钟
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// 基于数量清理临时图片(保留最新的N张)
|
|
586
|
+
private async cleanupTempImagesByCount() {
|
|
587
|
+
try {
|
|
588
|
+
const tempDir = this.ctx.baseDir + '/data/chat-patch/temp'
|
|
589
|
+
if (!require('fs').existsSync(tempDir)) {
|
|
590
|
+
return
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const now = Date.now()
|
|
594
|
+
const protectionTime = 30 * 1000 // 30秒保护期,刚上传的图片不会被删除
|
|
595
|
+
|
|
596
|
+
const files = require('fs').readdirSync(tempDir)
|
|
597
|
+
.filter((file: string) => file.startsWith('temp_'))
|
|
598
|
+
.map((file: string) => {
|
|
599
|
+
const filePath = `${tempDir}/${file}`
|
|
600
|
+
try {
|
|
601
|
+
const stats = require('fs').statSync(filePath)
|
|
602
|
+
const fileAge = now - stats.mtime.getTime()
|
|
603
|
+
return {
|
|
604
|
+
name: file,
|
|
605
|
+
path: filePath,
|
|
606
|
+
mtime: stats.mtime.getTime(),
|
|
607
|
+
age: fileAge,
|
|
608
|
+
protected: fileAge < protectionTime // 是否在保护期内
|
|
609
|
+
}
|
|
610
|
+
} catch (error) {
|
|
611
|
+
return null
|
|
612
|
+
}
|
|
613
|
+
})
|
|
614
|
+
.filter((file: any) => file !== null)
|
|
615
|
+
.sort((a: any, b: any) => b.mtime - a.mtime) // 按修改时间降序排列(最新的在前)
|
|
616
|
+
|
|
617
|
+
const keepCount = this.config.keepTempImages
|
|
618
|
+
let cleanedCount = 0
|
|
619
|
+
let protectedCount = 0
|
|
620
|
+
|
|
621
|
+
// 分离受保护的文件和可删除的文件
|
|
622
|
+
const protectedFiles = files.filter((file: any) => file.protected)
|
|
623
|
+
const deletableFiles = files.filter((file: any) => !file.protected)
|
|
624
|
+
|
|
625
|
+
protectedCount = protectedFiles.length
|
|
626
|
+
|
|
627
|
+
// 如果可删除的文件数量超过了保留数量,则删除多余的
|
|
628
|
+
if (deletableFiles.length > keepCount) {
|
|
629
|
+
const filesToDelete = deletableFiles.slice(keepCount) // 保留前N个,删除其余的
|
|
630
|
+
|
|
631
|
+
for (const file of filesToDelete) {
|
|
632
|
+
try {
|
|
633
|
+
if (require('fs').existsSync(file.path)) {
|
|
634
|
+
require('fs').unlinkSync(file.path)
|
|
635
|
+
cleanedCount++
|
|
636
|
+
this.logInfo('清理多余临时图片:', file.path)
|
|
637
|
+
}
|
|
638
|
+
} catch (fileError) {
|
|
639
|
+
this.logger.warn('删除临时文件失败:', { file: file.name, error: fileError })
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (cleanedCount > 0 || protectedCount > 0) {
|
|
645
|
+
this.logInfo(`基于数量清理完成,保留最新 ${keepCount} 张图片,清理了 ${cleanedCount} 张多余图片,保护了 ${protectedCount} 张新上传图片`)
|
|
646
|
+
}
|
|
647
|
+
} catch (error) {
|
|
648
|
+
this.logger.warn('基于数量清理临时文件失败:', error)
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private logInfo(...args: any[]) {
|
|
653
|
+
if (this.config.loggerinfo) {
|
|
654
|
+
(this.logger.info as (...args: any[]) => void)(...args)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|