koishi-plugin-best-cave 2.0.1 → 2.0.3
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 +38 -0
- package/lib/Utils.d.ts +79 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +27 -26
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Context, Logger } from 'koishi';
|
|
2
|
+
import { FileManager } from './FileManager';
|
|
3
|
+
import { Config } from './index';
|
|
4
|
+
/**
|
|
5
|
+
* 数据管理器 (DataManager)
|
|
6
|
+
* @description
|
|
7
|
+
* 负责处理回声洞数据的导入和导出功能。
|
|
8
|
+
*/
|
|
9
|
+
export declare class DataManager {
|
|
10
|
+
private ctx;
|
|
11
|
+
private config;
|
|
12
|
+
private fileManager;
|
|
13
|
+
private logger;
|
|
14
|
+
/**
|
|
15
|
+
* 创建一个 DataManager 实例。
|
|
16
|
+
* @param ctx - Koishi 上下文,用于数据库操作。
|
|
17
|
+
* @param config - 插件配置。
|
|
18
|
+
* @param fileManager - 文件管理器实例,用于读写导入/导出文件。
|
|
19
|
+
* @param logger - 日志记录器实例。
|
|
20
|
+
*/
|
|
21
|
+
constructor(ctx: Context, config: Config, fileManager: FileManager, logger: Logger);
|
|
22
|
+
/**
|
|
23
|
+
* 注册与数据导入导出相关的 `.export` 和 `.import` 子命令。
|
|
24
|
+
* @param cave - 主 `cave` 命令的实例,用于挂载子命令。
|
|
25
|
+
*/
|
|
26
|
+
registerCommands(cave: any): void;
|
|
27
|
+
/**
|
|
28
|
+
* 导出所有状态为 'active' 的回声洞数据。
|
|
29
|
+
* 数据将被序列化为 JSON 并保存到 `cave_export.json` 文件中。
|
|
30
|
+
* @returns 一个描述导出结果的字符串消息。
|
|
31
|
+
*/
|
|
32
|
+
exportData(): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* 从 `cave_import.json` 文件导入回声洞数据。
|
|
35
|
+
* @returns 一个描述导入结果的字符串消息。
|
|
36
|
+
*/
|
|
37
|
+
importData(): Promise<string>;
|
|
38
|
+
}
|
package/lib/Utils.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Context, h, Logger, Session } from 'koishi';
|
|
2
|
+
import { CaveObject, Config, StoredElement } from './index';
|
|
3
|
+
import { FileManager } from './FileManager';
|
|
4
|
+
/**
|
|
5
|
+
* 将数据库中存储的 StoredElement[] 数组转换为 Koishi h() 元素数组。
|
|
6
|
+
* @param elements - 从数据库读取的元素对象数组。
|
|
7
|
+
* @returns 转换后的 h() 元素数组,用于消息发送。
|
|
8
|
+
*/
|
|
9
|
+
export declare function storedFormatToHElements(elements: StoredElement[]): h[];
|
|
10
|
+
/**
|
|
11
|
+
* 将指向本地媒体文件的 h() 元素转换为内联 Base64 格式。
|
|
12
|
+
* @param element - 包含本地文件路径的 h() 媒体元素。
|
|
13
|
+
* @param fileManager - FileManager 实例,用于读取文件。
|
|
14
|
+
* @param logger - Logger 实例,用于记录错误。
|
|
15
|
+
* @returns 转换后的 h() 元素,其 src 属性为 Base64 数据 URI。
|
|
16
|
+
*/
|
|
17
|
+
export declare function mediaElementToBase64(element: h, fileManager: FileManager, logger: Logger): Promise<h>;
|
|
18
|
+
/**
|
|
19
|
+
* 构建一条包含回声洞内容的完整消息,准备发送。
|
|
20
|
+
* 此函数会处理 S3 URL、文件映射路径或本地文件到 Base64 的转换。
|
|
21
|
+
* @param cave - 要展示的回声洞对象。
|
|
22
|
+
* @param config - 插件配置。
|
|
23
|
+
* @param fileManager - FileManager 实例。
|
|
24
|
+
* @param logger - Logger 实例。
|
|
25
|
+
* @returns 一个包含 h() 元素和字符串的消息数组。
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildCaveMessage(cave: CaveObject, config: Config, fileManager: FileManager, logger: Logger): Promise<(string | h)[]>;
|
|
28
|
+
/**
|
|
29
|
+
* 清理数据库中所有被标记为 'delete' 状态的回声洞及其关联的文件。
|
|
30
|
+
* @param ctx - Koishi 上下文。
|
|
31
|
+
* @param fileManager - FileManager 实例,用于删除文件。
|
|
32
|
+
* @param logger - Logger 实例。
|
|
33
|
+
*/
|
|
34
|
+
export declare function cleanupPendingDeletions(ctx: Context, fileManager: FileManager, logger: Logger): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* 根据插件配置(是否分群)和当前会话,生成数据库查询所需的范围条件。
|
|
37
|
+
* @param session - 当前会话对象。
|
|
38
|
+
* @param config - 插件配置。
|
|
39
|
+
* @returns 一个用于数据库查询的条件对象。
|
|
40
|
+
*/
|
|
41
|
+
export declare function getScopeQuery(session: Session, config: Config): object;
|
|
42
|
+
/**
|
|
43
|
+
* 获取下一个可用的回声洞 ID。
|
|
44
|
+
* 策略是找到当前已存在的 ID 中最小的未使用正整数。
|
|
45
|
+
* @param ctx - Koishi 上下文。
|
|
46
|
+
* @param query - 查询回声洞的范围条件,用于分群模式。
|
|
47
|
+
* @returns 一个可用的新 ID。
|
|
48
|
+
* @performance 对于非常大的数据集,此函数可能会有性能瓶颈,因为它需要获取所有现有 ID。
|
|
49
|
+
*/
|
|
50
|
+
export declare function getNextCaveId(ctx: Context, query?: object): Promise<number>;
|
|
51
|
+
/**
|
|
52
|
+
* 下载网络媒体资源并保存到文件存储中(本地或 S3)。
|
|
53
|
+
* @param ctx - Koishi 上下文。
|
|
54
|
+
* @param fileManager - FileManager 实例。
|
|
55
|
+
* @param url - 媒体资源的 URL。
|
|
56
|
+
* @param originalName - 原始文件名,用于获取扩展名。
|
|
57
|
+
* @param type - 媒体类型 ('img', 'video', 'audio', 'file')。
|
|
58
|
+
* @param caveId - 新建回声洞的 ID。
|
|
59
|
+
* @param index - 媒体在消息中的索引。
|
|
60
|
+
* @param channelId - 频道 ID。
|
|
61
|
+
* @param userId - 用户 ID。
|
|
62
|
+
* @returns 保存后的文件名/标识符。
|
|
63
|
+
*/
|
|
64
|
+
export declare function downloadMedia(ctx: Context, fileManager: FileManager, url: string, originalName: string, type: string, caveId: number, index: number, channelId: string, userId: string): Promise<string>;
|
|
65
|
+
/**
|
|
66
|
+
* 检查用户在当前频道是否处于指令冷却状态。
|
|
67
|
+
* @param session - 当前会话对象。
|
|
68
|
+
* @param config - 插件配置。
|
|
69
|
+
* @param lastUsed - 存储各频道最后使用时间的 Map。
|
|
70
|
+
* @returns 如果处于冷却中,返回提示信息字符串;否则返回 null。
|
|
71
|
+
*/
|
|
72
|
+
export declare function checkCooldown(session: Session, config: Config, lastUsed: Map<string, number>): string | null;
|
|
73
|
+
/**
|
|
74
|
+
* 更新指定频道的指令使用时间戳。
|
|
75
|
+
* @param session - 当前会话对象。
|
|
76
|
+
* @param config - 插件配置。
|
|
77
|
+
* @param lastUsed - 存储各频道最后使用时间的 Map。
|
|
78
|
+
*/
|
|
79
|
+
export declare function updateCooldownTimestamp(session: Session, config: Config, lastUsed: Map<string, number>): void;
|
package/lib/index.d.ts
CHANGED
|
@@ -41,13 +41,14 @@ declare module 'koishi' {
|
|
|
41
41
|
* 插件的配置接口。
|
|
42
42
|
*/
|
|
43
43
|
export interface Config {
|
|
44
|
-
|
|
44
|
+
coolDown: number;
|
|
45
45
|
perChannel: boolean;
|
|
46
46
|
adminUsers: string[];
|
|
47
47
|
enableProfile: boolean;
|
|
48
|
-
|
|
48
|
+
enableIO: boolean;
|
|
49
49
|
enableReview: boolean;
|
|
50
50
|
caveFormat: string;
|
|
51
|
+
localPath?: string;
|
|
51
52
|
enableS3: boolean;
|
|
52
53
|
endpoint?: string;
|
|
53
54
|
region?: string;
|
package/lib/index.js
CHANGED
|
@@ -298,23 +298,27 @@ async function mediaElementToBase64(element, fileManager, logger2) {
|
|
|
298
298
|
__name(mediaElementToBase64, "mediaElementToBase64");
|
|
299
299
|
async function buildCaveMessage(cave, config, fileManager, logger2) {
|
|
300
300
|
const caveHElements = storedFormatToHElements(cave.elements);
|
|
301
|
-
const processedElements = await Promise.all(caveHElements.map(
|
|
301
|
+
const processedElements = await Promise.all(caveHElements.map((element) => {
|
|
302
302
|
const isMedia = ["image", "video", "audio", "file"].includes(element.type);
|
|
303
303
|
const fileName = element.attrs.src;
|
|
304
304
|
if (!isMedia || !fileName) {
|
|
305
|
-
return element;
|
|
305
|
+
return Promise.resolve(element);
|
|
306
306
|
}
|
|
307
307
|
if (config.enableS3 && config.publicUrl) {
|
|
308
308
|
const fullUrl = config.publicUrl.endsWith("/") ? `${config.publicUrl}${fileName}` : `${config.publicUrl}/${fileName}`;
|
|
309
|
-
return (0, import_koishi.h)(element.type, { ...element.attrs, src: fullUrl });
|
|
309
|
+
return Promise.resolve((0, import_koishi.h)(element.type, { ...element.attrs, src: fullUrl }));
|
|
310
|
+
}
|
|
311
|
+
if (config.localPath) {
|
|
312
|
+
const mappedPath = path2.join(config.localPath, fileName);
|
|
313
|
+
const fileUri = `file://${mappedPath}`;
|
|
314
|
+
return Promise.resolve((0, import_koishi.h)(element.type, { ...element.attrs, src: fileUri }));
|
|
310
315
|
}
|
|
311
316
|
return mediaElementToBase64(element, fileManager, logger2);
|
|
312
317
|
}));
|
|
313
318
|
const finalMessage = [];
|
|
314
319
|
const formatString = config.caveFormat;
|
|
315
320
|
const separatorIndex = formatString.indexOf("|");
|
|
316
|
-
let headerFormat;
|
|
317
|
-
let footerFormat;
|
|
321
|
+
let headerFormat, footerFormat;
|
|
318
322
|
if (separatorIndex === -1) {
|
|
319
323
|
headerFormat = formatString;
|
|
320
324
|
footerFormat = "";
|
|
@@ -323,14 +327,10 @@ async function buildCaveMessage(cave, config, fileManager, logger2) {
|
|
|
323
327
|
footerFormat = formatString.substring(separatorIndex + 1);
|
|
324
328
|
}
|
|
325
329
|
const headerText = headerFormat.replace("{id}", cave.id.toString()).replace("{name}", cave.userName);
|
|
326
|
-
if (headerText.trim()) {
|
|
327
|
-
finalMessage.push((0, import_koishi.h)("p", {}, headerText));
|
|
328
|
-
}
|
|
330
|
+
if (headerText.trim()) finalMessage.push((0, import_koishi.h)("p", {}, headerText));
|
|
329
331
|
finalMessage.push(...processedElements);
|
|
330
332
|
const footerText = footerFormat.replace("{id}", cave.id.toString()).replace("{name}", cave.userName);
|
|
331
|
-
if (footerText.trim()) {
|
|
332
|
-
finalMessage.push((0, import_koishi.h)("p", {}, footerText));
|
|
333
|
-
}
|
|
333
|
+
if (footerText.trim()) finalMessage.push((0, import_koishi.h)("p", {}, footerText));
|
|
334
334
|
return finalMessage;
|
|
335
335
|
}
|
|
336
336
|
__name(buildCaveMessage, "buildCaveMessage");
|
|
@@ -376,20 +376,20 @@ async function downloadMedia(ctx, fileManager, url, originalName, type, caveId,
|
|
|
376
376
|
}
|
|
377
377
|
__name(downloadMedia, "downloadMedia");
|
|
378
378
|
function checkCooldown(session, config, lastUsed) {
|
|
379
|
-
if (config.
|
|
379
|
+
if (config.coolDown <= 0 || !session.channelId || config.adminUsers.includes(session.userId)) {
|
|
380
380
|
return null;
|
|
381
381
|
}
|
|
382
382
|
const now = Date.now();
|
|
383
383
|
const lastTime = lastUsed.get(session.channelId) || 0;
|
|
384
|
-
if (now - lastTime < config.
|
|
385
|
-
const waitTime = Math.ceil((config.
|
|
384
|
+
if (now - lastTime < config.coolDown * 1e3) {
|
|
385
|
+
const waitTime = Math.ceil((config.coolDown * 1e3 - (now - lastTime)) / 1e3);
|
|
386
386
|
return `指令冷却中,请在 ${waitTime} 秒后重试`;
|
|
387
387
|
}
|
|
388
388
|
return null;
|
|
389
389
|
}
|
|
390
390
|
__name(checkCooldown, "checkCooldown");
|
|
391
391
|
function updateCooldownTimestamp(session, config, lastUsed) {
|
|
392
|
-
if (config.
|
|
392
|
+
if (config.coolDown > 0 && session.channelId) {
|
|
393
393
|
lastUsed.set(session.channelId, Date.now());
|
|
394
394
|
}
|
|
395
395
|
}
|
|
@@ -633,24 +633,25 @@ var usage = `
|
|
|
633
633
|
var logger = new import_koishi3.Logger("best-cave");
|
|
634
634
|
var Config = import_koishi3.Schema.intersect([
|
|
635
635
|
import_koishi3.Schema.object({
|
|
636
|
-
|
|
636
|
+
coolDown: import_koishi3.Schema.number().default(10).description("冷却时间(秒)"),
|
|
637
637
|
perChannel: import_koishi3.Schema.boolean().default(false).description("启用分群模式"),
|
|
638
638
|
enableProfile: import_koishi3.Schema.boolean().default(false).description("启用自定义昵称"),
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
639
|
+
enableIO: import_koishi3.Schema.boolean().default(false).description("启用导入导出"),
|
|
640
|
+
caveFormat: import_koishi3.Schema.string().default("回声洞 ——({id})|—— {name}").description("自定义文本"),
|
|
641
|
+
adminUsers: import_koishi3.Schema.array(import_koishi3.Schema.string()).default([]).description("管理员 ID 列表")
|
|
642
642
|
}).description("基础配置"),
|
|
643
643
|
import_koishi3.Schema.object({
|
|
644
644
|
enableReview: import_koishi3.Schema.boolean().default(false).description("启用审核")
|
|
645
645
|
}).description("审核配置"),
|
|
646
646
|
import_koishi3.Schema.object({
|
|
647
|
+
localPath: import_koishi3.Schema.string().description("文件映射路径"),
|
|
647
648
|
enableS3: import_koishi3.Schema.boolean().default(false).description("启用 S3 存储"),
|
|
648
|
-
endpoint: import_koishi3.Schema.string().required().description("端点 (Endpoint)"),
|
|
649
|
-
bucket: import_koishi3.Schema.string().required().description("存储桶 (Bucket)"),
|
|
650
|
-
region: import_koishi3.Schema.string().default("auto").description("区域 (Region)"),
|
|
651
649
|
publicUrl: import_koishi3.Schema.string().description("公共访问 URL").role("link"),
|
|
652
|
-
|
|
653
|
-
|
|
650
|
+
endpoint: import_koishi3.Schema.string().description("端点 (Endpoint)").role("link"),
|
|
651
|
+
bucket: import_koishi3.Schema.string().description("存储桶 (Bucket)"),
|
|
652
|
+
region: import_koishi3.Schema.string().default("auto").description("区域 (Region)"),
|
|
653
|
+
accessKeyId: import_koishi3.Schema.string().description("Access Key ID").role("secret"),
|
|
654
|
+
secretAccessKey: import_koishi3.Schema.string().description("Secret Access Key").role("secret")
|
|
654
655
|
}).description("存储配置")
|
|
655
656
|
]);
|
|
656
657
|
function apply(ctx, config) {
|
|
@@ -718,7 +719,7 @@ function apply(ctx, config) {
|
|
|
718
719
|
const scopeQuery = getScopeQuery(session, config);
|
|
719
720
|
const newId = await getNextCaveId(ctx, scopeQuery);
|
|
720
721
|
const finalElementsForDb = [];
|
|
721
|
-
let mediaIndex =
|
|
722
|
+
let mediaIndex = 0;
|
|
722
723
|
async function traverseAndProcess(elements) {
|
|
723
724
|
for (const el of elements) {
|
|
724
725
|
const elementType = el.type === "image" ? "img" : el.type;
|
|
@@ -825,7 +826,7 @@ ${caveIds}`;
|
|
|
825
826
|
profileManager = new ProfileManager(ctx);
|
|
826
827
|
profileManager.registerCommands(cave);
|
|
827
828
|
}
|
|
828
|
-
if (config.
|
|
829
|
+
if (config.enableIO) {
|
|
829
830
|
dataManager = new DataManager(ctx, config, fileManager, logger);
|
|
830
831
|
dataManager.registerCommands(cave);
|
|
831
832
|
}
|