koishi-plugin-chat-patch 2.3.0 → 2.4.4

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/config.d.ts CHANGED
@@ -3,7 +3,6 @@ export interface Config {
3
3
  loggerinfo: boolean;
4
4
  clearIndexedDBOnStart: boolean;
5
5
  maxMessagesPerChannel: number;
6
- keepMessagesOnClear: number;
7
6
  maxPersistImages: number;
8
7
  blockedPlatforms: Array<{
9
8
  platformName: string;
@@ -4,16 +4,24 @@ 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
+ readChannelMessages(selfId: string, channelId: string): MessageInfo[];
20
+ private writeChannelMessages;
21
+ private readMetadata;
22
+ readMetadataOnly(): Omit<ChatData, 'messages'>;
23
+ private writeMetadata;
24
+ private loadAllChannelMessages;
17
25
  readChatDataFromFile(): ChatData;
18
26
  writeChatDataToFile(data: ChatData): void;
19
27
  private scheduleWrite;
package/lib/index.js CHANGED
@@ -304,7 +304,8 @@ var MessageHandler = class {
304
304
  }
305
305
  async downloadAndCacheMedia(url, type) {
306
306
  try {
307
- if (!url || url.startsWith("data:") || url.startsWith("file:")) return url;
307
+ if (!url || url.startsWith("data:")) return url;
308
+ if (url.includes("/vite/@fs/")) return url;
308
309
  let folder = "media";
309
310
  if (type === "image") folder = "images";
310
311
  else if (type === "avatar") folder = "avatars";
@@ -317,13 +318,14 @@ var MessageHandler = class {
317
318
  const ext = require("node:path").extname(new URL(url).pathname) || (type === "image" ? ".jpg" : ".mp4");
318
319
  const filename = `${hash}${ext}`;
319
320
  const filePath = require("node:path").join(dir, filename);
320
- if (require("node:fs").existsSync(filePath)) {
321
- return require("node:url").pathToFileURL(filePath).href;
321
+ if (!require("node:fs").existsSync(filePath)) {
322
+ const buffer = await this.ctx.http.get(url, { responseType: "arraybuffer" });
323
+ require("node:fs").writeFileSync(filePath, Buffer.from(buffer));
322
324
  }
323
- const buffer = await this.ctx.http.get(url, { responseType: "arraybuffer" });
324
- require("node:fs").writeFileSync(filePath, Buffer.from(buffer));
325
- return require("node:url").pathToFileURL(filePath).href;
325
+ const normalizedPath = filePath.replace(/\\/g, "/");
326
+ return `/vite/@fs/${normalizedPath}`;
326
327
  } catch (e) {
328
+ this.logger.warn("下载并缓存媒体失败:", e);
327
329
  return url;
328
330
  }
329
331
  }
@@ -540,27 +542,153 @@ var FileManager = class {
540
542
  constructor(ctx, config) {
541
543
  this.ctx = ctx;
542
544
  this.config = config;
543
- this.dataFilePath = import_node_path2.default.resolve(ctx.baseDir, "data", "chat-patch", "chat-data.json");
545
+ const baseDir = import_node_path2.default.resolve(ctx.baseDir, "data", "chat-patch");
546
+ this.chatHistoryDir = import_node_path2.default.join(baseDir, "chat-history");
547
+ this.metadataFilePath = import_node_path2.default.join(baseDir, "metadata.json");
544
548
  this.logger = ctx.logger("chat-patch");
545
549
  this.utils = new Utils(config);
550
+ this.cleanupOldDataFiles(baseDir);
546
551
  this.memoryCache = this.readChatDataFromFile();
547
552
  }
548
553
  static {
549
554
  __name(this, "FileManager");
550
555
  }
551
- dataFilePath;
552
- fileOperationLock = Promise.resolve();
556
+ chatHistoryDir;
557
+ // 聊天记录根目录
558
+ metadataFilePath;
559
+ // 元数据文件路径(存储bots、channels、pinned等信息)
553
560
  logger;
554
561
  utils;
555
562
  memoryCache = null;
556
- pendingMessages = [];
557
- writeTimer = null;
563
+ pendingMessages = /* @__PURE__ */ new Map();
564
+ // 按channelKey分组的待写入消息
565
+ writeTimers = /* @__PURE__ */ new Map();
566
+ // 每个频道独立的写入定时器
558
567
  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 });
568
+ // 清理旧版本的JSON文件
569
+ cleanupOldDataFiles(baseDir) {
570
+ const oldFiles = ["chat-data.json", "data.json", "messages.json"];
571
+ for (const fileName of oldFiles) {
572
+ const filePath = import_node_path2.default.join(baseDir, fileName);
573
+ if (import_node_fs2.default.existsSync(filePath)) {
574
+ try {
575
+ import_node_fs2.default.unlinkSync(filePath);
576
+ this.logger.info(`已删除旧版本数据文件: ${fileName}`);
577
+ } catch (error) {
578
+ this.logger.warn(`删除旧版本数据文件失败: ${fileName}`, error);
579
+ }
580
+ }
581
+ }
582
+ }
583
+ // 确保目录存在
584
+ ensureDir(dirPath) {
585
+ if (!import_node_fs2.default.existsSync(dirPath)) {
586
+ import_node_fs2.default.mkdirSync(dirPath, { recursive: true });
587
+ }
588
+ }
589
+ // 获取频道消息文件路径
590
+ getChannelFilePath(selfId, channelId) {
591
+ const botDir = import_node_path2.default.join(this.chatHistoryDir, selfId);
592
+ this.ensureDir(botDir);
593
+ return import_node_path2.default.join(botDir, `${channelId}.json`);
594
+ }
595
+ // 读取单个频道的消息(公共方法)
596
+ readChannelMessages(selfId, channelId) {
597
+ const filePath = this.getChannelFilePath(selfId, channelId);
598
+ if (!import_node_fs2.default.existsSync(filePath)) {
599
+ return [];
600
+ }
601
+ try {
602
+ const jsonData = import_node_fs2.default.readFileSync(filePath, "utf8");
603
+ const messages = JSON.parse(jsonData);
604
+ return Array.isArray(messages) ? messages : [];
605
+ } catch (error) {
606
+ this.logger.error(`读取频道消息失败 [${selfId}:${channelId}]:`, error);
607
+ return [];
608
+ }
609
+ }
610
+ // 写入单个频道的消息
611
+ writeChannelMessages(selfId, channelId, messages) {
612
+ const filePath = this.getChannelFilePath(selfId, channelId);
613
+ try {
614
+ const jsonData = JSON.stringify(messages, null, 2);
615
+ import_node_fs2.default.writeFileSync(filePath, jsonData, "utf8");
616
+ } catch (error) {
617
+ this.logger.error(`写入频道消息失败 [${selfId}:${channelId}]:`, error);
618
+ }
619
+ }
620
+ // 读取元数据(bots、channels、pinned等)
621
+ readMetadata() {
622
+ if (!import_node_fs2.default.existsSync(this.metadataFilePath)) {
623
+ return {
624
+ bots: {},
625
+ channels: {},
626
+ pinnedBots: [],
627
+ pinnedChannels: []
628
+ };
629
+ }
630
+ try {
631
+ const jsonData = import_node_fs2.default.readFileSync(this.metadataFilePath, "utf8");
632
+ const data = JSON.parse(jsonData);
633
+ return {
634
+ bots: data.bots || {},
635
+ channels: data.channels || {},
636
+ pinnedBots: data.pinnedBots || [],
637
+ pinnedChannels: data.pinnedChannels || [],
638
+ lastSaveTime: data.lastSaveTime
639
+ };
640
+ } catch (error) {
641
+ this.logger.error("读取元数据失败:", error);
642
+ return {
643
+ bots: {},
644
+ channels: {},
645
+ pinnedBots: [],
646
+ pinnedChannels: []
647
+ };
648
+ }
649
+ }
650
+ // 只读取元数据,不加载消息(公共方法)
651
+ readMetadataOnly() {
652
+ return this.readMetadata();
653
+ }
654
+ // 写入元数据
655
+ writeMetadata(metadata) {
656
+ try {
657
+ this.ensureDir(import_node_path2.default.dirname(this.metadataFilePath));
658
+ const dataToWrite = {
659
+ ...metadata,
660
+ lastSaveTime: Date.now()
661
+ };
662
+ const jsonData = JSON.stringify(dataToWrite, null, 2);
663
+ import_node_fs2.default.writeFileSync(this.metadataFilePath, jsonData, "utf8");
664
+ } catch (error) {
665
+ this.logger.error("写入元数据失败:", error);
666
+ }
667
+ }
668
+ // 扫描所有频道消息文件并加载到内存
669
+ loadAllChannelMessages() {
670
+ const messages = {};
671
+ if (!import_node_fs2.default.existsSync(this.chatHistoryDir)) {
672
+ return messages;
673
+ }
674
+ try {
675
+ const botDirs = import_node_fs2.default.readdirSync(this.chatHistoryDir);
676
+ for (const botId of botDirs) {
677
+ const botDir = import_node_path2.default.join(this.chatHistoryDir, botId);
678
+ const stat = import_node_fs2.default.statSync(botDir);
679
+ if (!stat.isDirectory()) continue;
680
+ const channelFiles = import_node_fs2.default.readdirSync(botDir);
681
+ for (const fileName of channelFiles) {
682
+ if (!fileName.endsWith(".json")) continue;
683
+ const channelId = fileName.replace(".json", "");
684
+ const channelKey = `${botId}:${channelId}`;
685
+ messages[channelKey] = this.readChannelMessages(botId, channelId);
686
+ }
687
+ }
688
+ } catch (error) {
689
+ this.logger.error("加载频道消息失败:", error);
563
690
  }
691
+ return messages;
564
692
  }
565
693
  readChatDataFromFile() {
566
694
  if (this.memoryCache) {
@@ -568,18 +696,12 @@ var FileManager = class {
568
696
  }
569
697
  process.nextTick(() => {
570
698
  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
- }
699
+ const metadata = this.readMetadata();
700
+ const messages = this.loadAllChannelMessages();
701
+ this.memoryCache = {
702
+ ...metadata,
703
+ messages
704
+ };
583
705
  } catch (error) {
584
706
  this.logger.error("读取聊天数据失败:", error);
585
707
  }
@@ -598,36 +720,45 @@ var FileManager = class {
598
720
  this.memoryCache = data;
599
721
  process.nextTick(() => {
600
722
  try {
601
- this.ensureDataDir();
602
- const jsonData = JSON.stringify(data, null, 2);
603
- import_node_fs2.default.writeFileSync(this.dataFilePath, jsonData, "utf8");
723
+ const { messages, ...metadata } = data;
724
+ this.writeMetadata(metadata);
725
+ for (const [channelKey, channelMessages] of Object.entries(messages)) {
726
+ const [selfId, channelId] = channelKey.split(":");
727
+ if (selfId && channelId) {
728
+ this.writeChannelMessages(selfId, channelId, channelMessages);
729
+ }
730
+ }
604
731
  } catch (error) {
605
732
  this.logger.error("写入聊天数据失败:", error);
606
733
  }
607
734
  });
608
735
  }
609
- scheduleWrite() {
610
- if (this.writeTimer) {
611
- this.writeTimer();
612
- this.writeTimer = null;
736
+ // 为特定频道安排写入
737
+ scheduleWrite(channelKey) {
738
+ const existingTimer = this.writeTimers.get(channelKey);
739
+ if (existingTimer) {
740
+ existingTimer();
613
741
  }
614
- this.writeTimer = this.ctx.setTimeout(() => {
742
+ const timer = this.ctx.setTimeout(() => {
615
743
  process.nextTick(() => {
616
- this.flushPendingMessages();
744
+ this.flushPendingMessages(channelKey);
617
745
  });
618
- this.writeTimer = null;
746
+ this.writeTimers.delete(channelKey);
619
747
  }, this.WRITE_DEBOUNCE_MS);
748
+ this.writeTimers.set(channelKey, timer);
620
749
  }
621
- flushPendingMessages() {
622
- if (this.pendingMessages.length === 0) return;
623
- const messagesToWrite = [...this.pendingMessages];
624
- this.pendingMessages = [];
750
+ // 刷新特定频道的待写入消息
751
+ flushPendingMessages(channelKey) {
752
+ const messagesToWrite = this.pendingMessages.get(channelKey);
753
+ if (!messagesToWrite || messagesToWrite.length === 0) return;
754
+ this.pendingMessages.delete(channelKey);
625
755
  const data = this.memoryCache || this.readChatDataFromFile();
756
+ const [selfId, channelId] = channelKey.split(":");
757
+ if (!selfId || !channelId) return;
758
+ if (!data.messages[channelKey]) {
759
+ data.messages[channelKey] = [];
760
+ }
626
761
  for (const messageInfo of messagesToWrite) {
627
- const channelKey = `${messageInfo.selfId}:${messageInfo.channelId}`;
628
- if (!data.messages[channelKey]) {
629
- data.messages[channelKey] = [];
630
- }
631
762
  const existingMessage = data.messages[channelKey].find((m) => m.id === messageInfo.id);
632
763
  if (existingMessage) {
633
764
  continue;
@@ -637,13 +768,14 @@ var FileManager = class {
637
768
  }
638
769
  const cleanedMessageInfo = this.utils.cleanBase64Content(messageInfo);
639
770
  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
771
  }
645
- this.writeChatDataToFile(data);
646
- this.logInfo(`批量写入 ${messagesToWrite.length} 条消息`);
772
+ if (data.messages[channelKey].length > this.config.maxMessagesPerChannel) {
773
+ data.messages[channelKey].sort((a, b) => a.timestamp - b.timestamp);
774
+ data.messages[channelKey] = data.messages[channelKey].slice(-this.config.maxMessagesPerChannel);
775
+ }
776
+ this.writeChannelMessages(selfId, channelId, data.messages[channelKey]);
777
+ this.memoryCache = data;
778
+ this.logInfo(`批量写入 ${messagesToWrite.length} 条消息到频道 ${channelKey}`);
647
779
  }
648
780
  cleanExcessMessages(data) {
649
781
  let cleanedCount = 0;
@@ -668,9 +800,12 @@ var FileManager = class {
668
800
  };
669
801
  }
670
802
  async addMessageToFile(messageInfo) {
671
- this.pendingMessages.push(messageInfo);
672
- const data = this.memoryCache || this.readChatDataFromFile();
673
803
  const channelKey = `${messageInfo.selfId}:${messageInfo.channelId}`;
804
+ if (!this.pendingMessages.has(channelKey)) {
805
+ this.pendingMessages.set(channelKey, []);
806
+ }
807
+ this.pendingMessages.get(channelKey).push(messageInfo);
808
+ const data = this.memoryCache || this.readChatDataFromFile();
674
809
  if (!data.messages[channelKey]) {
675
810
  data.messages[channelKey] = [];
676
811
  }
@@ -687,14 +822,14 @@ var FileManager = class {
687
822
  }
688
823
  this.memoryCache = data;
689
824
  }
690
- this.scheduleWrite();
825
+ this.scheduleWrite(channelKey);
691
826
  }
692
827
  dispose() {
693
- if (this.writeTimer) {
694
- this.writeTimer();
695
- this.writeTimer = null;
828
+ for (const [channelKey, timer] of this.writeTimers.entries()) {
829
+ timer();
830
+ this.flushPendingMessages(channelKey);
696
831
  }
697
- this.flushPendingMessages();
832
+ this.writeTimers.clear();
698
833
  }
699
834
  logInfo(...args) {
700
835
  if (this.config.loggerinfo) {
@@ -733,8 +868,8 @@ var ApiHandlers = class {
733
868
  });
734
869
  this.ctx.console.addListener("get-chat-data", async () => {
735
870
  try {
736
- const data = this.fileManager.readChatDataFromFile();
737
- this.logInfo("获取基础聊天数据");
871
+ const data = this.fileManager.readMetadataOnly();
872
+ this.logInfo("获取基础聊天数据(仅元数据)");
738
873
  return {
739
874
  success: true,
740
875
  data: {
@@ -742,6 +877,7 @@ var ApiHandlers = class {
742
877
  channels: data.channels || {},
743
878
  pinnedBots: data.pinnedBots || [],
744
879
  pinnedChannels: data.pinnedChannels || [],
880
+ // 不返回消息数据,由前端按需加载
745
881
  messages: {}
746
882
  }
747
883
  };
@@ -752,9 +888,7 @@ var ApiHandlers = class {
752
888
  });
753
889
  this.ctx.console.addListener("get-history-messages", async (requestData) => {
754
890
  try {
755
- const data = this.fileManager.readChatDataFromFile();
756
- const channelKey = `${requestData.selfId}:${requestData.channelId}`;
757
- let messages = data.messages[channelKey] || [];
891
+ let messages = this.fileManager.readChannelMessages(requestData.selfId, requestData.channelId);
758
892
  const sortedMessages = messages.sort((a, b) => b.timestamp - a.timestamp);
759
893
  if (requestData.limit !== void 0) {
760
894
  const limit = requestData.limit;
@@ -764,7 +898,7 @@ var ApiHandlers = class {
764
898
  } else {
765
899
  messages = sortedMessages.sort((a, b) => a.timestamp - b.timestamp);
766
900
  }
767
- this.logInfo("获取历史消息:", channelKey, "共", messages.length, "条消息");
901
+ this.logInfo("获取历史消息:", `${requestData.selfId}:${requestData.channelId}`, "共", messages.length, "条消息");
768
902
  return {
769
903
  success: true,
770
904
  messages,
@@ -797,61 +931,72 @@ var ApiHandlers = class {
797
931
  });
798
932
  this.ctx.console.addListener("fetch-image", async (data) => {
799
933
  try {
934
+ if (data.url.includes("/vite/@fs/")) {
935
+ return {
936
+ success: true,
937
+ viteUrl: data.url
938
+ };
939
+ }
800
940
  if (this.isFileUrl(data.url)) {
801
941
  this.logInfo("处理本地文件请求:", data.url);
802
- return await this.handleLocalFileRequest(data.url);
942
+ const filePath2 = require("node:url").fileURLToPath(data.url);
943
+ const normalizedPath2 = filePath2.replace(/\\/g, "/");
944
+ return {
945
+ success: true,
946
+ viteUrl: `/vite/@fs/${normalizedPath2}`
947
+ };
948
+ }
949
+ const dir = require("node:path").join(this.ctx.baseDir, "data", "chat-patch", "persist-media", "images");
950
+ if (!require("node:fs").existsSync(dir)) {
951
+ require("node:fs").mkdirSync(dir, { recursive: true });
803
952
  }
804
- const response = await fetch(data.url, {
805
- headers: {
806
- "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",
807
- "Referer": ""
953
+ const crypto = require("node:crypto");
954
+ const hash = crypto.createHash("md5").update(data.url).digest("hex");
955
+ const ext = require("node:path").extname(new import_node_url2.URL(data.url).pathname) || ".jpg";
956
+ const filename = `${hash}${ext}`;
957
+ const filePath = require("node:path").join(dir, filename);
958
+ if (!require("node:fs").existsSync(filePath)) {
959
+ const response = await fetch(data.url, {
960
+ headers: {
961
+ "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",
962
+ "Referer": ""
963
+ }
964
+ });
965
+ if (!response.ok) {
966
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
808
967
  }
809
- });
810
- if (!response.ok) {
811
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
968
+ const buffer = await response.arrayBuffer();
969
+ require("node:fs").writeFileSync(filePath, Buffer.from(buffer));
812
970
  }
813
- const buffer = await response.arrayBuffer();
814
- const base64 = Buffer.from(buffer).toString("base64");
815
- const contentType = response.headers.get("content-type") || "image/jpeg";
971
+ const normalizedPath = filePath.replace(/\\/g, "/");
816
972
  return {
817
973
  success: true,
818
- base64,
819
- contentType,
820
- dataUrl: `data:${contentType};base64,${base64}`
974
+ viteUrl: `/vite/@fs/${normalizedPath}`
821
975
  };
822
976
  } catch (error) {
977
+ this.logger.error("获取图片失败:", error);
823
978
  return { success: false, error: error?.message || String(error) };
824
979
  }
825
980
  });
826
981
  this.ctx.console.addListener("clear-channel-history", async (data) => {
827
982
  try {
828
- this.logInfo("收到清理历史记录请求:", data);
983
+ this.logInfo("收到清理历史记录请求(已废弃,建议使用删除频道数据):", data);
829
984
  const chatData = this.fileManager.readChatDataFromFile();
830
985
  const channelKey = `${data.selfId}:${data.channelId}`;
831
986
  if (!chatData.messages[channelKey]) {
832
987
  return { success: true, message: "频道没有历史消息" };
833
988
  }
834
- const messages = chatData.messages[channelKey];
835
- const originalCount = messages.length;
836
- const keepCount = data.keepCount || this.config.keepMessagesOnClear;
837
- if (keepCount > 0 && originalCount <= keepCount) {
838
- return { success: true, message: `消息数量(${originalCount})未超过保留数量(${keepCount}),无需清理` };
839
- }
840
- const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp);
841
- const keptMessages = keepCount > 0 ? sortedMessages.slice(-keepCount) : [];
842
- const clearedCount = originalCount - keptMessages.length;
843
- chatData.messages[channelKey] = keptMessages;
989
+ const originalCount = chatData.messages[channelKey].length;
990
+ delete chatData.messages[channelKey];
844
991
  this.fileManager.writeChatDataToFile(chatData);
845
- this.logInfo(`频道 ${channelKey} 历史记录清理完成:`, {
846
- 原始消息数: originalCount,
847
- 保留消息数: keptMessages.length,
848
- 清理消息数: clearedCount
992
+ this.logInfo(`频道 ${channelKey} 历史记录已清空:`, {
993
+ 清理消息数: originalCount
849
994
  });
850
995
  return {
851
996
  success: true,
852
- message: `成功清理 ${clearedCount} 条历史消息,保留最新 ${keptMessages.length} 条`,
853
- clearedCount,
854
- keptCount: keptMessages.length
997
+ message: `成功清理 ${originalCount} 条历史消息`,
998
+ clearedCount: originalCount,
999
+ keptCount: 0
855
1000
  };
856
1001
  } catch (error) {
857
1002
  this.logger.error("清理频道历史记录失败:", error);
@@ -1063,7 +1208,6 @@ var ApiHandlers = class {
1063
1208
  success: true,
1064
1209
  config: {
1065
1210
  maxMessagesPerChannel: this.config.maxMessagesPerChannel,
1066
- keepMessagesOnClear: this.config.keepMessagesOnClear,
1067
1211
  maxPersistImages: this.config.maxPersistImages,
1068
1212
  loggerinfo: this.config.loggerinfo,
1069
1213
  blockedPlatforms: this.config.blockedPlatforms || [],
@@ -1148,29 +1292,47 @@ var ApiHandlers = class {
1148
1292
  this.ctx.console.addListener("fetch-video-temp", async (data) => {
1149
1293
  try {
1150
1294
  this.logInfo("收到视频临时加载请求:", data.url);
1295
+ if (data.url.includes("/vite/@fs/")) {
1296
+ return {
1297
+ success: true,
1298
+ viteUrl: data.url
1299
+ };
1300
+ }
1151
1301
  if (this.isFileUrl(data.url)) {
1152
- const result = await this.handleLocalFileRequest(data.url);
1153
- return result;
1302
+ const filePath2 = require("node:url").fileURLToPath(data.url);
1303
+ const normalizedPath2 = filePath2.replace(/\\/g, "/");
1304
+ return {
1305
+ success: true,
1306
+ viteUrl: `/vite/@fs/${normalizedPath2}`
1307
+ };
1154
1308
  }
1155
- const response = await fetch(data.url, {
1156
- headers: {
1157
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
1158
- "Referer": ""
1309
+ const dir = require("node:path").join(this.ctx.baseDir, "data", "chat-patch", "persist-media", "media");
1310
+ if (!require("node:fs").existsSync(dir)) {
1311
+ require("node:fs").mkdirSync(dir, { recursive: true });
1312
+ }
1313
+ const crypto = require("node:crypto");
1314
+ const hash = crypto.createHash("md5").update(data.url).digest("hex");
1315
+ const ext = require("node:path").extname(new import_node_url2.URL(data.url).pathname) || ".mp4";
1316
+ const filename = `${hash}${ext}`;
1317
+ const filePath = require("node:path").join(dir, filename);
1318
+ if (!require("node:fs").existsSync(filePath)) {
1319
+ const response = await fetch(data.url, {
1320
+ headers: {
1321
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
1322
+ "Referer": ""
1323
+ }
1324
+ });
1325
+ if (!response.ok) {
1326
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1159
1327
  }
1160
- });
1161
- if (!response.ok) {
1162
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1328
+ const buffer = await response.arrayBuffer();
1329
+ require("node:fs").writeFileSync(filePath, Buffer.from(buffer));
1330
+ this.logInfo("视频下载成功:", { size: buffer.byteLength, path: filePath });
1163
1331
  }
1164
- const buffer = await response.arrayBuffer();
1165
- const base64 = Buffer.from(buffer).toString("base64");
1166
- const contentType = response.headers.get("content-type") || "video/mp4";
1167
- this.logInfo("视频下载成功:", { size: buffer.byteLength, contentType });
1332
+ const normalizedPath = filePath.replace(/\\/g, "/");
1168
1333
  return {
1169
1334
  success: true,
1170
- base64,
1171
- contentType,
1172
- dataUrl: `data:${contentType};base64,${base64}`,
1173
- size: buffer.byteLength
1335
+ viteUrl: `/vite/@fs/${normalizedPath}`
1174
1336
  };
1175
1337
  } catch (error) {
1176
1338
  this.logger.error("视频临时加载失败:", error);
@@ -1255,7 +1417,6 @@ var import_koishi2 = require("koishi");
1255
1417
  var Config = import_koishi2.Schema.intersect([
1256
1418
  import_koishi2.Schema.object({
1257
1419
  maxMessagesPerChannel: import_koishi2.Schema.number().default(500).description("每个群组最大保存消息数量").min(50).max(1500).step(1),
1258
- keepMessagesOnClear: import_koishi2.Schema.number().default(50).description("手动清理历史记录时保留的消息数量").min(0).max(1e3).step(1),
1259
1420
  maxPersistImages: import_koishi2.Schema.number().default(100).description("持久化存储的图片缓存数量").min(10).max(500).step(1),
1260
1421
  blockedPlatforms: import_koishi2.Schema.array(import_koishi2.Schema.object({
1261
1422
  platformName: import_koishi2.Schema.string().description("平台名称或关键词"),
@@ -15,7 +15,7 @@ export declare class MessageHandler {
15
15
  getCorrectChannelId(selfId: string): string | undefined;
16
16
  updateBotInfoToFile(session: Session): void;
17
17
  updateChannelInfoToFile(session: Session): string;
18
- downloadAndCacheMedia(url: string, type: 'image' | 'media' | 'avatar'): Promise<any>;
18
+ downloadAndCacheMedia(url: string, type: 'image' | 'media' | 'avatar'): Promise<string>;
19
19
  private processMediaElementsAsync;
20
20
  private processUserMessage;
21
21
  private processBotMessage;
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.4",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [