koishi-plugin-best-cave 1.5.3 → 1.5.5
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 +355 -374
- package/lib/utils/AuditHandler.d.ts +43 -0
- package/lib/utils/ContentHasher.d.ts +80 -0
- package/lib/utils/FileHandler.d.ts +63 -0
- package/lib/utils/HashManager.d.ts +108 -0
- package/lib/utils/IdManager.d.ts +69 -0
- package/lib/utils/MediaHandle.d.ts +42 -0
- package/lib/utils/MediaHandler.d.ts +52 -0
- package/lib/utils/ProcessHandle.d.ts +7 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -53,11 +53,11 @@ __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 fs7 = __toESM(require("fs"));
|
|
58
|
+
var path7 = __toESM(require("path"));
|
|
59
59
|
|
|
60
|
-
// src/utils/
|
|
60
|
+
// src/utils/FileHandler.ts
|
|
61
61
|
var fs = __toESM(require("fs"));
|
|
62
62
|
var path = __toESM(require("path"));
|
|
63
63
|
var import_koishi = require("koishi");
|
|
@@ -216,7 +216,7 @@ var FileHandler = class {
|
|
|
216
216
|
}
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
-
// src/utils/
|
|
219
|
+
// src/utils/IdManager.ts
|
|
220
220
|
var fs2 = __toESM(require("fs"));
|
|
221
221
|
var path2 = __toESM(require("path"));
|
|
222
222
|
var import_koishi2 = require("koishi");
|
|
@@ -440,12 +440,12 @@ var IdManager = class {
|
|
|
440
440
|
}
|
|
441
441
|
};
|
|
442
442
|
|
|
443
|
-
// src/utils/
|
|
443
|
+
// src/utils/HashManager.ts
|
|
444
444
|
var import_koishi3 = require("koishi");
|
|
445
445
|
var fs3 = __toESM(require("fs"));
|
|
446
446
|
var path3 = __toESM(require("path"));
|
|
447
447
|
|
|
448
|
-
// src/utils/
|
|
448
|
+
// src/utils/ContentHasher.ts
|
|
449
449
|
var import_sharp = __toESM(require("sharp"));
|
|
450
450
|
var ContentHasher = class {
|
|
451
451
|
static {
|
|
@@ -605,22 +605,13 @@ var ContentHasher = class {
|
|
|
605
605
|
}
|
|
606
606
|
return hash.toString(36);
|
|
607
607
|
}
|
|
608
|
-
/**
|
|
609
|
-
* 批量比较一个新哈希值与多个已存在哈希值的相似度
|
|
610
|
-
* @param newHash - 新的哈希值
|
|
611
|
-
* @param existingHashes - 已存在的哈希值数组
|
|
612
|
-
* @returns 相似度数组,每个元素对应一个已存在哈希值的相似度
|
|
613
|
-
*/
|
|
614
|
-
static batchCompareSimilarity(newHash, existingHashes) {
|
|
615
|
-
return existingHashes.map((hash) => this.calculateSimilarity(newHash, hash));
|
|
616
|
-
}
|
|
617
608
|
};
|
|
618
609
|
|
|
619
|
-
// src/utils/
|
|
610
|
+
// src/utils/HashManager.ts
|
|
620
611
|
var import_util = require("util");
|
|
621
612
|
var logger3 = new import_koishi3.Logger("HashManager");
|
|
622
613
|
var readFileAsync = (0, import_util.promisify)(fs3.readFile);
|
|
623
|
-
var
|
|
614
|
+
var HashManager = class _HashManager {
|
|
624
615
|
/**
|
|
625
616
|
* 初始化HashManager实例
|
|
626
617
|
* @param caveDir 回声洞数据目录路径
|
|
@@ -629,7 +620,7 @@ var ContentHashManager = class _ContentHashManager {
|
|
|
629
620
|
this.caveDir = caveDir;
|
|
630
621
|
}
|
|
631
622
|
static {
|
|
632
|
-
__name(this, "
|
|
623
|
+
__name(this, "HashManager");
|
|
633
624
|
}
|
|
634
625
|
// 哈希数据文件名
|
|
635
626
|
static HASH_FILE = "hash.json";
|
|
@@ -643,13 +634,13 @@ var ContentHashManager = class _ContentHashManager {
|
|
|
643
634
|
// 初始化状态标志
|
|
644
635
|
initialized = false;
|
|
645
636
|
get filePath() {
|
|
646
|
-
return path3.join(this.caveDir,
|
|
637
|
+
return path3.join(this.caveDir, _HashManager.HASH_FILE);
|
|
647
638
|
}
|
|
648
639
|
get resourceDir() {
|
|
649
640
|
return path3.join(this.caveDir, "resources");
|
|
650
641
|
}
|
|
651
642
|
get caveFilePath() {
|
|
652
|
-
return path3.join(this.caveDir,
|
|
643
|
+
return path3.join(this.caveDir, _HashManager.CAVE_FILE);
|
|
653
644
|
}
|
|
654
645
|
/**
|
|
655
646
|
* 初始化哈希存储
|
|
@@ -973,7 +964,7 @@ var ContentHashManager = class _ContentHashManager {
|
|
|
973
964
|
* @param batchSize 批处理大小
|
|
974
965
|
* @private
|
|
975
966
|
*/
|
|
976
|
-
async processBatch(items, processor, batchSize =
|
|
967
|
+
async processBatch(items, processor, batchSize = _HashManager.BATCH_SIZE) {
|
|
977
968
|
for (let i = 0; i < items.length; i += batchSize) {
|
|
978
969
|
const batch = items.slice(i, i + batchSize);
|
|
979
970
|
await Promise.all(
|
|
@@ -989,7 +980,7 @@ var ContentHashManager = class _ContentHashManager {
|
|
|
989
980
|
}
|
|
990
981
|
};
|
|
991
982
|
|
|
992
|
-
// src/utils/
|
|
983
|
+
// src/utils/AuditHandler.ts
|
|
993
984
|
var import_koishi4 = require("koishi");
|
|
994
985
|
var fs4 = __toESM(require("fs"));
|
|
995
986
|
var path4 = __toESM(require("path"));
|
|
@@ -1188,37 +1179,319 @@ ${session.text("commands.cave.audit.from")}${cave.contributor_number}`;
|
|
|
1188
1179
|
}
|
|
1189
1180
|
};
|
|
1190
1181
|
|
|
1182
|
+
// src/utils/MediaHandler.ts
|
|
1183
|
+
var import_koishi5 = require("koishi");
|
|
1184
|
+
var fs5 = __toESM(require("fs"));
|
|
1185
|
+
var path5 = __toESM(require("path"));
|
|
1186
|
+
var logger4 = new import_koishi5.Logger("MediaHandle");
|
|
1187
|
+
async function buildMessage(cave, resourceDir, session) {
|
|
1188
|
+
if (!cave?.elements?.length) {
|
|
1189
|
+
return session.text("commands.cave.error.noContent");
|
|
1190
|
+
}
|
|
1191
|
+
const videoElement = cave.elements.find((el) => el.type === "video");
|
|
1192
|
+
const nonVideoElements = cave.elements.filter((el) => el.type !== "video").sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
1193
|
+
if (videoElement?.file) {
|
|
1194
|
+
const basicInfo = [
|
|
1195
|
+
session.text("commands.cave.message.caveTitle", [cave.cave_id]),
|
|
1196
|
+
session.text("commands.cave.message.contributorSuffix", [cave.contributor_name])
|
|
1197
|
+
].join("\n");
|
|
1198
|
+
await session?.send(basicInfo);
|
|
1199
|
+
const filePath = path5.join(resourceDir, videoElement.file);
|
|
1200
|
+
const base64Data = await processMediaFile(filePath, "video");
|
|
1201
|
+
if (base64Data && session) {
|
|
1202
|
+
await session.send((0, import_koishi5.h)("video", { src: base64Data }));
|
|
1203
|
+
}
|
|
1204
|
+
return "";
|
|
1205
|
+
}
|
|
1206
|
+
const lines = [session.text("commands.cave.message.caveTitle", [cave.cave_id])];
|
|
1207
|
+
for (const element of nonVideoElements) {
|
|
1208
|
+
if (element.type === "text") {
|
|
1209
|
+
lines.push(element.content);
|
|
1210
|
+
} else if (element.type === "img" && element.file) {
|
|
1211
|
+
const filePath = path5.join(resourceDir, element.file);
|
|
1212
|
+
const base64Data = await processMediaFile(filePath, "image");
|
|
1213
|
+
if (base64Data) {
|
|
1214
|
+
lines.push((0, import_koishi5.h)("image", { src: base64Data }));
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
lines.push(session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]));
|
|
1219
|
+
return lines.join("\n");
|
|
1220
|
+
}
|
|
1221
|
+
__name(buildMessage, "buildMessage");
|
|
1222
|
+
async function sendMessage(session, key, params = [], isTemp = true, timeout = 1e4) {
|
|
1223
|
+
try {
|
|
1224
|
+
const msg = await session.send(session.text(key, params));
|
|
1225
|
+
if (isTemp && msg) {
|
|
1226
|
+
setTimeout(async () => {
|
|
1227
|
+
try {
|
|
1228
|
+
await session.bot.deleteMessage(session.channelId, msg);
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
logger4.debug(`Failed to delete temporary message: ${error.message}`);
|
|
1231
|
+
}
|
|
1232
|
+
}, timeout);
|
|
1233
|
+
}
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
logger4.error(`Failed to send message: ${error.message}`);
|
|
1236
|
+
}
|
|
1237
|
+
return "";
|
|
1238
|
+
}
|
|
1239
|
+
__name(sendMessage, "sendMessage");
|
|
1240
|
+
async function processMediaFile(filePath, type) {
|
|
1241
|
+
const data = await fs5.promises.readFile(filePath).catch(() => null);
|
|
1242
|
+
if (!data) return null;
|
|
1243
|
+
return `data:${type}/${type === "image" ? "png" : "mp4"};base64,${data.toString("base64")}`;
|
|
1244
|
+
}
|
|
1245
|
+
__name(processMediaFile, "processMediaFile");
|
|
1246
|
+
async function extractMediaContent(originalContent, config, session) {
|
|
1247
|
+
const textParts = originalContent.split(/<(img|video)[^>]+>/).map((text, idx) => text.trim() && {
|
|
1248
|
+
type: "text",
|
|
1249
|
+
content: text.replace(/^(img|video)$/, "").trim(),
|
|
1250
|
+
index: idx * 3
|
|
1251
|
+
}).filter((text) => text && text.content);
|
|
1252
|
+
const getMediaElements = /* @__PURE__ */ __name((type, maxSize) => {
|
|
1253
|
+
const regex = new RegExp(`<${type}[^>]+src="([^"]+)"[^>]*>`, "g");
|
|
1254
|
+
const elements = [];
|
|
1255
|
+
const urls = [];
|
|
1256
|
+
let match;
|
|
1257
|
+
let idx = 0;
|
|
1258
|
+
while ((match = regex.exec(originalContent)) !== null) {
|
|
1259
|
+
const element = match[0];
|
|
1260
|
+
const url = match[1];
|
|
1261
|
+
const fileName = element.match(/file="([^"]+)"/)?.[1];
|
|
1262
|
+
const fileSize = element.match(/fileSize="([^"]+)"/)?.[1];
|
|
1263
|
+
if (fileSize) {
|
|
1264
|
+
const sizeInBytes = parseInt(fileSize);
|
|
1265
|
+
if (sizeInBytes > maxSize * 1024 * 1024) {
|
|
1266
|
+
throw new Error(session.text("commands.cave.message.mediaSizeExceeded", [type]));
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
urls.push(url);
|
|
1270
|
+
elements.push({
|
|
1271
|
+
type,
|
|
1272
|
+
index: type === "video" ? Number.MAX_SAFE_INTEGER : idx * 3 + 1,
|
|
1273
|
+
fileName,
|
|
1274
|
+
fileSize
|
|
1275
|
+
});
|
|
1276
|
+
idx++;
|
|
1277
|
+
}
|
|
1278
|
+
return { urls, elements };
|
|
1279
|
+
}, "getMediaElements");
|
|
1280
|
+
const { urls: imageUrls, elements: imageElementsRaw } = getMediaElements("img", config.imageMaxSize);
|
|
1281
|
+
const imageElements = imageElementsRaw;
|
|
1282
|
+
const { urls: videoUrls, elements: videoElementsRaw } = getMediaElements("video", config.videoMaxSize);
|
|
1283
|
+
const videoElements = videoElementsRaw;
|
|
1284
|
+
return { imageUrls, imageElements, videoUrls, videoElements, textParts };
|
|
1285
|
+
}
|
|
1286
|
+
__name(extractMediaContent, "extractMediaContent");
|
|
1287
|
+
async function saveMedia(urls, fileNames, resourceDir, caveId, mediaType, config, ctx, session, buffers) {
|
|
1288
|
+
const accept = mediaType === "img" ? "image/*" : "video/*";
|
|
1289
|
+
const hashStorage = new HashManager(path5.join(ctx.baseDir, "data", "cave"));
|
|
1290
|
+
await hashStorage.initialize();
|
|
1291
|
+
const downloadTasks = urls.map(async (url, i) => {
|
|
1292
|
+
const fileName = fileNames[i];
|
|
1293
|
+
const ext = path5.extname(fileName || url) || (mediaType === "img" ? ".png" : ".mp4");
|
|
1294
|
+
try {
|
|
1295
|
+
const response = await ctx.http(decodeURIComponent(url).replace(/&/g, "&"), {
|
|
1296
|
+
method: "GET",
|
|
1297
|
+
responseType: "arraybuffer",
|
|
1298
|
+
timeout: 3e4,
|
|
1299
|
+
headers: {
|
|
1300
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
|
|
1301
|
+
"Accept": accept,
|
|
1302
|
+
"Referer": "https://qq.com"
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
if (!response.data) throw new Error("empty_response");
|
|
1306
|
+
const buffer = Buffer.from(response.data);
|
|
1307
|
+
if (buffers && mediaType === "img") {
|
|
1308
|
+
buffers.push(buffer);
|
|
1309
|
+
}
|
|
1310
|
+
const md5 = path5.basename(fileName || `${mediaType}`, ext).replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "");
|
|
1311
|
+
const files = await fs5.promises.readdir(resourceDir);
|
|
1312
|
+
const duplicateFile = files.find((file) => {
|
|
1313
|
+
const match = file.match(/^\d+_([^.]+)/);
|
|
1314
|
+
return match && match[1] === md5;
|
|
1315
|
+
});
|
|
1316
|
+
if (duplicateFile) {
|
|
1317
|
+
const duplicateCaveId = parseInt(duplicateFile.split("_")[0]);
|
|
1318
|
+
if (!isNaN(duplicateCaveId)) {
|
|
1319
|
+
const caveFilePath = path5.join(ctx.baseDir, "data", "cave", "cave.json");
|
|
1320
|
+
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1321
|
+
const originalCave = data.find((item) => item.cave_id === duplicateCaveId);
|
|
1322
|
+
if (originalCave) {
|
|
1323
|
+
const message = session.text("commands.cave.error.exactDuplicateFound");
|
|
1324
|
+
await session.send(message + await buildMessage(originalCave, resourceDir, session));
|
|
1325
|
+
throw new Error("duplicate_found");
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (mediaType === "img" && config.enableImageDuplicate) {
|
|
1330
|
+
const result = await hashStorage.findDuplicates(
|
|
1331
|
+
{ images: [buffer] },
|
|
1332
|
+
{
|
|
1333
|
+
image: config.imageDuplicateThreshold,
|
|
1334
|
+
text: config.textDuplicateThreshold
|
|
1335
|
+
}
|
|
1336
|
+
);
|
|
1337
|
+
if (result.length > 0 && result[0] !== null) {
|
|
1338
|
+
const duplicate = result[0];
|
|
1339
|
+
const similarity = duplicate.similarity;
|
|
1340
|
+
if (similarity >= config.imageDuplicateThreshold) {
|
|
1341
|
+
const caveFilePath = path5.join(ctx.baseDir, "data", "cave", "cave.json");
|
|
1342
|
+
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1343
|
+
const originalCave = data.find((item) => item.cave_id === duplicate.caveId);
|
|
1344
|
+
if (originalCave) {
|
|
1345
|
+
const message = session.text(
|
|
1346
|
+
"commands.cave.error.similarDuplicateFound",
|
|
1347
|
+
[(similarity * 100).toFixed(1)]
|
|
1348
|
+
);
|
|
1349
|
+
await session.send(message + await buildMessage(originalCave, resourceDir, session));
|
|
1350
|
+
throw new Error("duplicate_found");
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const finalFileName = `${caveId}_${md5}${ext}`;
|
|
1356
|
+
const filePath = path5.join(resourceDir, finalFileName);
|
|
1357
|
+
await FileHandler.saveMediaFile(filePath, buffer);
|
|
1358
|
+
return finalFileName;
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
if (error.message === "duplicate_found") {
|
|
1361
|
+
throw error;
|
|
1362
|
+
}
|
|
1363
|
+
logger4.error(`Failed to download media: ${error.message}`);
|
|
1364
|
+
throw new Error(session.text(`commands.cave.error.upload${mediaType === "img" ? "Image" : "Video"}Failed`));
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
return Promise.all(downloadTasks);
|
|
1368
|
+
}
|
|
1369
|
+
__name(saveMedia, "saveMedia");
|
|
1370
|
+
|
|
1371
|
+
// src/utils/ProcessHandle.ts
|
|
1372
|
+
var fs6 = __toESM(require("fs"));
|
|
1373
|
+
var path6 = __toESM(require("path"));
|
|
1374
|
+
async function processList(session, config, idManager, userId, pageNum = 1) {
|
|
1375
|
+
const stats = idManager.getStats();
|
|
1376
|
+
if (userId && userId in stats) {
|
|
1377
|
+
const ids = stats[userId];
|
|
1378
|
+
return session.text("commands.cave.list.totalItems", [userId, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
|
|
1379
|
+
}
|
|
1380
|
+
const lines = Object.entries(stats).map(([cid, ids]) => {
|
|
1381
|
+
return session.text("commands.cave.list.totalItems", [cid, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
|
|
1382
|
+
});
|
|
1383
|
+
const totalSubmissions = Object.values(stats).reduce((sum, arr) => sum + arr.length, 0);
|
|
1384
|
+
if (config.enablePagination) {
|
|
1385
|
+
const itemsPerPage = config.itemsPerPage;
|
|
1386
|
+
const totalPages = Math.max(1, Math.ceil(lines.length / itemsPerPage));
|
|
1387
|
+
pageNum = Math.min(Math.max(1, pageNum), totalPages);
|
|
1388
|
+
const start = (pageNum - 1) * itemsPerPage;
|
|
1389
|
+
const paginatedLines = lines.slice(start, start + itemsPerPage);
|
|
1390
|
+
return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + paginatedLines.join("\n") + "\n" + session.text("commands.cave.list.pageInfo", [pageNum, totalPages]);
|
|
1391
|
+
} else {
|
|
1392
|
+
return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + lines.join("\n");
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
__name(processList, "processList");
|
|
1396
|
+
async function processView(caveFilePath, resourceDir, session, options, content) {
|
|
1397
|
+
const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
|
|
1398
|
+
if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
|
|
1399
|
+
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1400
|
+
const cave = data.find((item) => item.cave_id === caveId);
|
|
1401
|
+
if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
|
|
1402
|
+
return buildMessage(cave, resourceDir, session);
|
|
1403
|
+
}
|
|
1404
|
+
__name(processView, "processView");
|
|
1405
|
+
async function processRandom(caveFilePath, resourceDir, session) {
|
|
1406
|
+
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1407
|
+
if (data.length === 0) {
|
|
1408
|
+
return sendMessage(session, "commands.cave.error.noCave", [], true);
|
|
1409
|
+
}
|
|
1410
|
+
const cave = (() => {
|
|
1411
|
+
const validCaves = data.filter((cave2) => cave2.elements && cave2.elements.length > 0);
|
|
1412
|
+
if (!validCaves.length) return void 0;
|
|
1413
|
+
const randomIndex = Math.floor(Math.random() * validCaves.length);
|
|
1414
|
+
return validCaves[randomIndex];
|
|
1415
|
+
})();
|
|
1416
|
+
return cave ? buildMessage(cave, resourceDir, session) : sendMessage(session, "commands.cave.error.getCave", [], true);
|
|
1417
|
+
}
|
|
1418
|
+
__name(processRandom, "processRandom");
|
|
1419
|
+
async function processDelete(caveFilePath, resourceDir, pendingFilePath, session, config, options, content, idManager, HashManager2) {
|
|
1420
|
+
const caveId = parseInt(content[0] || (typeof options.r === "string" ? options.r : ""));
|
|
1421
|
+
if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
|
|
1422
|
+
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1423
|
+
const pendingData = await FileHandler.readJsonData(pendingFilePath);
|
|
1424
|
+
const targetInData = data.find((item) => item.cave_id === caveId);
|
|
1425
|
+
const targetInPending = pendingData.find((item) => item.cave_id === caveId);
|
|
1426
|
+
if (!targetInData && !targetInPending) {
|
|
1427
|
+
return sendMessage(session, "commands.cave.error.notFound", [], true);
|
|
1428
|
+
}
|
|
1429
|
+
const targetCave = targetInData || targetInPending;
|
|
1430
|
+
const isPending = !targetInData;
|
|
1431
|
+
if (targetCave.contributor_number !== session.userId && !config.manager.includes(session.userId)) {
|
|
1432
|
+
return sendMessage(session, "commands.cave.remove.noPermission", [], true);
|
|
1433
|
+
}
|
|
1434
|
+
const caveContent = await buildMessage(targetCave, resourceDir, session);
|
|
1435
|
+
if (targetCave.elements) {
|
|
1436
|
+
await HashManager2.updateCaveContent(caveId, {
|
|
1437
|
+
images: void 0,
|
|
1438
|
+
texts: void 0
|
|
1439
|
+
});
|
|
1440
|
+
for (const element of targetCave.elements) {
|
|
1441
|
+
if ((element.type === "img" || element.type === "video") && element.file) {
|
|
1442
|
+
const fullPath = path6.join(resourceDir, element.file);
|
|
1443
|
+
if (fs6.existsSync(fullPath)) {
|
|
1444
|
+
await fs6.promises.unlink(fullPath);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (isPending) {
|
|
1450
|
+
const newPendingData = pendingData.filter((item) => item.cave_id !== caveId);
|
|
1451
|
+
await FileHandler.writeJsonData(pendingFilePath, newPendingData);
|
|
1452
|
+
} else {
|
|
1453
|
+
const newData = data.filter((item) => item.cave_id !== caveId);
|
|
1454
|
+
await FileHandler.writeJsonData(caveFilePath, newData);
|
|
1455
|
+
await idManager.removeStat(targetCave.contributor_number, caveId);
|
|
1456
|
+
}
|
|
1457
|
+
await idManager.markDeleted(caveId);
|
|
1458
|
+
const deleteStatus = isPending ? session.text("commands.cave.remove.deletePending") : "";
|
|
1459
|
+
const deleteMessage = session.text("commands.cave.remove.deleted");
|
|
1460
|
+
return `${deleteMessage}${deleteStatus}${caveContent}`;
|
|
1461
|
+
}
|
|
1462
|
+
__name(processDelete, "processDelete");
|
|
1463
|
+
|
|
1191
1464
|
// src/index.ts
|
|
1192
1465
|
var name = "best-cave";
|
|
1193
1466
|
var inject = ["database"];
|
|
1194
|
-
var Config =
|
|
1195
|
-
manager:
|
|
1467
|
+
var Config = import_koishi6.Schema.object({
|
|
1468
|
+
manager: import_koishi6.Schema.array(import_koishi6.Schema.string()).required(),
|
|
1196
1469
|
// 管理员用户ID
|
|
1197
|
-
number:
|
|
1470
|
+
number: import_koishi6.Schema.number().default(60),
|
|
1198
1471
|
// 冷却时间(秒)
|
|
1199
|
-
enableAudit:
|
|
1472
|
+
enableAudit: import_koishi6.Schema.boolean().default(false),
|
|
1200
1473
|
// 启用审核
|
|
1201
|
-
enableTextDuplicate:
|
|
1474
|
+
enableTextDuplicate: import_koishi6.Schema.boolean().default(true),
|
|
1202
1475
|
// 启用文本查重
|
|
1203
|
-
textDuplicateThreshold:
|
|
1476
|
+
textDuplicateThreshold: import_koishi6.Schema.number().default(0.9),
|
|
1204
1477
|
// 文本查重阈值
|
|
1205
|
-
enableImageDuplicate:
|
|
1478
|
+
enableImageDuplicate: import_koishi6.Schema.boolean().default(true),
|
|
1206
1479
|
// 开启图片查重
|
|
1207
|
-
imageDuplicateThreshold:
|
|
1480
|
+
imageDuplicateThreshold: import_koishi6.Schema.number().default(0.8),
|
|
1208
1481
|
// 图片查重阈值
|
|
1209
|
-
imageMaxSize:
|
|
1482
|
+
imageMaxSize: import_koishi6.Schema.number().default(4),
|
|
1210
1483
|
// 图片大小限制(MB)
|
|
1211
|
-
allowVideo:
|
|
1484
|
+
allowVideo: import_koishi6.Schema.boolean().default(true),
|
|
1212
1485
|
// 允许视频
|
|
1213
|
-
videoMaxSize:
|
|
1486
|
+
videoMaxSize: import_koishi6.Schema.number().default(16),
|
|
1214
1487
|
// 视频大小限制(MB)
|
|
1215
|
-
enablePagination:
|
|
1488
|
+
enablePagination: import_koishi6.Schema.boolean().default(false),
|
|
1216
1489
|
// 启用分页
|
|
1217
|
-
itemsPerPage:
|
|
1490
|
+
itemsPerPage: import_koishi6.Schema.number().default(10),
|
|
1218
1491
|
// 每页条数
|
|
1219
|
-
blacklist:
|
|
1492
|
+
blacklist: import_koishi6.Schema.array(import_koishi6.Schema.string()).default([]),
|
|
1220
1493
|
// 黑名单
|
|
1221
|
-
whitelist:
|
|
1494
|
+
whitelist: import_koishi6.Schema.array(import_koishi6.Schema.string()).default([])
|
|
1222
1495
|
// 白名单
|
|
1223
1496
|
}).i18n({
|
|
1224
1497
|
"zh-CN": require_zh_CN()._config,
|
|
@@ -1227,124 +1500,21 @@ var Config = import_koishi5.Schema.object({
|
|
|
1227
1500
|
async function apply(ctx, config) {
|
|
1228
1501
|
ctx.i18n.define("zh-CN", require_zh_CN());
|
|
1229
1502
|
ctx.i18n.define("en-US", require_en_US());
|
|
1230
|
-
const dataDir =
|
|
1231
|
-
const caveDir =
|
|
1232
|
-
await FileHandler.ensureDirectory(dataDir);
|
|
1503
|
+
const dataDir = path7.join(ctx.baseDir, "data");
|
|
1504
|
+
const caveDir = path7.join(dataDir, "cave");
|
|
1233
1505
|
await FileHandler.ensureDirectory(caveDir);
|
|
1234
|
-
await FileHandler.ensureDirectory(
|
|
1235
|
-
await FileHandler.ensureJsonFile(
|
|
1236
|
-
await FileHandler.ensureJsonFile(
|
|
1237
|
-
await FileHandler.ensureJsonFile(
|
|
1506
|
+
await FileHandler.ensureDirectory(path7.join(caveDir, "resources"));
|
|
1507
|
+
await FileHandler.ensureJsonFile(path7.join(caveDir, "cave.json"));
|
|
1508
|
+
await FileHandler.ensureJsonFile(path7.join(caveDir, "pending.json"));
|
|
1509
|
+
await FileHandler.ensureJsonFile(path7.join(caveDir, "hash.json"));
|
|
1238
1510
|
const idManager = new IdManager(ctx.baseDir);
|
|
1239
|
-
const contentHashManager = new
|
|
1511
|
+
const contentHashManager = new HashManager(caveDir);
|
|
1240
1512
|
const auditManager = new AuditManager(ctx, config, caveDir, idManager);
|
|
1241
1513
|
await Promise.all([
|
|
1242
|
-
idManager.initialize(
|
|
1514
|
+
idManager.initialize(path7.join(caveDir, "cave.json"), path7.join(caveDir, "pending.json")),
|
|
1243
1515
|
contentHashManager.initialize()
|
|
1244
1516
|
]);
|
|
1245
1517
|
const lastUsed = /* @__PURE__ */ new Map();
|
|
1246
|
-
async function processList(session, config2, userId, pageNum = 1) {
|
|
1247
|
-
const stats = idManager.getStats();
|
|
1248
|
-
if (userId && userId in stats) {
|
|
1249
|
-
const ids = stats[userId];
|
|
1250
|
-
return session.text("commands.cave.list.totalItems", [userId, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
|
|
1251
|
-
}
|
|
1252
|
-
const lines = Object.entries(stats).map(([cid, ids]) => {
|
|
1253
|
-
return session.text("commands.cave.list.totalItems", [cid, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
|
|
1254
|
-
});
|
|
1255
|
-
const totalSubmissions = Object.values(stats).reduce((sum, arr) => sum + arr.length, 0);
|
|
1256
|
-
if (config2.enablePagination) {
|
|
1257
|
-
const itemsPerPage = config2.itemsPerPage;
|
|
1258
|
-
const totalPages = Math.max(1, Math.ceil(lines.length / itemsPerPage));
|
|
1259
|
-
pageNum = Math.min(Math.max(1, pageNum), totalPages);
|
|
1260
|
-
const start = (pageNum - 1) * itemsPerPage;
|
|
1261
|
-
const paginatedLines = lines.slice(start, start + itemsPerPage);
|
|
1262
|
-
return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + paginatedLines.join("\n") + "\n" + session.text("commands.cave.list.pageInfo", [pageNum, totalPages]);
|
|
1263
|
-
} else {
|
|
1264
|
-
return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + lines.join("\n");
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
__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
|
-
async function processView(caveFilePath, resourceDir, session, options, content) {
|
|
1282
|
-
const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
|
|
1283
|
-
if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
|
|
1284
|
-
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1285
|
-
const cave = data.find((item) => item.cave_id === caveId);
|
|
1286
|
-
if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
|
|
1287
|
-
return buildMessage(cave, resourceDir, session);
|
|
1288
|
-
}
|
|
1289
|
-
__name(processView, "processView");
|
|
1290
|
-
async function processRandom(caveFilePath, resourceDir, session) {
|
|
1291
|
-
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1292
|
-
if (data.length === 0) {
|
|
1293
|
-
return sendMessage(session, "commands.cave.error.noCave", [], true);
|
|
1294
|
-
}
|
|
1295
|
-
const cave = (() => {
|
|
1296
|
-
const validCaves = data.filter((cave2) => cave2.elements && cave2.elements.length > 0);
|
|
1297
|
-
if (!validCaves.length) return void 0;
|
|
1298
|
-
const randomIndex = Math.floor(Math.random() * validCaves.length);
|
|
1299
|
-
return validCaves[randomIndex];
|
|
1300
|
-
})();
|
|
1301
|
-
return cave ? buildMessage(cave, resourceDir, session) : sendMessage(session, "commands.cave.error.getCave", [], true);
|
|
1302
|
-
}
|
|
1303
|
-
__name(processRandom, "processRandom");
|
|
1304
|
-
async function processDelete(caveFilePath, resourceDir, pendingFilePath, session, config2, options, content) {
|
|
1305
|
-
const caveId = parseInt(content[0] || (typeof options.r === "string" ? options.r : ""));
|
|
1306
|
-
if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
|
|
1307
|
-
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1308
|
-
const pendingData = await FileHandler.readJsonData(pendingFilePath);
|
|
1309
|
-
const targetInData = data.find((item) => item.cave_id === caveId);
|
|
1310
|
-
const targetInPending = pendingData.find((item) => item.cave_id === caveId);
|
|
1311
|
-
if (!targetInData && !targetInPending) {
|
|
1312
|
-
return sendMessage(session, "commands.cave.error.notFound", [], true);
|
|
1313
|
-
}
|
|
1314
|
-
const targetCave = targetInData || targetInPending;
|
|
1315
|
-
const isPending = !targetInData;
|
|
1316
|
-
if (targetCave.contributor_number !== session.userId && !config2.manager.includes(session.userId)) {
|
|
1317
|
-
return sendMessage(session, "commands.cave.remove.noPermission", [], true);
|
|
1318
|
-
}
|
|
1319
|
-
const caveContent = await buildMessage(targetCave, resourceDir, session);
|
|
1320
|
-
if (targetCave.elements) {
|
|
1321
|
-
await contentHashManager.updateCaveContent(caveId, {
|
|
1322
|
-
images: void 0,
|
|
1323
|
-
texts: void 0
|
|
1324
|
-
});
|
|
1325
|
-
for (const element of targetCave.elements) {
|
|
1326
|
-
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);
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
if (isPending) {
|
|
1335
|
-
const newPendingData = pendingData.filter((item) => item.cave_id !== caveId);
|
|
1336
|
-
await FileHandler.writeJsonData(pendingFilePath, newPendingData);
|
|
1337
|
-
} else {
|
|
1338
|
-
const newData = data.filter((item) => item.cave_id !== caveId);
|
|
1339
|
-
await FileHandler.writeJsonData(caveFilePath, newData);
|
|
1340
|
-
await idManager.removeStat(targetCave.contributor_number, caveId);
|
|
1341
|
-
}
|
|
1342
|
-
await idManager.markDeleted(caveId);
|
|
1343
|
-
const deleteStatus = isPending ? session.text("commands.cave.remove.deletePending") : "";
|
|
1344
|
-
const deleteMessage = session.text("commands.cave.remove.deleted");
|
|
1345
|
-
return `${deleteMessage}${deleteStatus}${caveContent}`;
|
|
1346
|
-
}
|
|
1347
|
-
__name(processDelete, "processDelete");
|
|
1348
1518
|
async function processAdd(ctx2, config2, caveFilePath, resourceDir, pendingFilePath, session, content) {
|
|
1349
1519
|
let caveId;
|
|
1350
1520
|
try {
|
|
@@ -1355,9 +1525,15 @@ async function apply(ctx, config) {
|
|
|
1355
1525
|
const inputContent = content.length > 0 ? content.join("\n") : await (async () => {
|
|
1356
1526
|
await sendMessage(session, "commands.cave.add.noContent", [], true, 6e4);
|
|
1357
1527
|
const reply = await session.prompt({ timeout: 6e4 });
|
|
1358
|
-
if (!reply)
|
|
1528
|
+
if (!reply) {
|
|
1529
|
+
await sendMessage(session, "commands.cave.add.operationTimeout", [], true);
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1359
1532
|
return reply;
|
|
1360
1533
|
})();
|
|
1534
|
+
if (!inputContent) {
|
|
1535
|
+
return "";
|
|
1536
|
+
}
|
|
1361
1537
|
if (inputContent.includes("/app/.config/QQ/")) {
|
|
1362
1538
|
return sendMessage(session, "commands.cave.add.localFileNotAllowed", [], true);
|
|
1363
1539
|
}
|
|
@@ -1415,7 +1591,7 @@ async function apply(ctx, config) {
|
|
|
1415
1591
|
index: Number.MAX_SAFE_INTEGER
|
|
1416
1592
|
});
|
|
1417
1593
|
}
|
|
1418
|
-
const hashStorage = new
|
|
1594
|
+
const hashStorage = new HashManager(path7.join(ctx2.baseDir, "data", "cave"));
|
|
1419
1595
|
await hashStorage.initialize();
|
|
1420
1596
|
const hashStatus = await hashStorage.getStatus();
|
|
1421
1597
|
if (!hashStatus.lastUpdated || hashStatus.entries.length === 0) {
|
|
@@ -1465,7 +1641,7 @@ async function apply(ctx, config) {
|
|
|
1465
1641
|
await Promise.all([
|
|
1466
1642
|
FileHandler.writeJsonData(caveFilePath, data),
|
|
1467
1643
|
contentHashManager.updateCaveContent(caveId, {
|
|
1468
|
-
images: savedImages.length > 0 ? await Promise.all(savedImages.map((file) =>
|
|
1644
|
+
images: savedImages.length > 0 ? await Promise.all(savedImages.map((file) => fs7.promises.readFile(path7.join(resourceDir, file)))) : void 0,
|
|
1469
1645
|
texts: textParts.filter((p) => p.type === "text").map((p) => p.content)
|
|
1470
1646
|
})
|
|
1471
1647
|
]);
|
|
@@ -1478,7 +1654,7 @@ async function apply(ctx, config) {
|
|
|
1478
1654
|
if (error.message === "duplicate_found") {
|
|
1479
1655
|
return "";
|
|
1480
1656
|
}
|
|
1481
|
-
|
|
1657
|
+
logger5.error(`Failed to process add command: ${error.message}`);
|
|
1482
1658
|
return sendMessage(session, "commands.cave.error.addFailed", [], true);
|
|
1483
1659
|
}
|
|
1484
1660
|
}
|
|
@@ -1488,11 +1664,11 @@ async function apply(ctx, config) {
|
|
|
1488
1664
|
return sendMessage(session, "commands.cave.message.blacklisted", [], true);
|
|
1489
1665
|
}
|
|
1490
1666
|
}).action(async ({ session, options }, ...content) => {
|
|
1491
|
-
const dataDir2 =
|
|
1492
|
-
const caveDir2 =
|
|
1493
|
-
const caveFilePath =
|
|
1494
|
-
const resourceDir =
|
|
1495
|
-
const pendingFilePath =
|
|
1667
|
+
const dataDir2 = path7.join(ctx.baseDir, "data");
|
|
1668
|
+
const caveDir2 = path7.join(dataDir2, "cave");
|
|
1669
|
+
const caveFilePath = path7.join(caveDir2, "cave.json");
|
|
1670
|
+
const resourceDir = path7.join(caveDir2, "resources");
|
|
1671
|
+
const pendingFilePath = path7.join(caveDir2, "pending.json");
|
|
1496
1672
|
const needsCooldown = !options.l && !options.a;
|
|
1497
1673
|
if (needsCooldown) {
|
|
1498
1674
|
const guildId = session.guildId;
|
|
@@ -1511,23 +1687,23 @@ async function apply(ctx, config) {
|
|
|
1511
1687
|
if (config.manager.includes(session.userId)) {
|
|
1512
1688
|
if (!isNaN(num)) {
|
|
1513
1689
|
if (num < 1e4) {
|
|
1514
|
-
return await processList(session, config, void 0, num);
|
|
1690
|
+
return await processList(session, config, idManager, void 0, num);
|
|
1515
1691
|
} else {
|
|
1516
|
-
return await processList(session, config, num.toString());
|
|
1692
|
+
return await processList(session, config, idManager, num.toString());
|
|
1517
1693
|
}
|
|
1518
1694
|
} else if (input) {
|
|
1519
|
-
return await processList(session, config, input);
|
|
1695
|
+
return await processList(session, config, idManager, input);
|
|
1520
1696
|
}
|
|
1521
|
-
return await processList(session, config);
|
|
1697
|
+
return await processList(session, config, idManager);
|
|
1522
1698
|
} else {
|
|
1523
|
-
return await processList(session, config, session.userId);
|
|
1699
|
+
return await processList(session, config, idManager, session.userId);
|
|
1524
1700
|
}
|
|
1525
1701
|
}
|
|
1526
1702
|
if (options.g) {
|
|
1527
1703
|
return await processView(caveFilePath, resourceDir, session, options, content);
|
|
1528
1704
|
}
|
|
1529
1705
|
if (options.r) {
|
|
1530
|
-
return await processDelete(caveFilePath, resourceDir, pendingFilePath, session, config, options, content);
|
|
1706
|
+
return await processDelete(caveFilePath, resourceDir, pendingFilePath, session, config, options, content, idManager, contentHashManager);
|
|
1531
1707
|
}
|
|
1532
1708
|
if (options.a) {
|
|
1533
1709
|
return await processAdd(ctx, config, caveFilePath, resourceDir, pendingFilePath, session, content);
|
|
@@ -1539,60 +1715,30 @@ async function apply(ctx, config) {
|
|
|
1539
1715
|
return sendMessage(session, "commands.cave.message.managerOnly", [], true);
|
|
1540
1716
|
}
|
|
1541
1717
|
}).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
|
-
);
|
|
1718
|
+
const dataDir2 = path7.join(ctx.baseDir, "data");
|
|
1719
|
+
const caveDir2 = path7.join(dataDir2, "cave");
|
|
1720
|
+
const caveFilePath = path7.join(caveDir2, "cave.json");
|
|
1721
|
+
const resourceDir = path7.join(caveDir2, "resources");
|
|
1722
|
+
const pendingFilePath = path7.join(caveDir2, "pending.json");
|
|
1723
|
+
const pendingData = await FileHandler.readJsonData(pendingFilePath);
|
|
1724
|
+
return await auditManager.processAudit(pendingData, true, caveFilePath, resourceDir, pendingFilePath, session, id === "all" ? void 0 : parseInt(id));
|
|
1555
1725
|
});
|
|
1556
1726
|
caveCommand.subcommand(".reject <id:text>", "拒绝回声洞审核").before(async ({ session }) => {
|
|
1557
1727
|
if (!config.manager.includes(session.userId)) {
|
|
1558
1728
|
return sendMessage(session, "commands.cave.message.managerOnly", [], true);
|
|
1559
1729
|
}
|
|
1560
1730
|
}).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
|
-
);
|
|
1731
|
+
const dataDir2 = path7.join(ctx.baseDir, "data");
|
|
1732
|
+
const caveDir2 = path7.join(dataDir2, "cave");
|
|
1733
|
+
const caveFilePath = path7.join(caveDir2, "cave.json");
|
|
1734
|
+
const resourceDir = path7.join(caveDir2, "resources");
|
|
1735
|
+
const pendingFilePath = path7.join(caveDir2, "pending.json");
|
|
1736
|
+
const pendingData = await FileHandler.readJsonData(pendingFilePath);
|
|
1737
|
+
return await auditManager.processAudit(pendingData, false, caveFilePath, resourceDir, pendingFilePath, session, id === "all" ? void 0 : parseInt(id));
|
|
1574
1738
|
});
|
|
1575
1739
|
}
|
|
1576
1740
|
__name(apply, "apply");
|
|
1577
|
-
var
|
|
1578
|
-
async function sendMessage(session, key, params = [], isTemp = true, timeout = 1e4) {
|
|
1579
|
-
try {
|
|
1580
|
-
const msg = await session.send(session.text(key, params));
|
|
1581
|
-
if (isTemp && msg) {
|
|
1582
|
-
setTimeout(async () => {
|
|
1583
|
-
try {
|
|
1584
|
-
await session.bot.deleteMessage(session.channelId, msg);
|
|
1585
|
-
} catch (error) {
|
|
1586
|
-
logger4.debug(`Failed to delete temporary message: ${error.message}`);
|
|
1587
|
-
}
|
|
1588
|
-
}, timeout);
|
|
1589
|
-
}
|
|
1590
|
-
} catch (error) {
|
|
1591
|
-
logger4.error(`Failed to send message: ${error.message}`);
|
|
1592
|
-
}
|
|
1593
|
-
return "";
|
|
1594
|
-
}
|
|
1595
|
-
__name(sendMessage, "sendMessage");
|
|
1741
|
+
var logger5 = new import_koishi6.Logger("cave");
|
|
1596
1742
|
function cleanElementsForSave(elements, keepIndex = false) {
|
|
1597
1743
|
if (!elements?.length) return [];
|
|
1598
1744
|
const cleanedElements = elements.map((element) => {
|
|
@@ -1617,171 +1763,6 @@ function cleanElementsForSave(elements, keepIndex = false) {
|
|
|
1617
1763
|
return keepIndex ? cleanedElements.sort((a, b) => (a.index || 0) - (b.index || 0)) : cleanedElements;
|
|
1618
1764
|
}
|
|
1619
1765
|
__name(cleanElementsForSave, "cleanElementsForSave");
|
|
1620
|
-
async function processMediaFile(filePath, type) {
|
|
1621
|
-
const data = await fs5.promises.readFile(filePath).catch(() => null);
|
|
1622
|
-
if (!data) return null;
|
|
1623
|
-
return `data:${type}/${type === "image" ? "png" : "mp4"};base64,${data.toString("base64")}`;
|
|
1624
|
-
}
|
|
1625
|
-
__name(processMediaFile, "processMediaFile");
|
|
1626
|
-
async function buildMessage(cave, resourceDir, session) {
|
|
1627
|
-
if (!cave?.elements?.length) {
|
|
1628
|
-
return session.text("commands.cave.error.noContent");
|
|
1629
|
-
}
|
|
1630
|
-
const videoElement = cave.elements.find((el) => el.type === "video");
|
|
1631
|
-
const nonVideoElements = cave.elements.filter((el) => el.type !== "video").sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
1632
|
-
if (videoElement?.file) {
|
|
1633
|
-
const basicInfo = [
|
|
1634
|
-
session.text("commands.cave.message.caveTitle", [cave.cave_id]),
|
|
1635
|
-
session.text("commands.cave.message.contributorSuffix", [cave.contributor_name])
|
|
1636
|
-
].join("\n");
|
|
1637
|
-
await session?.send(basicInfo);
|
|
1638
|
-
const filePath = path5.join(resourceDir, videoElement.file);
|
|
1639
|
-
const base64Data = await processMediaFile(filePath, "video");
|
|
1640
|
-
if (base64Data && session) {
|
|
1641
|
-
await session.send((0, import_koishi5.h)("video", { src: base64Data }));
|
|
1642
|
-
}
|
|
1643
|
-
return "";
|
|
1644
|
-
}
|
|
1645
|
-
const lines = [session.text("commands.cave.message.caveTitle", [cave.cave_id])];
|
|
1646
|
-
for (const element of nonVideoElements) {
|
|
1647
|
-
if (element.type === "text") {
|
|
1648
|
-
lines.push(element.content);
|
|
1649
|
-
} else if (element.type === "img" && element.file) {
|
|
1650
|
-
const filePath = path5.join(resourceDir, element.file);
|
|
1651
|
-
const base64Data = await processMediaFile(filePath, "image");
|
|
1652
|
-
if (base64Data) {
|
|
1653
|
-
lines.push((0, import_koishi5.h)("image", { src: base64Data }));
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
lines.push(session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]));
|
|
1658
|
-
return lines.join("\n");
|
|
1659
|
-
}
|
|
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(/&/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
1766
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1786
1767
|
0 && (module.exports = {
|
|
1787
1768
|
Config,
|