koishi-plugin-best-cave 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 shangxue
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/lib/index.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { Context, Schema } from 'koishi';
2
+ export declare const name = "cave";
3
+ export declare const inject: string[];
4
+ export interface User {
5
+ userId: string;
6
+ username: string;
7
+ nickname?: string;
8
+ }
9
+ export interface getStrangerInfo {
10
+ user_id: string;
11
+ nickname: string;
12
+ }
13
+ export interface Config {
14
+ manager: string[];
15
+ number: number;
16
+ enableAudit: boolean;
17
+ }
18
+ export declare const Config: Schema<Config>;
19
+ export declare function apply(ctx: Context, config: Config): Promise<void>;
package/lib/index.js ADDED
@@ -0,0 +1,502 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ Config: () => Config,
34
+ apply: () => apply,
35
+ inject: () => inject,
36
+ name: () => name
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+ var import_koishi = require("koishi");
40
+ var fs = __toESM(require("fs"));
41
+ var path = __toESM(require("path"));
42
+ var logger = new import_koishi.Logger("cave");
43
+ var name = "cave";
44
+ var inject = ["database"];
45
+ var Config = import_koishi.Schema.object({
46
+ manager: import_koishi.Schema.array(import_koishi.Schema.string()).required().description("管理员账号"),
47
+ number: import_koishi.Schema.number().default(60).description("群内调用冷却时间(秒)"),
48
+ enableAudit: import_koishi.Schema.boolean().default(false).description("是否开启审核功能")
49
+ });
50
+ function readJsonData(filePath, validator) {
51
+ try {
52
+ const data = fs.readFileSync(filePath, "utf8");
53
+ const parsed = JSON.parse(data || "[]");
54
+ if (!Array.isArray(parsed)) return [];
55
+ return validator ? parsed.filter(validator) : parsed;
56
+ } catch (error) {
57
+ logger.error(`读取文件失败 ${filePath}: ${error.message}`);
58
+ return [];
59
+ }
60
+ }
61
+ __name(readJsonData, "readJsonData");
62
+ function writeJsonData(filePath, data) {
63
+ try {
64
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
65
+ } catch (error) {
66
+ logger.error(`写入文件失败: ${error.message}`);
67
+ throw error;
68
+ }
69
+ }
70
+ __name(writeJsonData, "writeJsonData");
71
+ async function ensureDirectory(dir) {
72
+ try {
73
+ if (!fs.existsSync(dir)) {
74
+ await fs.promises.mkdir(dir, { recursive: true });
75
+ }
76
+ } catch (error) {
77
+ logger.error(`创建目录失败 ${dir}: ${error.message}`);
78
+ throw error;
79
+ }
80
+ }
81
+ __name(ensureDirectory, "ensureDirectory");
82
+ async function ensureJsonFile(filePath, defaultContent = "[]") {
83
+ try {
84
+ if (!fs.existsSync(filePath)) {
85
+ await fs.promises.writeFile(filePath, defaultContent, "utf8");
86
+ }
87
+ } catch (error) {
88
+ logger.error(`创建文件失败 ${filePath}: ${error.message}`);
89
+ throw error;
90
+ }
91
+ }
92
+ __name(ensureJsonFile, "ensureJsonFile");
93
+ async function saveImages(urls, imageDir, caveId, config, ctx) {
94
+ const savedFiles = [];
95
+ for (let i = 0; i < urls.length; i++) {
96
+ try {
97
+ const url = urls[i];
98
+ const processedUrl = (() => {
99
+ try {
100
+ const decodedUrl = decodeURIComponent(url);
101
+ if (decodedUrl.includes("multimedia.nt.qq.com.cn")) {
102
+ return decodedUrl.replace(/&amp;/g, "&");
103
+ }
104
+ return url;
105
+ } catch {
106
+ return url;
107
+ }
108
+ })();
109
+ const ext = url.match(/\.([^./?]+)(?:[?#]|$)/)?.[1] || "png";
110
+ const filename = `${caveId}_${i + 1}.${ext}`;
111
+ const targetPath = path.join(imageDir, filename);
112
+ const buffer = await ctx.http.get(processedUrl, {
113
+ responseType: "arraybuffer",
114
+ timeout: 3e4,
115
+ headers: {
116
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
117
+ "Accept": "image/*",
118
+ "Referer": "https://qq.com"
119
+ }
120
+ });
121
+ if (buffer && buffer.byteLength > 0) {
122
+ await fs.promises.writeFile(targetPath, Buffer.from(buffer));
123
+ savedFiles.push(filename);
124
+ }
125
+ } catch (error) {
126
+ logger.error(`保存图片失败: ${error.message}`);
127
+ }
128
+ }
129
+ return savedFiles;
130
+ }
131
+ __name(saveImages, "saveImages");
132
+ async function sendAuditMessage(ctx, config, cave, content) {
133
+ const auditMessage = `待审核回声洞:
134
+ ${content}
135
+ 来自:${cave.contributor_number}`;
136
+ for (const managerId of config.manager) {
137
+ try {
138
+ await ctx.bots[0]?.sendPrivateMessage(managerId, auditMessage);
139
+ } catch (error) {
140
+ logger.error(`发送审核消息 ${managerId} 失败: ${error.message}`);
141
+ }
142
+ }
143
+ }
144
+ __name(sendAuditMessage, "sendAuditMessage");
145
+ async function handleSingleCaveAudit(ctx, cave, isApprove, imageDir, data) {
146
+ try {
147
+ if (isApprove && data) {
148
+ const caveWithoutIndex = {
149
+ ...cave,
150
+ elements: cleanElementsForSave(cave.elements, false)
151
+ };
152
+ data.push(caveWithoutIndex);
153
+ logger.info(`审核通过回声洞(${cave.cave_id})`);
154
+ } else if (!isApprove && cave.elements) {
155
+ for (const element of cave.elements) {
156
+ if (element.type === "img" && element.file) {
157
+ const fullPath = path.join(imageDir, element.file);
158
+ if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
159
+ }
160
+ }
161
+ logger.info(`审核失败回声洞(${cave.cave_id})`);
162
+ }
163
+ return true;
164
+ } catch (error) {
165
+ logger.error(`处理回声洞(${cave.cave_id})失败: ${error.message}`);
166
+ return false;
167
+ }
168
+ }
169
+ __name(handleSingleCaveAudit, "handleSingleCaveAudit");
170
+ async function handleAudit(ctx, pendingData, isApprove, caveFilePath, imageDir, pendingFilePath, targetId) {
171
+ if (pendingData.length === 0) return "没有待审核回声洞";
172
+ if (typeof targetId === "number") {
173
+ const pendingIndex = pendingData.findIndex((item) => item.cave_id === targetId);
174
+ if (pendingIndex === -1) return "未找到该待审核回声洞";
175
+ const cave = pendingData[pendingIndex];
176
+ const data2 = isApprove ? readJsonData(caveFilePath) : null;
177
+ const success = await handleSingleCaveAudit(ctx, cave, isApprove, imageDir, data2);
178
+ if (!success) return "处理失败,请稍后重试";
179
+ if (isApprove && data2) writeJsonData(caveFilePath, data2);
180
+ pendingData.splice(pendingIndex, 1);
181
+ writeJsonData(pendingFilePath, pendingData);
182
+ const remainingCount = pendingData.length;
183
+ if (remainingCount > 0) {
184
+ const remainingIds = pendingData.map((c) => c.cave_id).join(", ");
185
+ return `${isApprove ? "审核通过" : "拒绝"}成功,还有 ${remainingCount} 条待审核:[${remainingIds}]`;
186
+ }
187
+ return isApprove ? "已通过该回声洞" : "已拒绝该回声洞";
188
+ }
189
+ const data = isApprove ? readJsonData(caveFilePath) : null;
190
+ let processedCount = 0;
191
+ for (const cave of pendingData) {
192
+ const success = await handleSingleCaveAudit(ctx, cave, isApprove, imageDir, data);
193
+ if (success) processedCount++;
194
+ }
195
+ if (isApprove && data) writeJsonData(caveFilePath, data);
196
+ writeJsonData(pendingFilePath, []);
197
+ return isApprove ? `✅ 已通过 ${processedCount}/${pendingData.length} 条回声洞` : `❌ 已拒绝 ${processedCount}/${pendingData.length} 条回声洞`;
198
+ }
199
+ __name(handleAudit, "handleAudit");
200
+ function buildMessage(cave, imageDir) {
201
+ let content = `回声洞 ——(${cave.cave_id})
202
+ `;
203
+ for (const element of cave.elements) {
204
+ if (element.type === "text") {
205
+ content += element.content + "\n";
206
+ } else if (element.type === "img" && element.file) {
207
+ try {
208
+ const fullImagePath = path.join(imageDir, element.file);
209
+ if (fs.existsSync(fullImagePath)) {
210
+ const imageBuffer = fs.readFileSync(fullImagePath);
211
+ const base64Image = imageBuffer.toString("base64");
212
+ content += (0, import_koishi.h)("image", { src: `data:image/png;base64,${base64Image}` }) + "\n";
213
+ }
214
+ } catch (error) {
215
+ logger.error(`读取图片失败: ${error.message}`);
216
+ }
217
+ }
218
+ }
219
+ return content + `—— ${cave.contributor_name}`;
220
+ }
221
+ __name(buildMessage, "buildMessage");
222
+ function cleanElementsForSave(elements, keepIndex = false) {
223
+ const sorted = elements.sort((a, b) => a.index - b.index);
224
+ return sorted.map(({ type, content, file, index }) => ({
225
+ type,
226
+ ...keepIndex && { index },
227
+ ...content && { content },
228
+ ...file && { file }
229
+ }));
230
+ }
231
+ __name(cleanElementsForSave, "cleanElementsForSave");
232
+ async function apply(ctx, config) {
233
+ const dataDir = path.join(ctx.baseDir, "data");
234
+ const caveDir = path.join(dataDir, "cave");
235
+ const caveFilePath = path.join(caveDir, "cave.json");
236
+ const imageDir = path.join(caveDir, "images");
237
+ const pendingFilePath = path.join(caveDir, "pending.json");
238
+ try {
239
+ await ensureDirectory(dataDir);
240
+ await ensureDirectory(caveDir);
241
+ await ensureDirectory(imageDir);
242
+ await ensureJsonFile(caveFilePath);
243
+ await ensureJsonFile(pendingFilePath);
244
+ } catch (error) {
245
+ logger.error("初始化目录结构失败:", error);
246
+ throw error;
247
+ }
248
+ const lastUsed = /* @__PURE__ */ new Map();
249
+ ctx.command("cave", "回声洞").usage("支持添加、抽取、查看、查询回声洞").example("cave 随机抽取回声洞").example("cave -a 内容 添加新回声洞").example("cave -g/r x 查看/删除指定回声洞").example("cave -p/d x/all 通过/拒绝待审回声洞").example("cave -l x 查询投稿者投稿列表").option("a", "添加回声洞").option("g", "查看回声洞", { type: "string" }).option("r", "删除回声洞", { type: "string" }).option("p", "通过审核", { type: "string" }).option("d", "拒绝审核", { type: "string" }).option("l", "查询投稿统计", { type: "string" }).before(async ({ session, options }) => {
250
+ if ((options.l || options.p || options.d) && !config.manager.includes(session.userId)) {
251
+ return "只有管理员才能执行此操作";
252
+ }
253
+ }).action(async ({ session, options }, ...content) => {
254
+ if (options.l !== void 0) {
255
+ let formatIds = function(ids) {
256
+ const lines = [];
257
+ for (let i = 0; i < ids.length; i += 10) {
258
+ lines.push(ids.slice(i, i + 10).join(", "));
259
+ }
260
+ return lines.join("\n");
261
+ };
262
+ __name(formatIds, "formatIds");
263
+ const caveFilePath2 = path.join(ctx.baseDir, "data", "cave", "cave.json");
264
+ const caveDir2 = path.join(ctx.baseDir, "data", "cave");
265
+ const caveData = readJsonData(caveFilePath2);
266
+ const stats = {};
267
+ for (const cave of caveData) {
268
+ if (cave.contributor_number === "10000") continue;
269
+ if (!stats[cave.contributor_number]) stats[cave.contributor_number] = [];
270
+ stats[cave.contributor_number].push(cave.cave_id);
271
+ }
272
+ const statFilePath = path.join(caveDir2, "stat.json");
273
+ try {
274
+ fs.writeFileSync(statFilePath, JSON.stringify(stats, null, 2), "utf8");
275
+ } catch (error) {
276
+ logger.error(`写入投稿统计失败: ${error.message}`);
277
+ }
278
+ let queryId = null;
279
+ if (typeof options.l === "string") {
280
+ const match = String(options.l).match(/\d+/);
281
+ if (match) queryId = match[0];
282
+ } else if (!queryId && content.length > 0) {
283
+ const numberMatch = content.join(" ").match(/\d+/);
284
+ if (numberMatch) {
285
+ queryId = numberMatch[0];
286
+ }
287
+ }
288
+ if (queryId) {
289
+ if (stats[queryId]) {
290
+ const count = stats[queryId].length;
291
+ return `${queryId} 共计投稿 ${count} 项回声洞:
292
+ ` + formatIds(stats[queryId]);
293
+ } else {
294
+ return `未找到投稿者 ${queryId}`;
295
+ }
296
+ } else {
297
+ let total = 0;
298
+ const lines = Object.entries(stats).map(([cid, ids]) => {
299
+ total += ids.length;
300
+ return `${cid} 共计投稿 ${ids.length} 项回声洞:
301
+ ` + formatIds(ids);
302
+ });
303
+ return `共计投稿 ${total} 项回声洞:
304
+ ` + lines.join("\n");
305
+ }
306
+ }
307
+ try {
308
+ if (options.p || options.d) {
309
+ const pendingData = readJsonData(pendingFilePath);
310
+ const isApprove = Boolean(options.p);
311
+ if (options.p === true && content[0] === "all" || options.d === true && content[0] === "all") {
312
+ return await handleAudit(ctx, pendingData, isApprove, caveFilePath, imageDir, pendingFilePath);
313
+ }
314
+ const id = parseInt(content[0] || (typeof options.p === "string" ? options.p : "") || (typeof options.d === "string" ? options.d : ""));
315
+ if (isNaN(id)) return "请输入正确的回声洞序号";
316
+ return await handleAudit(ctx, pendingData, isApprove, caveFilePath, imageDir, pendingFilePath, id);
317
+ }
318
+ const data = readJsonData(
319
+ caveFilePath,
320
+ (item) => item && typeof item.cave_id === "number" && Array.isArray(item.elements) && item.elements.every(
321
+ (el) => el.type === "text" && typeof el.content === "string" || el.type === "img" && typeof el.file === "string"
322
+ ) && typeof item.contributor_number === "string" && typeof item.contributor_name === "string"
323
+ );
324
+ if (options.a) {
325
+ const originalContent = session.quote?.content || session.content;
326
+ const elements = [];
327
+ const imageUrls = [];
328
+ const prefixes = Array.isArray(session.app.config.prefix) ? session.app.config.prefix : [session.app.config.prefix];
329
+ const nicknames = Array.isArray(session.app.config.nickname) ? session.app.config.nickname : session.app.config.nickname ? [session.app.config.nickname] : [];
330
+ const allTriggers = [...prefixes, ...nicknames];
331
+ const triggerPattern = allTriggers.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
332
+ const commandPattern = new RegExp(`^(?:${triggerPattern})?\\s*cave -a\\s*`);
333
+ const textParts = originalContent.replace(commandPattern, "").split(/<img[^>]+>/g).map((text) => text.trim()).filter((text) => text).map((text, idx) => ({
334
+ type: "text",
335
+ content: text,
336
+ index: idx * 2
337
+ // 文本使用偶数索引
338
+ }));
339
+ const imgMatches = originalContent.match(/<img[^>]+src="([^"]+)"[^>]*>/g) || [];
340
+ const imageElements = imgMatches.map((img, idx) => {
341
+ const match = img.match(/src="([^"]+)"/);
342
+ if (match?.[1]) {
343
+ imageUrls.push(match[1]);
344
+ return {
345
+ type: "img",
346
+ index: idx * 2 + 1
347
+ // 图片使用奇数索引
348
+ };
349
+ }
350
+ return null;
351
+ }).filter((el) => el !== null);
352
+ const pendingData = readJsonData(pendingFilePath);
353
+ const maxDataId = data.length > 0 ? Math.max(...data.map((item) => item.cave_id)) : 0;
354
+ const maxPendingId = pendingData.length > 0 ? Math.max(...pendingData.map((item) => item.cave_id)) : 0;
355
+ const caveId = Math.max(maxDataId, maxPendingId) + 1;
356
+ let savedImages = [];
357
+ if (imageUrls.length > 0) {
358
+ try {
359
+ savedImages = await saveImages(imageUrls, imageDir, caveId, config, ctx);
360
+ } catch (error) {
361
+ logger.error(`保存图片失败: ${error.message}`);
362
+ }
363
+ }
364
+ elements.push(...textParts);
365
+ savedImages.forEach((file, idx) => {
366
+ if (imageElements[idx]) {
367
+ elements.push({
368
+ ...imageElements[idx],
369
+ type: "img",
370
+ file
371
+ });
372
+ }
373
+ });
374
+ elements.sort((a, b) => a.index - b.index);
375
+ if (elements.length === 0) {
376
+ return "添加失败:无内容,请尝试重新发送";
377
+ }
378
+ let contributorName = session.username;
379
+ if (ctx.database) {
380
+ try {
381
+ const userInfo = await ctx.database.getUser(session.platform, session.userId);
382
+ contributorName = userInfo?.nickname || session.username;
383
+ } catch (error) {
384
+ logger.error(`获取用户昵称失败: ${error.message}`);
385
+ }
386
+ }
387
+ const newCave = {
388
+ cave_id: caveId,
389
+ elements: cleanElementsForSave(elements, true),
390
+ contributor_number: session.userId,
391
+ contributor_name: contributorName
392
+ };
393
+ if (config.enableAudit) {
394
+ pendingData.push({
395
+ ...newCave,
396
+ elements: cleanElementsForSave(elements, true)
397
+ });
398
+ writeJsonData(pendingFilePath, pendingData);
399
+ await sendAuditMessage(ctx, config, newCave, buildMessage(newCave, imageDir));
400
+ return `✨ 已提交审核,序号为 (${caveId})`;
401
+ }
402
+ const caveWithoutIndex = {
403
+ ...newCave,
404
+ elements: cleanElementsForSave(elements, false)
405
+ };
406
+ data.push(caveWithoutIndex);
407
+ writeJsonData(caveFilePath, data);
408
+ return `✨ 添加成功!序号为 (${caveId})`;
409
+ }
410
+ if (options.g) {
411
+ const caveId = parseInt(content[0] || (typeof options.g === "string" ? options.g : ""));
412
+ if (isNaN(caveId)) {
413
+ return "请输入正确的回声洞序号";
414
+ }
415
+ const cave = data.find((item) => item.cave_id === caveId);
416
+ if (!cave) {
417
+ return "未找到该序号的回声洞";
418
+ }
419
+ return buildMessage(cave, imageDir);
420
+ }
421
+ if (!options.a && !options.g && !options.r) {
422
+ if (data.length === 0) return "暂无回声洞可用";
423
+ const guildId = session.guildId;
424
+ const now = Date.now();
425
+ const lastCall = lastUsed.get(guildId) || 0;
426
+ const isManager = config.manager.includes(session.userId);
427
+ if (!isManager && now - lastCall < config.number * 1e3) {
428
+ const waitTime = Math.ceil((config.number * 1e3 - (now - lastCall)) / 1e3);
429
+ return `群聊冷却中...请${waitTime}秒后再试`;
430
+ }
431
+ if (!isManager) {
432
+ lastUsed.set(guildId, now);
433
+ }
434
+ const cave = (() => {
435
+ const validCaves = data.filter((cave2) => cave2.elements && cave2.elements.length > 0);
436
+ if (!validCaves.length) return void 0;
437
+ const randomIndex = Math.floor(Math.random() * validCaves.length);
438
+ return validCaves[randomIndex];
439
+ })();
440
+ if (!cave) return "获取回声洞失败";
441
+ return buildMessage(cave, imageDir);
442
+ }
443
+ if (options.r) {
444
+ const caveId = parseInt(content[0] || (typeof options.r === "string" ? options.r : ""));
445
+ if (isNaN(caveId)) {
446
+ return "请输入正确的回声洞序号";
447
+ }
448
+ const index = data.findIndex((item) => item.cave_id === caveId);
449
+ const pendingData = readJsonData(pendingFilePath);
450
+ const pendingIndex = pendingData.findIndex((item) => item.cave_id === caveId);
451
+ if (index === -1 && pendingIndex === -1) {
452
+ return "未找到该序号的回声洞";
453
+ }
454
+ let targetCave;
455
+ let isPending = false;
456
+ if (index !== -1) {
457
+ targetCave = data[index];
458
+ } else {
459
+ targetCave = pendingData[pendingIndex];
460
+ isPending = true;
461
+ }
462
+ if (targetCave.contributor_number !== session.userId && !config.manager.includes(session.userId)) {
463
+ return "你不是这条回声洞的添加者!";
464
+ }
465
+ if (targetCave.elements) {
466
+ try {
467
+ for (const element of targetCave.elements) {
468
+ if (element.type === "img" && element.file) {
469
+ const fullPath = path.join(imageDir, element.file);
470
+ if (fs.existsSync(fullPath)) {
471
+ fs.unlinkSync(fullPath);
472
+ }
473
+ }
474
+ }
475
+ } catch (error) {
476
+ logger.error(`删除图片失败: ${error.message}`);
477
+ }
478
+ }
479
+ if (isPending) {
480
+ pendingData.splice(pendingIndex, 1);
481
+ writeJsonData(pendingFilePath, pendingData);
482
+ return `✅ 已删除待审核回声洞 (${caveId})`;
483
+ } else {
484
+ data.splice(index, 1);
485
+ writeJsonData(caveFilePath, data);
486
+ return `✅ 已删除回声洞 (${caveId})`;
487
+ }
488
+ }
489
+ } catch (error) {
490
+ logger.error(`操作失败: ${error.message}`);
491
+ return "操作失败,请稍后重试";
492
+ }
493
+ });
494
+ }
495
+ __name(apply, "apply");
496
+ // Annotate the CommonJS export names for ESM import in node:
497
+ 0 && (module.exports = {
498
+ Config,
499
+ apply,
500
+ inject,
501
+ name
502
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "koishi-plugin-best-cave",
3
+ "description": "最好的 cave 插件,可开关的审核系统,可引用添加,支持图文混合内容,可查阅投稿列表,完美复刻你的 .cave 体验!",
4
+ "version": "0.0.1",
5
+ "contributors": [
6
+ "Yis_Rime <yis_rime@outlook.com>"
7
+ ],
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/YisRime/koishi-plugin-best-cave.git"
11
+ },
12
+ "main": "lib/index.js",
13
+ "typings": "lib/index.d.ts",
14
+ "files": [
15
+ "lib",
16
+ "dist"
17
+ ],
18
+ "license": "MIT",
19
+ "scripts": {},
20
+ "keywords": [
21
+ "chatbot",
22
+ "koishi",
23
+ "plugin",
24
+ "cave"
25
+ ],
26
+ "devDependencies": {},
27
+ "peerDependencies": {
28
+ "koishi": "^4.18.3"
29
+ }
30
+ }
package/readme.md ADDED
@@ -0,0 +1,39 @@
1
+ # koishi-plugin-best-cave
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-best-cave?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-best-cave)
4
+
5
+ ## 简介
6
+
7
+ 最好的 cave 插件,可开关的审核系统,可引用添加,支持图文混合内容,可查阅投稿列表,完美复刻你的 .cave 体验!
8
+
9
+ ## 核心功能
10
+
11
+ - 支持文字与图片混合保存
12
+ - 智能处理各类图片链接
13
+ - 完整的权限管理系统
14
+ - 可选的内容审核流程
15
+ - 群组调用冷却机制
16
+
17
+ ## 基础指令
18
+
19
+ | 指令 | 说明 | 权限 |
20
+ |------|------|------|
21
+ | `cave` | 随机展示一条回声洞 | 所有人 |
22
+ | `cave -a <内容>` | 添加新回声洞 | 所有人 |
23
+ | `cave -g <编号>` | 查看指定回声洞 | 所有人 |
24
+ | `cave -r <编号>` | 删除指定回声洞 | 内容贡献者/管理员 |
25
+
26
+ ## 管理指令
27
+
28
+ | 指令 | 说明 | 权限 |
29
+ |------|------|------|
30
+ | `cave -l [用户ID]` | 查看投稿统计 | 管理员 |
31
+ | `cave -p <编号/all>` | 通过待审核内容 | 管理员 |
32
+ | `cave -d <编号/all>` | 拒绝待审核内容 | 管理员 |
33
+
34
+ ## 注意事项
35
+
36
+ 1. 图片会自动保存到本地,请确保存储空间充足
37
+ 2. 管理员不受群组冷却时间限制
38
+ 3. 开启审核模式后,新内容需要审核才能生效
39
+ 4. 引用消息添加支持保留原消息格式