koishi-plugin-best-cave 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -25,4 +25,5 @@ export interface Config {
25
25
  itemsPerPage: number;
26
26
  duplicateThreshold: number;
27
27
  enableDuplicate: boolean;
28
+ enableMD5: boolean;
28
29
  }
package/lib/index.js CHANGED
@@ -33,14 +33,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
33
33
  // src/locales/zh-CN.yml
34
34
  var require_zh_CN = __commonJS({
35
35
  "src/locales/zh-CN.yml"(exports2, module2) {
36
- module2.exports = { _config: { manager: "管理员", number: "冷却时间(秒)", enableAudit: "启用审核", imageMaxSize: "图片最大大小(MB)", enableDuplicate: "启用图片查重", duplicateThreshold: "图片相似度阈值(0-1)", allowVideo: "允许视频上传", videoMaxSize: "视频最大大小(MB)", enablePagination: "启用统计分页", itemsPerPage: "每页显示数目", blacklist: "黑名单(用户)", whitelist: "审核白名单(用户/群组/频道)" }, commands: { cave: { description: "回声洞", usage: "支持添加、抽取、查看、管理回声洞", examples: "使用 cave 随机抽取回声洞\n使用 -a 直接添加或引用添加\n使用 -g 查看指定回声洞\n使用 -r 删除指定回声洞", options: { a: "添加回声洞", g: "查看回声洞", r: "删除回声洞", p: "通过审核(批量)", d: "拒绝审核(批量)", l: "查询投稿统计" }, add: { noContent: "请在一分钟内发送内容", operationTimeout: "操作超时,添加取消", videoDisabled: "不允许上传视频", submitPending: "提交成功,序号为({0})", addSuccess: "添加成功,序号为({0})", mediaSizeExceeded: "{0}文件大小超过限制", localFileNotAllowed: "检测到本地文件路径,无法保存" }, remove: { noPermission: "你无权删除他人添加的回声洞", deletePending: "删除(待审核)", deleted: "已删除" }, list: { pageInfo: "第 {0} / {1} 页", header: "当前共有 {0} 项回声洞:", totalItems: "用户 {0} 共计投稿 {1} 项:", idsLine: "{0}" }, audit: { noPending: "暂无待审核回声洞", pendingNotFound: "未找到待审核回声洞", pendingResult: "{0},剩余 {1} 个待审核回声洞:[{2}]", auditPassed: "已通过", auditRejected: "已拒绝", batchAuditResult: "已{0} {1}/{2} 项回声洞", title: "待审核回声洞:", from: "投稿人:", sendFailed: "发送审核消息失败,无法联系管理员 {0}" }, error: { noContent: "回声洞内容为空", getCave: "获取回声洞失败", noCave: "没有回声洞", invalidId: "请输入有效的回声洞ID", notFound: "未找到该回声洞", exactDuplicateFound: "发现完全相同的", similarDuplicateFound: "发现相似度为 {0}% 的" }, message: { blacklisted: "你已被列入黑名单", managerOnly: "此操作仅限管理员可用", cooldown: "群聊冷却中...请在 {0} 秒后重试", caveTitle: "回声洞 —— ({0})", contributorSuffix: "—— {0}", mediaSizeExceeded: "{0}文件大小超过限制" } } } };
36
+ module2.exports = { _config: { manager: "管理员", number: "冷却时间(秒)", enableAudit: "启用审核", imageMaxSize: "图片最大大小(MB)", enableMD5: "启用MD5查重", enableDuplicate: "启用pHash查重", duplicateThreshold: "pHash相似度阈值(0-1)", allowVideo: "允许视频上传", videoMaxSize: "视频最大大小(MB)", enablePagination: "启用统计分页", itemsPerPage: "每页显示数目", blacklist: "黑名单(用户)", whitelist: "审核白名单(用户/群组/频道)" }, commands: { cave: { description: "回声洞", usage: "支持添加、抽取、查看、管理回声洞", examples: "使用 cave 随机抽取回声洞\n使用 -a 直接添加或引用添加\n使用 -g 查看指定回声洞\n使用 -r 删除指定回声洞", options: { a: "添加回声洞", g: "查看回声洞", r: "删除回声洞", p: "通过审核(批量)", d: "拒绝审核(批量)", l: "查询投稿统计" }, add: { noContent: "请在一分钟内发送内容", operationTimeout: "操作超时,添加取消", videoDisabled: "不允许上传视频", submitPending: "提交成功,序号为({0})", addSuccess: "添加成功,序号为({0})", mediaSizeExceeded: "{0}文件大小超过限制", localFileNotAllowed: "检测到本地文件路径,无法保存" }, remove: { noPermission: "你无权删除他人添加的回声洞", deletePending: "删除(待审核)", deleted: "已删除" }, list: { pageInfo: "第 {0} / {1} 页", header: "当前共有 {0} 项回声洞:", totalItems: "用户 {0} 共计投稿 {1} 项:", idsLine: "{0}" }, audit: { noPending: "暂无待审核回声洞", pendingNotFound: "未找到待审核回声洞", pendingResult: "{0},剩余 {1} 个待审核回声洞:[{2}]", auditPassed: "已通过", auditRejected: "已拒绝", batchAuditResult: "已{0} {1}/{2} 项回声洞", title: "待审核回声洞:", from: "投稿人:", sendFailed: "发送审核消息失败,无法联系管理员 {0}" }, error: { noContent: "回声洞内容为空", getCave: "获取回声洞失败", noCave: "没有回声洞", invalidId: "请输入有效的回声洞ID", notFound: "未找到该回声洞", exactDuplicateFound: "发现完全相同的", similarDuplicateFound: "发现相似度为 {0}% 的" }, message: { blacklisted: "你已被列入黑名单", managerOnly: "此操作仅限管理员可用", cooldown: "群聊冷却中...请在 {0} 秒后重试", caveTitle: "回声洞 —— ({0})", contributorSuffix: "—— {0}", mediaSizeExceeded: "{0}文件大小超过限制" } } } };
37
37
  }
38
38
  });
39
39
 
40
40
  // src/locales/en-US.yml
41
41
  var require_en_US = __commonJS({
42
42
  "src/locales/en-US.yml"(exports2, module2) {
43
- module2.exports = { _config: { manager: "Administrator", number: "Cooldown time (seconds)", enableAudit: "Enable moderation", imageMaxSize: "Maximum image size (MB)", enableDuplicate: "Enable image duplicate check", duplicateThreshold: "Image similarity threshold (0-1)", allowVideo: "Allow video upload", videoMaxSize: "Maximum video size (MB)", enablePagination: "Enable statistics pagination", itemsPerPage: "Items per page", blacklist: "Blacklist (users)", whitelist: "Moderation whitelist (users/groups/channels)" }, commands: { cave: { description: "Echo Cave", usage: "Support adding, drawing, viewing, and managing echo caves", examples: "Use cave to randomly draw an echo\nUse -a to add directly or add by reference\nUse -g to view specific echo\nUse -r to delete specific echo", options: { a: "Add echo", g: "View echo", r: "Delete echo", p: "Approve moderation (batch)", d: "Reject moderation (batch)", l: "Query submission statistics" }, add: { noContent: "Please send content within one minute", operationTimeout: "Operation timeout, addition cancelled", videoDisabled: "Video upload not allowed", submitPending: "Submission successful, ID is ({0})", addSuccess: "Added successfully, ID is ({0})", mediaSizeExceeded: "{0} file size exceeds limit", localFileNotAllowed: "Local file path detected, cannot save" }, remove: { noPermission: "You don't have permission to delete others' echos", deletePending: "Delete (pending review)", deleted: "Deleted" }, list: { pageInfo: "Page {0} / {1}", header: "Currently there are {0} echos:", totalItems: "User {0} has submitted {1} items:", idsLine: "{0}" }, audit: { noPending: "No pending echos for review", pendingNotFound: "Pending echo not found", pendingResult: "{0}, {1} pending echos remaining: [{2}]", auditPassed: "Approved", auditRejected: "Rejected", batchAuditResult: "{0} {1}/{2} echos", title: "Pending echos:", from: "Submitted by:", sendFailed: "Failed to send moderation message, cannot contact administrator {0}" }, error: { noContent: "Echo content is empty", getCave: "Failed to get echo", noCave: "No echos available", invalidId: "Please enter a valid echo ID", notFound: "Echo not found", exactDuplicateFound: "Found exactly identical", similarDuplicateFound: "Found {0}% similar" }, message: { blacklisted: "You have been blacklisted", managerOnly: "This operation is limited to administrators only", cooldown: "Group chat cooling down... Please try again in {0} seconds", caveTitle: "Echo Cave —— ({0})", contributorSuffix: "—— {0}", mediaSizeExceeded: "{0} file size exceeds limit" } } } };
43
+ module2.exports = { _config: { manager: "Administrator", number: "Cooldown time (seconds)", enableAudit: "Enable moderation", imageMaxSize: "Maximum image size (MB)", enableMD5: "Enable MD5 duplicate check", enableDuplicate: "Enable pHash duplicate check", duplicateThreshold: "pHash similarity threshold (0-1)", allowVideo: "Allow video upload", videoMaxSize: "Maximum video size (MB)", enablePagination: "Enable statistics pagination", itemsPerPage: "Items per page", blacklist: "Blacklist (users)", whitelist: "Moderation whitelist (users/groups/channels)" }, commands: { cave: { description: "Echo Cave", usage: "Support adding, drawing, viewing, and managing echo caves", examples: "Use cave to randomly draw an echo\nUse -a to add directly or add by reference\nUse -g to view specific echo\nUse -r to delete specific echo", options: { a: "Add echo", g: "View echo", r: "Delete echo", p: "Approve moderation (batch)", d: "Reject moderation (batch)", l: "Query submission statistics" }, add: { noContent: "Please send content within one minute", operationTimeout: "Operation timeout, addition cancelled", videoDisabled: "Video upload not allowed", submitPending: "Submission successful, ID is ({0})", addSuccess: "Added successfully, ID is ({0})", mediaSizeExceeded: "{0} file size exceeds limit", localFileNotAllowed: "Local file path detected, cannot save" }, remove: { noPermission: "You don't have permission to delete others' echos", deletePending: "Delete (pending review)", deleted: "Deleted" }, list: { pageInfo: "Page {0} / {1}", header: "Currently there are {0} echos:", totalItems: "User {0} has submitted {1} items:", idsLine: "{0}" }, audit: { noPending: "No pending echos for review", pendingNotFound: "Pending echo not found", pendingResult: "{0}, {1} pending echos remaining: [{2}]", auditPassed: "Approved", auditRejected: "Rejected", batchAuditResult: "{0} {1}/{2} echos", title: "Pending echos:", from: "Submitted by:", sendFailed: "Failed to send moderation message, cannot contact administrator {0}" }, error: { noContent: "Echo content is empty", getCave: "Failed to get echo", noCave: "No echos available", invalidId: "Please enter a valid echo ID", notFound: "Echo not found", exactDuplicateFound: "Found exactly identical", similarDuplicateFound: "Found {0}% similar" }, message: { blacklisted: "You have been blacklisted", managerOnly: "This operation is limited to administrators only", cooldown: "Group chat cooling down... Please try again in {0} seconds", caveTitle: "Echo Cave —— ({0})", contributorSuffix: "—— {0}", mediaSizeExceeded: "{0} file size exceeds limit" } } } };
44
44
  }
45
45
  });
46
46
 
@@ -908,10 +908,12 @@ var Config = import_koishi4.Schema.object({
908
908
  // 启用审核
909
909
  imageMaxSize: import_koishi4.Schema.number().default(4),
910
910
  // 图片大小限制(MB)
911
+ enableMD5: import_koishi4.Schema.boolean().default(true),
912
+ // 启用MD5查重
911
913
  enableDuplicate: import_koishi4.Schema.boolean().default(true),
912
- // 开启查重
914
+ // 启用相似度查重
913
915
  duplicateThreshold: import_koishi4.Schema.number().default(0.8),
914
- // 查重阈值(0-1)
916
+ // 相似度查重阈值(0-1)
915
917
  allowVideo: import_koishi4.Schema.boolean().default(true),
916
918
  // 允许视频
917
919
  videoMaxSize: import_koishi4.Schema.number().default(16),
@@ -1481,23 +1483,27 @@ async function saveMedia(urls, fileNames, resourceDir, caveId, mediaType, config
1481
1483
  const buffer = Buffer.from(response.data);
1482
1484
  if (mediaType === "img") {
1483
1485
  const baseName = path4.basename(fileName || "md5", ext).replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "");
1484
- const files = await fs4.promises.readdir(resourceDir);
1485
- const duplicateFile = files.find((file) => file.startsWith(baseName + "_"));
1486
- if (duplicateFile) {
1487
- const duplicateCaveId = parseInt(duplicateFile.split("_")[1]);
1488
- if (!isNaN(duplicateCaveId)) {
1489
- const caveFilePath = path4.join(ctx.baseDir, "data", "cave", "cave.json");
1490
- const data = await FileHandler.readJsonData(caveFilePath);
1491
- const originalCave = data.find((item) => item.cave_id === duplicateCaveId);
1492
- if (originalCave) {
1493
- const message = session.text("commands.cave.error.exactDuplicateFound");
1494
- await session.send(message + await buildMessage(originalCave, resourceDir, session));
1495
- throw new Error("duplicate_found");
1486
+ if (config.enableMD5) {
1487
+ const files = await fs4.promises.readdir(resourceDir);
1488
+ const duplicateFile = files.find((file) => file.startsWith(baseName + "_"));
1489
+ if (duplicateFile) {
1490
+ const duplicateCaveId = parseInt(duplicateFile.split("_")[1]);
1491
+ if (!isNaN(duplicateCaveId)) {
1492
+ const caveFilePath = path4.join(ctx.baseDir, "data", "cave", "cave.json");
1493
+ const data = await FileHandler.readJsonData(caveFilePath);
1494
+ const originalCave = data.find((item) => item.cave_id === duplicateCaveId);
1495
+ if (originalCave) {
1496
+ const message = session.text("commands.cave.error.exactDuplicateFound");
1497
+ await session.send(message + await buildMessage(originalCave, resourceDir, session));
1498
+ throw new Error("duplicate_found");
1499
+ }
1496
1500
  }
1497
1501
  }
1498
1502
  }
1499
1503
  if (config.enableDuplicate) {
1500
- const result = await hashStorage.findDuplicates([buffer], config.duplicateThreshold);
1504
+ const hashStorage2 = new HashStorage(path4.join(ctx.baseDir, "data", "cave"));
1505
+ await hashStorage2.initialize();
1506
+ const result = await hashStorage2.findDuplicates([buffer], config.duplicateThreshold);
1501
1507
  if (result.length > 0 && result[0] !== null) {
1502
1508
  const duplicate = result[0];
1503
1509
  const similarity = duplicate.similarity;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "最好的 cave 插件,可开关的审核系统,可引用添加,支持图文混合内容,可查阅投稿列表,完美复刻你的 .cave 体验!",
4
- "version": "1.3.2",
4
+ "version": "1.3.4",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],
package/readme.md CHANGED
@@ -8,15 +8,36 @@
8
8
 
9
9
  ### 核心功能
10
10
 
11
- - 支持文字与图片混合保存
12
- - 智能处理各类图片与视频链接
13
- - 内容智能排序,保持原始布局
14
- - 完整的权限管理系统
15
- - 可选的内容审核流程
16
- - 群组调用冷却机制
17
- - 重复内容智能检测
18
- - 黑白名单系统
19
- - 分页显示支持
11
+ - **内容管理**
12
+ - 支持文字与图片混合保存,自动保持布局顺序
13
+ - 视频内容单独发送,支持多种格式
14
+ - 自动文本格式化与排版
15
+ - 引用消息自动解析和保存
16
+ - 支持引用已有内容的布局
17
+
18
+ - **审核机制**
19
+ - 可配置的审核开关与多级权限
20
+ - 完整的黑白名单系统(支持用户/群组/频道)
21
+ - 白名单用户自动跳过审核
22
+ - 支持单条和批量审核操作
23
+ - 拒绝审核时自动清理媒体文件
24
+ - 审核消息自动通知管理员
25
+
26
+ - **媒体处理**
27
+ - 智能处理多种类型媒体链接
28
+ - 支持本地图片上传和URL引用
29
+ - 自动文件大小检查与限制
30
+ - 基于感知哈希的图片查重
31
+ - 可配置的相似度阈值检测
32
+ - MD5文件名重复检查
33
+
34
+ - **使用体验**
35
+ - 基于群组的调用冷却机制
36
+ - 管理员操作不受冷却限制
37
+ - 支持按页浏览投稿记录
38
+ - 支持按用户ID查询统计
39
+ - 临时消息自动清理
40
+ - 错误提示自动消失
20
41
 
21
42
  ### 指令
22
43
 
@@ -54,9 +75,14 @@
54
75
  ### 注意事项
55
76
 
56
77
  1. 图片和视频会自动保存到本地,请确保存储空间充足
57
- 2. 管理员不受群组冷却时间限制
58
- 3. 开启审核模式后,白名单内的用户可直接投稿
59
- 4. 引用消息添加时会保留原消息的格式与布局
60
- 5. 支持自动检测重复图片内容,可通过阈值调整严格程度
78
+ 2. 管理员不受群组冷却时间限制且可查看所有用户统计
79
+ 3. 开启审核模式后,白名单内的用户/群组/频道可直接投稿
80
+ 4. 引用消息添加时会保留原消息的格式与布局顺序
81
+ 5. 支持两种重复检测机制:
82
+ - 基于MD5的精确查重
83
+ - 基于感知哈希的相似度查重
61
84
  6. 黑名单中的用户无法使用任何功能
62
- 7. 支持按页码查看投稿统计,提升大量数据的浏览体验
85
+ 7. 支持按页码和用户ID查看投稿统计
86
+ 8. 临时消息(如错误提示)会在10秒后自动消失
87
+ 9. 视频内容会单独发送以保证正常显示
88
+ 10. 支持自动清理被拒绝或删除的媒体文件