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 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
@@ -19,7 +19,7 @@ export interface CaveObject {
19
19
  channelId: string;
20
20
  userId: string;
21
21
  userName: string;
22
- status: 'active' | 'delete' | 'pending';
22
+ status: 'active' | 'delete' | 'pending' | 'preload';
23
23
  time: Date;
24
24
  }
25
25
  declare module 'koishi' {
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()) finalMessage.push(headerText);
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()) finalMessage.push(footerText);
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
- if (session.channelId !== this.config.adminChannel) return "此指令仅限在管理群组中使用";
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
- if (session.channelId !== this.config.adminChannel) return "此指令仅限在管理群组中使用";
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 || null,
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
- if (session.channelId !== this.config.adminChannel) return "此指令仅限在管理群组中使用";
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
- if (!this.config.adminChannel) {
457
- this.logger.warn(`未配置管理群组,已自动通过回声洞(${cave.id})`);
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
- const mediaToSave = [];
586
- let mediaIndex = 0;
587
- const typeMap = {
588
- "img": "image",
589
- "image": "image",
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 newCave = {
627
- id: newId,
628
- elements: finalElementsForDb,
629
- channelId: session.channelId,
630
- userId: session.userId,
631
- userName: customNickname || session.username,
632
- status: config.enableReview ? "pending" : "active",
633
- time: /* @__PURE__ */ new Date()
634
- };
635
- await ctx.database.create("cave", newCave);
636
- try {
637
- await Promise.all(mediaToSave.map(async (media) => {
638
- const response = await ctx.http.get(media.sourceUrl, { responseType: "arraybuffer", timeout: 3e4 });
639
- await fileManager.saveFile(media.fileName, Buffer.from(response));
640
- }));
641
- } catch (fileSaveError) {
642
- logger.error(`文件保存失败:`, fileSaveError);
643
- await ctx.database.remove("cave", { id: newId });
644
- throw fileSaveError;
645
- }
646
- if (newCave.status === "pending") {
647
- reviewManager.sendForReview(newCave);
648
- return `提交成功,序号为(${newCave.id})`;
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
- if (targetCave.userId !== session.userId && session.channelId !== config.adminChannel) {
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" }]);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "最强大的回声洞现已重构完成啦!注意数据格式需要使用脚本转换哦~",
4
- "version": "2.0.8",
4
+ "version": "2.0.9",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],