#
Command with arguments
#
Creating an array of arguments
The first thing that we need to do to use arguments, is to actually separate them. A command with arguments would normally look something like this: !mycommand arg1 arg2 arg3
In this, we need to do 3 things:
- Remove the prefix
- Grab the command part (
mycommand
) - Grab the array of arguments which will be:
["arg1", "arg2", "arg3"]
In the greatest majority of the code I've seen, arguments are split at the beginning of the code, and each command will put the argument array back together as necessary within the command code.
In my experience, the best (and most efficient) way of separating all these things is the following 2 lines of code:
const args = message.content.slice(prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
Let's break this down into what it actually does, line by line.
.slice(prefix.length)
removes the prefix such as!
or+
from the message content, leavingmycommand arg1 arg2 arg3
..trim()
ensures there's no extra spaces before/after the text..split(/ +/g)
splits the string by one or many spaces. Why not just by space? Because sometimes especially on mobile, you might have an extra space before or after mentions, or just straight up to an extra space by mistake. This means thatmycommand arg1 arg2 arg3
will work just as well as if they only had 1 space.
On the second line:
args.shift()
whereshift()
will remove one element from the array and return it. This gives usmycommand
that's returned, and theargs
array becomes only['arg1', 'arg2','arg3']
.toLowerCase()
so our command is always in lowercase, meaning!Ping
,!ping
and!PiNg
will all work.
#
Using the command
variable properly
So now that we have our command
variable, we no longer need to use the if (message.content.startsWith(prefix+'command'))
for every command. We can simplify this by looking only at the command
variable itself. For example, these 2 very basic commands:
if (command === 'ping') {
message.channel.send('Pong!');
} else
if (command === 'blah') {
message.channel.send('Meh.');
}
Now isn't that just so pretty and clean? I love it!
Alternatively, you can also use this in a switch/case command block (I don't like it, but to each their own, right?):
switch (command) {
case "ping" :
message.channel.send('Pong!');
break;
case "blah" :
message.channel.send('Meh.');
break;
}
Here's a complete example that's very often the command handler I've used as a base to build on:
client.on("messageCreate", message => {
if (message.author.bot) return;
// This is where we'll put our code.
if (message.content.indexOf(config.prefix) !== 0) return;
const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
if (command === 'ping') {
message.channel.send('Pong!');
} else
if (command === 'blah') {
message.channel.send('Meh.');
}
});
#
Working with the arguments
Alright let's get to the meat of this page: actually using the args
array in a few command examples.
The first one is a perfectly useless command, but for the life of me, I can't actually think of a really simple command using only one-word arguments. So here is a ridiculous ASL command:
if (command === "asl") {
let age = args[0]; // Remember arrays are 0-based!.
let sex = args[1];
let location = args[2];
message.reply(`Hello ${message.author.username}, I see you're a ${age} year old ${sex} from ${location}. Wanna date?`);
}
And if you want to be really fancy with ECMAScript 6, here's an awesome one:
if (command === "asl") {
let [age, sex, location] = args;
message.reply(`Hello ${message.author.username}, I see you're a ${age} year old ${sex} from ${location}. Wanna date?`);
}
This is called Destructuring and it's awesome!
#
Grabbing Mentions
Another way to use arguments, when the command should target a specific user (or users), is to use Mentions. For instance, to kick annoying shit-posters with !kick @Xx_SniperBitch_xX @UselessIdiot
can be done with ease, instead of attempting to grab their ID or their name.
In the context of the message
event handler, all mentions in a message are part of the message.mentions
object. This object then contains multiple Collections of different mention types. Here are the various available mention types:
message.mentions.members
contains all @mention as GuildMember objects.message.mentions.users
contains all @mention as User objects.message.mentions.roles
contains all @role mention as Role objects.message.mentions.channels
contains all #channel mentions as TextChannel or VoiceChannel objects.
Each of these are collections so any collection method can be used on them. The most common method to use on mentions is .first() which gets the very first mention, since there is often only one of them.
Let's build a quick and dirty kick
command, then. No error handling or mod checks - just straight up! (Cul Sec, as the French would say):
// Kick a single member in the mention
if (command === "kick") {
let member = message.mentions.members.first();
member.kick();
}
This would be called with, for example, !kick @AnnoyingUser23
#
Variable Length arguments
Let's make the above kick command a little better. Because Discord now supports kick reasons in the Audit Logs, the Discord.js kick()
command also supports an optional reason
argument. But, because the reason can have multiple words in it, we need to join all these words together.
So let's do this now, with what we've already learned, and a little extra:
if (command === "kick") {
let member = message.mentions.members.first();
let reason = args.slice(1).join(" ");
member.kick(reason);
}
So, the reason is obtained by removing the first elements (the mention, which looks like <@1234567489213>
) and re-joining the rest of the array elements with a space.
To use this command, a user would do something like: !kick @SuperGamerDude Obvious Troll, shit-posting
.
Here's another example, with a super simple command, the say
command. It makes the bot say what you just sent, and then delete your message:
if (command === "say"){
let text = args.join(" ");
message.delete();
message.channel.send(text);
}
If you're thinking, "What if I have more than one argument with spaces?", yes that's a tougher problem. Ideally, if you need more than one argument with spaces in it, do not use spaces to split the arguments. For example, !newtag First Var Second Var Third Var
won't work. But !newtag First Var;Second Var;Third Var;
could work by removing the command, splitting by ;
then splitting by space. Not for the faint of heart!
#
Going one step further
Now, there's most definitely always room for some optimization, and better code. At this point, "parsing arguments" becomes something you might realize is necessary for all of your commands, and writing "(command === 'thing')" for every command is dull and boring. So, as your next step, consider looking at making A Basic Command Handler. This greatly simplifies the creation of new commands.