koishi-plugin-best-cave 1.1.4 → 1.2.0

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 (3) hide show
  1. package/lib/index.d.ts +25 -8
  2. package/lib/index.js +782 -511
  3. package/package.json +1 -1
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: "管理员", 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}文件大小超过限制" } } } };
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}文件大小超过限制", 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: "未找到该回声洞" }, 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: "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" } } } };
43
+ module2.exports = { _config: { manager: "Administrators", blacklist: "Blacklist (Users)", whitelist: "Audit Whitelist (Users/Groups/Channels)", number: "Cooldown Time (seconds)", enableAudit: "Enable Audit", allowVideo: "Allow Video Upload", videoMaxSize: "Maximum Video Size (MB)", imageMaxSize: "Maximum Image Size (MB)", enablePagination: "Enable Statistics Pagination", itemsPerPage: "Items Per Page" }, commands: { cave: { description: "Echo Cave", usage: "Supports adding, drawing, viewing, and managing echo cave entries", examples: "Use cave to randomly draw an entry\nUse -a to add directly or add by reference\nUse -g to view specific entry\nUse -r to delete specific entry", options: { a: "Add entry", g: "View entry", r: "Delete entry", 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 cancelled", videoDisabled: "Video upload is not allowed", submitPending: "Submission successful, ID is ({0})", addSuccess: "Addition successful, 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' entries", deletePending: "Delete (pending audit)", deleted: "Deleted" }, list: { pageInfo: "Page {0} / {1}", header: "Currently there are {0} entries:", totalItems: "User {0} has submitted {1} entries:", idsLine: "{0}" }, audit: { noPending: "No pending entries", pendingNotFound: "Pending entry not found", pendingResult: "{0}, {1} pending entries remaining: [{2}]", auditPassed: "Approved", auditRejected: "Rejected", batchAuditResult: "Has {0} {1}/{2} entries", title: "Pending Entries:", from: "Submitted by:", sendFailed: "Failed to send audit message, cannot contact administrator {0}" }, error: { noContent: "Entry content is empty", getCave: "Failed to get entry", noCave: "No entries found", invalidId: "Please enter a valid entry ID", notFound: "Entry not found" }, message: { blacklisted: "You have been blacklisted", managerOnly: "This operation is restricted to administrators", 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
 
@@ -49,8 +49,6 @@ var src_exports = {};
49
49
  __export(src_exports, {
50
50
  Config: () => Config,
51
51
  apply: () => apply,
52
- handleCaveAction: () => handleCaveAction,
53
- initCavePaths: () => initCavePaths,
54
52
  inject: () => inject,
55
53
  name: () => name
56
54
  });
@@ -78,18 +76,382 @@ var Config = import_koishi.Schema.object({
78
76
  async function apply(ctx, config) {
79
77
  ctx.i18n.define("zh-CN", require_zh_CN());
80
78
  ctx.i18n.define("en-US", require_en_US());
81
- const { caveFilePath, resourceDir, pendingFilePath } = await initCavePaths(ctx);
79
+ const dataDir = path.join(ctx.baseDir, "data");
80
+ const caveDir = path.join(dataDir, "cave");
81
+ const caveFilePath = path.join(caveDir, "cave.json");
82
+ const resourceDir = path.join(caveDir, "resources");
83
+ const pendingFilePath = path.join(caveDir, "pending.json");
84
+ await FileHandler.ensureDirectory(dataDir);
85
+ await FileHandler.ensureDirectory(caveDir);
86
+ await FileHandler.ensureDirectory(resourceDir);
87
+ await FileHandler.ensureJsonFile(caveFilePath);
88
+ await FileHandler.ensureJsonFile(pendingFilePath);
89
+ const idManager = new IdManager(ctx.baseDir);
90
+ await idManager.initialize(caveFilePath, pendingFilePath);
82
91
  const lastUsed = /* @__PURE__ */ new Map();
92
+ async function processList(session, config2, userId, pageNum = 1) {
93
+ const stats = idManager.getStats();
94
+ if (userId && userId in stats) {
95
+ const ids = stats[userId];
96
+ return session.text("commands.cave.list.totalItems", [userId, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
97
+ }
98
+ const lines = Object.entries(stats).map(([cid, ids]) => {
99
+ return session.text("commands.cave.list.totalItems", [cid, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
100
+ });
101
+ const totalSubmissions = Object.values(stats).reduce((sum, arr) => sum + arr.length, 0);
102
+ if (config2.enablePagination) {
103
+ const itemsPerPage = config2.itemsPerPage;
104
+ const totalPages = Math.max(1, Math.ceil(lines.length / itemsPerPage));
105
+ pageNum = Math.min(Math.max(1, pageNum), totalPages);
106
+ const start = (pageNum - 1) * itemsPerPage;
107
+ const paginatedLines = lines.slice(start, start + itemsPerPage);
108
+ return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + paginatedLines.join("\n") + "\n" + session.text("commands.cave.list.pageInfo", [pageNum, totalPages]);
109
+ } else {
110
+ return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + lines.join("\n");
111
+ }
112
+ }
113
+ __name(processList, "processList");
114
+ async function processAudit(ctx2, pendingFilePath2, caveFilePath2, resourceDir2, session, options, content) {
115
+ const pendingData = await FileHandler.readJsonData(pendingFilePath2);
116
+ const isApprove = Boolean(options.p);
117
+ if (options.p === true && content[0] === "all" || options.d === true && content[0] === "all") {
118
+ return await handleAudit(ctx2, pendingData, isApprove, caveFilePath2, resourceDir2, pendingFilePath2, session);
119
+ }
120
+ const id = parseInt(content[0] || (typeof options.p === "string" ? options.p : "") || (typeof options.d === "string" ? options.d : ""));
121
+ if (isNaN(id)) {
122
+ return sendMessage(session, "commands.cave.error.invalidId", [], true);
123
+ }
124
+ return sendMessage(session, await handleAudit(ctx2, pendingData, isApprove, caveFilePath2, resourceDir2, pendingFilePath2, session, id), [], true);
125
+ }
126
+ __name(processAudit, "processAudit");
127
+ async function processView(caveFilePath2, resourceDir2, session, options, content, config2) {
128
+ if (!await checkCooldown(session, config2)) {
129
+ return "";
130
+ }
131
+ const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
132
+ if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
133
+ const data = await FileHandler.readJsonData(caveFilePath2);
134
+ const cave = data.find((item) => item.cave_id === caveId);
135
+ if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
136
+ return buildMessage(cave, resourceDir2, session);
137
+ }
138
+ __name(processView, "processView");
139
+ async function processRandom(caveFilePath2, resourceDir2, session, config2) {
140
+ const data = await FileHandler.readJsonData(caveFilePath2);
141
+ if (data.length === 0) {
142
+ return sendMessage(session, "commands.cave.error.noCave", [], true);
143
+ }
144
+ if (!await checkCooldown(session, config2)) {
145
+ return "";
146
+ }
147
+ const cave = (() => {
148
+ const validCaves = data.filter((cave2) => cave2.elements && cave2.elements.length > 0);
149
+ if (!validCaves.length) return void 0;
150
+ const randomIndex = Math.floor(Math.random() * validCaves.length);
151
+ return validCaves[randomIndex];
152
+ })();
153
+ return cave ? buildMessage(cave, resourceDir2, session) : sendMessage(session, "commands.cave.error.getCave", [], true);
154
+ }
155
+ __name(processRandom, "processRandom");
156
+ async function processDelete(caveFilePath2, resourceDir2, pendingFilePath2, session, config2, options, content) {
157
+ const caveId = parseInt(content[0] || (typeof options.r === "string" ? options.r : ""));
158
+ if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
159
+ const data = await FileHandler.readJsonData(caveFilePath2);
160
+ const pendingData = await FileHandler.readJsonData(pendingFilePath2);
161
+ const targetInData = data.find((item) => item.cave_id === caveId);
162
+ const targetInPending = pendingData.find((item) => item.cave_id === caveId);
163
+ if (!targetInData && !targetInPending) {
164
+ return sendMessage(session, "commands.cave.error.notFound", [], true);
165
+ }
166
+ const targetCave = targetInData || targetInPending;
167
+ const isPending = !targetInData;
168
+ if (targetCave.contributor_number !== session.userId && !config2.manager.includes(session.userId)) {
169
+ return sendMessage(session, "commands.cave.remove.noPermission", [], true);
170
+ }
171
+ const caveContent = await buildMessage(targetCave, resourceDir2, session);
172
+ if (targetCave.elements) {
173
+ for (const element of targetCave.elements) {
174
+ if ((element.type === "img" || element.type === "video") && element.file) {
175
+ const fullPath = path.join(resourceDir2, element.file);
176
+ if (fs.existsSync(fullPath)) {
177
+ await fs.promises.unlink(fullPath);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ if (isPending) {
183
+ const newPendingData = pendingData.filter((item) => item.cave_id !== caveId);
184
+ await FileHandler.writeJsonData(pendingFilePath2, newPendingData);
185
+ } else {
186
+ const newData = data.filter((item) => item.cave_id !== caveId);
187
+ await FileHandler.writeJsonData(caveFilePath2, newData);
188
+ await idManager.removeStat(targetCave.contributor_number, caveId);
189
+ }
190
+ await idManager.markDeleted(caveId);
191
+ const deleteStatus = isPending ? session.text("commands.cave.remove.deletePending") : "";
192
+ const deleteMessage = session.text("commands.cave.remove.deleted");
193
+ return `${deleteMessage}${deleteStatus}${caveContent}`;
194
+ }
195
+ __name(processDelete, "processDelete");
196
+ async function processAdd(ctx2, config2, caveFilePath2, resourceDir2, pendingFilePath2, session, content) {
197
+ try {
198
+ const inputContent = content.length > 0 ? content.join("\n") : await (async () => {
199
+ await sendMessage(session, "commands.cave.add.noContent", [], true);
200
+ const reply = await session.prompt({ timeout: 6e4 });
201
+ if (!reply) throw new Error(session.text("commands.cave.add.operationTimeout"));
202
+ return reply;
203
+ })();
204
+ const caveId = await idManager.getNextId();
205
+ if (inputContent.includes("/app/.config/QQ/")) {
206
+ return sendMessage(session, "commands.cave.add.localFileNotAllowed", [], true);
207
+ }
208
+ const bypassAudit = config2.whitelist.includes(session.userId) || config2.whitelist.includes(session.guildId) || config2.whitelist.includes(session.channelId);
209
+ const { imageUrls, imageElements, videoUrls, videoElements, textParts } = await extractMediaContent(inputContent, config2, session);
210
+ if (videoUrls.length > 0 && !config2.allowVideo) {
211
+ return sendMessage(session, "commands.cave.add.videoDisabled", [], true);
212
+ }
213
+ const [savedImages, savedVideos] = await Promise.all([
214
+ imageUrls.length > 0 ? saveMedia(
215
+ imageUrls,
216
+ imageElements.map((el) => el.fileName),
217
+ resourceDir2,
218
+ caveId,
219
+ "img",
220
+ ctx2,
221
+ session
222
+ ) : [],
223
+ videoUrls.length > 0 ? saveMedia(
224
+ videoUrls,
225
+ videoElements.map((el) => el.fileName),
226
+ resourceDir2,
227
+ caveId,
228
+ "video",
229
+ ctx2,
230
+ session
231
+ ) : []
232
+ ]);
233
+ const newCave = {
234
+ cave_id: caveId,
235
+ elements: [
236
+ ...textParts,
237
+ ...imageElements.map((el, idx) => ({
238
+ ...el,
239
+ file: savedImages[idx],
240
+ // 保持原始文本和图片的相对位置
241
+ index: el.index
242
+ }))
243
+ ].sort((a, b) => a.index - b.index),
244
+ contributor_number: session.userId,
245
+ contributor_name: session.username
246
+ };
247
+ if (videoUrls.length > 0 && savedVideos.length > 0) {
248
+ newCave.elements.push({
249
+ type: "video",
250
+ file: savedVideos[0],
251
+ index: Number.MAX_SAFE_INTEGER
252
+ // 确保视频总是在最后
253
+ });
254
+ }
255
+ if (config2.enableAudit && !bypassAudit) {
256
+ const pendingData = await FileHandler.readJsonData(pendingFilePath2);
257
+ pendingData.push(newCave);
258
+ await Promise.all([
259
+ FileHandler.writeJsonData(pendingFilePath2, pendingData),
260
+ sendAuditMessage(ctx2, config2, newCave, await buildMessage(newCave, resourceDir2, session), session)
261
+ ]);
262
+ return sendMessage(session, "commands.cave.add.submitPending", [caveId], false);
263
+ }
264
+ const data = await FileHandler.readJsonData(caveFilePath2);
265
+ data.push({
266
+ ...newCave,
267
+ elements: cleanElementsForSave(newCave.elements, false)
268
+ });
269
+ await FileHandler.writeJsonData(caveFilePath2, data);
270
+ await idManager.addStat(session.userId, caveId);
271
+ return sendMessage(session, "commands.cave.add.addSuccess", [caveId], false);
272
+ } catch (error) {
273
+ logger.error(`Failed to process add command: ${error.message}`);
274
+ return sendMessage(session, `commands.cave.error.${error.code || "unknown"}`, [], true);
275
+ }
276
+ }
277
+ __name(processAdd, "processAdd");
278
+ async function handleAudit(ctx2, pendingData, isApprove, caveFilePath2, resourceDir2, pendingFilePath2, session, targetId) {
279
+ if (pendingData.length === 0) {
280
+ return sendMessage(session, "commands.cave.audit.noPending", [], true);
281
+ }
282
+ if (typeof targetId === "number") {
283
+ const targetCave = pendingData.find((item) => item.cave_id === targetId);
284
+ if (!targetCave) {
285
+ return sendMessage(session, "commands.cave.audit.pendingNotFound", [], true);
286
+ }
287
+ const newPendingData = pendingData.filter((item) => item.cave_id !== targetId);
288
+ if (isApprove) {
289
+ const oldCaveData = await FileHandler.readJsonData(caveFilePath2);
290
+ const newCaveData = [...oldCaveData, {
291
+ ...targetCave,
292
+ cave_id: targetId,
293
+ // 明确指定ID
294
+ // 保存到 cave.json 时移除 index
295
+ elements: cleanElementsForSave(targetCave.elements, false)
296
+ }];
297
+ await FileHandler.withTransaction([
298
+ {
299
+ filePath: caveFilePath2,
300
+ operation: /* @__PURE__ */ __name(async () => FileHandler.writeJsonData(caveFilePath2, newCaveData), "operation"),
301
+ rollback: /* @__PURE__ */ __name(async () => FileHandler.writeJsonData(caveFilePath2, oldCaveData), "rollback")
302
+ },
303
+ {
304
+ filePath: pendingFilePath2,
305
+ operation: /* @__PURE__ */ __name(async () => FileHandler.writeJsonData(pendingFilePath2, newPendingData), "operation"),
306
+ rollback: /* @__PURE__ */ __name(async () => FileHandler.writeJsonData(pendingFilePath2, pendingData), "rollback")
307
+ }
308
+ ]);
309
+ await idManager.addStat(targetCave.contributor_number, targetId);
310
+ } else {
311
+ await FileHandler.writeJsonData(pendingFilePath2, newPendingData);
312
+ await idManager.markDeleted(targetId);
313
+ if (targetCave.elements) {
314
+ for (const element of targetCave.elements) {
315
+ if ((element.type === "img" || element.type === "video") && element.file) {
316
+ const fullPath = path.join(resourceDir2, element.file);
317
+ if (fs.existsSync(fullPath)) {
318
+ await fs.promises.unlink(fullPath);
319
+ }
320
+ }
321
+ }
322
+ }
323
+ }
324
+ const remainingCount = newPendingData.length;
325
+ if (remainingCount > 0) {
326
+ const remainingIds = newPendingData.map((c) => c.cave_id).join(", ");
327
+ const action = isApprove ? "auditPassed" : "auditRejected";
328
+ return sendMessage(session, "commands.cave.audit.pendingResult", [
329
+ session.text(`commands.cave.audit.${action}`),
330
+ remainingCount,
331
+ remainingIds
332
+ ], false);
333
+ }
334
+ return sendMessage(
335
+ session,
336
+ isApprove ? "commands.cave.audit.auditPassed" : "commands.cave.audit.auditRejected",
337
+ [],
338
+ false
339
+ );
340
+ }
341
+ const data = isApprove ? await FileHandler.readJsonData(caveFilePath2) : null;
342
+ let processedCount = 0;
343
+ if (isApprove && data) {
344
+ const oldData = [...data];
345
+ const newData = [...data];
346
+ await FileHandler.withTransaction([
347
+ {
348
+ filePath: caveFilePath2,
349
+ operation: /* @__PURE__ */ __name(async () => {
350
+ for (const cave of pendingData) {
351
+ newData.push({
352
+ ...cave,
353
+ cave_id: cave.cave_id,
354
+ // 确保ID保持不变
355
+ // 保存到 cave.json 时移除 index
356
+ elements: cleanElementsForSave(cave.elements, false)
357
+ });
358
+ processedCount++;
359
+ await idManager.addStat(cave.contributor_number, cave.cave_id);
360
+ }
361
+ return FileHandler.writeJsonData(caveFilePath2, newData);
362
+ }, "operation"),
363
+ rollback: /* @__PURE__ */ __name(async () => FileHandler.writeJsonData(caveFilePath2, oldData), "rollback")
364
+ },
365
+ {
366
+ filePath: pendingFilePath2,
367
+ operation: /* @__PURE__ */ __name(async () => FileHandler.writeJsonData(pendingFilePath2, []), "operation"),
368
+ rollback: /* @__PURE__ */ __name(async () => FileHandler.writeJsonData(pendingFilePath2, pendingData), "rollback")
369
+ }
370
+ ]);
371
+ } else {
372
+ for (const cave of pendingData) {
373
+ await idManager.markDeleted(cave.cave_id);
374
+ }
375
+ await FileHandler.writeJsonData(pendingFilePath2, []);
376
+ for (const cave of pendingData) {
377
+ if (cave.elements) {
378
+ for (const element of cave.elements) {
379
+ if ((element.type === "img" || element.type === "video") && element.file) {
380
+ const fullPath = path.join(resourceDir2, element.file);
381
+ if (fs.existsSync(fullPath)) {
382
+ await fs.promises.unlink(fullPath);
383
+ }
384
+ }
385
+ }
386
+ }
387
+ processedCount++;
388
+ }
389
+ }
390
+ return sendMessage(session, "commands.cave.audit.batchAuditResult", [
391
+ isApprove ? "通过" : "拒绝",
392
+ processedCount,
393
+ pendingData.length
394
+ ], false);
395
+ }
396
+ __name(handleAudit, "handleAudit");
397
+ async function checkCooldown(session, config2) {
398
+ const guildId = session.guildId;
399
+ const now = Date.now();
400
+ const lastTime = lastUsed.get(guildId) || 0;
401
+ const isManager = config2.manager.includes(session.userId);
402
+ if (!isManager && now - lastTime < config2.number * 1e3) {
403
+ const waitTime = Math.ceil((config2.number * 1e3 - (now - lastTime)) / 1e3);
404
+ await sendMessage(session, "commands.cave.message.cooldown", [waitTime], true);
405
+ return false;
406
+ }
407
+ lastUsed.set(guildId, now);
408
+ return true;
409
+ }
410
+ __name(checkCooldown, "checkCooldown");
83
411
  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
412
  if (config.blacklist.includes(session.userId)) {
85
- return sendTempMessage(session, "commands.cave.message.blacklisted");
413
+ return sendMessage(session, "commands.cave.message.blacklisted", [], true);
86
414
  }
87
- if (session.content && session.content.includes("-help")) return;
88
- if ((options.l || options.p || options.d) && !config.manager.includes(session.userId)) {
89
- return sendTempMessage(session, "commands.cave.message.managerOnly");
415
+ if ((options.p || options.d) && !config.manager.includes(session.userId)) {
416
+ return sendMessage(session, "commands.cave.message.managerOnly", [], true);
90
417
  }
91
418
  }).action(async ({ session, options }, ...content) => {
92
- return await handleCaveAction(ctx, config, session, options, content, lastUsed);
419
+ const dataDir2 = path.join(ctx.baseDir, "data");
420
+ const caveDir2 = path.join(dataDir2, "cave");
421
+ const caveFilePath2 = path.join(caveDir2, "cave.json");
422
+ const resourceDir2 = path.join(caveDir2, "resources");
423
+ const pendingFilePath2 = path.join(caveDir2, "pending.json");
424
+ if (options.l !== void 0) {
425
+ const input = typeof options.l === "string" ? options.l : content[0];
426
+ const num = parseInt(input);
427
+ if (config.manager.includes(session.userId)) {
428
+ if (!isNaN(num)) {
429
+ if (num < 1e4) {
430
+ return await processList(session, config, void 0, num);
431
+ } else {
432
+ return await processList(session, config, num.toString());
433
+ }
434
+ } else if (input) {
435
+ return await processList(session, config, input);
436
+ }
437
+ return await processList(session, config);
438
+ } else {
439
+ return await processList(session, config, session.userId);
440
+ }
441
+ }
442
+ if (options.p || options.d) {
443
+ return await processAudit(ctx, pendingFilePath2, caveFilePath2, resourceDir2, session, options, content);
444
+ }
445
+ if (options.g) {
446
+ return await processView(caveFilePath2, resourceDir2, session, options, content, config);
447
+ }
448
+ if (options.r) {
449
+ return await processDelete(caveFilePath2, resourceDir2, pendingFilePath2, session, config, options, content);
450
+ }
451
+ if (options.a) {
452
+ return await processAdd(ctx, config, caveFilePath2, resourceDir2, pendingFilePath2, session, content);
453
+ }
454
+ return await processRandom(caveFilePath2, resourceDir2, session, config);
93
455
  });
94
456
  }
95
457
  __name(apply, "apply");
@@ -98,563 +460,472 @@ var FileHandler = class {
98
460
  static {
99
461
  __name(this, "FileHandler");
100
462
  }
101
- static readJsonData(filePath, session, validator) {
463
+ static locks = /* @__PURE__ */ new Map();
464
+ static RETRY_COUNT = 3;
465
+ static RETRY_DELAY = 1e3;
466
+ static CONCURRENCY_LIMIT = 5;
467
+ /**
468
+ * 并发控制
469
+ */
470
+ static async withConcurrencyLimit(operation, limit = this.CONCURRENCY_LIMIT) {
471
+ while (this.locks.size >= limit) {
472
+ await Promise.race(this.locks.values());
473
+ }
474
+ return operation();
475
+ }
476
+ /**
477
+ * 统一的文件操作包装器
478
+ */
479
+ static async withFileOp(filePath, operation) {
480
+ const key = filePath;
481
+ while (this.locks.has(key)) {
482
+ await this.locks.get(key);
483
+ }
484
+ const operationPromise = (async () => {
485
+ for (let i = 0; i < this.RETRY_COUNT; i++) {
486
+ try {
487
+ return await operation();
488
+ } catch (error) {
489
+ if (i === this.RETRY_COUNT - 1) throw error;
490
+ await new Promise((resolve) => setTimeout(resolve, this.RETRY_DELAY));
491
+ }
492
+ }
493
+ throw new Error("Operation failed after retries");
494
+ })();
495
+ this.locks.set(key, operationPromise);
102
496
  try {
103
- const data = fs.readFileSync(filePath, "utf8");
104
- const parsed = JSON.parse(data || "[]");
105
- return Array.isArray(parsed) ? validator ? parsed.filter(validator) : parsed : [];
106
- } catch (error) {
107
- logger.error(session.text("commands.cave.error.fileRead", [error.message]));
108
- return [];
497
+ return await operationPromise;
498
+ } finally {
499
+ this.locks.delete(key);
109
500
  }
110
501
  }
111
- static writeJsonData(filePath, data, session) {
502
+ /**
503
+ * 事务处理
504
+ */
505
+ static async withTransaction(operations) {
506
+ const results = [];
507
+ const completed = /* @__PURE__ */ new Set();
112
508
  try {
113
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
509
+ for (const { filePath, operation } of operations) {
510
+ const result = await this.withFileOp(filePath, operation);
511
+ results.push(result);
512
+ completed.add(filePath);
513
+ }
514
+ return results;
114
515
  } catch (error) {
115
- logger.error(session.text("commands.cave.error.fileWrite", [error.message]));
116
- throw new Error(session.text("commands.cave.error.saveFailed"));
516
+ await Promise.all(
517
+ operations.filter(({ filePath }) => completed.has(filePath)).map(async ({ filePath, rollback }) => {
518
+ if (rollback) {
519
+ await this.withFileOp(filePath, rollback).catch(
520
+ (e) => logger.error(`Rollback failed for ${filePath}: ${e.message}`)
521
+ );
522
+ }
523
+ })
524
+ );
525
+ throw error;
117
526
  }
118
527
  }
528
+ /**
529
+ * JSON文件读写
530
+ */
531
+ static async readJsonData(filePath) {
532
+ return this.withFileOp(filePath, async () => {
533
+ try {
534
+ const data = await fs.promises.readFile(filePath, "utf8");
535
+ return JSON.parse(data || "[]");
536
+ } catch (error) {
537
+ return [];
538
+ }
539
+ });
540
+ }
541
+ static async writeJsonData(filePath, data) {
542
+ const tmpPath = `${filePath}.tmp`;
543
+ await this.withFileOp(filePath, async () => {
544
+ await fs.promises.writeFile(tmpPath, JSON.stringify(data, null, 2));
545
+ await fs.promises.rename(tmpPath, filePath);
546
+ });
547
+ }
548
+ /**
549
+ * 文件系统操作
550
+ */
119
551
  static async ensureDirectory(dir) {
120
- !fs.existsSync(dir) && await fs.promises.mkdir(dir, { recursive: true });
552
+ await this.withConcurrencyLimit(async () => {
553
+ if (!fs.existsSync(dir)) {
554
+ await fs.promises.mkdir(dir, { recursive: true });
555
+ }
556
+ });
121
557
  }
122
- static async ensureJsonFile(filePath, defaultContent = "[]") {
123
- !fs.existsSync(filePath) && await fs.promises.writeFile(filePath, defaultContent, "utf8");
558
+ static async ensureJsonFile(filePath) {
559
+ await this.withFileOp(filePath, async () => {
560
+ if (!fs.existsSync(filePath)) {
561
+ await fs.promises.writeFile(filePath, "[]", "utf8");
562
+ }
563
+ });
564
+ }
565
+ /**
566
+ * 媒体文件操作
567
+ */
568
+ static async saveMediaFile(filePath, data) {
569
+ await this.withConcurrencyLimit(async () => {
570
+ const dir = path.dirname(filePath);
571
+ await this.ensureDirectory(dir);
572
+ await this.withFileOp(
573
+ filePath,
574
+ () => fs.promises.writeFile(filePath, data)
575
+ );
576
+ });
577
+ }
578
+ static async deleteMediaFile(filePath) {
579
+ await this.withFileOp(filePath, async () => {
580
+ if (fs.existsSync(filePath)) {
581
+ await fs.promises.unlink(filePath);
582
+ }
583
+ });
124
584
  }
125
585
  };
126
- async function saveMedia(urls, fileNames, fileSizes, resourceDir, caveId, config, ctx, mediaType, session) {
127
- const savedFiles = [];
128
- const defaults = mediaType === "img" ? { ext: "png", accept: "image/*", maxSize: config.imageMaxSize } : { ext: "mp4", accept: "video/*", maxSize: config.videoMaxSize };
129
- const extPattern = /\.[a-zA-Z0-9]+$/;
130
- for (let i = 0; i < urls.length; i++) {
586
+ var IdManager = class {
587
+ static {
588
+ __name(this, "IdManager");
589
+ }
590
+ deletedIds = /* @__PURE__ */ new Set();
591
+ maxId = 0;
592
+ initialized = false;
593
+ statusFilePath;
594
+ stats = {};
595
+ usedIds = /* @__PURE__ */ new Set();
596
+ // 新增:跟踪所有使用中的ID
597
+ constructor(baseDir) {
598
+ const caveDir = path.join(baseDir, "data", "cave");
599
+ this.statusFilePath = path.join(caveDir, "status.json");
600
+ }
601
+ async initialize(caveFilePath, pendingFilePath) {
602
+ if (this.initialized) return;
131
603
  try {
132
- const url = urls[i];
133
- const processedUrl = (() => {
134
- try {
135
- const decodedUrl = decodeURIComponent(url);
136
- return decodedUrl.includes("multimedia.nt.qq.com.cn") ? decodedUrl.replace(/&amp;/g, "&") : url;
137
- } catch {
138
- return url;
139
- }
140
- })();
141
- let ext = defaults.ext;
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;
604
+ this.initialized = true;
605
+ const status = fs.existsSync(this.statusFilePath) ? JSON.parse(await fs.promises.readFile(this.statusFilePath, "utf8")) : {
606
+ deletedIds: [],
607
+ maxId: 0,
608
+ stats: {},
609
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
610
+ };
611
+ const [caveData, pendingData] = await Promise.all([
612
+ FileHandler.readJsonData(caveFilePath),
613
+ FileHandler.readJsonData(pendingFilePath)
614
+ ]);
615
+ this.usedIds.clear();
616
+ const conflicts = /* @__PURE__ */ new Map();
617
+ const collectIds = /* @__PURE__ */ __name((items) => {
618
+ items.forEach((item) => {
619
+ if (this.usedIds.has(item.cave_id)) {
620
+ if (!conflicts.has(item.cave_id)) {
621
+ conflicts.set(item.cave_id, []);
622
+ }
623
+ conflicts.get(item.cave_id)?.push(item);
624
+ } else {
625
+ this.usedIds.add(item.cave_id);
626
+ }
627
+ });
628
+ }, "collectIds");
629
+ collectIds(caveData);
630
+ collectIds(pendingData);
631
+ if (conflicts.size > 0) {
632
+ logger.warn(`Found ${conflicts.size} ID conflicts, auto-fixing...`);
633
+ for (const [conflictId, items] of conflicts) {
634
+ items.forEach((item, index) => {
635
+ if (index > 0) {
636
+ let newId = this.maxId + 1;
637
+ while (this.usedIds.has(newId)) {
638
+ newId++;
639
+ }
640
+ logger.info(`Reassigning ID ${item.cave_id} -> ${newId} for item`);
641
+ item.cave_id = newId;
642
+ this.usedIds.add(newId);
643
+ this.maxId = Math.max(this.maxId, newId);
644
+ }
645
+ });
149
646
  }
647
+ await Promise.all([
648
+ FileHandler.writeJsonData(caveFilePath, caveData),
649
+ FileHandler.writeJsonData(pendingFilePath, pendingData)
650
+ ]);
150
651
  }
151
- if (fileName && extPattern.test(fileName)) {
152
- ext = fileName.match(extPattern)[0].slice(1);
153
- }
154
- const finalFileName = fileName ? `${caveId}_${path.basename(fileName)}` : `${caveId}_${i + 1}.${ext}`;
155
- const targetPath = path.join(resourceDir, finalFileName);
156
- const response = await ctx.http(processedUrl, {
157
- method: "GET",
158
- responseType: "arraybuffer",
159
- timeout: 3e4,
160
- headers: {
161
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
162
- "Accept": defaults.accept,
163
- "Referer": "https://qq.com"
652
+ this.maxId = Math.max(
653
+ this.maxId,
654
+ status.maxId || 0,
655
+ ...[...this.usedIds]
656
+ );
657
+ this.deletedIds = new Set(
658
+ status.deletedIds?.filter((id) => !this.usedIds.has(id)) || []
659
+ );
660
+ this.stats = {};
661
+ for (const cave of caveData) {
662
+ if (cave.contributor_number === "10000") continue;
663
+ if (!this.stats[cave.contributor_number]) {
664
+ this.stats[cave.contributor_number] = [];
164
665
  }
165
- });
166
- const fileBuffer = Buffer.from(response.data);
167
- await fs.promises.writeFile(targetPath, fileBuffer);
168
- savedFiles.push(finalFileName);
666
+ this.stats[cave.contributor_number].push(cave.cave_id);
667
+ }
668
+ await this.saveStatus();
669
+ this.initialized = true;
169
670
  } catch (error) {
170
- const errorKey = mediaType === "img" ? "commands.cave.error.uploadImageFailed" : "commands.cave.error.uploadVideoFailed";
171
- await sendTempMessage(session, errorKey);
172
- return [];
671
+ this.initialized = false;
672
+ logger.error(`IdManager initialization failed: ${error.message}`);
673
+ throw error;
173
674
  }
174
675
  }
175
- return savedFiles;
176
- }
177
- __name(saveMedia, "saveMedia");
178
- async function sendAuditMessage(ctx, config, cave, content, session) {
179
- const auditMessage = `${session.text("commands.cave.audit.title")}
180
- ${content}
181
- ${session.text("commands.cave.audit.from")}${cave.contributor_number}`;
182
- for (const managerId of config.manager) {
183
- try {
184
- await ctx.bots[0]?.sendPrivateMessage(managerId, auditMessage);
185
- } catch (error) {
186
- logger.error(session.text("commands.cave.audit.sendFailed", [managerId]));
676
+ getNextId() {
677
+ if (!this.initialized) {
678
+ throw new Error("IdManager not initialized");
679
+ }
680
+ let nextId;
681
+ if (this.deletedIds.size === 0) {
682
+ nextId = ++this.maxId;
683
+ } else {
684
+ nextId = Math.min(...Array.from(this.deletedIds));
685
+ this.deletedIds.delete(nextId);
187
686
  }
687
+ while (this.usedIds.has(nextId)) {
688
+ nextId = ++this.maxId;
689
+ }
690
+ this.usedIds.add(nextId);
691
+ this.saveStatus().catch(
692
+ (err) => logger.error(`Failed to save status after getNextId: ${err.message}`)
693
+ );
694
+ return nextId;
188
695
  }
189
- }
190
- __name(sendAuditMessage, "sendAuditMessage");
191
- async function handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data, session) {
192
- try {
193
- if (isApprove && data) {
194
- const caveWithoutIndex = {
195
- ...cave,
196
- elements: cleanElementsForSave(cave.elements, false)
197
- };
198
- data.push(caveWithoutIndex);
199
- } else if (!isApprove && cave.elements) {
200
- for (const element of cave.elements) {
201
- if (element.type === "img" && element.file) {
202
- const fullPath = path.join(resourceDir, element.file);
203
- if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
204
- }
696
+ async markDeleted(id) {
697
+ if (!this.initialized) {
698
+ throw new Error("IdManager not initialized");
699
+ }
700
+ this.deletedIds.add(id);
701
+ this.usedIds.delete(id);
702
+ if (id === this.maxId) {
703
+ const maxUsedId = Math.max(...Array.from(this.usedIds));
704
+ this.maxId = maxUsedId;
705
+ }
706
+ await this.saveStatus();
707
+ }
708
+ // 添加新的统计记录
709
+ async addStat(contributorNumber, caveId) {
710
+ if (contributorNumber === "10000") return;
711
+ if (!this.stats[contributorNumber]) {
712
+ this.stats[contributorNumber] = [];
713
+ }
714
+ this.stats[contributorNumber].push(caveId);
715
+ await this.saveStatus();
716
+ }
717
+ // 删除统计记录
718
+ async removeStat(contributorNumber, caveId) {
719
+ if (this.stats[contributorNumber]) {
720
+ this.stats[contributorNumber] = this.stats[contributorNumber].filter((id) => id !== caveId);
721
+ if (this.stats[contributorNumber].length === 0) {
722
+ delete this.stats[contributorNumber];
205
723
  }
724
+ await this.saveStatus();
206
725
  }
207
- return true;
208
- } catch (error) {
209
- return sendTempMessage(session, "commands.cave.error.auditProcess", [error.message]);
210
726
  }
211
- }
212
- __name(handleSingleCaveAudit, "handleSingleCaveAudit");
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);
234
- }
235
- return sendMessage(
236
- session,
237
- isApprove ? "commands.cave.audit.auditPassed" : "commands.cave.audit.auditRejected",
238
- [],
239
- false
240
- // 审核结果改为永久消息
241
- );
727
+ // 获取统计信息
728
+ getStats() {
729
+ return this.stats;
242
730
  }
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);
255
- }
256
- __name(handleAudit, "handleAudit");
257
- function cleanElementsForSave(elements, keepIndex = false) {
258
- const sorted = elements.sort((a, b) => a.index - b.index);
259
- return sorted.map(({ type, content, file, index }) => ({
260
- type,
261
- ...keepIndex && { index },
262
- ...content && { content },
263
- ...file && { file }
264
- }));
265
- }
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 () => {
731
+ async saveStatus() {
270
732
  try {
271
- await session.bot.deleteMessage(session.channelId, msg);
733
+ const status = {
734
+ deletedIds: Array.from(this.deletedIds).sort((a, b) => a - b),
735
+ maxId: this.maxId,
736
+ stats: this.stats,
737
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
738
+ };
739
+ const tmpPath = `${this.statusFilePath}.tmp`;
740
+ await fs.promises.writeFile(tmpPath, JSON.stringify(status, null, 2), "utf8");
741
+ await fs.promises.rename(tmpPath, this.statusFilePath);
272
742
  } catch (error) {
273
- logger.error("Failed to delete message:", error);
743
+ logger.error(`Failed to save status: ${error.message}`);
744
+ throw error;
274
745
  }
275
- }, timeout);
746
+ }
747
+ };
748
+ async function sendMessage(session, key, params = [], isTemp = true, timeout = 1e4) {
749
+ try {
750
+ const msg = await session.send(session.text(key, params));
751
+ if (isTemp && msg) {
752
+ setTimeout(async () => {
753
+ try {
754
+ await session.bot.deleteMessage(session.channelId, msg);
755
+ } catch (error) {
756
+ logger.debug(`Failed to delete temporary message: ${error.message}`);
757
+ }
758
+ }, timeout);
759
+ }
760
+ } catch (error) {
761
+ logger.error(`Failed to send message: ${error.message}`);
762
+ }
276
763
  return "";
277
764
  }
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 () => {
765
+ __name(sendMessage, "sendMessage");
766
+ async function sendAuditMessage(ctx, config, cave, content, session) {
767
+ const auditMessage = `${session.text("commands.cave.audit.title")}
768
+ ${content}
769
+ ${session.text("commands.cave.audit.from")}${cave.contributor_number}`;
770
+ for (const managerId of config.manager) {
771
+ const bot = ctx.bots[0];
772
+ if (bot) {
283
773
  try {
284
- await session.bot.deleteMessage(session.channelId, msg);
774
+ await bot.sendPrivateMessage(managerId, auditMessage);
285
775
  } catch (error) {
286
- logger.error("Failed to delete message:", error);
776
+ logger.error(session.text("commands.cave.audit.sendFailed", [managerId]));
287
777
  }
288
- }, timeout);
778
+ }
289
779
  }
290
- return "";
291
780
  }
292
- __name(sendMessage, "sendMessage");
293
- async function extractMediaContent(originalContent) {
294
- const parsedTexts = originalContent.split(/<img[^>]+>|<video[^>]+>/g).map((t) => t.trim()).filter((t) => t);
295
- const textParts = [];
296
- parsedTexts.forEach((text, idx) => {
297
- textParts.push({ type: "text", content: text, index: idx * 3 });
298
- });
299
- const imageUrls = [];
300
- const imageElements = [];
301
- const videoUrls = [];
302
- const videoElements = [];
303
- const imgMatches = originalContent.match(/<img[^>]+src="([^"]+)"[^>]*>/g) || [];
304
- imgMatches.forEach((img, idx) => {
305
- const srcMatch = img.match(/src="([^"]+)"/);
306
- const fileName = img.match(/file="([^"]+)"/)?.[1];
307
- const fileSize = img.match(/fileSize="([^"]+)"/)?.[1];
308
- if (srcMatch?.[1]) {
309
- imageUrls.push(srcMatch[1]);
310
- imageElements.push({ type: "img", index: idx * 3 + 1, fileName, fileSize });
311
- }
312
- });
313
- const videoMatches = originalContent.match(/<video[^>]+src="([^"]+)"[^>]*>/g) || [];
314
- videoMatches.forEach((video, idx) => {
315
- const srcMatch = video.match(/src="([^"]+)"/);
316
- const fileName = video.match(/file="([^"]+)"/)?.[1];
317
- const fileSize = video.match(/fileSize="([^"]+)"/)?.[1];
318
- if (srcMatch?.[1]) {
319
- videoUrls.push(srcMatch[1]);
320
- videoElements.push({ type: "video", index: idx * 3 + 2, fileName, fileSize });
781
+ __name(sendAuditMessage, "sendAuditMessage");
782
+ function cleanElementsForSave(elements, keepIndex = false) {
783
+ if (!elements?.length) return [];
784
+ const cleanedElements = elements.map((element) => {
785
+ if (element.type === "text") {
786
+ const cleanedElement = {
787
+ type: "text",
788
+ content: element.content
789
+ };
790
+ if (keepIndex) cleanedElement.index = element.index;
791
+ return cleanedElement;
792
+ } else if (element.type === "img" || element.type === "video") {
793
+ const mediaElement = element;
794
+ const cleanedElement = {
795
+ type: mediaElement.type
796
+ };
797
+ if (mediaElement.file) cleanedElement.file = mediaElement.file;
798
+ if (keepIndex) cleanedElement.index = element.index;
799
+ return cleanedElement;
321
800
  }
801
+ return element;
322
802
  });
323
- return { imageUrls, imageElements, videoUrls, videoElements, textParts };
803
+ return keepIndex ? cleanedElements.sort((a, b) => (a.index || 0) - (b.index || 0)) : cleanedElements;
324
804
  }
325
- __name(extractMediaContent, "extractMediaContent");
805
+ __name(cleanElementsForSave, "cleanElementsForSave");
806
+ async function processMediaFile(filePath, type) {
807
+ const data = await fs.promises.readFile(filePath).catch(() => null);
808
+ if (!data) return null;
809
+ return `data:${type}/${type === "image" ? "png" : "mp4"};base64,${data.toString("base64")}`;
810
+ }
811
+ __name(processMediaFile, "processMediaFile");
326
812
  async function buildMessage(cave, resourceDir, session) {
327
- let content = session.text("commands.cave.message.caveTitle", [cave.cave_id]) + "\n";
328
- const videoElements = [];
329
- for (const element of cave.elements) {
330
- if (element.type === "text") {
331
- content += element.content + "\n";
332
- } else if (element.type === "img" && element.file) {
333
- const fullImagePath = path.join(resourceDir, element.file);
334
- if (fs.existsSync(fullImagePath)) {
335
- try {
336
- const imageBuffer = fs.readFileSync(fullImagePath);
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";
340
- }
341
- } else {
342
- content += session.text("commands.cave.message.mediaInvalid", ["图片"]) + "\n";
343
- }
344
- } else if (element.type === "video" && element.file) {
345
- videoElements.push({ file: element.file });
813
+ if (!cave?.elements?.length) {
814
+ return session.text("commands.cave.error.noContent");
815
+ }
816
+ const videoElement = cave.elements.find((el) => el.type === "video");
817
+ const nonVideoElements = cave.elements.filter((el) => el.type !== "video").sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
818
+ if (videoElement?.file) {
819
+ const basicInfo = [
820
+ session.text("commands.cave.message.caveTitle", [cave.cave_id]),
821
+ session.text("commands.cave.message.contributorSuffix", [cave.contributor_name])
822
+ ].join("\n");
823
+ await session?.send(basicInfo);
824
+ const filePath = path.join(resourceDir, videoElement.file);
825
+ const base64Data = await processMediaFile(filePath, "video");
826
+ if (base64Data && session) {
827
+ await session.send((0, import_koishi.h)("video", { src: base64Data }));
346
828
  }
829
+ return "";
347
830
  }
348
- if (videoElements.length > 0 && session) {
349
- content += session.text("commands.cave.message.videoSending") + "\n";
350
- for (const video of videoElements) {
351
- const fullVideoPath = path.join(resourceDir, video.file);
352
- if (fs.existsSync(fullVideoPath)) {
353
- try {
354
- const videoBuffer = fs.readFileSync(fullVideoPath);
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";
358
- }
359
- } else {
360
- content += session.text("commands.cave.message.mediaInvalid", ["视频"]) + "\n";
831
+ const lines = [session.text("commands.cave.message.caveTitle", [cave.cave_id])];
832
+ for (const element of nonVideoElements) {
833
+ if (element.type === "text") {
834
+ lines.push(element.content);
835
+ } else if (element.type === "img" && element.file) {
836
+ const filePath = path.join(resourceDir, element.file);
837
+ const base64Data = await processMediaFile(filePath, "image");
838
+ if (base64Data) {
839
+ lines.push((0, import_koishi.h)("image", { src: base64Data }));
361
840
  }
362
841
  }
363
842
  }
364
- content += session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]);
365
- return content;
843
+ lines.push(session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]));
844
+ return lines.join("\n");
366
845
  }
367
846
  __name(buildMessage, "buildMessage");
368
- async function initCavePaths(ctx) {
369
- const dataDir = path.join(ctx.baseDir, "data");
370
- const caveDir = path.join(dataDir, "cave");
371
- const caveFilePath = path.join(caveDir, "cave.json");
372
- const resourceDir = path.join(caveDir, "resources");
373
- const pendingFilePath = path.join(caveDir, "pending.json");
374
- await FileHandler.ensureDirectory(dataDir);
375
- await FileHandler.ensureDirectory(caveDir);
376
- await FileHandler.ensureDirectory(resourceDir);
377
- await FileHandler.ensureJsonFile(caveFilePath);
378
- await FileHandler.ensureJsonFile(pendingFilePath);
379
- return { dataDir, caveDir, caveFilePath, resourceDir, pendingFilePath };
380
- }
381
- __name(initCavePaths, "initCavePaths");
382
- async function handleCaveAction(ctx, config, session, options, content, lastUsed) {
383
- try {
384
- const { caveFilePath, resourceDir, pendingFilePath } = await initCavePaths(ctx);
385
- async function processList() {
386
- const caveData = FileHandler.readJsonData(caveFilePath, session);
387
- const caveDir = path.dirname(caveFilePath);
388
- const stats = {};
389
- for (const cave of caveData) {
390
- if (cave.contributor_number === "10000") continue;
391
- if (!stats[cave.contributor_number]) stats[cave.contributor_number] = [];
392
- stats[cave.contributor_number].push(cave.cave_id);
393
- }
394
- const statFilePath = path.join(caveDir, "stat.json");
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]);
410
- } else {
411
- return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + lines.join("\n");
412
- }
413
- }
414
- __name(processList, "processList");
415
- async function processAudit() {
416
- const pendingData = FileHandler.readJsonData(pendingFilePath, session);
417
- const isApprove = Boolean(options.p);
418
- if (options.p === true && content[0] === "all" || options.d === true && content[0] === "all") {
419
- return await handleAudit(ctx, pendingData, isApprove, caveFilePath, resourceDir, pendingFilePath, session);
420
- }
421
- const id = parseInt(content[0] || (typeof options.p === "string" ? options.p : "") || (typeof options.d === "string" ? options.d : ""));
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);
426
- }
427
- __name(processAudit, "processAudit");
428
- async function processView() {
429
- const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
430
- if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
431
- const data = FileHandler.readJsonData(
432
- caveFilePath,
433
- session,
434
- (item) => item && typeof item.cave_id === "number" && Array.isArray(item.elements) && item.elements.every(
435
- (el) => el.type === "text" && typeof el.content === "string" || el.type === "img" && typeof el.file === "string" || el.type === "video" && typeof el.file === "string"
436
- ) && typeof item.contributor_number === "string" && typeof item.contributor_name === "string"
437
- );
438
- const cave = data.find((item) => item.cave_id === caveId);
439
- if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
440
- const caveContent = await buildMessage(cave, resourceDir, session);
441
- return caveContent;
442
- }
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
- }
475
- }
476
- __name(processRandom, "processRandom");
477
- async function processDelete() {
478
- const caveId = parseInt(content[0] || (typeof options.r === "string" ? options.r : ""));
479
- if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
480
- const data = FileHandler.readJsonData(
481
- caveFilePath,
482
- session,
483
- (item) => item && typeof item.cave_id === "number"
484
- );
485
- const pendingData = FileHandler.readJsonData(pendingFilePath, session);
486
- const index = data.findIndex((item) => item.cave_id === caveId);
487
- const pendingIndex = pendingData.findIndex((item) => item.cave_id === caveId);
488
- if (index === -1 && pendingIndex === -1) return sendMessage(session, "commands.cave.error.notFound", [], true);
489
- let targetCave;
490
- let isPending = false;
491
- if (index !== -1) {
492
- targetCave = data[index];
493
- } else {
494
- targetCave = pendingData[pendingIndex];
495
- isPending = true;
496
- }
497
- if (targetCave.contributor_number !== session.userId && !config.manager.includes(session.userId)) {
498
- return sendMessage(session, "commands.cave.remove.noPermission", [], true);
499
- }
500
- const caveContent = await buildMessage(targetCave, resourceDir, session);
501
- if (targetCave.elements) {
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);
506
- }
847
+ async function extractMediaContent(originalContent, config, session) {
848
+ const textParts = originalContent.split(/<(img|video)[^>]+>/).map((text, idx) => text.trim() && {
849
+ type: "text",
850
+ content: text.replace(/^(img|video)$/, "").trim(),
851
+ index: idx * 3
852
+ }).filter((text) => text && text.content);
853
+ const getMediaElements = /* @__PURE__ */ __name((type, maxSize) => {
854
+ const regex = new RegExp(`<${type}[^>]+src="([^"]+)"[^>]*>`, "g");
855
+ const elements = [];
856
+ const urls = [];
857
+ let match;
858
+ let idx = 0;
859
+ while ((match = regex.exec(originalContent)) !== null) {
860
+ const element = match[0];
861
+ const url = match[1];
862
+ const fileName = element.match(/file="([^"]+)"/)?.[1];
863
+ const fileSize = element.match(/fileSize="([^"]+)"/)?.[1];
864
+ if (fileSize) {
865
+ const sizeInBytes = parseInt(fileSize);
866
+ if (sizeInBytes > maxSize * 1024 * 1024) {
867
+ throw new Error(session.text("commands.cave.message.mediaSizeExceeded", [type]));
507
868
  }
508
869
  }
509
- if (isPending) {
510
- pendingData.splice(pendingIndex, 1);
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}
515
- ${caveContent}`;
516
- } else {
517
- data.splice(index, 1);
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}`;
522
- }
870
+ urls.push(url);
871
+ elements.push({
872
+ type,
873
+ index: type === "video" ? Number.MAX_SAFE_INTEGER : idx * 3 + 1,
874
+ // 视频始终在最后
875
+ fileName,
876
+ fileSize
877
+ });
878
+ idx++;
523
879
  }
524
- __name(processDelete, "processDelete");
525
- async function processAdd() {
526
- try {
527
- let inputParts = [];
528
- if (content.length > 0) {
529
- inputParts = content;
530
- }
531
- if (!inputParts.length) {
532
- await sendMessage(session, "commands.cave.add.noContent", [], true);
533
- const reply = await session.prompt({ timeout: 6e4 });
534
- if (!reply || reply.trim() === "") {
535
- return sendMessage(session, "commands.cave.add.operationTimeout", [], true);
536
- }
537
- inputParts = [reply];
538
- }
539
- let { imageUrls, imageElements, videoUrls, videoElements, textParts } = await extractMediaContent(inputParts.join("\n"));
540
- if (videoUrls.length > 0 && !config.allowVideo) {
541
- return sendMessage(session, "commands.cave.add.videoDisabled", [], true);
542
- }
543
- const pendingData = FileHandler.readJsonData(pendingFilePath, session);
544
- const data = FileHandler.readJsonData(caveFilePath, session, (item) => item && typeof item.cave_id === "number");
545
- const usedIds = /* @__PURE__ */ new Set([...data.map((item) => item.cave_id), ...pendingData.map((item) => item.cave_id)]);
546
- let caveId = 1;
547
- while (usedIds.has(caveId)) {
548
- caveId++;
549
- }
550
- let savedImages = [];
551
- if (imageUrls.length > 0) {
552
- try {
553
- const imageFileNames = imageElements.map((el) => el.fileName);
554
- const imageFileSizes = imageElements.map((el) => el.fileSize);
555
- savedImages = await saveMedia(
556
- imageUrls,
557
- imageFileNames,
558
- imageFileSizes,
559
- resourceDir,
560
- caveId,
561
- config,
562
- ctx,
563
- "img",
564
- session
565
- );
566
- } catch (error) {
567
- return sendMessage(session, "commands.cave.error.uploadImageFailed", [], true);
568
- }
569
- }
570
- let savedVideos = [];
571
- if (videoUrls.length > 0) {
572
- try {
573
- const videoFileNames = videoElements.map((el) => el.fileName);
574
- const videoFileSizes = videoElements.map((el) => el.fileSize);
575
- savedVideos = await saveMedia(
576
- videoUrls,
577
- videoFileNames,
578
- videoFileSizes,
579
- resourceDir,
580
- caveId,
581
- config,
582
- ctx,
583
- "video",
584
- session
585
- );
586
- } catch (error) {
587
- return sendMessage(session, "commands.cave.error.uploadVideoFailed", [], true);
588
- }
589
- }
590
- const elements = [];
591
- elements.push(...textParts);
592
- savedImages.forEach((file, idx) => {
593
- if (imageElements[idx]) {
594
- elements.push({ ...imageElements[idx], type: "img", file });
595
- }
596
- });
597
- savedVideos.forEach((file, idx) => {
598
- if (videoElements[idx]) {
599
- elements.push({ ...videoElements[idx], type: "video", file });
600
- }
601
- });
602
- elements.sort((a, b) => a.index - b.index);
603
- let contributorName = session.username;
604
- if (ctx.database) {
605
- try {
606
- const userInfo = await ctx.database.getUser(session.platform, session.userId);
607
- contributorName = userInfo?.nickname || session.username;
608
- } catch (error) {
609
- return sendMessage(
610
- session,
611
- "commands.cave.error.userInfo",
612
- [error.message],
613
- true
614
- );
615
- }
616
- }
617
- const newCave = {
618
- cave_id: caveId,
619
- elements: cleanElementsForSave(elements, true),
620
- contributor_number: session.userId,
621
- contributor_name: contributorName
622
- };
623
- const bypassAudit = config.whitelist.includes(session.userId) || session.guildId && config.whitelist.includes(session.guildId) || session.channelId && config.whitelist.includes(session.channelId);
624
- if (config.enableAudit && !bypassAudit) {
625
- pendingData.push({ ...newCave, elements: cleanElementsForSave(elements, true) });
626
- FileHandler.writeJsonData(pendingFilePath, pendingData, session);
627
- await sendAuditMessage(ctx, config, newCave, await buildMessage(newCave, resourceDir, session), session);
628
- return sendMessage(session, "commands.cave.add.submitPending", [caveId], false);
629
- } else {
630
- const caveWithoutIndex = { ...newCave, elements: cleanElementsForSave(elements, false) };
631
- data.push(caveWithoutIndex);
632
- FileHandler.writeJsonData(caveFilePath, data, session);
633
- return sendMessage(session, "commands.cave.add.addSuccess", [caveId], false);
880
+ return { urls, elements };
881
+ }, "getMediaElements");
882
+ const { urls: imageUrls, elements: imageElementsRaw } = getMediaElements("img", config.imageMaxSize);
883
+ const imageElements = imageElementsRaw;
884
+ const { urls: videoUrls, elements: videoElementsRaw } = getMediaElements("video", config.videoMaxSize);
885
+ const videoElements = videoElementsRaw;
886
+ return { imageUrls, imageElements, videoUrls, videoElements, textParts };
887
+ }
888
+ __name(extractMediaContent, "extractMediaContent");
889
+ async function saveMedia(urls, fileNames, resourceDir, caveId, mediaType, ctx, session) {
890
+ const { ext, accept } = mediaType === "img" ? { ext: "png", accept: "image/*" } : { ext: "mp4", accept: "video/*" };
891
+ const downloadTasks = urls.map(async (url, i) => {
892
+ const fileName = fileNames[i];
893
+ const fileExt = fileName?.match(/\.[a-zA-Z0-9]+$/)?.[0]?.slice(1) || ext;
894
+ const baseName = fileName;
895
+ path.basename(fileName, path.extname(fileName)).replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "");
896
+ const finalFileName = `${caveId}_${baseName}.${fileExt}`;
897
+ const filePath = path.join(resourceDir, finalFileName);
898
+ try {
899
+ const response = await ctx.http(decodeURIComponent(url).replace(/&amp;/g, "&"), {
900
+ method: "GET",
901
+ responseType: "arraybuffer",
902
+ timeout: 3e4,
903
+ headers: {
904
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
905
+ "Accept": accept,
906
+ "Referer": "https://qq.com"
634
907
  }
635
- } catch (error) {
636
- return sendMessage(session, "commands.cave.error.commandProcess", [error.message], true);
637
- }
908
+ });
909
+ if (!response.data) throw new Error("empty_response");
910
+ await FileHandler.saveMediaFile(filePath, Buffer.from(response.data));
911
+ return finalFileName;
912
+ } catch (error) {
913
+ logger.error(`Failed to download media: ${error.message}`);
914
+ throw error;
638
915
  }
639
- __name(processAdd, "processAdd");
640
- if (options.l !== void 0) return await processList();
641
- if (options.p || options.d) return await processAudit();
642
- if (options.g) return await processView();
643
- if (options.r) return await processDelete();
644
- if (options.a) return await processAdd();
645
- return await processRandom();
646
- } catch (error) {
647
- logger.error(error);
648
- return sendMessage(session, "commands.cave.error.commandProcess", [error.message], true);
916
+ });
917
+ const results = await Promise.allSettled(downloadTasks);
918
+ const successfulResults = results.filter((result) => result.status === "fulfilled").map((result) => result.value);
919
+ if (!successfulResults.length) {
920
+ throw new Error(session.text(`commands.cave.error.upload${mediaType === "img" ? "Image" : "Video"}Failed`));
649
921
  }
922
+ return successfulResults;
650
923
  }
651
- __name(handleCaveAction, "handleCaveAction");
924
+ __name(saveMedia, "saveMedia");
652
925
  // Annotate the CommonJS export names for ESM import in node:
653
926
  0 && (module.exports = {
654
927
  Config,
655
928
  apply,
656
- handleCaveAction,
657
- initCavePaths,
658
929
  inject,
659
930
  name
660
931
  });