koishi-plugin-best-cave 1.5.2 → 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"));
@@ -346,14 +346,20 @@ var IdManager = class {
346
346
  throw new Error("IdManager not initialized");
347
347
  }
348
348
  let nextId;
349
- if (this.deletedIds.size === 0) {
350
- nextId = ++this.maxId;
349
+ if (this.deletedIds.size > 0) {
350
+ const minDeletedId = Math.min(...Array.from(this.deletedIds));
351
+ if (!isNaN(minDeletedId) && minDeletedId > 0) {
352
+ nextId = minDeletedId;
353
+ this.deletedIds.delete(nextId);
354
+ } else {
355
+ nextId = this.maxId + 1;
356
+ }
351
357
  } else {
352
- nextId = Math.min(...Array.from(this.deletedIds));
353
- this.deletedIds.delete(nextId);
358
+ nextId = this.maxId + 1;
354
359
  }
355
- while (this.usedIds.has(nextId)) {
356
- nextId = ++this.maxId;
360
+ while (isNaN(nextId) || nextId <= 0 || this.usedIds.has(nextId)) {
361
+ nextId = this.maxId + 1;
362
+ this.maxId++;
357
363
  }
358
364
  this.usedIds.add(nextId);
359
365
  this.saveStatus().catch(
@@ -1182,37 +1188,181 @@ ${session.text("commands.cave.audit.from")}${cave.contributor_number}`;
1182
1188
  }
1183
1189
  };
1184
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
+
1185
1335
  // src/index.ts
1186
1336
  var name = "best-cave";
1187
1337
  var inject = ["database"];
1188
- var Config = import_koishi5.Schema.object({
1189
- 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(),
1190
1340
  // 管理员用户ID
1191
- number: import_koishi5.Schema.number().default(60),
1341
+ number: import_koishi6.Schema.number().default(60),
1192
1342
  // 冷却时间(秒)
1193
- enableAudit: import_koishi5.Schema.boolean().default(false),
1343
+ enableAudit: import_koishi6.Schema.boolean().default(false),
1194
1344
  // 启用审核
1195
- enableTextDuplicate: import_koishi5.Schema.boolean().default(true),
1345
+ enableTextDuplicate: import_koishi6.Schema.boolean().default(true),
1196
1346
  // 启用文本查重
1197
- textDuplicateThreshold: import_koishi5.Schema.number().default(0.9),
1347
+ textDuplicateThreshold: import_koishi6.Schema.number().default(0.9),
1198
1348
  // 文本查重阈值
1199
- enableImageDuplicate: import_koishi5.Schema.boolean().default(true),
1349
+ enableImageDuplicate: import_koishi6.Schema.boolean().default(true),
1200
1350
  // 开启图片查重
1201
- imageDuplicateThreshold: import_koishi5.Schema.number().default(0.8),
1351
+ imageDuplicateThreshold: import_koishi6.Schema.number().default(0.8),
1202
1352
  // 图片查重阈值
1203
- imageMaxSize: import_koishi5.Schema.number().default(4),
1353
+ imageMaxSize: import_koishi6.Schema.number().default(4),
1204
1354
  // 图片大小限制(MB)
1205
- allowVideo: import_koishi5.Schema.boolean().default(true),
1355
+ allowVideo: import_koishi6.Schema.boolean().default(true),
1206
1356
  // 允许视频
1207
- videoMaxSize: import_koishi5.Schema.number().default(16),
1357
+ videoMaxSize: import_koishi6.Schema.number().default(16),
1208
1358
  // 视频大小限制(MB)
1209
- enablePagination: import_koishi5.Schema.boolean().default(false),
1359
+ enablePagination: import_koishi6.Schema.boolean().default(false),
1210
1360
  // 启用分页
1211
- itemsPerPage: import_koishi5.Schema.number().default(10),
1361
+ itemsPerPage: import_koishi6.Schema.number().default(10),
1212
1362
  // 每页条数
1213
- blacklist: import_koishi5.Schema.array(import_koishi5.Schema.string()).default([]),
1363
+ blacklist: import_koishi6.Schema.array(import_koishi6.Schema.string()).default([]),
1214
1364
  // 黑名单
1215
- whitelist: import_koishi5.Schema.array(import_koishi5.Schema.string()).default([])
1365
+ whitelist: import_koishi6.Schema.array(import_koishi6.Schema.string()).default([])
1216
1366
  // 白名单
1217
1367
  }).i18n({
1218
1368
  "zh-CN": require_zh_CN()._config,
@@ -1221,19 +1371,19 @@ var Config = import_koishi5.Schema.object({
1221
1371
  async function apply(ctx, config) {
1222
1372
  ctx.i18n.define("zh-CN", require_zh_CN());
1223
1373
  ctx.i18n.define("en-US", require_en_US());
1224
- const dataDir = path5.join(ctx.baseDir, "data");
1225
- const caveDir = path5.join(dataDir, "cave");
1374
+ const dataDir = path6.join(ctx.baseDir, "data");
1375
+ const caveDir = path6.join(dataDir, "cave");
1226
1376
  await FileHandler.ensureDirectory(dataDir);
1227
1377
  await FileHandler.ensureDirectory(caveDir);
1228
- await FileHandler.ensureDirectory(path5.join(caveDir, "resources"));
1229
- await FileHandler.ensureJsonFile(path5.join(caveDir, "cave.json"));
1230
- await FileHandler.ensureJsonFile(path5.join(caveDir, "pending.json"));
1231
- 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"));
1232
1382
  const idManager = new IdManager(ctx.baseDir);
1233
1383
  const contentHashManager = new ContentHashManager(caveDir);
1234
1384
  const auditManager = new AuditManager(ctx, config, caveDir, idManager);
1235
1385
  await Promise.all([
1236
- 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")),
1237
1387
  contentHashManager.initialize()
1238
1388
  ]);
1239
1389
  const lastUsed = /* @__PURE__ */ new Map();
@@ -1259,26 +1409,13 @@ async function apply(ctx, config) {
1259
1409
  }
1260
1410
  }
1261
1411
  __name(processList, "processList");
1262
- async function processAudit(pendingFilePath, caveFilePath, resourceDir, session, options, content) {
1263
- const pendingData = await FileHandler.readJsonData(pendingFilePath);
1264
- const isApprove = Boolean(options.p);
1265
- if (options.p === true && content[0] === "all" || options.d === true && content[0] === "all") {
1266
- return await auditManager.processAudit(pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, session);
1267
- }
1268
- const id = parseInt(content[0] || (typeof options.p === "string" ? options.p : "") || (typeof options.d === "string" ? options.d : ""));
1269
- if (isNaN(id)) {
1270
- return sendMessage(session, "commands.cave.error.invalidId", [], true);
1271
- }
1272
- return await auditManager.processAudit(pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, session, id);
1273
- }
1274
- __name(processAudit, "processAudit");
1275
1412
  async function processView(caveFilePath, resourceDir, session, options, content) {
1276
1413
  const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
1277
1414
  if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
1278
1415
  const data = await FileHandler.readJsonData(caveFilePath);
1279
1416
  const cave = data.find((item) => item.cave_id === caveId);
1280
1417
  if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
1281
- return buildMessage(cave, resourceDir, session);
1418
+ return buildMessage2(cave, resourceDir, session);
1282
1419
  }
1283
1420
  __name(processView, "processView");
1284
1421
  async function processRandom(caveFilePath, resourceDir, session) {
@@ -1292,7 +1429,7 @@ async function apply(ctx, config) {
1292
1429
  const randomIndex = Math.floor(Math.random() * validCaves.length);
1293
1430
  return validCaves[randomIndex];
1294
1431
  })();
1295
- 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);
1296
1433
  }
1297
1434
  __name(processRandom, "processRandom");
1298
1435
  async function processDelete(caveFilePath, resourceDir, pendingFilePath, session, config2, options, content) {
@@ -1310,7 +1447,7 @@ async function apply(ctx, config) {
1310
1447
  if (targetCave.contributor_number !== session.userId && !config2.manager.includes(session.userId)) {
1311
1448
  return sendMessage(session, "commands.cave.remove.noPermission", [], true);
1312
1449
  }
1313
- const caveContent = await buildMessage(targetCave, resourceDir, session);
1450
+ const caveContent = await buildMessage2(targetCave, resourceDir, session);
1314
1451
  if (targetCave.elements) {
1315
1452
  await contentHashManager.updateCaveContent(caveId, {
1316
1453
  images: void 0,
@@ -1318,9 +1455,9 @@ async function apply(ctx, config) {
1318
1455
  });
1319
1456
  for (const element of targetCave.elements) {
1320
1457
  if ((element.type === "img" || element.type === "video") && element.file) {
1321
- const fullPath = path5.join(resourceDir, element.file);
1322
- if (fs5.existsSync(fullPath)) {
1323
- await fs5.promises.unlink(fullPath);
1458
+ const fullPath = path6.join(resourceDir, element.file);
1459
+ if (fs6.existsSync(fullPath)) {
1460
+ await fs6.promises.unlink(fullPath);
1324
1461
  }
1325
1462
  }
1326
1463
  }
@@ -1342,13 +1479,22 @@ async function apply(ctx, config) {
1342
1479
  async function processAdd(ctx2, config2, caveFilePath, resourceDir, pendingFilePath, session, content) {
1343
1480
  let caveId;
1344
1481
  try {
1482
+ caveId = await idManager.getNextId();
1483
+ if (isNaN(caveId) || caveId <= 0) {
1484
+ throw new Error("Invalid ID generated");
1485
+ }
1345
1486
  const inputContent = content.length > 0 ? content.join("\n") : await (async () => {
1346
1487
  await sendMessage(session, "commands.cave.add.noContent", [], true, 6e4);
1347
1488
  const reply = await session.prompt({ timeout: 6e4 });
1348
- 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
+ }
1349
1493
  return reply;
1350
1494
  })();
1351
- caveId = await idManager.getNextId();
1495
+ if (!inputContent) {
1496
+ return "";
1497
+ }
1352
1498
  if (inputContent.includes("/app/.config/QQ/")) {
1353
1499
  return sendMessage(session, "commands.cave.add.localFileNotAllowed", [], true);
1354
1500
  }
@@ -1384,6 +1530,7 @@ async function apply(ctx, config) {
1384
1530
  ]);
1385
1531
  const newCave = {
1386
1532
  cave_id: caveId,
1533
+ // 确保使用有效的数字ID
1387
1534
  elements: [
1388
1535
  ...textParts,
1389
1536
  ...imageElements.map((el, idx) => ({
@@ -1392,9 +1539,11 @@ async function apply(ctx, config) {
1392
1539
  // 保持原始文本和图片的相对位置
1393
1540
  index: el.index
1394
1541
  }))
1395
- ].sort((a, b) => a.index - a.index),
1396
- contributor_number: session.userId,
1397
- contributor_name: session.username
1542
+ ].sort((a, b) => a.index - b.index),
1543
+ contributor_number: session.userId || "100000",
1544
+ // 添加默认值
1545
+ contributor_name: session.username || "User"
1546
+ // 添加默认值
1398
1547
  };
1399
1548
  if (videoUrls.length > 0 && savedVideos.length > 0) {
1400
1549
  newCave.elements.push({
@@ -1403,7 +1552,7 @@ async function apply(ctx, config) {
1403
1552
  index: Number.MAX_SAFE_INTEGER
1404
1553
  });
1405
1554
  }
1406
- const hashStorage = new ContentHashManager(path5.join(ctx2.baseDir, "data", "cave"));
1555
+ const hashStorage = new ContentHashManager(path6.join(ctx2.baseDir, "data", "cave"));
1407
1556
  await hashStorage.initialize();
1408
1557
  const hashStatus = await hashStorage.getStatus();
1409
1558
  if (!hashStatus.lastUpdated || hashStatus.entries.length === 0) {
@@ -1420,7 +1569,7 @@ async function apply(ctx, config) {
1420
1569
  pendingData.push(newCave);
1421
1570
  await Promise.all([
1422
1571
  FileHandler.writeJsonData(pendingFilePath, pendingData),
1423
- auditManager.sendAuditMessage(newCave, await buildMessage(newCave, resourceDir, session), session)
1572
+ auditManager.sendAuditMessage(newCave, await buildMessage2(newCave, resourceDir, session), session)
1424
1573
  ]);
1425
1574
  return sendMessage(session, "commands.cave.add.submitPending", [caveId], false);
1426
1575
  }
@@ -1446,27 +1595,27 @@ async function apply(ctx, config) {
1446
1595
  "commands.cave.error.similarDuplicateFound",
1447
1596
  [(result.similarity * 100).toFixed(1)]
1448
1597
  );
1449
- await session.send(duplicateMessage + await buildMessage(originalCave, resourceDir, session));
1598
+ await session.send(duplicateMessage + await buildMessage2(originalCave, resourceDir, session));
1450
1599
  throw new Error("duplicate_found");
1451
1600
  }
1452
1601
  }
1453
1602
  await Promise.all([
1454
1603
  FileHandler.writeJsonData(caveFilePath, data),
1455
1604
  contentHashManager.updateCaveContent(caveId, {
1456
- 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,
1457
1606
  texts: textParts.filter((p) => p.type === "text").map((p) => p.content)
1458
1607
  })
1459
1608
  ]);
1460
1609
  await idManager.addStat(session.userId, caveId);
1461
1610
  return sendMessage(session, "commands.cave.add.addSuccess", [caveId], false);
1462
1611
  } catch (error) {
1463
- if (error.message !== "duplicate_found") {
1612
+ if (typeof caveId === "number" && !isNaN(caveId) && caveId > 0) {
1464
1613
  await idManager.markDeleted(caveId);
1465
1614
  }
1466
1615
  if (error.message === "duplicate_found") {
1467
1616
  return "";
1468
1617
  }
1469
- logger4.error(`Failed to process add command: ${error.message}`);
1618
+ logger5.error(`Failed to process add command: ${error.message}`);
1470
1619
  return sendMessage(session, "commands.cave.error.addFailed", [], true);
1471
1620
  }
1472
1621
  }
@@ -1476,11 +1625,11 @@ async function apply(ctx, config) {
1476
1625
  return sendMessage(session, "commands.cave.message.blacklisted", [], true);
1477
1626
  }
1478
1627
  }).action(async ({ session, options }, ...content) => {
1479
- const dataDir2 = path5.join(ctx.baseDir, "data");
1480
- const caveDir2 = path5.join(dataDir2, "cave");
1481
- const caveFilePath = path5.join(caveDir2, "cave.json");
1482
- const resourceDir = path5.join(caveDir2, "resources");
1483
- 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");
1484
1633
  const needsCooldown = !options.l && !options.a;
1485
1634
  if (needsCooldown) {
1486
1635
  const guildId = session.guildId;
@@ -1527,42 +1676,30 @@ async function apply(ctx, config) {
1527
1676
  return sendMessage(session, "commands.cave.message.managerOnly", [], true);
1528
1677
  }
1529
1678
  }).action(async ({ session }, id) => {
1530
- const dataDir2 = path5.join(ctx.baseDir, "data");
1531
- const caveDir2 = path5.join(dataDir2, "cave");
1532
- const caveFilePath = path5.join(caveDir2, "cave.json");
1533
- const resourceDir = path5.join(caveDir2, "resources");
1534
- const pendingFilePath = path5.join(caveDir2, "pending.json");
1535
- return await processAudit(
1536
- pendingFilePath,
1537
- caveFilePath,
1538
- resourceDir,
1539
- session,
1540
- { p: true },
1541
- [id]
1542
- );
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));
1543
1686
  });
1544
1687
  caveCommand.subcommand(".reject <id:text>", "拒绝回声洞审核").before(async ({ session }) => {
1545
1688
  if (!config.manager.includes(session.userId)) {
1546
1689
  return sendMessage(session, "commands.cave.message.managerOnly", [], true);
1547
1690
  }
1548
1691
  }).action(async ({ session }, id) => {
1549
- const dataDir2 = path5.join(ctx.baseDir, "data");
1550
- const caveDir2 = path5.join(dataDir2, "cave");
1551
- const caveFilePath = path5.join(caveDir2, "cave.json");
1552
- const resourceDir = path5.join(caveDir2, "resources");
1553
- const pendingFilePath = path5.join(caveDir2, "pending.json");
1554
- return await processAudit(
1555
- pendingFilePath,
1556
- caveFilePath,
1557
- resourceDir,
1558
- session,
1559
- { d: true },
1560
- [id]
1561
- );
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));
1562
1699
  });
1563
1700
  }
1564
1701
  __name(apply, "apply");
1565
- var logger4 = new import_koishi5.Logger("cave");
1702
+ var logger5 = new import_koishi6.Logger("cave");
1566
1703
  async function sendMessage(session, key, params = [], isTemp = true, timeout = 1e4) {
1567
1704
  try {
1568
1705
  const msg = await session.send(session.text(key, params));
@@ -1571,12 +1708,12 @@ async function sendMessage(session, key, params = [], isTemp = true, timeout = 1
1571
1708
  try {
1572
1709
  await session.bot.deleteMessage(session.channelId, msg);
1573
1710
  } catch (error) {
1574
- logger4.debug(`Failed to delete temporary message: ${error.message}`);
1711
+ logger5.debug(`Failed to delete temporary message: ${error.message}`);
1575
1712
  }
1576
1713
  }, timeout);
1577
1714
  }
1578
1715
  } catch (error) {
1579
- logger4.error(`Failed to send message: ${error.message}`);
1716
+ logger5.error(`Failed to send message: ${error.message}`);
1580
1717
  }
1581
1718
  return "";
1582
1719
  }
@@ -1606,12 +1743,12 @@ function cleanElementsForSave(elements, keepIndex = false) {
1606
1743
  }
1607
1744
  __name(cleanElementsForSave, "cleanElementsForSave");
1608
1745
  async function processMediaFile(filePath, type) {
1609
- const data = await fs5.promises.readFile(filePath).catch(() => null);
1746
+ const data = await fs6.promises.readFile(filePath).catch(() => null);
1610
1747
  if (!data) return null;
1611
1748
  return `data:${type}/${type === "image" ? "png" : "mp4"};base64,${data.toString("base64")}`;
1612
1749
  }
1613
1750
  __name(processMediaFile, "processMediaFile");
1614
- async function buildMessage(cave, resourceDir, session) {
1751
+ async function buildMessage2(cave, resourceDir, session) {
1615
1752
  if (!cave?.elements?.length) {
1616
1753
  return session.text("commands.cave.error.noContent");
1617
1754
  }
@@ -1623,10 +1760,10 @@ async function buildMessage(cave, resourceDir, session) {
1623
1760
  session.text("commands.cave.message.contributorSuffix", [cave.contributor_name])
1624
1761
  ].join("\n");
1625
1762
  await session?.send(basicInfo);
1626
- const filePath = path5.join(resourceDir, videoElement.file);
1763
+ const filePath = path6.join(resourceDir, videoElement.file);
1627
1764
  const base64Data = await processMediaFile(filePath, "video");
1628
1765
  if (base64Data && session) {
1629
- await session.send((0, import_koishi5.h)("video", { src: base64Data }));
1766
+ await session.send((0, import_koishi6.h)("video", { src: base64Data }));
1630
1767
  }
1631
1768
  return "";
1632
1769
  }
@@ -1635,141 +1772,17 @@ async function buildMessage(cave, resourceDir, session) {
1635
1772
  if (element.type === "text") {
1636
1773
  lines.push(element.content);
1637
1774
  } else if (element.type === "img" && element.file) {
1638
- const filePath = path5.join(resourceDir, element.file);
1775
+ const filePath = path6.join(resourceDir, element.file);
1639
1776
  const base64Data = await processMediaFile(filePath, "image");
1640
1777
  if (base64Data) {
1641
- lines.push((0, import_koishi5.h)("image", { src: base64Data }));
1778
+ lines.push((0, import_koishi6.h)("image", { src: base64Data }));
1642
1779
  }
1643
1780
  }
1644
1781
  }
1645
1782
  lines.push(session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]));
1646
1783
  return lines.join("\n");
1647
1784
  }
1648
- __name(buildMessage, "buildMessage");
1649
- async function extractMediaContent(originalContent, config, session) {
1650
- const textParts = originalContent.split(/<(img|video)[^>]+>/).map((text, idx) => text.trim() && {
1651
- type: "text",
1652
- content: text.replace(/^(img|video)$/, "").trim(),
1653
- index: idx * 3
1654
- }).filter((text) => text && text.content);
1655
- const getMediaElements = /* @__PURE__ */ __name((type, maxSize) => {
1656
- const regex = new RegExp(`<${type}[^>]+src="([^"]+)"[^>]*>`, "g");
1657
- const elements = [];
1658
- const urls = [];
1659
- let match;
1660
- let idx = 0;
1661
- while ((match = regex.exec(originalContent)) !== null) {
1662
- const element = match[0];
1663
- const url = match[1];
1664
- const fileName = element.match(/file="([^"]+)"/)?.[1];
1665
- const fileSize = element.match(/fileSize="([^"]+)"/)?.[1];
1666
- if (fileSize) {
1667
- const sizeInBytes = parseInt(fileSize);
1668
- if (sizeInBytes > maxSize * 1024 * 1024) {
1669
- throw new Error(session.text("commands.cave.message.mediaSizeExceeded", [type]));
1670
- }
1671
- }
1672
- urls.push(url);
1673
- elements.push({
1674
- type,
1675
- index: type === "video" ? Number.MAX_SAFE_INTEGER : idx * 3 + 1,
1676
- fileName,
1677
- fileSize
1678
- });
1679
- idx++;
1680
- }
1681
- return { urls, elements };
1682
- }, "getMediaElements");
1683
- const { urls: imageUrls, elements: imageElementsRaw } = getMediaElements("img", config.imageMaxSize);
1684
- const imageElements = imageElementsRaw;
1685
- const { urls: videoUrls, elements: videoElementsRaw } = getMediaElements("video", config.videoMaxSize);
1686
- const videoElements = videoElementsRaw;
1687
- return { imageUrls, imageElements, videoUrls, videoElements, textParts };
1688
- }
1689
- __name(extractMediaContent, "extractMediaContent");
1690
- async function saveMedia(urls, fileNames, resourceDir, caveId, mediaType, config, ctx, session, buffers) {
1691
- const accept = mediaType === "img" ? "image/*" : "video/*";
1692
- const hashStorage = new ContentHashManager(path5.join(ctx.baseDir, "data", "cave"));
1693
- await hashStorage.initialize();
1694
- const downloadTasks = urls.map(async (url, i) => {
1695
- const fileName = fileNames[i];
1696
- const ext = path5.extname(fileName || url) || (mediaType === "img" ? ".png" : ".mp4");
1697
- try {
1698
- const response = await ctx.http(decodeURIComponent(url).replace(/&amp;/g, "&"), {
1699
- method: "GET",
1700
- responseType: "arraybuffer",
1701
- timeout: 3e4,
1702
- headers: {
1703
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
1704
- "Accept": accept,
1705
- "Referer": "https://qq.com"
1706
- }
1707
- });
1708
- if (!response.data) throw new Error("empty_response");
1709
- const buffer = Buffer.from(response.data);
1710
- if (buffers && mediaType === "img") {
1711
- buffers.push(buffer);
1712
- }
1713
- const md5 = path5.basename(fileName || `${mediaType}`, ext).replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "");
1714
- const files = await fs5.promises.readdir(resourceDir);
1715
- const duplicateFile = files.find((file) => {
1716
- const match = file.match(/^\d+_([^.]+)/);
1717
- return match && match[1] === md5;
1718
- });
1719
- if (duplicateFile) {
1720
- const duplicateCaveId = parseInt(duplicateFile.split("_")[0]);
1721
- if (!isNaN(duplicateCaveId)) {
1722
- const caveFilePath = path5.join(ctx.baseDir, "data", "cave", "cave.json");
1723
- const data = await FileHandler.readJsonData(caveFilePath);
1724
- const originalCave = data.find((item) => item.cave_id === duplicateCaveId);
1725
- if (originalCave) {
1726
- const message = session.text("commands.cave.error.exactDuplicateFound");
1727
- await session.send(message + await buildMessage(originalCave, resourceDir, session));
1728
- throw new Error("duplicate_found");
1729
- }
1730
- }
1731
- }
1732
- if (mediaType === "img" && config.enableImageDuplicate) {
1733
- const result = await hashStorage.findDuplicates(
1734
- { images: [buffer] },
1735
- {
1736
- image: config.imageDuplicateThreshold,
1737
- text: config.textDuplicateThreshold
1738
- }
1739
- );
1740
- if (result.length > 0 && result[0] !== null) {
1741
- const duplicate = result[0];
1742
- const similarity = duplicate.similarity;
1743
- if (similarity >= config.imageDuplicateThreshold) {
1744
- const caveFilePath = path5.join(ctx.baseDir, "data", "cave", "cave.json");
1745
- const data = await FileHandler.readJsonData(caveFilePath);
1746
- const originalCave = data.find((item) => item.cave_id === duplicate.caveId);
1747
- if (originalCave) {
1748
- const message = session.text(
1749
- "commands.cave.error.similarDuplicateFound",
1750
- [(similarity * 100).toFixed(1)]
1751
- );
1752
- await session.send(message + await buildMessage(originalCave, resourceDir, session));
1753
- throw new Error("duplicate_found");
1754
- }
1755
- }
1756
- }
1757
- }
1758
- const finalFileName = `${caveId}_${md5}${ext}`;
1759
- const filePath = path5.join(resourceDir, finalFileName);
1760
- await FileHandler.saveMediaFile(filePath, buffer);
1761
- return finalFileName;
1762
- } catch (error) {
1763
- if (error.message === "duplicate_found") {
1764
- throw error;
1765
- }
1766
- logger4.error(`Failed to download media: ${error.message}`);
1767
- throw new Error(session.text(`commands.cave.error.upload${mediaType === "img" ? "Image" : "Video"}Failed`));
1768
- }
1769
- });
1770
- return Promise.all(downloadTasks);
1771
- }
1772
- __name(saveMedia, "saveMedia");
1785
+ __name(buildMessage2, "buildMessage");
1773
1786
  // Annotate the CommonJS export names for ESM import in node:
1774
1787
  0 && (module.exports = {
1775
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.2",
4
+ "version": "1.5.4",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],