koishi-plugin-best-cave 2.0.7 → 2.0.9
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/DataManager.d.ts +1 -2
- package/lib/FileManager.d.ts +1 -2
- package/lib/ProfileManager.d.ts +1 -2
- package/lib/ReviewManager.d.ts +4 -7
- package/lib/Utils.d.ts +29 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +150 -116
- package/package.json +1 -1
package/lib/DataManager.d.ts
CHANGED
package/lib/FileManager.d.ts
CHANGED
package/lib/ProfileManager.d.ts
CHANGED
package/lib/ReviewManager.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { Context,
|
|
1
|
+
import { Context, Logger } from 'koishi';
|
|
2
2
|
import { CaveObject, Config } from './index';
|
|
3
3
|
import { FileManager } from './FileManager';
|
|
4
4
|
/**
|
|
5
5
|
* @class ReviewManager
|
|
6
|
-
* @description
|
|
7
|
-
* 负责处理回声洞的审核流程。当 `enableReview` 配置开启时,
|
|
8
|
-
* 此管理器将被激活,处理新洞的提交、审核通知和审核操作。
|
|
6
|
+
* @description 负责处理回声洞的审核流程,处理新洞的提交、审核通知和审核操作。
|
|
9
7
|
*/
|
|
10
8
|
export declare class ReviewManager {
|
|
11
9
|
private ctx;
|
|
@@ -26,7 +24,7 @@ export declare class ReviewManager {
|
|
|
26
24
|
*/
|
|
27
25
|
registerCommands(cave: any): void;
|
|
28
26
|
/**
|
|
29
|
-
* @description
|
|
27
|
+
* @description 将新回声洞提交到管理群组以供审核。
|
|
30
28
|
* @param cave 新创建的、状态为 'pending' 的回声洞对象。
|
|
31
29
|
*/
|
|
32
30
|
sendForReview(cave: CaveObject): Promise<void>;
|
|
@@ -34,8 +32,7 @@ export declare class ReviewManager {
|
|
|
34
32
|
* @description 处理管理员的审核决定(通过或拒绝)。
|
|
35
33
|
* @param action 'approve' (通过) 或 'reject' (拒绝)。
|
|
36
34
|
* @param caveId 被审核的回声洞 ID。
|
|
37
|
-
* @param adminUserName 操作管理员的昵称。
|
|
38
35
|
* @returns 返回给操作者的确认消息。
|
|
39
36
|
*/
|
|
40
|
-
processReview(action: 'approve' | 'reject', caveId: number
|
|
37
|
+
processReview(action: 'approve' | 'reject', caveId: number): Promise<string>;
|
|
41
38
|
}
|
package/lib/Utils.d.ts
CHANGED
|
@@ -53,3 +53,32 @@ export declare function checkCooldown(session: Session, config: Config, lastUsed
|
|
|
53
53
|
* @description 更新指定频道的指令使用时间戳。
|
|
54
54
|
*/
|
|
55
55
|
export declare function updateCooldownTimestamp(session: Session, config: Config, lastUsed: Map<string, number>): void;
|
|
56
|
+
/**
|
|
57
|
+
* @description 解析消息元素,分离出文本和待下载的媒体文件。
|
|
58
|
+
* @param sourceElements - 原始的 Koishi 消息元素数组。
|
|
59
|
+
* @param newId - 这条回声洞的新 ID。
|
|
60
|
+
* @param channelId - 频道 ID。
|
|
61
|
+
* @param userId - 用户 ID。
|
|
62
|
+
* @returns 一个包含数据库元素和待保存媒体列表的对象。
|
|
63
|
+
*/
|
|
64
|
+
export declare function processMessageElements(sourceElements: h[], newId: number, channelId: string, userId: string): Promise<{
|
|
65
|
+
finalElementsForDb: StoredElement[];
|
|
66
|
+
mediaToSave: {
|
|
67
|
+
sourceUrl: string;
|
|
68
|
+
fileName: string;
|
|
69
|
+
}[];
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* @description 异步处理文件上传和状态更新的后台任务。
|
|
73
|
+
* @param ctx - Koishi 上下文。
|
|
74
|
+
* @param config - 插件配置。
|
|
75
|
+
* @param fileManager - 文件管理器实例。
|
|
76
|
+
* @param logger - 日志记录器实例。
|
|
77
|
+
* @param reviewManager - 审核管理器实例 (可能为 null)。
|
|
78
|
+
* @param cave - 已创建的、状态为 'preload' 的回声洞对象。
|
|
79
|
+
* @param mediaToSave - 需要下载和保存的媒体文件列表。
|
|
80
|
+
*/
|
|
81
|
+
export declare function handleFileUploads(ctx: Context, config: Config, fileManager: FileManager, logger: Logger, reviewManager: any, cave: CaveObject, mediaToSave: {
|
|
82
|
+
sourceUrl: string;
|
|
83
|
+
fileName: string;
|
|
84
|
+
}[]): Promise<void>;
|
package/lib/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface CaveObject {
|
|
|
19
19
|
channelId: string;
|
|
20
20
|
userId: string;
|
|
21
21
|
userName: string;
|
|
22
|
-
status: 'active' | 'delete' | 'pending';
|
|
22
|
+
status: 'active' | 'delete' | 'pending' | 'preload';
|
|
23
23
|
time: Date;
|
|
24
24
|
}
|
|
25
25
|
declare module 'koishi' {
|
|
@@ -30,7 +30,7 @@ declare module 'koishi' {
|
|
|
30
30
|
export interface Config {
|
|
31
31
|
coolDown: number;
|
|
32
32
|
perChannel: boolean;
|
|
33
|
-
|
|
33
|
+
adminChannel: string;
|
|
34
34
|
enableProfile: boolean;
|
|
35
35
|
enableIO: boolean;
|
|
36
36
|
enableReview: boolean;
|
package/lib/index.js
CHANGED
|
@@ -37,8 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
usage: () => usage
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
|
-
var
|
|
41
|
-
var path3 = __toESM(require("path"));
|
|
40
|
+
var import_koishi3 = require("koishi");
|
|
42
41
|
|
|
43
42
|
// src/FileManager.ts
|
|
44
43
|
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
@@ -259,10 +258,14 @@ async function buildCaveMessage(cave, config, fileManager, logger2) {
|
|
|
259
258
|
const [headerFormat, footerFormat = ""] = config.caveFormat.split("|");
|
|
260
259
|
const replacements = { id: cave.id.toString(), name: cave.userName };
|
|
261
260
|
const headerText = headerFormat.replace(/\{id\}|\{name\}/g, (match) => replacements[match.slice(1, -1)]);
|
|
262
|
-
if (headerText.trim())
|
|
261
|
+
if (headerText.trim()) {
|
|
262
|
+
finalMessage.push(headerText.trim() + "\n");
|
|
263
|
+
}
|
|
263
264
|
finalMessage.push(...processedElements);
|
|
264
265
|
const footerText = footerFormat.replace(/\{id\}|\{name\}/g, (match) => replacements[match.slice(1, -1)]);
|
|
265
|
-
if (footerText.trim())
|
|
266
|
+
if (footerText.trim()) {
|
|
267
|
+
finalMessage.push("\n" + footerText.trim());
|
|
268
|
+
}
|
|
266
269
|
return finalMessage;
|
|
267
270
|
}
|
|
268
271
|
__name(buildCaveMessage, "buildCaveMessage");
|
|
@@ -296,7 +299,7 @@ async function getNextCaveId(ctx, query = {}) {
|
|
|
296
299
|
}
|
|
297
300
|
__name(getNextCaveId, "getNextCaveId");
|
|
298
301
|
function checkCooldown(session, config, lastUsed) {
|
|
299
|
-
if (config.coolDown <= 0 || !session.channelId
|
|
302
|
+
if (config.coolDown <= 0 || !session.channelId) return null;
|
|
300
303
|
const now = Date.now();
|
|
301
304
|
const lastTime = lastUsed.get(session.channelId) || 0;
|
|
302
305
|
if (now - lastTime < config.coolDown * 1e3) {
|
|
@@ -312,6 +315,69 @@ function updateCooldownTimestamp(session, config, lastUsed) {
|
|
|
312
315
|
}
|
|
313
316
|
}
|
|
314
317
|
__name(updateCooldownTimestamp, "updateCooldownTimestamp");
|
|
318
|
+
async function processMessageElements(sourceElements, newId, channelId, userId) {
|
|
319
|
+
const finalElementsForDb = [];
|
|
320
|
+
const mediaToSave = [];
|
|
321
|
+
let mediaIndex = 0;
|
|
322
|
+
const typeMap = {
|
|
323
|
+
"img": "image",
|
|
324
|
+
"image": "image",
|
|
325
|
+
"video": "video",
|
|
326
|
+
"audio": "audio",
|
|
327
|
+
"file": "file",
|
|
328
|
+
"text": "text"
|
|
329
|
+
};
|
|
330
|
+
async function traverse(elements) {
|
|
331
|
+
for (const el of elements) {
|
|
332
|
+
const normalizedType = typeMap[el.type];
|
|
333
|
+
if (!normalizedType) {
|
|
334
|
+
if (el.children) await traverse(el.children);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (["image", "video", "audio", "file"].includes(normalizedType) && el.attrs.src) {
|
|
338
|
+
let fileIdentifier = el.attrs.src;
|
|
339
|
+
if (fileIdentifier.startsWith("http")) {
|
|
340
|
+
mediaIndex++;
|
|
341
|
+
const defaultExtMap = { "image": ".jpg", "video": ".mp4", "audio": ".mp3", "file": ".dat" };
|
|
342
|
+
const ext = el.attrs.file && path2.extname(el.attrs.file) ? path2.extname(el.attrs.file) : defaultExtMap[normalizedType] || ".dat";
|
|
343
|
+
const channelIdentifier = channelId || "private";
|
|
344
|
+
const fileName = `${newId}_${mediaIndex}_${channelIdentifier}_${userId}${ext}`;
|
|
345
|
+
mediaToSave.push({ sourceUrl: fileIdentifier, fileName });
|
|
346
|
+
fileIdentifier = fileName;
|
|
347
|
+
}
|
|
348
|
+
finalElementsForDb.push({ type: normalizedType, file: fileIdentifier });
|
|
349
|
+
} else if (normalizedType === "text" && el.attrs.content?.trim()) {
|
|
350
|
+
finalElementsForDb.push({ type: "text", content: el.attrs.content.trim() });
|
|
351
|
+
}
|
|
352
|
+
if (el.children) {
|
|
353
|
+
await traverse(el.children);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
__name(traverse, "traverse");
|
|
358
|
+
await traverse(sourceElements);
|
|
359
|
+
return { finalElementsForDb, mediaToSave };
|
|
360
|
+
}
|
|
361
|
+
__name(processMessageElements, "processMessageElements");
|
|
362
|
+
async function handleFileUploads(ctx, config, fileManager, logger2, reviewManager, cave, mediaToSave) {
|
|
363
|
+
try {
|
|
364
|
+
await Promise.all(mediaToSave.map(async (media) => {
|
|
365
|
+
const response = await ctx.http.get(media.sourceUrl, { responseType: "arraybuffer", timeout: 3e4 });
|
|
366
|
+
await fileManager.saveFile(media.fileName, Buffer.from(response));
|
|
367
|
+
}));
|
|
368
|
+
const finalStatus = config.enableReview ? "pending" : "active";
|
|
369
|
+
await ctx.database.upsert("cave", [{ id: cave.id, status: finalStatus }]);
|
|
370
|
+
if (finalStatus === "pending" && reviewManager) {
|
|
371
|
+
const [finalCave] = await ctx.database.get("cave", { id: cave.id });
|
|
372
|
+
if (finalCave) reviewManager.sendForReview(finalCave);
|
|
373
|
+
}
|
|
374
|
+
} catch (fileSaveError) {
|
|
375
|
+
logger2.error(`回声洞(${cave.id})文件保存失败:`, fileSaveError);
|
|
376
|
+
await ctx.database.upsert("cave", [{ id: cave.id, status: "delete" }]);
|
|
377
|
+
await cleanupPendingDeletions(ctx, fileManager, logger2);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
__name(handleFileUploads, "handleFileUploads");
|
|
315
381
|
|
|
316
382
|
// src/DataManager.ts
|
|
317
383
|
var DataManager = class {
|
|
@@ -337,7 +403,8 @@ var DataManager = class {
|
|
|
337
403
|
*/
|
|
338
404
|
registerCommands(cave) {
|
|
339
405
|
cave.subcommand(".export", "导出回声洞数据").usage("将所有回声洞数据导出到 cave_export.json。").action(async ({ session }) => {
|
|
340
|
-
|
|
406
|
+
const adminChannelId = this.config.adminChannel ? this.config.adminChannel.split(":")[1] : null;
|
|
407
|
+
if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
|
|
341
408
|
try {
|
|
342
409
|
await session.send("正在导出数据,请稍候...");
|
|
343
410
|
return await this.exportData();
|
|
@@ -347,7 +414,8 @@ var DataManager = class {
|
|
|
347
414
|
}
|
|
348
415
|
});
|
|
349
416
|
cave.subcommand(".import", "导入回声洞数据").usage("从 cave_import.json 中导入回声洞数据。").action(async ({ session }) => {
|
|
350
|
-
|
|
417
|
+
const adminChannelId = this.config.adminChannel ? this.config.adminChannel.split(":")[1] : null;
|
|
418
|
+
if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
|
|
351
419
|
try {
|
|
352
420
|
await session.send("正在导入数据,请稍候...");
|
|
353
421
|
return await this.importData();
|
|
@@ -390,10 +458,8 @@ var DataManager = class {
|
|
|
390
458
|
const newCave = {
|
|
391
459
|
...cave,
|
|
392
460
|
id: newId,
|
|
393
|
-
channelId: cave.channelId
|
|
394
|
-
// 保证 channelId 存在
|
|
461
|
+
channelId: cave.channelId,
|
|
395
462
|
status: "active"
|
|
396
|
-
// 导入的数据直接设为活跃状态
|
|
397
463
|
};
|
|
398
464
|
await this.ctx.database.create("cave", newCave);
|
|
399
465
|
successCount++;
|
|
@@ -403,6 +469,7 @@ var DataManager = class {
|
|
|
403
469
|
};
|
|
404
470
|
|
|
405
471
|
// src/ReviewManager.ts
|
|
472
|
+
var import_koishi2 = require("koishi");
|
|
406
473
|
var ReviewManager = class {
|
|
407
474
|
/**
|
|
408
475
|
* @constructor
|
|
@@ -426,7 +493,8 @@ var ReviewManager = class {
|
|
|
426
493
|
*/
|
|
427
494
|
registerCommands(cave) {
|
|
428
495
|
cave.subcommand(".review [id:posint] [action:string]", "审核回声洞").usage("查看或审核回声洞,使用 <Y/N> 进行审核。").action(async ({ session }, id, action) => {
|
|
429
|
-
|
|
496
|
+
const adminChannelId = this.config.adminChannel ? this.config.adminChannel.split(":")[1] : null;
|
|
497
|
+
if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
|
|
430
498
|
if (!id) {
|
|
431
499
|
const pendingCaves = await this.ctx.database.get("cave", { status: "pending" });
|
|
432
500
|
if (pendingCaves.length === 0) return "当前没有需要审核的回声洞";
|
|
@@ -445,53 +513,45 @@ ${pendingCaves.map((c) => c.id).join(", ")}`;
|
|
|
445
513
|
else if (["n", "no", "deny", "reject"].includes(normalizedAction)) reviewAction = "reject";
|
|
446
514
|
else return `无效操作: "${action}"
|
|
447
515
|
请使用 "Y" (通过) 或 "N" (拒绝)`;
|
|
448
|
-
return this.processReview(reviewAction, id
|
|
516
|
+
return this.processReview(reviewAction, id);
|
|
449
517
|
});
|
|
450
518
|
}
|
|
451
519
|
/**
|
|
452
|
-
* @description
|
|
520
|
+
* @description 将新回声洞提交到管理群组以供审核。
|
|
453
521
|
* @param cave 新创建的、状态为 'pending' 的回声洞对象。
|
|
454
522
|
*/
|
|
455
523
|
async sendForReview(cave) {
|
|
456
|
-
|
|
457
|
-
|
|
524
|
+
const channelParts = this.config.adminChannel?.split(":");
|
|
525
|
+
if (!channelParts || channelParts.length < 2 || !channelParts[1]) {
|
|
526
|
+
this.logger.warn(`管理群组配置无效,已自动通过回声洞(${cave.id})`);
|
|
458
527
|
await this.ctx.database.upsert("cave", [{ id: cave.id, status: "active" }]);
|
|
459
528
|
return;
|
|
460
529
|
}
|
|
461
530
|
const reviewMessage = [`待审核:`, ...await buildCaveMessage(cave, this.config, this.fileManager, this.logger)];
|
|
462
531
|
try {
|
|
463
|
-
await this.ctx.broadcast(this.config.
|
|
532
|
+
await this.ctx.broadcast([this.config.adminChannel], import_koishi2.h.normalize(reviewMessage));
|
|
464
533
|
} catch (error) {
|
|
465
|
-
this.logger.error(
|
|
534
|
+
this.logger.error(`发送回声洞(${cave.id})审核消息失败:`, error);
|
|
466
535
|
}
|
|
467
536
|
}
|
|
468
537
|
/**
|
|
469
538
|
* @description 处理管理员的审核决定(通过或拒绝)。
|
|
470
539
|
* @param action 'approve' (通过) 或 'reject' (拒绝)。
|
|
471
540
|
* @param caveId 被审核的回声洞 ID。
|
|
472
|
-
* @param adminUserName 操作管理员的昵称。
|
|
473
541
|
* @returns 返回给操作者的确认消息。
|
|
474
542
|
*/
|
|
475
|
-
async processReview(action, caveId
|
|
543
|
+
async processReview(action, caveId) {
|
|
476
544
|
const [cave] = await this.ctx.database.get("cave", { id: caveId, status: "pending" });
|
|
477
|
-
if (!cave) return `回声洞(${caveId}
|
|
545
|
+
if (!cave) return `回声洞(${caveId})无需审核`;
|
|
478
546
|
let resultMessage;
|
|
479
|
-
let broadcastMessage;
|
|
480
547
|
if (action === "approve") {
|
|
481
548
|
await this.ctx.database.upsert("cave", [{ id: caveId, status: "active" }]);
|
|
482
549
|
resultMessage = `回声洞(${caveId})已通过`;
|
|
483
|
-
broadcastMessage = `回声洞(${caveId})已由管理员 "${adminUserName}" 通过`;
|
|
484
550
|
} else {
|
|
485
551
|
await this.ctx.database.upsert("cave", [{ id: caveId, status: "delete" }]);
|
|
486
552
|
resultMessage = `回声洞(${caveId})已拒绝`;
|
|
487
|
-
broadcastMessage = `回声洞(${caveId})已由管理员 "${adminUserName}" 拒绝`;
|
|
488
553
|
cleanupPendingDeletions(this.ctx, this.fileManager, this.logger);
|
|
489
554
|
}
|
|
490
|
-
if (this.config.adminUsers?.length) {
|
|
491
|
-
this.ctx.broadcast(this.config.adminUsers, broadcastMessage).catch((err) => {
|
|
492
|
-
this.logger.error(`广播回声洞(${cave.id})审核结果失败:`, err);
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
555
|
return resultMessage;
|
|
496
556
|
}
|
|
497
557
|
};
|
|
@@ -511,28 +571,28 @@ var usage = `
|
|
|
511
571
|
<p>🐛 遇到问题?请通过 <strong>Issues</strong> 提交反馈,或加入 QQ 群 <a href="https://qm.qq.com/q/PdLMx9Jowq" style="color:#e0574a;text-decoration:none;"><strong>855571375</strong></a> 进行交流</p>
|
|
512
572
|
</div>
|
|
513
573
|
`;
|
|
514
|
-
var logger = new
|
|
515
|
-
var Config =
|
|
516
|
-
|
|
517
|
-
coolDown:
|
|
518
|
-
perChannel:
|
|
519
|
-
enableProfile:
|
|
520
|
-
enableIO:
|
|
521
|
-
|
|
522
|
-
|
|
574
|
+
var logger = new import_koishi3.Logger("best-cave");
|
|
575
|
+
var Config = import_koishi3.Schema.intersect([
|
|
576
|
+
import_koishi3.Schema.object({
|
|
577
|
+
coolDown: import_koishi3.Schema.number().default(10).description("冷却时间(秒)"),
|
|
578
|
+
perChannel: import_koishi3.Schema.boolean().default(false).description("启用分群模式"),
|
|
579
|
+
enableProfile: import_koishi3.Schema.boolean().default(false).description("启用自定义昵称"),
|
|
580
|
+
enableIO: import_koishi3.Schema.boolean().default(false).description("启用导入导出"),
|
|
581
|
+
adminChannel: import_koishi3.Schema.string().default("onebot:").description("管理群组 ID"),
|
|
582
|
+
caveFormat: import_koishi3.Schema.string().default("回声洞 ——({id})|—— {name}").description("自定义文本")
|
|
523
583
|
}).description("基础配置"),
|
|
524
|
-
|
|
525
|
-
enableReview:
|
|
584
|
+
import_koishi3.Schema.object({
|
|
585
|
+
enableReview: import_koishi3.Schema.boolean().default(false).description("启用审核")
|
|
526
586
|
}).description("审核配置"),
|
|
527
|
-
|
|
528
|
-
localPath:
|
|
529
|
-
enableS3:
|
|
530
|
-
publicUrl:
|
|
531
|
-
endpoint:
|
|
532
|
-
bucket:
|
|
533
|
-
region:
|
|
534
|
-
accessKeyId:
|
|
535
|
-
secretAccessKey:
|
|
587
|
+
import_koishi3.Schema.object({
|
|
588
|
+
localPath: import_koishi3.Schema.string().description("文件映射路径"),
|
|
589
|
+
enableS3: import_koishi3.Schema.boolean().default(false).description("启用 S3 存储"),
|
|
590
|
+
publicUrl: import_koishi3.Schema.string().description("公共访问 URL").role("link"),
|
|
591
|
+
endpoint: import_koishi3.Schema.string().description("端点 (Endpoint)").role("link"),
|
|
592
|
+
bucket: import_koishi3.Schema.string().description("存储桶 (Bucket)"),
|
|
593
|
+
region: import_koishi3.Schema.string().default("auto").description("区域 (Region)"),
|
|
594
|
+
accessKeyId: import_koishi3.Schema.string().description("Access Key ID").role("secret"),
|
|
595
|
+
secretAccessKey: import_koishi3.Schema.string().description("Secret Access Key").role("secret")
|
|
536
596
|
}).description("存储配置")
|
|
537
597
|
]);
|
|
538
598
|
function apply(ctx, config) {
|
|
@@ -578,85 +638,58 @@ function apply(ctx, config) {
|
|
|
578
638
|
if (session.quote?.elements) {
|
|
579
639
|
sourceElements = session.quote.elements;
|
|
580
640
|
} else if (content?.trim()) {
|
|
581
|
-
sourceElements =
|
|
641
|
+
sourceElements = import_koishi3.h.parse(content);
|
|
582
642
|
} else {
|
|
583
643
|
await session.send("请在一分钟内发送你要添加的内容");
|
|
584
644
|
const reply = await session.prompt(6e4);
|
|
585
645
|
if (!reply) return "操作超时,已取消添加";
|
|
586
|
-
sourceElements =
|
|
646
|
+
sourceElements = import_koishi3.h.parse(reply);
|
|
587
647
|
}
|
|
588
648
|
const idScopeQuery = {};
|
|
589
649
|
if (config.perChannel && session.channelId) {
|
|
590
650
|
idScopeQuery["channelId"] = session.channelId;
|
|
591
651
|
}
|
|
592
652
|
const newId = await getNextCaveId(ctx, idScopeQuery);
|
|
593
|
-
const finalElementsForDb =
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
"video": "video",
|
|
600
|
-
"audio": "audio",
|
|
601
|
-
"file": "file",
|
|
602
|
-
"text": "text"
|
|
603
|
-
};
|
|
604
|
-
async function traverseAndProcess(elements) {
|
|
605
|
-
for (const el of elements) {
|
|
606
|
-
const normalizedType = typeMap[el.type];
|
|
607
|
-
if (!normalizedType) {
|
|
608
|
-
if (el.children) await traverseAndProcess(el.children);
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
if (["image", "video", "audio", "file"].includes(normalizedType) && el.attrs.src) {
|
|
612
|
-
let fileIdentifier = el.attrs.src;
|
|
613
|
-
if (fileIdentifier.startsWith("http")) {
|
|
614
|
-
mediaIndex++;
|
|
615
|
-
const defaultExtMap = { "image": ".jpg", "video": ".mp4", "audio": ".mp3", "file": ".dat" };
|
|
616
|
-
const ext = el.attrs.file && path3.extname(el.attrs.file) ? path3.extname(el.attrs.file) : defaultExtMap[normalizedType] || ".dat";
|
|
617
|
-
const channelIdentifier = session.channelId || "private";
|
|
618
|
-
const fileName = `${newId}_${mediaIndex}_${channelIdentifier}_${session.userId}${ext}`;
|
|
619
|
-
mediaToSave.push({ sourceUrl: fileIdentifier, fileName });
|
|
620
|
-
fileIdentifier = fileName;
|
|
621
|
-
}
|
|
622
|
-
finalElementsForDb.push({ type: normalizedType, file: fileIdentifier });
|
|
623
|
-
} else if (normalizedType === "text" && el.attrs.content?.trim()) {
|
|
624
|
-
finalElementsForDb.push({ type: "text", content: el.attrs.content.trim() });
|
|
625
|
-
}
|
|
626
|
-
if (el.children) {
|
|
627
|
-
await traverseAndProcess(el.children);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
__name(traverseAndProcess, "traverseAndProcess");
|
|
632
|
-
await traverseAndProcess(sourceElements);
|
|
653
|
+
const { finalElementsForDb, mediaToSave } = await processMessageElements(
|
|
654
|
+
sourceElements,
|
|
655
|
+
newId,
|
|
656
|
+
session.channelId,
|
|
657
|
+
session.userId
|
|
658
|
+
);
|
|
633
659
|
if (finalElementsForDb.length === 0) return "内容为空,已取消添加";
|
|
634
660
|
const customNickname = config.enableProfile ? await profileManager.getNickname(session.userId) : null;
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
await
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
661
|
+
const userName = customNickname || session.username;
|
|
662
|
+
if (mediaToSave.length > 0) {
|
|
663
|
+
const newCave = {
|
|
664
|
+
id: newId,
|
|
665
|
+
elements: finalElementsForDb,
|
|
666
|
+
channelId: session.channelId,
|
|
667
|
+
userId: session.userId,
|
|
668
|
+
userName,
|
|
669
|
+
status: "preload",
|
|
670
|
+
time: /* @__PURE__ */ new Date()
|
|
671
|
+
};
|
|
672
|
+
await ctx.database.create("cave", newCave);
|
|
673
|
+
handleFileUploads(ctx, config, fileManager, logger, reviewManager, newCave, mediaToSave);
|
|
674
|
+
return config.enableReview ? `提交成功,序号为(${newId})` : `添加成功,序号为(${newId})`;
|
|
675
|
+
} else {
|
|
676
|
+
const finalStatus = config.enableReview ? "pending" : "active";
|
|
677
|
+
const newCave = {
|
|
678
|
+
id: newId,
|
|
679
|
+
elements: finalElementsForDb,
|
|
680
|
+
channelId: session.channelId,
|
|
681
|
+
userId: session.userId,
|
|
682
|
+
userName,
|
|
683
|
+
status: finalStatus,
|
|
684
|
+
time: /* @__PURE__ */ new Date()
|
|
685
|
+
};
|
|
686
|
+
await ctx.database.create("cave", newCave);
|
|
687
|
+
if (finalStatus === "pending") {
|
|
688
|
+
reviewManager.sendForReview(newCave);
|
|
689
|
+
return `提交成功,序号为(${newId})`;
|
|
690
|
+
}
|
|
691
|
+
return `添加成功,序号为(${newId})`;
|
|
658
692
|
}
|
|
659
|
-
return `添加成功,序号为(${newId})`;
|
|
660
693
|
} catch (error) {
|
|
661
694
|
logger.error("添加回声洞失败:", error);
|
|
662
695
|
return "添加失败,请稍后再试";
|
|
@@ -682,8 +715,9 @@ function apply(ctx, config) {
|
|
|
682
715
|
try {
|
|
683
716
|
const [targetCave] = await ctx.database.get("cave", { id, status: "active" });
|
|
684
717
|
if (!targetCave) return `回声洞(${id})不存在`;
|
|
685
|
-
|
|
686
|
-
|
|
718
|
+
const adminChannelId = config.adminChannel ? config.adminChannel.split(":")[1] : null;
|
|
719
|
+
if (targetCave.userId !== session.userId && session.channelId !== adminChannelId) {
|
|
720
|
+
return "你没有权限删除这条回声洞";
|
|
687
721
|
}
|
|
688
722
|
await ctx.database.upsert("cave", [{ id, status: "delete" }]);
|
|
689
723
|
const caveMessage = await buildCaveMessage(targetCave, config, fileManager, logger);
|