UNPKG

koishi-plugin-cave

Version:

A mysterious cave where the sound is echoed over and over again.

630 lines (624 loc) 23.4 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, getFileCountPlusOne: () => getFileCountPlusOne, getMaxCaveId: () => getMaxCaveId, name: () => name, saveImages: () => saveImages }); module.exports = __toCommonJS(src_exports); var import_koishi = require("koishi"); var fs = __toESM(require("fs")); var path = __toESM(require("path")); var import_util = __toESM(require("util")); var logger = new import_koishi.Logger("cave"); var name = "cave"; var Config = import_koishi.Schema.intersect([ import_koishi.Schema.object({ manager: import_koishi.Schema.array(import_koishi.Schema.string()).required().description("管理员QQ,一个项目填一个ID"), consoleinfo: import_koishi.Schema.boolean().default(false).description("日志调试模式"), consolefor: import_koishi.Schema.boolean().default(false).description("开启-g指令指定回声洞"), number: import_koishi.Schema.number().default(3).description("群单位回声洞冷却时间,单位为秒"), helpinfo: import_koishi.Schema.boolean().default(true).description("开启冷却显示"), nameinfo: import_koishi.Schema.boolean().default(false).description("使用陌生人接口获取昵称") }) ]); async function saveImages(urls, selectedPath, safeFilename, imageExtension, config, session, ctx) { let url = urls; let fileRoot = path.join(selectedPath, safeFilename); let fileExt = `.${imageExtension}`; let targetPath = `${fileRoot}${fileExt}`; let index = 0; if (config.consoleinfo) { logger.info("提取到的图片链接:" + url); } while (fs.existsSync(targetPath)) { index++; targetPath = `${fileRoot}_${index}${fileExt}`; } try { const buffer = await ctx.http.get(url); if (buffer.byteLength === 0) throw new Error("下载的数据为空"); await fs.promises.writeFile(targetPath, Buffer.from(buffer)); return targetPath; } catch (error) { logger.info("保存图片时出错: " + error.message); } if (config.consoleinfo) { logger.info("提取到的图片链接:" + targetPath); } } __name(saveImages, "saveImages"); async function getFileCountPlusOne(dirPath) { let maxNumber = 0; function traverseDir(dir) { const files = fs.readdirSync(dir); for (const file of files) { const baseName = path.basename(file, path.extname(file)); const number = parseInt(baseName, 10); maxNumber = Math.max(maxNumber, number); } } __name(traverseDir, "traverseDir"); traverseDir(dirPath); return (maxNumber + 1).toString(); } __name(getFileCountPlusOne, "getFileCountPlusOne"); var readFile2 = import_util.default.promisify(fs.readFile); async function getMaxCaveId(filePath) { try { const data = await readFile2(filePath, "utf8"); const objs = JSON.parse(data); if (Array.isArray(objs)) { if (objs.length > 0 && "cave_id" in objs[0]) { let maxCaveId = Math.max(...objs.map((obj) => obj.cave_id)); return maxCaveId + 1; } else { console.log("Array is empty or objects do not have a cave_id property."); return 1; } } else { console.log("Data is not an array."); } } catch (err) { console.log(`Error reading file from disk: ${err}`); } } __name(getMaxCaveId, "getMaxCaveId"); function readJsonFile(filePath) { try { const data = fs.readFileSync(filePath, "utf8"); return JSON.parse(data); } catch (error) { throw new Error(`读取文件出错: ${error.message}`); } } __name(readJsonFile, "readJsonFile"); function writeJsonFile(filePath, data) { try { fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8"); } catch (error) { throw new Error(`写入文件出错: ${error.message}`); } } __name(writeJsonFile, "writeJsonFile"); function getRandomObject(data) { const index = Math.floor(Math.random() * data.length); return data[index]; } __name(getRandomObject, "getRandomObject"); function destructureAndPrint(item) { const { path: path2 } = item; return path2 || ""; } __name(destructureAndPrint, "destructureAndPrint"); function destructureAndPrints(item) { const { text } = item; return text ? JSON.stringify(text, null, 2) : ""; } __name(destructureAndPrints, "destructureAndPrints"); function stringToUnicode(str) { return str.split("").map((char) => { const hex = char.charCodeAt(0).toString(16).padStart(4, "0"); return `\\u${hex}`; }).join(""); } __name(stringToUnicode, "stringToUnicode"); function unicodeToString(str) { return str.replace(/\\u([\dA-Fa-f]{4})/g, (_, hex) => { return String.fromCharCode(parseInt(hex, 16)); }); } __name(unicodeToString, "unicodeToString"); async function deleteFiles(paths) { for (const filePath of paths) { try { await fs.promises.unlink(filePath); } catch (err) { console.error(`Error deleting file ${filePath}:`, err); } } } __name(deleteFiles, "deleteFiles"); async function apply(ctx, config) { async function ensureDirExists(dirPath) { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } __name(ensureDirExists, "ensureDirExists"); async function writeJSONFile(caveFilePath2, data) { const jsonData = JSON.stringify(data, null, 2); fs.writeFileSync(caveFilePath2, jsonData, "utf-8"); } __name(writeJSONFile, "writeJSONFile"); const caveDirPath = path.join(ctx.baseDir, "data", "cave"); const cavePicturesDirPath = path.join(caveDirPath, "pictures"); const caveFilePath = path.join(caveDirPath, "cave.json"); const caveDataFilePath = path.join(caveDirPath, "data.json"); await ensureDirExists(caveDirPath); await ensureDirExists(cavePicturesDirPath); async function ensureFileExists(caveDirPath2) { if (!fs.existsSync(caveDirPath2)) { fs.writeFileSync(caveDirPath2, "[]", "utf-8"); } } __name(ensureFileExists, "ensureFileExists"); async function ensureFileExistss(caveDirPath2) { if (!fs.existsSync(caveDirPath2)) { fs.writeFileSync(caveDirPath2, "{}", "utf-8"); } } __name(ensureFileExistss, "ensureFileExistss"); await ensureFileExists(caveFilePath); await ensureFileExistss(caveDataFilePath); const lastUsed = /* @__PURE__ */ new Map(); ctx.command("cave [image]", "回声洞").option("a", "-a 添加回声洞").option("g", "-g 查看当前id的回声洞内容").option("r", "-r 删除当前id的回声洞").usage(`注意: 直接使用回声洞指令该为cave,添加回声洞为cave -a,-g为只能管理员使用.当前回声洞全局冷却为${config.number}`).example("cave 随机一条回声洞\ncave -a 回复你要添加的图片或文字添加回声洞(不能是合并转发,视频,音频等)\ncave -g 查看当前id的回声洞内容(只能管理员使用或开启可选项后所有人都能使用)\ncave -r 删除当前id的回声洞").action(async ({ session, options }, image) => { if (options.a) { let quote = session.quote; let imageURL; let sessioncontent = session.content; imageURL = import_koishi.h.select(sessioncontent, "img").map((a) => a.attrs.src)[0]; if (!imageURL && !quote) { return "请输入图片或引用回复一条消息"; } let elements = quote?.elements; let textContents = []; let imgSrcs = []; let message = []; if (elements) { elements.forEach((element) => { if (element.type === "text") { textContents.push(stringToUnicode(element.attrs.content)); } else if (element.type === "img") { imgSrcs.push(element.attrs.src); } }); let textMessage = { "type": "text", "text": textContents.join(" ") }; if (textContents.length > 0) { message.push(textMessage); } imageURL = imgSrcs[0]; } else if (image) { let quotemessage; quotemessage = session.quote?.content ?? image; imageURL = import_koishi.h.select(quotemessage, "img").map((a) => a.attrs.src)[0]; } let selectedPath; selectedPath = cavePicturesDirPath; let safeFilename; const getFile = await getFileCountPlusOne(cavePicturesDirPath); safeFilename = getFile; const imageExtension = "png"; if (config.consoleinfo) { if (imageURL) { logger.info("用户输入: " + imageURL); } } let getcaveid = await getMaxCaveId(caveFilePath); let cave_id = getcaveid; let contributor_id = quote?.user.id; if (contributor_id === void 0) { contributor_id = session.userId; } let state = 1; async function sendToManagers(messageElements) { const bot = ctx.bots[0]; if (!bot) { logger.warn("未找到 bot 实例,无法发送私信给审核人员。"); return; } for (const manager of config.manager) { await bot.sendPrivateMessage(manager, messageElements); } } __name(sendToManagers, "sendToManagers"); try { if (imageURL) { const savedImagePath = await saveImages(imageURL, selectedPath, safeFilename, imageExtension, config, session, ctx); let imageMessage = { "type": "image", "path": savedImagePath }; if (!savedImagePath) { return "保存失败,请稍后重试(有多稍后取决于是否真的能下下来)"; } message.push(imageMessage); } if (message.length === 0) { return `请不要引用合并转发,视频,语音等 或消息在bot重启之前发送,无法寻找上下文`; } let caveObject = { cave_id, message, contributor_id, state }; let caveObjects = []; caveObjects.push(caveObject); let rawdata = fs.readFileSync(caveFilePath); let existingMessages = JSON.parse(rawdata.toString()); existingMessages.push(...caveObjects); let json = JSON.stringify(existingMessages, null, 2); fs.writeFile(caveFilePath, json, "utf8", function(err) { if (err) { console.log("写入文件时出错:", err); } else { console.log("JSON 文件已成功保存!"); } }); await session.send(`添加成功,序号为 [${cave_id}] 提交者: ${contributor_id}`); let textContentss = unicodeToString(textContents.join(" ")); if (imageURL) { let messageElements = [ `新的待审核回声洞[ ${cave_id} ] `, textContentss, import_koishi.h.image(imageURL), `—— ${contributor_id}` ]; await sendToManagers(messageElements.join("\n")); } else { let messageElements = [ `新的待审核回声洞[ ${cave_id} ] `, textContentss, `—— ${contributor_id}` ]; await sendToManagers(messageElements.join("\n")); } } catch (error) { if (imageURL) { logger.info(`保存图片出错:${error.message}`); } } } if (options.r) { const data = await readJsonFile(caveFilePath); const imageNumber = Number(image); const managers = config.manager; if (!managers.includes(session.userId)) { await session.send("您没有权限执行此命令。"); return; } if (isNaN(imageNumber)) { await session.send("请输入一个有效的回声洞序号。"); return; } const caveToDelete = data.find((item) => item.cave_id === imageNumber); const updatedData = data.filter((item) => item.cave_id !== imageNumber); if (updatedData.length === data.length) { await session.send("未找到对应的回声洞序号。"); return; } const messages = caveToDelete.message || []; const filesToDelete = messages.map((message) => message.path); await deleteFiles(filesToDelete); await writeJsonFile(caveFilePath, updatedData); await session.send(`回声洞序号 ${imageNumber} 已成功删除。`); } if (options.g) { const data = await readJsonFile(caveFilePath); const imageNumber = Number(image); const managers = config.manager; if (!config.consolefor) { if (!managers.includes(session.userId)) { await session.send("您没有权限执行此命令。"); return; } } if (isNaN(imageNumber)) { await session.send("请输入一个有效的回声洞序号。"); return; } const caveToFind = data.find((item) => item.cave_id === imageNumber); if (!caveToFind) { await session.send("未找到对应的回声洞序号。"); return; } else { const { cave_id, message, contributor_id, state } = caveToFind; let texts = caveToFind.message.map(destructureAndPrints); let messages = caveToFind.message.map(destructureAndPrint); let filteredTexts = texts.filter((text) => text !== void 0); let username = contributor_id; if (config.nameinfo) { const user = await ctx.bots[0].getUser(contributor_id); username = user.name; } let chars = filteredTexts.map((text) => { if (text !== void 0) { if (text.includes("\\u")) { let codePoints = text.split("\\u").filter(Boolean).map((hex) => { let codePoint = parseInt(hex, 16); if (isNaN(codePoint)) { return ""; } return String.fromCodePoint(codePoint); }); return codePoints.join(""); } else { try { return JSON.parse(`"${text.replace(/"/g, '\\"')}"`); } catch (e) { console.error(e); return text; } } } return ""; }); const file = "file:///"; let str = chars.join(""); if (!str) { const messageElements = [ `回声洞 —— [ ${cave_id} ]`, ` `, (0, import_koishi.h)("image", { src: file + messages.filter((msg) => msg).join("\n") }), `—— ${username}` ]; session.send(messageElements); } else if (messages.length === 1 && messages[0] !== "") { const messageElements = [ `回声洞 —— [ ${cave_id} ]`, ` `, import_koishi.h.text(str), // 确保文本部分正确显示 "\n\n", // 手动添加换行符 (0, import_koishi.h)("image", { src: file + messages.filter((msg) => msg).join("\n") }), // '\n\n', // 手动添加换行符 `—— ${username}` ]; session.send(messageElements); } else { const messageElements = [ `回声洞 —— [ ${cave_id} ]`, ` `, import_koishi.h.text(str), // 确保文本部分正确显示 "\n\n", // 手动添加换行符 `—— ${username}` ]; session.send(messageElements); } } } if (!options.a && !options.r && !options.g) { const data = readJsonFile(caveFilePath); const filteredData = data.filter((item) => item.state === 0); const guildId = session.guildId; const lastCall = lastUsed.get(guildId) || 0; const now = Date.now(); if (filteredData.length === 0) { return "没有找到符合条件的回声洞."; } const diff = now - lastCall; if (diff < config.number * 1e3) { const timeLeft = Math.ceil((config.number * 1e3 - diff) / 1e3); if (config.helpinfo) { return `群回声洞调用的太频繁了,请等待${timeLeft}秒后再试`; } else { return; } } lastUsed.set(guildId, now); const randomObject = getRandomObject(filteredData); const { cave_id, message, contributor_id, state } = randomObject; let texts = randomObject.message.map(destructureAndPrints); let messages = randomObject.message.map(destructureAndPrint); let filteredTexts = texts.filter((text) => text !== void 0); console.log(contributor_id); let username = contributor_id; if (config.nameinfo) { const user = await ctx.bots[0].getUser(contributor_id); username = user.name; } let chars = filteredTexts.map((text) => { if (text !== void 0) { if (text.includes("\\u")) { let codePoints = text.split("\\u").filter(Boolean).map((hex) => { let codePoint = parseInt(hex, 16); if (isNaN(codePoint)) { return ""; } return String.fromCodePoint(codePoint); }); return codePoints.join(""); } else { try { return JSON.parse(`"${text.replace(/"/g, '\\"')}"`); } catch (e) { console.error(e); return text; } } } return ""; }); const file = "file:///"; let str = chars.join(""); if (!str) { const messageElements = [ `回声洞 —— [ ${cave_id} ]`, ` `, (0, import_koishi.h)("image", { src: file + messages.filter((msg) => msg).join("\n") }), `—— ${username}` ]; session.send(messageElements); } else if (messages.length === 1 && messages[0] !== "") { const messageElements = [ `回声洞 —— [ ${cave_id} ]`, ` `, import_koishi.h.text(str), // 确保文本部分正确显示 "\n\n", // 手动添加换行符 (0, import_koishi.h)("image", { src: file + messages.filter((msg) => msg).join("\n") }), // '\n\n', // 手动添加换行符 `—— ${username}` ]; session.send(messageElements); } else { const messageElements = [ `回声洞 —— [ ${cave_id} ]`, ` `, import_koishi.h.text(str), // 确保文本部分正确显示 "\n\n", // 手动添加换行符 `—— ${username}` ]; session.send(messageElements); } } }); ctx.private().command("setcave <image>", "私聊审核").alias("回声洞审核").option("t", "-t 私聊审核通过回声洞").option("f", "-f 私聊审核不通过回声洞").usage("注意:未审核的回声洞不在可抽取列表,该指令只能在私聊使用,群聊使用无响应属于正常.").example("setcave -t 1/all 审核通过id为1/全部的未审核回声洞\nsetcave -f 1/all 不通过id为1/全部的未审核回声洞").action(async ({ session, options }, image) => { const all = "all"; const data = readJsonFile(caveFilePath); let userid = session.event.user, id; const managers = config.manager; if (options.t) { if (!managers.includes(session.userId)) { await session.send("您没有权限执行此命令。"); return; } if (isNaN(Number(image)) && image !== all) { await session.send("错误:image 参数只接受数字与ALL类型。"); return; } try { if (image === all) { let updatedCount = 0; data.forEach((item) => { if (item.state === 1) { item.state = 0; updatedCount++; } }); writeJsonFile(caveFilePath, data); await session.send(`已通过(${updatedCount})个回声洞。`); } else { const imageNumber = Number(image); const targetItem = data.find((item) => item.cave_id === imageNumber); if (targetItem && targetItem.state === 1) { targetItem.state = 0; writeJsonFile(caveFilePath, data); await session.send(`回声洞序号: [(]${imageNumber}] 已审核`); } else { await session.send(`未找到序号为 [(]${imageNumber}] 的未审核回声洞。`); } } } catch (error) { await session.send(`读取或写入 JSON 文件时出错: ${error.message}`); } } if (options.f) { if (!managers.includes(session.userId)) { await session.send("您没有权限执行此命令。"); return; } if (isNaN(Number(image)) && image !== all) { await session.send("错误:image 参数只接受数字与ALL类型。"); return; } try { if (image === all) { let updatedCount = 0; data.forEach((item) => { if (item.state === 1) { item.state = 2; updatedCount++; } }); writeJsonFile(caveFilePath, data); await session.send(`已不通过 [${updatedCount}] 个回声洞。`); } else { const imageNumber = Number(image); const targetItem = data.find((item) => item.cave_id === imageNumber); if (targetItem && targetItem.state === 1) { targetItem.state = 2; writeJsonFile(caveFilePath, data); await session.send(`回声洞序号: [${imageNumber}] 审核不通过`); } else { await session.send(`未找到序号为 [${imageNumber}] 的未审核回声洞。`); } } } catch (error) { await session.send(`读取或写入 JSON 文件时出错: ${error.message}`); } } }); } __name(apply, "apply"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, apply, getFileCountPlusOne, getMaxCaveId, name, saveImages });