Conditional role-giving in Discord.JS

Viewed 202

I'm extremely new to coding in JavaScript and I'm setting up a simple event bot for a Discord server.

It's very simple in concept, all the bot really does is allow anyone to assign a "Dead" role to any member they mention.

const { Client, Intents } = require('discord.js');
const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] });
var prefix = "!";
client.on('ready', () => { console.log(`Logged in as ${client.user.tag}!`) });
client.on("messageCreate", (message) => {
    let staff = message.guild.roles.cache.find(r => r.name === "Staff");
    let dead = message.guild.roles.cache.find(r => r.name === "Dead");
    let member = message.mentions.members.first();
    if (!message.content.startsWith(prefix)) return;
    //Ping command
    if (message.content.startsWith(prefix + "ping")) {
        message.channel.send("Pong!");
    }
    //Error if command doesn't include a mention
    if (!member)
        return message.channel.send('You need to mention a member, <@' + message.author.id + '>')
    //Kill command
    if (message.content.startsWith(prefix + "kill")) {
        if (message.member.roles.cache.has(staff)) {
            return message.channel.send(`${member} is not participating in the event!`);
        }
        if (message.member.roles.cache.has(dead)) {
            return message.channel.send(`${member.toString()} is already dead!`);
        } else {
            member.roles.add(dead);
            return message.channel.send(`${member.toString()} has been eliminated!`);
        }

    }
}
);
client.login('Token');

However, the problems I'm having are:

• The kill command, while it works in giving people the Dead role and announcing a member getting eliminated, it doesn't throw the corresponding errors if a member is already dead or has the Staff role. It just announces that the member has been eliminated, regardless of what roles they have.

• Typing any word with a ! before (like !test) results in the "Need to mention a member" error. (This includes the !ping command)

What am I doing wrong here? I've checked the Discord.js documentation for v13 and I can't seem to find an answer. Thanks in advance!

EDIT: Fixed everything! I had forgotten a return for the ping and I ended up using incorrect functions for the kill command. This is the final code and it works as intended.

const { Client, Intents } = require('discord.js');
const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] });
var prefix = "!";
client.on('ready', () => { console.log(`Logged in as ${client.user.tag}!`) });
client.on("messageCreate", (message) => {
    if (!message.content.startsWith(prefix)) return;
//Ping command
    if (message.content.startsWith(prefix + "ping")) {
        return message.channel.send('Pong! <@' + message.author.id + '>');
    }
//Kill command
    if (message.content.startsWith(prefix + "kill")) {
        let member = message.mentions.members.first();
        let dead = message.guild.roles.cache.find(r => r.name === "Dead");
//No mention
        if (!member)
        return message.channel.send('You need to mention a member, <@' + message.author.id + '>');
//Staff check
        if (member.roles.cache.find(r => r.name == "Staff")) {
            return message.channel.send(`${member} is not participating in the event!`);
        }
//Dead check
        if (member.roles.cache.find(r => r.name == "Dead")) {
            return message.channel.send(`${member.toString()} is already dead!`);
        } else {
            member.roles.add(dead);
            return message.channel.send(`${member.toString()} has been eliminated!`);
        }
    }
});

Thank you all for your help! And feel free to critique the final code as well, all tips and tidbits are welcome!

3 Answers

Checking for roles

The member.roles.cache that you are attempting to search in is a Collection object, which extends the normal JavaScript Map. Maps store their values in a Key-Value pair. In this case the roles is the value and the key is an auto-generated Snowflake, which is basically a unique ID. Now, perhaps counter-intuitively, the .has() function is not actually searching if your cache (Map) has a value matching whatever you parse as a parameter, but if it has a key that matches it.

Searching if a Map has a specific value in it is a bit more tricky. There's a few ways to go about it. One of those is to quickly convert the Map to an Array and call .includes() on that array like so:

Array.from(yourMap.values()).includes(yourRole)

Alternatively, you can iterate over all values of the Map and check them all against your roles like so:

const findInMap = (map, val) => {
  for (let [k, v] of map) {
    if (v === val) { 
      return true; 
    }
  }  
  return false;
}

I'm not a fan of either way to be honest, but since you do not have a choice in how the roles are stored you have to make due, right?

"Need to mention a member"

It is very important in programming to keep track of what path your program will take when it is executed. In your .on('messageCreate') function we can take a look at this.

First we fetch a bunch of variables and check if the message starts with your predefined prefix for commands. If it does not you return so it leaves the codeblock immediately, which is good. However, in the next if statement this goes a bit wrong. You check if the command is !ping and if so, you send a message back. Nowhere do you tell the program to leave the codeblock such as with a return statement even though we know the rest of the code in there is irrelevant to the message we received.

Because you only check if the message has your prefix and then nothing stops it from reaching the if-statement that asks if a member is mentioned it will be executed for every message that starts with !.

A solution

To solve this problem you can very simply move the if-statement that checks for a mentioned member into the if-statement block that is executed after we know it is an !kill command. This way it will only require a mentioned member if we actually do an !kill command.

It is because the code is executing all your conditions. If you want ping to actually just ping and not execute rest of the command you can add return to it.

For example:

return message.channel.send("Pong!");

I see you have utilised return already on your code but for example the ping commands response does not have return statement so that is why it will check if the member is null or not

Ok, so the reason you always get the "need to mention a member" thing is because of two reasons, first you don't return on the ping command and second because you don't check for a member in the command where you need it. You don't need to do that on a "global" level since not every command needs a mention. So the fix here is to move the member check into the actual command part of your code.

I would also recommend using some kind of command handler that catches wrong commands.

As for the first problem, you need to check for roles via the ID of the role. That is because the cache of the roles Manager is a Collection, which is mapped by the ID of whatever it is thats in that collection. So simply add a .id at the end and it should work just fine.

Your code in a corrected way:

client.on("messageCreate", (message) => {
    if (!message.content.startsWith(prefix)) return;
    //Ping command
    if (message.content.startsWith(prefix + "ping")) {
        return message.channel.send("Pong!");
    }
    //Kill command
    if (message.content.startsWith(prefix + "kill")) {
        let staff = message.guild.roles.cache.find(r => r.name === "Staff");
        let dead = message.guild.roles.cache.find(r => r.name === "Dead");
        let member = message.mentions.members.first();
        //Error if command doesn't include a mention
        if (!member) return message.channel.send('You need to mention a member, <@' + message.author.id + '>');

        if (message.member.roles.cache.has(staff.id)) {
            return message.channel.send(`${member} is not participating in the event!`);
        }
        if (message.member.roles.cache.has(dead.id)) {
            return message.channel.send(`${member.toString()} is already dead!`);
        } else {
            member.roles.add(dead);
            return message.channel.send(`${member.toString()} has been eliminated!`);
        }

    }
});
Related