koishi-plugin-best-cave 1.1.0 → 1.1.1

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.
Files changed (2) hide show
  1. package/lib/index.js +368 -316
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __getProtoOf = Object.getPrototypeOf;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
7
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name2 in all)
10
13
  __defProp(target, name2, { get: all[name2], enumerable: true });
@@ -27,6 +30,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/locales/zh-CN.yml
34
+ var require_zh_CN = __commonJS({
35
+ "src/locales/zh-CN.yml"(exports2, module2) {
36
+ module2.exports = { _config: { manager: "管理员", blacklist: "黑名单(用户)", whitelist: "审核白名单(用户/群组/频道)", number: "冷却时间(秒)", enableAudit: "启用审核", allowVideo: "允许视频上传", videoMaxSize: "视频最大大小(MB)", imageMaxSize: "图片最大大小(MB)", enablePagination: "启用统计分页", itemsPerPage: "每页显示数目" }, 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}文件大小超过限制", mediaFormatUnsupported: "{0}格式不支持", videoSendFailed: "视频发送失败" }, 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: { commandProcess: "命令处理失败:{0}", getCave: "获取回声洞失败", noCave: "暂无回声洞", saveFailed: "保存数据失败", mediaLoadFailed: "[加载{0}失败]", videoSendFailed: "视频发送失败", uploadImageFailed: "图片上传失败", uploadVideoFailed: "视频上传失败", fileRead: "读取 JSON 数据失败:{0}", fileWrite: "写入 JSON 数据失败:{0}", notFound: "未找到", userInfo: "获取用户信息失败:{0}", auditProcess: "审核处理失败:{0}", invalidId: "请输入有效的回声洞序号" }, message: { blacklisted: "你已被列入黑名单", managerOnly: "此操作仅限管理员可用", cooldown: "群聊冷却中...请在 {0} 秒后重试", caveTitle: "回声洞 —— ({0})", videoSending: "[视频发送中]", mediaInvalid: "[无效的{0}]", contributorSuffix: "—— {0}", mediaSizeExceeded: "{0}文件大小超过限制" } } } };
37
+ }
38
+ });
39
+
40
+ // src/locales/en-US.yml
41
+ var require_en_US = __commonJS({
42
+ "src/locales/en-US.yml"(exports2, module2) {
43
+ module2.exports = { _config: { manager: "Manager", blacklist: "Blacklist (User)", whitelist: "Whitelist (User/Group/Channel)", number: "Cooldown Time (Seconds)", enableAudit: "Enable Audit", allowVideo: "Allow Video Upload", videoMaxSize: "Max Video Size (MB)", imageMaxSize: "Max Image Size (MB)", enablePagination: "Enable Pagination", itemsPerPage: "Items Per Page" }, commands: { cave: { description: "Echo Cave", usage: "Support adding, drawing, viewing, and managing Echo Cave", examples: "Use cave to randomly draw Echo Cave\nUse -a to add or reference directly\nUse -g to view specified Echo Cave\nUse -r to delete specified Echo Cave", options: { a: "Add Echo Cave", g: "View Echo Cave", r: "Delete Echo Cave", p: "Approve Audit (Batch)", d: "Reject Audit (Batch)", l: "Query Submission Statistics" }, add: { noContent: "Please send content within one minute", operationTimeout: "Operation timed out, addition canceled", videoDisabled: "Video upload not allowed", submitPending: "Submission successful, ID is ({0})", addSuccess: "Addition successful, ID is ({0})", mediaSizeExceeded: "{0} file size exceeds limit", mediaFormatUnsupported: "{0} format not supported", videoSendFailed: "Video send failed" }, remove: { noPermission: "You do not have permission to delete others' Echo Cave", deletePending: "Delete (Pending Audit)", deleted: "Deleted" }, list: { pageInfo: "Page {0} / {1}", header: "There are currently {0} Echo Caves:", totalItems: "User {0} has submitted a total of {1} items:", idsLine: "{0}" }, audit: { noPending: "No pending Echo Caves", pendingNotFound: "Pending Echo Cave not found", pendingResult: "{0}, {1} pending Echo Caves remaining: [{2}]", auditPassed: "Approved", auditRejected: "Rejected", batchAuditResult: "{0} {1}/{2} Echo Caves", title: "Pending Echo Caves:", from: "Contributor:", sendFailed: "Failed to send audit message, unable to contact manager {0}" }, error: { commandProcess: "Command processing failed: {0}", getCave: "Failed to get Echo Cave", noCave: "No Echo Caves", saveFailed: "Failed to save data", mediaLoadFailed: "[Failed to load {0}]", videoSendFailed: "Video send failed", uploadImageFailed: "Image upload failed", uploadVideoFailed: "Video upload failed", fileRead: "Failed to read JSON data: {0}", fileWrite: "Failed to write JSON data: {0}", notFound: "Not found", userInfo: "Failed to get user information: {0}", auditProcess: "Audit processing failed: {0}", invalidId: "Please enter a valid Echo Cave ID" }, message: { blacklisted: "You have been blacklisted", managerOnly: "This operation is for managers only", cooldown: "Group chat cooldown... Please try again in {0} seconds", caveTitle: "Echo Cave —— ({0})", videoSending: "[Video sending]", mediaInvalid: "[Invalid {0}]", contributorSuffix: "—— {0}", mediaSizeExceeded: "{0} file size exceeds limit" } } } };
44
+ }
45
+ });
46
+
30
47
  // src/index.ts
31
48
  var src_exports = {};
32
49
  __export(src_exports, {
@@ -44,22 +61,32 @@ var path = __toESM(require("path"));
44
61
  var name = "cave";
45
62
  var inject = ["database"];
46
63
  var Config = import_koishi.Schema.object({
47
- manager: import_koishi.Schema.array(import_koishi.Schema.string()).required().description("管理员"),
48
- blacklist: import_koishi.Schema.array(import_koishi.Schema.string()).default([]).description("黑名单"),
49
- number: import_koishi.Schema.number().default(60).description("调用冷却时间(秒)"),
50
- enableAudit: import_koishi.Schema.boolean().default(false).description("审核功能"),
51
- allowVideo: import_koishi.Schema.boolean().default(true).description("允许添加视频"),
52
- videoMaxSize: import_koishi.Schema.number().default(10).description("视频最大大小(MB)"),
53
- imageMaxSize: import_koishi.Schema.number().default(5).description("图片最大大小(MB)")
64
+ manager: import_koishi.Schema.array(import_koishi.Schema.string()).required(),
65
+ blacklist: import_koishi.Schema.array(import_koishi.Schema.string()).default([]),
66
+ whitelist: import_koishi.Schema.array(import_koishi.Schema.string()).default([]),
67
+ number: import_koishi.Schema.number().default(60),
68
+ enableAudit: import_koishi.Schema.boolean().default(false),
69
+ allowVideo: import_koishi.Schema.boolean().default(true),
70
+ videoMaxSize: import_koishi.Schema.number().default(16),
71
+ imageMaxSize: import_koishi.Schema.number().default(4),
72
+ enablePagination: import_koishi.Schema.boolean().default(false),
73
+ itemsPerPage: import_koishi.Schema.number().default(10)
74
+ }).i18n({
75
+ "zh-CN": require_zh_CN()._config,
76
+ "en-US": require_en_US()._config
54
77
  });
55
78
  async function apply(ctx, config) {
79
+ ctx.i18n.define("zh-CN", require_zh_CN());
80
+ ctx.i18n.define("en-US", require_en_US());
56
81
  const { caveFilePath, resourceDir, pendingFilePath } = await initCavePaths(ctx);
57
82
  const lastUsed = /* @__PURE__ */ new Map();
58
- ctx.command("cave [message:text]", "回声洞").usage("支持添加、抽取、查看、查询回声洞").example("cave 随机抽取回声洞").example("cave -a 内容 添加新回声洞").example("cave -g/r x 查看/删除指定回声洞").example("cave -p/d x/all 通过/拒绝待审回声洞").example("cave -l x 查询投稿者投稿列表").option("a", "添加回声洞").option("g", "查看回声洞", { type: "string" }).option("r", "删除回声洞", { type: "string" }).option("p", "通过审核", { type: "string" }).option("d", "拒绝审核", { type: "string" }).option("l", "查询投稿统计", { type: "string" }).before(async ({ session, options }) => {
59
- if (config.blacklist.includes(session.userId)) return "你已被列入黑名单";
83
+ ctx.command("cave [message]").option("a", "添加回声洞").option("g", "查看回声洞", { type: "string" }).option("r", "删除回声洞", { type: "string" }).option("p", "通过审核", { type: "string" }).option("d", "拒绝审核", { type: "string" }).option("l", "查询投稿统计", { type: "string" }).before(async ({ session, options }) => {
84
+ if (config.blacklist.includes(session.userId)) {
85
+ return sendTempMessage(session, "commands.cave.message.blacklisted");
86
+ }
60
87
  if (session.content && session.content.includes("-help")) return;
61
88
  if ((options.l || options.p || options.d) && !config.manager.includes(session.userId)) {
62
- return "此操作仅管理员可用";
89
+ return sendTempMessage(session, "commands.cave.message.managerOnly");
63
90
  }
64
91
  }).action(async ({ session, options }, ...content) => {
65
92
  return await handleCaveAction(ctx, config, session, options, content, lastUsed);
@@ -71,43 +98,32 @@ var FileHandler = class {
71
98
  static {
72
99
  __name(this, "FileHandler");
73
100
  }
74
- static readJsonData(filePath, validator) {
101
+ static readJsonData(filePath, session, validator) {
75
102
  try {
76
103
  const data = fs.readFileSync(filePath, "utf8");
77
104
  const parsed = JSON.parse(data || "[]");
78
- if (!Array.isArray(parsed)) return [];
79
- return validator ? parsed.filter(validator) : parsed;
105
+ return Array.isArray(parsed) ? validator ? parsed.filter(validator) : parsed : [];
80
106
  } catch (error) {
81
- throw new Error(`操作失败: ${error.message}`);
107
+ logger.error(session.text("commands.cave.error.fileRead", [error.message]));
108
+ return [];
82
109
  }
83
110
  }
84
- static writeJsonData(filePath, data) {
111
+ static writeJsonData(filePath, data, session) {
85
112
  try {
86
113
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
87
114
  } catch (error) {
88
- throw new Error(`操作失败: ${error.message}`);
115
+ logger.error(session.text("commands.cave.error.fileWrite", [error.message]));
116
+ throw new Error(session.text("commands.cave.error.saveFailed"));
89
117
  }
90
118
  }
91
119
  static async ensureDirectory(dir) {
92
- try {
93
- if (!fs.existsSync(dir)) {
94
- await fs.promises.mkdir(dir, { recursive: true });
95
- }
96
- } catch (error) {
97
- throw new Error(`操作失败: ${error.message}`);
98
- }
120
+ !fs.existsSync(dir) && await fs.promises.mkdir(dir, { recursive: true });
99
121
  }
100
122
  static async ensureJsonFile(filePath, defaultContent = "[]") {
101
- try {
102
- if (!fs.existsSync(filePath)) {
103
- await fs.promises.writeFile(filePath, defaultContent, "utf8");
104
- }
105
- } catch (error) {
106
- throw new Error(`操作失败: ${error.message}`);
107
- }
123
+ !fs.existsSync(filePath) && await fs.promises.writeFile(filePath, defaultContent, "utf8");
108
124
  }
109
125
  };
110
- async function saveMedia(urls, fileSuggestions, resourceDir, caveId, config, ctx, mediaType) {
126
+ async function saveMedia(urls, fileNames, fileSizes, resourceDir, caveId, config, ctx, mediaType, session) {
111
127
  const savedFiles = [];
112
128
  const defaults = mediaType === "img" ? { ext: "png", accept: "image/*", maxSize: config.imageMaxSize } : { ext: "mp4", accept: "video/*", maxSize: config.videoMaxSize };
113
129
  const extPattern = /\.[a-zA-Z0-9]+$/;
@@ -123,32 +139,20 @@ async function saveMedia(urls, fileSuggestions, resourceDir, caveId, config, ctx
123
139
  }
124
140
  })();
125
141
  let ext = defaults.ext;
126
- const suggestion = fileSuggestions[i];
127
- if (suggestion && suggestion.includes(";")) {
128
- const parts = suggestion.split(";");
129
- const filenameCandidate = parts[0];
130
- const sizeCandidate = parseInt(parts[1]);
131
- if (extPattern.test(filenameCandidate)) {
132
- ext = filenameCandidate.match(extPattern)[0].slice(1);
142
+ const fileName = fileNames[i];
143
+ const fileSize = fileSizes[i];
144
+ if (fileSize) {
145
+ const sizeInBytes = parseInt(fileSize);
146
+ if (sizeInBytes > defaults.maxSize * 1024 * 1024) {
147
+ logger.warn(session.text("commands.cave.message.mediaSizeExceeded", [mediaType]));
148
+ continue;
133
149
  }
134
- if (sizeCandidate > defaults.maxSize * 1024 * 1024) {
135
- throw new Error(`${mediaType === "img" ? "图片" : "视频"}超出大小限制 (${defaults.maxSize}MB),实际大小为 ${(sizeCandidate / (1024 * 1024)).toFixed(2)}MB`);
136
- }
137
- } else if (suggestion && extPattern.test(suggestion)) {
138
- ext = suggestion.match(extPattern)[0].slice(1);
139
150
  }
140
- let filename;
141
- if (suggestion && suggestion.includes(";")) {
142
- const parts = suggestion.split(";");
143
- const srcFilename = path.basename(parts[0]);
144
- filename = `${caveId}_${srcFilename}`;
145
- } else if (suggestion && extPattern.test(suggestion)) {
146
- const srcFilename = path.basename(suggestion);
147
- filename = `${caveId}_${srcFilename}`;
148
- } else {
149
- filename = `${caveId}_${i + 1}.${ext}`;
151
+ if (fileName && extPattern.test(fileName)) {
152
+ ext = fileName.match(extPattern)[0].slice(1);
150
153
  }
151
- const targetPath = path.join(resourceDir, filename);
154
+ const finalFileName = fileName ? `${caveId}_${path.basename(fileName)}` : `${caveId}_${i + 1}.${ext}`;
155
+ const targetPath = path.join(resourceDir, finalFileName);
152
156
  const response = await ctx.http(processedUrl, {
153
157
  method: "GET",
154
158
  responseType: "arraybuffer",
@@ -161,28 +165,30 @@ async function saveMedia(urls, fileSuggestions, resourceDir, caveId, config, ctx
161
165
  });
162
166
  const fileBuffer = Buffer.from(response.data);
163
167
  await fs.promises.writeFile(targetPath, fileBuffer);
164
- savedFiles.push(filename);
168
+ savedFiles.push(finalFileName);
165
169
  } catch (error) {
166
- throw new Error(`操作失败: ${error.message}`);
170
+ const errorKey = mediaType === "img" ? "commands.cave.error.uploadImageFailed" : "commands.cave.error.uploadVideoFailed";
171
+ await sendTempMessage(session, errorKey);
172
+ return [];
167
173
  }
168
174
  }
169
175
  return savedFiles;
170
176
  }
171
177
  __name(saveMedia, "saveMedia");
172
- async function sendAuditMessage(ctx, config, cave, content) {
173
- const auditMessage = `待审核回声洞:
178
+ async function sendAuditMessage(ctx, config, cave, content, session) {
179
+ const auditMessage = `${session.text("commands.cave.audit.title")}
174
180
  ${content}
175
- 来自:${cave.contributor_number}`;
181
+ ${session.text("commands.cave.audit.from")}${cave.contributor_number}`;
176
182
  for (const managerId of config.manager) {
177
183
  try {
178
184
  await ctx.bots[0]?.sendPrivateMessage(managerId, auditMessage);
179
185
  } catch (error) {
180
- logger.error(`操作失败: ${error.message}`);
186
+ logger.error(session.text("commands.cave.audit.sendFailed", [managerId]));
181
187
  }
182
188
  }
183
189
  }
184
190
  __name(sendAuditMessage, "sendAuditMessage");
185
- async function handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data) {
191
+ async function handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data, session) {
186
192
  try {
187
193
  if (isApprove && data) {
188
194
  const caveWithoutIndex = {
@@ -200,42 +206,52 @@ async function handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data) {
200
206
  }
201
207
  return true;
202
208
  } catch (error) {
203
- throw new Error(`操作失败: ${error.message}`);
209
+ return sendTempMessage(session, "commands.cave.error.auditProcess", [error.message]);
204
210
  }
205
211
  }
206
212
  __name(handleSingleCaveAudit, "handleSingleCaveAudit");
207
- async function handleAudit(ctx, pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, targetId) {
208
- try {
209
- if (pendingData.length === 0) return "没有待审核回声洞";
210
- if (typeof targetId === "number") {
211
- const pendingIndex = pendingData.findIndex((item) => item.cave_id === targetId);
212
- if (pendingIndex === -1) return "未找到该待审核回声洞";
213
- const cave = pendingData[pendingIndex];
214
- const data2 = isApprove ? FileHandler.readJsonData(caveFilePath) : null;
215
- const success = await handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data2);
216
- if (!success) return "处理失败,请稍后重试";
217
- if (isApprove && data2) FileHandler.writeJsonData(caveFilePath, data2);
218
- pendingData.splice(pendingIndex, 1);
219
- FileHandler.writeJsonData(pendingFilePath, pendingData);
220
- const remainingCount = pendingData.length;
221
- if (remainingCount > 0) {
222
- const remainingIds = pendingData.map((c) => c.cave_id).join(", ");
223
- return `${isApprove ? "审核通过" : "拒绝"}成功,还有 ${remainingCount} 条待审核:[${remainingIds}]`;
224
- }
225
- return isApprove ? "已通过该回声洞" : "已拒绝该回声洞";
226
- }
227
- const data = isApprove ? FileHandler.readJsonData(caveFilePath) : null;
228
- let processedCount = 0;
229
- for (const cave of pendingData) {
230
- const success = await handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data);
231
- if (success) processedCount++;
213
+ async function handleAudit(ctx, pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, session, targetId) {
214
+ if (pendingData.length === 0) return sendMessage(session, "commands.cave.audit.noPending", [], true);
215
+ if (typeof targetId === "number") {
216
+ const pendingIndex = pendingData.findIndex((item) => item.cave_id === targetId);
217
+ if (pendingIndex === -1) return sendMessage(session, "commands.cave.audit.pendingNotFound", [], true);
218
+ const cave = pendingData[pendingIndex];
219
+ const data2 = isApprove ? FileHandler.readJsonData(caveFilePath, session) : null;
220
+ const auditResult = await handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data2, session);
221
+ if (typeof auditResult === "string") return auditResult;
222
+ if (isApprove && data2) FileHandler.writeJsonData(caveFilePath, data2, session);
223
+ pendingData.splice(pendingIndex, 1);
224
+ FileHandler.writeJsonData(pendingFilePath, pendingData, session);
225
+ const remainingCount = pendingData.length;
226
+ if (remainingCount > 0) {
227
+ const remainingIds = pendingData.map((c) => c.cave_id).join(", ");
228
+ const action = isApprove ? "auditPassed" : "auditRejected";
229
+ return sendMessage(session, "commands.cave.audit.pendingResult", [
230
+ session.text(`commands.cave.audit.${action}`),
231
+ remainingCount,
232
+ remainingIds
233
+ ], false);
232
234
  }
233
- if (isApprove && data) FileHandler.writeJsonData(caveFilePath, data);
234
- FileHandler.writeJsonData(pendingFilePath, []);
235
- return isApprove ? `已通过 ${processedCount}/${pendingData.length} 条回声洞` : `已拒绝 ${processedCount}/${pendingData.length} 条回声洞`;
236
- } catch (error) {
237
- throw new Error(`操作失败: ${error.message}`);
235
+ return sendMessage(
236
+ session,
237
+ isApprove ? "commands.cave.audit.auditPassed" : "commands.cave.audit.auditRejected",
238
+ [],
239
+ false
240
+ // 审核结果改为永久消息
241
+ );
238
242
  }
243
+ const data = isApprove ? FileHandler.readJsonData(caveFilePath, session) : null;
244
+ let processedCount = 0;
245
+ for (const cave of pendingData) {
246
+ await handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data, session) && processedCount++;
247
+ }
248
+ if (isApprove && data) FileHandler.writeJsonData(caveFilePath, data, session);
249
+ FileHandler.writeJsonData(pendingFilePath, [], session);
250
+ return sendMessage(session, "commands.cave.audit.batchAuditResult", [
251
+ isApprove ? "通过" : "拒绝",
252
+ processedCount,
253
+ pendingData.length
254
+ ], false);
239
255
  }
240
256
  __name(handleAudit, "handleAudit");
241
257
  function cleanElementsForSave(elements, keepIndex = false) {
@@ -248,6 +264,32 @@ function cleanElementsForSave(elements, keepIndex = false) {
248
264
  }));
249
265
  }
250
266
  __name(cleanElementsForSave, "cleanElementsForSave");
267
+ async function sendTempMessage(session, key, params = [], timeout = 1e4) {
268
+ const msg = await session.send(session.text(key, params));
269
+ setTimeout(async () => {
270
+ try {
271
+ await session.bot.deleteMessage(session.channelId, msg);
272
+ } catch (error) {
273
+ logger.error("Failed to delete message:", error);
274
+ }
275
+ }, timeout);
276
+ return "";
277
+ }
278
+ __name(sendTempMessage, "sendTempMessage");
279
+ async function sendMessage(session, key, params = [], isTemp = true, timeout = 1e4) {
280
+ const msg = await session.send(session.text(key, params));
281
+ if (isTemp) {
282
+ setTimeout(async () => {
283
+ try {
284
+ await session.bot.deleteMessage(session.channelId, msg);
285
+ } catch (error) {
286
+ logger.error("Failed to delete message:", error);
287
+ }
288
+ }, timeout);
289
+ }
290
+ return "";
291
+ }
292
+ __name(sendMessage, "sendMessage");
251
293
  async function extractMediaContent(originalContent) {
252
294
  const parsedTexts = originalContent.split(/<img[^>]+>|<video[^>]+>/g).map((t) => t.trim()).filter((t) => t);
253
295
  const textParts = [];
@@ -261,67 +303,65 @@ async function extractMediaContent(originalContent) {
261
303
  const imgMatches = originalContent.match(/<img[^>]+src="([^"]+)"[^>]*>/g) || [];
262
304
  imgMatches.forEach((img, idx) => {
263
305
  const srcMatch = img.match(/src="([^"]+)"/);
264
- const fileMatch = img.match(/file="([^"]+)"(?:\s+fileSize="([^"]+)")?/);
306
+ const fileName = img.match(/file="([^"]+)"/)?.[1];
307
+ const fileSize = img.match(/fileSize="([^"]+)"/)?.[1];
265
308
  if (srcMatch?.[1]) {
266
309
  imageUrls.push(srcMatch[1]);
267
- const suggestion = fileMatch ? fileMatch[2] ? `${fileMatch[1]};${fileMatch[2]}` : fileMatch[1] : void 0;
268
- imageElements.push({ type: "img", index: idx * 3 + 1, fileAttr: suggestion });
310
+ imageElements.push({ type: "img", index: idx * 3 + 1, fileName, fileSize });
269
311
  }
270
312
  });
271
313
  const videoMatches = originalContent.match(/<video[^>]+src="([^"]+)"[^>]*>/g) || [];
272
314
  videoMatches.forEach((video, idx) => {
273
315
  const srcMatch = video.match(/src="([^"]+)"/);
274
- const fileMatch = video.match(/file="([^"]+)"(?:\s+fileSize="([^"]+)")?/);
316
+ const fileName = video.match(/file="([^"]+)"/)?.[1];
317
+ const fileSize = video.match(/fileSize="([^"]+)"/)?.[1];
275
318
  if (srcMatch?.[1]) {
276
319
  videoUrls.push(srcMatch[1]);
277
- const suggestion = fileMatch ? fileMatch[2] ? `${fileMatch[1]};${fileMatch[2]}` : fileMatch[1] : void 0;
278
- videoElements.push({ type: "video", index: idx * 3 + 2, fileAttr: suggestion });
320
+ videoElements.push({ type: "video", index: idx * 3 + 2, fileName, fileSize });
279
321
  }
280
322
  });
281
323
  return { imageUrls, imageElements, videoUrls, videoElements, textParts };
282
324
  }
283
325
  __name(extractMediaContent, "extractMediaContent");
284
326
  async function buildMessage(cave, resourceDir, session) {
285
- let content = `回声洞 ——(${cave.cave_id})
286
- `;
327
+ let content = session.text("commands.cave.message.caveTitle", [cave.cave_id]) + "\n";
287
328
  const videoElements = [];
288
329
  for (const element of cave.elements) {
289
330
  if (element.type === "text") {
290
331
  content += element.content + "\n";
291
332
  } else if (element.type === "img" && element.file) {
292
- try {
293
- const fullImagePath = path.join(resourceDir, element.file);
294
- if (fs.existsSync(fullImagePath)) {
333
+ const fullImagePath = path.join(resourceDir, element.file);
334
+ if (fs.existsSync(fullImagePath)) {
335
+ try {
295
336
  const imageBuffer = fs.readFileSync(fullImagePath);
296
- const base64Image = imageBuffer.toString("base64");
297
- content += (0, import_koishi.h)("image", { src: `data:image/png;base64,${base64Image}` }) + "\n";
337
+ content += (0, import_koishi.h)("image", { src: `data:image/png;base64,${imageBuffer.toString("base64")}` }) + "\n";
338
+ } catch (error) {
339
+ content += session.text("commands.cave.error.mediaLoadFailed", ["图片"]) + "\n";
298
340
  }
299
- } catch (error) {
300
- throw new Error(`操作失败: ${error.message}`);
341
+ } else {
342
+ content += session.text("commands.cave.message.mediaInvalid", ["图片"]) + "\n";
301
343
  }
302
344
  } else if (element.type === "video" && element.file) {
303
345
  videoElements.push({ file: element.file });
304
346
  }
305
347
  }
306
348
  if (videoElements.length > 0 && session) {
307
- content += `[视频已发送]
308
- `;
349
+ content += session.text("commands.cave.message.videoSending") + "\n";
309
350
  for (const video of videoElements) {
310
- try {
311
- const fullVideoPath = path.join(resourceDir, video.file);
312
- if (fs.existsSync(fullVideoPath)) {
351
+ const fullVideoPath = path.join(resourceDir, video.file);
352
+ if (fs.existsSync(fullVideoPath)) {
353
+ try {
313
354
  const videoBuffer = fs.readFileSync(fullVideoPath);
314
- const base64Video = videoBuffer.toString("base64");
315
- session.send((0, import_koishi.h)("video", { src: `data:video/mp4;base64,${base64Video}` })).catch((error) => {
316
- throw new Error(`操作失败: ${error.message}`);
317
- });
355
+ session.send((0, import_koishi.h)("video", { src: `data:video/mp4;base64,${videoBuffer.toString("base64")}` })).catch((error) => logger.warn(session.text("commands.cave.error.videoSendFailed"), error.message));
356
+ } catch (error) {
357
+ content += session.text("commands.cave.error.mediaLoadFailed", ["视频"]) + "\n";
318
358
  }
319
- } catch (error) {
320
- throw new Error(`操作失败: ${error.message}`);
359
+ } else {
360
+ content += session.text("commands.cave.message.mediaInvalid", ["视频"]) + "\n";
321
361
  }
322
362
  }
323
363
  }
324
- content += `—— ${cave.contributor_name}`;
364
+ content += session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]);
325
365
  return content;
326
366
  }
327
367
  __name(buildMessage, "buildMessage");
@@ -340,18 +380,10 @@ async function initCavePaths(ctx) {
340
380
  }
341
381
  __name(initCavePaths, "initCavePaths");
342
382
  async function handleCaveAction(ctx, config, session, options, content, lastUsed) {
343
- const { caveFilePath, resourceDir, pendingFilePath } = await initCavePaths(ctx);
344
- async function processList() {
345
- try {
346
- let formatIds = function(ids) {
347
- const lines = [];
348
- for (let i = 0; i < ids.length; i += 10) {
349
- lines.push(ids.slice(i, i + 10).join(", "));
350
- }
351
- return lines.join("\n");
352
- };
353
- __name(formatIds, "formatIds");
354
- const caveData = FileHandler.readJsonData(caveFilePath);
383
+ try {
384
+ const { caveFilePath, resourceDir, pendingFilePath } = await initCavePaths(ctx);
385
+ async function processList() {
386
+ const caveData = FileHandler.readJsonData(caveFilePath, session);
355
387
  const caveDir = path.dirname(caveFilePath);
356
388
  const stats = {};
357
389
  for (const cave of caveData) {
@@ -360,114 +392,100 @@ async function handleCaveAction(ctx, config, session, options, content, lastUsed
360
392
  stats[cave.contributor_number].push(cave.cave_id);
361
393
  }
362
394
  const statFilePath = path.join(caveDir, "stat.json");
363
- try {
364
- fs.writeFileSync(statFilePath, JSON.stringify(stats, null, 2), "utf8");
365
- } catch (error) {
366
- throw new Error(`操作失败: ${error.message}`);
367
- }
368
- let queryId = null;
369
- if (typeof options.l === "string") {
370
- const match = String(options.l).match(/\d+/);
371
- if (match) queryId = match[0];
372
- } else if (content.length > 0) {
373
- const numberMatch = content.join(" ").match(/\d+/);
374
- if (numberMatch) queryId = numberMatch[0];
375
- }
376
- if (queryId) {
377
- if (stats[queryId]) {
378
- const count = stats[queryId].length;
379
- return `${queryId} 共计投稿 ${count} 项回声洞:
380
- ` + formatIds(stats[queryId]);
381
- } else {
382
- return `未找到投稿者 ${queryId}`;
383
- }
395
+ fs.writeFileSync(statFilePath, JSON.stringify(stats, null, 2), "utf8");
396
+ const lines = Object.entries(stats).map(([cid, ids]) => {
397
+ return session.text("commands.cave.list.totalItems", [cid, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
398
+ });
399
+ const totalSubmissions = Object.values(stats).reduce((sum, arr) => sum + arr.length, 0);
400
+ if (config.enablePagination) {
401
+ const itemsPerPage = config.itemsPerPage;
402
+ const totalPages = Math.max(1, Math.ceil(lines.length / itemsPerPage));
403
+ let query = (content[0] || String(options.l) || "").trim();
404
+ let pageNum = parseInt(query, 10);
405
+ if (isNaN(pageNum) || pageNum < 1) pageNum = 1;
406
+ if (pageNum > totalPages) pageNum = totalPages;
407
+ const start = (pageNum - 1) * itemsPerPage;
408
+ const paginatedLines = lines.slice(start, start + itemsPerPage);
409
+ return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + paginatedLines.join("\n") + "\n" + session.text("commands.cave.list.pageInfo", [pageNum, totalPages]);
384
410
  } else {
385
- let total = 0;
386
- const lines = Object.entries(stats).map(([cid, ids]) => {
387
- total += ids.length;
388
- return `${cid} 共计投稿 ${ids.length} 项回声洞:
389
- ` + formatIds(ids);
390
- });
391
- return `==回声洞共计投稿 ${total} 项==
392
- ` + lines.join("\n");
411
+ return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + lines.join("\n");
393
412
  }
394
- } catch (error) {
395
- return `操作失败: ${error.message}`;
396
413
  }
397
- }
398
- __name(processList, "processList");
399
- async function processAudit() {
400
- try {
401
- const pendingData = FileHandler.readJsonData(pendingFilePath);
414
+ __name(processList, "processList");
415
+ async function processAudit() {
416
+ const pendingData = FileHandler.readJsonData(pendingFilePath, session);
402
417
  const isApprove = Boolean(options.p);
403
418
  if (options.p === true && content[0] === "all" || options.d === true && content[0] === "all") {
404
- return await handleAudit(ctx, pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath);
419
+ return await handleAudit(ctx, pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, session);
405
420
  }
406
421
  const id = parseInt(content[0] || (typeof options.p === "string" ? options.p : "") || (typeof options.d === "string" ? options.d : ""));
407
- if (isNaN(id)) return "请输入正确的回声洞序号";
408
- return await handleAudit(ctx, pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, id);
409
- } catch (error) {
410
- return `操作失败: ${error.message}`;
422
+ if (isNaN(id)) {
423
+ return sendMessage(session, "commands.cave.error.invalidId", [], true);
424
+ }
425
+ return sendMessage(session, await handleAudit(ctx, pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, session, id), [], true);
411
426
  }
412
- }
413
- __name(processAudit, "processAudit");
414
- async function processView() {
415
- try {
427
+ __name(processAudit, "processAudit");
428
+ async function processView() {
416
429
  const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
417
- if (isNaN(caveId)) return "请输入正确的回声洞序号";
430
+ if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
418
431
  const data = FileHandler.readJsonData(
419
432
  caveFilePath,
433
+ session,
420
434
  (item) => item && typeof item.cave_id === "number" && Array.isArray(item.elements) && item.elements.every(
421
435
  (el) => el.type === "text" && typeof el.content === "string" || el.type === "img" && typeof el.file === "string" || el.type === "video" && typeof el.file === "string"
422
436
  ) && typeof item.contributor_number === "string" && typeof item.contributor_name === "string"
423
437
  );
424
438
  const cave = data.find((item) => item.cave_id === caveId);
425
- if (!cave) return "未找到该序号的回声洞";
439
+ if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
426
440
  const caveContent = await buildMessage(cave, resourceDir, session);
427
441
  return caveContent;
428
- } catch (error) {
429
- return `操作失败: ${error.message}`;
430
442
  }
431
- }
432
- __name(processView, "processView");
433
- async function processRandom() {
434
- const data = FileHandler.readJsonData(
435
- caveFilePath,
436
- (item) => item && typeof item.cave_id === "number" && Array.isArray(item.elements) && item.elements.every(
437
- (el) => el.type === "text" && typeof el.content === "string" || el.type === "img" && typeof el.file === "string"
438
- ) && typeof item.contributor_number === "string" && typeof item.contributor_name === "string"
439
- );
440
- if (data.length === 0) return "暂无回声洞可用";
441
- const guildId = session.guildId;
442
- const now = Date.now();
443
- const lastCall = lastUsed.get(guildId) || 0;
444
- const isManager = config.manager.includes(session.userId);
445
- if (!isManager && now - lastCall < config.number * 1e3) {
446
- const waitTime = Math.ceil((config.number * 1e3 - (now - lastCall)) / 1e3);
447
- return `群聊冷却中...请${waitTime}秒后再试`;
443
+ __name(processView, "processView");
444
+ async function processRandom() {
445
+ try {
446
+ const data = FileHandler.readJsonData(
447
+ caveFilePath,
448
+ session,
449
+ (item) => item && typeof item.cave_id === "number" && Array.isArray(item.elements) && item.elements.every(
450
+ (el) => el.type === "text" && typeof el.content === "string" || el.type === "img" && typeof el.file === "string"
451
+ ) && typeof item.contributor_number === "string" && typeof item.contributor_name === "string"
452
+ );
453
+ if (data.length === 0) {
454
+ return sendMessage(session, "commands.cave.error.noCave", [], true);
455
+ }
456
+ const guildId = session.guildId;
457
+ const now = Date.now();
458
+ const lastCall = lastUsed.get(guildId) || 0;
459
+ const isManager = config.manager.includes(session.userId);
460
+ if (!isManager && now - lastCall < config.number * 1e3) {
461
+ const waitTime = Math.ceil((config.number * 1e3 - (now - lastCall)) / 1e3);
462
+ return sendMessage(session, "commands.cave.message.cooldown", [waitTime], true);
463
+ }
464
+ if (!isManager) lastUsed.set(guildId, now);
465
+ const cave = (() => {
466
+ const validCaves = data.filter((cave2) => cave2.elements && cave2.elements.length > 0);
467
+ if (!validCaves.length) return void 0;
468
+ const randomIndex = Math.floor(Math.random() * validCaves.length);
469
+ return validCaves[randomIndex];
470
+ })();
471
+ return cave ? buildMessage(cave, resourceDir, session) : sendMessage(session, "commands.cave.error.getCave", [], true);
472
+ } catch (error) {
473
+ return sendMessage(session, "commands.cave.error.commandProcess", [error.message], true);
474
+ }
448
475
  }
449
- if (!isManager) lastUsed.set(guildId, now);
450
- const cave = (() => {
451
- const validCaves = data.filter((cave2) => cave2.elements && cave2.elements.length > 0);
452
- if (!validCaves.length) return void 0;
453
- const randomIndex = Math.floor(Math.random() * validCaves.length);
454
- return validCaves[randomIndex];
455
- })();
456
- return cave ? buildMessage(cave, resourceDir, session) : "获取回声洞失败";
457
- }
458
- __name(processRandom, "processRandom");
459
- async function processDelete() {
460
- try {
476
+ __name(processRandom, "processRandom");
477
+ async function processDelete() {
461
478
  const caveId = parseInt(content[0] || (typeof options.r === "string" ? options.r : ""));
462
- if (isNaN(caveId)) return "请输入正确的回声洞序号";
479
+ if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
463
480
  const data = FileHandler.readJsonData(
464
481
  caveFilePath,
482
+ session,
465
483
  (item) => item && typeof item.cave_id === "number"
466
484
  );
467
- const pendingData = FileHandler.readJsonData(pendingFilePath);
485
+ const pendingData = FileHandler.readJsonData(pendingFilePath, session);
468
486
  const index = data.findIndex((item) => item.cave_id === caveId);
469
487
  const pendingIndex = pendingData.findIndex((item) => item.cave_id === caveId);
470
- if (index === -1 && pendingIndex === -1) return "未找到该序号的回声洞";
488
+ if (index === -1 && pendingIndex === -1) return sendMessage(session, "commands.cave.error.notFound", [], true);
471
489
  let targetCave;
472
490
  let isPending = false;
473
491
  if (index !== -1) {
@@ -477,130 +495,163 @@ async function handleCaveAction(ctx, config, session, options, content, lastUsed
477
495
  isPending = true;
478
496
  }
479
497
  if (targetCave.contributor_number !== session.userId && !config.manager.includes(session.userId)) {
480
- return "不可删除他人添加的回声洞!";
498
+ return sendMessage(session, "commands.cave.remove.noPermission", [], true);
481
499
  }
482
500
  const caveContent = await buildMessage(targetCave, resourceDir, session);
483
501
  if (targetCave.elements) {
484
- try {
485
- for (const element of targetCave.elements) {
486
- if ((element.type === "img" || element.type === "video") && element.file) {
487
- const fullPath = path.join(resourceDir, element.file);
488
- if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
489
- }
502
+ for (const element of targetCave.elements) {
503
+ if ((element.type === "img" || element.type === "video") && element.file) {
504
+ const fullPath = path.join(resourceDir, element.file);
505
+ if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
490
506
  }
491
- } catch (error) {
492
- return `操作失败: ${error.message}`;
493
507
  }
494
508
  }
495
509
  if (isPending) {
496
510
  pendingData.splice(pendingIndex, 1);
497
- FileHandler.writeJsonData(pendingFilePath, pendingData);
498
- return `已删除(待审核)
511
+ FileHandler.writeJsonData(pendingFilePath, pendingData, session);
512
+ const deleteStatus = isPending ? session.text("commands.cave.remove.deletePending") : "";
513
+ const deleteMessage = session.text("commands.cave.remove.deleted");
514
+ return `${deleteMessage}${deleteStatus}
499
515
  ${caveContent}`;
500
516
  } else {
501
517
  data.splice(index, 1);
502
- FileHandler.writeJsonData(caveFilePath, data);
503
- return `已删除
504
- ${caveContent}`;
518
+ FileHandler.writeJsonData(caveFilePath, data, session);
519
+ const deleteStatus = isPending ? session.text("commands.cave.remove.deletePending") : "";
520
+ const deleteMessage = session.text("commands.cave.remove.deleted");
521
+ return `${deleteMessage}${deleteStatus}${caveContent}`;
505
522
  }
506
- } catch (error) {
507
- return `操作失败: ${error.message}`;
508
523
  }
509
- }
510
- __name(processDelete, "processDelete");
511
- async function processAdd() {
512
- try {
513
- let originalContent = session.quote?.content || session.content;
514
- const prefixes = Array.isArray(session.app.config.prefix) ? session.app.config.prefix : [session.app.config.prefix];
515
- const nicknames = Array.isArray(session.app.config.nickname) ? session.app.config.nickname : session.app.config.nickname ? [session.app.config.nickname] : [];
516
- const allTriggers = [...prefixes, ...nicknames];
517
- const triggerPattern = allTriggers.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
518
- const commandPattern = new RegExp(`^(?:${triggerPattern}).*?-a\\s*`);
519
- originalContent = originalContent.replace(commandPattern, "");
520
- let { imageUrls, imageElements, videoUrls, videoElements, textParts } = await extractMediaContent(originalContent);
521
- if (textParts.length === 0 && imageUrls.length === 0 && videoUrls.length === 0) {
522
- await session.send("请在一分钟内发送你要添加的内容");
523
- const reply = await session.prompt({ timeout: 6e4 });
524
- if (!reply || reply.trim() === "") {
525
- return "操作超时,放弃本次添加";
524
+ __name(processDelete, "processDelete");
525
+ async function processAdd() {
526
+ try {
527
+ let originalContent = content.join(" ") || "";
528
+ if (session.content) {
529
+ originalContent = session.content;
526
530
  }
527
- const replyResult = await extractMediaContent(reply);
528
- imageUrls = replyResult.imageUrls;
529
- imageElements = replyResult.imageElements;
530
- videoUrls = replyResult.videoUrls;
531
- videoElements = replyResult.videoElements;
532
- textParts = replyResult.textParts;
533
- }
534
- if (videoUrls.length > 0 && !config.allowVideo) {
535
- return "已关闭上传视频功能";
536
- }
537
- const pendingData = FileHandler.readJsonData(pendingFilePath);
538
- const data = FileHandler.readJsonData(caveFilePath, (item) => item && typeof item.cave_id === "number");
539
- const maxDataId = data.length > 0 ? Math.max(...data.map((item) => item.cave_id)) : 0;
540
- const maxPendingId = pendingData.length > 0 ? Math.max(...pendingData.map((item) => item.cave_id)) : 0;
541
- const caveId = Math.max(maxDataId, maxPendingId) + 1;
542
- let savedImages = [];
543
- if (imageUrls.length > 0) {
544
- try {
545
- const fileSuggestions = imageElements.map((el) => el.fileAttr);
546
- savedImages = await saveMedia(imageUrls, fileSuggestions, resourceDir, caveId, config, ctx, "img");
547
- } catch (error) {
548
- return `操作失败: ${error.message}`;
531
+ if (session.quote?.content) {
532
+ originalContent = originalContent ? `${originalContent}
533
+ ${session.quote.content}` : session.quote.content;
549
534
  }
550
- }
551
- let savedVideos = [];
552
- if (videoUrls.length > 0) {
553
- try {
554
- const fileSuggestions = videoElements.map((el) => el.fileAttr);
555
- savedVideos = await saveMedia(videoUrls, fileSuggestions, resourceDir, caveId, config, ctx, "video");
556
- } catch (error) {
557
- return `操作失败: ${error.message}`;
535
+ const prefixes = Array.isArray(session.app.config.prefix) ? session.app.config.prefix : [session.app.config.prefix];
536
+ const nicknames = Array.isArray(session.app.config.nickname) ? session.app.config.nickname : session.app.config.nickname ? [session.app.config.nickname] : [];
537
+ const allTriggers = [...prefixes, ...nicknames];
538
+ const triggerPattern = allTriggers.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
539
+ const commandPattern = new RegExp(`^(?:${triggerPattern}).*?-a\\s*`);
540
+ originalContent = originalContent.replace(commandPattern, "");
541
+ let { imageUrls, imageElements, videoUrls, videoElements, textParts } = await extractMediaContent(originalContent);
542
+ if (textParts.length === 0 && imageUrls.length === 0 && videoUrls.length === 0) {
543
+ await sendMessage(session, "commands.cave.add.noContent", [], true);
544
+ const reply = await session.prompt({ timeout: 6e4 });
545
+ if (!reply || reply.trim() === "") {
546
+ return sendMessage(session, "commands.cave.add.operationTimeout", [], true);
547
+ }
548
+ const replyResult = await extractMediaContent(reply);
549
+ imageUrls = replyResult.imageUrls;
550
+ imageElements = replyResult.imageElements;
551
+ videoUrls = replyResult.videoUrls;
552
+ videoElements = replyResult.videoElements;
553
+ textParts = replyResult.textParts;
558
554
  }
559
- }
560
- const elements = [];
561
- elements.push(...textParts);
562
- savedImages.forEach((file, idx) => {
563
- if (imageElements[idx]) {
564
- elements.push({ ...imageElements[idx], type: "img", file });
555
+ if (videoUrls.length > 0 && !config.allowVideo) {
556
+ return sendMessage(session, "commands.cave.add.videoDisabled", [], true);
565
557
  }
566
- });
567
- savedVideos.forEach((file, idx) => {
568
- if (videoElements[idx]) {
569
- elements.push({ ...videoElements[idx], type: "video", file });
558
+ const pendingData = FileHandler.readJsonData(pendingFilePath, session);
559
+ const data = FileHandler.readJsonData(caveFilePath, session, (item) => item && typeof item.cave_id === "number");
560
+ const usedIds = /* @__PURE__ */ new Set([...data.map((item) => item.cave_id), ...pendingData.map((item) => item.cave_id)]);
561
+ let caveId = 1;
562
+ while (usedIds.has(caveId)) {
563
+ caveId++;
570
564
  }
571
- });
572
- elements.sort((a, b) => a.index - b.index);
573
- let contributorName = session.username;
574
- if (ctx.database) {
575
- try {
576
- const userInfo = await ctx.database.getUser(session.platform, session.userId);
577
- contributorName = userInfo?.nickname || session.username;
578
- } catch (error) {
579
- throw new Error(`操作失败: ${error.message}`);
565
+ let savedImages = [];
566
+ if (imageUrls.length > 0) {
567
+ try {
568
+ const imageFileNames = imageElements.map((el) => el.fileName);
569
+ const imageFileSizes = imageElements.map((el) => el.fileSize);
570
+ savedImages = await saveMedia(
571
+ imageUrls,
572
+ imageFileNames,
573
+ imageFileSizes,
574
+ resourceDir,
575
+ caveId,
576
+ config,
577
+ ctx,
578
+ "img",
579
+ session
580
+ );
581
+ } catch (error) {
582
+ return sendMessage(session, "commands.cave.error.uploadImageFailed", [], true);
583
+ }
580
584
  }
585
+ let savedVideos = [];
586
+ if (videoUrls.length > 0) {
587
+ try {
588
+ const videoFileNames = videoElements.map((el) => el.fileName);
589
+ const videoFileSizes = videoElements.map((el) => el.fileSize);
590
+ savedVideos = await saveMedia(
591
+ videoUrls,
592
+ videoFileNames,
593
+ videoFileSizes,
594
+ resourceDir,
595
+ caveId,
596
+ config,
597
+ ctx,
598
+ "video",
599
+ session
600
+ );
601
+ } catch (error) {
602
+ return sendMessage(session, "commands.cave.error.uploadVideoFailed", [], true);
603
+ }
604
+ }
605
+ const elements = [];
606
+ elements.push(...textParts);
607
+ savedImages.forEach((file, idx) => {
608
+ if (imageElements[idx]) {
609
+ elements.push({ ...imageElements[idx], type: "img", file });
610
+ }
611
+ });
612
+ savedVideos.forEach((file, idx) => {
613
+ if (videoElements[idx]) {
614
+ elements.push({ ...videoElements[idx], type: "video", file });
615
+ }
616
+ });
617
+ elements.sort((a, b) => a.index - b.index);
618
+ let contributorName = session.username;
619
+ if (ctx.database) {
620
+ try {
621
+ const userInfo = await ctx.database.getUser(session.platform, session.userId);
622
+ contributorName = userInfo?.nickname || session.username;
623
+ } catch (error) {
624
+ return sendMessage(
625
+ session,
626
+ "commands.cave.error.userInfo",
627
+ [error.message],
628
+ true
629
+ );
630
+ }
631
+ }
632
+ const newCave = {
633
+ cave_id: caveId,
634
+ elements: cleanElementsForSave(elements, true),
635
+ contributor_number: session.userId,
636
+ contributor_name: contributorName
637
+ };
638
+ const bypassAudit = config.whitelist.includes(session.userId) || session.guildId && config.whitelist.includes(session.guildId) || session.channelId && config.whitelist.includes(session.channelId);
639
+ if (config.enableAudit && !bypassAudit) {
640
+ pendingData.push({ ...newCave, elements: cleanElementsForSave(elements, true) });
641
+ FileHandler.writeJsonData(pendingFilePath, pendingData, session);
642
+ await sendAuditMessage(ctx, config, newCave, await buildMessage(newCave, resourceDir, session), session);
643
+ return sendMessage(session, "commands.cave.add.submitPending", [caveId], false);
644
+ } else {
645
+ const caveWithoutIndex = { ...newCave, elements: cleanElementsForSave(elements, false) };
646
+ data.push(caveWithoutIndex);
647
+ FileHandler.writeJsonData(caveFilePath, data, session);
648
+ return sendMessage(session, "commands.cave.add.addSuccess", [caveId], false);
649
+ }
650
+ } catch (error) {
651
+ return sendMessage(session, "commands.cave.error.commandProcess", [error.message], true);
581
652
  }
582
- const newCave = {
583
- cave_id: caveId,
584
- elements: cleanElementsForSave(elements, true),
585
- contributor_number: session.userId,
586
- contributor_name: contributorName
587
- };
588
- if (config.enableAudit) {
589
- pendingData.push({ ...newCave, elements: cleanElementsForSave(elements, true) });
590
- FileHandler.writeJsonData(pendingFilePath, pendingData);
591
- await sendAuditMessage(ctx, config, newCave, await buildMessage(newCave, resourceDir, session));
592
- return `已提交审核,序号为 (${caveId})`;
593
- }
594
- const caveWithoutIndex = { ...newCave, elements: cleanElementsForSave(elements, false) };
595
- data.push(caveWithoutIndex);
596
- FileHandler.writeJsonData(caveFilePath, data);
597
- return `添加成功!序号为 (${caveId})`;
598
- } catch (error) {
599
- return `操作失败: ${error.message}`;
600
653
  }
601
- }
602
- __name(processAdd, "processAdd");
603
- try {
654
+ __name(processAdd, "processAdd");
604
655
  if (options.l !== void 0) return await processList();
605
656
  if (options.p || options.d) return await processAudit();
606
657
  if (options.g) return await processView();
@@ -608,7 +659,8 @@ ${caveContent}`;
608
659
  if (options.a) return await processAdd();
609
660
  return await processRandom();
610
661
  } catch (error) {
611
- return `操作失败: ${error.message.replace(/^操作失败: /, "")}`;
662
+ logger.error(error);
663
+ return sendMessage(session, "commands.cave.error.commandProcess", [error.message], true);
612
664
  }
613
665
  }
614
666
  __name(handleCaveAction, "handleCaveAction");
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.1.0",
4
+ "version": "1.1.1",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],