Discord.js 14.11按钮异常:单点击批量审批/驳回所有提交截图
解决Discord Bot按钮批量处理提交的问题
问题根源
- 每次执行
screenshot命令时,都会重复绑定全局interactionCreate事件,导致多个事件监听器堆积。点击任意按钮时,所有已绑定的监听器都会触发,进而批量处理所有提交。 - 按钮
customId为固定值,无法区分对应哪个提交的消息;变量作用域混乱,多个监听器共享同一批变量值,导致逻辑错误。
解决方案
- 移除命令内部的全局事件绑定,改用消息组件收集器,仅监听当前提交消息的按钮交互。
- 给按钮
customId添加唯一标识(结合消息ID),确保每个提交的按钮能被精准识别。 - 优化数据库操作逻辑,添加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
相关产品推荐
相关产品推荐

