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/client/icons/activity.vue +9 -9
- package/client/icons/index.ts +4 -4
- package/client/index.scss +90 -90
- package/client/index.ts +25 -25
- package/client/vue/composables/useChatActions.ts +1 -1
- package/client/vue/composables/useChatData.ts +1 -1
- package/client/vue/composables/useImageCache.ts +1 -1
- package/client/vue/types.ts +1 -1
- package/lib/file-manager.d.ts +25 -0
- package/lib/index.js +284 -193
- package/lib/message-handler.d.ts +7 -5
- package/package.json +1 -1
- package/readme.md +25 -25
- package/src/api-handlers.ts +19 -65
- package/src/config.ts +45 -46
- package/src/file-manager.ts +215 -169
- package/src/index.ts +58 -31
- package/src/message-handler.ts +161 -134
- package/src/types.ts +62 -62
- package/src/utils.ts +4 -16
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
230
|
+
immediateName = `私聊(${directUserName})`;
|
|
245
231
|
} else if (existingChannel?.name && !existingChannel.name.includes("未知")) {
|
|
246
|
-
|
|
232
|
+
immediateName = existingChannel.name;
|
|
247
233
|
} else if (session.platform && session.platform.toLowerCase().includes("sandbox")) {
|
|
248
|
-
|
|
234
|
+
immediateName = `私聊(${session.userId})`;
|
|
249
235
|
} else {
|
|
250
|
-
|
|
236
|
+
immediateName = "私聊(未知用户)";
|
|
251
237
|
}
|
|
252
|
-
} else {
|
|
253
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
318
|
-
}
|
|
319
|
-
return elements;
|
|
355
|
+
});
|
|
320
356
|
}
|
|
321
|
-
async
|
|
357
|
+
async processUserMessage(session, timestamp) {
|
|
322
358
|
try {
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
364
|
+
this.processMediaElementsAsync(session.elements, true);
|
|
329
365
|
}
|
|
330
366
|
if (session.quote?.elements) {
|
|
331
|
-
|
|
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("
|
|
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
|
-
|
|
412
|
-
const guildName =
|
|
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("
|
|
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
|
-
|
|
529
|
-
|
|
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
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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.
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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",
|
|
1295
|
-
if (utils.isPlatformBlocked(session.platform || "unknown"))
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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",
|
|
1302
|
-
if (utils.isPlatformBlocked(session.platform || "unknown"))
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
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:
|