koishi-plugin-best-cave 2.1.1 → 2.1.2

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.
@@ -0,0 +1,38 @@
1
+ import { Context, Logger } from 'koishi';
2
+ import { FileManager } from './FileManager';
3
+ import { Config } from './index';
4
+ /**
5
+ * @class DataManager
6
+ * @description 负责处理回声洞数据的导入和导出功能。
7
+ */
8
+ export declare class DataManager {
9
+ private ctx;
10
+ private config;
11
+ private fileManager;
12
+ private logger;
13
+ private reusableIds;
14
+ /**
15
+ * @constructor
16
+ * @param ctx Koishi 上下文,用于数据库操作。
17
+ * @param config 插件配置。
18
+ * @param fileManager 文件管理器实例。
19
+ * @param logger 日志记录器实例。
20
+ * @param reusableIds 可复用 ID 的内存缓存。
21
+ */
22
+ constructor(ctx: Context, config: Config, fileManager: FileManager, logger: Logger, reusableIds: Set<number>);
23
+ /**
24
+ * @description 注册 `.export` 和 `.import` 子命令。
25
+ * @param cave - 主 `cave` 命令实例。
26
+ */
27
+ registerCommands(cave: any): void;
28
+ /**
29
+ * @description 导出所有 'active' 状态的回声洞数据到 `cave_export.json`。
30
+ * @returns 描述导出结果的消息字符串。
31
+ */
32
+ exportData(): Promise<string>;
33
+ /**
34
+ * @description 从 `cave_import.json` 文件导入回声洞数据。
35
+ * @returns 描述导入结果的消息字符串。
36
+ */
37
+ importData(): Promise<string>;
38
+ }
@@ -0,0 +1,40 @@
1
+ import { Context, Logger } from 'koishi';
2
+ import { CaveObject, Config } from './index';
3
+ import { FileManager } from './FileManager';
4
+ /**
5
+ * @class ReviewManager
6
+ * @description 负责处理回声洞的审核流程,处理新洞的提交、审核通知和审核操作。
7
+ */
8
+ export declare class ReviewManager {
9
+ private ctx;
10
+ private config;
11
+ private fileManager;
12
+ private logger;
13
+ private reusableIds;
14
+ /**
15
+ * @constructor
16
+ * @param ctx Koishi 上下文。
17
+ * @param config 插件配置。
18
+ * @param fileManager 文件管理器实例。
19
+ * @param logger 日志记录器实例。
20
+ * @param reusableIds 可复用 ID 的内存缓存。
21
+ */
22
+ constructor(ctx: Context, config: Config, fileManager: FileManager, logger: Logger, reusableIds: Set<number>);
23
+ /**
24
+ * @description 注册与审核相关的子命令。
25
+ * @param cave - 主 `cave` 命令实例。
26
+ */
27
+ registerCommands(cave: any): void;
28
+ /**
29
+ * @description 将新回声洞提交到管理群组以供审核。
30
+ * @param cave 新创建的、状态为 'pending' 的回声洞对象。
31
+ */
32
+ sendForReview(cave: CaveObject): Promise<void>;
33
+ /**
34
+ * @description 处理管理员的审核决定(通过或拒绝)。
35
+ * @param action 'approve' (通过) 或 'reject' (拒绝)。
36
+ * @param caveId 被审核的回声洞 ID。
37
+ * @returns 返回给操作者的确认消息。
38
+ */
39
+ processReview(action: 'approve' | 'reject', caveId: number): Promise<string>;
40
+ }
package/lib/Utils.d.ts CHANGED
@@ -22,8 +22,9 @@ export declare function buildCaveMessage(cave: CaveObject, config: Config, fileM
22
22
  * @param ctx Koishi 上下文。
23
23
  * @param fileManager FileManager 实例。
24
24
  * @param logger Logger 实例。
25
+ * @param reusableIds 可复用 ID 的内存缓存。
25
26
  */
26
- export declare function cleanupPendingDeletions(ctx: Context, fileManager: FileManager, logger: Logger): Promise<void>;
27
+ export declare function cleanupPendingDeletions(ctx: Context, fileManager: FileManager, logger: Logger, reusableIds: Set<number>): Promise<void>;
27
28
  /**
28
29
  * @description 根据配置(是否分群)和当前会话,生成数据库查询的范围条件。
29
30
  * @param session 当前会话对象。
@@ -32,13 +33,14 @@ export declare function cleanupPendingDeletions(ctx: Context, fileManager: FileM
32
33
  */
33
34
  export declare function getScopeQuery(session: Session, config: Config): object;
34
35
  /**
35
- * @description 获取下一个可用的回声洞 ID(最小的未使用正整数)。
36
+ * @description 获取下一个可用的回声洞 ID
37
+ * 实现了三阶段逻辑:优先使用回收ID -> 扫描空闲ID -> 获取最大ID+1。
36
38
  * @param ctx Koishi 上下文。
37
39
  * @param query 查询范围条件,用于分群模式。
40
+ * @param reusableIds 可复用 ID 的内存缓存。
38
41
  * @returns 可用的新 ID。
39
- * @performance 在大数据集下,此函数可能存在性能瓶颈,因为它需要获取所有现有ID。
40
42
  */
41
- export declare function getNextCaveId(ctx: Context, query?: object): Promise<number>;
43
+ export declare function getNextCaveId(ctx: Context, query: object, reusableIds: Set<number>): Promise<number>;
42
44
  /**
43
45
  * @description 检查用户是否处于指令冷却中。
44
46
  * @returns 若在冷却中则返回提示字符串,否则返回 null。
@@ -72,8 +74,9 @@ export declare function processMessageElements(sourceElements: h[], newId: numbe
72
74
  * @param reviewManager - 审核管理器实例 (可能为 null)。
73
75
  * @param cave - 已创建的、状态为 'preload' 的回声洞对象。
74
76
  * @param mediaToSave - 需要下载和保存的媒体文件列表。
77
+ * @param reusableIds 可复用 ID 的内存缓存。
75
78
  */
76
79
  export declare function handleFileUploads(ctx: Context, config: Config, fileManager: FileManager, logger: Logger, reviewManager: any, cave: CaveObject, mediaToSave: {
77
80
  sourceUrl: string;
78
81
  fileName: string;
79
- }[]): Promise<void>;
82
+ }[], reusableIds: Set<number>): Promise<void>;
package/lib/index.js CHANGED
@@ -212,10 +212,98 @@ var ProfileManager = class {
212
212
  }
213
213
  };
214
214
 
215
+ // src/DataManager.ts
216
+ var DataManager = class {
217
+ /**
218
+ * @constructor
219
+ * @param ctx Koishi 上下文,用于数据库操作。
220
+ * @param config 插件配置。
221
+ * @param fileManager 文件管理器实例。
222
+ * @param logger 日志记录器实例。
223
+ * @param reusableIds 可复用 ID 的内存缓存。
224
+ */
225
+ constructor(ctx, config, fileManager, logger2, reusableIds) {
226
+ this.ctx = ctx;
227
+ this.config = config;
228
+ this.fileManager = fileManager;
229
+ this.logger = logger2;
230
+ this.reusableIds = reusableIds;
231
+ }
232
+ static {
233
+ __name(this, "DataManager");
234
+ }
235
+ /**
236
+ * @description 注册 `.export` 和 `.import` 子命令。
237
+ * @param cave - 主 `cave` 命令实例。
238
+ */
239
+ registerCommands(cave) {
240
+ const requireAdmin = /* @__PURE__ */ __name((action) => async ({ session }) => {
241
+ const adminChannelId = this.config.adminChannel?.split(":")[1];
242
+ if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
243
+ try {
244
+ await session.send("正在处理,请稍候...");
245
+ return await action();
246
+ } catch (error) {
247
+ this.logger.error("数据操作时发生错误:", error);
248
+ return `操作失败: ${error.message}`;
249
+ }
250
+ }, "requireAdmin");
251
+ cave.subcommand(".export", "导出回声洞数据").usage("将所有回声洞数据导出到 cave_export.json。").action(requireAdmin(() => this.exportData()));
252
+ cave.subcommand(".import", "导入回声洞数据").usage("从 cave_import.json 中导入回声洞数据。").action(requireAdmin(() => this.importData()));
253
+ }
254
+ /**
255
+ * @description 导出所有 'active' 状态的回声洞数据到 `cave_export.json`。
256
+ * @returns 描述导出结果的消息字符串。
257
+ */
258
+ async exportData() {
259
+ const fileName = "cave_export.json";
260
+ const cavesToExport = await this.ctx.database.get("cave", { status: "active" });
261
+ const portableCaves = cavesToExport.map(({ id, ...rest }) => rest);
262
+ const data = JSON.stringify(portableCaves, null, 2);
263
+ await this.fileManager.saveFile(fileName, Buffer.from(data));
264
+ return `成功导出 ${portableCaves.length} 条数据`;
265
+ }
266
+ /**
267
+ * @description 从 `cave_import.json` 文件导入回声洞数据。
268
+ * @returns 描述导入结果的消息字符串。
269
+ */
270
+ async importData() {
271
+ const fileName = "cave_import.json";
272
+ let importedCaves;
273
+ try {
274
+ const fileContent = await this.fileManager.readFile(fileName);
275
+ importedCaves = JSON.parse(fileContent.toString("utf-8"));
276
+ if (!Array.isArray(importedCaves)) throw new Error("文件格式无效");
277
+ if (!importedCaves.length) throw new Error("导入文件为空");
278
+ } catch (error) {
279
+ this.logger.error(`读取导入文件失败:`, error);
280
+ throw new Error(`读取导入文件失败: ${error.message}`);
281
+ }
282
+ const [lastCave] = await this.ctx.database.get("cave", {}, {
283
+ sort: { id: "desc" },
284
+ limit: 1,
285
+ fields: ["id"]
286
+ });
287
+ let startId = (lastCave?.id || 0) + 1;
288
+ const newCavesToInsert = importedCaves.map((cave, index) => ({
289
+ ...cave,
290
+ id: startId + index,
291
+ status: "active"
292
+ }));
293
+ await this.ctx.database.upsert("cave", newCavesToInsert);
294
+ this.reusableIds.clear();
295
+ return `成功导入 ${newCavesToInsert.length} 条数据`;
296
+ }
297
+ };
298
+
299
+ // src/ReviewManager.ts
300
+ var import_koishi2 = require("koishi");
301
+
215
302
  // src/Utils.ts
216
303
  var import_koishi = require("koishi");
217
304
  var path2 = __toESM(require("path"));
218
305
  var mimeTypeMap = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".mp4": "video/mp4", ".mp3": "audio/mpeg", ".webp": "image/webp" };
306
+ var MAX_ID_FLAG = 0;
219
307
  function storedFormatToHElements(elements) {
220
308
  return elements.map((el) => {
221
309
  if (el.type === "text") return import_koishi.h.text(el.content);
@@ -256,13 +344,15 @@ async function buildCaveMessage(cave, config, fileManager, logger2) {
256
344
  return finalMessage;
257
345
  }
258
346
  __name(buildCaveMessage, "buildCaveMessage");
259
- async function cleanupPendingDeletions(ctx, fileManager, logger2) {
347
+ async function cleanupPendingDeletions(ctx, fileManager, logger2, reusableIds) {
260
348
  try {
261
349
  const cavesToDelete = await ctx.database.get("cave", { status: "delete" });
262
350
  if (!cavesToDelete.length) return;
263
351
  for (const cave of cavesToDelete) {
264
352
  const deletePromises = cave.elements.filter((el) => el.file).map((el) => fileManager.deleteFile(el.file));
265
353
  await Promise.all(deletePromises);
354
+ reusableIds.add(cave.id);
355
+ reusableIds.delete(MAX_ID_FLAG);
266
356
  await ctx.database.remove("cave", { id: cave.id });
267
357
  }
268
358
  } catch (error) {
@@ -275,13 +365,34 @@ function getScopeQuery(session, config) {
275
365
  return config.perChannel && session.channelId ? { ...baseQuery, channelId: session.channelId } : baseQuery;
276
366
  }
277
367
  __name(getScopeQuery, "getScopeQuery");
278
- async function getNextCaveId(ctx, query = {}) {
368
+ async function getNextCaveId(ctx, query = {}, reusableIds) {
369
+ for (const id of reusableIds) {
370
+ if (id > MAX_ID_FLAG) {
371
+ reusableIds.delete(id);
372
+ return id;
373
+ }
374
+ }
375
+ if (reusableIds.has(MAX_ID_FLAG)) {
376
+ reusableIds.delete(MAX_ID_FLAG);
377
+ const [lastCave] = await ctx.database.get("cave", query, {
378
+ fields: ["id"],
379
+ sort: { id: "desc" },
380
+ limit: 1
381
+ });
382
+ const newId2 = (lastCave?.id || 0) + 1;
383
+ reusableIds.add(MAX_ID_FLAG);
384
+ return newId2;
385
+ }
279
386
  const allCaveIds = (await ctx.database.get("cave", query, { fields: ["id"] })).map((c) => c.id);
280
387
  const existingIds = new Set(allCaveIds);
281
388
  let newId = 1;
282
389
  while (existingIds.has(newId)) {
283
390
  newId++;
284
391
  }
392
+ const maxIdInDb = allCaveIds.length > 0 ? Math.max(...allCaveIds) : 0;
393
+ if (existingIds.size === maxIdInDb) {
394
+ reusableIds.add(MAX_ID_FLAG);
395
+ }
285
396
  return newId;
286
397
  }
287
398
  __name(getNextCaveId, "getNextCaveId");
@@ -339,7 +450,7 @@ async function processMessageElements(sourceElements, newId, channelId, userId)
339
450
  return { finalElementsForDb, mediaToSave };
340
451
  }
341
452
  __name(processMessageElements, "processMessageElements");
342
- async function handleFileUploads(ctx, config, fileManager, logger2, reviewManager, cave, mediaToSave) {
453
+ async function handleFileUploads(ctx, config, fileManager, logger2, reviewManager, cave, mediaToSave, reusableIds) {
343
454
  try {
344
455
  const uploadPromises = mediaToSave.map(async (media) => {
345
456
  const response = await ctx.http.get(media.sourceUrl, { responseType: "arraybuffer", timeout: 3e4 });
@@ -355,88 +466,12 @@ async function handleFileUploads(ctx, config, fileManager, logger2, reviewManage
355
466
  } catch (fileSaveError) {
356
467
  logger2.error(`回声洞(${cave.id})文件保存失败:`, fileSaveError);
357
468
  await ctx.database.upsert("cave", [{ id: cave.id, status: "delete" }]);
358
- cleanupPendingDeletions(ctx, fileManager, logger2);
469
+ cleanupPendingDeletions(ctx, fileManager, logger2, reusableIds);
359
470
  }
360
471
  }
361
472
  __name(handleFileUploads, "handleFileUploads");
362
473
 
363
- // src/DataManager.ts
364
- var DataManager = class {
365
- /**
366
- * @constructor
367
- * @param ctx Koishi 上下文,用于数据库操作。
368
- * @param config 插件配置。
369
- * @param fileManager 文件管理器实例。
370
- * @param logger 日志记录器实例。
371
- */
372
- constructor(ctx, config, fileManager, logger2) {
373
- this.ctx = ctx;
374
- this.config = config;
375
- this.fileManager = fileManager;
376
- this.logger = logger2;
377
- }
378
- static {
379
- __name(this, "DataManager");
380
- }
381
- /**
382
- * @description 注册 `.export` 和 `.import` 子命令。
383
- * @param cave - 主 `cave` 命令实例。
384
- */
385
- registerCommands(cave) {
386
- const requireAdmin = /* @__PURE__ */ __name((action) => async ({ session }) => {
387
- const adminChannelId = this.config.adminChannel?.split(":")[1];
388
- if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
389
- try {
390
- await session.send("正在处理,请稍候...");
391
- return await action();
392
- } catch (error) {
393
- this.logger.error("数据操作时发生错误:", error);
394
- return `操作失败: ${error.message || "未知错误"}`;
395
- }
396
- }, "requireAdmin");
397
- cave.subcommand(".export", "导出回声洞数据").usage("将所有回声洞数据导出到 cave_export.json。").action(requireAdmin(() => this.exportData()));
398
- cave.subcommand(".import", "导入回声洞数据").usage("从 cave_import.json 中导入回声洞数据。").action(requireAdmin(() => this.importData()));
399
- }
400
- /**
401
- * @description 导出所有 'active' 状态的回声洞数据到 `cave_export.json`。
402
- * @returns 描述导出结果的消息字符串。
403
- */
404
- async exportData() {
405
- const fileName = "cave_export.json";
406
- const cavesToExport = await this.ctx.database.get("cave", { status: "active" });
407
- const portableCaves = cavesToExport.map(({ id, ...rest }) => rest);
408
- const data = JSON.stringify(portableCaves, null, 2);
409
- await this.fileManager.saveFile(fileName, Buffer.from(data));
410
- return `成功导出 ${portableCaves.length} 条数据`;
411
- }
412
- /**
413
- * @description 从 `cave_import.json` 文件导入回声洞数据。
414
- * @returns 描述导入结果的消息字符串。
415
- */
416
- async importData() {
417
- const fileName = "cave_import.json";
418
- let importedCaves;
419
- try {
420
- const fileContent = await this.fileManager.readFile(fileName);
421
- importedCaves = JSON.parse(fileContent.toString("utf-8"));
422
- if (!Array.isArray(importedCaves)) throw new Error("导入文件格式无效");
423
- } catch (error) {
424
- this.logger.error(`读取导入文件失败:`, error);
425
- throw new Error(`读取导入文件失败: ${error.message || "未知错误"}`);
426
- }
427
- let successCount = 0;
428
- for (const cave of importedCaves) {
429
- const newId = await getNextCaveId(this.ctx, {});
430
- const newCave = { ...cave, id: newId, status: "active" };
431
- await this.ctx.database.create("cave", newCave);
432
- successCount++;
433
- }
434
- return `成功导入 ${successCount} 条回声洞数据`;
435
- }
436
- };
437
-
438
474
  // src/ReviewManager.ts
439
- var import_koishi2 = require("koishi");
440
475
  var ReviewManager = class {
441
476
  /**
442
477
  * @constructor
@@ -444,46 +479,69 @@ var ReviewManager = class {
444
479
  * @param config 插件配置。
445
480
  * @param fileManager 文件管理器实例。
446
481
  * @param logger 日志记录器实例。
482
+ * @param reusableIds 可复用 ID 的内存缓存。
447
483
  */
448
- constructor(ctx, config, fileManager, logger2) {
484
+ constructor(ctx, config, fileManager, logger2, reusableIds) {
449
485
  this.ctx = ctx;
450
486
  this.config = config;
451
487
  this.fileManager = fileManager;
452
488
  this.logger = logger2;
489
+ this.reusableIds = reusableIds;
453
490
  }
454
491
  static {
455
492
  __name(this, "ReviewManager");
456
493
  }
457
494
  /**
458
- * @description 注册与审核相关的 `.review` 子命令。
495
+ * @description 注册与审核相关的子命令。
459
496
  * @param cave - 主 `cave` 命令实例。
460
497
  */
461
498
  registerCommands(cave) {
462
- cave.subcommand(".review [id:posint] [action:string]", "审核回声洞").usage("查看或审核回声洞,使用 <Y/N> 进行审核。").action(async ({ session }, id, action) => {
499
+ const requireAdmin = /* @__PURE__ */ __name((session) => {
463
500
  const adminChannelId = this.config.adminChannel?.split(":")[1];
464
- if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
501
+ if (session.channelId !== adminChannelId) {
502
+ return "此指令仅限在管理群组中使用";
503
+ }
504
+ return null;
505
+ }, "requireAdmin");
506
+ const review = cave.subcommand(".review [id:posint]", "审核回声洞").usage("查看所有待审核回声洞,或查看指定待审核回声洞。").action(async ({ session }, id) => {
507
+ const adminError = requireAdmin(session);
508
+ if (adminError) return adminError;
465
509
  if (!id) {
466
510
  const pendingCaves = await this.ctx.database.get("cave", { status: "pending" }, { fields: ["id"] });
467
511
  if (!pendingCaves.length) return "当前没有需要审核的回声洞";
468
512
  return `当前共有 ${pendingCaves.length} 条待审核回声洞,序号为:
469
- ${pendingCaves.map((c) => c.id).join(", ")}`;
513
+ ${pendingCaves.map((c) => c.id).join("|")}`;
470
514
  }
471
515
  const [targetCave] = await this.ctx.database.get("cave", { id });
472
516
  if (!targetCave) return `回声洞(${id})不存在`;
473
517
  if (targetCave.status !== "pending") return `回声洞(${id})无需审核`;
474
- if (!action) {
475
- return [`待审核:`, ...await buildCaveMessage(targetCave, this.config, this.fileManager, this.logger)];
476
- }
477
- const normalizedAction = action.toLowerCase();
478
- if (["y", "yes", "pass", "approve"].includes(normalizedAction)) {
479
- return this.processReview("approve", id);
480
- }
481
- if (["n", "no", "deny", "reject"].includes(normalizedAction)) {
482
- return this.processReview("reject", id);
483
- }
484
- return `无效操作: "${action}"
485
- 请使用 "Y" (通过) 或 "N" (拒绝)`;
518
+ return [`待审核:`, ...await buildCaveMessage(targetCave, this.config, this.fileManager, this.logger)];
486
519
  });
520
+ const createReviewAction = /* @__PURE__ */ __name((actionType) => async ({ session }, id) => {
521
+ const adminError = requireAdmin(session);
522
+ if (adminError) return adminError;
523
+ await session.send("正在处理,请稍候...");
524
+ try {
525
+ if (!id) {
526
+ const pendingCaves = await this.ctx.database.get("cave", { status: "pending" });
527
+ if (!pendingCaves.length) return `当前没有需要${actionType === "approve" ? "通过" : "拒绝"}的回声洞`;
528
+ if (actionType === "approve") {
529
+ await this.ctx.database.upsert("cave", pendingCaves.map((c) => ({ id: c.id, status: "active" })));
530
+ return `已通过 ${pendingCaves.length} 条回声洞`;
531
+ } else {
532
+ await this.ctx.database.upsert("cave", pendingCaves.map((c) => ({ id: c.id, status: "delete" })));
533
+ cleanupPendingDeletions(this.ctx, this.fileManager, this.logger, this.reusableIds);
534
+ return `已拒绝 ${pendingCaves.length} 条回声洞`;
535
+ }
536
+ }
537
+ return this.processReview(actionType, id);
538
+ } catch (error) {
539
+ this.logger.error(`审核操作失败:`, error);
540
+ return `操作失败: ${error.message}`;
541
+ }
542
+ }, "createReviewAction");
543
+ review.subcommand(".Y [id:posint]", "通过审核").usage("通过回声洞审核,可批量操作。").action(createReviewAction("approve"));
544
+ review.subcommand(".N [id:posint]", "拒绝审核").usage("拒绝回声洞审核,可批量操作。").action(createReviewAction("reject"));
487
545
  }
488
546
  /**
489
547
  * @description 将新回声洞提交到管理群组以供审核。
@@ -516,7 +574,7 @@ ${pendingCaves.map((c) => c.id).join(", ")}`;
516
574
  return `回声洞(${caveId})已通过`;
517
575
  } else {
518
576
  await this.ctx.database.upsert("cave", [{ id: caveId, status: "delete" }]);
519
- cleanupPendingDeletions(this.ctx, this.fileManager, this.logger);
577
+ cleanupPendingDeletions(this.ctx, this.fileManager, this.logger, this.reusableIds);
520
578
  return `回声洞(${caveId})已拒绝`;
521
579
  }
522
580
  }
@@ -573,9 +631,10 @@ function apply(ctx, config) {
573
631
  }, { primary: "id" });
574
632
  const fileManager = new FileManager(ctx.baseDir, config, logger);
575
633
  const lastUsed = /* @__PURE__ */ new Map();
634
+ const reusableIds = /* @__PURE__ */ new Set();
576
635
  const profileManager = config.enableProfile ? new ProfileManager(ctx) : null;
577
- const dataManager = config.enableIO ? new DataManager(ctx, config, fileManager, logger) : null;
578
- const reviewManager = config.enableReview ? new ReviewManager(ctx, config, fileManager, logger) : null;
636
+ const dataManager = config.enableIO ? new DataManager(ctx, config, fileManager, logger, reusableIds) : null;
637
+ const reviewManager = config.enableReview ? new ReviewManager(ctx, config, fileManager, logger, reusableIds) : null;
579
638
  const cave = ctx.command("cave", "回声洞").option("add", "-a <content:text> 添加回声洞").option("view", "-g <id:posint> 查看指定回声洞").option("delete", "-r <id:posint> 删除指定回声洞").option("list", "-l 查询投稿统计").usage("随机抽取一条已添加的回声洞。").action(async ({ session, options }) => {
580
639
  if (options.add) return session.execute(`cave.add ${options.add}`);
581
640
  if (options.view) return session.execute(`cave.view ${options.view}`);
@@ -611,14 +670,16 @@ function apply(ctx, config) {
611
670
  sourceElements = import_koishi3.h.parse(reply);
612
671
  }
613
672
  const idScopeQuery = config.perChannel && session.channelId ? { channelId: session.channelId } : {};
614
- const newId = await getNextCaveId(ctx, idScopeQuery);
673
+ const newId = await getNextCaveId(ctx, idScopeQuery, reusableIds);
615
674
  const { finalElementsForDb, mediaToSave } = await processMessageElements(
616
675
  sourceElements,
617
676
  newId,
618
677
  session.channelId,
619
678
  session.userId
620
679
  );
621
- if (finalElementsForDb.length === 0) return "内容为空,已取消添加";
680
+ if (finalElementsForDb.length === 0) {
681
+ return "内容为空,已取消添加";
682
+ }
622
683
  const userName = (config.enableProfile ? await profileManager.getNickname(session.userId) : null) || session.username;
623
684
  const hasMedia = mediaToSave.length > 0;
624
685
  const initialStatus = hasMedia ? "preload" : config.enableReview ? "pending" : "active";
@@ -633,11 +694,12 @@ function apply(ctx, config) {
633
694
  };
634
695
  await ctx.database.create("cave", newCave);
635
696
  if (hasMedia) {
636
- handleFileUploads(ctx, config, fileManager, logger, reviewManager, newCave, mediaToSave);
697
+ handleFileUploads(ctx, config, fileManager, logger, reviewManager, newCave, mediaToSave, reusableIds);
637
698
  } else if (initialStatus === "pending") {
638
699
  reviewManager.sendForReview(newCave);
639
700
  }
640
- return initialStatus === "pending" || initialStatus === "preload" && config.enableReview ? `提交成功,序号为(${newId})` : `添加成功,序号为(${newId})`;
701
+ const responseMessage = initialStatus === "pending" || initialStatus === "preload" && config.enableReview ? `提交成功,序号为(${newId})` : `添加成功,序号为(${newId})`;
702
+ return responseMessage;
641
703
  } catch (error) {
642
704
  logger.error("添加回声洞失败:", error);
643
705
  return "添加失败,请稍后再试";
@@ -671,7 +733,7 @@ function apply(ctx, config) {
671
733
  }
672
734
  await ctx.database.upsert("cave", [{ id, status: "delete" }]);
673
735
  const caveMessage = await buildCaveMessage(targetCave, config, fileManager, logger);
674
- cleanupPendingDeletions(ctx, fileManager, logger);
736
+ cleanupPendingDeletions(ctx, fileManager, logger, reusableIds);
675
737
  return [`已删除`, ...caveMessage];
676
738
  } catch (error) {
677
739
  logger.error(`标记回声洞(${id})失败:`, error);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "最强大的回声洞现已重构完成啦!注意数据格式需要使用脚本转换哦~",
4
- "version": "2.1.1",
4
+ "version": "2.1.2",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],