Discord.js机器人JSON读写实现与writerrp指令报错修复
Discord.js JSON 读写功能问题修复
原有代码的报错触发点
- 直接将Discord的GuildMember类对象塞进待序列化的JSON内容中,这类对象自带循环引用,会直接抛出Converting circular structure to JSON类型错误
- 写文件逻辑使用覆盖式写入,每次触发指令都会把之前存储的所有记录全部清掉,无法实现多用户、多条证据的存储需求
- 缺少前置判断中断逻辑:当用户没@人、没填证据链接时,只发送了提示消息但没有终止后续执行流程,会把undefined值直接写入JSON
- 非固定的相对路径引用,文件结构调整后很容易触发文件权限、路径不存在的报错
- 异步写文件没有等待完成就执行冷却逻辑,存在时序不一致的问题
修复后的写入指令(对应writerrp需求)
const Discord = require("discord.js") const fs = require("fs"); const path = require("path"); const botconfig = require("../botconfig.json"); const colours = require("../colours.json"); const superagent = require("superagent"); const usedCommand = new Set(); module.exports.run = async (bot, message, args) => { await message.delete(); // 指令冷却校验 if (usedCommand.has(message.author.id)) { return message.reply("du kannst diesen Command erst in 15 Sekunden wieder verwenden.") .then(msg => msg.delete({ timeout: 10000 })); } // 参数有效性校验 let member = message.mentions.members.first() || message.guild.members.cache.get(args[0]); if (!member) { return message.reply("Bitte markiere einen gültigen Benutzer, für den du den Beweis speichern willst!") .then(msg => msg.delete({ timeout: 10000 })); } let reason = args.slice(1).join(" ").trim(); if (!reason) { return message.reply("Bitte gebe einen Beweis in Form von einem Link (Video: Youtube, Bild: prnt.sc) an!") .then(msg => msg.delete({ timeout: 10000 })); } // 定位存储文件路径,避免相对路径偏差 const rppFilePath = path.resolve(__dirname, "../rpp.json"); let storedProofs = []; // 读取原有存储内容 try { if (fs.existsSync(rppFilePath)) { storedProofs = JSON.parse(fs.readFileSync(rppFilePath, "utf8")); } } catch (err) { console.error("Lese-Fehler rpp.json:", err); return message.reply("Fehler beim Laden der gespeicherten Beweise!") .then(msg => msg.delete({ timeout: 10000 })); } // 构造可序列化的存储条目,仅保留核心信息 const newRecord = { targetUserId: member.id, targetUsername: member.user.tag, proofLink: reason, submitter: message.author.tag, submitTime: Date.now() }; storedProofs.push(newRecord); // 写回更新后的内容到文件 try { fs.writeFileSync(rppFilePath, JSON.stringify(storedProofs, null, 4), "utf8"); } catch (err) { console.error("Schreib-Fehler rpp.json:", err); return message.reply("Fehler beim Speichern des Beweises!") .then(msg => msg.delete({ timeout: 10000 })); } // 触发冷却计时 usedCommand.add(message.author.id); setTimeout(() => usedCommand.delete(message.author.id), 15000); message.reply(`Beweis für ${member.user.tag} wurde erfolgreich gespeichert!`) .then(msg => msg.delete({ timeout: 10000 })); } module.exports.config = { name: "writerrp", description: "Speichert Beweise zu markierten Nutzern in der lokalen JSON-Datei", usage: "+writerrp @[Nutzer] [Beweis-Link]", accessableby: "Members", aliases: ["proofwrite", "rpp"] }
配套readrrp读取指令实现
- 功能逻辑:接收@用户或用户ID为参数,从存储文件中筛选对应用户的所有证据条目,格式化后通过嵌入消息返回
const Discord = require("discord.js") const fs = require("fs"); const path = require("path"); module.exports.run = async (bot, message, args) => { await message.delete(); // 校验查询目标 let targetUser = message.mentions.members.first() || message.guild.members.cache.get(args[0]); if (!targetUser) { return message.reply("Bitte markiere den Nutzer, für den du gespeicherte Beweise abfragen willst!") .then(msg => msg.delete({ timeout: 10000 })); } // 读取存储文件 const rppFilePath = path.resolve(__dirname, "../rpp.json"); let storedProofs = []; if (fs.existsSync(rppFilePath)) { storedProofs = JSON.parse(fs.readFileSync(rppFilePath, "utf8")); } // 匹配对应用户的所有记录 const matchedProofs = storedProofs.filter(item => item.targetUserId === targetUser.id); if (matchedProofs.length === 0) { return message.reply(`Es wurden keine Beweise für ${targetUser.user.tag} gefunden.`) .then(msg => msg.delete({ timeout: 10000 })); } // 构造展示用嵌入 const responseEmbed = new Discord.MessageEmbed() .setTitle(`Gespeicherte Beweise für ${targetUser.user.tag}`) .setColor("#ff4444"); matchedProofs.forEach((entry, index) => { responseEmbed.addField( `Beweis #${index + 1}`, `Eingereicht von ${entry.submitter} am ${new Date(entry.submitTime).toLocaleString("de-DE")}\nLink: ${entry.proofLink}` ); }); message.channel.send({ embeds: [responseEmbed] }); } module.exports.config = { name: "readrrp", description: "Liest gespeicherte Beweise zu einem Nutzer aus der lokalen JSON-Datei", usage: "+readrrp @[Nutzer]", accessableby: "Moderatoren", aliases: ["proofread"] }
开发注意点
- 小体量的本地JSON文件读写使用同步方法即可,不会造成Bot主进程阻塞,还能避免异步时序导致的执行顺序混乱问题
- 永远不要把Discord.js的类对象直接序列化到JSON,这类对象的内部引用结构天生不适合直接做字符串序列化
- 文件路径统一用
path.resolve拼接,适配不同操作系统的路径规则,避免硬写相对路径带来的兼容性问题
内容的提问来源于stack exchange,提问作者Beyazz
相关产品推荐
相关产品推荐

