koishi-plugin-chat-patch 2.0.0 → 2.1.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/index.scss +50 -1
- package/client/index.ts +2 -1
- package/client/vue/chat-logic.ts +471 -11
- package/client/vue/composables/useChatData.ts +4 -2
- package/client/vue/index.vue +279 -101
- package/client/vue/types.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/style.css +1 -1
- package/lib/api-handlers.d.ts +19 -0
- package/lib/index.js +226 -132
- package/lib/message-handler.d.ts +21 -0
- package/lib/types.d.ts +57 -0
- package/package.json +1 -1
- package/src/api-handlers.ts +122 -94
- package/src/message-handler.ts +160 -62
- package/src/types.ts +2 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FileManager } from './file-manager';
|
|
2
|
+
import { MessageHandler } from './message-handler';
|
|
3
|
+
import { Context } from 'koishi';
|
|
4
|
+
import { Config } from './config';
|
|
5
|
+
export declare class ApiHandlers {
|
|
6
|
+
private ctx;
|
|
7
|
+
private config;
|
|
8
|
+
private fileManager;
|
|
9
|
+
private messageHandler;
|
|
10
|
+
private logger;
|
|
11
|
+
constructor(ctx: Context, config: Config, fileManager: FileManager, messageHandler: MessageHandler);
|
|
12
|
+
registerApiHandlers(): void;
|
|
13
|
+
private isFileUrl;
|
|
14
|
+
private createFileUrl;
|
|
15
|
+
private handleLocalFileRequest;
|
|
16
|
+
private setupTempFileCleanup;
|
|
17
|
+
private cleanupMediaCache;
|
|
18
|
+
private logInfo;
|
|
19
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -197,55 +197,118 @@ var MessageHandler = class {
|
|
|
197
197
|
}
|
|
198
198
|
// 更新频道信息到JSON文件
|
|
199
199
|
async updateChannelInfoToFile(session) {
|
|
200
|
+
const isDirect = session.isDirect || session.channelId?.includes("private");
|
|
200
201
|
let guildName = session.channelId;
|
|
201
|
-
|
|
202
|
+
const directUserName = session.username || session.event?.user?.name || session.userId;
|
|
202
203
|
const data = this.fileManager.readChatDataFromFile();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (session.userId && session.isDirect && session.bot.getUser && typeof session.bot.getUser === "function") {
|
|
209
|
-
try {
|
|
210
|
-
const user = await session.bot.getUser(session.userId);
|
|
211
|
-
directUserName = user?.name || session.username || session.userId || "未知用户";
|
|
212
|
-
} catch (userError) {
|
|
213
|
-
this.logInfo("获取用户信息失败,使用备用名称:", userError);
|
|
204
|
+
if (!isDirect) {
|
|
205
|
+
try {
|
|
206
|
+
if (session.guildId && session.bot.getGuild && typeof session.bot.getGuild === "function") {
|
|
207
|
+
const guild = await session.bot.getGuild(session.guildId);
|
|
208
|
+
guildName = guild?.name || session.channelId;
|
|
214
209
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
210
|
+
if (session.guildId && !session.bot.getGuild && session.bot.getChannel && typeof session.bot.getChannel === "function") {
|
|
211
|
+
try {
|
|
212
|
+
const channel = await session.bot.getChannel(session.guildId);
|
|
213
|
+
guildName = channel?.name || session.channelId;
|
|
214
|
+
} catch (channelError) {
|
|
215
|
+
this.logInfo("获取频道信息失败,使用频道ID作为备用:", channelError);
|
|
216
|
+
}
|
|
222
217
|
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.logInfo("获取频道信息失败,使用频道ID作为备用:", error);
|
|
220
|
+
guildName = session.channelId;
|
|
223
221
|
}
|
|
224
|
-
} catch (error) {
|
|
225
|
-
this.logInfo("获取频道信息失败,使用频道ID作为备用:", error);
|
|
226
|
-
guildName = session.channelId;
|
|
227
222
|
}
|
|
228
223
|
if (!data.channels[session.selfId]) {
|
|
229
224
|
data.channels[session.selfId] = {};
|
|
230
225
|
}
|
|
226
|
+
const existingChannel = data.channels[session.selfId][session.channelId];
|
|
227
|
+
let finalName;
|
|
228
|
+
if (isDirect) {
|
|
229
|
+
if (directUserName && directUserName !== session.userId) {
|
|
230
|
+
finalName = `私聊(${directUserName})`;
|
|
231
|
+
} else if (existingChannel?.name && !existingChannel.name.includes("未知")) {
|
|
232
|
+
finalName = existingChannel.name;
|
|
233
|
+
} else {
|
|
234
|
+
finalName = "私聊(未知用户)";
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
finalName = guildName || session.channelId;
|
|
238
|
+
}
|
|
231
239
|
const channelInfo = {
|
|
232
240
|
id: session.channelId,
|
|
233
|
-
name:
|
|
241
|
+
name: finalName,
|
|
234
242
|
type: session.type || 0,
|
|
235
243
|
channelId: session.channelId,
|
|
236
244
|
guildName,
|
|
237
|
-
isDirect:
|
|
245
|
+
isDirect: !!isDirect
|
|
238
246
|
};
|
|
247
|
+
if (existingChannel && existingChannel.name !== finalName) {
|
|
248
|
+
this.logInfo("更新频道名称:", {
|
|
249
|
+
channelId: session.channelId,
|
|
250
|
+
oldName: existingChannel.name,
|
|
251
|
+
newName: finalName
|
|
252
|
+
});
|
|
253
|
+
}
|
|
239
254
|
data.channels[session.selfId][session.channelId] = channelInfo;
|
|
240
255
|
this.fileManager.writeChatDataToFile(data);
|
|
241
256
|
this.logInfo("更新频道信息到文件:", channelInfo.name);
|
|
242
257
|
return guildName;
|
|
243
258
|
}
|
|
259
|
+
// 下载并缓存媒体文件
|
|
260
|
+
async downloadAndCacheMedia(url, type) {
|
|
261
|
+
try {
|
|
262
|
+
if (!url || url.startsWith("data:") || url.startsWith("file:")) return url;
|
|
263
|
+
let folder = "media";
|
|
264
|
+
if (type === "image") folder = "images";
|
|
265
|
+
else if (type === "avatar") folder = "avatars";
|
|
266
|
+
const dir = require("node:path").join(this.ctx.baseDir, "data", "chat-patch", "persist-media", folder);
|
|
267
|
+
if (!require("node:fs").existsSync(dir)) {
|
|
268
|
+
require("node:fs").mkdirSync(dir, { recursive: true });
|
|
269
|
+
}
|
|
270
|
+
const crypto = require("node:crypto");
|
|
271
|
+
const hash = crypto.createHash("md5").update(url).digest("hex");
|
|
272
|
+
const ext = require("node:path").extname(new URL(url).pathname) || (type === "image" ? ".jpg" : ".mp4");
|
|
273
|
+
const filename = `${hash}${ext}`;
|
|
274
|
+
const filePath = require("node:path").join(dir, filename);
|
|
275
|
+
if (require("node:fs").existsSync(filePath)) {
|
|
276
|
+
return require("node:url").pathToFileURL(filePath).href;
|
|
277
|
+
}
|
|
278
|
+
const buffer = await this.ctx.http.get(url, { responseType: "arraybuffer" });
|
|
279
|
+
require("node:fs").writeFileSync(filePath, Buffer.from(buffer));
|
|
280
|
+
return require("node:url").pathToFileURL(filePath).href;
|
|
281
|
+
} catch (e) {
|
|
282
|
+
return url;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// 处理消息中的媒体元素并缓存
|
|
286
|
+
async processMediaElements(elements) {
|
|
287
|
+
if (!elements) return elements;
|
|
288
|
+
for (const el of elements) {
|
|
289
|
+
if (["image", "img", "mface"].includes(el.type)) {
|
|
290
|
+
const src = el.attrs.src || el.attrs.url || el.attrs.file;
|
|
291
|
+
if (src) el.attrs.src = await this.downloadAndCacheMedia(src, "image");
|
|
292
|
+
} else if (["audio", "video"].includes(el.type)) {
|
|
293
|
+
const src = el.attrs.src || el.attrs.url || el.attrs.file;
|
|
294
|
+
if (src) el.attrs.src = await this.downloadAndCacheMedia(src, "media");
|
|
295
|
+
}
|
|
296
|
+
if (el.children) await this.processMediaElements(el.children);
|
|
297
|
+
}
|
|
298
|
+
return elements;
|
|
299
|
+
}
|
|
244
300
|
async broadcastMessageEvent(session) {
|
|
245
301
|
try {
|
|
246
302
|
await this.updateBotInfoToFile(session);
|
|
247
303
|
const guildName = await this.updateChannelInfoToFile(session);
|
|
304
|
+
const isDirect = session.isDirect || session.channelId?.includes("private");
|
|
248
305
|
const timestamp = Date.now();
|
|
306
|
+
if (session.elements) {
|
|
307
|
+
await this.processMediaElements(session.elements);
|
|
308
|
+
}
|
|
309
|
+
if (session.quote?.elements) {
|
|
310
|
+
await this.processMediaElements(session.quote.elements);
|
|
311
|
+
}
|
|
249
312
|
let quoteInfo = void 0;
|
|
250
313
|
if (session.quote) {
|
|
251
314
|
quoteInfo = {
|
|
@@ -290,7 +353,7 @@ var MessageHandler = class {
|
|
|
290
353
|
guildName,
|
|
291
354
|
platform: session.platform || "unknown",
|
|
292
355
|
quote: quoteInfo ? this.utils.cleanBase64Content(quoteInfo) : void 0,
|
|
293
|
-
isDirect:
|
|
356
|
+
isDirect: !!isDirect
|
|
294
357
|
};
|
|
295
358
|
await this.fileManager.addMessageToFile(messageInfo);
|
|
296
359
|
const messageEvent = {
|
|
@@ -323,22 +386,47 @@ var MessageHandler = class {
|
|
|
323
386
|
async broadcastBotMessageEvent(session) {
|
|
324
387
|
try {
|
|
325
388
|
const correctChannelId = this.getCorrectChannelId(session.selfId);
|
|
326
|
-
this.logInfo("机器人发送消息调试信息:", {
|
|
327
|
-
"session.channelId": session.channelId,
|
|
328
|
-
"session.event?.channel?.id": session.event?.channel?.id,
|
|
329
|
-
"correctChannelId": correctChannelId,
|
|
330
|
-
"session.guildId": session.guildId,
|
|
331
|
-
"session.isDirect": session.isDirect,
|
|
332
|
-
"session.platform": session.platform
|
|
333
|
-
});
|
|
334
389
|
const finalChannelId = correctChannelId || session.channelId;
|
|
335
390
|
await this.updateBotInfoToFile(session);
|
|
336
391
|
const guildName = await this.updateChannelInfoToFile(session);
|
|
392
|
+
const isDirect = session.isDirect || finalChannelId?.includes("private");
|
|
337
393
|
const timestamp = Date.now();
|
|
394
|
+
if (session.event?.message?.elements) {
|
|
395
|
+
await this.processMediaElements(session.event.message.elements);
|
|
396
|
+
}
|
|
338
397
|
let content = session.content || "";
|
|
339
398
|
if (!content && session.event?.message?.elements) {
|
|
340
399
|
content = this.utils.extractTextContent(session.event.message.elements).trim();
|
|
341
400
|
}
|
|
401
|
+
let quoteInfo = void 0;
|
|
402
|
+
const quoteMatch = content.match(/<quote id="([^"]+)"\/>/);
|
|
403
|
+
if (quoteMatch) {
|
|
404
|
+
const quoteId = quoteMatch[1];
|
|
405
|
+
const data = this.fileManager.readChatDataFromFile();
|
|
406
|
+
const channelKey = `${session.selfId}:${finalChannelId}`;
|
|
407
|
+
const quotedMsg = data.messages[channelKey]?.find((m) => m.id === quoteId);
|
|
408
|
+
if (quotedMsg) {
|
|
409
|
+
const realId = quotedMsg.id.startsWith("bot-msg-") ? quotedMsg.realId : quotedMsg.id;
|
|
410
|
+
quoteInfo = {
|
|
411
|
+
messageId: realId || quotedMsg.id,
|
|
412
|
+
id: realId || quotedMsg.id,
|
|
413
|
+
content: quotedMsg.content,
|
|
414
|
+
elements: quotedMsg.elements,
|
|
415
|
+
user: {
|
|
416
|
+
id: quotedMsg.userId,
|
|
417
|
+
name: quotedMsg.username,
|
|
418
|
+
userId: quotedMsg.userId,
|
|
419
|
+
avatar: quotedMsg.avatar,
|
|
420
|
+
username: quotedMsg.username
|
|
421
|
+
},
|
|
422
|
+
timestamp: quotedMsg.timestamp
|
|
423
|
+
};
|
|
424
|
+
content = content.replace(/<quote id="[^"]+"\/>\s*/, "");
|
|
425
|
+
if (realId) {
|
|
426
|
+
session.content = session.content.replace(/id="[^"]+"/, `id="${realId}"`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
342
430
|
const messageInfo = {
|
|
343
431
|
id: `bot-msg-${timestamp}`,
|
|
344
432
|
content,
|
|
@@ -352,7 +440,10 @@ var MessageHandler = class {
|
|
|
352
440
|
type: "bot",
|
|
353
441
|
guildName,
|
|
354
442
|
platform: session.platform || "unknown",
|
|
355
|
-
|
|
443
|
+
quote: quoteInfo,
|
|
444
|
+
isDirect: !!isDirect,
|
|
445
|
+
sending: true
|
|
446
|
+
// 标记为正在发送
|
|
356
447
|
};
|
|
357
448
|
await this.fileManager.addMessageToFile(messageInfo);
|
|
358
449
|
const messageEvent = {
|
|
@@ -369,26 +460,14 @@ var MessageHandler = class {
|
|
|
369
460
|
guildName,
|
|
370
461
|
channelType: session.event?.channel?.type || session.type || 0,
|
|
371
462
|
elements: this.utils.cleanBase64Content(session.event?.message?.elements),
|
|
372
|
-
|
|
463
|
+
quote: quoteInfo,
|
|
464
|
+
isDirect: !!isDirect,
|
|
465
|
+
sending: true,
|
|
373
466
|
bot: {
|
|
374
467
|
avatar: session.bot.user?.avatar,
|
|
375
468
|
name: session.bot.user?.name
|
|
376
469
|
}
|
|
377
470
|
};
|
|
378
|
-
const imageElements = session.event?.message?.elements?.filter(
|
|
379
|
-
(element) => element.type === "img" || element.type === "image" || element.type === "mface"
|
|
380
|
-
) || [];
|
|
381
|
-
this.logInfo("机器人发送消息 (before-send):", {
|
|
382
|
-
selfId: session.selfId,
|
|
383
|
-
channelId: messageEvent.channelId,
|
|
384
|
-
originalChannelId: session.channelId,
|
|
385
|
-
correctedChannelId: finalChannelId,
|
|
386
|
-
content,
|
|
387
|
-
platform: session.platform,
|
|
388
|
-
imageCount: imageElements.length,
|
|
389
|
-
imageUrls: imageElements.map((el) => el.attrs?.src || el.attrs?.url || el.attrs?.file)
|
|
390
|
-
});
|
|
391
|
-
this.correctChannelIds.delete(session.selfId);
|
|
392
471
|
this.ctx.console.broadcast("chat-bot-message-event", messageEvent);
|
|
393
472
|
} catch (error) {
|
|
394
473
|
this.logger.error("广播机器人消息事件失败:", error);
|
|
@@ -574,19 +653,16 @@ var ApiHandlers = class {
|
|
|
574
653
|
this.ctx.console.addListener("get-chat-data", async () => {
|
|
575
654
|
try {
|
|
576
655
|
const data = this.fileManager.readChatDataFromFile();
|
|
577
|
-
|
|
578
|
-
this.logInfo("获取聊天数据:", {
|
|
579
|
-
机器人数量: Object.keys(cleanedData.bots).length,
|
|
580
|
-
频道数量: Object.keys(cleanedData.channels).reduce((total, botId) => total + Object.keys(cleanedData.channels[botId] || {}).length, 0),
|
|
581
|
-
消息频道数: Object.keys(cleanedData.messages).length,
|
|
582
|
-
总消息数: Object.values(cleanedData.messages).reduce((total, msgs) => total + msgs.length, 0)
|
|
583
|
-
});
|
|
656
|
+
this.logInfo("获取基础聊天数据");
|
|
584
657
|
return {
|
|
585
658
|
success: true,
|
|
586
659
|
data: {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
660
|
+
bots: data.bots || {},
|
|
661
|
+
channels: data.channels || {},
|
|
662
|
+
pinnedBots: data.pinnedBots || [],
|
|
663
|
+
pinnedChannels: data.pinnedChannels || [],
|
|
664
|
+
// 不返回 messages,由前端按需拉取
|
|
665
|
+
messages: {}
|
|
590
666
|
}
|
|
591
667
|
};
|
|
592
668
|
} catch (error) {
|
|
@@ -731,31 +807,23 @@ var ApiHandlers = class {
|
|
|
731
807
|
this.logInfo("消息发送成功:", result);
|
|
732
808
|
const messageId = Array.isArray(result) ? result[0] : result;
|
|
733
809
|
if (messageId) {
|
|
734
|
-
const
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
channelType: 0,
|
|
749
|
-
// 这个信息在前端会补充
|
|
750
|
-
elements: [],
|
|
751
|
-
// 这个信息在前端会补充
|
|
752
|
-
isDirect: false
|
|
753
|
-
// 这个信息在前端会补充
|
|
754
|
-
};
|
|
755
|
-
this.ctx.console.broadcast("bot-message-sent-event", botMessageEvent);
|
|
810
|
+
const chatData = this.fileManager.readChatDataFromFile();
|
|
811
|
+
const channelKey = `${data.selfId}:${data.channelId}`;
|
|
812
|
+
const messages = chatData.messages[channelKey] || [];
|
|
813
|
+
const msg = [...messages].reverse().find((m) => m.type === "bot" && m.sending);
|
|
814
|
+
if (msg) {
|
|
815
|
+
msg.realId = messageId;
|
|
816
|
+
msg.sending = false;
|
|
817
|
+
this.fileManager.writeChatDataToFile(chatData);
|
|
818
|
+
this.ctx.console.broadcast("bot-message-updated", {
|
|
819
|
+
channelKey,
|
|
820
|
+
tempId: msg.id,
|
|
821
|
+
realId: messageId
|
|
822
|
+
});
|
|
823
|
+
}
|
|
756
824
|
}
|
|
757
825
|
return {
|
|
758
|
-
success:
|
|
826
|
+
success: !!messageId,
|
|
759
827
|
messageId,
|
|
760
828
|
tempImageIds: data.images?.map((img) => img.tempId) || []
|
|
761
829
|
};
|
|
@@ -923,6 +991,61 @@ var ApiHandlers = class {
|
|
|
923
991
|
return { success: false, error: error?.message || String(error) };
|
|
924
992
|
}
|
|
925
993
|
});
|
|
994
|
+
this.ctx.console.addListener("get-user-info", async (data) => {
|
|
995
|
+
try {
|
|
996
|
+
const bot = this.ctx.bots.find((b) => b.selfId === data.selfId);
|
|
997
|
+
if (!bot) return { success: false, error: "机器人不存在" };
|
|
998
|
+
const user = await bot.getUser(data.userId, data.guildId);
|
|
999
|
+
if (user) {
|
|
1000
|
+
if (user.avatar) {
|
|
1001
|
+
user.avatar = await this.messageHandler.downloadAndCacheMedia(user.avatar, "avatar");
|
|
1002
|
+
}
|
|
1003
|
+
const chatData = this.fileManager.readChatDataFromFile();
|
|
1004
|
+
let changed = false;
|
|
1005
|
+
const botChannels = chatData.channels[data.selfId] || {};
|
|
1006
|
+
const possibleChannelIds = [
|
|
1007
|
+
data.userId,
|
|
1008
|
+
`private:${data.userId}`,
|
|
1009
|
+
`direct:${data.userId}`
|
|
1010
|
+
];
|
|
1011
|
+
for (const channelId of possibleChannelIds) {
|
|
1012
|
+
const channel = botChannels[channelId];
|
|
1013
|
+
if (channel && channel.isDirect) {
|
|
1014
|
+
const newName = `私聊(${user.name})`;
|
|
1015
|
+
if (channel.name !== newName) {
|
|
1016
|
+
channel.name = newName;
|
|
1017
|
+
changed = true;
|
|
1018
|
+
this.logInfo("更新私聊频道名称:", { channelId, oldName: channel.name, newName });
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
const channelKeyPrefix = `${data.selfId}:`;
|
|
1023
|
+
for (const [key, messages] of Object.entries(chatData.messages)) {
|
|
1024
|
+
if (key.startsWith(channelKeyPrefix)) {
|
|
1025
|
+
messages.forEach((msg) => {
|
|
1026
|
+
if (msg.userId === data.userId) {
|
|
1027
|
+
if (user.name && msg.username !== user.name) {
|
|
1028
|
+
msg.username = user.name;
|
|
1029
|
+
changed = true;
|
|
1030
|
+
}
|
|
1031
|
+
if (user.avatar && msg.avatar !== user.avatar) {
|
|
1032
|
+
msg.avatar = user.avatar;
|
|
1033
|
+
changed = true;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (changed) {
|
|
1040
|
+
this.fileManager.writeChatDataToFile(chatData);
|
|
1041
|
+
this.ctx.console.broadcast("chat-data-updated", {});
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return { success: true, data: user };
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
return { success: false, error: error?.message || "获取用户信息失败" };
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
926
1049
|
this.ctx.console.addListener("debug-get-raw-data", async () => {
|
|
927
1050
|
try {
|
|
928
1051
|
const data = this.fileManager.readChatDataFromFile();
|
|
@@ -979,61 +1102,32 @@ var ApiHandlers = class {
|
|
|
979
1102
|
// 设置定时清理临时文件
|
|
980
1103
|
setupTempFileCleanup() {
|
|
981
1104
|
setInterval(() => {
|
|
982
|
-
this.
|
|
1105
|
+
this.cleanupMediaCache();
|
|
983
1106
|
}, 5 * 60 * 1e3);
|
|
984
1107
|
}
|
|
985
|
-
//
|
|
986
|
-
async
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
return {
|
|
1000
|
-
name: file,
|
|
1001
|
-
path: filePath,
|
|
1002
|
-
mtime: stats.mtime.getTime(),
|
|
1003
|
-
age: fileAge,
|
|
1004
|
-
protected: fileAge < protectionTime
|
|
1005
|
-
// 是否在保护期内
|
|
1006
|
-
};
|
|
1007
|
-
} catch (error) {
|
|
1008
|
-
return null;
|
|
1009
|
-
}
|
|
1010
|
-
}).filter((file) => file !== null).sort((a, b) => b.mtime - a.mtime);
|
|
1011
|
-
const keepCount = this.config.keepTempImages;
|
|
1012
|
-
let cleanedCount = 0;
|
|
1013
|
-
let protectedCount = 0;
|
|
1014
|
-
const protectedFiles = files.filter((file) => file.protected);
|
|
1015
|
-
const deletableFiles = files.filter((file) => !file.protected);
|
|
1016
|
-
protectedCount = protectedFiles.length;
|
|
1017
|
-
if (deletableFiles.length > keepCount) {
|
|
1018
|
-
const filesToDelete = deletableFiles.slice(keepCount);
|
|
1019
|
-
for (const file of filesToDelete) {
|
|
1108
|
+
// 统一清理媒体缓存
|
|
1109
|
+
async cleanupMediaCache() {
|
|
1110
|
+
const baseDir = this.ctx.baseDir + "/data/chat-patch/persist-media";
|
|
1111
|
+
if (!require("node:fs").existsSync(baseDir)) return;
|
|
1112
|
+
const cleanupDir = /* @__PURE__ */ __name((dirName, limit) => {
|
|
1113
|
+
const dirPath = require("node:path").join(baseDir, dirName);
|
|
1114
|
+
if (!require("node:fs").existsSync(dirPath)) return;
|
|
1115
|
+
const files = require("node:fs").readdirSync(dirPath).map((file) => {
|
|
1116
|
+
const filePath = require("node:path").join(dirPath, file);
|
|
1117
|
+
const stats = require("node:fs").statSync(filePath);
|
|
1118
|
+
return { name: file, path: filePath, mtime: stats.mtimeMs };
|
|
1119
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
1120
|
+
if (files.length > limit) {
|
|
1121
|
+
files.slice(limit).forEach((f) => {
|
|
1020
1122
|
try {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
cleanedCount++;
|
|
1024
|
-
this.logInfo("清理多余临时图片:", file.path);
|
|
1025
|
-
}
|
|
1026
|
-
} catch (fileError) {
|
|
1027
|
-
this.logger.warn("删除临时文件失败:", { file: file.name, error: fileError });
|
|
1123
|
+
require("node:fs").unlinkSync(f.path);
|
|
1124
|
+
} catch {
|
|
1028
1125
|
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
if (cleanedCount > 0 || protectedCount > 0) {
|
|
1032
|
-
this.logInfo(`基于数量清理完成,保留最新 ${keepCount} 张图片,清理了 ${cleanedCount} 张多余图片,保护了 ${protectedCount} 张新上传图片`);
|
|
1126
|
+
});
|
|
1033
1127
|
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1128
|
+
}, "cleanupDir");
|
|
1129
|
+
cleanupDir("images", 100);
|
|
1130
|
+
cleanupDir("media", 20);
|
|
1037
1131
|
}
|
|
1038
1132
|
logInfo(...args) {
|
|
1039
1133
|
if (this.config.loggerinfo) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Context, Session } from 'koishi';
|
|
2
|
+
import { FileManager } from './file-manager';
|
|
3
|
+
import { Config } from './config';
|
|
4
|
+
export declare class MessageHandler {
|
|
5
|
+
private ctx;
|
|
6
|
+
private config;
|
|
7
|
+
private fileManager;
|
|
8
|
+
private logger;
|
|
9
|
+
private utils;
|
|
10
|
+
private correctChannelIds;
|
|
11
|
+
constructor(ctx: Context, config: Config, fileManager: FileManager);
|
|
12
|
+
setCorrectChannelId(selfId: string, channelId: string): void;
|
|
13
|
+
getCorrectChannelId(selfId: string): string | undefined;
|
|
14
|
+
updateBotInfoToFile(session: Session): Promise<void>;
|
|
15
|
+
updateChannelInfoToFile(session: Session): Promise<string>;
|
|
16
|
+
downloadAndCacheMedia(url: string, type: 'image' | 'media' | 'avatar'): Promise<any>;
|
|
17
|
+
private processMediaElements;
|
|
18
|
+
broadcastMessageEvent(session: Session): Promise<void>;
|
|
19
|
+
broadcastBotMessageEvent(session: Session): Promise<void>;
|
|
20
|
+
private logInfo;
|
|
21
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { h } from 'koishi';
|
|
2
|
+
export interface BotInfo {
|
|
3
|
+
selfId: string;
|
|
4
|
+
platform: string;
|
|
5
|
+
username: string;
|
|
6
|
+
avatar?: string;
|
|
7
|
+
status: 'online' | 'offline';
|
|
8
|
+
}
|
|
9
|
+
export interface ChannelInfo {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
type: number | string;
|
|
13
|
+
channelId?: string;
|
|
14
|
+
guildName?: string;
|
|
15
|
+
isDirect?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface QuoteInfo {
|
|
18
|
+
messageId: string;
|
|
19
|
+
id: string;
|
|
20
|
+
content: string;
|
|
21
|
+
elements?: h[];
|
|
22
|
+
user: {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
userId: string;
|
|
26
|
+
avatar?: string;
|
|
27
|
+
username: string;
|
|
28
|
+
};
|
|
29
|
+
timestamp: number;
|
|
30
|
+
}
|
|
31
|
+
export interface MessageInfo {
|
|
32
|
+
id: string;
|
|
33
|
+
content: string;
|
|
34
|
+
userId: string;
|
|
35
|
+
username: string;
|
|
36
|
+
avatar?: string;
|
|
37
|
+
timestamp: number;
|
|
38
|
+
channelId: string;
|
|
39
|
+
selfId: string;
|
|
40
|
+
elements?: h[];
|
|
41
|
+
type: 'user' | 'bot';
|
|
42
|
+
guildId?: string;
|
|
43
|
+
guildName?: string;
|
|
44
|
+
platform: string;
|
|
45
|
+
quote?: QuoteInfo;
|
|
46
|
+
isDirect?: boolean;
|
|
47
|
+
sending?: boolean;
|
|
48
|
+
realId?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface ChatData {
|
|
51
|
+
bots: Record<string, BotInfo>;
|
|
52
|
+
channels: Record<string, Record<string, ChannelInfo>>;
|
|
53
|
+
messages: Record<string, MessageInfo[]>;
|
|
54
|
+
pinnedBots: string[];
|
|
55
|
+
pinnedChannels: string[];
|
|
56
|
+
lastSaveTime?: number;
|
|
57
|
+
}
|
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.
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|