You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Discord.js 14.11按钮异常:单点击批量审批/驳回所有提交截图

解决Discord Bot按钮批量处理提交的问题

问题根源

  1. 每次执行screenshot命令时,都会重复绑定全局interactionCreate事件,导致多个事件监听器堆积。点击任意按钮时,所有已绑定的监听器都会触发,进而批量处理所有提交。
  2. 按钮customId为固定值,无法区分对应哪个提交的消息;变量作用域混乱,多个监听器共享同一批变量值,导致逻辑错误。

解决方案

  1. 移除命令内部的全局事件绑定,改用消息组件收集器,仅监听当前提交消息的按钮交互。
  2. 给按钮customId添加唯一标识(结合消息ID),确保每个提交的按钮能被精准识别。
  3. 优化数据库操作逻辑,添加SQL注入防护,处理完按钮后禁用按钮避免重复操作。

修改后的完整代码

module.exports = {
  data: {
    "name": "screenshot",
    "type": 1,
    "description": "Submit your screenshot",
    "options": [
      {
        "name": "nickname",
        "description": "Insert your discord server NickName",
        "type": 3,
        "required": true,
      },
      {
        "name": "image",
        "description": "Select your Image from your PC",
        "type": 11,
        "required": true,
      },
    ]
  },

  async execute(interaction, client){
    const username = interaction.options.getString('nickname');
    const imageurl = interaction.options.getAttachment('image');

    // 上传提示Embed
    const loadingEmbed = new EmbedBuilder()
      .setDescription("``<<<Uploading!>>>``")
      .setColor("#00b0f4")
      .setTimestamp();
    
    // 发送初始回复并获取消息对象
    const submittedMsg = await interaction.reply({
      embeds: [loadingEmbed],
      fetchReply: true
    });
    
    // 提交成功的Embed
    const submitSuccessEmbed = new EmbedBuilder()
      .setDescription("``<<<Your ScreenShot has been submitted for validation \n\n Thank you!>>>``")
      .setThumbnail(imageurl.url)
      .setImage(imageurl.url)
      .setColor("#00b0f4")
      .setTimestamp();
    
    // 给按钮添加唯一标识,结合消息ID区分不同提交
    const row = new ActionRowBuilder()
      .addComponents(
        new ButtonBuilder()
          .setCustomId(`accept_${submittedMsg.id}`)
          .setLabel('Accept')
          .setEmoji('✅')
          .setStyle('3'),
        new ButtonBuilder()
          .setCustomId(`reject_${submittedMsg.id}`)
          .setLabel('Reject')
          .setEmoji('❌')
          .setStyle('1'),
      );

    // 更新回复为提交成功状态,带按钮
    await interaction.editReply({ embeds: [submitSuccessEmbed], components: [row] });

    // 创建消息组件收集器,只监听当前消息的按钮交互
    const collector = submittedMsg.createMessageComponentCollector({
      filter: i => i.member.permissions.has('ADMINISTRATOR'), // 仅管理员可操作
      time: 86400000 // 收集器有效期1天,可按需调整
    });

    // 监听按钮点击事件
    collector.on('collect', async (btnInteraction) => {
      // 先回复交互避免超时
      await btnInteraction.deferReply({ ephemeral: false });

      const messageID = submittedMsg.id;
      const adminId = btnInteraction.member.user.id;

      // 处理Accept按钮
      if (btnInteraction.customId === `accept_${messageID}`) {
        // 参数化查询避免SQL注入
        const sql = "INSERT INTO `screenshot` (`name`, `url`, `status`, `messageid`, `memebrid`) VALUES (?, ?, '1', ?, ?)";
        con.query(sql, [username, imageurl.url, messageID, adminId], function (err, result){
          if(err) throw err;

          // 更新消息为已通过状态
          const acceptedEmbed = new EmbedBuilder()
            .setDescription("``<<<Your ScreenShot has been Accepted \n\n Thank You! >>>``")
            .setThumbnail(imageurl.url)
            .setImage(imageurl.url)
            .setColor("#00b0f4")
            .setTimestamp();
          
          btnInteraction.editReply({ embeds: [acceptedEmbed] });
          // 禁用按钮防止重复点击
          row.components.forEach(btn => btn.setDisabled(true));
          interaction.editReply({ components: [row] });
          console.log("screenshot Inserted and approved.");
        });
      }

      // 处理Reject按钮
      if (btnInteraction.customId === `reject_${messageID}`) {
        // 参数化查询更新状态
        const sql = "UPDATE `screenshot` SET `status` = '0' WHERE messageid = ?";
        con.query(sql, [messageID], function (err, result) {
          if (err) throw err;

          const rejectedEmbed = new EmbedBuilder()
            .setDescription("``<<<Your ScreenShot has been Rejected \n\n Thank You! >>>``")
            .setThumbnail(imageurl.url)
            .setImage(imageurl.url)
            .setColor("#00b0f4")
            .setTimestamp();
          
          btnInteraction.editReply({ embeds: [rejectedEmbed] });
          // 禁用按钮防止重复点击
          row.components.forEach(btn => btn.setDisabled(true));
          interaction.editReply({ components: [row] });
          console.log(result.affectedRows + " record(s) updated");
        });
      }

      // 停止收集器,结束当前消息的监听
      collector.stop();
    });

    // 收集器过期时禁用按钮
    collector.on('end', () => {
      row.components.forEach(btn => btn.setDisabled(true));
      interaction.editReply({ components: [row] }).catch(() => {});
    });
  }
};

关键改进说明

  • 专属消息监听:使用createMessageComponentCollector替代全局事件,仅监听当前提交消息的按钮,避免监听器堆积。
  • 唯一按钮标识:按钮ID结合消息ID,确保每个提交的按钮唯一,不会混淆不同提交。
  • 安全防护:改用参数化SQL查询,避免SQL注入风险。
  • 防重复操作:处理完按钮后立即禁用,防止重复点击触发多次操作。
  • 交互优化:使用deferReply避免Discord交互超时报错。

内容的提问来源于stack exchange,提问作者MOHAMED AL YOUSUFI

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 09:08:10