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.
- package/lib/index.d.ts +25 -8
- package/lib/index.js +782 -511
- 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
|
|
|
@@ -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
|
|
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
|
|
413
|
+
return sendMessage(session, "commands.cave.message.blacklisted", [], true);
|
|
86
414
|
}
|
|
87
|
-
if (
|
|
88
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
502
|
+
/**
|
|
503
|
+
* 事务处理
|
|
504
|
+
*/
|
|
505
|
+
static async withTransaction(operations) {
|
|
506
|
+
const results = [];
|
|
507
|
+
const completed = /* @__PURE__ */ new Set();
|
|
112
508
|
try {
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
167
|
-
await
|
|
168
|
-
|
|
666
|
+
this.stats[cave.contributor_number].push(cave.cave_id);
|
|
667
|
+
}
|
|
668
|
+
await this.saveStatus();
|
|
669
|
+
this.initialized = true;
|
|
169
670
|
} catch (error) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
671
|
+
this.initialized = false;
|
|
672
|
+
logger.error(`IdManager initialization failed: ${error.message}`);
|
|
673
|
+
throw error;
|
|
173
674
|
}
|
|
174
675
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
743
|
+
logger.error(`Failed to save status: ${error.message}`);
|
|
744
|
+
throw error;
|
|
274
745
|
}
|
|
275
|
-
}
|
|
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(
|
|
279
|
-
async function
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
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
|
|
774
|
+
await bot.sendPrivateMessage(managerId, auditMessage);
|
|
285
775
|
} catch (error) {
|
|
286
|
-
logger.error("
|
|
776
|
+
logger.error(session.text("commands.cave.audit.sendFailed", [managerId]));
|
|
287
777
|
}
|
|
288
|
-
}
|
|
778
|
+
}
|
|
289
779
|
}
|
|
290
|
-
return "";
|
|
291
780
|
}
|
|
292
|
-
__name(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
803
|
+
return keepIndex ? cleanedElements.sort((a, b) => (a.index || 0) - (b.index || 0)) : cleanedElements;
|
|
324
804
|
}
|
|
325
|
-
__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");
|
|
326
812
|
async function buildMessage(cave, resourceDir, session) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
365
|
-
return
|
|
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
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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(/&/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
|
-
}
|
|
636
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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(
|
|
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
|
});
|