Skip to content

Commit

Permalink
refactor(parsing-mention-arguments): remove everything message comman…
Browse files Browse the repository at this point in the history
…d related (discordjs#901)
  • Loading branch information
didinele authored Oct 11, 2021
1 parent 3380f2b commit 47155d5
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 237 deletions.

This file was deleted.

76 changes: 0 additions & 76 deletions code-samples/miscellaneous/parsing-mention-arguments/13/index.js

This file was deleted.

174 changes: 17 additions & 157 deletions guide/miscellaneous/parsing-mention-arguments.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,8 @@
# Parsing mention arguments
# Parsing mentions

In a previous chapter, you learned how to build commands with user input; you also learned how to use *mentions* as user input.
However, using `message.mentions` can lead to a few problems.
For example, you do not know which mention belongs to which argument.
Or if you are splitting the message's content by spaces to get the args,
The mentions will still take up space in your args array, messing up the rest of your args parsing if you are not careful.

Say you are writing a bot for moderating your server. You will want a kick or a ban command, which allows you to mention the person you are trying to ban.
But what happens if you try to use the command like this?

<DiscordMessages>
<DiscordMessage profile="user">
!ban <DiscordMention>Offender</DiscordMention> Because they were rude to <DiscordMention>Victim</DiscordMention>.
</DiscordMessage>
</DiscordMessages>

You might expect it to ban @Offender because that is who you mentioned first.
However, the Discord API does not send the mentions in the order they appear; They are sorted by their ID instead.

If the @Victim happens to have joined Discord before @Offender and has a smaller ID, they might get banned instead.
Or maybe someone misuses a command, the bot might still accept it, but it will create an unexpected outcome.
Say someone accidentally used the ban command like this:

<DiscordMessages>
<DiscordMessage profile="user">
!ban Because they were rude to <DiscordMention>Victim</DiscordMention>.
</DiscordMessage>
</DiscordMessages>

The bot will still ban someone, but it will be the @Victim again. `message.mentions.users` still contains a mention, which the bot will use. But in reality, you would want your bot to be able to tell the user they misused the command.
Discord.js is already geared to help you handle mentions using `message.mentions`.
However, there are situations where using `message.mentions` can lead to a few problems, in which case you may want to parse them on your own.
For example, you cannot tell where the mention is located in the message's content, or if the same user/role/channel was mentioned more than once.

## How Discord mentions work

Expand All @@ -53,36 +27,10 @@ then the `message.content` for that message will look something like this

## Implementation

So, how do you use this new information for your bot?
Most of your code will not change; however, instead of using `message.mentions` to find the mentioned users, you will have to do it manually.
This may sound scary at first, but you will see it is pretty simple once you see the code.

Say you already have a simple command handler like this:

```js
client.on('messageCreate', message => {
if (!message.content.startsWith(prefix) || message.author.bot) return;

const args = message.content.slice(prefix.length).trim().split(/ +/);
const command = args.shift().toLowerCase();
});
```

Now you can quickly test the waters by upgrading the avatar command from [last time](/creating-your-bot/commands-with-user-input.md).
This is what we have so far. It is pretty simple; it will show the avatar of who used the command.
```js {3-7}
client.on('messageCreate', message => {
// ...
if (command === 'avatar') {
const user = message.author;
Instead of using `message.mentions` to find say, a user, you will have to do it manually, which requires a few simple steps.

return message.channel.send(`${user.username}'s avatar: ${user.displayAvatarURL({ dynamic: true })}`);
}
});
```

But how do you get the correct user now? Well, this requires a few simple steps.
Putting it into a function will make it easily reusable. We will use the name `getUserFromMention` here.

```js
function getUserFromMention(mention) {
if (!mention) return;
Expand All @@ -107,103 +55,19 @@ It essentially just works itself through the structure of the mention bit by bit
Whenever it encounters an error with the mention (i.e., invalid structure), it merely returns `undefined` to signal the mention is invalid.

::: tip
The `.slice()` method is used in a more advanced way here. You can read the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) for more info.
The `.slice()` method is used with a negative number. You can read [MDN's documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) for information on how that works.
:::

Now you have a nifty function you can use to convert a raw mention into a proper user object.
Plugging it into the command will give you this:

```js {4-11}
client.on('messageCreate', message => {
// ...
if (command === 'avatar') {
if (args[0]) {
const user = getUserFromMention(args[0]);
if (!user) {
return message.reply('Please use a proper mention if you want to see someone elses avatar.');
}

return message.channel.send(`${user.username}'s avatar: ${user.displayAvatarURL({ dynamic: true })}`);
}

return message.channel.send(`${message.author.username}, your avatar: ${message.author.displayAvatarURL({ dynamic: true })}`);
}
});
```

And here, we plug the new function into the command.
If the user-supplied an argument, it should be the user mention, so it just gets passed right into the function.

And that is it! Simple, isn't it? Start up your bot and see if it works.

<DiscordMessages>
<DiscordMessage author="AnotherUser" avatar="green">
!avatar <DiscordMention profile="user" />
</DiscordMessage>
<DiscordMessage profile="bot">
User's avatar:
<a href="https://i.imgur.com/AfFp7pu.png" target="_blank" rel="noreferrer noopener">https://cdn.discordapp.com/avatars/123456789012345678/0ab1c2d34efg5678902345h6i7890j12.png</a>
<br />
<img src="https://i.imgur.com/AfFp7pu.png" style="width: 128px; height: 128px;" alt="" />
</DiscordMessage>
</DiscordMessages>

So now, instead of using `message.mentions`, you can use your new, fantastic function.
This will allow you to add proper checks for all your args so that you can tell when a command is and isn't used correctly.

But this does not mark the end of the page. If you feel adventurous, you can read on and learn how to use Regular Expressions to easily convert a mention into a user object in just two lines.

### Ban command

You now know how to parse user mentions for a simple command like the avatar command. However, the avatar command does not benefit from it as much as the intro's example.

When writing a ban command where a mention might appear in the reason, manual parsing mentions is a lot more important. You can see an example of how to do it as follows:

```js {1,3-21}
client.on('messageCreate', async message => {
// ...
if (command === 'ban') {
if (args.length < 2) {
return message.reply('Please mention the user you want to ban and specify a ban reason.');
}

const user = getUserFromMention(args[0]);
if (!user) {
return message.reply('Please use a proper mention if you want to ban someone.');
}

const reason = args.slice(1).join(' ');
try {
await message.guild.members.ban(user, { reason });
} catch (error) {
return message.channel.send(`Failed to ban **${user.tag}**: ${error}`);
}

return message.channel.send(`Successfully banned **${user.tag}** from the server!`);
}
});
```

Now if you send a command like the following you can always be sure it will use the mention at the very front to figure out who to ban, and will properly validate the mention:

<DiscordMessages>
<DiscordMessage profile="user">
!ban <DiscordMention>Offender</DiscordMention> because they were rude to <DiscordMention>Victim</DiscordMention>.
</DiscordMessage>
</DiscordMessages>

### Using Regular Expressions

Previously you learn how to use rudimentary string-related functions to turn the special mention syntax Discord uses into a proper discord.js User object.
Previously you learned how to use rudimentary string-related functions to turn the special mention syntax Discord uses into a proper discord.js User object.
But using Regular Expressions (aka "RegEx" or "RegExp"), you can condense all that logic into a single line! Crazy, right?

If you have never worked with Regular Expressions before, this might seem daunting. But in fact, you already have used regular expressions. Remember `withoutPrefix.split(/ +/);`? This little `/ +/` is a Regular Expression. The `/` on either side tell JavaScript where the Regular Expression begins and where it ends; the stuff in between is its content.

::: tip
For a more detailed explanation, please consult the [MDN's documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp).
For a more detailed explanation, consult [MDN's documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) on regular expressions.
:::

The RegEx you will use for user mentions will look like this: `/^<@!?(\d+)>$/`.
The RegEx you will use for user mentions will look something like this: `/^<@!?(\d+)>$/`.
Here is how the RegEx works:

1. The `^` at the beginning and the `$` at the end means the mention has to take up the entire string.
Expand All @@ -214,32 +78,28 @@ Here is how the RegEx works:

Using the `.match()` method on strings, you can get the capture group's values, i.e., the mention's ID.

::: warning
discord.js has <DocsLink path="class/MessageMentions?scrollTo=s-CHANNELS_PATTERN">built-in patterns</DocsLink> for matching mentions, however as of version 11.4 they do not contain any groups
and thus aren't useful for actually getting the ID out of the mention.
::: tip
discord.js has <DocsLink path="class/MessageMentions?scrollTo=s-CHANNELS_PATTERN">built-in patterns</DocsLink> for matching mentions.
:::

Updating your `getUserFromMention` function to use RegEx gives you this:

```js
const { MessageMentions: { USERS_PATTERN } } = require('discord.js');

function getUserFromMention(mention) {
// The id is the first and only match found by the RegEx.
const matches = mention.match(/^<@!?(\d+)>$/);
const matches = mention.match(USERS_PATTERN);

// If supplied variable was not a mention, matches will be null instead of an array.
if (!matches) return;

// However, the first element in the matches array will be the entire mention, not just the ID,
// The first element in the matches array will be the entire mention, not just the ID,
// so use index 1.
const id = matches[1];

return client.users.cache.get(id);
}
```

See? That is *much* shorter and not that complicated.
If you rerun your bot now, everything should still work the same.

## Resulting code

<ResultingCode />
That is *much* shorter, and not all that complicated!

0 comments on commit 47155d5

Please sign in to comment.