You need to enable JavaScript to run this app. discord.js 如何让工单机器人的消息反应重启后仍正常生效
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.29 11:27:02