koishi-plugin-best-cave 2.0.7 → 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.
@@ -3,8 +3,7 @@ import { FileManager } from './FileManager';
3
3
  import { Config } from './index';
4
4
  /**
5
5
  * @class DataManager
6
- * @description
7
- * 负责处理回声洞数据的导入和导出功能。
6
+ * @description 负责处理回声洞数据的导入和导出功能。
8
7
  */
9
8
  export declare class DataManager {
10
9
  private ctx;
@@ -2,8 +2,7 @@ import { Logger } from 'koishi';
2
2
  import { Config } from './index';
3
3
  /**
4
4
  * @class FileManager
5
- * @description
6
- * 封装了对文件的存储、读取和删除操作。
5
+ * @description 封装了对文件的存储、读取和删除操作。
7
6
  * 能根据配置自动选择使用本地文件系统或 AWS S3 作为存储后端。
8
7
  * 内置 Promise 文件锁,防止本地文件的并发写入冲突。
9
8
  */
@@ -15,8 +15,7 @@ declare module 'koishi' {
15
15
  }
16
16
  /**
17
17
  * @class ProfileManager
18
- * @description
19
- * 负责管理用户在回声洞中的自定义昵称。
18
+ * @description 负责管理用户在回声洞中的自定义昵称。
20
19
  * 当插件配置 `enableProfile` 为 true 时实例化。
21
20
  */
22
21
  export declare class ProfileManager {
@@ -1,11 +1,9 @@
1
- import { Context, h, Logger } from 'koishi';
1
+ import { Context, Logger } from 'koishi';
2
2
  import { CaveObject, Config } from './index';
3
3
  import { FileManager } from './FileManager';
4
4
  /**
5
5
  * @class ReviewManager
6
- * @description
7
- * 负责处理回声洞的审核流程。当 `enableReview` 配置开启时,
8
- * 此管理器将被激活,处理新洞的提交、审核通知和审核操作。
6
+ * @description 负责处理回声洞的审核流程,处理新洞的提交、审核通知和审核操作。
9
7
  */
10
8
  export declare class ReviewManager {
11
9
  private ctx;
@@ -26,7 +24,7 @@ export declare class ReviewManager {
26
24
  */
27
25
  registerCommands(cave: any): void;
28
26
  /**
29
- * @description 将新回声洞提交给管理员审核。
27
+ * @description 将新回声洞提交到管理群组以供审核。
30
28
  * @param cave 新创建的、状态为 'pending' 的回声洞对象。
31
29
  */
32
30
  sendForReview(cave: CaveObject): Promise<void>;
@@ -34,8 +32,7 @@ export declare class ReviewManager {
34
32
  * @description 处理管理员的审核决定(通过或拒绝)。
35
33
  * @param action 'approve' (通过) 或 'reject' (拒绝)。
36
34
  * @param caveId 被审核的回声洞 ID。
37
- * @param adminUserName 操作管理员的昵称。
38
35
  * @returns 返回给操作者的确认消息。
39
36
  */
40
- processReview(action: 'approve' | 'reject', caveId: number, adminUserName: string): Promise<string | (string | h)[]>;
37
+ processReview(action: 'approve' | 'reject', caveId: number): Promise<string>;
41
38
  }
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' {
@@ -30,7 +30,7 @@ declare module 'koishi' {
30
30
  export interface Config {
31
31
  coolDown: number;
32
32
  perChannel: boolean;
33
- adminUsers: string[];
33
+ adminChannel: string;
34
34
  enableProfile: boolean;
35
35
  enableIO: boolean;
36
36
  enableReview: boolean;
package/lib/index.js CHANGED
@@ -37,8 +37,7 @@ __export(index_exports, {
37
37
  usage: () => usage
38
38
  });
39
39
  module.exports = __toCommonJS(index_exports);
40
- var import_koishi2 = require("koishi");
41
- var path3 = __toESM(require("path"));
40
+ var import_koishi3 = require("koishi");
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");
@@ -296,7 +299,7 @@ async function getNextCaveId(ctx, query = {}) {
296
299
  }
297
300
  __name(getNextCaveId, "getNextCaveId");
298
301
  function checkCooldown(session, config, lastUsed) {
299
- if (config.coolDown <= 0 || !session.channelId || config.adminUsers.includes(session.userId)) return null;
302
+ if (config.coolDown <= 0 || !session.channelId) return null;
300
303
  const now = Date.now();
301
304
  const lastTime = lastUsed.get(session.channelId) || 0;
302
305
  if (now - lastTime < config.coolDown * 1e3) {
@@ -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 (!this.config.adminUsers.includes(session.userId)) 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 (!this.config.adminUsers.includes(session.userId)) 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,10 +458,8 @@ 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
- // 导入的数据直接设为活跃状态
397
463
  };
398
464
  await this.ctx.database.create("cave", newCave);
399
465
  successCount++;
@@ -403,6 +469,7 @@ var DataManager = class {
403
469
  };
404
470
 
405
471
  // src/ReviewManager.ts
472
+ var import_koishi2 = require("koishi");
406
473
  var ReviewManager = class {
407
474
  /**
408
475
  * @constructor
@@ -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 (!this.config.adminUsers.includes(session.userId)) 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 "当前没有需要审核的回声洞";
@@ -445,53 +513,45 @@ ${pendingCaves.map((c) => c.id).join(", ")}`;
445
513
  else if (["n", "no", "deny", "reject"].includes(normalizedAction)) reviewAction = "reject";
446
514
  else return `无效操作: "${action}"
447
515
  请使用 "Y" (通过) 或 "N" (拒绝)`;
448
- return this.processReview(reviewAction, id, session.username);
516
+ return this.processReview(reviewAction, id);
449
517
  });
450
518
  }
451
519
  /**
452
- * @description 将新回声洞提交给管理员审核。
520
+ * @description 将新回声洞提交到管理群组以供审核。
453
521
  * @param cave 新创建的、状态为 'pending' 的回声洞对象。
454
522
  */
455
523
  async sendForReview(cave) {
456
- if (!this.config.adminUsers?.length) {
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
  }
461
530
  const reviewMessage = [`待审核:`, ...await buildCaveMessage(cave, this.config, this.fileManager, this.logger)];
462
531
  try {
463
- await this.ctx.broadcast(this.config.adminUsers, reviewMessage);
532
+ await this.ctx.broadcast([this.config.adminChannel], import_koishi2.h.normalize(reviewMessage));
464
533
  } catch (error) {
465
- this.logger.error(`广播回声洞(${cave.id})审核请求失败:`, error);
534
+ this.logger.error(`发送回声洞(${cave.id})审核消息失败:`, error);
466
535
  }
467
536
  }
468
537
  /**
469
538
  * @description 处理管理员的审核决定(通过或拒绝)。
470
539
  * @param action 'approve' (通过) 或 'reject' (拒绝)。
471
540
  * @param caveId 被审核的回声洞 ID。
472
- * @param adminUserName 操作管理员的昵称。
473
541
  * @returns 返回给操作者的确认消息。
474
542
  */
475
- async processReview(action, caveId, adminUserName) {
543
+ async processReview(action, caveId) {
476
544
  const [cave] = await this.ctx.database.get("cave", { id: caveId, status: "pending" });
477
- if (!cave) return `回声洞(${caveId})不存在或无需审核`;
545
+ if (!cave) return `回声洞(${caveId})无需审核`;
478
546
  let resultMessage;
479
- let broadcastMessage;
480
547
  if (action === "approve") {
481
548
  await this.ctx.database.upsert("cave", [{ id: caveId, status: "active" }]);
482
549
  resultMessage = `回声洞(${caveId})已通过`;
483
- broadcastMessage = `回声洞(${caveId})已由管理员 "${adminUserName}" 通过`;
484
550
  } else {
485
551
  await this.ctx.database.upsert("cave", [{ id: caveId, status: "delete" }]);
486
552
  resultMessage = `回声洞(${caveId})已拒绝`;
487
- broadcastMessage = `回声洞(${caveId})已由管理员 "${adminUserName}" 拒绝`;
488
553
  cleanupPendingDeletions(this.ctx, this.fileManager, this.logger);
489
554
  }
490
- if (this.config.adminUsers?.length) {
491
- this.ctx.broadcast(this.config.adminUsers, broadcastMessage).catch((err) => {
492
- this.logger.error(`广播回声洞(${cave.id})审核结果失败:`, err);
493
- });
494
- }
495
555
  return resultMessage;
496
556
  }
497
557
  };
@@ -511,28 +571,28 @@ var usage = `
511
571
  <p>🐛 遇到问题?请通过 <strong>Issues</strong> 提交反馈,或加入 QQ 群 <a href="https://qm.qq.com/q/PdLMx9Jowq" style="color:#e0574a;text-decoration:none;"><strong>855571375</strong></a> 进行交流</p>
512
572
  </div>
513
573
  `;
514
- var logger = new import_koishi2.Logger("best-cave");
515
- var Config = import_koishi2.Schema.intersect([
516
- import_koishi2.Schema.object({
517
- coolDown: import_koishi2.Schema.number().default(10).description("冷却时间(秒)"),
518
- perChannel: import_koishi2.Schema.boolean().default(false).description("启用分群模式"),
519
- enableProfile: import_koishi2.Schema.boolean().default(false).description("启用自定义昵称"),
520
- enableIO: import_koishi2.Schema.boolean().default(false).description("启用导入导出"),
521
- caveFormat: import_koishi2.Schema.string().default("回声洞 ——({id})|—— {name}").description("自定义文本"),
522
- adminUsers: import_koishi2.Schema.array(import_koishi2.Schema.string()).default([]).description("管理员 ID 列表")
574
+ var logger = new import_koishi3.Logger("best-cave");
575
+ var Config = import_koishi3.Schema.intersect([
576
+ import_koishi3.Schema.object({
577
+ coolDown: import_koishi3.Schema.number().default(10).description("冷却时间(秒)"),
578
+ perChannel: import_koishi3.Schema.boolean().default(false).description("启用分群模式"),
579
+ enableProfile: import_koishi3.Schema.boolean().default(false).description("启用自定义昵称"),
580
+ enableIO: import_koishi3.Schema.boolean().default(false).description("启用导入导出"),
581
+ adminChannel: import_koishi3.Schema.string().default("onebot:").description("管理群组 ID"),
582
+ caveFormat: import_koishi3.Schema.string().default("回声洞 ——({id})|—— {name}").description("自定义文本")
523
583
  }).description("基础配置"),
524
- import_koishi2.Schema.object({
525
- enableReview: import_koishi2.Schema.boolean().default(false).description("启用审核")
584
+ import_koishi3.Schema.object({
585
+ enableReview: import_koishi3.Schema.boolean().default(false).description("启用审核")
526
586
  }).description("审核配置"),
527
- import_koishi2.Schema.object({
528
- localPath: import_koishi2.Schema.string().description("文件映射路径"),
529
- enableS3: import_koishi2.Schema.boolean().default(false).description("启用 S3 存储"),
530
- publicUrl: import_koishi2.Schema.string().description("公共访问 URL").role("link"),
531
- endpoint: import_koishi2.Schema.string().description("端点 (Endpoint)").role("link"),
532
- bucket: import_koishi2.Schema.string().description("存储桶 (Bucket)"),
533
- region: import_koishi2.Schema.string().default("auto").description("区域 (Region)"),
534
- accessKeyId: import_koishi2.Schema.string().description("Access Key ID").role("secret"),
535
- secretAccessKey: import_koishi2.Schema.string().description("Secret Access Key").role("secret")
587
+ import_koishi3.Schema.object({
588
+ localPath: import_koishi3.Schema.string().description("文件映射路径"),
589
+ enableS3: import_koishi3.Schema.boolean().default(false).description("启用 S3 存储"),
590
+ publicUrl: import_koishi3.Schema.string().description("公共访问 URL").role("link"),
591
+ endpoint: import_koishi3.Schema.string().description("端点 (Endpoint)").role("link"),
592
+ bucket: import_koishi3.Schema.string().description("存储桶 (Bucket)"),
593
+ region: import_koishi3.Schema.string().default("auto").description("区域 (Region)"),
594
+ accessKeyId: import_koishi3.Schema.string().description("Access Key ID").role("secret"),
595
+ secretAccessKey: import_koishi3.Schema.string().description("Secret Access Key").role("secret")
536
596
  }).description("存储配置")
537
597
  ]);
538
598
  function apply(ctx, config) {
@@ -578,85 +638,58 @@ function apply(ctx, config) {
578
638
  if (session.quote?.elements) {
579
639
  sourceElements = session.quote.elements;
580
640
  } else if (content?.trim()) {
581
- sourceElements = import_koishi2.h.parse(content);
641
+ sourceElements = import_koishi3.h.parse(content);
582
642
  } else {
583
643
  await session.send("请在一分钟内发送你要添加的内容");
584
644
  const reply = await session.prompt(6e4);
585
645
  if (!reply) return "操作超时,已取消添加";
586
- sourceElements = import_koishi2.h.parse(reply);
646
+ sourceElements = import_koishi3.h.parse(reply);
587
647
  }
588
648
  const idScopeQuery = {};
589
649
  if (config.perChannel && session.channelId) {
590
650
  idScopeQuery["channelId"] = session.channelId;
591
651
  }
592
652
  const newId = await getNextCaveId(ctx, idScopeQuery);
593
- const finalElementsForDb = [];
594
- const mediaToSave = [];
595
- let mediaIndex = 0;
596
- const typeMap = {
597
- "img": "image",
598
- "image": "image",
599
- "video": "video",
600
- "audio": "audio",
601
- "file": "file",
602
- "text": "text"
603
- };
604
- async function traverseAndProcess(elements) {
605
- for (const el of elements) {
606
- const normalizedType = typeMap[el.type];
607
- if (!normalizedType) {
608
- if (el.children) await traverseAndProcess(el.children);
609
- continue;
610
- }
611
- if (["image", "video", "audio", "file"].includes(normalizedType) && el.attrs.src) {
612
- let fileIdentifier = el.attrs.src;
613
- if (fileIdentifier.startsWith("http")) {
614
- mediaIndex++;
615
- const defaultExtMap = { "image": ".jpg", "video": ".mp4", "audio": ".mp3", "file": ".dat" };
616
- const ext = el.attrs.file && path3.extname(el.attrs.file) ? path3.extname(el.attrs.file) : defaultExtMap[normalizedType] || ".dat";
617
- const channelIdentifier = session.channelId || "private";
618
- const fileName = `${newId}_${mediaIndex}_${channelIdentifier}_${session.userId}${ext}`;
619
- mediaToSave.push({ sourceUrl: fileIdentifier, fileName });
620
- fileIdentifier = fileName;
621
- }
622
- finalElementsForDb.push({ type: normalizedType, file: fileIdentifier });
623
- } else if (normalizedType === "text" && el.attrs.content?.trim()) {
624
- finalElementsForDb.push({ type: "text", content: el.attrs.content.trim() });
625
- }
626
- if (el.children) {
627
- await traverseAndProcess(el.children);
628
- }
629
- }
630
- }
631
- __name(traverseAndProcess, "traverseAndProcess");
632
- await traverseAndProcess(sourceElements);
653
+ const { finalElementsForDb, mediaToSave } = await processMessageElements(
654
+ sourceElements,
655
+ newId,
656
+ session.channelId,
657
+ session.userId
658
+ );
633
659
  if (finalElementsForDb.length === 0) return "内容为空,已取消添加";
634
660
  const customNickname = config.enableProfile ? await profileManager.getNickname(session.userId) : null;
635
- const newCave = {
636
- id: newId,
637
- elements: finalElementsForDb,
638
- channelId: session.channelId,
639
- userId: session.userId,
640
- userName: customNickname || session.username,
641
- status: config.enableReview ? "pending" : "active",
642
- time: /* @__PURE__ */ new Date()
643
- };
644
- await ctx.database.create("cave", newCave);
645
- try {
646
- await Promise.all(mediaToSave.map(async (media) => {
647
- const response = await ctx.http.get(media.sourceUrl, { responseType: "arraybuffer", timeout: 3e4 });
648
- await fileManager.saveFile(media.fileName, Buffer.from(response));
649
- }));
650
- } catch (fileSaveError) {
651
- logger.error(`文件保存失败:`, fileSaveError);
652
- await ctx.database.remove("cave", { id: newId });
653
- throw fileSaveError;
654
- }
655
- if (newCave.status === "pending") {
656
- reviewManager.sendForReview(newCave);
657
- 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})`;
658
692
  }
659
- return `添加成功,序号为(${newId})`;
660
693
  } catch (error) {
661
694
  logger.error("添加回声洞失败:", error);
662
695
  return "添加失败,请稍后再试";
@@ -682,8 +715,9 @@ function apply(ctx, config) {
682
715
  try {
683
716
  const [targetCave] = await ctx.database.get("cave", { id, status: "active" });
684
717
  if (!targetCave) return `回声洞(${id})不存在`;
685
- if (targetCave.userId !== session.userId && !config.adminUsers.includes(session.userId)) {
686
- return "抱歉,你没有权限删除这条回声洞";
718
+ const adminChannelId = config.adminChannel ? config.adminChannel.split(":")[1] : null;
719
+ if (targetCave.userId !== session.userId && session.channelId !== adminChannelId) {
720
+ return "你没有权限删除这条回声洞";
687
721
  }
688
722
  await ctx.database.upsert("cave", [{ id, status: "delete" }]);
689
723
  const caveMessage = await buildCaveMessage(targetCave, config, fileManager, logger);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "最强大的回声洞现已重构完成啦!注意数据格式需要使用脚本转换哦~",
4
- "version": "2.0.7",
4
+ "version": "2.0.9",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],