discord.js 如何让工单机器人的消息反应重启后仍正常生效
Discord工单机器人重启后反应事件失效的解决方案
核心失效原因
- Discord.js 默认仅缓存机器人上线后新产生的消息、反应数据,重启后机器人无法感知历史存在的触发消息上的新增反应
- 未配置对应的网关意图与部分对象拉取规则,Discord网关不会向重启后的机器人推送历史消息上的反应事件
- 未持久化存储工单触发消息的ID,机器人重启后无法定位到需要监听反应的目标消息
操作步骤
1. 配置客户端初始化参数,开启事件接收能力
在创建Discord客户端实例时,显式声明需要的网关意图与允许拉取的非缓存对象,否则就算写了事件监听也收不到历史消息的反应事件:
const { Client, Intents } = require('discord.js'); const client = new Client({ intents: [ Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS // 必选:允许接收服务器消息的反应事件 ], partials: [ 'MESSAGE', 'CHANNEL', 'REACTION' // 必选:允许主动拉取不在本地缓存里的历史消息、反应数据 ] });
注意:需要到Discord开发者后台的机器人设置页,确认
GUILD_MESSAGE_REACTIONS对应的权限已开启,没有被网关拦截。
2. 持久化工单触发消息的标识
每次发送带反应选项的工单引导消息后,把消息所属频道ID、消息自身ID存到本地文件或简易数据库,避免重启后丢失目标消息信息:
const fs = require('fs'); // 原代码发送引导消息的逻辑保留 let messageEmbed = await message.channel.send(ticketEmbed); await messageEmbed.react("1️⃣"); await messageEmbed.react("2️⃣"); await messageEmbed.react("3️⃣"); await messageEmbed.react("4️⃣"); // 持久化存储触发消息信息 const triggerConfig = { channelId: message.channel.id, messageId: messageEmbed.id }; fs.writeFileSync('./ticket_trigger_config.json', JSON.stringify(triggerConfig));
3. 修正事件处理逻辑,适配重启后的运行场景
在反应事件监听中,先拉取非缓存的数据、校验反应对应的目标消息是不是存储的工单触发消息,同时修复原代码里的变量引用错误、作用域问题:
client.on("messageReactionAdd", async (reaction, user) => { // 拉取未缓存的reaction、message数据,避免操作报错 if (reaction.message.partial) await reaction.message.fetch(); if (reaction.partial) await reaction.fetch(); // 过滤机器人自身的反应操作 if (user.bot) return; if (!reaction.message.guild) return; // 读取存储的触发消息配置 const triggerConfig = JSON.parse(fs.readFileSync('./ticket_trigger_config.json', 'utf8')); // 跳过非目标消息上的反应 if (reaction.message.id !== triggerConfig.messageId) return; // 对应1️⃣反应的工单创建逻辑 if (reaction.emoji.name === "1️⃣") { const categoryId = "822577084223586364"; const ticketOperator = reaction.message.guild.members.cache.get(user.id); let ticketExists = false; // 检查该用户是否已有同类型工单 reaction.message.guild.channels.cache.forEach((channel) => { if (channel.name === `${user.username}-playerreport`) { ticketExists = true; user.send("You already have an open ticket!"); } }); if (ticketExists) return; // 创建工单频道 const createdChannel = await reaction.message.guild.channels.create(`${user.username}-playerreport`, { type: "text", }); await createdChannel.setParent(categoryId); // 设置频道权限 await createdChannel.permissionOverwrites.edit( reaction.message.guild.roles.everyone, { SEND_MESSAGES: false, VIEW_CHANNEL: false } ); await createdChannel.permissionOverwrites.edit(ticketOperator, { SEND_MESSAGES: true, VIEW_CHANNEL: true, READ_MESSAGE_HISTORY: true, CREATE_INSTANT_INVITE: false, ATTACH_FILES: true, CONNECT: true, ADD_REACTIONS: true, }); // 发送欢迎embed const ticketEmbed = new Discord.MessageEmbed() .setTitle(`Hey ${ticketOperator}, who do you want to report, and why?`) .setDescription("Staff will try to help you as soon as possible") .setThumbnail("https://cdn.discordapp.com/attachments/790156012119392287/821827094916759603/image_2.png"); createdChannel.send({ embeds: [ticketEmbed] }); } });
原代码附带bug修复
- 移除了未定义的
firstreaction变量,直接匹配对应emoji名称 - 修复了
forEach内var ticketExists的作用域问题,确保外层能正确判断工单是否存在 - 替换了所有原代码中误用的不存在的
message变量,统一使用事件回调里的reaction对象获取对应服务器、频道信息 - 适配了新版Discord.js的权限覆盖API写法,避免旧版
updateOverwrite方法的兼容问题
内容的提问来源于stack exchange,提问作者Daniel Bruggert
相关产品推荐
相关产品推荐

