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 +239 -226
- 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"));
|
|
@@ -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
|
|
350
|
-
|
|
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 =
|
|
353
|
-
this.deletedIds.delete(nextId);
|
|
358
|
+
nextId = this.maxId + 1;
|
|
354
359
|
}
|
|
355
|
-
while (this.usedIds.has(nextId)) {
|
|
356
|
-
nextId =
|
|
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(/&/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 =
|
|
1189
|
-
manager:
|
|
1338
|
+
var Config = import_koishi6.Schema.object({
|
|
1339
|
+
manager: import_koishi6.Schema.array(import_koishi6.Schema.string()).required(),
|
|
1190
1340
|
// 管理员用户ID
|
|
1191
|
-
number:
|
|
1341
|
+
number: import_koishi6.Schema.number().default(60),
|
|
1192
1342
|
// 冷却时间(秒)
|
|
1193
|
-
enableAudit:
|
|
1343
|
+
enableAudit: import_koishi6.Schema.boolean().default(false),
|
|
1194
1344
|
// 启用审核
|
|
1195
|
-
enableTextDuplicate:
|
|
1345
|
+
enableTextDuplicate: import_koishi6.Schema.boolean().default(true),
|
|
1196
1346
|
// 启用文本查重
|
|
1197
|
-
textDuplicateThreshold:
|
|
1347
|
+
textDuplicateThreshold: import_koishi6.Schema.number().default(0.9),
|
|
1198
1348
|
// 文本查重阈值
|
|
1199
|
-
enableImageDuplicate:
|
|
1349
|
+
enableImageDuplicate: import_koishi6.Schema.boolean().default(true),
|
|
1200
1350
|
// 开启图片查重
|
|
1201
|
-
imageDuplicateThreshold:
|
|
1351
|
+
imageDuplicateThreshold: import_koishi6.Schema.number().default(0.8),
|
|
1202
1352
|
// 图片查重阈值
|
|
1203
|
-
imageMaxSize:
|
|
1353
|
+
imageMaxSize: import_koishi6.Schema.number().default(4),
|
|
1204
1354
|
// 图片大小限制(MB)
|
|
1205
|
-
allowVideo:
|
|
1355
|
+
allowVideo: import_koishi6.Schema.boolean().default(true),
|
|
1206
1356
|
// 允许视频
|
|
1207
|
-
videoMaxSize:
|
|
1357
|
+
videoMaxSize: import_koishi6.Schema.number().default(16),
|
|
1208
1358
|
// 视频大小限制(MB)
|
|
1209
|
-
enablePagination:
|
|
1359
|
+
enablePagination: import_koishi6.Schema.boolean().default(false),
|
|
1210
1360
|
// 启用分页
|
|
1211
|
-
itemsPerPage:
|
|
1361
|
+
itemsPerPage: import_koishi6.Schema.number().default(10),
|
|
1212
1362
|
// 每页条数
|
|
1213
|
-
blacklist:
|
|
1363
|
+
blacklist: import_koishi6.Schema.array(import_koishi6.Schema.string()).default([]),
|
|
1214
1364
|
// 黑名单
|
|
1215
|
-
whitelist:
|
|
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 =
|
|
1225
|
-
const caveDir =
|
|
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(
|
|
1229
|
-
await FileHandler.ensureJsonFile(
|
|
1230
|
-
await FileHandler.ensureJsonFile(
|
|
1231
|
-
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"));
|
|
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(
|
|
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
|
|
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 ?
|
|
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
|
|
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 =
|
|
1322
|
-
if (
|
|
1323
|
-
await
|
|
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)
|
|
1489
|
+
if (!reply) {
|
|
1490
|
+
await sendMessage(session, "commands.cave.add.operationTimeout", [], true);
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1349
1493
|
return reply;
|
|
1350
1494
|
})();
|
|
1351
|
-
|
|
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 -
|
|
1396
|
-
contributor_number: session.userId,
|
|
1397
|
-
|
|
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(
|
|
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
|
|
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
|
|
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) =>
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
1480
|
-
const caveDir2 =
|
|
1481
|
-
const caveFilePath =
|
|
1482
|
-
const resourceDir =
|
|
1483
|
-
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");
|
|
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 =
|
|
1531
|
-
const caveDir2 =
|
|
1532
|
-
const caveFilePath =
|
|
1533
|
-
const resourceDir =
|
|
1534
|
-
const pendingFilePath =
|
|
1535
|
-
|
|
1536
|
-
|
|
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 =
|
|
1550
|
-
const caveDir2 =
|
|
1551
|
-
const caveFilePath =
|
|
1552
|
-
const resourceDir =
|
|
1553
|
-
const pendingFilePath =
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
|
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
|
-
|
|
1711
|
+
logger5.debug(`Failed to delete temporary message: ${error.message}`);
|
|
1575
1712
|
}
|
|
1576
1713
|
}, timeout);
|
|
1577
1714
|
}
|
|
1578
1715
|
} catch (error) {
|
|
1579
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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,
|
|
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 =
|
|
1775
|
+
const filePath = path6.join(resourceDir, element.file);
|
|
1639
1776
|
const base64Data = await processMediaFile(filePath, "image");
|
|
1640
1777
|
if (base64Data) {
|
|
1641
|
-
lines.push((0,
|
|
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(
|
|
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(/&/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[]>;
|