Discord.js多投票场次按钮交互重复绑定报错问题排查
问题背景
我开发了一款基于Discord.js的游戏结果投票机器人,管理员可通过斜杠命令发送附带两个投票按钮的消息,支持同时开启多个投票场次。当前按钮交互处理代码如下:
interaction.client.on("interactionCreate", async (i) => { if (!i.isButton()) return; let team; try { if (i.customId === 'team1Button' || i.customId === 'team2Button') { await i.deferReply({ ephemeral: true }); // Handle team vote team = await getTeamEmoji(i.message.id, i.customId); // Assign value to team variable await castVote(team.name, i.user.username, i.message.id); // Reply to interaction await i.editReply({ content: `Vote for ${team.emoji} submitted.`, ephemeral: true, }); } } catch (error) { console.error("Interaction handling error:", error); logError(error, i.message.id, i.user.username, 'Interaction handling error: ' + i); } });
代码可正常完成投票且机器人不会崩溃,但当存在2个及以上活跃投票场次时,错误日志会不断出现以下报错:
error "DiscordAPIError[10062]: Unknown interaction"
或
error "DiscordAPIError[40060]: Interaction has already been acknowledged."
且投票场次每增加1个,用户投票时触发的报错数就会对应增加1个,其中DiscordAPIError[10062]: Unknown interaction报错占比更高。错误日志函数代码如下:
async function logError(error, message, user, description) { try { const today = new Date(); const formattedDate = today.toLocaleDateString(); // e.g., "09/18/2023" (format may vary depending on your system's locale) const errorString = error instanceof Error ? error.toString() : String(error); // Convert error object to string data = { date: formattedDate, error: errorString, message: message, user: user, status: 'Open', description: description } const errorCollectionRef = collection(db, 'ErrorLog'); await addDoc(errorCollectionRef, data); } catch (error) { console.error('An error occurred in logError:', error); } }
请问为何功能正常但错误日志被填满?该如何解决?是交互处理逻辑问题还是错误日志的问题?
问题根源
核心问题是重复绑定interactionCreate事件监听器:
你在创建投票场次的斜杠命令逻辑里,每次都会执行interaction.client.on("interactionCreate", ...),导致每新增一个投票场次,就多绑定一次按钮交互的处理逻辑。当用户点击按钮时,同一个交互会被多个监听器同时处理:
- 第一个监听器会先调用
deferReply确认交互,后续监听器再处理时,就会触发Interaction has already been acknowledged报错; - 如果第一个监听器已经完成
editReply,Discord会认为该交互已过期,后续监听器再调用API就会抛出Unknown interaction报错。
这就是投票场次越多,报错数越多的直接原因。
解决步骤
1. 移除重复的事件绑定
把interactionCreate事件监听器放到机器人全局初始化代码中,只绑定一次,不要在斜杠命令的处理逻辑内部重复绑定。示例结构如下:
// 全局初始化时只绑定一次交互事件 client.on("interactionCreate", async (i) => { // 先过滤非按钮交互 if (!i.isButton()) return; // 检查交互是否已被处理,避免重复操作 if (i.replied || i.deferred) return; let team; try { if (i.customId === 'team1Button' || i.customId === 'team2Button') { await i.deferReply({ ephemeral: true }); team = await getTeamEmoji(i.message.id, i.customId); await castVote(team.name, i.user.username, i.message.id); await i.editReply({ content: `Vote for ${team.emoji} submitted.`, ephemeral: true, }); } } catch (error) { console.error("Interaction handling error:", error); // 优化日志内容,避免冗余的交互对象字符串 logError(error, i.message.id, i.user.username, `Interaction ID: ${i.id}, Custom ID: ${i.customId}`); } }); // 单独处理斜杠命令,只负责发送投票消息 client.on("interactionCreate", async (interaction) => { if (!interaction.isChatInputCommand()) return; if (interaction.commandName === "create-vote") { // 构建按钮行 const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId('team1Button') .setLabel('队伍1') .setStyle(ButtonStyle.Primary), new ButtonBuilder() .setCustomId('team2Button') .setLabel('队伍2') .setStyle(ButtonStyle.Danger) ); await interaction.reply({ content: '请投票支持队伍', components: [row] }); } });
2. 优化错误日志记录
原代码中'Interaction handling error: ' + i会把整个交互对象转成冗长的字符串,建议改为记录交互ID和自定义ID,让日志更简洁有用,避免不必要的冗余数据。
3. 增加交互状态检查
在按钮交互处理逻辑开头添加if (i.replied || i.deferred) return;,可以进一步防止重复处理已经被确认的交互,减少潜在报错。
验证效果
修改完成后,无论开启多少个投票场次,每个按钮交互只会被全局的单一监听器处理一次,不会再出现重复的Unknown interaction和Interaction has already been acknowledged报错,错误日志也不会被无效信息填满,同时投票功能保持正常。
内容的提问来源于stack exchange,提问作者Cole Perry

