I'm having an issue creating my multiple select menu options on Discord.js v14.
I'm wanting to add more than one role to the member when more than one option is selected in the drop-down menu. However, I'm getting the following error:
TypeError [InvalidType]: Supplied roles is not a Role, Snowflake or Array or Collection of Roles or Snowflakes issue
If one role is selected it works fine and adds the role but when two roles are selected this error crops up.
This is the code I'm working with:
if (interaction.isSelectMenu()){
if (interaction.customId == "optin_menu"){
let optional_roles = "";
await interaction.values.forEach(async value => {
optional_roles = `${value}` //the value being the name of the role
const roles = interaction.guild.roles.cache.find(r => r.name === `${optional_roles}`); // Find the role with by name
const member = interaction.guild.members.cache.get(interaction.user.id); // The member we add the role too.
member.roles.add(roles)
});
interaction.reply({content: `${optional_roles} added`, ephemeral: true})
Help appreciated.
CodePudding user response:
So there are several problems with your code.
The first thing is you try to use the operation_roles
variable to store a list of role names (I think). But you also use it to find the role (r => r.name === optional_roles
). If the select menu values are "Role 1"
, "Role 2"
and "Role 3"
, on the first iteration operation_roles
is "Role 1"
, on the second one it will be "Role 1 Role 2"
and on the third one it becomes "Role 1 Role 2 Role 3"
. You won't be able to find roles with those names.
You will want to find the role by value
, like this:
// Find the role with by name
const role = interaction.guild.roles.cache.find(
r => r.name === value
);
Another error is that you don't handle cases when roles
is not a Role, so member.roles.add
will throw a TypeError; "Supplied roles is not a Role, Snowflake or Array or Collection of Roles or Snowflakes issue".
if (!role || !member) {
// do something if either member or role not available
return null;
}
member.roles.add(role);
Next, await
won't do anything in front of that forEach
.
Another error is that you try to find the member who selected the items in the menu (interaction.user.id
). You'll want to get the member whose roles you want to update.
I'd suggest you use the role ID instead of the name as the value
of the select menu. Not sure what your current code looks like, but I'd use something like this:
const row = new ActionRowBuilder()
.addComponents(
new SelectMenuBuilder()
.setCustomId('optin_menu')
.setPlaceholder('Select role(s)')
.setMinValues(1)
.setMaxValues(25)
.addOptions(
interaction.guild.roles.cache.first(25).map((role) => ({
label: role.name,
description: role.name,
// use role.id instead
value: role.id,
})),
),
);
When you receive the interaction
with the customId
optin_menu
you can wait for all the roles to be added first. You can create an array of promises and use Promise.all()
to resolve these. You could return the role names from these promises and later just send them in your interaction.reply()
:
if (interaction.customId === 'optin_menu') {
const promises = interaction.values.map(async (value) => {
// Find the role by its ID instead
const role = interaction.guild.roles.cache.get(value);
const member = interaction.guild.members.cache.get('memberID');
if (!role || !member)
return null;
// wait for the role to be added
await member.roles.add(role);
// return the role name
return role.name;
});
// wait for the promises to resolve
const rolesAdded = await Promise.all(promises);
// send a message and join the roles by a comma
interaction.reply({
content: `The following roles are added: ${rolesAdded
.filter(Boolean)
.join(', ')}`,
ephemeral: true,
});
}
Now, you'll need to find out how to pass the member
and use it inside if (interaction.customId === 'optin_menu')
. One quick hack would be to add the member ID to your select menu's value
. In this example, I just create a string with the role ID and the member ID separated by a -
. This way you could later split it and use both IDs.
If you have a slash command like this:
new SlashCommandBuilder()
.setName('addrole')
.setDescription('Add roles to a member')
.addUserOption((option) =>
option
.setName('target')
.setDescription('Select a user')
.setRequired(),
)
Something like this should work:
client.on('interactionCreate', async (interaction) => {
if (interaction.commandName === 'addrole') {
// as we added .setName('target') above we use .getMember('target')
const member = interaction.options.getMember('target');
const row = new ActionRowBuilder().addComponents(
new SelectMenuBuilder()
.setCustomId('optin_menu')
.setPlaceholder('Select role(s)')
.setMinValues(1)
.setMaxValues(25)
.addOptions(
interaction.guild.roles.cache.first(25).map((role) => ({
label: role.name,
description: role.name,
value: `${role.id}-${member.id}`,
})),
),
);
interaction.reply({
content: `Please, select at least one role to add it to ${member}!`,
components: [row],
});
}
if (interaction.customId === 'optin_menu') {
const promises = interaction.values.map(async (value) => {
const [roleID, memberID] = value.split('-');
const role = interaction.guild.roles.cache.get(roleID);
const member = interaction.guild.members.cache.get(memberID);
if (!role || !member) return null;
await member.roles.add(role);
return role.name;
});
let rolesAdded = await Promise.all(promises);
interaction.reply({
content: `The following roles are added: ${rolesAdded
.filter(Boolean)
.join(', ')}`,
ephemeral: true,
});
}
});
CodePudding user response:
if (!interaction.isSelectMenu()) {
return
}
if (customId === 'optin_menu') {
const component = interaction.component
const removed = component.options.filter((option) => {
return !values.includes(option.value)
})
for (const id of removed) {
const role = interaction.guild.roles.cache.find(r => r.name === `${id.value}`);
member.roles.remove(role)
}
for (const id of values) {
const role2 = interaction.guild.roles.cache.find(r => r.name === `${id}`);
member.roles.add(role2)
}
interaction.reply({
content: 'Roles updated!',
ephemeral: true
})
};