javascriptnode.jsbuttondiscord.jsinteraction

Discord.js v14 how to handle button interactions


I'm trying to make a help command for my discord bot with menus and buttons, but I encountered multiple problems:

I don't know how to handle the "Interaction failed" text showing up when I click a button even tho the button responded.

How can I use the deferUpdate() for buttons in the latest discord.js? If I should use collectors, where should I put the collector? I read the official docs but still don't understand how to use it.

I also tried not to use collectors or await, but I always get "Interaction has been acknowledged" the second time I use the help command.

Furthermore, I'm always getting errors like Invalid Body Form, Unknown interaction, or Interaction has already been acknowledged when clicking the buttons.

Here's the part of my code handling the interaction:

message.channel.send({
            embeds: [page1], components:[row]
        }).then((msg) => {
            let times = 0
            var interval = setInterval(function(){
                times += 1;
                if(times === 15){
                    clearInterval(interval);
                    firstBtn.setDisabled(true)
                    lastBtn.setDisabled(true)
                }
            }, 1000); 
            client.on('interactionCreate', interaction => {
                if (!interaction.isButton()) return;

                if(interaction.customId == 'next'){
                    curPage += 1
                    firstBtn.setDisabled(false)
                    previousBtn.setDisabled(false)
                    nextBtn.setDisabled(false)
                    if(curPage == maxPage){
                        nextBtn.setDisabled(true)
                        lastBtn.setDisabled(true)
                    }
                    msg.edit({embeds: [pages[curPage-1]], components:[row]})
                } else if(interaction.customId == 'previous'){
                    curPage -= 1
                    lastBtn.setDisabled(false)
                    previousBtn.setDisabled(false)
                    nextBtn.setDisabled(false)
                    if(curPage == 1){
                        previousBtn.setDisabled(true)
                        firstBtn.setDisabled(true)
                    }
                    msg.edit({embeds: [pages[curPage-1]], components:[row]})
                } else if(interaction.customId == 'first'){
                    curPage = 1
                    firstBtn.setDisabled(true)
                    lastBtn.setDisabled(false)
                    previousBtn.setDisabled(true)
                    nextBtn.setDisabled(false)
                    msg.edit({embeds: [pages[curPage-1]], components:[row]})
                } else if(interaction.customId == 'last'){
                    curPage = maxPage
                    firstBtn.setDisabled(false)
                    lastBtn.setDisabled(true)
                    nextBtn.setDisabled(true)
                    previousBtn.setDisabled(false)

                    msg.edit({embeds: [pages[curPage-1]], components:[row]})
                }

                const collector = interaction.channel.createMessageComponentCollector({ time: 3500 });

                collector.on('collect', async i => {
                    if(i.user.id === interaction.user.id){
                        if (interaction.isButton()) {
                            interaction.deferUpdate();
                        }
                    }
                });

                collector.on('end', collected => {console.log(`Collected ${collected.size} items`); collector.stop()});
            });
        })
    },

Full code of the help command:

help: function (message, client) {
        let categories = listCmds.getCates();
        let maxPage = 2;
        let curPage = 1;
        let funCmds = listCmds.getCmds(listCmds.liCmds.categories.FUN)

        let row = new ActionRowBuilder()
            .addComponents(
                firstBtn = new ButtonBuilder()
                    .setCustomId('first')
                    .setLabel('First page')
                    .setStyle(ButtonStyle.Success)
                    .setDisabled(true)
            )
            .addComponents(
                previousBtn = new ButtonBuilder()
                    .setCustomId('previous')
                    .setLabel('<<')
                    .setStyle(ButtonStyle.Primary)
            )
            .addComponents(
                nextBtn = new ButtonBuilder()
                    .setCustomId('next')
                    .setLabel('>>')
                    .setStyle(ButtonStyle.Primary)
            )
            .addComponents(
                lastBtn = new ButtonBuilder()
                    .setCustomId('last')
                    .setLabel('Last page')
                    .setStyle(ButtonStyle.Success)
            )
        let page1 = new EmbedBuilder()
            .setAuthor({ name: 'Help menu', iconURL: 'https://i.postimg.cc/rwXj33rv/sonnnn.png' })
            .setTitle(`${categories[0]}`)
            .setColor([144, 238, 144])
            .setURL('https://www.youtube.com/channel/UCVUWJ4CIuE9e7hXNiKOjg5w/featured')
            .addFields(
                { name: funCmds[0][0], value: funCmds[0][1]},
                { name: funCmds[1][0], value: funCmds[1][1]},
                { name: funCmds[2][0], value: funCmds[2][1]},
                { name: funCmds[3][0], value: funCmds[3][1]},
            )
            .setFooter({ text: `Page 1/${maxPage}. If you find any bugs, please report at https://discord.gg/wRtZ6fRhZC.`})

        let page2 = new EmbedBuilder()
            .setAuthor({ name: 'Help menu', iconURL: 'https://i.postimg.cc/rwXj33rv/sonnnn.png' })
            .setTitle(`This is the end!`)
            .setColor([144, 238, 144])
            .setURL('https://www.youtube.com/channel/UCVUWJ4CIuE9e7hXNiKOjg5w/featured')
            .addFields(
                {
                    name: 'More categories of commands coming soon!',
                    value: ':)',
                    inline: true
                },
            )
            .setFooter({ text: `Page 2/${maxPage}. If you find any bugs, please report at https://discord.gg/wRtZ6fRhZC.`})

        let pages = [page1, page2]

        message.channel.send({
            embeds: [page1], components:[row]
        }).then((msg) => {
            let times = 0
            var interval = setInterval(function(){
                times += 1;
                if(times === 15){
                    clearInterval(interval);
                    firstBtn.setDisabled(true)
                    lastBtn.setDisabled(true)
                }
            }, 1000); 
            client.on('interactionCreate', interaction => {
                if (!interaction.isButton()) return;

                if(interaction.customId == 'next'){
                    curPage += 1
                    firstBtn.setDisabled(false)
                    previousBtn.setDisabled(false)
                    nextBtn.setDisabled(false)
                    if(curPage == maxPage){
                        nextBtn.setDisabled(true)
                        lastBtn.setDisabled(true)
                    }
                    msg.edit({embeds: [pages[curPage-1]], components:[row]})
                } else if(interaction.customId == 'previous'){
                    curPage -= 1
                    lastBtn.setDisabled(false)
                    previousBtn.setDisabled(false)
                    nextBtn.setDisabled(false)
                    if(curPage == 1){
                        previousBtn.setDisabled(true)
                        firstBtn.setDisabled(true)
                    }
                    msg.edit({embeds: [pages[curPage-1]], components:[row]})
                } else if(interaction.customId == 'first'){
                    curPage = 1
                    firstBtn.setDisabled(true)
                    lastBtn.setDisabled(false)
                    previousBtn.setDisabled(true)
                    nextBtn.setDisabled(false)
                    msg.edit({embeds: [pages[curPage-1]], components:[row]})
                } else if(interaction.customId == 'last'){
                    curPage = maxPage
                    firstBtn.setDisabled(false)
                    lastBtn.setDisabled(true)
                    nextBtn.setDisabled(true)
                    previousBtn.setDisabled(false)

                    msg.edit({embeds: [pages[curPage-1]], components:[row]})
                }

                const collector = interaction.channel.createMessageComponentCollector({ time: 3500 });

                collector.on('collect', async i => {
                    if(i.user.id === interaction.user.id){
                        if (interaction.isButton()) {
                            interaction.deferUpdate();
                        }
                    }
                });

                collector.on('end', collected => {console.log(`Collected ${collected.size} items`); collector.stop()});
            });
        })
    },

Solution

  • You need to handle buttons inside your collector, and defer the collector's buttons, not the interaction itself.

    So remove completely the client.on part, and do something like this:

    // should definitely increase the time in your collector.
    const collector = interaction.channel.createMessageComponentCollector({ time: 3500 });
    
    collector.on('collect', async i => {
      // check if the author triggered the interaction
      if(i.member.id != interaction.user.id) {
        return i.reply({ content: `This interaction is not for you`, ephemeral: true})
      }
    
      if(i.customId == 'next') {
        // defer the interaction
        await i.deferUpdate();
    
        curPage += 1
        firstBtn.setDisabled(false)
        previousBtn.setDisabled(false)
        nextBtn.setDisabled(false)
    
        if(curPage == maxPage){
          nextBtn.setDisabled(true)
          lastBtn.setDisabled(true)
        }
        msg.edit({embeds: [pages[curPage-1]], components:[row]})
      }
    
      if(i.customId == 'previous') {
        // defer update
        await i.deferUpdate();
    
       // Rest of your code
     }
    
     // and so on
    
    });