koishi-plugin-best-cave 1.5.3 → 1.5.4

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/index.js CHANGED
@@ -53,9 +53,9 @@ __export(src_exports, {
53
53
  name: () => name
54
54
  });
55
55
  module.exports = __toCommonJS(src_exports);
56
- var import_koishi5 = require("koishi");
57
- var fs5 = __toESM(require("fs"));
58
- var path5 = __toESM(require("path"));
56
+ var import_koishi6 = require("koishi");
57
+ var fs6 = __toESM(require("fs"));
58
+ var path6 = __toESM(require("path"));
59
59
 
60
60
  // src/utils/FileHandle.ts
61
61
  var fs = __toESM(require("fs"));
@@ -1188,37 +1188,181 @@ ${session.text("commands.cave.audit.from")}${cave.contributor_number}`;
1188
1188
  }
1189
1189
  };
1190
1190
 
1191
+ // src/utils/MediaHandle.ts
1192
+ var import_koishi5 = require("koishi");
1193
+ var fs5 = __toESM(require("fs"));
1194
+ var path5 = __toESM(require("path"));
1195
+ var logger4 = new import_koishi5.Logger("MediaHandle");
1196
+ async function extractMediaContent(originalContent, config, session) {
1197
+ const textParts = originalContent.split(/<(img|video)[^>]+>/).map((text, idx) => text.trim() && {
1198
+ type: "text",
1199
+ content: text.replace(/^(img|video)$/, "").trim(),
1200
+ index: idx * 3
1201
+ }).filter((text) => text && text.content);
1202
+ const getMediaElements = /* @__PURE__ */ __name((type, maxSize) => {
1203
+ const regex = new RegExp(`<${type}[^>]+src="([^"]+)"[^>]*>`, "g");
1204
+ const elements = [];
1205
+ const urls = [];
1206
+ let match;
1207
+ let idx = 0;
1208
+ while ((match = regex.exec(originalContent)) !== null) {
1209
+ const element = match[0];
1210
+ const url = match[1];
1211
+ const fileName = element.match(/file="([^"]+)"/)?.[1];
1212
+ const fileSize = element.match(/fileSize="([^"]+)"/)?.[1];
1213
+ if (fileSize) {
1214
+ const sizeInBytes = parseInt(fileSize);
1215
+ if (sizeInBytes > maxSize * 1024 * 1024) {
1216
+ throw new Error(session.text("commands.cave.message.mediaSizeExceeded", [type]));
1217
+ }
1218
+ }
1219
+ urls.push(url);
1220
+ elements.push({
1221
+ type,
1222
+ index: type === "video" ? Number.MAX_SAFE_INTEGER : idx * 3 + 1,
1223
+ fileName,
1224
+ fileSize
1225
+ });
1226
+ idx++;
1227
+ }
1228
+ return { urls, elements };
1229
+ }, "getMediaElements");
1230
+ const { urls: imageUrls, elements: imageElementsRaw } = getMediaElements("img", config.imageMaxSize);
1231
+ const imageElements = imageElementsRaw;
1232
+ const { urls: videoUrls, elements: videoElementsRaw } = getMediaElements("video", config.videoMaxSize);
1233
+ const videoElements = videoElementsRaw;
1234
+ return { imageUrls, imageElements, videoUrls, videoElements, textParts };
1235
+ }
1236
+ __name(extractMediaContent, "extractMediaContent");
1237
+ async function saveMedia(urls, fileNames, resourceDir, caveId, mediaType, config, ctx, session, buffers) {
1238
+ const accept = mediaType === "img" ? "image/*" : "video/*";
1239
+ const hashStorage = new ContentHashManager(path5.join(ctx.baseDir, "data", "cave"));
1240
+ await hashStorage.initialize();
1241
+ const downloadTasks = urls.map(async (url, i) => {
1242
+ const fileName = fileNames[i];
1243
+ const ext = path5.extname(fileName || url) || (mediaType === "img" ? ".png" : ".mp4");
1244
+ try {
1245
+ const response = await ctx.http(decodeURIComponent(url).replace(/&amp;/g, "&"), {
1246
+ method: "GET",
1247
+ responseType: "arraybuffer",
1248
+ timeout: 3e4,
1249
+ headers: {
1250
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
1251
+ "Accept": accept,
1252
+ "Referer": "https://qq.com"
1253
+ }
1254
+ });
1255
+ if (!response.data) throw new Error("empty_response");
1256
+ const buffer = Buffer.from(response.data);
1257
+ if (buffers && mediaType === "img") {
1258
+ buffers.push(buffer);
1259
+ }
1260
+ const md5 = path5.basename(fileName || `${mediaType}`, ext).replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "");
1261
+ const files = await fs5.promises.readdir(resourceDir);
1262
+ const duplicateFile = files.find((file) => {
1263
+ const match = file.match(/^\d+_([^.]+)/);
1264
+ return match && match[1] === md5;
1265
+ });
1266
+ if (duplicateFile) {
1267
+ const duplicateCaveId = parseInt(duplicateFile.split("_")[0]);
1268
+ if (!isNaN(duplicateCaveId)) {
1269
+ const caveFilePath = path5.join(ctx.baseDir, "data", "cave", "cave.json");
1270
+ const data = await FileHandler.readJsonData(caveFilePath);
1271
+ const originalCave = data.find((item) => item.cave_id === duplicateCaveId);
1272
+ if (originalCave) {
1273
+ const message = session.text("commands.cave.error.exactDuplicateFound");
1274
+ await session.send(message + await buildMessage(originalCave, resourceDir, session));
1275
+ throw new Error("duplicate_found");
1276
+ }
1277
+ }
1278
+ }
1279
+ if (mediaType === "img" && config.enableImageDuplicate) {
1280
+ const result = await hashStorage.findDuplicates(
1281
+ { images: [buffer] },
1282
+ {
1283
+ image: config.imageDuplicateThreshold,
1284
+ text: config.textDuplicateThreshold
1285
+ }
1286
+ );
1287
+ if (result.length > 0 && result[0] !== null) {
1288
+ const duplicate = result[0];
1289
+ const similarity = duplicate.similarity;
1290
+ if (similarity >= config.imageDuplicateThreshold) {
1291
+ const caveFilePath = path5.join(ctx.baseDir, "data", "cave", "cave.json");
1292
+ const data = await FileHandler.readJsonData(caveFilePath);
1293
+ const originalCave = data.find((item) => item.cave_id === duplicate.caveId);
1294
+ if (originalCave) {
1295
+ const message = session.text(
1296
+ "commands.cave.error.similarDuplicateFound",
1297
+ [(similarity * 100).toFixed(1)]
1298
+ );
1299
+ await session.send(message + await buildMessage(originalCave, resourceDir, session));
1300
+ throw new Error("duplicate_found");
1301
+ }
1302
+ }
1303
+ }
1304
+ }
1305
+ const finalFileName = `${caveId}_${md5}${ext}`;
1306
+ const filePath = path5.join(resourceDir, finalFileName);
1307
+ await FileHandler.saveMediaFile(filePath, buffer);
1308
+ return finalFileName;
1309
+ } catch (error) {
1310
+ if (error.message === "duplicate_found") {
1311
+ throw error;
1312
+ }
1313
+ logger4.error(`Failed to download media: ${error.message}`);
1314
+ throw new Error(session.text(`commands.cave.error.upload${mediaType === "img" ? "Image" : "Video"}Failed`));
1315
+ }
1316
+ });
1317
+ return Promise.all(downloadTasks);
1318
+ }
1319
+ __name(saveMedia, "saveMedia");
1320
+ async function buildMessage(cave, resourceDir, session) {
1321
+ if (!cave?.elements?.length) {
1322
+ return session.text("commands.cave.error.noContent");
1323
+ }
1324
+ const lines = [session.text("commands.cave.message.caveTitle", [cave.cave_id])];
1325
+ for (const element of cave.elements) {
1326
+ if (element.type === "text") {
1327
+ lines.push(element.content);
1328
+ }
1329
+ }
1330
+ lines.push(session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]));
1331
+ return lines.join("\n");
1332
+ }
1333
+ __name(buildMessage, "buildMessage");
1334
+
1191
1335
  // src/index.ts
1192
1336
  var name = "best-cave";
1193
1337
  var inject = ["database"];
1194
- var Config = import_koishi5.Schema.object({
1195
- manager: import_koishi5.Schema.array(import_koishi5.Schema.string()).required(),
1338
+ var Config = import_koishi6.Schema.object({
1339
+ manager: import_koishi6.Schema.array(import_koishi6.Schema.string()).required(),
1196
1340
  // 管理员用户ID
1197
- number: import_koishi5.Schema.number().default(60),
1341
+ number: import_koishi6.Schema.number().default(60),
1198
1342
  // 冷却时间(秒)
1199
- enableAudit: import_koishi5.Schema.boolean().default(false),
1343
+ enableAudit: import_koishi6.Schema.boolean().default(false),
1200
1344
  // 启用审核
1201
- enableTextDuplicate: import_koishi5.Schema.boolean().default(true),
1345
+ enableTextDuplicate: import_koishi6.Schema.boolean().default(true),
1202
1346
  // 启用文本查重
1203
- textDuplicateThreshold: import_koishi5.Schema.number().default(0.9),
1347
+ textDuplicateThreshold: import_koishi6.Schema.number().default(0.9),
1204
1348
  // 文本查重阈值
1205
- enableImageDuplicate: import_koishi5.Schema.boolean().default(true),
1349
+ enableImageDuplicate: import_koishi6.Schema.boolean().default(true),
1206
1350
  // 开启图片查重
1207
- imageDuplicateThreshold: import_koishi5.Schema.number().default(0.8),
1351
+ imageDuplicateThreshold: import_koishi6.Schema.number().default(0.8),
1208
1352
  // 图片查重阈值
1209
- imageMaxSize: import_koishi5.Schema.number().default(4),
1353
+ imageMaxSize: import_koishi6.Schema.number().default(4),
1210
1354
  // 图片大小限制(MB)
1211
- allowVideo: import_koishi5.Schema.boolean().default(true),
1355
+ allowVideo: import_koishi6.Schema.boolean().default(true),
1212
1356
  // 允许视频
1213
- videoMaxSize: import_koishi5.Schema.number().default(16),
1357
+ videoMaxSize: import_koishi6.Schema.number().default(16),
1214
1358
  // 视频大小限制(MB)
1215
- enablePagination: import_koishi5.Schema.boolean().default(false),
1359
+ enablePagination: import_koishi6.Schema.boolean().default(false),
1216
1360
  // 启用分页
1217
- itemsPerPage: import_koishi5.Schema.number().default(10),
1361
+ itemsPerPage: import_koishi6.Schema.number().default(10),
1218
1362
  // 每页条数
1219
- blacklist: import_koishi5.Schema.array(import_koishi5.Schema.string()).default([]),
1363
+ blacklist: import_koishi6.Schema.array(import_koishi6.Schema.string()).default([]),
1220
1364
  // 黑名单
1221
- whitelist: import_koishi5.Schema.array(import_koishi5.Schema.string()).default([])
1365
+ whitelist: import_koishi6.Schema.array(import_koishi6.Schema.string()).default([])
1222
1366
  // 白名单
1223
1367
  }).i18n({
1224
1368
  "zh-CN": require_zh_CN()._config,
@@ -1227,19 +1371,19 @@ var Config = import_koishi5.Schema.object({
1227
1371
  async function apply(ctx, config) {
1228
1372
  ctx.i18n.define("zh-CN", require_zh_CN());
1229
1373
  ctx.i18n.define("en-US", require_en_US());
1230
- const dataDir = path5.join(ctx.baseDir, "data");
1231
- const caveDir = path5.join(dataDir, "cave");
1374
+ const dataDir = path6.join(ctx.baseDir, "data");
1375
+ const caveDir = path6.join(dataDir, "cave");
1232
1376
  await FileHandler.ensureDirectory(dataDir);
1233
1377
  await FileHandler.ensureDirectory(caveDir);
1234
- await FileHandler.ensureDirectory(path5.join(caveDir, "resources"));
1235
- await FileHandler.ensureJsonFile(path5.join(caveDir, "cave.json"));
1236
- await FileHandler.ensureJsonFile(path5.join(caveDir, "pending.json"));
1237
- await FileHandler.ensureJsonFile(path5.join(caveDir, "hash.json"));
1378
+ await FileHandler.ensureDirectory(path6.join(caveDir, "resources"));
1379
+ await FileHandler.ensureJsonFile(path6.join(caveDir, "cave.json"));
1380
+ await FileHandler.ensureJsonFile(path6.join(caveDir, "pending.json"));
1381
+ await FileHandler.ensureJsonFile(path6.join(caveDir, "hash.json"));
1238
1382
  const idManager = new IdManager(ctx.baseDir);
1239
1383
  const contentHashManager = new ContentHashManager(caveDir);
1240
1384
  const auditManager = new AuditManager(ctx, config, caveDir, idManager);
1241
1385
  await Promise.all([
1242
- idManager.initialize(path5.join(caveDir, "cave.json"), path5.join(caveDir, "pending.json")),
1386
+ idManager.initialize(path6.join(caveDir, "cave.json"), path6.join(caveDir, "pending.json")),
1243
1387
  contentHashManager.initialize()
1244
1388
  ]);
1245
1389
  const lastUsed = /* @__PURE__ */ new Map();
@@ -1265,26 +1409,13 @@ async function apply(ctx, config) {
1265
1409
  }
1266
1410
  }
1267
1411
  __name(processList, "processList");
1268
- async function processAudit(pendingFilePath, caveFilePath, resourceDir, session, options, content) {
1269
- const pendingData = await FileHandler.readJsonData(pendingFilePath);
1270
- const isApprove = Boolean(options.p);
1271
- if (options.p === true && content[0] === "all" || options.d === true && content[0] === "all") {
1272
- return await auditManager.processAudit(pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, session);
1273
- }
1274
- const id = parseInt(content[0] || (typeof options.p === "string" ? options.p : "") || (typeof options.d === "string" ? options.d : ""));
1275
- if (isNaN(id)) {
1276
- return sendMessage(session, "commands.cave.error.invalidId", [], true);
1277
- }
1278
- return await auditManager.processAudit(pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, session, id);
1279
- }
1280
- __name(processAudit, "processAudit");
1281
1412
  async function processView(caveFilePath, resourceDir, session, options, content) {
1282
1413
  const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
1283
1414
  if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
1284
1415
  const data = await FileHandler.readJsonData(caveFilePath);
1285
1416
  const cave = data.find((item) => item.cave_id === caveId);
1286
1417
  if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
1287
- return buildMessage(cave, resourceDir, session);
1418
+ return buildMessage2(cave, resourceDir, session);
1288
1419
  }
1289
1420
  __name(processView, "processView");
1290
1421
  async function processRandom(caveFilePath, resourceDir, session) {
@@ -1298,7 +1429,7 @@ async function apply(ctx, config) {
1298
1429
  const randomIndex = Math.floor(Math.random() * validCaves.length);
1299
1430
  return validCaves[randomIndex];
1300
1431
  })();
1301
- return cave ? buildMessage(cave, resourceDir, session) : sendMessage(session, "commands.cave.error.getCave", [], true);
1432
+ return cave ? buildMessage2(cave, resourceDir, session) : sendMessage(session, "commands.cave.error.getCave", [], true);
1302
1433
  }
1303
1434
  __name(processRandom, "processRandom");
1304
1435
  async function processDelete(caveFilePath, resourceDir, pendingFilePath, session, config2, options, content) {
@@ -1316,7 +1447,7 @@ async function apply(ctx, config) {
1316
1447
  if (targetCave.contributor_number !== session.userId && !config2.manager.includes(session.userId)) {
1317
1448
  return sendMessage(session, "commands.cave.remove.noPermission", [], true);
1318
1449
  }
1319
- const caveContent = await buildMessage(targetCave, resourceDir, session);
1450
+ const caveContent = await buildMessage2(targetCave, resourceDir, session);
1320
1451
  if (targetCave.elements) {
1321
1452
  await contentHashManager.updateCaveContent(caveId, {
1322
1453
  images: void 0,
@@ -1324,9 +1455,9 @@ async function apply(ctx, config) {
1324
1455
  });
1325
1456
  for (const element of targetCave.elements) {
1326
1457
  if ((element.type === "img" || element.type === "video") && element.file) {
1327
- const fullPath = path5.join(resourceDir, element.file);
1328
- if (fs5.existsSync(fullPath)) {
1329
- await fs5.promises.unlink(fullPath);
1458
+ const fullPath = path6.join(resourceDir, element.file);
1459
+ if (fs6.existsSync(fullPath)) {
1460
+ await fs6.promises.unlink(fullPath);
1330
1461
  }
1331
1462
  }
1332
1463
  }
@@ -1355,9 +1486,15 @@ async function apply(ctx, config) {
1355
1486
  const inputContent = content.length > 0 ? content.join("\n") : await (async () => {
1356
1487
  await sendMessage(session, "commands.cave.add.noContent", [], true, 6e4);
1357
1488
  const reply = await session.prompt({ timeout: 6e4 });
1358
- if (!reply) sendMessage(session, "commands.cave.add.operationTimeout", [], true);
1489
+ if (!reply) {
1490
+ await sendMessage(session, "commands.cave.add.operationTimeout", [], true);
1491
+ return null;
1492
+ }
1359
1493
  return reply;
1360
1494
  })();
1495
+ if (!inputContent) {
1496
+ return "";
1497
+ }
1361
1498
  if (inputContent.includes("/app/.config/QQ/")) {
1362
1499
  return sendMessage(session, "commands.cave.add.localFileNotAllowed", [], true);
1363
1500
  }
@@ -1415,7 +1552,7 @@ async function apply(ctx, config) {
1415
1552
  index: Number.MAX_SAFE_INTEGER
1416
1553
  });
1417
1554
  }
1418
- const hashStorage = new ContentHashManager(path5.join(ctx2.baseDir, "data", "cave"));
1555
+ const hashStorage = new ContentHashManager(path6.join(ctx2.baseDir, "data", "cave"));
1419
1556
  await hashStorage.initialize();
1420
1557
  const hashStatus = await hashStorage.getStatus();
1421
1558
  if (!hashStatus.lastUpdated || hashStatus.entries.length === 0) {
@@ -1432,7 +1569,7 @@ async function apply(ctx, config) {
1432
1569
  pendingData.push(newCave);
1433
1570
  await Promise.all([
1434
1571
  FileHandler.writeJsonData(pendingFilePath, pendingData),
1435
- auditManager.sendAuditMessage(newCave, await buildMessage(newCave, resourceDir, session), session)
1572
+ auditManager.sendAuditMessage(newCave, await buildMessage2(newCave, resourceDir, session), session)
1436
1573
  ]);
1437
1574
  return sendMessage(session, "commands.cave.add.submitPending", [caveId], false);
1438
1575
  }
@@ -1458,14 +1595,14 @@ async function apply(ctx, config) {
1458
1595
  "commands.cave.error.similarDuplicateFound",
1459
1596
  [(result.similarity * 100).toFixed(1)]
1460
1597
  );
1461
- await session.send(duplicateMessage + await buildMessage(originalCave, resourceDir, session));
1598
+ await session.send(duplicateMessage + await buildMessage2(originalCave, resourceDir, session));
1462
1599
  throw new Error("duplicate_found");
1463
1600
  }
1464
1601
  }
1465
1602
  await Promise.all([
1466
1603
  FileHandler.writeJsonData(caveFilePath, data),
1467
1604
  contentHashManager.updateCaveContent(caveId, {
1468
- images: savedImages.length > 0 ? await Promise.all(savedImages.map((file) => fs5.promises.readFile(path5.join(resourceDir, file)))) : void 0,
1605
+ images: savedImages.length > 0 ? await Promise.all(savedImages.map((file) => fs6.promises.readFile(path6.join(resourceDir, file)))) : void 0,
1469
1606
  texts: textParts.filter((p) => p.type === "text").map((p) => p.content)
1470
1607
  })
1471
1608
  ]);
@@ -1478,7 +1615,7 @@ async function apply(ctx, config) {
1478
1615
  if (error.message === "duplicate_found") {
1479
1616
  return "";
1480
1617
  }
1481
- logger4.error(`Failed to process add command: ${error.message}`);
1618
+ logger5.error(`Failed to process add command: ${error.message}`);
1482
1619
  return sendMessage(session, "commands.cave.error.addFailed", [], true);
1483
1620
  }
1484
1621
  }
@@ -1488,11 +1625,11 @@ async function apply(ctx, config) {
1488
1625
  return sendMessage(session, "commands.cave.message.blacklisted", [], true);
1489
1626
  }
1490
1627
  }).action(async ({ session, options }, ...content) => {
1491
- const dataDir2 = path5.join(ctx.baseDir, "data");
1492
- const caveDir2 = path5.join(dataDir2, "cave");
1493
- const caveFilePath = path5.join(caveDir2, "cave.json");
1494
- const resourceDir = path5.join(caveDir2, "resources");
1495
- const pendingFilePath = path5.join(caveDir2, "pending.json");
1628
+ const dataDir2 = path6.join(ctx.baseDir, "data");
1629
+ const caveDir2 = path6.join(dataDir2, "cave");
1630
+ const caveFilePath = path6.join(caveDir2, "cave.json");
1631
+ const resourceDir = path6.join(caveDir2, "resources");
1632
+ const pendingFilePath = path6.join(caveDir2, "pending.json");
1496
1633
  const needsCooldown = !options.l && !options.a;
1497
1634
  if (needsCooldown) {
1498
1635
  const guildId = session.guildId;
@@ -1539,42 +1676,30 @@ async function apply(ctx, config) {
1539
1676
  return sendMessage(session, "commands.cave.message.managerOnly", [], true);
1540
1677
  }
1541
1678
  }).action(async ({ session }, id) => {
1542
- const dataDir2 = path5.join(ctx.baseDir, "data");
1543
- const caveDir2 = path5.join(dataDir2, "cave");
1544
- const caveFilePath = path5.join(caveDir2, "cave.json");
1545
- const resourceDir = path5.join(caveDir2, "resources");
1546
- const pendingFilePath = path5.join(caveDir2, "pending.json");
1547
- return await processAudit(
1548
- pendingFilePath,
1549
- caveFilePath,
1550
- resourceDir,
1551
- session,
1552
- { p: true },
1553
- [id]
1554
- );
1679
+ const dataDir2 = path6.join(ctx.baseDir, "data");
1680
+ const caveDir2 = path6.join(dataDir2, "cave");
1681
+ const caveFilePath = path6.join(caveDir2, "cave.json");
1682
+ const resourceDir = path6.join(caveDir2, "resources");
1683
+ const pendingFilePath = path6.join(caveDir2, "pending.json");
1684
+ const pendingData = await FileHandler.readJsonData(pendingFilePath);
1685
+ return await auditManager.processAudit(pendingData, true, caveFilePath, resourceDir, pendingFilePath, session, id === "all" ? void 0 : parseInt(id));
1555
1686
  });
1556
1687
  caveCommand.subcommand(".reject <id:text>", "拒绝回声洞审核").before(async ({ session }) => {
1557
1688
  if (!config.manager.includes(session.userId)) {
1558
1689
  return sendMessage(session, "commands.cave.message.managerOnly", [], true);
1559
1690
  }
1560
1691
  }).action(async ({ session }, id) => {
1561
- const dataDir2 = path5.join(ctx.baseDir, "data");
1562
- const caveDir2 = path5.join(dataDir2, "cave");
1563
- const caveFilePath = path5.join(caveDir2, "cave.json");
1564
- const resourceDir = path5.join(caveDir2, "resources");
1565
- const pendingFilePath = path5.join(caveDir2, "pending.json");
1566
- return await processAudit(
1567
- pendingFilePath,
1568
- caveFilePath,
1569
- resourceDir,
1570
- session,
1571
- { d: true },
1572
- [id]
1573
- );
1692
+ const dataDir2 = path6.join(ctx.baseDir, "data");
1693
+ const caveDir2 = path6.join(dataDir2, "cave");
1694
+ const caveFilePath = path6.join(caveDir2, "cave.json");
1695
+ const resourceDir = path6.join(caveDir2, "resources");
1696
+ const pendingFilePath = path6.join(caveDir2, "pending.json");
1697
+ const pendingData = await FileHandler.readJsonData(pendingFilePath);
1698
+ return await auditManager.processAudit(pendingData, false, caveFilePath, resourceDir, pendingFilePath, session, id === "all" ? void 0 : parseInt(id));
1574
1699
  });
1575
1700
  }
1576
1701
  __name(apply, "apply");
1577
- var logger4 = new import_koishi5.Logger("cave");
1702
+ var logger5 = new import_koishi6.Logger("cave");
1578
1703
  async function sendMessage(session, key, params = [], isTemp = true, timeout = 1e4) {
1579
1704
  try {
1580
1705
  const msg = await session.send(session.text(key, params));
@@ -1583,12 +1708,12 @@ async function sendMessage(session, key, params = [], isTemp = true, timeout = 1
1583
1708
  try {
1584
1709
  await session.bot.deleteMessage(session.channelId, msg);
1585
1710
  } catch (error) {
1586
- logger4.debug(`Failed to delete temporary message: ${error.message}`);
1711
+ logger5.debug(`Failed to delete temporary message: ${error.message}`);
1587
1712
  }
1588
1713
  }, timeout);
1589
1714
  }
1590
1715
  } catch (error) {
1591
- logger4.error(`Failed to send message: ${error.message}`);
1716
+ logger5.error(`Failed to send message: ${error.message}`);
1592
1717
  }
1593
1718
  return "";
1594
1719
  }
@@ -1618,12 +1743,12 @@ function cleanElementsForSave(elements, keepIndex = false) {
1618
1743
  }
1619
1744
  __name(cleanElementsForSave, "cleanElementsForSave");
1620
1745
  async function processMediaFile(filePath, type) {
1621
- const data = await fs5.promises.readFile(filePath).catch(() => null);
1746
+ const data = await fs6.promises.readFile(filePath).catch(() => null);
1622
1747
  if (!data) return null;
1623
1748
  return `data:${type}/${type === "image" ? "png" : "mp4"};base64,${data.toString("base64")}`;
1624
1749
  }
1625
1750
  __name(processMediaFile, "processMediaFile");
1626
- async function buildMessage(cave, resourceDir, session) {
1751
+ async function buildMessage2(cave, resourceDir, session) {
1627
1752
  if (!cave?.elements?.length) {
1628
1753
  return session.text("commands.cave.error.noContent");
1629
1754
  }
@@ -1635,10 +1760,10 @@ async function buildMessage(cave, resourceDir, session) {
1635
1760
  session.text("commands.cave.message.contributorSuffix", [cave.contributor_name])
1636
1761
  ].join("\n");
1637
1762
  await session?.send(basicInfo);
1638
- const filePath = path5.join(resourceDir, videoElement.file);
1763
+ const filePath = path6.join(resourceDir, videoElement.file);
1639
1764
  const base64Data = await processMediaFile(filePath, "video");
1640
1765
  if (base64Data && session) {
1641
- await session.send((0, import_koishi5.h)("video", { src: base64Data }));
1766
+ await session.send((0, import_koishi6.h)("video", { src: base64Data }));
1642
1767
  }
1643
1768
  return "";
1644
1769
  }
@@ -1647,141 +1772,17 @@ async function buildMessage(cave, resourceDir, session) {
1647
1772
  if (element.type === "text") {
1648
1773
  lines.push(element.content);
1649
1774
  } else if (element.type === "img" && element.file) {
1650
- const filePath = path5.join(resourceDir, element.file);
1775
+ const filePath = path6.join(resourceDir, element.file);
1651
1776
  const base64Data = await processMediaFile(filePath, "image");
1652
1777
  if (base64Data) {
1653
- lines.push((0, import_koishi5.h)("image", { src: base64Data }));
1778
+ lines.push((0, import_koishi6.h)("image", { src: base64Data }));
1654
1779
  }
1655
1780
  }
1656
1781
  }
1657
1782
  lines.push(session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]));
1658
1783
  return lines.join("\n");
1659
1784
  }
1660
- __name(buildMessage, "buildMessage");
1661
- async function extractMediaContent(originalContent, config, session) {
1662
- const textParts = originalContent.split(/<(img|video)[^>]+>/).map((text, idx) => text.trim() && {
1663
- type: "text",
1664
- content: text.replace(/^(img|video)$/, "").trim(),
1665
- index: idx * 3
1666
- }).filter((text) => text && text.content);
1667
- const getMediaElements = /* @__PURE__ */ __name((type, maxSize) => {
1668
- const regex = new RegExp(`<${type}[^>]+src="([^"]+)"[^>]*>`, "g");
1669
- const elements = [];
1670
- const urls = [];
1671
- let match;
1672
- let idx = 0;
1673
- while ((match = regex.exec(originalContent)) !== null) {
1674
- const element = match[0];
1675
- const url = match[1];
1676
- const fileName = element.match(/file="([^"]+)"/)?.[1];
1677
- const fileSize = element.match(/fileSize="([^"]+)"/)?.[1];
1678
- if (fileSize) {
1679
- const sizeInBytes = parseInt(fileSize);
1680
- if (sizeInBytes > maxSize * 1024 * 1024) {
1681
- throw new Error(session.text("commands.cave.message.mediaSizeExceeded", [type]));
1682
- }
1683
- }
1684
- urls.push(url);
1685
- elements.push({
1686
- type,
1687
- index: type === "video" ? Number.MAX_SAFE_INTEGER : idx * 3 + 1,
1688
- fileName,
1689
- fileSize
1690
- });
1691
- idx++;
1692
- }
1693
- return { urls, elements };
1694
- }, "getMediaElements");
1695
- const { urls: imageUrls, elements: imageElementsRaw } = getMediaElements("img", config.imageMaxSize);
1696
- const imageElements = imageElementsRaw;
1697
- const { urls: videoUrls, elements: videoElementsRaw } = getMediaElements("video", config.videoMaxSize);
1698
- const videoElements = videoElementsRaw;
1699
- return { imageUrls, imageElements, videoUrls, videoElements, textParts };
1700
- }
1701
- __name(extractMediaContent, "extractMediaContent");
1702
- async function saveMedia(urls, fileNames, resourceDir, caveId, mediaType, config, ctx, session, buffers) {
1703
- const accept = mediaType === "img" ? "image/*" : "video/*";
1704
- const hashStorage = new ContentHashManager(path5.join(ctx.baseDir, "data", "cave"));
1705
- await hashStorage.initialize();
1706
- const downloadTasks = urls.map(async (url, i) => {
1707
- const fileName = fileNames[i];
1708
- const ext = path5.extname(fileName || url) || (mediaType === "img" ? ".png" : ".mp4");
1709
- try {
1710
- const response = await ctx.http(decodeURIComponent(url).replace(/&amp;/g, "&"), {
1711
- method: "GET",
1712
- responseType: "arraybuffer",
1713
- timeout: 3e4,
1714
- headers: {
1715
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
1716
- "Accept": accept,
1717
- "Referer": "https://qq.com"
1718
- }
1719
- });
1720
- if (!response.data) throw new Error("empty_response");
1721
- const buffer = Buffer.from(response.data);
1722
- if (buffers && mediaType === "img") {
1723
- buffers.push(buffer);
1724
- }
1725
- const md5 = path5.basename(fileName || `${mediaType}`, ext).replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "");
1726
- const files = await fs5.promises.readdir(resourceDir);
1727
- const duplicateFile = files.find((file) => {
1728
- const match = file.match(/^\d+_([^.]+)/);
1729
- return match && match[1] === md5;
1730
- });
1731
- if (duplicateFile) {
1732
- const duplicateCaveId = parseInt(duplicateFile.split("_")[0]);
1733
- if (!isNaN(duplicateCaveId)) {
1734
- const caveFilePath = path5.join(ctx.baseDir, "data", "cave", "cave.json");
1735
- const data = await FileHandler.readJsonData(caveFilePath);
1736
- const originalCave = data.find((item) => item.cave_id === duplicateCaveId);
1737
- if (originalCave) {
1738
- const message = session.text("commands.cave.error.exactDuplicateFound");
1739
- await session.send(message + await buildMessage(originalCave, resourceDir, session));
1740
- throw new Error("duplicate_found");
1741
- }
1742
- }
1743
- }
1744
- if (mediaType === "img" && config.enableImageDuplicate) {
1745
- const result = await hashStorage.findDuplicates(
1746
- { images: [buffer] },
1747
- {
1748
- image: config.imageDuplicateThreshold,
1749
- text: config.textDuplicateThreshold
1750
- }
1751
- );
1752
- if (result.length > 0 && result[0] !== null) {
1753
- const duplicate = result[0];
1754
- const similarity = duplicate.similarity;
1755
- if (similarity >= config.imageDuplicateThreshold) {
1756
- const caveFilePath = path5.join(ctx.baseDir, "data", "cave", "cave.json");
1757
- const data = await FileHandler.readJsonData(caveFilePath);
1758
- const originalCave = data.find((item) => item.cave_id === duplicate.caveId);
1759
- if (originalCave) {
1760
- const message = session.text(
1761
- "commands.cave.error.similarDuplicateFound",
1762
- [(similarity * 100).toFixed(1)]
1763
- );
1764
- await session.send(message + await buildMessage(originalCave, resourceDir, session));
1765
- throw new Error("duplicate_found");
1766
- }
1767
- }
1768
- }
1769
- }
1770
- const finalFileName = `${caveId}_${md5}${ext}`;
1771
- const filePath = path5.join(resourceDir, finalFileName);
1772
- await FileHandler.saveMediaFile(filePath, buffer);
1773
- return finalFileName;
1774
- } catch (error) {
1775
- if (error.message === "duplicate_found") {
1776
- throw error;
1777
- }
1778
- logger4.error(`Failed to download media: ${error.message}`);
1779
- throw new Error(session.text(`commands.cave.error.upload${mediaType === "img" ? "Image" : "Video"}Failed`));
1780
- }
1781
- });
1782
- return Promise.all(downloadTasks);
1783
- }
1784
- __name(saveMedia, "saveMedia");
1785
+ __name(buildMessage2, "buildMessage");
1785
1786
  // Annotate the CommonJS export names for ESM import in node:
1786
1787
  0 && (module.exports = {
1787
1788
  Config,
@@ -0,0 +1,42 @@
1
+ import { Context } from 'koishi';
2
+ export interface BaseElement {
3
+ type: 'text' | 'img' | 'video';
4
+ index: number;
5
+ }
6
+ export interface TextElement extends BaseElement {
7
+ type: 'text';
8
+ content: string;
9
+ }
10
+ export interface MediaElement extends BaseElement {
11
+ type: 'img' | 'video';
12
+ file?: string;
13
+ fileName?: string;
14
+ fileSize?: string;
15
+ filePath?: string;
16
+ }
17
+ export type Element = TextElement | MediaElement;
18
+ export declare function extractMediaContent(originalContent: string, config: {
19
+ imageMaxSize: number;
20
+ videoMaxSize: number;
21
+ }, session: any): Promise<{
22
+ imageUrls: string[];
23
+ imageElements: Array<{
24
+ type: 'img';
25
+ index: number;
26
+ fileName?: string;
27
+ fileSize?: string;
28
+ }>;
29
+ videoUrls: string[];
30
+ videoElements: Array<{
31
+ type: 'video';
32
+ index: number;
33
+ fileName?: string;
34
+ fileSize?: string;
35
+ }>;
36
+ textParts: Element[];
37
+ }>;
38
+ export declare function saveMedia(urls: string[], fileNames: (string | undefined)[], resourceDir: string, caveId: number, mediaType: 'img' | 'video', config: {
39
+ enableImageDuplicate: boolean;
40
+ imageDuplicateThreshold: number;
41
+ textDuplicateThreshold: number;
42
+ }, ctx: Context, session: any, buffers?: Buffer[]): Promise<string[]>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "最好的 cave 插件,可开关的审核系统,可引用添加,支持图文混合内容,可查阅投稿列表,完美复刻你的 .cave 体验!",
4
- "version": "1.5.3",
4
+ "version": "1.5.4",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],