#dynamic select menu handler

1 messages · Page 1 of 1 (latest)

slender vigil
#

I'm looking for an example of the architecture of someone handling a menu interaction, or any interaction besides ready.js and interactionCreate.js

modern wolf
#

oh boy, well what exactly are you looking for?

slender vigil
#

Really any working example beyond those two files. If I can see it in the full context of a repo, I think I can reverse engineer it or at least get past what's stumping me on the guide

modern wolf
#

i can write up a super quick sandbox box i guess
it's actually still exactly the same, if you'd tell me specifically what you want to look at, i could give you the pointers

slender vigil
#

Sure, let me push something to my repo

#

I have a menu command that I'm not sure how to incorporate into the events

modern wolf
#

what you should do is before checking if (!interaction.isCommand()) return;
check if it's a select menu and run that logic there

quartz isle
#

isButton(), isSelectMenu(), isCommand()

#

Depends on what you want

modern wolf
#

now do you want to also split up different files for your different menus as well?

slender vigil
#

so the control flow here is:

  1. deploy-command.js sends out commands to the target server
  2. index.js fires up ready.js and collects all events and commands.
  3. A valid command occurs on a server, which fires off to interactionCreate.js ..... and then.... that's where I get confused
modern wolf
#
if( interaction.isSelectMenu()) {
  // select menu process
} else if (interaction.isCommand()) {
  // slashie process
}```
if you want to split up your menu handlers, the guide doesn't have that
slender vigil
#

Is 1-3 right so far?

modern wolf
#

yes you're not wrong at all
but do you think that select menus get registered as commands? just asking if you misunderstood that

slender vigil
#

No I think in this example /menu is the command, and when the menus themselves get interacted with um, they fire off something, that manages to reach interactionCreate.js, but after that I get very confused.

#

Thank you for your patience by the way, when you don't know enough to form the right questions, it's a struggle to communicate

modern wolf
#

yeah i understand that, let me take a look at the repo a few minutes later, then i'll come back to this

quartz isle
#

A selectMenu fires the interactionCreate event. As I mentioned earlier you need to check if isButton(), isSelectMenu(), isCommand(), depending on what you want

slender vigil
#

Do I need to create a menu.js file in /events? Or should I be adding this to interactionCreate.js?

quartz isle
#

In the interactionCreate event. If the inreraction is a command, interaction.isCommand() will return true, if its a menu, interaction.isSelectMenu() returns true

#

And so on

quartz isle
#

^^

modern wolf
#

menu is the one that's out of place in your code

quartz isle
#

If you want it to divide it into different files once again, you can "copy" (adapting it to your needs) the code used to the event handler or the command handler

modern wolf
#

let's get this straight
menu, is a command, right?

slender vigil
#

yes /menu is a command

modern wolf
#

then what's it doing in the events file?

slender vigil
#

There's one in the commands folder too. I started putting something in events as well because I thought that's where interactionCreate.js would be reaching out to next. I don't fully understand how interactionCreate.js works

modern wolf
#

understood
so when you interact with the bot specifically, like with slash commands, select menus and buttons sent by the bot, it creates an interaction
and that emits interactionCreate
so with that out of the way let's go through your command handler

client.commands = new Collection(readdirSync('./commands').filter(isJs).map(path => {
   const cmd = require(`./commands/${path}`)
   return [cmd.data.name, cmd]
}))

so you are reading every single js file in this commands directory and then adding them in what is basically a javascript Map right? and attaching to your client
we did this to use it later on
now the structure for this is Collection<CommandName, CommandData>

name: 'interactionCreate',
    async execute(interaction) {
        if (!interaction.isCommand()) return;

        const command = interaction.client.commands.get(interaction.commandName);

        if (!command) return;

        try {
            await command.execute(interaction);
            console.log("in the try block of interactionCreate")
        } catch (error) {
            console.error(error);
            await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
        }

    },
``` here what you do is you check if the command the user initiated `interaction.commandName` is indeed a command you locally stored
and if so, you run it's .execute function
slender vigil
#

and the execution function is what's in the

async execute(interaction) { }``` block in the command's file, yes?
modern wolf
#

yes exactly

#

notice that we used .commandName, the unique commandName in order to identify which command we need to handle

custom ids, is what we'll use for selectmenus as their unique identifiers

#

first, prepare your code to handle both select menus and slashies, since your current code can be harder to follow

if( interaction.isSelectMenu()) {
  // select menu process
} else if (interaction.isCommand()) {
  // slashie process
}
``` move to this first
slender vigil
#

so I'm putting that code in the execute(interaction) {} block of ./commands/menu.js yes?

modern wolf
#

nope, we're putting this in our interaction create handler

#

/commands/menu.js is a command, we're dealing with interactionCreate the event

slender vigil
modern wolf
#

sorta but notice the interaction.isCommand at the very beginning

slender vigil
#

?

modern wolf
#

splendid

slender vigil
#

❤️

modern wolf
#

so what, now you want to dynamically execute select menus?

slender vigil
#

But how will I eventually differentiate between different commands? They all get handled in here?

modern wolf
#

different slashies?
yeah we use commandName as the unique identifier as i've mentioned

slender vigil
modern wolf
#

okay so this is not covered in the guide

#

as i've said, identifiers, with slashies, there are commandNames
with events there are.. well the event names
and with menus there are custom ids

so let's think of a structure first
make a select menus folder first and make a random menu file, like selectmenus/testMenu.js

slender vigil
#

on it!

modern wolf
#

okay so now let's look at the structure for an event handler for reference

module.exports = {
    name: 'ready',
    once: true,
    execute(client) {
        console.log(`Ready! Logged in as ${client.user.tag}`);
    },
};

so we export an object, where we have a

once, which we can count as some optional data and not care for now

an execute, which is the function that gets called, this is the handler itself pretty much

and name, this is how we know this is the ready file, this is as i've been saying for so long, the unique identifier ™️

modern wolf
#

now all we need to take from this is the unique identifier and the handler
and for menus, that would be customId
and then for the handler we can have an execute function

#

so the structure for each of the files in the SelectMenus folder is...

{
  customId: id,
  execute() {}
}```
#

are we clear up to this point?

slender vigil
#

I believe so

modern wolf
#

wanna push your changes to a temporary branch for now?

modern wolf
#

push some temporary data to the file as well
whatever customId you may want and an execute that maybe replies with hello

slender vigil
#

Okay I added some data to testMenu.js

#

see if I'm on the right track

#

or were you expecting a different format

#

?

modern wolf
#

why's your selectmenus/testMenu.js empty?

slender vigil
#

because I failed to commit and push!

#

try now

modern wolf
#

i'm telling you to push some dummy data to the file

// testMenu.js
module.exports = {
  customId: "someid",
  execute(interaction) => {
    interaction.reply("hello")
  }
}```
slender vigil
#

alright dumbed it down

modern wolf
#

one that follows the menu handler structure we've established

#

okay so you see how js client.commands = new Collection(readdirSync('./commands').filter(isJs).map(path => { const cmd = require(`./commands/${path}`) return [cmd.data.name, cmd] })) we attach these to client.commands?
we'll pretty much just copy this entire code
but assign it to client.selectMenus

#

this is in your index.js btw

slender vigil
#

so I'm creating a block that reads:

client.selectMenus = new Collection(readdirSync('./commands').filter(isJs).map(path => {
    const cmd = require(`./commands/${path}`)
    return [cmd.data.name, cmd]
}))```
and adding that to my index.js file as well?
modern wolf
#

yep

#

but we'll read ./selectmenus instead of ./commands

slender vigil
modern wolf
#

amazing

slender vigil
#

changes pushed

modern wolf
#

but

    return [cmd.data.name, cmd]
```we used the command name as the unique identifier right?
but select menus don't have names, what do we use as the identifiers for select menu handlers?
slender vigil
#

(changed line 17 to point to selectmenus as well. oops)

#

customIds!

modern wolf
#

😮

modern wolf
#

that's correct 👏👏👏
so now we're done pushing the data to a local collection

#

now move to interactionCreate.js

slender vigil
#

💃

#

so just to be clear

modern wolf
#
} else if (interaction.isCommand()) {
            // slashie process

            const command = interaction.client.commands.get(interaction.commandName);

            if (!command) return;

            try {

                await command.execute(interaction);
                console.log("in the try block of interactionCreate")
            } catch (error) {
                console.error(error);
                await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
            }


        }
``` okay so the process as i've explained
we receive the interaction, we search `client.commands` for the `commandName`, the *unique identifier*

then we call execute basically
now i want you to write the entire thing for menus by yourself
slender vigil
#

16-19 are correct now?

#

I mean return [cmd.data.customId, cmd] sorry

modern wolf
#

no you didn't change cmd.data.name to use customids
[cmd.customId, cmd]
the structure is like so
[identifier, handler]

modern wolf
slender vigil
#
client.selectMenus = new Collection(readdirSync('./selectmenus').filter(isJs).map(path => {
    const cmd = require(`./selectmenus/${path}`)
    return [cmd.customId, cmd]
}))
``` ?
modern wolf
#

👍

slender vigil
#

yessir

modern wolf
#

there is interaction.customId for menus

slender vigil
#

so inside of here:

//interactionCreate.js
    if (interaction.isSelectMenu()) {

            
            // select menu process
        } else if (interaction.isCommand()) {```
I can start to search for the custom id of the menu event coming through?
modern wolf
#

yes buut it's still the interactionCreate event, there's no menu event, i'm picky because it may lead to misunderstandings 👀

slender vigil
#

No I appreciate the pickiness, a lot

#

I appreciate all of this

#

so, so much

#
module.exports = {
    name: 'interactionCreate',
    async execute(interaction) {
        if (interaction.isSelectMenu()) {
            if (interaction.customId == "someid") {
                // do something here?
            }
            // select menu process
        } else if (interaction.isCommand()) {
            // slashie process
....
...
modern wolf
#

well yes but no
you want to dynamically execute this, check interaction.client.selectMenus if the customId is included
the same way you did here

 const command = interaction.client.commands.get(interaction.commandName);
            if (!command) return;
            try {
                await command.execute(interaction);
slender vigil
#
// interactionCreate.js
module.exports = {
    name: 'interactionCreate',
    async execute(interaction) {

        if (interaction.isSelectMenu()) {

            const command = interaction.client.selectmenus.get(interaction.customId);
            if (!command) return;
            try {
                await command.execute(interaction);
            }

            // select menu process
        } else if (interaction.isCommand()) {
#

?

modern wolf
#

hmmm

#

yep, all okay 👍

#

congrats you just set up a dynamic menu handler

#

now to test it

#

i believe you have a menu command, right? make it send a menu where the customId is someid

slender vigil
#
//  ./commands/menu.js
const row = new MessageActionRow()
            .addComponents(
                new MessageSelectMenu()
                    .setCustomId('someid')  // like this, Ben?
                    .setPlaceholder('Select a dungeon')
                    .addOptions([
                        {
                            label: 'DOS',
                            description: 'De Other Side',
                            value: 'DOS',
                        },
                        {
                            label: 'HOA',
                            description: 'Halls of Atonement',
                            value: 'HOA',
                        },
                        
...```
modern wolf
#

🧑‍🍳🤌

slender vigil
#

heheh

modern wolf
#

now send it, and then click it

slender vigil
modern wolf
#

uh oh
time for debugging
in here

        if (interaction.isSelectMenu()) {
// in here
```you should log a few things, interaction.customId, interaction.client.selectMenus
slender vigil
#
        if (interaction.isSelectMenu()) {
            console.log("In the interaction.selectMenu() is true block") // yeah?
            const command = interaction.client.selectmenus.get(interaction.customId);
            if (!command) return;
            try {
                
                await command.execute(interaction);

            }```
modern wolf
#

yeah

#

also wasn't it client.selectMenus

#

with a capital m

slender vigil
#

it was indeed. good catch

#

ah I could kiss you

modern wolf
#

yeah better not log it as a string but as an object log(string, object)

#

congrats!
now you can have different files for your different menus

#

also before we wrap this up
rename this thread to "dynamic select menu handler" please, that way people looking for the issue may find it helpful and we're done 🥳

slender vigil
#

dynamic select menu handler

#

Thank you so much for your time, patience, and hand holding