Discord.js v14按钮交互失败,mapBanCollector未触发求助
Discord.js v14 地图禁用按钮交互失败问题排查
问题现象
- 点击地图禁用按钮后,Discord提示「交互失败」
- 自定义
mapBanCollector未触发,console.log('The mapBanCollector is working!')无输出 - 预期功能:允许1队队长从地图池禁用1张地图,随后在聊天显示被禁用地图
核心问题点
Collector绑定对象错误
你使用interaction.channel.createMessageComponentCollector监听频道内所有按钮,但按钮实际挂载在编辑后的特定回复消息上。频道级collector无法精准匹配目标按钮,导致触发失败。未及时响应交互
Discord要求所有组件交互必须在3秒内进行ack(如deferUpdate()/update()),若collector未触发,交互会因超时直接提示失败。变量作用域错误
banButton是forEach循环内的局部变量,在collector的collect事件中访问banButton.customId会直接抛出引用错误,阻断逻辑执行。组件更新逻辑混乱
直接修改原有ActionRow和Embed实例后未重新构建完整组件数组,导致Discord无法识别更新内容;且仅传递单个禁用按钮,而非完整的组件结构。未定义变量引用
end事件中直接使用bannedMap变量,但该变量未在事件作用域内定义,会触发报错。
修复后的关键代码
const shuffledQueue = [...queue].sort(() => Math.random() - 0.5); const captains = [shuffledQueue[0], shuffledQueue[1]]; const team1Captain = captains[0]; const mapPool = ['Bind','Haven','Split','Ascent','Icebox','Breeze','Fracture','Pearl','Lotus']; const MAX_MAPS_TO_BAN = 1; let mapBanCount = 0; const mapBanButtons = []; mapPool.forEach((map) => { const banButton = new ButtonBuilder() .setCustomId(`ban_${map}`) .setLabel(map) .setStyle(ButtonStyle.Danger) .setDisabled(false); mapBanButtons.push(banButton); }); const actionRow1 = new ActionRowBuilder().addComponents(mapBanButtons.slice(0, 3)); const actionRow2 = new ActionRowBuilder().addComponents(mapBanButtons.slice(3, 6)); const actionRow3 = new ActionRowBuilder().addComponents(mapBanButtons.slice(6, 9)); const mapBanPhase1Embed = new EmbedBuilder() .setTitle('Map Ban Phase') .setDescription(`${team1Captain.username}, please ban a map.`) .addFields({name: 'Maps', value: mapPool.join('\n')}); // 编辑回复并保存消息实例 const targetMessage = await interaction.editReply({ embeds: [mapBanPhase1Embed], components: [actionRow1, actionRow2, actionRow3], }); // 绑定collector到目标消息,而非频道 const mapBanCollector = targetMessage.createMessageComponentCollector({ componentType: ComponentType.Button, time: 180000, filter: (i) => i.user.id === team1Captain.id, }); mapBanCollector.on('collect', async (buttonInteraction) => { // 立即ack响应,避免交互超时失败 await buttonInteraction.deferUpdate(); console.log('The mapBanCollector is working!'); if (mapBanCount >= MAX_MAPS_TO_BAN) return; // 从当前交互按钮获取禁用地图 const bannedMap = buttonInteraction.customId.split('_')[1]; console.log(`${team1Captain.username} has banned ${bannedMap}.`); // 更新对应按钮为禁用状态 const btnIndex = mapBanButtons.findIndex(btn => btn.customId === buttonInteraction.customId); if (btnIndex !== -1) { mapBanButtons[btnIndex].setDisabled(true); // 重新构建完整的ActionRow数组 const updatedRows = [ new ActionRowBuilder().addComponents(mapBanButtons.slice(0,3)), new ActionRowBuilder().addComponents(mapBanButtons.slice(3,6)), new ActionRowBuilder().addComponents(mapBanButtons.slice(6,9)), ]; // 更新Embed内容 const updatedPool = mapPool.filter(map => map !== bannedMap); mapBanPhase1Embed.setDescription(`${team1Captain.username} has banned ${bannedMap}.`); mapBanPhase1Embed.setFields({name: 'Maps', value: updatedPool.join('\n')}); mapBanCount++; if (mapBanCount >= MAX_MAPS_TO_BAN) { // 结束阶段,移除所有组件 await interaction.editReply({ embeds: [mapBanPhase1Embed], components: [], }); mapBanCollector.stop(); } else { await interaction.editReply({ embeds: [mapBanPhase1Embed], components: updatedRows, }); } } }); mapBanCollector.on('end', async (collected) => { // 从收集到的交互中获取禁用地图(若有) const bannedMap = collected.first() ? collected.first().customId.split('_')[1] : 'no map'; console.log(`Captain A has banned ${bannedMap}.`); // 后续地图选择逻辑(略) // 注意:所有交互更新需确保消息存在,避免报错 });
额外注意事项
- 需从
discord.js导入ComponentType,v14要求明确指定组件类型 - 所有异步操作必须使用
await,避免未处理的Promise导致逻辑异常 - 测试时可在filter函数中添加日志,确认是否有触发过滤逻辑
- 避免直接修改原有的Embed/组件实例,必要时创建副本后修改
内容的提问来源于stack exchange,提问作者Shadow
相关产品推荐
相关产品推荐

