koishi-plugin-chat-patch 2.3.0 → 2.4.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.
@@ -4,16 +4,23 @@ import { Config } from './config';
4
4
  export declare class FileManager {
5
5
  private ctx;
6
6
  private config;
7
- private dataFilePath;
8
- private fileOperationLock;
7
+ private chatHistoryDir;
8
+ private metadataFilePath;
9
9
  private logger;
10
10
  private utils;
11
11
  private memoryCache;
12
12
  private pendingMessages;
13
- private writeTimer;
13
+ private writeTimers;
14
14
  private readonly WRITE_DEBOUNCE_MS;
15
15
  constructor(ctx: Context, config: Config);
16
- private ensureDataDir;
16
+ private cleanupOldDataFiles;
17
+ private ensureDir;
18
+ private getChannelFilePath;
19
+ private readChannelMessages;
20
+ private writeChannelMessages;
21
+ private readMetadata;
22
+ private writeMetadata;
23
+ private loadAllChannelMessages;
17
24
  readChatDataFromFile(): ChatData;
18
25
  writeChatDataToFile(data: ChatData): void;
19
26
  private scheduleWrite;
package/lib/index.js CHANGED
@@ -540,27 +540,149 @@ var FileManager = class {
540
540
  constructor(ctx, config) {
541
541
  this.ctx = ctx;
542
542
  this.config = config;
543
- this.dataFilePath = import_node_path2.default.resolve(ctx.baseDir, "data", "chat-patch", "chat-data.json");
543
+ const baseDir = import_node_path2.default.resolve(ctx.baseDir, "data", "chat-patch");
544
+ this.chatHistoryDir = import_node_path2.default.join(baseDir, "chat-history");
545
+ this.metadataFilePath = import_node_path2.default.join(baseDir, "metadata.json");
544
546
  this.logger = ctx.logger("chat-patch");
545
547
  this.utils = new Utils(config);
548
+ this.cleanupOldDataFiles(baseDir);
546
549
  this.memoryCache = this.readChatDataFromFile();
547
550
  }
548
551
  static {
549
552
  __name(this, "FileManager");
550
553
  }
551
- dataFilePath;
552
- fileOperationLock = Promise.resolve();
554
+ chatHistoryDir;
555
+ // 聊天记录根目录
556
+ metadataFilePath;
557
+ // 元数据文件路径(存储bots、channels、pinned等信息)
553
558
  logger;
554
559
  utils;
555
560
  memoryCache = null;
556
- pendingMessages = [];
557
- writeTimer = null;
561
+ pendingMessages = /* @__PURE__ */ new Map();
562
+ // 按channelKey分组的待写入消息
563
+ writeTimers = /* @__PURE__ */ new Map();
564
+ // 每个频道独立的写入定时器
558
565
  WRITE_DEBOUNCE_MS = 1e3;
559
- ensureDataDir() {
560
- const dir = import_node_path2.default.dirname(this.dataFilePath);
561
- if (!import_node_fs2.default.existsSync(dir)) {
562
- import_node_fs2.default.mkdirSync(dir, { recursive: true });
566
+ // 清理旧版本的JSON文件
567
+ cleanupOldDataFiles(baseDir) {
568
+ const oldFiles = ["chat-data.json", "data.json", "messages.json"];
569
+ for (const fileName of oldFiles) {
570
+ const filePath = import_node_path2.default.join(baseDir, fileName);
571
+ if (import_node_fs2.default.existsSync(filePath)) {
572
+ try {
573
+ import_node_fs2.default.unlinkSync(filePath);
574
+ this.logger.info(`已删除旧版本数据文件: ${fileName}`);
575
+ } catch (error) {
576
+ this.logger.warn(`删除旧版本数据文件失败: ${fileName}`, error);
577
+ }
578
+ }
579
+ }
580
+ }
581
+ // 确保目录存在
582
+ ensureDir(dirPath) {
583
+ if (!import_node_fs2.default.existsSync(dirPath)) {
584
+ import_node_fs2.default.mkdirSync(dirPath, { recursive: true });
585
+ }
586
+ }
587
+ // 获取频道消息文件路径
588
+ getChannelFilePath(selfId, channelId) {
589
+ const botDir = import_node_path2.default.join(this.chatHistoryDir, selfId);
590
+ this.ensureDir(botDir);
591
+ return import_node_path2.default.join(botDir, `${channelId}.json`);
592
+ }
593
+ // 读取单个频道的消息
594
+ readChannelMessages(selfId, channelId) {
595
+ const filePath = this.getChannelFilePath(selfId, channelId);
596
+ if (!import_node_fs2.default.existsSync(filePath)) {
597
+ return [];
598
+ }
599
+ try {
600
+ const jsonData = import_node_fs2.default.readFileSync(filePath, "utf8");
601
+ const messages = JSON.parse(jsonData);
602
+ return Array.isArray(messages) ? messages : [];
603
+ } catch (error) {
604
+ this.logger.error(`读取频道消息失败 [${selfId}:${channelId}]:`, error);
605
+ return [];
606
+ }
607
+ }
608
+ // 写入单个频道的消息
609
+ writeChannelMessages(selfId, channelId, messages) {
610
+ const filePath = this.getChannelFilePath(selfId, channelId);
611
+ try {
612
+ const jsonData = JSON.stringify(messages, null, 2);
613
+ import_node_fs2.default.writeFileSync(filePath, jsonData, "utf8");
614
+ } catch (error) {
615
+ this.logger.error(`写入频道消息失败 [${selfId}:${channelId}]:`, error);
616
+ }
617
+ }
618
+ // 读取元数据(bots、channels、pinned等)
619
+ readMetadata() {
620
+ if (!import_node_fs2.default.existsSync(this.metadataFilePath)) {
621
+ return {
622
+ bots: {},
623
+ channels: {},
624
+ pinnedBots: [],
625
+ pinnedChannels: []
626
+ };
627
+ }
628
+ try {
629
+ const jsonData = import_node_fs2.default.readFileSync(this.metadataFilePath, "utf8");
630
+ const data = JSON.parse(jsonData);
631
+ return {
632
+ bots: data.bots || {},
633
+ channels: data.channels || {},
634
+ pinnedBots: data.pinnedBots || [],
635
+ pinnedChannels: data.pinnedChannels || [],
636
+ lastSaveTime: data.lastSaveTime
637
+ };
638
+ } catch (error) {
639
+ this.logger.error("读取元数据失败:", error);
640
+ return {
641
+ bots: {},
642
+ channels: {},
643
+ pinnedBots: [],
644
+ pinnedChannels: []
645
+ };
646
+ }
647
+ }
648
+ // 写入元数据
649
+ writeMetadata(metadata) {
650
+ try {
651
+ this.ensureDir(import_node_path2.default.dirname(this.metadataFilePath));
652
+ const dataToWrite = {
653
+ ...metadata,
654
+ lastSaveTime: Date.now()
655
+ };
656
+ const jsonData = JSON.stringify(dataToWrite, null, 2);
657
+ import_node_fs2.default.writeFileSync(this.metadataFilePath, jsonData, "utf8");
658
+ } catch (error) {
659
+ this.logger.error("写入元数据失败:", error);
660
+ }
661
+ }
662
+ // 扫描所有频道消息文件并加载到内存
663
+ loadAllChannelMessages() {
664
+ const messages = {};
665
+ if (!import_node_fs2.default.existsSync(this.chatHistoryDir)) {
666
+ return messages;
667
+ }
668
+ try {
669
+ const botDirs = import_node_fs2.default.readdirSync(this.chatHistoryDir);
670
+ for (const botId of botDirs) {
671
+ const botDir = import_node_path2.default.join(this.chatHistoryDir, botId);
672
+ const stat = import_node_fs2.default.statSync(botDir);
673
+ if (!stat.isDirectory()) continue;
674
+ const channelFiles = import_node_fs2.default.readdirSync(botDir);
675
+ for (const fileName of channelFiles) {
676
+ if (!fileName.endsWith(".json")) continue;
677
+ const channelId = fileName.replace(".json", "");
678
+ const channelKey = `${botId}:${channelId}`;
679
+ messages[channelKey] = this.readChannelMessages(botId, channelId);
680
+ }
681
+ }
682
+ } catch (error) {
683
+ this.logger.error("加载频道消息失败:", error);
563
684
  }
685
+ return messages;
564
686
  }
565
687
  readChatDataFromFile() {
566
688
  if (this.memoryCache) {
@@ -568,18 +690,12 @@ var FileManager = class {
568
690
  }
569
691
  process.nextTick(() => {
570
692
  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
- }
693
+ const metadata = this.readMetadata();
694
+ const messages = this.loadAllChannelMessages();
695
+ this.memoryCache = {
696
+ ...metadata,
697
+ messages
698
+ };
583
699
  } catch (error) {
584
700
  this.logger.error("读取聊天数据失败:", error);
585
701
  }
@@ -598,36 +714,45 @@ var FileManager = class {
598
714
  this.memoryCache = data;
599
715
  process.nextTick(() => {
600
716
  try {
601
- this.ensureDataDir();
602
- const jsonData = JSON.stringify(data, null, 2);
603
- import_node_fs2.default.writeFileSync(this.dataFilePath, jsonData, "utf8");
717
+ const { messages, ...metadata } = data;
718
+ this.writeMetadata(metadata);
719
+ for (const [channelKey, channelMessages] of Object.entries(messages)) {
720
+ const [selfId, channelId] = channelKey.split(":");
721
+ if (selfId && channelId) {
722
+ this.writeChannelMessages(selfId, channelId, channelMessages);
723
+ }
724
+ }
604
725
  } catch (error) {
605
726
  this.logger.error("写入聊天数据失败:", error);
606
727
  }
607
728
  });
608
729
  }
609
- scheduleWrite() {
610
- if (this.writeTimer) {
611
- this.writeTimer();
612
- this.writeTimer = null;
730
+ // 为特定频道安排写入
731
+ scheduleWrite(channelKey) {
732
+ const existingTimer = this.writeTimers.get(channelKey);
733
+ if (existingTimer) {
734
+ existingTimer();
613
735
  }
614
- this.writeTimer = this.ctx.setTimeout(() => {
736
+ const timer = this.ctx.setTimeout(() => {
615
737
  process.nextTick(() => {
616
- this.flushPendingMessages();
738
+ this.flushPendingMessages(channelKey);
617
739
  });
618
- this.writeTimer = null;
740
+ this.writeTimers.delete(channelKey);
619
741
  }, this.WRITE_DEBOUNCE_MS);
742
+ this.writeTimers.set(channelKey, timer);
620
743
  }
621
- flushPendingMessages() {
622
- if (this.pendingMessages.length === 0) return;
623
- const messagesToWrite = [...this.pendingMessages];
624
- this.pendingMessages = [];
744
+ // 刷新特定频道的待写入消息
745
+ flushPendingMessages(channelKey) {
746
+ const messagesToWrite = this.pendingMessages.get(channelKey);
747
+ if (!messagesToWrite || messagesToWrite.length === 0) return;
748
+ this.pendingMessages.delete(channelKey);
625
749
  const data = this.memoryCache || this.readChatDataFromFile();
750
+ const [selfId, channelId] = channelKey.split(":");
751
+ if (!selfId || !channelId) return;
752
+ if (!data.messages[channelKey]) {
753
+ data.messages[channelKey] = [];
754
+ }
626
755
  for (const messageInfo of messagesToWrite) {
627
- const channelKey = `${messageInfo.selfId}:${messageInfo.channelId}`;
628
- if (!data.messages[channelKey]) {
629
- data.messages[channelKey] = [];
630
- }
631
756
  const existingMessage = data.messages[channelKey].find((m) => m.id === messageInfo.id);
632
757
  if (existingMessage) {
633
758
  continue;
@@ -637,13 +762,14 @@ var FileManager = class {
637
762
  }
638
763
  const cleanedMessageInfo = this.utils.cleanBase64Content(messageInfo);
639
764
  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
- }
644
765
  }
645
- this.writeChatDataToFile(data);
646
- this.logInfo(`批量写入 ${messagesToWrite.length} 条消息`);
766
+ if (data.messages[channelKey].length > this.config.maxMessagesPerChannel) {
767
+ data.messages[channelKey].sort((a, b) => a.timestamp - b.timestamp);
768
+ data.messages[channelKey] = data.messages[channelKey].slice(-this.config.maxMessagesPerChannel);
769
+ }
770
+ this.writeChannelMessages(selfId, channelId, data.messages[channelKey]);
771
+ this.memoryCache = data;
772
+ this.logInfo(`批量写入 ${messagesToWrite.length} 条消息到频道 ${channelKey}`);
647
773
  }
648
774
  cleanExcessMessages(data) {
649
775
  let cleanedCount = 0;
@@ -668,9 +794,12 @@ var FileManager = class {
668
794
  };
669
795
  }
670
796
  async addMessageToFile(messageInfo) {
671
- this.pendingMessages.push(messageInfo);
672
- const data = this.memoryCache || this.readChatDataFromFile();
673
797
  const channelKey = `${messageInfo.selfId}:${messageInfo.channelId}`;
798
+ if (!this.pendingMessages.has(channelKey)) {
799
+ this.pendingMessages.set(channelKey, []);
800
+ }
801
+ this.pendingMessages.get(channelKey).push(messageInfo);
802
+ const data = this.memoryCache || this.readChatDataFromFile();
674
803
  if (!data.messages[channelKey]) {
675
804
  data.messages[channelKey] = [];
676
805
  }
@@ -687,14 +816,14 @@ var FileManager = class {
687
816
  }
688
817
  this.memoryCache = data;
689
818
  }
690
- this.scheduleWrite();
819
+ this.scheduleWrite(channelKey);
691
820
  }
692
821
  dispose() {
693
- if (this.writeTimer) {
694
- this.writeTimer();
695
- this.writeTimer = null;
822
+ for (const [channelKey, timer] of this.writeTimers.entries()) {
823
+ timer();
824
+ this.flushPendingMessages(channelKey);
696
825
  }
697
- this.flushPendingMessages();
826
+ this.writeTimers.clear();
698
827
  }
699
828
  logInfo(...args) {
700
829
  if (this.config.loggerinfo) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chat-patch",
3
3
  "description": "[<ruby>chat-patch<rp>(</rp><rt>点我预览效果</rt><rp>)</rp></ruby>](https://i0.hdslb.com/bfs/openplatform/71074dfc9e5256fc3333d8bd8478bec1af874046.png) 视奸小插件((bushi( (低性能警告)。手机端适配。灵感来自 chat 插件。",
4
- "version": "2.3.0",
4
+ "version": "2.4.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [