koishi-plugin-best-cave 2.7.25 → 2.7.27

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.
Files changed (2) hide show
  1. package/lib/index.js +114 -90
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -103,9 +103,9 @@ var FileManager = class {
103
103
  });
104
104
  await this.s3Client.send(command);
105
105
  } else {
106
- await fs.mkdir(this.resourceDir, { recursive: true }).catch((error2) => {
107
- this.logger.error(`创建资源目录失败 ${this.resourceDir}:`, error2);
108
- throw error2;
106
+ await fs.mkdir(this.resourceDir, { recursive: true }).catch((error) => {
107
+ this.logger.error(`创建资源目录失败 ${this.resourceDir}:`, error);
108
+ throw error;
109
109
  });
110
110
  const filePath = path.join(this.resourceDir, fileName);
111
111
  await this.withLock(filePath, () => fs.writeFile(filePath, data));
@@ -139,9 +139,9 @@ var FileManager = class {
139
139
  const filePath = path.join(this.resourceDir, fileIdentifier);
140
140
  await this.withLock(filePath, () => fs.unlink(filePath));
141
141
  }
142
- } catch (error2) {
143
- if (error2.code !== "ENOENT" && error2.name !== "NoSuchKey") {
144
- this.logger.warn(`删除文件 ${fileIdentifier} 失败:`, error2);
142
+ } catch (error) {
143
+ if (error.code !== "ENOENT" && error.name !== "NoSuchKey") {
144
+ this.logger.warn(`删除文件 ${fileIdentifier} 失败:`, error);
145
145
  }
146
146
  }
147
147
  }
@@ -238,8 +238,8 @@ async function buildCaveMessage(cave, config, fileManager, logger2, platform, pr
238
238
  return resultNodes;
239
239
  }));
240
240
  return (0, import_koishi.h)("message", { forward: true }, messageNodes.flat());
241
- } catch (error2) {
242
- logger2.warn(`解析回声洞(${cave.id})合并转发内容失败:`, error2);
241
+ } catch (error) {
242
+ logger2.warn(`解析回声洞(${cave.id})合并转发内容失败:`, error);
243
243
  return import_koishi.h.text("[合并转发]");
244
244
  }
245
245
  }
@@ -252,8 +252,8 @@ async function buildCaveMessage(cave, config, fileManager, logger2, platform, pr
252
252
  const data2 = await fileManager.readFile(fileName);
253
253
  const mimeType = mimeTypeMap[path2.extname(fileName).toLowerCase()] || "application/octet-stream";
254
254
  return (0, import_koishi.h)(el.type, { ...el, src: `data:${mimeType};base64,${data2.toString("base64")}` });
255
- } catch (error2) {
256
- logger2.warn(`转换文件 ${fileName} 为 Base64 失败:`, error2);
255
+ } catch (error) {
256
+ logger2.warn(`转换文件 ${fileName} 为 Base64 失败:`, error);
257
257
  return (0, import_koishi.h)("p", {}, `[${el.type}]`);
258
258
  }
259
259
  }
@@ -332,8 +332,8 @@ async function cleanupPendingDeletions(ctx, config, fileManager, logger2, reusab
332
332
  await ctx.database.remove("cave", { id: { $in: idsToDelete } });
333
333
  await ctx.database.remove("cave_hash", { cave: { $in: idsToDelete } });
334
334
  if (config.enableAI) await ctx.database.remove("cave_meta", { cave: { $in: idsToDelete } });
335
- } catch (error2) {
336
- logger2.error("清理回声洞时发生错误:", error2);
335
+ } catch (error) {
336
+ logger2.error("清理回声洞时发生错误:", error);
337
337
  }
338
338
  }
339
339
  __name(cleanupPendingDeletions, "cleanupPendingDeletions");
@@ -481,8 +481,8 @@ async function performSimilarityChecks(ctx, config, hashManager, logger2, finalE
481
481
  }
482
482
  }
483
483
  return { duplicate: false, textHashesToStore, imageHashesToStore };
484
- } catch (error2) {
485
- logger2.warn("相似度比较失败:", error2);
484
+ } catch (error) {
485
+ logger2.warn("相似度比较失败:", error);
486
486
  return { duplicate: false, textHashesToStore: [], imageHashesToStore: [] };
487
487
  }
488
488
  }
@@ -578,9 +578,9 @@ var DataManager = class {
578
578
  try {
579
579
  await session.send("正在处理,请稍候...");
580
580
  return await action();
581
- } catch (error2) {
582
- this.logger.error("数据操作时发生错误:", error2);
583
- return `操作失败: ${error2.message}`;
581
+ } catch (error) {
582
+ this.logger.error("数据操作时发生错误:", error);
583
+ return `操作失败: ${error.message}`;
584
584
  }
585
585
  }, "commandAction");
586
586
  cave.subcommand(".export", "导出回声洞数据", { hidden: true, authority: 4 }).usage("将所有回声洞数据导出到 cave.json 中。").action(commandAction(() => this.exportData()));
@@ -607,8 +607,8 @@ var DataManager = class {
607
607
  const fileContent = await this.fileManager.readFile(fileName);
608
608
  importedCaves = JSON.parse(fileContent.toString("utf-8"));
609
609
  if (!Array.isArray(importedCaves) || !importedCaves.length) throw new Error("导入文件格式无效或为空");
610
- } catch (error2) {
611
- throw new Error(`读取导入文件失败: ${error2.message}`);
610
+ } catch (error) {
611
+ throw new Error(`读取导入文件失败: ${error.message}`);
612
612
  }
613
613
  const allDbCaves = await this.ctx.database.get("cave", {}, { fields: ["id"] });
614
614
  const existingIds = new Set(allDbCaves.map((c) => c.id));
@@ -703,9 +703,9 @@ ${pendingCaves.map((c) => c.id).join("|")}`;
703
703
  await this.ctx.database.upsert("cave", processedIds.map((id) => ({ id, status: targetStatus })));
704
704
  if (targetStatus === "delete") cleanupPendingDeletions(this.ctx, this.config, this.fileManager, this.logger, this.reusableIds);
705
705
  return `已${actionText}回声洞(${processedIds.join("|")})`;
706
- } catch (error2) {
707
- this.logger.error(`审核操作失败:`, error2);
708
- return `操作失败: ${error2.message}`;
706
+ } catch (error) {
707
+ this.logger.error(`审核操作失败:`, error);
708
+ return `操作失败: ${error.message}`;
709
709
  }
710
710
  }, "createPendAction");
711
711
  pend.subcommand(".Y [...ids:posint]", "通过审核").usage("通过一个或多个指定 ID 的回声洞审核。若不指定 ID,则通过所有待审核的回声洞。").action(createPendAction("approve"));
@@ -725,8 +725,8 @@ ${pendingCaves.map((c) => c.id).join("|")}`;
725
725
  const [platform] = this.config.adminChannel.split(":", 1);
726
726
  const caveMessages = await buildCaveMessage(cave, this.config, this.fileManager, this.logger, platform, "待审核");
727
727
  for (const message of caveMessages) if (message.length > 0) await this.ctx.broadcast([this.config.adminChannel], import_koishi2.h.normalize(message));
728
- } catch (error2) {
729
- this.logger.error(`发送回声洞(${cave.id})审核消息失败:`, error2);
728
+ } catch (error) {
729
+ this.logger.error(`发送回声洞(${cave.id})审核消息失败:`, error);
730
730
  }
731
731
  }
732
732
  };
@@ -809,17 +809,17 @@ var HashManager = class {
809
809
  const newHashesForCave = tempHashes;
810
810
  if (newHashesForCave.length > 0) hashesToInsert.push(...newHashesForCave);
811
811
  if (hashesToInsert.length >= 100) await flushBatch();
812
- } catch (error2) {
812
+ } catch (error) {
813
813
  errorCount++;
814
- this.logger.warn(`补全回声洞(${cave2.id})哈希时出错: ${error2.message}`);
814
+ this.logger.warn(`补全回声洞(${cave2.id})哈希时出错: ${error.message}`);
815
815
  }
816
816
  }
817
817
  await flushBatch();
818
818
  const successCount = processedCaveCount - errorCount;
819
819
  return `已补全 ${successCount} 个回声洞的 ${totalHashesGenerated} 条哈希(失败 ${errorCount} 条)`;
820
- } catch (error2) {
821
- this.logger.error("补全哈希失败:", error2);
822
- return `操作失败: ${error2.message}`;
820
+ } catch (error) {
821
+ this.logger.error("补全哈希失败:", error);
822
+ return `操作失败: ${error.message}`;
823
823
  }
824
824
  });
825
825
  cave.subcommand(".check", "检查相似度", { hidden: true }).usage("检查所有回声洞,找出相似度过高的内容。").option("textThreshold", "-t <threshold:number> 文本相似度阈值 (%)").option("imageThreshold", "-i <threshold:number> 图片相似度阈值 (%)").action(async ({ session, options }) => {
@@ -903,9 +903,9 @@ ${textResult.reportLines.join("\n")}`;
903
903
  ${imageResult.reportLines.join("\n")}`;
904
904
  }
905
905
  return report.trim();
906
- } catch (error2) {
907
- this.logger.error("检查相似度失败:", error2);
908
- return `检查失败: ${error2.message}`;
906
+ } catch (error) {
907
+ this.logger.error("检查相似度失败:", error);
908
+ return `检查失败: ${error.message}`;
909
909
  }
910
910
  });
911
911
  cave.subcommand(".fix [...ids:posint]", "修复回声洞", { hidden: true, authority: 3 }).usage("扫描并修复回声洞中的图片,可指定一个或多个 ID。").action(async ({ session }, ...ids) => {
@@ -931,18 +931,18 @@ ${imageResult.reportLines.join("\n")}`;
931
931
  await this.fileManager.saveFile(element.file, sanitizedBuffer);
932
932
  fixedFiles++;
933
933
  }
934
- } catch (error2) {
935
- if (error2.code !== "ENOENT" && error2.name !== "NoSuchKey") {
936
- this.logger.warn(`无法修复回声洞(${cave2.id})的图片(${element.file}):`, error2);
934
+ } catch (error) {
935
+ if (error.code !== "ENOENT" && error.name !== "NoSuchKey") {
936
+ this.logger.warn(`无法修复回声洞(${cave2.id})的图片(${element.file}):`, error);
937
937
  errorCount++;
938
938
  }
939
939
  }
940
940
  }
941
941
  }
942
942
  return `已修复 ${cavesToProcess.length} 个回声洞的 ${fixedFiles} 张图片(失败 ${errorCount} 条)`;
943
- } catch (error2) {
944
- this.logger.error("修复图像文件时发生严重错误:", error2);
945
- return `操作失败: ${error2.message}`;
943
+ } catch (error) {
944
+ this.logger.error("修复图像文件时发生严重错误:", error);
945
+ return `操作失败: ${error.message}`;
946
946
  }
947
947
  });
948
948
  }
@@ -1083,7 +1083,6 @@ ${imageResult.reportLines.join("\n")}`;
1083
1083
 
1084
1084
  // src/AIManager.ts
1085
1085
  var path3 = __toESM(require("path"));
1086
- var import_console = require("console");
1087
1086
  var AIManager = class {
1088
1087
  /**
1089
1088
  * @constructor
@@ -1155,20 +1154,36 @@ var AIManager = class {
1155
1154
  if (cavesToAnalyze.length === 0) return "无需分析回声洞";
1156
1155
  await session.send(`开始分析 ${cavesToAnalyze.length} 个回声洞...`);
1157
1156
  let successCount = 0;
1158
- const batchSize = 10;
1159
- for (let i = 0; i < cavesToAnalyze.length; i += batchSize) {
1160
- const batch = cavesToAnalyze.slice(i, i + batchSize);
1161
- this.logger.info(`[${i + 1}/${cavesToAnalyze.length}] 正在分析 ${batch.length} 条回声洞...`);
1162
- const analyses = await this.analyze(batch);
1163
- if (analyses.length > 0) {
1164
- await this.ctx.database.upsert("cave_meta", analyses);
1165
- successCount += analyses.length;
1157
+ let failedCount = 0;
1158
+ let consecutiveFailures = 0;
1159
+ for (let i = 0; i < cavesToAnalyze.length; i += 25) {
1160
+ const batch = cavesToAnalyze.slice(i, i + 25);
1161
+ this.logger.info(`[${i + 1}/${cavesToAnalyze.length}] 正在分析 ${batch.length} 个回声洞...`);
1162
+ const analysisPromises = batch.map((cave2) => this.analyze([cave2]));
1163
+ const results = await Promise.allSettled(analysisPromises);
1164
+ const successfulAnalyses = [];
1165
+ for (let j = 0; j < results.length; j++) {
1166
+ const result = results[j];
1167
+ const cave2 = batch[j];
1168
+ if (result.status === "fulfilled" && result.value.length > 0) {
1169
+ successfulAnalyses.push(result.value[0]);
1170
+ consecutiveFailures = 0;
1171
+ } else {
1172
+ failedCount++;
1173
+ consecutiveFailures++;
1174
+ if (result.status === "rejected") this.logger.error(`分析回声洞(${cave2.id})失败:`, result.reason);
1175
+ }
1176
+ }
1177
+ if (successfulAnalyses.length > 0) {
1178
+ await this.ctx.database.upsert("cave_meta", successfulAnalyses);
1179
+ successCount += successfulAnalyses.length;
1166
1180
  }
1181
+ if (consecutiveFailures >= 3) break;
1167
1182
  }
1168
- return `已分析 ${successCount} 个回声洞`;
1169
- } catch (error2) {
1170
- this.logger.error("分析回声洞失败:", error2);
1171
- return `操作失败: ${error2.message}`;
1183
+ return `已分析 ${successCount} 个回声洞(失败 ${failedCount} 个)`;
1184
+ } catch (error) {
1185
+ this.logger.error("分析回声洞失败:", error);
1186
+ return `操作失败: ${error.message}`;
1172
1187
  }
1173
1188
  });
1174
1189
  cave.subcommand(".compare", "比较重复性", { hidden: true }).usage("检查回声洞,找出可能重复的内容。").action(async ({ session }) => {
@@ -1213,9 +1228,9 @@ var AIManager = class {
1213
1228
  - ${sortedCluster.join("|")}`;
1214
1229
  });
1215
1230
  return report;
1216
- } catch (error2) {
1217
- this.logger.error("检查重复性失败:", error2);
1218
- return `检查失败: ${error2.message}`;
1231
+ } catch (error) {
1232
+ this.logger.error("检查重复性失败:", error);
1233
+ return `检查失败: ${error.message}`;
1219
1234
  }
1220
1235
  });
1221
1236
  }
@@ -1241,8 +1256,8 @@ var AIManager = class {
1241
1256
  });
1242
1257
  const duplicateIds = (await Promise.all(comparisonPromises)).filter((id) => id !== null);
1243
1258
  return { duplicate: duplicateIds.length > 0, ids: duplicateIds };
1244
- } catch (error2) {
1245
- this.logger.error("查重回声洞出错:", error2);
1259
+ } catch (error) {
1260
+ this.logger.error("查重回声洞出错:", error);
1246
1261
  return { duplicate: false };
1247
1262
  }
1248
1263
  }
@@ -1265,16 +1280,19 @@ var AIManager = class {
1265
1280
  type: "image_url",
1266
1281
  image_url: { url: `data:${mimeType};base64,${buffer.toString("base64")}` }
1267
1282
  };
1268
- } catch (error2) {
1269
- this.logger.warn(`读取文件(${el.file})失败:`, error2);
1283
+ } catch (error) {
1284
+ this.logger.warn(`读取文件(${el.file})失败:`, error);
1285
+ return null;
1270
1286
  }
1271
1287
  })
1272
1288
  );
1273
1289
  const images = imageElements.filter(Boolean);
1274
1290
  if (!combinedText.trim() && images.length === 0) return null;
1275
- const contentForAI = [{ type: "text", text: `请分析以下内容:
1291
+ const contentForAI = [];
1292
+ if (combinedText.trim()) contentForAI.push({ type: "text", text: `请分析以下内容:
1276
1293
 
1277
- ${combinedText}` }, ...images];
1294
+ ${combinedText}` });
1295
+ contentForAI.push(...images);
1278
1296
  const userMessage = { role: "user", content: contentForAI };
1279
1297
  const response = await this.requestAI([userMessage], this.ANALYSIS_SYSTEM_PROMPT);
1280
1298
  if (response) return {
@@ -1306,8 +1324,8 @@ ${combinedText}` }, ...images];
1306
1324
  const userMessage = { role: "user", content: JSON.stringify(userMessageContent) };
1307
1325
  const response = await this.requestAI([userMessage], this.DUPLICATE_CHECK_SYSTEM_PROMPT);
1308
1326
  return response?.duplicate || false;
1309
- } catch (error2) {
1310
- this.logger.error(`比较回声洞(${caveA.id})与(${caveB.id})失败:`, error2);
1327
+ } catch (error) {
1328
+ this.logger.error(`比较回声洞(${caveA.id})与(${caveB.id})失败:`, error);
1311
1329
  return false;
1312
1330
  }
1313
1331
  }
@@ -1346,22 +1364,28 @@ ${combinedText}` }, ...images];
1346
1364
  "Content-Type": "application/json",
1347
1365
  "Authorization": `Bearer ${this.config.aiApiKey}`
1348
1366
  };
1349
- const response = await this.http.post(fullUrl, payload, { headers, timeout: 6e5 });
1350
- const content = response?.choices?.[0]?.message?.content;
1351
- if (!content?.trim()) throw import_console.error;
1352
- const candidates = [];
1353
- const jsonBlockMatch = content.match(/```json\s*([\s\S]*?)\s*```/i);
1354
- if (jsonBlockMatch && jsonBlockMatch[1]) candidates.push(jsonBlockMatch[1]);
1355
- candidates.push(content);
1356
- const firstBrace = content.indexOf("{");
1357
- const lastBrace = content.lastIndexOf("}");
1358
- if (firstBrace !== -1 && lastBrace > firstBrace) candidates.push(content.substring(firstBrace, lastBrace + 1));
1359
- for (const candidate of [...new Set(candidates)]) try {
1360
- return JSON.parse(candidate);
1361
- } catch {
1367
+ try {
1368
+ const response = await this.http.post(fullUrl, payload, { headers, timeout: 6e5 });
1369
+ const content = response?.choices?.[0]?.message?.content;
1370
+ if (!content?.trim()) throw new Error();
1371
+ const candidates = [];
1372
+ const jsonBlockMatch = content.match(/```json\s*([\s\S]*?)\s*```/i);
1373
+ if (jsonBlockMatch && jsonBlockMatch[1]) candidates.push(jsonBlockMatch[1]);
1374
+ candidates.push(content);
1375
+ const firstBrace = content.indexOf("{");
1376
+ const lastBrace = content.lastIndexOf("}");
1377
+ if (firstBrace !== -1 && lastBrace > firstBrace) candidates.push(content.substring(firstBrace, lastBrace + 1));
1378
+ for (const candidate of [...new Set(candidates)]) {
1379
+ try {
1380
+ return JSON.parse(candidate);
1381
+ } catch (parseError) {
1382
+ }
1383
+ }
1384
+ this.logger.error("解析失败", "原始响应:", JSON.stringify(response, null, 2));
1385
+ throw new Error();
1386
+ } catch (e) {
1387
+ throw e;
1362
1388
  }
1363
- this.logger.error("解析失败:", import_console.error, "原始响应:", JSON.stringify(response, null, 2));
1364
- throw import_console.error;
1365
1389
  }
1366
1390
  };
1367
1391
 
@@ -1440,8 +1464,8 @@ function apply(ctx, config) {
1440
1464
  await ctx.database.upsert("cave", idsToMark);
1441
1465
  await cleanupPendingDeletions(ctx, config, fileManager, logger, reusableIds);
1442
1466
  }
1443
- } catch (error2) {
1444
- logger.error("清理残留回声洞时发生错误:", error2);
1467
+ } catch (error) {
1468
+ logger.error("清理残留回声洞时发生错误:", error);
1445
1469
  }
1446
1470
  });
1447
1471
  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 }) => {
@@ -1457,8 +1481,8 @@ function apply(ctx, config) {
1457
1481
  const [randomCave] = await ctx.database.get("cave", { ...query, id: randomId });
1458
1482
  const messages = await buildCaveMessage(randomCave, config, fileManager, logger, session.platform);
1459
1483
  for (const message of messages) if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1460
- } catch (error2) {
1461
- logger.error("随机获取回声洞失败:", error2);
1484
+ } catch (error) {
1485
+ logger.error("随机获取回声洞失败:", error);
1462
1486
  return "随机获取回声洞失败";
1463
1487
  }
1464
1488
  });
@@ -1534,11 +1558,11 @@ function apply(ctx, config) {
1534
1558
  if (allHashesToInsert.length > 0) await ctx.database.upsert("cave_hash", allHashesToInsert);
1535
1559
  }
1536
1560
  if (finalStatus === "pending" && reviewManager) reviewManager.sendForPend(newCave);
1537
- } catch (error2) {
1538
- logger.error(`回声洞(${newId})处理失败:`, error2);
1561
+ } catch (error) {
1562
+ logger.error(`回声洞(${newId})处理失败:`, error);
1539
1563
  await ctx.database.upsert("cave", [{ id: newId, status: "delete" }]);
1540
1564
  await cleanupPendingDeletions(ctx, config, fileManager, logger, reusableIds);
1541
- await session.send(`回声洞(${newId})处理失败: ${error2.message}`);
1565
+ await session.send(`回声洞(${newId})处理失败: ${error.message}`);
1542
1566
  }
1543
1567
  })();
1544
1568
  });
@@ -1549,8 +1573,8 @@ function apply(ctx, config) {
1549
1573
  if (!targetCave) return `回声洞(${id})不存在`;
1550
1574
  const messages = await buildCaveMessage(targetCave, config, fileManager, logger, session.platform);
1551
1575
  for (const message of messages) if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1552
- } catch (error2) {
1553
- logger.error(`查看回声洞(${id})失败:`, error2);
1576
+ } catch (error) {
1577
+ logger.error(`查看回声洞(${id})失败:`, error);
1554
1578
  return "查看失败,请稍后再试";
1555
1579
  }
1556
1580
  });
@@ -1566,8 +1590,8 @@ function apply(ctx, config) {
1566
1590
  const caveMessages = await buildCaveMessage(targetCave, config, fileManager, logger, session.platform, "已删除");
1567
1591
  for (const message of caveMessages) if (message.length > 0) await session.send(import_koishi3.h.normalize(message));
1568
1592
  cleanupPendingDeletions(ctx, config, fileManager, logger, reusableIds);
1569
- } catch (error2) {
1570
- logger.error(`标记回声洞(${id})失败:`, error2);
1593
+ } catch (error) {
1594
+ logger.error(`标记回声洞(${id})失败:`, error);
1571
1595
  return "删除失败,请稍后再试";
1572
1596
  }
1573
1597
  });
@@ -1596,8 +1620,8 @@ function apply(ctx, config) {
1596
1620
  `;
1597
1621
  });
1598
1622
  return report.trim();
1599
- } catch (error2) {
1600
- logger.error("查询排行失败:", error2);
1623
+ } catch (error) {
1624
+ logger.error("查询排行失败:", error);
1601
1625
  return "查询失败,请稍后再试";
1602
1626
  }
1603
1627
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "功能强大、高度可定制的回声洞插件。支持丰富的媒体类型、内容查重、AI分析、人工审核、用户昵称、数据迁移以及本地/S3 双重文件存储后端。",
4
- "version": "2.7.25",
4
+ "version": "2.7.27",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],