koishi-plugin-best-cave 1.5.4 → 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 +196 -216
- 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/MediaHandler.d.ts +52 -0
- package/lib/utils/ProcessHandle.d.ts +7 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -54,10 +54,10 @@ __export(src_exports, {
|
|
|
54
54
|
});
|
|
55
55
|
module.exports = __toCommonJS(src_exports);
|
|
56
56
|
var import_koishi6 = require("koishi");
|
|
57
|
-
var
|
|
58
|
-
var
|
|
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,11 +1179,70 @@ ${session.text("commands.cave.audit.from")}${cave.contributor_number}`;
|
|
|
1188
1179
|
}
|
|
1189
1180
|
};
|
|
1190
1181
|
|
|
1191
|
-
// src/utils/
|
|
1182
|
+
// src/utils/MediaHandler.ts
|
|
1192
1183
|
var import_koishi5 = require("koishi");
|
|
1193
1184
|
var fs5 = __toESM(require("fs"));
|
|
1194
1185
|
var path5 = __toESM(require("path"));
|
|
1195
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");
|
|
1196
1246
|
async function extractMediaContent(originalContent, config, session) {
|
|
1197
1247
|
const textParts = originalContent.split(/<(img|video)[^>]+>/).map((text, idx) => text.trim() && {
|
|
1198
1248
|
type: "text",
|
|
@@ -1236,7 +1286,7 @@ async function extractMediaContent(originalContent, config, session) {
|
|
|
1236
1286
|
__name(extractMediaContent, "extractMediaContent");
|
|
1237
1287
|
async function saveMedia(urls, fileNames, resourceDir, caveId, mediaType, config, ctx, session, buffers) {
|
|
1238
1288
|
const accept = mediaType === "img" ? "image/*" : "video/*";
|
|
1239
|
-
const hashStorage = new
|
|
1289
|
+
const hashStorage = new HashManager(path5.join(ctx.baseDir, "data", "cave"));
|
|
1240
1290
|
await hashStorage.initialize();
|
|
1241
1291
|
const downloadTasks = urls.map(async (url, i) => {
|
|
1242
1292
|
const fileName = fileNames[i];
|
|
@@ -1317,20 +1367,99 @@ async function saveMedia(urls, fileNames, resourceDir, caveId, mediaType, config
|
|
|
1317
1367
|
return Promise.all(downloadTasks);
|
|
1318
1368
|
}
|
|
1319
1369
|
__name(saveMedia, "saveMedia");
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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");
|
|
1323
1393
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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
|
+
}
|
|
1328
1447
|
}
|
|
1329
1448
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
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}`;
|
|
1332
1461
|
}
|
|
1333
|
-
__name(
|
|
1462
|
+
__name(processDelete, "processDelete");
|
|
1334
1463
|
|
|
1335
1464
|
// src/index.ts
|
|
1336
1465
|
var name = "best-cave";
|
|
@@ -1371,111 +1500,21 @@ var Config = import_koishi6.Schema.object({
|
|
|
1371
1500
|
async function apply(ctx, config) {
|
|
1372
1501
|
ctx.i18n.define("zh-CN", require_zh_CN());
|
|
1373
1502
|
ctx.i18n.define("en-US", require_en_US());
|
|
1374
|
-
const dataDir =
|
|
1375
|
-
const caveDir =
|
|
1376
|
-
await FileHandler.ensureDirectory(dataDir);
|
|
1503
|
+
const dataDir = path7.join(ctx.baseDir, "data");
|
|
1504
|
+
const caveDir = path7.join(dataDir, "cave");
|
|
1377
1505
|
await FileHandler.ensureDirectory(caveDir);
|
|
1378
|
-
await FileHandler.ensureDirectory(
|
|
1379
|
-
await FileHandler.ensureJsonFile(
|
|
1380
|
-
await FileHandler.ensureJsonFile(
|
|
1381
|
-
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"));
|
|
1382
1510
|
const idManager = new IdManager(ctx.baseDir);
|
|
1383
|
-
const contentHashManager = new
|
|
1511
|
+
const contentHashManager = new HashManager(caveDir);
|
|
1384
1512
|
const auditManager = new AuditManager(ctx, config, caveDir, idManager);
|
|
1385
1513
|
await Promise.all([
|
|
1386
|
-
idManager.initialize(
|
|
1514
|
+
idManager.initialize(path7.join(caveDir, "cave.json"), path7.join(caveDir, "pending.json")),
|
|
1387
1515
|
contentHashManager.initialize()
|
|
1388
1516
|
]);
|
|
1389
1517
|
const lastUsed = /* @__PURE__ */ new Map();
|
|
1390
|
-
async function processList(session, config2, userId, pageNum = 1) {
|
|
1391
|
-
const stats = idManager.getStats();
|
|
1392
|
-
if (userId && userId in stats) {
|
|
1393
|
-
const ids = stats[userId];
|
|
1394
|
-
return session.text("commands.cave.list.totalItems", [userId, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
|
|
1395
|
-
}
|
|
1396
|
-
const lines = Object.entries(stats).map(([cid, ids]) => {
|
|
1397
|
-
return session.text("commands.cave.list.totalItems", [cid, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
|
|
1398
|
-
});
|
|
1399
|
-
const totalSubmissions = Object.values(stats).reduce((sum, arr) => sum + arr.length, 0);
|
|
1400
|
-
if (config2.enablePagination) {
|
|
1401
|
-
const itemsPerPage = config2.itemsPerPage;
|
|
1402
|
-
const totalPages = Math.max(1, Math.ceil(lines.length / itemsPerPage));
|
|
1403
|
-
pageNum = Math.min(Math.max(1, pageNum), totalPages);
|
|
1404
|
-
const start = (pageNum - 1) * itemsPerPage;
|
|
1405
|
-
const paginatedLines = lines.slice(start, start + itemsPerPage);
|
|
1406
|
-
return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + paginatedLines.join("\n") + "\n" + session.text("commands.cave.list.pageInfo", [pageNum, totalPages]);
|
|
1407
|
-
} else {
|
|
1408
|
-
return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + lines.join("\n");
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
__name(processList, "processList");
|
|
1412
|
-
async function processView(caveFilePath, resourceDir, session, options, content) {
|
|
1413
|
-
const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
|
|
1414
|
-
if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
|
|
1415
|
-
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1416
|
-
const cave = data.find((item) => item.cave_id === caveId);
|
|
1417
|
-
if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
|
|
1418
|
-
return buildMessage2(cave, resourceDir, session);
|
|
1419
|
-
}
|
|
1420
|
-
__name(processView, "processView");
|
|
1421
|
-
async function processRandom(caveFilePath, resourceDir, session) {
|
|
1422
|
-
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1423
|
-
if (data.length === 0) {
|
|
1424
|
-
return sendMessage(session, "commands.cave.error.noCave", [], true);
|
|
1425
|
-
}
|
|
1426
|
-
const cave = (() => {
|
|
1427
|
-
const validCaves = data.filter((cave2) => cave2.elements && cave2.elements.length > 0);
|
|
1428
|
-
if (!validCaves.length) return void 0;
|
|
1429
|
-
const randomIndex = Math.floor(Math.random() * validCaves.length);
|
|
1430
|
-
return validCaves[randomIndex];
|
|
1431
|
-
})();
|
|
1432
|
-
return cave ? buildMessage2(cave, resourceDir, session) : sendMessage(session, "commands.cave.error.getCave", [], true);
|
|
1433
|
-
}
|
|
1434
|
-
__name(processRandom, "processRandom");
|
|
1435
|
-
async function processDelete(caveFilePath, resourceDir, pendingFilePath, session, config2, options, content) {
|
|
1436
|
-
const caveId = parseInt(content[0] || (typeof options.r === "string" ? options.r : ""));
|
|
1437
|
-
if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
|
|
1438
|
-
const data = await FileHandler.readJsonData(caveFilePath);
|
|
1439
|
-
const pendingData = await FileHandler.readJsonData(pendingFilePath);
|
|
1440
|
-
const targetInData = data.find((item) => item.cave_id === caveId);
|
|
1441
|
-
const targetInPending = pendingData.find((item) => item.cave_id === caveId);
|
|
1442
|
-
if (!targetInData && !targetInPending) {
|
|
1443
|
-
return sendMessage(session, "commands.cave.error.notFound", [], true);
|
|
1444
|
-
}
|
|
1445
|
-
const targetCave = targetInData || targetInPending;
|
|
1446
|
-
const isPending = !targetInData;
|
|
1447
|
-
if (targetCave.contributor_number !== session.userId && !config2.manager.includes(session.userId)) {
|
|
1448
|
-
return sendMessage(session, "commands.cave.remove.noPermission", [], true);
|
|
1449
|
-
}
|
|
1450
|
-
const caveContent = await buildMessage2(targetCave, resourceDir, session);
|
|
1451
|
-
if (targetCave.elements) {
|
|
1452
|
-
await contentHashManager.updateCaveContent(caveId, {
|
|
1453
|
-
images: void 0,
|
|
1454
|
-
texts: void 0
|
|
1455
|
-
});
|
|
1456
|
-
for (const element of targetCave.elements) {
|
|
1457
|
-
if ((element.type === "img" || element.type === "video") && element.file) {
|
|
1458
|
-
const fullPath = path6.join(resourceDir, element.file);
|
|
1459
|
-
if (fs6.existsSync(fullPath)) {
|
|
1460
|
-
await fs6.promises.unlink(fullPath);
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
if (isPending) {
|
|
1466
|
-
const newPendingData = pendingData.filter((item) => item.cave_id !== caveId);
|
|
1467
|
-
await FileHandler.writeJsonData(pendingFilePath, newPendingData);
|
|
1468
|
-
} else {
|
|
1469
|
-
const newData = data.filter((item) => item.cave_id !== caveId);
|
|
1470
|
-
await FileHandler.writeJsonData(caveFilePath, newData);
|
|
1471
|
-
await idManager.removeStat(targetCave.contributor_number, caveId);
|
|
1472
|
-
}
|
|
1473
|
-
await idManager.markDeleted(caveId);
|
|
1474
|
-
const deleteStatus = isPending ? session.text("commands.cave.remove.deletePending") : "";
|
|
1475
|
-
const deleteMessage = session.text("commands.cave.remove.deleted");
|
|
1476
|
-
return `${deleteMessage}${deleteStatus}${caveContent}`;
|
|
1477
|
-
}
|
|
1478
|
-
__name(processDelete, "processDelete");
|
|
1479
1518
|
async function processAdd(ctx2, config2, caveFilePath, resourceDir, pendingFilePath, session, content) {
|
|
1480
1519
|
let caveId;
|
|
1481
1520
|
try {
|
|
@@ -1552,7 +1591,7 @@ async function apply(ctx, config) {
|
|
|
1552
1591
|
index: Number.MAX_SAFE_INTEGER
|
|
1553
1592
|
});
|
|
1554
1593
|
}
|
|
1555
|
-
const hashStorage = new
|
|
1594
|
+
const hashStorage = new HashManager(path7.join(ctx2.baseDir, "data", "cave"));
|
|
1556
1595
|
await hashStorage.initialize();
|
|
1557
1596
|
const hashStatus = await hashStorage.getStatus();
|
|
1558
1597
|
if (!hashStatus.lastUpdated || hashStatus.entries.length === 0) {
|
|
@@ -1569,7 +1608,7 @@ async function apply(ctx, config) {
|
|
|
1569
1608
|
pendingData.push(newCave);
|
|
1570
1609
|
await Promise.all([
|
|
1571
1610
|
FileHandler.writeJsonData(pendingFilePath, pendingData),
|
|
1572
|
-
auditManager.sendAuditMessage(newCave, await
|
|
1611
|
+
auditManager.sendAuditMessage(newCave, await buildMessage(newCave, resourceDir, session), session)
|
|
1573
1612
|
]);
|
|
1574
1613
|
return sendMessage(session, "commands.cave.add.submitPending", [caveId], false);
|
|
1575
1614
|
}
|
|
@@ -1595,14 +1634,14 @@ async function apply(ctx, config) {
|
|
|
1595
1634
|
"commands.cave.error.similarDuplicateFound",
|
|
1596
1635
|
[(result.similarity * 100).toFixed(1)]
|
|
1597
1636
|
);
|
|
1598
|
-
await session.send(duplicateMessage + await
|
|
1637
|
+
await session.send(duplicateMessage + await buildMessage(originalCave, resourceDir, session));
|
|
1599
1638
|
throw new Error("duplicate_found");
|
|
1600
1639
|
}
|
|
1601
1640
|
}
|
|
1602
1641
|
await Promise.all([
|
|
1603
1642
|
FileHandler.writeJsonData(caveFilePath, data),
|
|
1604
1643
|
contentHashManager.updateCaveContent(caveId, {
|
|
1605
|
-
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,
|
|
1606
1645
|
texts: textParts.filter((p) => p.type === "text").map((p) => p.content)
|
|
1607
1646
|
})
|
|
1608
1647
|
]);
|
|
@@ -1625,11 +1664,11 @@ async function apply(ctx, config) {
|
|
|
1625
1664
|
return sendMessage(session, "commands.cave.message.blacklisted", [], true);
|
|
1626
1665
|
}
|
|
1627
1666
|
}).action(async ({ session, options }, ...content) => {
|
|
1628
|
-
const dataDir2 =
|
|
1629
|
-
const caveDir2 =
|
|
1630
|
-
const caveFilePath =
|
|
1631
|
-
const resourceDir =
|
|
1632
|
-
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");
|
|
1633
1672
|
const needsCooldown = !options.l && !options.a;
|
|
1634
1673
|
if (needsCooldown) {
|
|
1635
1674
|
const guildId = session.guildId;
|
|
@@ -1648,23 +1687,23 @@ async function apply(ctx, config) {
|
|
|
1648
1687
|
if (config.manager.includes(session.userId)) {
|
|
1649
1688
|
if (!isNaN(num)) {
|
|
1650
1689
|
if (num < 1e4) {
|
|
1651
|
-
return await processList(session, config, void 0, num);
|
|
1690
|
+
return await processList(session, config, idManager, void 0, num);
|
|
1652
1691
|
} else {
|
|
1653
|
-
return await processList(session, config, num.toString());
|
|
1692
|
+
return await processList(session, config, idManager, num.toString());
|
|
1654
1693
|
}
|
|
1655
1694
|
} else if (input) {
|
|
1656
|
-
return await processList(session, config, input);
|
|
1695
|
+
return await processList(session, config, idManager, input);
|
|
1657
1696
|
}
|
|
1658
|
-
return await processList(session, config);
|
|
1697
|
+
return await processList(session, config, idManager);
|
|
1659
1698
|
} else {
|
|
1660
|
-
return await processList(session, config, session.userId);
|
|
1699
|
+
return await processList(session, config, idManager, session.userId);
|
|
1661
1700
|
}
|
|
1662
1701
|
}
|
|
1663
1702
|
if (options.g) {
|
|
1664
1703
|
return await processView(caveFilePath, resourceDir, session, options, content);
|
|
1665
1704
|
}
|
|
1666
1705
|
if (options.r) {
|
|
1667
|
-
return await processDelete(caveFilePath, resourceDir, pendingFilePath, session, config, options, content);
|
|
1706
|
+
return await processDelete(caveFilePath, resourceDir, pendingFilePath, session, config, options, content, idManager, contentHashManager);
|
|
1668
1707
|
}
|
|
1669
1708
|
if (options.a) {
|
|
1670
1709
|
return await processAdd(ctx, config, caveFilePath, resourceDir, pendingFilePath, session, content);
|
|
@@ -1676,11 +1715,11 @@ async function apply(ctx, config) {
|
|
|
1676
1715
|
return sendMessage(session, "commands.cave.message.managerOnly", [], true);
|
|
1677
1716
|
}
|
|
1678
1717
|
}).action(async ({ session }, id) => {
|
|
1679
|
-
const dataDir2 =
|
|
1680
|
-
const caveDir2 =
|
|
1681
|
-
const caveFilePath =
|
|
1682
|
-
const resourceDir =
|
|
1683
|
-
const pendingFilePath =
|
|
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");
|
|
1684
1723
|
const pendingData = await FileHandler.readJsonData(pendingFilePath);
|
|
1685
1724
|
return await auditManager.processAudit(pendingData, true, caveFilePath, resourceDir, pendingFilePath, session, id === "all" ? void 0 : parseInt(id));
|
|
1686
1725
|
});
|
|
@@ -1689,35 +1728,17 @@ async function apply(ctx, config) {
|
|
|
1689
1728
|
return sendMessage(session, "commands.cave.message.managerOnly", [], true);
|
|
1690
1729
|
}
|
|
1691
1730
|
}).action(async ({ session }, id) => {
|
|
1692
|
-
const dataDir2 =
|
|
1693
|
-
const caveDir2 =
|
|
1694
|
-
const caveFilePath =
|
|
1695
|
-
const resourceDir =
|
|
1696
|
-
const pendingFilePath =
|
|
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");
|
|
1697
1736
|
const pendingData = await FileHandler.readJsonData(pendingFilePath);
|
|
1698
1737
|
return await auditManager.processAudit(pendingData, false, caveFilePath, resourceDir, pendingFilePath, session, id === "all" ? void 0 : parseInt(id));
|
|
1699
1738
|
});
|
|
1700
1739
|
}
|
|
1701
1740
|
__name(apply, "apply");
|
|
1702
1741
|
var logger5 = new import_koishi6.Logger("cave");
|
|
1703
|
-
async function sendMessage(session, key, params = [], isTemp = true, timeout = 1e4) {
|
|
1704
|
-
try {
|
|
1705
|
-
const msg = await session.send(session.text(key, params));
|
|
1706
|
-
if (isTemp && msg) {
|
|
1707
|
-
setTimeout(async () => {
|
|
1708
|
-
try {
|
|
1709
|
-
await session.bot.deleteMessage(session.channelId, msg);
|
|
1710
|
-
} catch (error) {
|
|
1711
|
-
logger5.debug(`Failed to delete temporary message: ${error.message}`);
|
|
1712
|
-
}
|
|
1713
|
-
}, timeout);
|
|
1714
|
-
}
|
|
1715
|
-
} catch (error) {
|
|
1716
|
-
logger5.error(`Failed to send message: ${error.message}`);
|
|
1717
|
-
}
|
|
1718
|
-
return "";
|
|
1719
|
-
}
|
|
1720
|
-
__name(sendMessage, "sendMessage");
|
|
1721
1742
|
function cleanElementsForSave(elements, keepIndex = false) {
|
|
1722
1743
|
if (!elements?.length) return [];
|
|
1723
1744
|
const cleanedElements = elements.map((element) => {
|
|
@@ -1742,47 +1763,6 @@ function cleanElementsForSave(elements, keepIndex = false) {
|
|
|
1742
1763
|
return keepIndex ? cleanedElements.sort((a, b) => (a.index || 0) - (b.index || 0)) : cleanedElements;
|
|
1743
1764
|
}
|
|
1744
1765
|
__name(cleanElementsForSave, "cleanElementsForSave");
|
|
1745
|
-
async function processMediaFile(filePath, type) {
|
|
1746
|
-
const data = await fs6.promises.readFile(filePath).catch(() => null);
|
|
1747
|
-
if (!data) return null;
|
|
1748
|
-
return `data:${type}/${type === "image" ? "png" : "mp4"};base64,${data.toString("base64")}`;
|
|
1749
|
-
}
|
|
1750
|
-
__name(processMediaFile, "processMediaFile");
|
|
1751
|
-
async function buildMessage2(cave, resourceDir, session) {
|
|
1752
|
-
if (!cave?.elements?.length) {
|
|
1753
|
-
return session.text("commands.cave.error.noContent");
|
|
1754
|
-
}
|
|
1755
|
-
const videoElement = cave.elements.find((el) => el.type === "video");
|
|
1756
|
-
const nonVideoElements = cave.elements.filter((el) => el.type !== "video").sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
1757
|
-
if (videoElement?.file) {
|
|
1758
|
-
const basicInfo = [
|
|
1759
|
-
session.text("commands.cave.message.caveTitle", [cave.cave_id]),
|
|
1760
|
-
session.text("commands.cave.message.contributorSuffix", [cave.contributor_name])
|
|
1761
|
-
].join("\n");
|
|
1762
|
-
await session?.send(basicInfo);
|
|
1763
|
-
const filePath = path6.join(resourceDir, videoElement.file);
|
|
1764
|
-
const base64Data = await processMediaFile(filePath, "video");
|
|
1765
|
-
if (base64Data && session) {
|
|
1766
|
-
await session.send((0, import_koishi6.h)("video", { src: base64Data }));
|
|
1767
|
-
}
|
|
1768
|
-
return "";
|
|
1769
|
-
}
|
|
1770
|
-
const lines = [session.text("commands.cave.message.caveTitle", [cave.cave_id])];
|
|
1771
|
-
for (const element of nonVideoElements) {
|
|
1772
|
-
if (element.type === "text") {
|
|
1773
|
-
lines.push(element.content);
|
|
1774
|
-
} else if (element.type === "img" && element.file) {
|
|
1775
|
-
const filePath = path6.join(resourceDir, element.file);
|
|
1776
|
-
const base64Data = await processMediaFile(filePath, "image");
|
|
1777
|
-
if (base64Data) {
|
|
1778
|
-
lines.push((0, import_koishi6.h)("image", { src: base64Data }));
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
lines.push(session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]));
|
|
1783
|
-
return lines.join("\n");
|
|
1784
|
-
}
|
|
1785
|
-
__name(buildMessage2, "buildMessage");
|
|
1786
1766
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1787
1767
|
0 && (module.exports = {
|
|
1788
1768
|
Config,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { Config } from '../index';
|
|
3
|
+
import { IdManager } from './IdManager';
|
|
4
|
+
export interface CaveObject {
|
|
5
|
+
cave_id: number;
|
|
6
|
+
elements: Element[];
|
|
7
|
+
contributor_number: string;
|
|
8
|
+
contributor_name: string;
|
|
9
|
+
}
|
|
10
|
+
interface BaseElement {
|
|
11
|
+
type: 'text' | 'img' | 'video';
|
|
12
|
+
index: number;
|
|
13
|
+
}
|
|
14
|
+
interface TextElement extends BaseElement {
|
|
15
|
+
type: 'text';
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
interface MediaElement extends BaseElement {
|
|
19
|
+
type: 'img' | 'video';
|
|
20
|
+
file?: string;
|
|
21
|
+
fileName?: string;
|
|
22
|
+
fileSize?: string;
|
|
23
|
+
filePath?: string;
|
|
24
|
+
}
|
|
25
|
+
type Element = TextElement | MediaElement;
|
|
26
|
+
export interface PendingCave extends CaveObject {
|
|
27
|
+
}
|
|
28
|
+
export declare class AuditManager {
|
|
29
|
+
private ctx;
|
|
30
|
+
private config;
|
|
31
|
+
private caveDir;
|
|
32
|
+
private idManager;
|
|
33
|
+
private logger;
|
|
34
|
+
constructor(ctx: Context, config: Config, caveDir: string, idManager: IdManager);
|
|
35
|
+
processAudit(pendingData: PendingCave[], isApprove: boolean, caveFilePath: string, resourceDir: string, pendingFilePath: string, session: any, targetId?: number): Promise<string>;
|
|
36
|
+
private handleSingleAudit;
|
|
37
|
+
private handleBatchAudit;
|
|
38
|
+
sendAuditMessage(cave: PendingCave, content: string, session: any): Promise<void>;
|
|
39
|
+
private deleteMediaFiles;
|
|
40
|
+
private cleanElementsForSave;
|
|
41
|
+
private sendMessage;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
/**
|
|
3
|
+
* 图片哈希计算工具类
|
|
4
|
+
* 使用 DCT(离散余弦变换)方法计算图片的感知哈希值,可用于图片相似度比较
|
|
5
|
+
*/
|
|
6
|
+
export declare class ContentHasher {
|
|
7
|
+
/**
|
|
8
|
+
* 计算图片的感知哈希值
|
|
9
|
+
* @param imageBuffer - 图片的二进制数据
|
|
10
|
+
* @returns 返回64位的十六进制哈希字符串
|
|
11
|
+
* @throws 当图片处理失败时可能抛出错误
|
|
12
|
+
*/
|
|
13
|
+
static calculateHash(imageBuffer: Buffer): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* 将二进制字符串转换为十六进制
|
|
16
|
+
* @param binary - 二进制字符串
|
|
17
|
+
* @returns 十六进制字符串
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
private static binaryToHex;
|
|
21
|
+
/**
|
|
22
|
+
* 将十六进制字符串转换为二进制
|
|
23
|
+
* @param hex - 十六进制字符串
|
|
24
|
+
* @returns 二进制字符串
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
27
|
+
private static hexToBinary;
|
|
28
|
+
/**
|
|
29
|
+
* 计算图像的DCT(离散余弦变换)
|
|
30
|
+
* @param data - 图像数据
|
|
31
|
+
* @param size - 图像尺寸
|
|
32
|
+
* @returns DCT变换后的矩阵
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
private static computeDCT;
|
|
36
|
+
/**
|
|
37
|
+
* 获取DCT系数
|
|
38
|
+
* @param index - 索引值
|
|
39
|
+
* @param size - 矩阵大小
|
|
40
|
+
* @returns DCT系数
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
private static getDCTCoefficient;
|
|
44
|
+
/**
|
|
45
|
+
* 计算数组的中位数
|
|
46
|
+
* @param arr - 输入数组
|
|
47
|
+
* @returns 中位数
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
private static calculateMedian;
|
|
51
|
+
/**
|
|
52
|
+
* 从DCT矩阵中提取特征值
|
|
53
|
+
* @param matrix - DCT矩阵
|
|
54
|
+
* @param size - 矩阵大小
|
|
55
|
+
* @returns 特征值数组
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
private static extractFeatures;
|
|
59
|
+
/**
|
|
60
|
+
* 计算两个哈希值之间的汉明距离
|
|
61
|
+
* @param hash1 - 第一个哈希值
|
|
62
|
+
* @param hash2 - 第二个哈希值
|
|
63
|
+
* @returns 汉明距离
|
|
64
|
+
* @throws 当两个哈希值长度不等时抛出错误
|
|
65
|
+
*/
|
|
66
|
+
static calculateDistance(hash1: string, hash2: string): number;
|
|
67
|
+
/**
|
|
68
|
+
* 计算两个图片哈希值的相似度
|
|
69
|
+
* @param hash1 - 第一个哈希值
|
|
70
|
+
* @param hash2 - 第二个哈希值
|
|
71
|
+
* @returns 返回0-1之间的相似度值,1表示完全相同,0表示完全不同
|
|
72
|
+
*/
|
|
73
|
+
static calculateSimilarity(hash1: string, hash2: string): number;
|
|
74
|
+
/**
|
|
75
|
+
* 计算文本的哈希值
|
|
76
|
+
* @param text - 输入文本
|
|
77
|
+
* @returns 文本的哈希值(36进制字符串)
|
|
78
|
+
*/
|
|
79
|
+
static calculateTextHash(text: string): string;
|
|
80
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare class FileHandler {
|
|
2
|
+
private static locks;
|
|
3
|
+
private static readonly RETRY_COUNT;
|
|
4
|
+
private static readonly RETRY_DELAY;
|
|
5
|
+
private static readonly CONCURRENCY_LIMIT;
|
|
6
|
+
/**
|
|
7
|
+
* 并发控制
|
|
8
|
+
* @param operation 要执行的操作
|
|
9
|
+
* @param limit 并发限制
|
|
10
|
+
* @returns 操作结果
|
|
11
|
+
*/
|
|
12
|
+
private static withConcurrencyLimit;
|
|
13
|
+
/**
|
|
14
|
+
* 文件操作包装器
|
|
15
|
+
* @param filePath 文件路径
|
|
16
|
+
* @param operation 要执行的操作
|
|
17
|
+
* @returns 操作结果
|
|
18
|
+
*/
|
|
19
|
+
private static withFileOp;
|
|
20
|
+
/**
|
|
21
|
+
* 事务处理
|
|
22
|
+
* @param operations 要执行的操作数组
|
|
23
|
+
* @returns 操作结果数组
|
|
24
|
+
*/
|
|
25
|
+
static withTransaction<T>(operations: Array<{
|
|
26
|
+
filePath: string;
|
|
27
|
+
operation: () => Promise<T>;
|
|
28
|
+
rollback?: () => Promise<void>;
|
|
29
|
+
}>): Promise<T[]>;
|
|
30
|
+
/**
|
|
31
|
+
* 读取 JSON 数据
|
|
32
|
+
* @param filePath 文件路径
|
|
33
|
+
* @returns JSON 数据
|
|
34
|
+
*/
|
|
35
|
+
static readJsonData<T>(filePath: string): Promise<T[]>;
|
|
36
|
+
/**
|
|
37
|
+
* 写入 JSON 数据
|
|
38
|
+
* @param filePath 文件路径
|
|
39
|
+
* @param data 要写入的数据
|
|
40
|
+
*/
|
|
41
|
+
static writeJsonData<T>(filePath: string, data: T[]): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* 确保目录存在
|
|
44
|
+
* @param dir 目录路径
|
|
45
|
+
*/
|
|
46
|
+
static ensureDirectory(dir: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* 确保 JSON 文件存在
|
|
49
|
+
* @param filePath 文件路径
|
|
50
|
+
*/
|
|
51
|
+
static ensureJsonFile(filePath: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* 保存媒体文件
|
|
54
|
+
* @param filePath 文件路径
|
|
55
|
+
* @param data 文件数据
|
|
56
|
+
*/
|
|
57
|
+
static saveMediaFile(filePath: string, data: Buffer | string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* 删除媒体文件
|
|
60
|
+
* @param filePath 文件路径
|
|
61
|
+
*/
|
|
62
|
+
static deleteMediaFile(filePath: string): Promise<void>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 哈希存储状态类型
|
|
3
|
+
*/
|
|
4
|
+
interface HashStorageStatus {
|
|
5
|
+
lastUpdated: string;
|
|
6
|
+
entries: Array<{
|
|
7
|
+
caveId: number;
|
|
8
|
+
imageHashes: string[];
|
|
9
|
+
textHashes: string[];
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 图片哈希值存储管理类
|
|
14
|
+
* 负责管理和维护回声洞图片的哈希值
|
|
15
|
+
*/
|
|
16
|
+
export declare class HashManager {
|
|
17
|
+
private readonly caveDir;
|
|
18
|
+
private static readonly HASH_FILE;
|
|
19
|
+
private static readonly CAVE_FILE;
|
|
20
|
+
private static readonly BATCH_SIZE;
|
|
21
|
+
private imageHashes;
|
|
22
|
+
private textHashes;
|
|
23
|
+
private initialized;
|
|
24
|
+
/**
|
|
25
|
+
* 初始化HashManager实例
|
|
26
|
+
* @param caveDir 回声洞数据目录路径
|
|
27
|
+
*/
|
|
28
|
+
constructor(caveDir: string);
|
|
29
|
+
private get filePath();
|
|
30
|
+
private get resourceDir();
|
|
31
|
+
private get caveFilePath();
|
|
32
|
+
/**
|
|
33
|
+
* 初始化哈希存储
|
|
34
|
+
* 读取现有哈希数据或重新构建哈希值
|
|
35
|
+
* @throws 初始化失败时抛出错误
|
|
36
|
+
*/
|
|
37
|
+
initialize(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* 获取当前哈希存储状态
|
|
40
|
+
* @returns 包含最后更新时间和所有条目的状态对象
|
|
41
|
+
*/
|
|
42
|
+
getStatus(): Promise<HashStorageStatus>;
|
|
43
|
+
/**
|
|
44
|
+
* 更新指定回声洞的图片哈希值
|
|
45
|
+
* @param caveId 回声洞ID
|
|
46
|
+
* @param content 图片buffer数组
|
|
47
|
+
*/
|
|
48
|
+
updateCaveContent(caveId: number, content: {
|
|
49
|
+
images?: Buffer[];
|
|
50
|
+
texts?: string[];
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* 更新所有回声洞的哈希值
|
|
54
|
+
* @param isInitialBuild 是否为初始构建
|
|
55
|
+
*/
|
|
56
|
+
updateAllCaves(isInitialBuild?: boolean): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* 查找重复的图片
|
|
59
|
+
* @param content 待查找的图片buffer数组
|
|
60
|
+
* @param thresholds 相似度阈值
|
|
61
|
+
* @returns 匹配结果数组,包含索引、回声洞ID和相似度
|
|
62
|
+
*/
|
|
63
|
+
findDuplicates(content: {
|
|
64
|
+
images?: Buffer[];
|
|
65
|
+
texts?: string[];
|
|
66
|
+
}, thresholds: {
|
|
67
|
+
image: number;
|
|
68
|
+
text: number;
|
|
69
|
+
}): Promise<Array<{
|
|
70
|
+
type: 'image' | 'text';
|
|
71
|
+
index: number;
|
|
72
|
+
caveId: number;
|
|
73
|
+
similarity: number;
|
|
74
|
+
} | null>>;
|
|
75
|
+
private findTextDuplicates;
|
|
76
|
+
private calculateTextSimilarity;
|
|
77
|
+
private findImageDuplicates;
|
|
78
|
+
/**
|
|
79
|
+
* 加载回声洞数据
|
|
80
|
+
* @returns 回声洞数据数组
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
private loadCaveData;
|
|
84
|
+
/**
|
|
85
|
+
* 保存哈希数据到文件
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
private saveContentHashes;
|
|
89
|
+
/**
|
|
90
|
+
* 构建初始哈希数据
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
private buildInitialHashes;
|
|
94
|
+
/**
|
|
95
|
+
* 更新缺失的哈希值
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
private updateMissingHashes;
|
|
99
|
+
/**
|
|
100
|
+
* 批量处理数组项
|
|
101
|
+
* @param items 待处理项数组
|
|
102
|
+
* @param processor 处理函数
|
|
103
|
+
* @param batchSize 批处理大小
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
private processBatch;
|
|
107
|
+
}
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ID管理器类
|
|
3
|
+
* 负责管理回声洞ID的分配、删除和统计信息
|
|
4
|
+
*/
|
|
5
|
+
export declare class IdManager {
|
|
6
|
+
private deletedIds;
|
|
7
|
+
private maxId;
|
|
8
|
+
private initialized;
|
|
9
|
+
private readonly statusFilePath;
|
|
10
|
+
private stats;
|
|
11
|
+
private usedIds;
|
|
12
|
+
/**
|
|
13
|
+
* 初始化ID管理器
|
|
14
|
+
* @param baseDir - 基础目录路径
|
|
15
|
+
*/
|
|
16
|
+
constructor(baseDir: string);
|
|
17
|
+
/**
|
|
18
|
+
* 初始化ID管理系统
|
|
19
|
+
* @param caveFilePath - 正式回声洞数据文件路径
|
|
20
|
+
* @param pendingFilePath - 待处理回声洞数据文件路径
|
|
21
|
+
* @throws 当初始化失败时抛出错误
|
|
22
|
+
*/
|
|
23
|
+
initialize(caveFilePath: string, pendingFilePath: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* 处理ID冲突
|
|
26
|
+
* @param conflicts - ID冲突映射表
|
|
27
|
+
* @param caveFilePath - 正式回声洞数据文件路径
|
|
28
|
+
* @param pendingFilePath - 待处理回声洞数据文件路径
|
|
29
|
+
* @param caveData - 正式回声洞数据
|
|
30
|
+
* @param pendingData - 待处理回声洞数据
|
|
31
|
+
* @private
|
|
32
|
+
*/
|
|
33
|
+
private handleConflicts;
|
|
34
|
+
/**
|
|
35
|
+
* 获取下一个可用的ID
|
|
36
|
+
* @returns 下一个可用的ID
|
|
37
|
+
* @throws 当ID管理器未初始化时抛出错误
|
|
38
|
+
*/
|
|
39
|
+
getNextId(): number;
|
|
40
|
+
/**
|
|
41
|
+
* 标记ID为已删除状态
|
|
42
|
+
* @param id - 要标记为删除的ID
|
|
43
|
+
* @throws 当ID管理器未初始化时抛出错误
|
|
44
|
+
*/
|
|
45
|
+
markDeleted(id: number): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* 添加贡献统计
|
|
48
|
+
* @param contributorNumber - 贡献者编号
|
|
49
|
+
* @param caveId - 回声洞ID
|
|
50
|
+
*/
|
|
51
|
+
addStat(contributorNumber: string, caveId: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* 移除贡献统计
|
|
54
|
+
* @param contributorNumber - 贡献者编号
|
|
55
|
+
* @param caveId - 回声洞ID
|
|
56
|
+
*/
|
|
57
|
+
removeStat(contributorNumber: string, caveId: number): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* 获取所有贡献统计信息
|
|
60
|
+
* @returns 贡献者编号到回声洞ID列表的映射
|
|
61
|
+
*/
|
|
62
|
+
getStats(): Record<string, number[]>;
|
|
63
|
+
/**
|
|
64
|
+
* 保存当前状态到文件
|
|
65
|
+
* @private
|
|
66
|
+
* @throws 当保存失败时抛出错误
|
|
67
|
+
*/
|
|
68
|
+
private saveStatus;
|
|
69
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
export interface BaseElement {
|
|
3
|
+
type: 'text' | 'img' | 'video';
|
|
4
|
+
index: number;
|
|
5
|
+
}
|
|
6
|
+
interface CaveObject {
|
|
7
|
+
cave_id: number;
|
|
8
|
+
elements: Element[];
|
|
9
|
+
contributor_number: string;
|
|
10
|
+
contributor_name: string;
|
|
11
|
+
}
|
|
12
|
+
export interface TextElement extends BaseElement {
|
|
13
|
+
type: 'text';
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MediaElement extends BaseElement {
|
|
17
|
+
type: 'img' | 'video';
|
|
18
|
+
file?: string;
|
|
19
|
+
fileName?: string;
|
|
20
|
+
fileSize?: string;
|
|
21
|
+
filePath?: string;
|
|
22
|
+
}
|
|
23
|
+
export type Element = TextElement | MediaElement;
|
|
24
|
+
export declare function buildMessage(cave: CaveObject, resourceDir: string, session?: any): Promise<string>;
|
|
25
|
+
export declare function sendMessage(session: any, key: string, params?: any[], isTemp?: boolean, timeout?: number): Promise<string>;
|
|
26
|
+
export declare function processMediaFile(filePath: string, type: 'image' | 'video'): Promise<string | null>;
|
|
27
|
+
export declare function extractMediaContent(originalContent: string, config: {
|
|
28
|
+
imageMaxSize: number;
|
|
29
|
+
videoMaxSize: number;
|
|
30
|
+
}, session: any): Promise<{
|
|
31
|
+
imageUrls: string[];
|
|
32
|
+
imageElements: Array<{
|
|
33
|
+
type: 'img';
|
|
34
|
+
index: number;
|
|
35
|
+
fileName?: string;
|
|
36
|
+
fileSize?: string;
|
|
37
|
+
}>;
|
|
38
|
+
videoUrls: string[];
|
|
39
|
+
videoElements: Array<{
|
|
40
|
+
type: 'video';
|
|
41
|
+
index: number;
|
|
42
|
+
fileName?: string;
|
|
43
|
+
fileSize?: string;
|
|
44
|
+
}>;
|
|
45
|
+
textParts: Element[];
|
|
46
|
+
}>;
|
|
47
|
+
export declare function saveMedia(urls: string[], fileNames: (string | undefined)[], resourceDir: string, caveId: number, mediaType: 'img' | 'video', config: {
|
|
48
|
+
enableImageDuplicate: boolean;
|
|
49
|
+
imageDuplicateThreshold: number;
|
|
50
|
+
textDuplicateThreshold: number;
|
|
51
|
+
}, ctx: Context, session: any, buffers?: Buffer[]): Promise<string[]>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Config } from '../index';
|
|
2
|
+
import { IdManager } from './IdManager';
|
|
3
|
+
import { HashManager } from './HashManager';
|
|
4
|
+
export declare function processList(session: any, config: Config, idManager: IdManager, userId?: string, pageNum?: number): Promise<string>;
|
|
5
|
+
export declare function processView(caveFilePath: string, resourceDir: string, session: any, options: any, content: string[]): Promise<string>;
|
|
6
|
+
export declare function processRandom(caveFilePath: string, resourceDir: string, session: any): Promise<string | void>;
|
|
7
|
+
export declare function processDelete(caveFilePath: string, resourceDir: string, pendingFilePath: string, session: any, config: Config, options: any, content: string[], idManager: IdManager, HashManager: HashManager): Promise<string>;
|