koishi-plugin-best-cave 2.0.0 → 2.0.2
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 +69 -0
- package/lib/index.js +36 -17
- 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
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "best-cave";
|
|
3
|
+
export declare const inject: string[];
|
|
4
|
+
export declare const usage = "\n<div style=\"border-radius: 10px; border: 1px solid #ddd; padding: 16px; margin-bottom: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);\">\n <h2 style=\"margin-top: 0; color: #4a6ee0;\">\uD83D\uDCCC \u63D2\u4EF6\u8BF4\u660E</h2>\n <p>\uD83D\uDCD6 <strong>\u4F7F\u7528\u6587\u6863</strong>\uFF1A\u8BF7\u70B9\u51FB\u5DE6\u4E0A\u89D2\u7684 <strong>\u63D2\u4EF6\u4E3B\u9875</strong> \u67E5\u770B\u63D2\u4EF6\u4F7F\u7528\u6587\u6863</p>\n <p>\uD83D\uDD0D <strong>\u66F4\u591A\u63D2\u4EF6</strong>\uFF1A\u53EF\u8BBF\u95EE <a href=\"https://github.com/YisRime\" style=\"color:#4a6ee0;text-decoration:none;\">\u82E1\u6DDE\u7684 GitHub</a> \u67E5\u770B\u672C\u4EBA\u7684\u6240\u6709\u63D2\u4EF6</p>\n</div>\n\n<div style=\"border-radius: 10px; border: 1px solid #ddd; padding: 16px; margin-bottom: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);\">\n <h2 style=\"margin-top: 0; color: #e0574a;\">\u2764\uFE0F \u652F\u6301\u4E0E\u53CD\u9988</h2>\n <p>\uD83C\uDF1F \u559C\u6B22\u8FD9\u4E2A\u63D2\u4EF6\uFF1F\u8BF7\u5728 <a href=\"https://github.com/YisRime\" style=\"color:#e0574a;text-decoration:none;\">GitHub</a> \u4E0A\u7ED9\u6211\u4E00\u4E2A Star\uFF01</p>\n <p>\uD83D\uDC1B \u9047\u5230\u95EE\u9898\uFF1F\u8BF7\u901A\u8FC7 <strong>Issues</strong> \u63D0\u4EA4\u53CD\u9988\uFF0C\u6216\u52A0\u5165 QQ \u7FA4 <a href=\"https://qm.qq.com/q/PdLMx9Jowq\" style=\"color:#e0574a;text-decoration:none;\"><strong>855571375</strong></a> \u8FDB\u884C\u4EA4\u6D41</p>\n</div>\n";
|
|
5
|
+
/**
|
|
6
|
+
* 存储在数据库中的单个消息元素。
|
|
7
|
+
* @property type - 元素类型。
|
|
8
|
+
* @property content - 文本内容,仅用于 'text' 类型。
|
|
9
|
+
* @property file - 文件标识符(本地文件名或 S3 Key),用于媒体类型。
|
|
10
|
+
*/
|
|
11
|
+
export interface StoredElement {
|
|
12
|
+
type: 'text' | 'img' | 'video' | 'audio' | 'file';
|
|
13
|
+
content?: string;
|
|
14
|
+
file?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 数据库中 `cave` 表的完整对象模型。
|
|
18
|
+
* @property id - 回声洞的唯一数字 ID。
|
|
19
|
+
* @property elements - 构成回声洞内容的元素数组。
|
|
20
|
+
* @property channelId - 提交回声洞的频道 ID,若为私聊则为 null。
|
|
21
|
+
* @property userId - 提交用户的 ID。
|
|
22
|
+
* @property userName - 提交用户的昵称。
|
|
23
|
+
* @property status - 回声洞状态: 'active' (活跃), 'delete' (待删除), 'pending' (待审核)。
|
|
24
|
+
* @property time - 提交时间。
|
|
25
|
+
*/
|
|
26
|
+
export interface CaveObject {
|
|
27
|
+
id: number;
|
|
28
|
+
elements: StoredElement[];
|
|
29
|
+
channelId: string;
|
|
30
|
+
userId: string;
|
|
31
|
+
userName: string;
|
|
32
|
+
status: 'active' | 'delete' | 'pending';
|
|
33
|
+
time: Date;
|
|
34
|
+
}
|
|
35
|
+
declare module 'koishi' {
|
|
36
|
+
interface Tables {
|
|
37
|
+
cave: CaveObject;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 插件的配置接口。
|
|
42
|
+
*/
|
|
43
|
+
export interface Config {
|
|
44
|
+
coolDown: number;
|
|
45
|
+
perChannel: boolean;
|
|
46
|
+
adminUsers: string[];
|
|
47
|
+
enableProfile: boolean;
|
|
48
|
+
enableIO: boolean;
|
|
49
|
+
enableReview: boolean;
|
|
50
|
+
caveFormat: string;
|
|
51
|
+
localPath?: string;
|
|
52
|
+
enableS3: boolean;
|
|
53
|
+
endpoint?: string;
|
|
54
|
+
region?: string;
|
|
55
|
+
accessKeyId?: string;
|
|
56
|
+
secretAccessKey?: string;
|
|
57
|
+
bucket?: string;
|
|
58
|
+
publicUrl?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 使用 Koishi Schema 定义插件的配置项,用于生成配置界面。
|
|
62
|
+
*/
|
|
63
|
+
export declare const Config: Schema<Config>;
|
|
64
|
+
/**
|
|
65
|
+
* 插件的入口函数。
|
|
66
|
+
* @param ctx - Koishi 上下文。
|
|
67
|
+
* @param config - 用户提供的插件配置。
|
|
68
|
+
*/
|
|
69
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -308,13 +308,30 @@ async function buildCaveMessage(cave, config, fileManager, logger2) {
|
|
|
308
308
|
const fullUrl = config.publicUrl.endsWith("/") ? `${config.publicUrl}${fileName}` : `${config.publicUrl}/${fileName}`;
|
|
309
309
|
return Promise.resolve((0, import_koishi.h)(element.type, { ...element.attrs, src: fullUrl }));
|
|
310
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 }));
|
|
315
|
+
}
|
|
311
316
|
return mediaElementToBase64(element, fileManager, logger2);
|
|
312
317
|
}));
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
+
const finalMessage = [];
|
|
319
|
+
const formatString = config.caveFormat;
|
|
320
|
+
const separatorIndex = formatString.indexOf("|");
|
|
321
|
+
let headerFormat, footerFormat;
|
|
322
|
+
if (separatorIndex === -1) {
|
|
323
|
+
headerFormat = formatString;
|
|
324
|
+
footerFormat = "";
|
|
325
|
+
} else {
|
|
326
|
+
headerFormat = formatString.substring(0, separatorIndex);
|
|
327
|
+
footerFormat = formatString.substring(separatorIndex + 1);
|
|
328
|
+
}
|
|
329
|
+
const headerText = headerFormat.replace("{id}", cave.id.toString()).replace("{name}", cave.userName);
|
|
330
|
+
if (headerText.trim()) finalMessage.push((0, import_koishi.h)("p", {}, headerText));
|
|
331
|
+
finalMessage.push(...processedElements);
|
|
332
|
+
const footerText = footerFormat.replace("{id}", cave.id.toString()).replace("{name}", cave.userName);
|
|
333
|
+
if (footerText.trim()) finalMessage.push((0, import_koishi.h)("p", {}, footerText));
|
|
334
|
+
return finalMessage;
|
|
318
335
|
}
|
|
319
336
|
__name(buildCaveMessage, "buildCaveMessage");
|
|
320
337
|
async function cleanupPendingDeletions(ctx, fileManager, logger2) {
|
|
@@ -359,20 +376,20 @@ async function downloadMedia(ctx, fileManager, url, originalName, type, caveId,
|
|
|
359
376
|
}
|
|
360
377
|
__name(downloadMedia, "downloadMedia");
|
|
361
378
|
function checkCooldown(session, config, lastUsed) {
|
|
362
|
-
if (config.
|
|
379
|
+
if (config.coolDown <= 0 || !session.channelId || config.adminUsers.includes(session.userId)) {
|
|
363
380
|
return null;
|
|
364
381
|
}
|
|
365
382
|
const now = Date.now();
|
|
366
383
|
const lastTime = lastUsed.get(session.channelId) || 0;
|
|
367
|
-
if (now - lastTime < config.
|
|
368
|
-
const waitTime = Math.ceil((config.
|
|
384
|
+
if (now - lastTime < config.coolDown * 1e3) {
|
|
385
|
+
const waitTime = Math.ceil((config.coolDown * 1e3 - (now - lastTime)) / 1e3);
|
|
369
386
|
return `指令冷却中,请在 ${waitTime} 秒后重试`;
|
|
370
387
|
}
|
|
371
388
|
return null;
|
|
372
389
|
}
|
|
373
390
|
__name(checkCooldown, "checkCooldown");
|
|
374
391
|
function updateCooldownTimestamp(session, config, lastUsed) {
|
|
375
|
-
if (config.
|
|
392
|
+
if (config.coolDown > 0 && session.channelId) {
|
|
376
393
|
lastUsed.set(session.channelId, Date.now());
|
|
377
394
|
}
|
|
378
395
|
}
|
|
@@ -616,23 +633,25 @@ var usage = `
|
|
|
616
633
|
var logger = new import_koishi3.Logger("best-cave");
|
|
617
634
|
var Config = import_koishi3.Schema.intersect([
|
|
618
635
|
import_koishi3.Schema.object({
|
|
619
|
-
|
|
636
|
+
coolDown: import_koishi3.Schema.number().default(10).description("冷却时间(秒)"),
|
|
620
637
|
perChannel: import_koishi3.Schema.boolean().default(false).description("启用分群模式"),
|
|
621
638
|
enableProfile: import_koishi3.Schema.boolean().default(false).description("启用自定义昵称"),
|
|
622
|
-
|
|
639
|
+
enableIO: import_koishi3.Schema.boolean().default(false).description("启用导入导出"),
|
|
640
|
+
caveFormat: import_koishi3.Schema.string().default("回声洞 ——({id})|—— {name}").required().description("自定义文本"),
|
|
623
641
|
adminUsers: import_koishi3.Schema.array(import_koishi3.Schema.string()).default([]).description("管理员 ID 列表")
|
|
624
642
|
}).description("基础配置"),
|
|
625
643
|
import_koishi3.Schema.object({
|
|
626
644
|
enableReview: import_koishi3.Schema.boolean().default(false).description("启用审核")
|
|
627
645
|
}).description("审核配置"),
|
|
628
646
|
import_koishi3.Schema.object({
|
|
647
|
+
localPath: import_koishi3.Schema.string().description("文件映射路径"),
|
|
629
648
|
enableS3: import_koishi3.Schema.boolean().default(false).description("启用 S3 存储"),
|
|
630
|
-
endpoint: import_koishi3.Schema.string().required().description("端点 (Endpoint)"),
|
|
631
|
-
bucket: import_koishi3.Schema.string().required().description("存储桶 (Bucket)"),
|
|
632
|
-
region: import_koishi3.Schema.string().default("auto").description("区域 (Region)"),
|
|
633
649
|
publicUrl: import_koishi3.Schema.string().description("公共访问 URL").role("link"),
|
|
634
|
-
|
|
635
|
-
|
|
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")
|
|
636
655
|
}).description("存储配置")
|
|
637
656
|
]);
|
|
638
657
|
function apply(ctx, config) {
|
|
@@ -807,7 +826,7 @@ ${caveIds}`;
|
|
|
807
826
|
profileManager = new ProfileManager(ctx);
|
|
808
827
|
profileManager.registerCommands(cave);
|
|
809
828
|
}
|
|
810
|
-
if (config.
|
|
829
|
+
if (config.enableIO) {
|
|
811
830
|
dataManager = new DataManager(ctx, config, fileManager, logger);
|
|
812
831
|
dataManager.registerCommands(cave);
|
|
813
832
|
}
|