koishi-plugin-best-cave 2.0.8 → 2.0.9
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/Utils.d.ts +29 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +119 -76
- package/package.json +1 -1
package/lib/Utils.d.ts
CHANGED
|
@@ -53,3 +53,32 @@ export declare function checkCooldown(session: Session, config: Config, lastUsed
|
|
|
53
53
|
* @description 更新指定频道的指令使用时间戳。
|
|
54
54
|
*/
|
|
55
55
|
export declare function updateCooldownTimestamp(session: Session, config: Config, lastUsed: Map<string, number>): void;
|
|
56
|
+
/**
|
|
57
|
+
* @description 解析消息元素,分离出文本和待下载的媒体文件。
|
|
58
|
+
* @param sourceElements - 原始的 Koishi 消息元素数组。
|
|
59
|
+
* @param newId - 这条回声洞的新 ID。
|
|
60
|
+
* @param channelId - 频道 ID。
|
|
61
|
+
* @param userId - 用户 ID。
|
|
62
|
+
* @returns 一个包含数据库元素和待保存媒体列表的对象。
|
|
63
|
+
*/
|
|
64
|
+
export declare function processMessageElements(sourceElements: h[], newId: number, channelId: string, userId: string): Promise<{
|
|
65
|
+
finalElementsForDb: StoredElement[];
|
|
66
|
+
mediaToSave: {
|
|
67
|
+
sourceUrl: string;
|
|
68
|
+
fileName: string;
|
|
69
|
+
}[];
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* @description 异步处理文件上传和状态更新的后台任务。
|
|
73
|
+
* @param ctx - Koishi 上下文。
|
|
74
|
+
* @param config - 插件配置。
|
|
75
|
+
* @param fileManager - 文件管理器实例。
|
|
76
|
+
* @param logger - 日志记录器实例。
|
|
77
|
+
* @param reviewManager - 审核管理器实例 (可能为 null)。
|
|
78
|
+
* @param cave - 已创建的、状态为 'preload' 的回声洞对象。
|
|
79
|
+
* @param mediaToSave - 需要下载和保存的媒体文件列表。
|
|
80
|
+
*/
|
|
81
|
+
export declare function handleFileUploads(ctx: Context, config: Config, fileManager: FileManager, logger: Logger, reviewManager: any, cave: CaveObject, mediaToSave: {
|
|
82
|
+
sourceUrl: string;
|
|
83
|
+
fileName: string;
|
|
84
|
+
}[]): Promise<void>;
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -38,7 +38,6 @@ __export(index_exports, {
|
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
40
|
var import_koishi3 = require("koishi");
|
|
41
|
-
var path3 = __toESM(require("path"));
|
|
42
41
|
|
|
43
42
|
// src/FileManager.ts
|
|
44
43
|
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
@@ -259,10 +258,14 @@ async function buildCaveMessage(cave, config, fileManager, logger2) {
|
|
|
259
258
|
const [headerFormat, footerFormat = ""] = config.caveFormat.split("|");
|
|
260
259
|
const replacements = { id: cave.id.toString(), name: cave.userName };
|
|
261
260
|
const headerText = headerFormat.replace(/\{id\}|\{name\}/g, (match) => replacements[match.slice(1, -1)]);
|
|
262
|
-
if (headerText.trim())
|
|
261
|
+
if (headerText.trim()) {
|
|
262
|
+
finalMessage.push(headerText.trim() + "\n");
|
|
263
|
+
}
|
|
263
264
|
finalMessage.push(...processedElements);
|
|
264
265
|
const footerText = footerFormat.replace(/\{id\}|\{name\}/g, (match) => replacements[match.slice(1, -1)]);
|
|
265
|
-
if (footerText.trim())
|
|
266
|
+
if (footerText.trim()) {
|
|
267
|
+
finalMessage.push("\n" + footerText.trim());
|
|
268
|
+
}
|
|
266
269
|
return finalMessage;
|
|
267
270
|
}
|
|
268
271
|
__name(buildCaveMessage, "buildCaveMessage");
|
|
@@ -312,6 +315,69 @@ function updateCooldownTimestamp(session, config, lastUsed) {
|
|
|
312
315
|
}
|
|
313
316
|
}
|
|
314
317
|
__name(updateCooldownTimestamp, "updateCooldownTimestamp");
|
|
318
|
+
async function processMessageElements(sourceElements, newId, channelId, userId) {
|
|
319
|
+
const finalElementsForDb = [];
|
|
320
|
+
const mediaToSave = [];
|
|
321
|
+
let mediaIndex = 0;
|
|
322
|
+
const typeMap = {
|
|
323
|
+
"img": "image",
|
|
324
|
+
"image": "image",
|
|
325
|
+
"video": "video",
|
|
326
|
+
"audio": "audio",
|
|
327
|
+
"file": "file",
|
|
328
|
+
"text": "text"
|
|
329
|
+
};
|
|
330
|
+
async function traverse(elements) {
|
|
331
|
+
for (const el of elements) {
|
|
332
|
+
const normalizedType = typeMap[el.type];
|
|
333
|
+
if (!normalizedType) {
|
|
334
|
+
if (el.children) await traverse(el.children);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (["image", "video", "audio", "file"].includes(normalizedType) && el.attrs.src) {
|
|
338
|
+
let fileIdentifier = el.attrs.src;
|
|
339
|
+
if (fileIdentifier.startsWith("http")) {
|
|
340
|
+
mediaIndex++;
|
|
341
|
+
const defaultExtMap = { "image": ".jpg", "video": ".mp4", "audio": ".mp3", "file": ".dat" };
|
|
342
|
+
const ext = el.attrs.file && path2.extname(el.attrs.file) ? path2.extname(el.attrs.file) : defaultExtMap[normalizedType] || ".dat";
|
|
343
|
+
const channelIdentifier = channelId || "private";
|
|
344
|
+
const fileName = `${newId}_${mediaIndex}_${channelIdentifier}_${userId}${ext}`;
|
|
345
|
+
mediaToSave.push({ sourceUrl: fileIdentifier, fileName });
|
|
346
|
+
fileIdentifier = fileName;
|
|
347
|
+
}
|
|
348
|
+
finalElementsForDb.push({ type: normalizedType, file: fileIdentifier });
|
|
349
|
+
} else if (normalizedType === "text" && el.attrs.content?.trim()) {
|
|
350
|
+
finalElementsForDb.push({ type: "text", content: el.attrs.content.trim() });
|
|
351
|
+
}
|
|
352
|
+
if (el.children) {
|
|
353
|
+
await traverse(el.children);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
__name(traverse, "traverse");
|
|
358
|
+
await traverse(sourceElements);
|
|
359
|
+
return { finalElementsForDb, mediaToSave };
|
|
360
|
+
}
|
|
361
|
+
__name(processMessageElements, "processMessageElements");
|
|
362
|
+
async function handleFileUploads(ctx, config, fileManager, logger2, reviewManager, cave, mediaToSave) {
|
|
363
|
+
try {
|
|
364
|
+
await Promise.all(mediaToSave.map(async (media) => {
|
|
365
|
+
const response = await ctx.http.get(media.sourceUrl, { responseType: "arraybuffer", timeout: 3e4 });
|
|
366
|
+
await fileManager.saveFile(media.fileName, Buffer.from(response));
|
|
367
|
+
}));
|
|
368
|
+
const finalStatus = config.enableReview ? "pending" : "active";
|
|
369
|
+
await ctx.database.upsert("cave", [{ id: cave.id, status: finalStatus }]);
|
|
370
|
+
if (finalStatus === "pending" && reviewManager) {
|
|
371
|
+
const [finalCave] = await ctx.database.get("cave", { id: cave.id });
|
|
372
|
+
if (finalCave) reviewManager.sendForReview(finalCave);
|
|
373
|
+
}
|
|
374
|
+
} catch (fileSaveError) {
|
|
375
|
+
logger2.error(`回声洞(${cave.id})文件保存失败:`, fileSaveError);
|
|
376
|
+
await ctx.database.upsert("cave", [{ id: cave.id, status: "delete" }]);
|
|
377
|
+
await cleanupPendingDeletions(ctx, fileManager, logger2);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
__name(handleFileUploads, "handleFileUploads");
|
|
315
381
|
|
|
316
382
|
// src/DataManager.ts
|
|
317
383
|
var DataManager = class {
|
|
@@ -337,7 +403,8 @@ var DataManager = class {
|
|
|
337
403
|
*/
|
|
338
404
|
registerCommands(cave) {
|
|
339
405
|
cave.subcommand(".export", "导出回声洞数据").usage("将所有回声洞数据导出到 cave_export.json。").action(async ({ session }) => {
|
|
340
|
-
|
|
406
|
+
const adminChannelId = this.config.adminChannel ? this.config.adminChannel.split(":")[1] : null;
|
|
407
|
+
if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
|
|
341
408
|
try {
|
|
342
409
|
await session.send("正在导出数据,请稍候...");
|
|
343
410
|
return await this.exportData();
|
|
@@ -347,7 +414,8 @@ var DataManager = class {
|
|
|
347
414
|
}
|
|
348
415
|
});
|
|
349
416
|
cave.subcommand(".import", "导入回声洞数据").usage("从 cave_import.json 中导入回声洞数据。").action(async ({ session }) => {
|
|
350
|
-
|
|
417
|
+
const adminChannelId = this.config.adminChannel ? this.config.adminChannel.split(":")[1] : null;
|
|
418
|
+
if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
|
|
351
419
|
try {
|
|
352
420
|
await session.send("正在导入数据,请稍候...");
|
|
353
421
|
return await this.importData();
|
|
@@ -390,8 +458,7 @@ var DataManager = class {
|
|
|
390
458
|
const newCave = {
|
|
391
459
|
...cave,
|
|
392
460
|
id: newId,
|
|
393
|
-
channelId: cave.channelId
|
|
394
|
-
// 保证 channelId 存在
|
|
461
|
+
channelId: cave.channelId,
|
|
395
462
|
status: "active"
|
|
396
463
|
};
|
|
397
464
|
await this.ctx.database.create("cave", newCave);
|
|
@@ -426,7 +493,8 @@ var ReviewManager = class {
|
|
|
426
493
|
*/
|
|
427
494
|
registerCommands(cave) {
|
|
428
495
|
cave.subcommand(".review [id:posint] [action:string]", "审核回声洞").usage("查看或审核回声洞,使用 <Y/N> 进行审核。").action(async ({ session }, id, action) => {
|
|
429
|
-
|
|
496
|
+
const adminChannelId = this.config.adminChannel ? this.config.adminChannel.split(":")[1] : null;
|
|
497
|
+
if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
|
|
430
498
|
if (!id) {
|
|
431
499
|
const pendingCaves = await this.ctx.database.get("cave", { status: "pending" });
|
|
432
500
|
if (pendingCaves.length === 0) return "当前没有需要审核的回声洞";
|
|
@@ -453,8 +521,9 @@ ${pendingCaves.map((c) => c.id).join(", ")}`;
|
|
|
453
521
|
* @param cave 新创建的、状态为 'pending' 的回声洞对象。
|
|
454
522
|
*/
|
|
455
523
|
async sendForReview(cave) {
|
|
456
|
-
|
|
457
|
-
|
|
524
|
+
const channelParts = this.config.adminChannel?.split(":");
|
|
525
|
+
if (!channelParts || channelParts.length < 2 || !channelParts[1]) {
|
|
526
|
+
this.logger.warn(`管理群组配置无效,已自动通过回声洞(${cave.id})`);
|
|
458
527
|
await this.ctx.database.upsert("cave", [{ id: cave.id, status: "active" }]);
|
|
459
528
|
return;
|
|
460
529
|
}
|
|
@@ -509,7 +578,7 @@ var Config = import_koishi3.Schema.intersect([
|
|
|
509
578
|
perChannel: import_koishi3.Schema.boolean().default(false).description("启用分群模式"),
|
|
510
579
|
enableProfile: import_koishi3.Schema.boolean().default(false).description("启用自定义昵称"),
|
|
511
580
|
enableIO: import_koishi3.Schema.boolean().default(false).description("启用导入导出"),
|
|
512
|
-
adminChannel: import_koishi3.Schema.string().description("管理群组 ID"),
|
|
581
|
+
adminChannel: import_koishi3.Schema.string().default("onebot:").description("管理群组 ID"),
|
|
513
582
|
caveFormat: import_koishi3.Schema.string().default("回声洞 ——({id})|—— {name}").description("自定义文本")
|
|
514
583
|
}).description("基础配置"),
|
|
515
584
|
import_koishi3.Schema.object({
|
|
@@ -581,73 +650,46 @@ function apply(ctx, config) {
|
|
|
581
650
|
idScopeQuery["channelId"] = session.channelId;
|
|
582
651
|
}
|
|
583
652
|
const newId = await getNextCaveId(ctx, idScopeQuery);
|
|
584
|
-
const finalElementsForDb =
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
"video": "video",
|
|
591
|
-
"audio": "audio",
|
|
592
|
-
"file": "file",
|
|
593
|
-
"text": "text"
|
|
594
|
-
};
|
|
595
|
-
async function traverseAndProcess(elements) {
|
|
596
|
-
for (const el of elements) {
|
|
597
|
-
const normalizedType = typeMap[el.type];
|
|
598
|
-
if (!normalizedType) {
|
|
599
|
-
if (el.children) await traverseAndProcess(el.children);
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
if (["image", "video", "audio", "file"].includes(normalizedType) && el.attrs.src) {
|
|
603
|
-
let fileIdentifier = el.attrs.src;
|
|
604
|
-
if (fileIdentifier.startsWith("http")) {
|
|
605
|
-
mediaIndex++;
|
|
606
|
-
const defaultExtMap = { "image": ".jpg", "video": ".mp4", "audio": ".mp3", "file": ".dat" };
|
|
607
|
-
const ext = el.attrs.file && path3.extname(el.attrs.file) ? path3.extname(el.attrs.file) : defaultExtMap[normalizedType] || ".dat";
|
|
608
|
-
const channelIdentifier = session.channelId || "private";
|
|
609
|
-
const fileName = `${newId}_${mediaIndex}_${channelIdentifier}_${session.userId}${ext}`;
|
|
610
|
-
mediaToSave.push({ sourceUrl: fileIdentifier, fileName });
|
|
611
|
-
fileIdentifier = fileName;
|
|
612
|
-
}
|
|
613
|
-
finalElementsForDb.push({ type: normalizedType, file: fileIdentifier });
|
|
614
|
-
} else if (normalizedType === "text" && el.attrs.content?.trim()) {
|
|
615
|
-
finalElementsForDb.push({ type: "text", content: el.attrs.content.trim() });
|
|
616
|
-
}
|
|
617
|
-
if (el.children) {
|
|
618
|
-
await traverseAndProcess(el.children);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
__name(traverseAndProcess, "traverseAndProcess");
|
|
623
|
-
await traverseAndProcess(sourceElements);
|
|
653
|
+
const { finalElementsForDb, mediaToSave } = await processMessageElements(
|
|
654
|
+
sourceElements,
|
|
655
|
+
newId,
|
|
656
|
+
session.channelId,
|
|
657
|
+
session.userId
|
|
658
|
+
);
|
|
624
659
|
if (finalElementsForDb.length === 0) return "内容为空,已取消添加";
|
|
625
660
|
const customNickname = config.enableProfile ? await profileManager.getNickname(session.userId) : null;
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
await
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
661
|
+
const userName = customNickname || session.username;
|
|
662
|
+
if (mediaToSave.length > 0) {
|
|
663
|
+
const newCave = {
|
|
664
|
+
id: newId,
|
|
665
|
+
elements: finalElementsForDb,
|
|
666
|
+
channelId: session.channelId,
|
|
667
|
+
userId: session.userId,
|
|
668
|
+
userName,
|
|
669
|
+
status: "preload",
|
|
670
|
+
time: /* @__PURE__ */ new Date()
|
|
671
|
+
};
|
|
672
|
+
await ctx.database.create("cave", newCave);
|
|
673
|
+
handleFileUploads(ctx, config, fileManager, logger, reviewManager, newCave, mediaToSave);
|
|
674
|
+
return config.enableReview ? `提交成功,序号为(${newId})` : `添加成功,序号为(${newId})`;
|
|
675
|
+
} else {
|
|
676
|
+
const finalStatus = config.enableReview ? "pending" : "active";
|
|
677
|
+
const newCave = {
|
|
678
|
+
id: newId,
|
|
679
|
+
elements: finalElementsForDb,
|
|
680
|
+
channelId: session.channelId,
|
|
681
|
+
userId: session.userId,
|
|
682
|
+
userName,
|
|
683
|
+
status: finalStatus,
|
|
684
|
+
time: /* @__PURE__ */ new Date()
|
|
685
|
+
};
|
|
686
|
+
await ctx.database.create("cave", newCave);
|
|
687
|
+
if (finalStatus === "pending") {
|
|
688
|
+
reviewManager.sendForReview(newCave);
|
|
689
|
+
return `提交成功,序号为(${newId})`;
|
|
690
|
+
}
|
|
691
|
+
return `添加成功,序号为(${newId})`;
|
|
649
692
|
}
|
|
650
|
-
return `添加成功,序号为(${newId})`;
|
|
651
693
|
} catch (error) {
|
|
652
694
|
logger.error("添加回声洞失败:", error);
|
|
653
695
|
return "添加失败,请稍后再试";
|
|
@@ -673,7 +715,8 @@ function apply(ctx, config) {
|
|
|
673
715
|
try {
|
|
674
716
|
const [targetCave] = await ctx.database.get("cave", { id, status: "active" });
|
|
675
717
|
if (!targetCave) return `回声洞(${id})不存在`;
|
|
676
|
-
|
|
718
|
+
const adminChannelId = config.adminChannel ? config.adminChannel.split(":")[1] : null;
|
|
719
|
+
if (targetCave.userId !== session.userId && session.channelId !== adminChannelId) {
|
|
677
720
|
return "你没有权限删除这条回声洞";
|
|
678
721
|
}
|
|
679
722
|
await ctx.database.upsert("cave", [{ id, status: "delete" }]);
|