koishi-plugin-chat-patch 2.2.0 → 2.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/lib/index.js CHANGED
@@ -54,7 +54,6 @@ var Utils = class {
54
54
  static {
55
55
  __name(this, "Utils");
56
56
  }
57
- // 检查平台是否被屏蔽
58
57
  isPlatformBlocked(platform) {
59
58
  if (!this.config.blockedPlatforms || this.config.blockedPlatforms.length === 0) {
60
59
  return false;
@@ -72,7 +71,6 @@ var Utils = class {
72
71
  }
73
72
  return false;
74
73
  }
75
- // 递归提取所有文本内容的函数
76
74
  extractTextContent(elements) {
77
75
  let text = "";
78
76
  for (const element of elements) {
@@ -88,7 +86,6 @@ var Utils = class {
88
86
  }
89
87
  return text;
90
88
  }
91
- // 检查字符串是否为base64格式
92
89
  isBase64(str) {
93
90
  if (!str || typeof str !== "string") return false;
94
91
  if (str.startsWith("data:")) {
@@ -100,7 +97,6 @@ var Utils = class {
100
97
  }
101
98
  return false;
102
99
  }
103
- // 持久化 base64 图片并返回本地文件 URL
104
100
  persistBase64Image(base64Data) {
105
101
  if (!this.ctx || !base64Data.startsWith("data:image/")) return base64Data;
106
102
  try {
@@ -127,8 +123,6 @@ var Utils = class {
127
123
  } catch (e) {
128
124
  }
129
125
  }
130
- // 清理对象中的base64内容,改为持久化存储
131
- // isBotMessage: 是否为机器人发送的消息
132
126
  cleanBase64Content(obj, isBotMessage = false) {
133
127
  if (obj === null || obj === void 0) {
134
128
  return obj;
@@ -184,95 +178,130 @@ var MessageHandler = class {
184
178
  }
185
179
  logger;
186
180
  utils;
187
- // 存储正确的 channelId 映射,key 是 selfId,value 是正确的 channelId
188
181
  correctChannelIds = /* @__PURE__ */ new Map();
189
- // 设置正确的 channelId
182
+ recordUserMessage(session, timestamp) {
183
+ setImmediate(() => {
184
+ this.processUserMessage(session, timestamp).catch((error) => {
185
+ this.logger.error("记录用户消息失败:", error);
186
+ });
187
+ });
188
+ }
189
+ recordBotMessage(session, timestamp) {
190
+ setImmediate(() => {
191
+ this.processBotMessage(session, timestamp).catch((error) => {
192
+ this.logger.error("记录机器人消息失败:", error);
193
+ });
194
+ });
195
+ }
190
196
  setCorrectChannelId(selfId, channelId) {
191
197
  this.correctChannelIds.set(selfId, channelId);
192
198
  this.logInfo("设置正确的 channelId:", { selfId, channelId });
193
199
  }
194
- // 获取正确的 channelId
195
200
  getCorrectChannelId(selfId) {
196
201
  return this.correctChannelIds.get(selfId);
197
202
  }
198
- // 更新机器人信息到JSON文件
199
- async updateBotInfoToFile(session) {
200
- const data = this.fileManager.readChatDataFromFile();
201
- const botInfo = {
202
- selfId: session.selfId,
203
- platform: session.platform || "unknown",
204
- username: session.bot.user?.name || `Bot-${session.selfId}`,
205
- avatar: session.bot.user?.avatar,
206
- status: "online"
207
- };
208
- data.bots[session.selfId] = botInfo;
209
- this.fileManager.writeChatDataToFile(data);
210
- this.logInfo("更新机器人信息到文件:", botInfo.username);
203
+ updateBotInfoToFile(session) {
204
+ setImmediate(() => {
205
+ try {
206
+ const data = this.fileManager.readChatDataFromFile();
207
+ const botInfo = {
208
+ selfId: session.selfId,
209
+ platform: session.platform || "unknown",
210
+ username: session.bot.user?.name || `Bot-${session.selfId}`,
211
+ avatar: session.bot.user?.avatar,
212
+ status: "online"
213
+ };
214
+ data.bots[session.selfId] = botInfo;
215
+ this.fileManager.writeChatDataToFile(data);
216
+ this.logInfo("更新机器人信息到文件:", botInfo.username);
217
+ } catch (error) {
218
+ this.logger.error("更新机器人信息失败:", error);
219
+ }
220
+ });
211
221
  }
212
- // 更新频道信息到JSON文件
213
- async updateChannelInfoToFile(session) {
222
+ updateChannelInfoToFile(session) {
214
223
  const isDirect = session.isDirect || session.channelId?.includes("private");
215
- let guildName = session.channelId;
216
224
  const directUserName = session.username || session.event?.user?.name || session.userId;
217
225
  const data = this.fileManager.readChatDataFromFile();
218
- if (!isDirect) {
219
- try {
220
- if (session.guildId && session.bot.getGuild && typeof session.bot.getGuild === "function") {
221
- const guild = await session.bot.getGuild(session.guildId);
222
- guildName = guild?.name || session.channelId;
223
- }
224
- if (session.guildId && !session.bot.getGuild && session.bot.getChannel && typeof session.bot.getChannel === "function") {
225
- try {
226
- const channel = await session.bot.getChannel(session.guildId);
227
- guildName = channel?.name || session.channelId;
228
- } catch (channelError) {
229
- this.logInfo("获取频道信息失败,使用频道ID作为备用:", channelError);
230
- }
231
- }
232
- } catch (error) {
233
- this.logInfo("获取频道信息失败,使用频道ID作为备用:", error);
234
- guildName = session.channelId;
235
- }
236
- }
237
- if (!data.channels[session.selfId]) {
238
- data.channels[session.selfId] = {};
239
- }
240
- const existingChannel = data.channels[session.selfId][session.channelId];
241
- let finalName;
226
+ const existingChannel = data.channels[session.selfId]?.[session.channelId];
227
+ let immediateName = session.channelId;
242
228
  if (isDirect) {
243
229
  if (directUserName && directUserName !== session.userId) {
244
- finalName = `私聊(${directUserName})`;
230
+ immediateName = `私聊(${directUserName})`;
245
231
  } else if (existingChannel?.name && !existingChannel.name.includes("未知")) {
246
- finalName = existingChannel.name;
232
+ immediateName = existingChannel.name;
247
233
  } else if (session.platform && session.platform.toLowerCase().includes("sandbox")) {
248
- finalName = `私聊(${session.userId})`;
234
+ immediateName = `私聊(${session.userId})`;
249
235
  } else {
250
- finalName = "私聊(未知用户)";
236
+ immediateName = "私聊(未知用户)";
251
237
  }
252
- } else {
253
- finalName = guildName || session.channelId;
254
- }
255
- const channelInfo = {
256
- id: session.channelId,
257
- name: finalName,
258
- type: session.type || 0,
259
- channelId: session.channelId,
260
- guildName,
261
- isDirect: !!isDirect
262
- };
263
- if (existingChannel && existingChannel.name !== finalName) {
264
- this.logInfo("更新频道名称:", {
265
- channelId: session.channelId,
266
- oldName: existingChannel.name,
267
- newName: finalName
268
- });
238
+ } else if (existingChannel?.guildName) {
239
+ immediateName = existingChannel.guildName;
269
240
  }
270
- data.channels[session.selfId][session.channelId] = channelInfo;
271
- this.fileManager.writeChatDataToFile(data);
272
- this.logInfo("更新频道信息到文件:", channelInfo.name);
273
- return guildName;
241
+ setImmediate(async () => {
242
+ try {
243
+ let guildName = session.channelId;
244
+ if (!isDirect) {
245
+ try {
246
+ if (session.guildId && session.bot.getGuild && typeof session.bot.getGuild === "function") {
247
+ const guild = await session.bot.getGuild(session.guildId);
248
+ guildName = guild?.name || session.channelId;
249
+ } else if (session.guildId && !session.bot.getGuild && session.bot.getChannel && typeof session.bot.getChannel === "function") {
250
+ try {
251
+ const channel = await session.bot.getChannel(session.guildId);
252
+ guildName = channel?.name || session.channelId;
253
+ } catch (channelError) {
254
+ this.logInfo("获取频道信息失败,使用频道ID作为备用:", channelError);
255
+ }
256
+ }
257
+ } catch (error) {
258
+ this.logInfo("获取频道信息失败,使用频道ID作为备用:", error);
259
+ guildName = session.channelId;
260
+ }
261
+ }
262
+ const freshData = this.fileManager.readChatDataFromFile();
263
+ if (!freshData.channels[session.selfId]) {
264
+ freshData.channels[session.selfId] = {};
265
+ }
266
+ const existingChannel2 = freshData.channels[session.selfId][session.channelId];
267
+ let finalName;
268
+ if (isDirect) {
269
+ if (directUserName && directUserName !== session.userId) {
270
+ finalName = `私聊(${directUserName})`;
271
+ } else if (existingChannel2?.name && !existingChannel2.name.includes("未知")) {
272
+ finalName = existingChannel2.name;
273
+ } else if (session.platform && session.platform.toLowerCase().includes("sandbox")) {
274
+ finalName = `私聊(${session.userId})`;
275
+ } else {
276
+ finalName = "私聊(未知用户)";
277
+ }
278
+ } else {
279
+ finalName = guildName || session.channelId;
280
+ }
281
+ const channelInfo = {
282
+ id: session.channelId,
283
+ name: finalName,
284
+ type: session.type || 0,
285
+ channelId: session.channelId,
286
+ guildName,
287
+ isDirect: !!isDirect
288
+ };
289
+ if (existingChannel2 && existingChannel2.name !== finalName) {
290
+ this.logInfo("更新频道名称:", {
291
+ channelId: session.channelId,
292
+ oldName: existingChannel2.name,
293
+ newName: finalName
294
+ });
295
+ }
296
+ freshData.channels[session.selfId][session.channelId] = channelInfo;
297
+ this.fileManager.writeChatDataToFile(freshData);
298
+ this.logInfo("更新频道信息到文件:", channelInfo.name);
299
+ } catch (error) {
300
+ this.logger.error("异步更新频道信息失败:", error);
301
+ }
302
+ });
303
+ return immediateName;
274
304
  }
275
- // 下载并缓存媒体文件
276
305
  async downloadAndCacheMedia(url, type) {
277
306
  try {
278
307
  if (!url || url.startsWith("data:") || url.startsWith("file:")) return url;
@@ -298,37 +327,44 @@ var MessageHandler = class {
298
327
  return url;
299
328
  }
300
329
  }
301
- // 处理消息中的媒体元素并缓存
302
- // 只缓存用户的图片和语音,不缓存视频和文件
303
- async processMediaElements(elements, isUserMessage = true) {
304
- if (!elements) return elements;
305
- for (const el of elements) {
306
- if (["image", "img", "mface"].includes(el.type)) {
307
- const src = el.attrs.src || el.attrs.url || el.attrs.file;
308
- if (src && isUserMessage) {
309
- el.attrs.src = await this.downloadAndCacheMedia(src, "image");
310
- }
311
- } else if (el.type === "audio") {
312
- const src = el.attrs.src || el.attrs.url || el.attrs.file;
313
- if (src && isUserMessage) {
314
- el.attrs.src = await this.downloadAndCacheMedia(src, "media");
330
+ processMediaElementsAsync(elements, isUserMessage = true) {
331
+ if (!elements) return;
332
+ setImmediate(async () => {
333
+ try {
334
+ for (const el of elements) {
335
+ if (["image", "img", "mface"].includes(el.type)) {
336
+ const src = el.attrs.src || el.attrs.url || el.attrs.file;
337
+ if (src && isUserMessage) {
338
+ this.downloadAndCacheMedia(src, "image").catch((e) => {
339
+ this.logger.warn("缓存图片失败:", e);
340
+ });
341
+ }
342
+ } else if (el.type === "audio") {
343
+ const src = el.attrs.src || el.attrs.url || el.attrs.file;
344
+ if (src && isUserMessage) {
345
+ this.downloadAndCacheMedia(src, "media").catch((e) => {
346
+ this.logger.warn("缓存语音失败:", e);
347
+ });
348
+ }
349
+ }
350
+ if (el.children) this.processMediaElementsAsync(el.children, isUserMessage);
315
351
  }
352
+ } catch (error) {
353
+ this.logger.error("处理媒体元素失败:", error);
316
354
  }
317
- if (el.children) await this.processMediaElements(el.children, isUserMessage);
318
- }
319
- return elements;
355
+ });
320
356
  }
321
- async broadcastMessageEvent(session) {
357
+ async processUserMessage(session, timestamp) {
322
358
  try {
323
- await this.updateBotInfoToFile(session);
324
- const guildName = await this.updateChannelInfoToFile(session);
359
+ if (!timestamp) timestamp = Date.now();
360
+ this.updateBotInfoToFile(session);
361
+ const guildName = this.updateChannelInfoToFile(session);
325
362
  const isDirect = session.isDirect || session.channelId?.includes("private");
326
- const timestamp = Date.now();
327
363
  if (session.elements) {
328
- await this.processMediaElements(session.elements, true);
364
+ this.processMediaElementsAsync(session.elements, true);
329
365
  }
330
366
  if (session.quote?.elements) {
331
- await this.processMediaElements(session.quote.elements, true);
367
+ this.processMediaElementsAsync(session.quote.elements, true);
332
368
  }
333
369
  let quoteInfo = void 0;
334
370
  if (session.quote) {
@@ -400,18 +436,17 @@ var MessageHandler = class {
400
436
  };
401
437
  this.ctx.console.broadcast("chat-message-event", messageEvent);
402
438
  } catch (error) {
403
- this.logger.error("广播消息事件失败:", error);
439
+ this.logger.error("处理用户消息失败:", error);
404
440
  }
405
441
  }
406
- // 处理机器人发送的消息
407
- async broadcastBotMessageEvent(session) {
442
+ async processBotMessage(session, timestamp) {
408
443
  try {
444
+ if (!timestamp) timestamp = Date.now();
409
445
  const correctChannelId = this.getCorrectChannelId(session.selfId);
410
446
  const finalChannelId = correctChannelId || session.channelId;
411
- await this.updateBotInfoToFile(session);
412
- const guildName = await this.updateChannelInfoToFile(session);
447
+ this.updateBotInfoToFile(session);
448
+ const guildName = this.updateChannelInfoToFile(session);
413
449
  const isDirect = session.isDirect || finalChannelId?.includes("private");
414
- const timestamp = Date.now();
415
450
  let content = session.content || "";
416
451
  if (!content && session.event?.message?.elements) {
417
452
  content = this.utils.extractTextContent(session.event.message.elements).trim();
@@ -488,7 +523,7 @@ var MessageHandler = class {
488
523
  };
489
524
  this.ctx.console.broadcast("chat-bot-message-event", messageEvent);
490
525
  } catch (error) {
491
- this.logger.error("广播机器人消息事件失败:", error);
526
+ this.logger.error("处理机器人消息失败:", error);
492
527
  }
493
528
  }
494
529
  logInfo(...args) {
@@ -508,6 +543,7 @@ var FileManager = class {
508
543
  this.dataFilePath = import_node_path2.default.resolve(ctx.baseDir, "data", "chat-patch", "chat-data.json");
509
544
  this.logger = ctx.logger("chat-patch");
510
545
  this.utils = new Utils(config);
546
+ this.memoryCache = this.readChatDataFromFile();
511
547
  }
512
548
  static {
513
549
  __name(this, "FileManager");
@@ -516,51 +552,99 @@ var FileManager = class {
516
552
  fileOperationLock = Promise.resolve();
517
553
  logger;
518
554
  utils;
519
- // 确保目录存在
555
+ memoryCache = null;
556
+ pendingMessages = [];
557
+ writeTimer = null;
558
+ WRITE_DEBOUNCE_MS = 1e3;
520
559
  ensureDataDir() {
521
560
  const dir = import_node_path2.default.dirname(this.dataFilePath);
522
561
  if (!import_node_fs2.default.existsSync(dir)) {
523
562
  import_node_fs2.default.mkdirSync(dir, { recursive: true });
524
563
  }
525
564
  }
526
- // 从JSON文件读取数据
527
565
  readChatDataFromFile() {
528
- try {
529
- if (import_node_fs2.default.existsSync(this.dataFilePath)) {
530
- const jsonData = import_node_fs2.default.readFileSync(this.dataFilePath, "utf8");
531
- const data = JSON.parse(jsonData);
532
- return {
533
- bots: data.bots || {},
534
- channels: data.channels || {},
535
- messages: data.messages || {},
536
- pinnedBots: data.pinnedBots || [],
537
- pinnedChannels: data.pinnedChannels || [],
538
- lastSaveTime: data.lastSaveTime
539
- };
540
- }
541
- } catch (error) {
542
- this.logger.error("读取聊天数据失败:", error);
566
+ if (this.memoryCache) {
567
+ return this.memoryCache;
543
568
  }
544
- return {
569
+ process.nextTick(() => {
570
+ try {
571
+ if (import_node_fs2.default.existsSync(this.dataFilePath)) {
572
+ const jsonData = import_node_fs2.default.readFileSync(this.dataFilePath, "utf8");
573
+ const data = JSON.parse(jsonData);
574
+ this.memoryCache = {
575
+ bots: data.bots || {},
576
+ channels: data.channels || {},
577
+ messages: data.messages || {},
578
+ pinnedBots: data.pinnedBots || [],
579
+ pinnedChannels: data.pinnedChannels || [],
580
+ lastSaveTime: data.lastSaveTime
581
+ };
582
+ }
583
+ } catch (error) {
584
+ this.logger.error("读取聊天数据失败:", error);
585
+ }
586
+ });
587
+ this.memoryCache = {
545
588
  bots: {},
546
589
  channels: {},
547
590
  messages: {},
548
591
  pinnedBots: [],
549
592
  pinnedChannels: []
550
593
  };
594
+ return this.memoryCache;
551
595
  }
552
- // 写入数据到JSON文件
553
596
  writeChatDataToFile(data) {
554
- try {
555
- this.ensureDataDir();
556
- data.lastSaveTime = Date.now();
557
- const jsonData = JSON.stringify(data, null, 2);
558
- import_node_fs2.default.writeFileSync(this.dataFilePath, jsonData, "utf8");
559
- } catch (error) {
560
- this.logger.error("写入聊天数据失败:", error);
597
+ data.lastSaveTime = Date.now();
598
+ this.memoryCache = data;
599
+ process.nextTick(() => {
600
+ try {
601
+ this.ensureDataDir();
602
+ const jsonData = JSON.stringify(data, null, 2);
603
+ import_node_fs2.default.writeFileSync(this.dataFilePath, jsonData, "utf8");
604
+ } catch (error) {
605
+ this.logger.error("写入聊天数据失败:", error);
606
+ }
607
+ });
608
+ }
609
+ scheduleWrite() {
610
+ if (this.writeTimer) {
611
+ this.writeTimer();
612
+ this.writeTimer = null;
613
+ }
614
+ this.writeTimer = this.ctx.setTimeout(() => {
615
+ process.nextTick(() => {
616
+ this.flushPendingMessages();
617
+ });
618
+ this.writeTimer = null;
619
+ }, this.WRITE_DEBOUNCE_MS);
620
+ }
621
+ flushPendingMessages() {
622
+ if (this.pendingMessages.length === 0) return;
623
+ const messagesToWrite = [...this.pendingMessages];
624
+ this.pendingMessages = [];
625
+ const data = this.memoryCache || this.readChatDataFromFile();
626
+ for (const messageInfo of messagesToWrite) {
627
+ const channelKey = `${messageInfo.selfId}:${messageInfo.channelId}`;
628
+ if (!data.messages[channelKey]) {
629
+ data.messages[channelKey] = [];
630
+ }
631
+ const existingMessage = data.messages[channelKey].find((m) => m.id === messageInfo.id);
632
+ if (existingMessage) {
633
+ continue;
634
+ }
635
+ if (!messageInfo.timestamp) {
636
+ messageInfo.timestamp = Date.now();
637
+ }
638
+ const cleanedMessageInfo = this.utils.cleanBase64Content(messageInfo);
639
+ data.messages[channelKey].push(cleanedMessageInfo);
640
+ if (data.messages[channelKey].length > this.config.maxMessagesPerChannel) {
641
+ data.messages[channelKey].sort((a, b) => a.timestamp - b.timestamp);
642
+ data.messages[channelKey] = data.messages[channelKey].slice(-this.config.maxMessagesPerChannel);
643
+ }
561
644
  }
645
+ this.writeChatDataToFile(data);
646
+ this.logInfo(`批量写入 ${messagesToWrite.length} 条消息`);
562
647
  }
563
- // 清理超量消息
564
648
  cleanExcessMessages(data) {
565
649
  let cleanedCount = 0;
566
650
  const cleanedMessages = {};
@@ -583,56 +667,34 @@ var FileManager = class {
583
667
  messages: cleanedMessages
584
668
  };
585
669
  }
586
- // 添加消息到JSON文件(使用锁机制防止并发冲突)
587
670
  async addMessageToFile(messageInfo) {
588
- this.fileOperationLock = this.fileOperationLock.then(async () => {
589
- const data = this.readChatDataFromFile();
590
- const channelKey = `${messageInfo.selfId}:${messageInfo.channelId}`;
591
- if (!data.messages[channelKey]) {
592
- data.messages[channelKey] = [];
593
- }
594
- const existingMessage = data.messages[channelKey].find((m) => m.id === messageInfo.id);
595
- if (existingMessage) {
596
- this.logInfo("消息已存在,跳过保存:", {
597
- channelKey,
598
- messageId: messageInfo.id,
599
- existingType: existingMessage.type,
600
- existingContent: existingMessage.content,
601
- newType: messageInfo.type,
602
- newContent: messageInfo.content
603
- });
604
- return;
605
- }
671
+ this.pendingMessages.push(messageInfo);
672
+ const data = this.memoryCache || this.readChatDataFromFile();
673
+ const channelKey = `${messageInfo.selfId}:${messageInfo.channelId}`;
674
+ if (!data.messages[channelKey]) {
675
+ data.messages[channelKey] = [];
676
+ }
677
+ const existingMessage = data.messages[channelKey].find((m) => m.id === messageInfo.id);
678
+ if (!existingMessage) {
606
679
  if (!messageInfo.timestamp) {
607
680
  messageInfo.timestamp = Date.now();
608
681
  }
609
682
  const cleanedMessageInfo = this.utils.cleanBase64Content(messageInfo);
610
- const beforeCount = data.messages[channelKey].length;
611
683
  data.messages[channelKey].push(cleanedMessageInfo);
612
- const afterCount = data.messages[channelKey].length;
613
684
  if (data.messages[channelKey].length > this.config.maxMessagesPerChannel) {
614
685
  data.messages[channelKey].sort((a, b) => a.timestamp - b.timestamp);
615
- const removedCount = data.messages[channelKey].length - this.config.maxMessagesPerChannel;
616
686
  data.messages[channelKey] = data.messages[channelKey].slice(-this.config.maxMessagesPerChannel);
617
- this.logInfo(`频道 ${channelKey} 达到消息上限,清理了 ${removedCount} 条旧消息`);
618
687
  }
619
- this.writeChatDataToFile(data);
620
- const isCommandMessage = messageInfo.content?.startsWith("++") || messageInfo.content?.startsWith(".");
621
- this.logInfo("添加消息到文件:", {
622
- channelKey,
623
- messageId: messageInfo.id,
624
- content: messageInfo.content,
625
- type: messageInfo.type,
626
- userId: messageInfo.userId,
627
- username: messageInfo.username,
628
- timestamp: messageInfo.timestamp,
629
- isCommandMessage,
630
- 消息数变化: `${beforeCount} -> ${afterCount} -> ${data.messages[channelKey].length}`
631
- });
632
- }).catch((error) => {
633
- this.logger.error("保存消息时发生错误:", error);
634
- });
635
- await this.fileOperationLock;
688
+ this.memoryCache = data;
689
+ }
690
+ this.scheduleWrite();
691
+ }
692
+ dispose() {
693
+ if (this.writeTimer) {
694
+ this.writeTimer();
695
+ this.writeTimer = null;
696
+ }
697
+ this.flushPendingMessages();
636
698
  }
637
699
  logInfo(...args) {
638
700
  if (this.config.loggerinfo) {
@@ -658,7 +720,6 @@ var ApiHandlers = class {
658
720
  __name(this, "ApiHandlers");
659
721
  }
660
722
  logger;
661
- // 当前临时缓存的视频文件路径
662
723
  currentTempVideo = null;
663
724
  registerApiHandlers() {
664
725
  this.ctx.console.addListener("clear-all-indexeddb-data", async () => {
@@ -681,7 +742,6 @@ var ApiHandlers = class {
681
742
  channels: data.channels || {},
682
743
  pinnedBots: data.pinnedBots || [],
683
744
  pinnedChannels: data.pinnedChannels || [],
684
- // 不返回 messages,由前端按需拉取
685
745
  messages: {}
686
746
  }
687
747
  };
@@ -1118,7 +1178,6 @@ var ApiHandlers = class {
1118
1178
  }
1119
1179
  });
1120
1180
  }
1121
- // 检查是否为文件 URL
1122
1181
  isFileUrl(url) {
1123
1182
  try {
1124
1183
  const parsedUrl = new import_node_url2.URL(url);
@@ -1127,7 +1186,6 @@ var ApiHandlers = class {
1127
1186
  return false;
1128
1187
  }
1129
1188
  }
1130
- // 创建文件 URL
1131
1189
  createFileUrl(filePath) {
1132
1190
  try {
1133
1191
  return (0, import_node_url2.pathToFileURL)(filePath).href;
@@ -1136,7 +1194,6 @@ var ApiHandlers = class {
1136
1194
  return `file://${filePath}`;
1137
1195
  }
1138
1196
  }
1139
- // 处理本地文件请求
1140
1197
  async handleLocalFileRequest(fileUrl) {
1141
1198
  try {
1142
1199
  const filePath = (0, import_node_url2.fileURLToPath)(fileUrl);
@@ -1158,13 +1215,11 @@ var ApiHandlers = class {
1158
1215
  };
1159
1216
  }
1160
1217
  }
1161
- // 设置定时清理临时文件
1162
1218
  setupTempFileCleanup() {
1163
- setInterval(() => {
1219
+ this.ctx.setInterval(() => {
1164
1220
  this.cleanupMediaCache();
1165
1221
  }, 5 * 60 * 1e3);
1166
1222
  }
1167
- // 统一清理媒体缓存
1168
1223
  async cleanupMediaCache() {
1169
1224
  const baseDir = this.ctx.baseDir + "/data/chat-patch/persist-media";
1170
1225
  if (!require("node:fs").existsSync(baseDir)) return;
@@ -1291,23 +1346,55 @@ async function apply(ctx, config) {
1291
1346
  消息频道数: Object.keys(cleanedData.messages).length,
1292
1347
  总消息数: Object.values(cleanedData.messages).reduce((total, msgs) => total + msgs.length, 0)
1293
1348
  });
1294
- ctx.on("message", async (session) => {
1295
- if (utils.isPlatformBlocked(session.platform || "unknown")) {
1296
- logInfo(`忽略来自被屏蔽平台的消息: ${session.platform}`);
1297
- return;
1298
- }
1299
- await messageHandler.broadcastMessageEvent(session);
1349
+ ctx.on("message", (session) => {
1350
+ if (utils.isPlatformBlocked(session.platform || "unknown")) return;
1351
+ const timestamp = Date.now();
1352
+ ctx.console.broadcast("chat-message-event", {
1353
+ type: "message",
1354
+ selfId: session.selfId,
1355
+ platform: session.platform || "unknown",
1356
+ channelId: session.channelId,
1357
+ messageId: session.event?.message?.id || `msg-${timestamp}`,
1358
+ content: session.content || "",
1359
+ userId: session.userId || "unknown",
1360
+ username: session.username || session.userId || "unknown",
1361
+ avatar: session.event?.user?.avatar,
1362
+ timestamp,
1363
+ isDirect: session.isDirect,
1364
+ elements: utils.cleanBase64Content(session.elements, false),
1365
+ bot: {
1366
+ avatar: session.bot.user?.avatar,
1367
+ name: session.bot.user?.name
1368
+ }
1369
+ });
1370
+ messageHandler.recordUserMessage(session, timestamp);
1300
1371
  });
1301
- ctx.on("before-send", async (session) => {
1302
- if (utils.isPlatformBlocked(session.platform || "unknown")) {
1303
- logInfo(`忽略来自被屏蔽平台的机器人消息: ${session.platform}`);
1304
- return;
1305
- }
1306
- await messageHandler.broadcastBotMessageEvent(session);
1372
+ ctx.on("before-send", (session) => {
1373
+ if (utils.isPlatformBlocked(session.platform || "unknown")) return;
1374
+ const timestamp = Date.now();
1375
+ ctx.console.broadcast("chat-bot-message-event", {
1376
+ type: "bot-message",
1377
+ selfId: session.selfId,
1378
+ platform: session.platform || "unknown",
1379
+ channelId: session.channelId,
1380
+ messageId: `bot-msg-${timestamp}`,
1381
+ content: session.content || "",
1382
+ userId: session.selfId,
1383
+ username: session.bot.user?.name || `Bot-${session.selfId}`,
1384
+ avatar: session.bot.user?.avatar,
1385
+ timestamp,
1386
+ sending: true,
1387
+ elements: utils.cleanBase64Content(session.event?.message?.elements, true),
1388
+ bot: {
1389
+ avatar: session.bot.user?.avatar,
1390
+ name: session.bot.user?.name
1391
+ }
1392
+ });
1393
+ messageHandler.recordBotMessage(session, timestamp);
1307
1394
  });
1308
1395
  ctx.on("ready", async () => {
1309
1396
  logInfo("插件启动完成,开始监听消息");
1310
- setInterval(() => {
1397
+ ctx.setInterval(() => {
1311
1398
  const data = fileManager.readChatDataFromFile();
1312
1399
  const cleanedData2 = fileManager.cleanExcessMessages(data);
1313
1400
  const originalCount2 = Object.values(data.messages).reduce((total, msgs) => total + msgs.length, 0);
@@ -1323,6 +1410,10 @@ async function apply(ctx, config) {
1323
1410
  dev: import_node_path3.default.resolve(__dirname, "../client/index.ts"),
1324
1411
  prod: import_node_path3.default.resolve(__dirname, "../dist")
1325
1412
  });
1413
+ ctx.on("dispose", () => {
1414
+ fileManager.dispose();
1415
+ logInfo("插件已卸载,所有待处理的消息已写入");
1416
+ });
1326
1417
  }
1327
1418
  __name(apply, "apply");
1328
1419
  // Annotate the CommonJS export names for ESM import in node: