koishi-plugin-best-cave 2.3.16 → 2.3.18

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
@@ -10,9 +10,10 @@ import { PendManager } from './PendManager';
10
10
  * @param fileManager 文件管理器实例。
11
11
  * @param logger 日志记录器实例。
12
12
  * @param platform 目标平台名称 (e.g., 'onebot')。
13
+ * @param prefix 可选的消息前缀 (e.g., '已删除', '待审核')。
13
14
  * @returns 包含多条消息的数组,每条消息是一个 (string | h)[] 数组。
14
15
  */
15
- export declare function buildCaveMessage(cave: CaveObject, config: Config, fileManager: FileManager, logger: Logger, platform?: string): Promise<(string | h)[][]>;
16
+ export declare function buildCaveMessage(cave: CaveObject, config: Config, fileManager: FileManager, logger: Logger, platform?: string, prefix?: string): Promise<(string | h)[][]>;
16
17
  /**
17
18
  * @description 清理数据库中标记为 'delete' 状态的回声洞及其关联文件和哈希。
18
19
  * @param ctx Koishi 上下文。
@@ -62,6 +63,20 @@ export declare function processMessageElements(sourceElements: h[], newId: numbe
62
63
  fileName: string;
63
64
  }[];
64
65
  }>;
66
+ /**
67
+ * @description 异步处理文件上传、查重和状态更新的后台任务。
68
+ * @param ctx - Koishi 上下文。
69
+ * @param config - 插件配置。
70
+ * @param fileManager - FileManager 实例,用于保存文件。
71
+ * @param logger - 日志记录器实例。
72
+ * @param reviewManager - ReviewManager 实例,用于提交审核。
73
+ * @param cave - 刚刚在数据库中创建的 `preload` 状态的回声洞对象。
74
+ * @param mediaToSave - 需要下载和处理的媒体文件列表。
75
+ * @param reusableIds - 可复用 ID 的内存缓存。
76
+ * @param session - 触发此操作的用户会话,用于发送反馈。
77
+ * @param hashManager - HashManager 实例,如果启用则用于哈希计算和比较。
78
+ * @param textHashesToStore - 已预先计算好的、待存入数据库的文本哈希对象数组。
79
+ */
65
80
  export declare function handleFileUploads(ctx: Context, config: Config, fileManager: FileManager, logger: Logger, reviewManager: PendManager, cave: CaveObject, mediaToToSave: {
66
81
  sourceUrl: string;
67
82
  fileName: string;
package/lib/index.js CHANGED
@@ -80,9 +80,7 @@ var FileManager = class {
80
80
  * @returns 异步操作的结果。
81
81
  */
82
82
  async withLock(fullPath, operation) {
83
- while (this.locks.has(fullPath)) {
84
- await this.locks.get(fullPath);
85
- }
83
+ while (this.locks.has(fullPath)) await this.locks.get(fullPath);
86
84
  const promise = operation().finally(() => {
87
85
  this.locks.delete(fullPath);
88
86
  });
@@ -232,9 +230,7 @@ var DataManager = class {
232
230
  */
233
231
  registerCommands(cave) {
234
232
  const requireAdmin = /* @__PURE__ */ __name((action) => async ({ session }) => {
235
- if (session.channelId !== this.config.adminChannel?.split(":")[1]) {
236
- return "此指令仅限在管理群组中使用";
237
- }
233
+ if (session.channelId !== this.config.adminChannel?.split(":")[1]) return "此指令仅限在管理群组中使用";
238
234
  try {
239
235
  await session.send("正在处理,请稍候...");
240
236
  return await action();
@@ -267,9 +263,7 @@ var DataManager = class {
267
263
  try {
268
264
  const fileContent = await this.fileManager.readFile(fileName);
269
265
  importedCaves = JSON.parse(fileContent.toString("utf-8"));
270
- if (!Array.isArray(importedCaves) || !importedCaves.length) {
271
- throw new Error("导入文件格式无效或为空");
272
- }
266
+ if (!Array.isArray(importedCaves) || !importedCaves.length) throw new Error("导入文件格式无效或为空");
273
267
  } catch (error) {
274
268
  throw new Error(`读取导入文件失败: ${error.message}`);
275
269
  }
@@ -292,7 +286,7 @@ var import_koishi2 = require("koishi");
292
286
  var import_koishi = require("koishi");
293
287
  var path2 = __toESM(require("path"));
294
288
  var mimeTypeMap = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".mp4": "video/mp4", ".mp3": "audio/mpeg", ".webp": "image/webp" };
295
- async function buildCaveMessage(cave, config, fileManager, logger2, platform) {
289
+ async function buildCaveMessage(cave, config, fileManager, logger2, platform, prefix) {
296
290
  async function transformToH(elements) {
297
291
  return Promise.all(elements.map(async (el) => {
298
292
  if (el.type === "text") return import_koishi.h.text(el.content);
@@ -306,7 +300,7 @@ async function buildCaveMessage(cave, config, fileManager, logger2, platform) {
306
300
  const content = await transformToH(node.elements);
307
301
  return (0, import_koishi.h)("message", {}, [author, ...content]);
308
302
  }));
309
- return (0, import_koishi.h)("forward", {}, messageNodes);
303
+ return (0, import_koishi.h)("message", { forward: true }, messageNodes);
310
304
  } catch (error) {
311
305
  logger2.warn(`解析回声洞(${cave.id})合并转发内容失败:`, error);
312
306
  return import_koishi.h.text("[合并转发]");
@@ -315,12 +309,8 @@ async function buildCaveMessage(cave, config, fileManager, logger2, platform) {
315
309
  if (["image", "video", "audio", "file"].includes(el.type)) {
316
310
  const fileName = el.file;
317
311
  if (!fileName) return (0, import_koishi.h)("p", {}, `[${el.type}]`);
318
- if (config.enableS3 && config.publicUrl) {
319
- return (0, import_koishi.h)(el.type, { ...el, src: new URL(fileName, config.publicUrl).href });
320
- }
321
- if (config.localPath) {
322
- return (0, import_koishi.h)(el.type, { ...el, src: `file://${path2.join(config.localPath, fileName)}` });
323
- }
312
+ if (config.enableS3 && config.publicUrl) return (0, import_koishi.h)(el.type, { ...el, src: new URL(fileName, config.publicUrl).href });
313
+ if (config.localPath) return (0, import_koishi.h)(el.type, { ...el, src: `file://${path2.join(config.localPath, fileName)}` });
324
314
  try {
325
315
  const data = await fileManager.readFile(fileName);
326
316
  const mimeType = mimeTypeMap[path2.extname(fileName).toLowerCase()] || "application/octet-stream";
@@ -336,10 +326,13 @@ async function buildCaveMessage(cave, config, fileManager, logger2, platform) {
336
326
  __name(transformToH, "transformToH");
337
327
  const caveHElements = await transformToH(cave.elements);
338
328
  const replacements = { id: cave.id.toString(), name: cave.userName };
339
- const [header, footer] = config.caveFormat.split("|", 2).map((part) => part.replace(/\{id\}|\{name\}/g, (match) => replacements[match.slice(1, -1)]).trim());
329
+ const [rawHeader, rawFooter] = config.caveFormat.split("|", 2);
330
+ let header = rawHeader ? rawHeader.replace(/\{id\}|\{name\}/g, (match) => replacements[match.slice(1, -1)]).trim() : "";
331
+ if (prefix) header = `${prefix}${header}`;
332
+ const footer = rawFooter ? rawFooter.replace(/\{id\}|\{name\}/g, (match) => replacements[match.slice(1, -1)]).trim() : "";
340
333
  const problematicTypes = ["video", "audio", "file", "forward"];
341
334
  const placeholderMap = { video: "[视频]", audio: "[音频]", file: "[文件]", forward: "[合并转发]" };
342
- const containsProblematic = platform === "onebot" && caveHElements.some((el) => problematicTypes.includes(el.type));
335
+ const containsProblematic = platform === "onebot" && caveHElements.some((el) => problematicTypes.includes(el.type) || el.type === "message" && el.attrs.forward);
343
336
  if (!containsProblematic) {
344
337
  const finalMessage = [];
345
338
  if (header) finalMessage.push(header + "\n");
@@ -350,8 +343,8 @@ async function buildCaveMessage(cave, config, fileManager, logger2, platform) {
350
343
  const initialMessageContent = [];
351
344
  const followUpMessages = [];
352
345
  for (const el of caveHElements) {
353
- if (problematicTypes.includes(el.type)) {
354
- initialMessageContent.push(import_koishi.h.text(placeholderMap[el.type]));
346
+ if (problematicTypes.includes(el.type) || el.type === "message" && el.attrs.forward) {
347
+ initialMessageContent.push(import_koishi.h.text(placeholderMap["forward"]));
355
348
  followUpMessages.push([el]);
356
349
  } else {
357
350
  initialMessageContent.push(el);
@@ -369,9 +362,7 @@ async function cleanupPendingDeletions(ctx, fileManager, logger2, reusableIds) {
369
362
  const cavesToDelete = await ctx.database.get("cave", { status: "delete" });
370
363
  if (!cavesToDelete.length) return;
371
364
  const idsToDelete = cavesToDelete.map((c) => c.id);
372
- for (const cave of cavesToDelete) {
373
- await Promise.all(cave.elements.filter((el) => el.file).map((el) => fileManager.deleteFile(el.file)));
374
- }
365
+ for (const cave of cavesToDelete) await Promise.all(cave.elements.filter((el) => el.file).map((el) => fileManager.deleteFile(el.file)));
375
366
  reusableIds.delete(0);
376
367
  idsToDelete.forEach((id) => reusableIds.add(id));
377
368
  await ctx.database.remove("cave", { id: { $in: idsToDelete } });
@@ -404,30 +395,22 @@ async function getNextCaveId(ctx, query = {}, reusableIds) {
404
395
  const existingIds = new Set(allCaveIds);
405
396
  let newId = 1;
406
397
  while (existingIds.has(newId)) newId++;
407
- if (existingIds.size === (allCaveIds.length > 0 ? Math.max(...allCaveIds) : 0)) {
408
- reusableIds.add(0);
409
- }
398
+ if (existingIds.size === (allCaveIds.length > 0 ? Math.max(...allCaveIds) : 0)) reusableIds.add(0);
410
399
  return newId;
411
400
  }
412
401
  __name(getNextCaveId, "getNextCaveId");
413
402
  function checkCooldown(session, config, lastUsed) {
414
403
  const adminChannelId = config.adminChannel?.split(":")[1];
415
- if (adminChannelId && session.channelId === adminChannelId) {
416
- return null;
417
- }
404
+ if (adminChannelId && session.channelId === adminChannelId) return null;
418
405
  if (config.coolDown <= 0 || !session.channelId) return null;
419
406
  const lastTime = lastUsed.get(session.channelId) || 0;
420
407
  const remainingTime = lastTime + config.coolDown * 1e3 - Date.now();
421
- if (remainingTime > 0) {
422
- return `指令冷却中,请在 ${Math.ceil(remainingTime / 1e3)} 秒后重试`;
423
- }
408
+ if (remainingTime > 0) return `指令冷却中,请在 ${Math.ceil(remainingTime / 1e3)} 秒后重试`;
424
409
  return null;
425
410
  }
426
411
  __name(checkCooldown, "checkCooldown");
427
412
  function updateCooldownTimestamp(session, config, lastUsed) {
428
- if (config.coolDown > 0 && session.channelId) {
429
- lastUsed.set(session.channelId, Date.now());
430
- }
413
+ if (config.coolDown > 0 && session.channelId) lastUsed.set(session.channelId, Date.now());
431
414
  }
432
415
  __name(updateCooldownTimestamp, "updateCooldownTimestamp");
433
416
  async function processMessageElements(sourceElements, newId, session, config, logger2) {
@@ -473,13 +456,9 @@ async function processMessageElements(sourceElements, newId, session, config, lo
473
456
  return (0, import_koishi.h)(type2, attrs);
474
457
  });
475
458
  const contentElements = await transform(elementsToProcess);
476
- if (contentElements.length > 0) {
477
- forwardNodes.push({ userId, userName, elements: contentElements });
478
- }
479
- }
480
- if (forwardNodes.length > 0) {
481
- result.push({ type: "forward", content: forwardNodes });
459
+ if (contentElements.length > 0) forwardNodes.push({ userId, userName, elements: contentElements });
482
460
  }
461
+ if (forwardNodes.length > 0) result.push({ type: "forward", content: forwardNodes });
483
462
  } else if (["image", "video", "audio", "file"].includes(type) && el.attrs.src) {
484
463
  let fileIdentifier = el.attrs.src;
485
464
  if (fileIdentifier.startsWith("http")) {
@@ -531,9 +510,7 @@ async function handleFileUploads(ctx, config, fileManager, logger2, reviewManage
531
510
  await ctx.database.upsert("cave", [{ id: cave.id, status: finalStatus }]);
532
511
  if (hashManager) {
533
512
  const allHashesToInsert = [...textHashesToStore, ...imageHashesToStore].map((h4) => ({ ...h4, cave: cave.id }));
534
- if (allHashesToInsert.length > 0) {
535
- await ctx.database.upsert("cave_hash", allHashesToInsert);
536
- }
513
+ if (allHashesToInsert.length > 0) await ctx.database.upsert("cave_hash", allHashesToInsert);
537
514
  }
538
515
  if (finalStatus === "pending" && reviewManager) {
539
516
  const [finalCave] = await ctx.database.get("cave", { id: cave.id });
@@ -572,9 +549,7 @@ var PendManager = class {
572
549
  */
573
550
  registerCommands(cave) {
574
551
  const requireAdmin = /* @__PURE__ */ __name((session) => {
575
- if (session.channelId !== this.config.adminChannel?.split(":")[1]) {
576
- return "此指令仅限在管理群组中使用";
577
- }
552
+ if (session.channelId !== this.config.adminChannel?.split(":")[1]) return "此指令仅限在管理群组中使用";
578
553
  return null;
579
554
  }, "requireAdmin");
580
555
  const pend = cave.subcommand(".pend [id:posint]", "审核回声洞").action(async ({ session }, id) => {
@@ -584,11 +559,8 @@ var PendManager = class {
584
559
  const [targetCave] = await this.ctx.database.get("cave", { id });
585
560
  if (!targetCave) return `回声洞(${id})不存在`;
586
561
  if (targetCave.status !== "pending") return `回声洞(${id})无需审核`;
587
- await session.send("待审核");
588
- const caveMessages = await buildCaveMessage(targetCave, this.config, this.fileManager, this.logger, session.platform);
589
- for (const message of caveMessages) {
590
- if (message.length > 0) await session.send(import_koishi2.h.normalize(message));
591
- }
562
+ const caveMessages = await buildCaveMessage(targetCave, this.config, this.fileManager, this.logger, session.platform, "待审核");
563
+ for (const message of caveMessages) if (message.length > 0) await session.send(import_koishi2.h.normalize(message));
592
564
  return;
593
565
  }
594
566
  const pendingCaves = await this.ctx.database.get("cave", { status: "pending" }, { fields: ["id"] });
@@ -634,11 +606,8 @@ ${pendingCaves.map((c) => c.id).join("|")}`;
634
606
  }
635
607
  try {
636
608
  const [platform] = this.config.adminChannel.split(":", 1);
637
- const caveMessages = await buildCaveMessage(cave, this.config, this.fileManager, this.logger, platform);
638
- await this.ctx.broadcast([this.config.adminChannel], "待审核");
639
- for (const message of caveMessages) {
640
- if (message.length > 0) await this.ctx.broadcast([this.config.adminChannel], import_koishi2.h.normalize(message));
641
- }
609
+ const caveMessages = await buildCaveMessage(cave, this.config, this.fileManager, this.logger, platform, "待审核");
610
+ for (const message of caveMessages) if (message.length > 0) await this.ctx.broadcast([this.config.adminChannel], import_koishi2.h.normalize(message));
642
611
  } catch (error) {
643
612
  this.logger.error(`发送回声洞(${cave.id})审核消息失败:`, error);
644
613
  }
@@ -679,9 +648,7 @@ var HashManager = class {
679
648
  registerCommands(cave) {
680
649
  const adminCheck = /* @__PURE__ */ __name(({ session }) => {
681
650
  const adminChannelId = this.config.adminChannel?.split(":")[1];
682
- if (session.channelId !== adminChannelId) {
683
- return "此指令仅限在管理群组中使用";
684
- }
651
+ if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
685
652
  }, "adminCheck");
686
653
  cave.subcommand(".hash", "校验回声洞").usage("校验缺失哈希的回声洞,补全哈希记录。").action(async (argv) => {
687
654
  const checkResult = adminCheck(argv);
@@ -738,9 +705,7 @@ var HashManager = class {
738
705
  existingHashSet.add(uniqueKey);
739
706
  }
740
707
  }
741
- if (hashesToInsert.length >= 100) {
742
- await flushBatch();
743
- }
708
+ if (hashesToInsert.length >= 100) await flushBatch();
744
709
  } catch (error) {
745
710
  errorCount++;
746
711
  this.logger.warn(`补全回声洞(${cave.id})哈希时发生错误: ${error.message}`);
@@ -823,17 +788,13 @@ var HashManager = class {
823
788
  const text2 = textHashes.get(id2);
824
789
  if (text1 && text2) {
825
790
  const similarity = this.calculateSimilarity(text1, text2);
826
- if (similarity >= textThreshold) {
827
- similarPairs.text.add(`${pair} = ${similarity.toFixed(2)}%`);
828
- }
791
+ if (similarity >= textThreshold) similarPairs.text.add(`${pair} = ${similarity.toFixed(2)}%`);
829
792
  }
830
793
  const global1 = globalHashes.get(id1);
831
794
  const global2 = globalHashes.get(id2);
832
795
  if (global1 && global2) {
833
796
  const similarity = this.calculateSimilarity(global1, global2);
834
- if (similarity >= imageThreshold) {
835
- similarPairs.global.add(`${pair} = ${similarity.toFixed(2)}%`);
836
- }
797
+ if (similarity >= imageThreshold) similarPairs.global.add(`${pair} = ${similarity.toFixed(2)}%`);
837
798
  }
838
799
  }
839
800
  }
@@ -862,9 +823,7 @@ var HashManager = class {
862
823
  components.get(root).add(id);
863
824
  }
864
825
  const partialGroups = [];
865
- for (const component of components.values()) {
866
- if (component.size > 1) partialGroups.push(Array.from(component).sort((a, b) => a - b).join(" & "));
867
- }
826
+ for (const component of components.values()) if (component.size > 1) partialGroups.push(Array.from(component).sort((a, b) => a - b).join(" & "));
868
827
  const totalFindings = similarPairs.text.size + similarPairs.global.size + partialGroups.length;
869
828
  if (totalFindings === 0) return "未发现高相似度的内容";
870
829
  let report = `已发现 ${totalFindings} 组高相似度的内容:`;
@@ -913,9 +872,7 @@ var HashManager = class {
913
872
  const scale = Math.sqrt(2 / N);
914
873
  for (let k = 0; k < N; k++) {
915
874
  let sum = 0;
916
- for (let n = 0; n < N; n++) {
917
- sum += input[n] * cosines[n][k];
918
- }
875
+ for (let n = 0; n < N; n++) sum += input[n] * cosines[n][k];
919
876
  output[k] = scale * sum;
920
877
  }
921
878
  output[0] /= Math.sqrt(2);
@@ -938,16 +895,10 @@ var HashManager = class {
938
895
  if (!Number.isInteger(hashGridSize)) throw new Error("哈希位数必须是完全平方数");
939
896
  const pixels = await (0, import_sharp.default)(imageBuffer).grayscale().resize(dctSize, dctSize, { fit: "fill" }).raw().toBuffer();
940
897
  const matrix = [];
941
- for (let y = 0; y < dctSize; y++) {
942
- matrix.push(Array.from(pixels.slice(y * dctSize, (y + 1) * dctSize)));
943
- }
898
+ for (let y = 0; y < dctSize; y++) matrix.push(Array.from(pixels.slice(y * dctSize, (y + 1) * dctSize)));
944
899
  const dctMatrix = this._dct2D(matrix);
945
900
  const coefficients = [];
946
- for (let y = 0; y < hashGridSize; y++) {
947
- for (let x = 0; x < hashGridSize; x++) {
948
- coefficients.push(dctMatrix[y][x]);
949
- }
950
- }
901
+ for (let y = 0; y < hashGridSize; y++) for (let x = 0; x < hashGridSize; x++) coefficients.push(dctMatrix[y][x]);
951
902
  const median = [...coefficients.slice(1)].sort((a, b) => a - b)[Math.floor((coefficients.length - 1) / 2)];
952
903
  const binaryHash = coefficients.map((val) => val > median ? "1" : "0").join("");
953
904
  return BigInt("0b" + binaryHash).toString(16).padStart(size / 4, "0");
@@ -963,9 +914,7 @@ var HashManager = class {
963
914
  const bin1 = hexToBinary(hex1);
964
915
  const bin2 = hexToBinary(hex2);
965
916
  const len = Math.min(bin1.length, bin2.length);
966
- for (let i = 0; i < len; i++) {
967
- if (bin1[i] !== bin2[i]) distance++;
968
- }
917
+ for (let i = 0; i < len; i++) if (bin1[i] !== bin2[i]) distance++;
969
918
  return distance;
970
919
  }
971
920
  /**
@@ -992,18 +941,14 @@ var HashManager = class {
992
941
  if (cleanText.length < n) {
993
942
  tokens.add(cleanText);
994
943
  } else {
995
- for (let i = 0; i <= cleanText.length - n; i++) {
996
- tokens.add(cleanText.substring(i, i + n));
997
- }
944
+ for (let i = 0; i <= cleanText.length - n; i++) tokens.add(cleanText.substring(i, i + n));
998
945
  }
999
946
  const tokenArray = Array.from(tokens);
1000
947
  if (tokenArray.length === 0) return "";
1001
948
  const vector = new Array(64).fill(0);
1002
949
  tokenArray.forEach((token) => {
1003
950
  const hash = crypto.createHash("md5").update(token).digest();
1004
- for (let i = 0; i < 64; i++) {
1005
- vector[i] += hash[Math.floor(i / 8)] >> i % 8 & 1 ? 1 : -1;
1006
- }
951
+ for (let i = 0; i < 64; i++) vector[i] += hash[Math.floor(i / 8)] >> i % 8 & 1 ? 1 : -1;
1007
952
  });
1008
953
  const binaryHash = vector.map((v) => v > 0 ? "1" : "0").join("");
1009
954
  return BigInt("0b" + binaryHash).toString(16).padStart(16, "0");
@@ -1011,9 +956,7 @@ var HashManager = class {
1011
956
  };
1012
957
  function hexToBinary(hex) {
1013
958
  let bin = "";
1014
- for (const char of hex) {
1015
- bin += parseInt(char, 16).toString(2).padStart(4, "0");
1016
- }
959
+ for (const char of hex) bin += parseInt(char, 16).toString(2).padStart(4, "0");
1017
960
  return bin;
1018
961
  }
1019
962
  __name(hexToBinary, "hexToBinary");
@@ -1090,16 +1033,12 @@ function apply(ctx, config) {
1090
1033
  try {
1091
1034
  const query = getScopeQuery(session, config);
1092
1035
  const candidates = await ctx.database.get("cave", query, { fields: ["id"] });
1093
- if (!candidates.length) {
1094
- return `当前${config.perChannel && session.channelId ? "本群" : ""}还没有任何回声洞`;
1095
- }
1036
+ if (!candidates.length) return `当前${config.perChannel && session.channelId ? "本群" : ""}还没有任何回声洞`;
1096
1037
  const randomId = candidates[Math.floor(Math.random() * candidates.length)].id;
1097
1038
  const [randomCave] = await ctx.database.get("cave", { ...query, id: randomId });
1098
1039
  updateCooldownTimestamp(session, config, lastUsed);
1099
1040
  const messages = await buildCaveMessage(randomCave, config, fileManager, logger, session.platform);
1100
- for (const message of messages) {
1101
- if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1102
- }
1041
+ for (const message of messages) if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1103
1042
  } catch (error) {
1104
1043
  logger.error("随机获取回声洞失败:", error);
1105
1044
  return "随机获取回声洞失败";
@@ -1126,10 +1065,8 @@ ${JSON.stringify(session, null, 2)}`);
1126
1065
  }
1127
1066
  const newId = await getNextCaveId(ctx, getScopeQuery(session, config, false), reusableIds);
1128
1067
  const { finalElementsForDb, mediaToSave } = await processMessageElements(sourceElements, newId, session, config, logger);
1129
- if (config.debug) {
1130
- logger.info(`数据库元素:
1068
+ if (config.debug) logger.info(`数据库元素:
1131
1069
  ${JSON.stringify(finalElementsForDb, null, 2)}`);
1132
- }
1133
1070
  if (finalElementsForDb.length === 0) return "无可添加内容";
1134
1071
  const textHashesToStore = [];
1135
1072
  if (hashManager) {
@@ -1140,9 +1077,7 @@ ${JSON.stringify(finalElementsForDb, null, 2)}`);
1140
1077
  const existingTextHashes = await ctx.database.get("cave_hash", { type: "simhash" });
1141
1078
  for (const existing of existingTextHashes) {
1142
1079
  const similarity = hashManager.calculateSimilarity(newSimhash, existing.hash);
1143
- if (similarity >= config.textThreshold) {
1144
- return `文本与回声洞(${existing.cave})的相似度(${similarity.toFixed(2)}%)超过阈值`;
1145
- }
1080
+ if (similarity >= config.textThreshold) return `文本与回声洞(${existing.cave})的相似度(${similarity.toFixed(2)}%)超过阈值`;
1146
1081
  }
1147
1082
  textHashesToStore.push({ hash: newSimhash, type: "simhash" });
1148
1083
  }
@@ -1163,12 +1098,8 @@ ${JSON.stringify(finalElementsForDb, null, 2)}`);
1163
1098
  if (hasMedia) {
1164
1099
  handleFileUploads(ctx, config, fileManager, logger, reviewManager, newCave, mediaToSave, reusableIds, session, hashManager, textHashesToStore);
1165
1100
  } else {
1166
- if (hashManager && textHashesToStore.length > 0) {
1167
- await ctx.database.upsert("cave_hash", textHashesToStore.map((h4) => ({ ...h4, cave: newCave.id })));
1168
- }
1169
- if (initialStatus === "pending") {
1170
- reviewManager.sendForPend(newCave);
1171
- }
1101
+ if (hashManager && textHashesToStore.length > 0) await ctx.database.upsert("cave_hash", textHashesToStore.map((h4) => ({ ...h4, cave: newCave.id })));
1102
+ if (initialStatus === "pending") reviewManager.sendForPend(newCave);
1172
1103
  }
1173
1104
  return initialStatus === "pending" || initialStatus === "preload" && config.enablePend ? `提交成功,序号为(${newCave.id})` : `添加成功,序号为(${newCave.id})`;
1174
1105
  } catch (error) {
@@ -1185,9 +1116,7 @@ ${JSON.stringify(finalElementsForDb, null, 2)}`);
1185
1116
  if (!targetCave) return `回声洞(${id})不存在`;
1186
1117
  updateCooldownTimestamp(session, config, lastUsed);
1187
1118
  const messages = await buildCaveMessage(targetCave, config, fileManager, logger, session.platform);
1188
- for (const message of messages) {
1189
- if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1190
- }
1119
+ for (const message of messages) if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1191
1120
  } catch (error) {
1192
1121
  logger.error(`查看回声洞(${id})失败:`, error);
1193
1122
  return "查看失败,请稍后再试";
@@ -1202,12 +1131,9 @@ ${JSON.stringify(finalElementsForDb, null, 2)}`);
1202
1131
  const isAdmin = session.channelId === config.adminChannel?.split(":")[1];
1203
1132
  if (!isAuthor && !isAdmin) return "你没有权限删除这条回声洞";
1204
1133
  await ctx.database.upsert("cave", [{ id, status: "delete" }]);
1205
- const caveMessages = await buildCaveMessage(targetCave, config, fileManager, logger, session.platform);
1134
+ const caveMessages = await buildCaveMessage(targetCave, config, fileManager, logger, session.platform, "已删除");
1206
1135
  cleanupPendingDeletions(ctx, fileManager, logger, reusableIds);
1207
- await session.send("已删除");
1208
- for (const message of caveMessages) {
1209
- if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1210
- }
1136
+ for (const message of caveMessages) if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1211
1137
  } catch (error) {
1212
1138
  logger.error(`标记回声洞(${id})失败:`, error);
1213
1139
  return "删除失败,请稍后再试";
@@ -1216,9 +1142,7 @@ ${JSON.stringify(finalElementsForDb, null, 2)}`);
1216
1142
  cave.subcommand(".list", "查询投稿统计").option("user", "-u <user:user> 指定用户").option("all", "-a 查看排行").action(async ({ session, options }) => {
1217
1143
  if (options.all) {
1218
1144
  const adminChannelId = config.adminChannel?.split(":")[1];
1219
- if (session.channelId !== adminChannelId) {
1220
- return "此指令仅限在管理群组中使用";
1221
- }
1145
+ if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
1222
1146
  try {
1223
1147
  const allCaves = await ctx.database.get("cave", { status: "active" });
1224
1148
  if (!allCaves.length) return "目前没有任何回声洞投稿。";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "功能强大、高度可定制的回声洞。支持丰富的媒体类型、内容查重、人工审核、用户昵称、数据迁移以及本地/S3 双重文件存储后端。",
4
- "version": "2.3.16",
4
+ "version": "2.3.18",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],