koishi-plugin-best-cave 1.1.5 → 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.
- package/lib/index.d.ts +25 -0
- package/lib/index.js +690 -474
- 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}文件大小超过限制",
|
|
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: "
|
|
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
|
|
|
@@ -86,18 +86,15 @@ async function apply(ctx, config) {
|
|
|
86
86
|
await FileHandler.ensureDirectory(resourceDir);
|
|
87
87
|
await FileHandler.ensureJsonFile(caveFilePath);
|
|
88
88
|
await FileHandler.ensureJsonFile(pendingFilePath);
|
|
89
|
+
const idManager = new IdManager(ctx.baseDir);
|
|
90
|
+
await idManager.initialize(caveFilePath, pendingFilePath);
|
|
89
91
|
const lastUsed = /* @__PURE__ */ new Map();
|
|
90
|
-
async function processList(
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!stats[cave.contributor_number]) stats[cave.contributor_number] = [];
|
|
97
|
-
stats[cave.contributor_number].push(cave.cave_id);
|
|
98
|
-
}
|
|
99
|
-
const statFilePath = path.join(caveDir2, "stat.json");
|
|
100
|
-
fs.writeFileSync(statFilePath, JSON.stringify(stats, null, 2), "utf8");
|
|
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
|
+
}
|
|
101
98
|
const lines = Object.entries(stats).map(([cid, ids]) => {
|
|
102
99
|
return session.text("commands.cave.list.totalItems", [cid, ids.length]) + "\n" + session.text("commands.cave.list.idsLine", [ids.join(",")]);
|
|
103
100
|
});
|
|
@@ -105,10 +102,7 @@ async function apply(ctx, config) {
|
|
|
105
102
|
if (config2.enablePagination) {
|
|
106
103
|
const itemsPerPage = config2.itemsPerPage;
|
|
107
104
|
const totalPages = Math.max(1, Math.ceil(lines.length / itemsPerPage));
|
|
108
|
-
|
|
109
|
-
let pageNum = parseInt(query, 10);
|
|
110
|
-
if (isNaN(pageNum) || pageNum < 1) pageNum = 1;
|
|
111
|
-
if (pageNum > totalPages) pageNum = totalPages;
|
|
105
|
+
pageNum = Math.min(Math.max(1, pageNum), totalPages);
|
|
112
106
|
const start = (pageNum - 1) * itemsPerPage;
|
|
113
107
|
const paginatedLines = lines.slice(start, start + itemsPerPage);
|
|
114
108
|
return session.text("commands.cave.list.header", [totalSubmissions]) + "\n" + paginatedLines.join("\n") + "\n" + session.text("commands.cave.list.pageInfo", [pageNum, totalPages]);
|
|
@@ -118,7 +112,7 @@ async function apply(ctx, config) {
|
|
|
118
112
|
}
|
|
119
113
|
__name(processList, "processList");
|
|
120
114
|
async function processAudit(ctx2, pendingFilePath2, caveFilePath2, resourceDir2, session, options, content) {
|
|
121
|
-
const pendingData = await
|
|
115
|
+
const pendingData = await FileHandler.readJsonData(pendingFilePath2);
|
|
122
116
|
const isApprove = Boolean(options.p);
|
|
123
117
|
if (options.p === true && content[0] === "all" || options.d === true && content[0] === "all") {
|
|
124
118
|
return await handleAudit(ctx2, pendingData, isApprove, caveFilePath2, resourceDir2, pendingFilePath2, session);
|
|
@@ -130,59 +124,47 @@ async function apply(ctx, config) {
|
|
|
130
124
|
return sendMessage(session, await handleAudit(ctx2, pendingData, isApprove, caveFilePath2, resourceDir2, pendingFilePath2, session, id), [], true);
|
|
131
125
|
}
|
|
132
126
|
__name(processAudit, "processAudit");
|
|
133
|
-
async function processView(caveFilePath2, resourceDir2, session, options, content) {
|
|
127
|
+
async function processView(caveFilePath2, resourceDir2, session, options, content, config2) {
|
|
128
|
+
if (!await checkCooldown(session, config2)) {
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
134
131
|
const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
|
|
135
132
|
if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
|
|
136
|
-
const data = await
|
|
133
|
+
const data = await FileHandler.readJsonData(caveFilePath2);
|
|
137
134
|
const cave = data.find((item) => item.cave_id === caveId);
|
|
138
135
|
if (!cave) return sendMessage(session, "commands.cave.error.notFound", [], true);
|
|
139
|
-
|
|
140
|
-
return caveContent;
|
|
136
|
+
return buildMessage(cave, resourceDir2, session);
|
|
141
137
|
}
|
|
142
138
|
__name(processView, "processView");
|
|
143
|
-
async function processRandom(caveFilePath2, resourceDir2, session, config2
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return sendMessage(session, "commands.cave.error.noCave", [], true);
|
|
148
|
-
}
|
|
149
|
-
const guildId = session.guildId;
|
|
150
|
-
const now = Date.now();
|
|
151
|
-
const lastCall = lastUsed2.get(guildId) || 0;
|
|
152
|
-
const isManager = config2.manager.includes(session.userId);
|
|
153
|
-
if (!isManager && now - lastCall < config2.number * 1e3) {
|
|
154
|
-
const waitTime = Math.ceil((config2.number * 1e3 - (now - lastCall)) / 1e3);
|
|
155
|
-
return sendMessage(session, "commands.cave.message.cooldown", [waitTime], true);
|
|
156
|
-
}
|
|
157
|
-
if (!isManager) lastUsed2.set(guildId, now);
|
|
158
|
-
const cave = (() => {
|
|
159
|
-
const validCaves = data.filter((cave2) => cave2.elements && cave2.elements.length > 0);
|
|
160
|
-
if (!validCaves.length) return void 0;
|
|
161
|
-
const randomIndex = Math.floor(Math.random() * validCaves.length);
|
|
162
|
-
return validCaves[randomIndex];
|
|
163
|
-
})();
|
|
164
|
-
return cave ? buildMessage(cave, resourceDir2, session) : sendMessage(session, "commands.cave.error.getCave", [], true);
|
|
165
|
-
} catch (error) {
|
|
166
|
-
return sendMessage(session, "commands.cave.error.commandProcess", [error.message], true);
|
|
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);
|
|
167
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);
|
|
168
154
|
}
|
|
169
155
|
__name(processRandom, "processRandom");
|
|
170
156
|
async function processDelete(caveFilePath2, resourceDir2, pendingFilePath2, session, config2, options, content) {
|
|
171
157
|
const caveId = parseInt(content[0] || (typeof options.r === "string" ? options.r : ""));
|
|
172
158
|
if (isNaN(caveId)) return sendMessage(session, "commands.cave.error.invalidId", [], true);
|
|
173
|
-
const data = await
|
|
174
|
-
const pendingData = await
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
let isPending = false;
|
|
180
|
-
if (index !== -1) {
|
|
181
|
-
targetCave = data[index];
|
|
182
|
-
} else {
|
|
183
|
-
targetCave = pendingData[pendingIndex];
|
|
184
|
-
isPending = 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);
|
|
185
165
|
}
|
|
166
|
+
const targetCave = targetInData || targetInPending;
|
|
167
|
+
const isPending = !targetInData;
|
|
186
168
|
if (targetCave.contributor_number !== session.userId && !config2.manager.includes(session.userId)) {
|
|
187
169
|
return sendMessage(session, "commands.cave.remove.noPermission", [], true);
|
|
188
170
|
}
|
|
@@ -191,180 +173,285 @@ async function apply(ctx, config) {
|
|
|
191
173
|
for (const element of targetCave.elements) {
|
|
192
174
|
if ((element.type === "img" || element.type === "video") && element.file) {
|
|
193
175
|
const fullPath = path.join(resourceDir2, element.file);
|
|
194
|
-
if (fs.existsSync(fullPath))
|
|
176
|
+
if (fs.existsSync(fullPath)) {
|
|
177
|
+
await fs.promises.unlink(fullPath);
|
|
178
|
+
}
|
|
195
179
|
}
|
|
196
180
|
}
|
|
197
181
|
}
|
|
198
182
|
if (isPending) {
|
|
199
|
-
pendingData.
|
|
200
|
-
await FileHandler.writeJsonData(pendingFilePath2,
|
|
201
|
-
const deleteStatus = isPending ? session.text("commands.cave.remove.deletePending") : "";
|
|
202
|
-
const deleteMessage = session.text("commands.cave.remove.deleted");
|
|
203
|
-
return `${deleteMessage}${deleteStatus}
|
|
204
|
-
${caveContent}`;
|
|
183
|
+
const newPendingData = pendingData.filter((item) => item.cave_id !== caveId);
|
|
184
|
+
await FileHandler.writeJsonData(pendingFilePath2, newPendingData);
|
|
205
185
|
} else {
|
|
206
|
-
data.
|
|
207
|
-
await FileHandler.writeJsonData(caveFilePath2,
|
|
208
|
-
|
|
209
|
-
const deleteMessage = session.text("commands.cave.remove.deleted");
|
|
210
|
-
return `${deleteMessage}${deleteStatus}${caveContent}`;
|
|
186
|
+
const newData = data.filter((item) => item.cave_id !== caveId);
|
|
187
|
+
await FileHandler.writeJsonData(caveFilePath2, newData);
|
|
188
|
+
await idManager.removeStat(targetCave.contributor_number, caveId);
|
|
211
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}`;
|
|
212
194
|
}
|
|
213
195
|
__name(processDelete, "processDelete");
|
|
214
196
|
async function processAdd(ctx2, config2, caveFilePath2, resourceDir2, pendingFilePath2, session, content) {
|
|
215
197
|
try {
|
|
216
|
-
|
|
217
|
-
if (content.length > 0) {
|
|
218
|
-
inputParts = content;
|
|
219
|
-
}
|
|
220
|
-
if (!inputParts.length) {
|
|
198
|
+
const inputContent = content.length > 0 ? content.join("\n") : await (async () => {
|
|
221
199
|
await sendMessage(session, "commands.cave.add.noContent", [], true);
|
|
222
200
|
const reply = await session.prompt({ timeout: 6e4 });
|
|
223
|
-
if (!reply
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const combinedInput = inputParts.join("\n");
|
|
229
|
-
if (combinedInput.includes("/app/.config/QQ/")) {
|
|
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/")) {
|
|
230
206
|
return sendMessage(session, "commands.cave.add.localFileNotAllowed", [], true);
|
|
231
207
|
}
|
|
232
|
-
|
|
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);
|
|
233
210
|
if (videoUrls.length > 0 && !config2.allowVideo) {
|
|
234
211
|
return sendMessage(session, "commands.cave.add.videoDisabled", [], true);
|
|
235
212
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"img",
|
|
257
|
-
session
|
|
258
|
-
);
|
|
259
|
-
} catch (error) {
|
|
260
|
-
return sendMessage(session, "commands.cave.error.uploadImageFailed", [], true);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
let savedVideos = [];
|
|
264
|
-
if (videoUrls.length > 0) {
|
|
265
|
-
try {
|
|
266
|
-
const videoFileNames = videoElements.map((el) => el.fileName);
|
|
267
|
-
const videoFileSizes = videoElements.map((el) => el.fileSize);
|
|
268
|
-
savedVideos = await saveMedia(
|
|
269
|
-
videoUrls,
|
|
270
|
-
videoFileNames,
|
|
271
|
-
videoFileSizes,
|
|
272
|
-
resourceDir2,
|
|
273
|
-
caveId,
|
|
274
|
-
config2,
|
|
275
|
-
ctx2,
|
|
276
|
-
"video",
|
|
277
|
-
session
|
|
278
|
-
);
|
|
279
|
-
} catch (error) {
|
|
280
|
-
return sendMessage(session, "commands.cave.error.uploadVideoFailed", [], true);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
const elements = [];
|
|
284
|
-
elements.push(...textParts);
|
|
285
|
-
savedImages.forEach((file, idx) => {
|
|
286
|
-
if (imageElements[idx]) {
|
|
287
|
-
elements.push({ ...imageElements[idx], type: "img", file });
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
savedVideos.forEach((file, idx) => {
|
|
291
|
-
if (videoElements[idx]) {
|
|
292
|
-
elements.push({ ...videoElements[idx], type: "video", file });
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
elements.sort((a, b) => a.index - b.index);
|
|
296
|
-
let contributorName = session.username;
|
|
297
|
-
if (ctx2.database) {
|
|
298
|
-
try {
|
|
299
|
-
const userInfo = await ctx2.database.getUser(session.platform, session.userId);
|
|
300
|
-
contributorName = userInfo?.nickname || session.username;
|
|
301
|
-
} catch (error) {
|
|
302
|
-
return sendMessage(
|
|
303
|
-
session,
|
|
304
|
-
"commands.cave.error.userInfo",
|
|
305
|
-
[error.message],
|
|
306
|
-
true
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
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
|
+
]);
|
|
310
233
|
const newCave = {
|
|
311
234
|
cave_id: caveId,
|
|
312
|
-
elements:
|
|
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),
|
|
313
244
|
contributor_number: session.userId,
|
|
314
|
-
contributor_name:
|
|
245
|
+
contributor_name: session.username
|
|
315
246
|
};
|
|
316
|
-
|
|
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
|
+
}
|
|
317
255
|
if (config2.enableAudit && !bypassAudit) {
|
|
318
|
-
pendingData
|
|
319
|
-
|
|
320
|
-
await
|
|
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
|
+
]);
|
|
321
262
|
return sendMessage(session, "commands.cave.add.submitPending", [caveId], false);
|
|
322
|
-
} else {
|
|
323
|
-
const caveWithoutIndex = { ...newCave, elements: cleanElementsForSave(elements, false) };
|
|
324
|
-
data.push(caveWithoutIndex);
|
|
325
|
-
await FileHandler.writeJsonData(caveFilePath2, data, session);
|
|
326
|
-
return sendMessage(session, "commands.cave.add.addSuccess", [caveId], false);
|
|
327
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);
|
|
328
272
|
} catch (error) {
|
|
329
|
-
|
|
273
|
+
logger.error(`Failed to process add command: ${error.message}`);
|
|
274
|
+
return sendMessage(session, `commands.cave.error.${error.code || "unknown"}`, [], true);
|
|
330
275
|
}
|
|
331
276
|
}
|
|
332
277
|
__name(processAdd, "processAdd");
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
return
|
|
336
|
-
}
|
|
337
|
-
if (session.content && session.content.includes("-help")) return;
|
|
338
|
-
if ((options.l || options.p || options.d) && !config.manager.includes(session.userId)) {
|
|
339
|
-
return sendTempMessage(session, "commands.cave.message.managerOnly");
|
|
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);
|
|
340
281
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const caveFilePath2 = path.join(caveDir2, "cave.json");
|
|
346
|
-
const resourceDir2 = path.join(caveDir2, "resources");
|
|
347
|
-
const pendingFilePath2 = path.join(caveDir2, "pending.json");
|
|
348
|
-
if (options.l !== void 0) {
|
|
349
|
-
return await processList(caveFilePath2, session, content, options, config);
|
|
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);
|
|
350
286
|
}
|
|
351
|
-
|
|
352
|
-
|
|
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
|
+
}
|
|
353
323
|
}
|
|
354
|
-
|
|
355
|
-
|
|
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);
|
|
356
333
|
}
|
|
357
|
-
|
|
358
|
-
|
|
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);
|
|
359
374
|
}
|
|
360
|
-
|
|
361
|
-
|
|
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");
|
|
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 }) => {
|
|
412
|
+
if (config.blacklist.includes(session.userId)) {
|
|
413
|
+
return sendMessage(session, "commands.cave.message.blacklisted", [], true);
|
|
414
|
+
}
|
|
415
|
+
if ((options.p || options.d) && !config.manager.includes(session.userId)) {
|
|
416
|
+
return sendMessage(session, "commands.cave.message.managerOnly", [], true);
|
|
417
|
+
}
|
|
418
|
+
}).action(async ({ session, options }, ...content) => {
|
|
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);
|
|
362
440
|
}
|
|
363
|
-
return await processRandom(caveFilePath2, resourceDir2, session, config, lastUsed);
|
|
364
|
-
} catch (error) {
|
|
365
|
-
logger.error(error);
|
|
366
|
-
return sendMessage(session, "commands.cave.error.commandProcess", [error.message], true);
|
|
367
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);
|
|
368
455
|
});
|
|
369
456
|
}
|
|
370
457
|
__name(apply, "apply");
|
|
@@ -373,339 +460,468 @@ var FileHandler = class {
|
|
|
373
460
|
static {
|
|
374
461
|
__name(this, "FileHandler");
|
|
375
462
|
}
|
|
376
|
-
static
|
|
377
|
-
static
|
|
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);
|
|
496
|
+
try {
|
|
497
|
+
return await operationPromise;
|
|
498
|
+
} finally {
|
|
499
|
+
this.locks.delete(key);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* 事务处理
|
|
504
|
+
*/
|
|
505
|
+
static async withTransaction(operations) {
|
|
506
|
+
const results = [];
|
|
507
|
+
const completed = /* @__PURE__ */ new Set();
|
|
378
508
|
try {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
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;
|
|
382
515
|
} catch (error) {
|
|
383
|
-
|
|
384
|
-
|
|
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;
|
|
385
526
|
}
|
|
386
527
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
528
|
+
/**
|
|
529
|
+
* JSON文件读写
|
|
530
|
+
*/
|
|
531
|
+
static async readJsonData(filePath) {
|
|
532
|
+
return this.withFileOp(filePath, async () => {
|
|
390
533
|
try {
|
|
391
|
-
const
|
|
392
|
-
|
|
534
|
+
const data = await fs.promises.readFile(filePath, "utf8");
|
|
535
|
+
return JSON.parse(data || "[]");
|
|
393
536
|
} catch (error) {
|
|
394
|
-
|
|
395
|
-
throw new Error(session.text("commands.cave.error.saveFailed"));
|
|
396
|
-
}
|
|
397
|
-
}, "writeOperation");
|
|
398
|
-
if (!this.writeQueue.has(queueKey)) {
|
|
399
|
-
this.writeQueue.set(queueKey, Promise.resolve());
|
|
400
|
-
}
|
|
401
|
-
const currentPromise = this.writeQueue.get(queueKey).then(writeOperation).finally(() => {
|
|
402
|
-
if (this.writeQueue.get(queueKey) === currentPromise) {
|
|
403
|
-
this.writeQueue.delete(queueKey);
|
|
537
|
+
return [];
|
|
404
538
|
}
|
|
405
539
|
});
|
|
406
|
-
this.writeQueue.set(queueKey, currentPromise);
|
|
407
|
-
return currentPromise;
|
|
408
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
|
+
*/
|
|
409
551
|
static async ensureDirectory(dir) {
|
|
410
|
-
|
|
552
|
+
await this.withConcurrencyLimit(async () => {
|
|
553
|
+
if (!fs.existsSync(dir)) {
|
|
554
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
555
|
+
}
|
|
556
|
+
});
|
|
411
557
|
}
|
|
412
|
-
static async ensureJsonFile(filePath
|
|
413
|
-
|
|
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
|
+
});
|
|
414
584
|
}
|
|
415
585
|
};
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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;
|
|
420
603
|
try {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
+
});
|
|
437
646
|
}
|
|
647
|
+
await Promise.all([
|
|
648
|
+
FileHandler.writeJsonData(caveFilePath, caveData),
|
|
649
|
+
FileHandler.writeJsonData(pendingFilePath, pendingData)
|
|
650
|
+
]);
|
|
438
651
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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] = [];
|
|
452
665
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
await
|
|
456
|
-
|
|
666
|
+
this.stats[cave.contributor_number].push(cave.cave_id);
|
|
667
|
+
}
|
|
668
|
+
await this.saveStatus();
|
|
669
|
+
this.initialized = true;
|
|
457
670
|
} catch (error) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
671
|
+
this.initialized = false;
|
|
672
|
+
logger.error(`IdManager initialization failed: ${error.message}`);
|
|
673
|
+
throw error;
|
|
461
674
|
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
await ctx.bots[0]?.sendPrivateMessage(managerId, auditMessage);
|
|
474
|
-
} catch (error) {
|
|
475
|
-
logger.error(session.text("commands.cave.audit.sendFailed", [managerId]));
|
|
675
|
+
}
|
|
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);
|
|
476
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;
|
|
477
695
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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];
|
|
494
723
|
}
|
|
724
|
+
await this.saveStatus();
|
|
495
725
|
}
|
|
496
|
-
return true;
|
|
497
|
-
} catch (error) {
|
|
498
|
-
return sendTempMessage(session, "commands.cave.error.auditProcess", [error.message]);
|
|
499
726
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (pendingData.length === 0) return sendMessage(session, "commands.cave.audit.noPending", [], true);
|
|
504
|
-
if (typeof targetId === "number") {
|
|
505
|
-
const pendingIndex = pendingData.findIndex((item) => item.cave_id === targetId);
|
|
506
|
-
if (pendingIndex === -1) return sendMessage(session, "commands.cave.audit.pendingNotFound", [], true);
|
|
507
|
-
const cave = pendingData[pendingIndex];
|
|
508
|
-
const data2 = isApprove ? await FileHandler.readJsonData(caveFilePath, session) : null;
|
|
509
|
-
const auditResult = await handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data2, session);
|
|
510
|
-
if (typeof auditResult === "string") return auditResult;
|
|
511
|
-
if (isApprove && data2) await FileHandler.writeJsonData(caveFilePath, data2, session);
|
|
512
|
-
pendingData.splice(pendingIndex, 1);
|
|
513
|
-
await FileHandler.writeJsonData(pendingFilePath, pendingData, session);
|
|
514
|
-
const remainingCount = pendingData.length;
|
|
515
|
-
if (remainingCount > 0) {
|
|
516
|
-
const remainingIds = pendingData.map((c) => c.cave_id).join(", ");
|
|
517
|
-
const action = isApprove ? "auditPassed" : "auditRejected";
|
|
518
|
-
return sendMessage(session, "commands.cave.audit.pendingResult", [
|
|
519
|
-
session.text(`commands.cave.audit.${action}`),
|
|
520
|
-
remainingCount,
|
|
521
|
-
remainingIds
|
|
522
|
-
], false);
|
|
523
|
-
}
|
|
524
|
-
return sendMessage(
|
|
525
|
-
session,
|
|
526
|
-
isApprove ? "commands.cave.audit.auditPassed" : "commands.cave.audit.auditRejected",
|
|
527
|
-
[],
|
|
528
|
-
false
|
|
529
|
-
// 审核结果改为永久消息
|
|
530
|
-
);
|
|
727
|
+
// 获取统计信息
|
|
728
|
+
getStats() {
|
|
729
|
+
return this.stats;
|
|
531
730
|
}
|
|
532
|
-
|
|
533
|
-
let processedCount = 0;
|
|
534
|
-
for (const cave of pendingData) {
|
|
535
|
-
await handleSingleCaveAudit(ctx, cave, isApprove, resourceDir, data, session) && processedCount++;
|
|
536
|
-
}
|
|
537
|
-
if (isApprove && data) await FileHandler.writeJsonData(caveFilePath, data, session);
|
|
538
|
-
await FileHandler.writeJsonData(pendingFilePath, [], session);
|
|
539
|
-
return sendMessage(session, "commands.cave.audit.batchAuditResult", [
|
|
540
|
-
isApprove ? "通过" : "拒绝",
|
|
541
|
-
processedCount,
|
|
542
|
-
pendingData.length
|
|
543
|
-
], false);
|
|
544
|
-
}
|
|
545
|
-
__name(handleAudit, "handleAudit");
|
|
546
|
-
function cleanElementsForSave(elements, keepIndex = false) {
|
|
547
|
-
const sorted = elements.sort((a, b) => a.index - b.index);
|
|
548
|
-
return sorted.map(({ type, content, file, index }) => ({
|
|
549
|
-
type,
|
|
550
|
-
...keepIndex && { index },
|
|
551
|
-
...content && { content },
|
|
552
|
-
...file && { file }
|
|
553
|
-
}));
|
|
554
|
-
}
|
|
555
|
-
__name(cleanElementsForSave, "cleanElementsForSave");
|
|
556
|
-
async function sendTempMessage(session, key, params = [], timeout = 1e4) {
|
|
557
|
-
const msg = await session.send(session.text(key, params));
|
|
558
|
-
setTimeout(async () => {
|
|
731
|
+
async saveStatus() {
|
|
559
732
|
try {
|
|
560
|
-
|
|
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);
|
|
561
742
|
} catch (error) {
|
|
562
|
-
logger.error(
|
|
743
|
+
logger.error(`Failed to save status: ${error.message}`);
|
|
744
|
+
throw error;
|
|
563
745
|
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
__name(sendTempMessage, "sendTempMessage");
|
|
568
|
-
var messageQueue = /* @__PURE__ */ new Map();
|
|
746
|
+
}
|
|
747
|
+
};
|
|
569
748
|
async function sendMessage(session, key, params = [], isTemp = true, timeout = 1e4) {
|
|
570
|
-
|
|
571
|
-
const sendOperation = /* @__PURE__ */ __name(async () => {
|
|
749
|
+
try {
|
|
572
750
|
const msg = await session.send(session.text(key, params));
|
|
573
|
-
if (isTemp) {
|
|
751
|
+
if (isTemp && msg) {
|
|
574
752
|
setTimeout(async () => {
|
|
575
753
|
try {
|
|
576
|
-
await session.bot.deleteMessage(channelId, msg);
|
|
754
|
+
await session.bot.deleteMessage(session.channelId, msg);
|
|
577
755
|
} catch (error) {
|
|
578
|
-
logger.
|
|
756
|
+
logger.debug(`Failed to delete temporary message: ${error.message}`);
|
|
579
757
|
}
|
|
580
758
|
}, timeout);
|
|
581
759
|
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
messageQueue.set(channelId, Promise.resolve());
|
|
760
|
+
} catch (error) {
|
|
761
|
+
logger.error(`Failed to send message: ${error.message}`);
|
|
585
762
|
}
|
|
586
|
-
const currentPromise = messageQueue.get(channelId).then(sendOperation).finally(() => {
|
|
587
|
-
if (messageQueue.get(channelId) === currentPromise) {
|
|
588
|
-
messageQueue.delete(channelId);
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
messageQueue.set(channelId, currentPromise);
|
|
592
763
|
return "";
|
|
593
764
|
}
|
|
594
765
|
__name(sendMessage, "sendMessage");
|
|
595
|
-
async function
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
const srcMatch = img.match(/src="([^"]+)"/);
|
|
608
|
-
const fileName = img.match(/file="([^"]+)"/)?.[1];
|
|
609
|
-
const fileSize = img.match(/fileSize="([^"]+)"/)?.[1];
|
|
610
|
-
if (srcMatch?.[1]) {
|
|
611
|
-
imageUrls.push(srcMatch[1]);
|
|
612
|
-
imageElements.push({ type: "img", index: idx * 3 + 1, fileName, fileSize });
|
|
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) {
|
|
773
|
+
try {
|
|
774
|
+
await bot.sendPrivateMessage(managerId, auditMessage);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
logger.error(session.text("commands.cave.audit.sendFailed", [managerId]));
|
|
777
|
+
}
|
|
613
778
|
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
779
|
+
}
|
|
780
|
+
}
|
|
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;
|
|
623
800
|
}
|
|
801
|
+
return element;
|
|
624
802
|
});
|
|
625
|
-
return
|
|
803
|
+
return keepIndex ? cleanedElements.sort((a, b) => (a.index || 0) - (b.index || 0)) : cleanedElements;
|
|
626
804
|
}
|
|
627
|
-
__name(
|
|
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");
|
|
628
812
|
async function buildMessage(cave, resourceDir, session) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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 }));
|
|
828
|
+
}
|
|
829
|
+
return "";
|
|
830
|
+
}
|
|
831
|
+
const lines = [session.text("commands.cave.message.caveTitle", [cave.cave_id])];
|
|
832
|
+
for (const element of nonVideoElements) {
|
|
632
833
|
if (element.type === "text") {
|
|
633
|
-
|
|
834
|
+
lines.push(element.content);
|
|
634
835
|
} else if (element.type === "img" && element.file) {
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
content += (0, import_koishi.h)("image", { src: `data:image/png;base64,${imageBuffer.toString("base64")}` }) + "\n";
|
|
640
|
-
} catch (error) {
|
|
641
|
-
content += session.text("commands.cave.error.mediaLoadFailed", ["图片"]) + "\n";
|
|
642
|
-
}
|
|
643
|
-
} else {
|
|
644
|
-
content += session.text("commands.cave.message.mediaInvalid", ["图片"]) + "\n";
|
|
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 }));
|
|
645
840
|
}
|
|
646
|
-
} else if (element.type === "video" && element.file) {
|
|
647
|
-
videoElements.push({ file: element.file });
|
|
648
841
|
}
|
|
649
842
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
843
|
+
lines.push(session.text("commands.cave.message.contributorSuffix", [cave.contributor_name]));
|
|
844
|
+
return lines.join("\n");
|
|
845
|
+
}
|
|
846
|
+
__name(buildMessage, "buildMessage");
|
|
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]));
|
|
660
868
|
}
|
|
661
|
-
} else {
|
|
662
|
-
content += session.text("commands.cave.message.mediaInvalid", ["视频"]) + "\n";
|
|
663
869
|
}
|
|
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++;
|
|
664
879
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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 };
|
|
668
887
|
}
|
|
669
|
-
__name(
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
this.lastPendingUpdate = now;
|
|
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(/&/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"
|
|
907
|
+
}
|
|
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;
|
|
697
915
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
704
|
-
static updatePendingData(data) {
|
|
705
|
-
this.pendingDataCache = data;
|
|
706
|
-
this.lastPendingUpdate = Date.now();
|
|
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`));
|
|
707
921
|
}
|
|
708
|
-
|
|
922
|
+
return successfulResults;
|
|
923
|
+
}
|
|
924
|
+
__name(saveMedia, "saveMedia");
|
|
709
925
|
// Annotate the CommonJS export names for ESM import in node:
|
|
710
926
|
0 && (module.exports = {
|
|
711
927
|
Config,
|