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 +216 -215
- package/lib/utils/MediaHandle.d.ts +42 -0
- package/package.json +1 -1
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
|
|
57
|
-
var
|
|
58
|
-
var
|
|
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(/&/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 =
|
|
1195
|
-
manager:
|
|
1338
|
+
var Config = import_koishi6.Schema.object({
|
|
1339
|
+
manager: import_koishi6.Schema.array(import_koishi6.Schema.string()).required(),
|
|
1196
1340
|
// 管理员用户ID
|
|
1197
|
-
number:
|
|
1341
|
+
number: import_koishi6.Schema.number().default(60),
|
|
1198
1342
|
// 冷却时间(秒)
|
|
1199
|
-
enableAudit:
|
|
1343
|
+
enableAudit: import_koishi6.Schema.boolean().default(false),
|
|
1200
1344
|
// 启用审核
|
|
1201
|
-
enableTextDuplicate:
|
|
1345
|
+
enableTextDuplicate: import_koishi6.Schema.boolean().default(true),
|
|
1202
1346
|
// 启用文本查重
|
|
1203
|
-
textDuplicateThreshold:
|
|
1347
|
+
textDuplicateThreshold: import_koishi6.Schema.number().default(0.9),
|
|
1204
1348
|
// 文本查重阈值
|
|
1205
|
-
enableImageDuplicate:
|
|
1349
|
+
enableImageDuplicate: import_koishi6.Schema.boolean().default(true),
|
|
1206
1350
|
// 开启图片查重
|
|
1207
|
-
imageDuplicateThreshold:
|
|
1351
|
+
imageDuplicateThreshold: import_koishi6.Schema.number().default(0.8),
|
|
1208
1352
|
// 图片查重阈值
|
|
1209
|
-
imageMaxSize:
|
|
1353
|
+
imageMaxSize: import_koishi6.Schema.number().default(4),
|
|
1210
1354
|
// 图片大小限制(MB)
|
|
1211
|
-
allowVideo:
|
|
1355
|
+
allowVideo: import_koishi6.Schema.boolean().default(true),
|
|
1212
1356
|
// 允许视频
|
|
1213
|
-
videoMaxSize:
|
|
1357
|
+
videoMaxSize: import_koishi6.Schema.number().default(16),
|
|
1214
1358
|
// 视频大小限制(MB)
|
|
1215
|
-
enablePagination:
|
|
1359
|
+
enablePagination: import_koishi6.Schema.boolean().default(false),
|
|
1216
1360
|
// 启用分页
|
|
1217
|
-
itemsPerPage:
|
|
1361
|
+
itemsPerPage: import_koishi6.Schema.number().default(10),
|
|
1218
1362
|
// 每页条数
|
|
1219
|
-
blacklist:
|
|
1363
|
+
blacklist: import_koishi6.Schema.array(import_koishi6.Schema.string()).default([]),
|
|
1220
1364
|
// 黑名单
|
|
1221
|
-
whitelist:
|
|
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 =
|
|
1231
|
-
const caveDir =
|
|
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(
|
|
1235
|
-
await FileHandler.ensureJsonFile(
|
|
1236
|
-
await FileHandler.ensureJsonFile(
|
|
1237
|
-
await FileHandler.ensureJsonFile(
|
|
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(
|
|
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
|
|
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 ?
|
|
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
|
|
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 =
|
|
1328
|
-
if (
|
|
1329
|
-
await
|
|
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)
|
|
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(
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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 =
|
|
1492
|
-
const caveDir2 =
|
|
1493
|
-
const caveFilePath =
|
|
1494
|
-
const resourceDir =
|
|
1495
|
-
const pendingFilePath =
|
|
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 =
|
|
1543
|
-
const caveDir2 =
|
|
1544
|
-
const caveFilePath =
|
|
1545
|
-
const resourceDir =
|
|
1546
|
-
const pendingFilePath =
|
|
1547
|
-
|
|
1548
|
-
|
|
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 =
|
|
1562
|
-
const caveDir2 =
|
|
1563
|
-
const caveFilePath =
|
|
1564
|
-
const resourceDir =
|
|
1565
|
-
const pendingFilePath =
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
|
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
|
-
|
|
1711
|
+
logger5.debug(`Failed to delete temporary message: ${error.message}`);
|
|
1587
1712
|
}
|
|
1588
1713
|
}, timeout);
|
|
1589
1714
|
}
|
|
1590
1715
|
} catch (error) {
|
|
1591
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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,
|
|
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 =
|
|
1775
|
+
const filePath = path6.join(resourceDir, element.file);
|
|
1651
1776
|
const base64Data = await processMediaFile(filePath, "image");
|
|
1652
1777
|
if (base64Data) {
|
|
1653
|
-
lines.push((0,
|
|
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(
|
|
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(/&/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[]>;
|