Skip to content

Commit

Permalink
added understanding
Browse files Browse the repository at this point in the history
  • Loading branch information
DiamondLionLV authored and DiamondLionLV committed Dec 22, 2021
1 parent 0f6966c commit 7a19b7c
Show file tree
Hide file tree
Showing 4 changed files with 565 additions and 0 deletions.
79 changes: 79 additions & 0 deletions understanding/collections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Collections

In this page we will explore Collections, and how to use them to grab data from various part of the API.

A **Collection** is a _utility class_ that stores data. Collections are the Javascript Map\(\) data structure with additional utility methods. This is used throughout discord.js rather than Arrays for anything that has an ID, for significantly improved performance and ease-of-use.

Examples of Collections include:

* `client.users.cache`, `client.guilds.cache`, `client.channels.cache`
* `guild.channels.cache`, `guild.members.cache`
* message logs \(in the callback of `messages.fetch()`\)
* `client.emojis.cache`

## Getting by ID

Very simply, to get anything by ID you can use `Collection.get(id)`. For instance, getting a channel can be `client.channels.cache.get("81385020756865024")`. Getting a user is also trivial: `client.users.cache.get("139412744439988224")`

## Finding by key

If you don't have the ID but only some other property, you may use `find()` to search by property:

`let guild = client.guilds.cache.find(guild => guild.name === "discord.js - imagine a bot");`

The _first_ result that returns `true` within the function, will be returned. The generic idea of this is:

`let result = <Collection>.find(item => item.property === "a value")`

You can also be looking at other data, properties not a the top level, etc. Your imagination is the limit.

Want a great example? Here's getting the first role that matches one of 4 role names:

```js
const acceptedRoles = ["Mod", "Moderator", "Staff", "Mod Staff"];
const modRole = member.roles.cache.find(role => acceptedRoles.includes(role.name));
if (!modRole) return "No role found";
```

Don't need to return the actual role? `.some()` might be what you need. It's faster than find, but will only return a boolean true/false if it finds something:

```js
const hasModRole = member.roles.cache.some(role => acceptedRoles.includes(role.name));
// hasModRole is boolean.
```

## Custom filtering

_Collections_ also have a custom way to filter their content with an anonymous function:

`let large_guilds = client.guilds.cache.filter(g => g.memberCount > 100);`

`filter()` returns a new collection containing only items where the filter returned `true`, in this case guilds with more than 100 members.

## Mapping Fields

One great thing you can do with a collection is to grab specific data from it with `map()`, which is useful when listing stuff. `<Collection>.map()` takes a function which returns a string. Its result is an array of all the strings returned by each item. Here's an example: let's get a complete list of all the guilds a bot is in, by name:

```js
const guildNames = client.guilds.cache.map(g => g.name).join("\n")
```

Since `.join()` is an array method, which links all entries together, we get a nice list of all guilds, with a line return between each. Neat!

We can also get a most custom string. Let's pretend the `user.tag` property doesn't exist, and we wanted to get all the user\#discriminator in our bot. Here's how we'd do it \(using awesome template literals\):

```js
const tags = client.users.cache.map(u => `${u.username}#${u.discriminator}`).join(", ");
```

## Combining and Chaining

In a lot of cases you can definitely chain methods together for really clean code. For instance, this is a comma-delimited list of all the small guilds in a bot:

```js
const smallGuilds = client.guilds.cache.filter(g => g.memberCount < 10).map(g => g.name).join("\n");
```

## More Data!

To see **all** of the Discord.js Collection Methods, please [refer to the docs](https://discord.js.org/#/docs/collection/main/general/welcome). Since Collection extends Map\(\), you will also need to refer to [this awesome mdn page](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) which describe the native methods - most notably `.forEach()`, `.has()`, etc.
80 changes: 80 additions & 0 deletions understanding/events-and-handlers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Events and Handlers

We already explored one event handler in [your first bot](../first-bot/your-first-bot.md), the `message` handler. Now let's take a look at some of the most important handlers that you will use, along with an example.

_**DO NOT NEST EVENTS**_ One important point: Do not nest any events \(aka "put one inside another"\). Ever. Events should be at the "root" level of your code, _beside_ the `message` handler and not within it.

## The `ready` event and its importance

Ah, asynchronous coding. So awesome. So hard to grasp when you first encounter it. The reality of discord.js and many, many other libraries you will encounter, is that code is not executed one line at a time, one after the other.

It should have been made obvious with the user of `client.on("message")` which triggers for each message. To explain how the `ready` event is important, let's look at the following code:

```js
const { Client, Intents } = require("discord.js");
const client = new Client({
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES]
});

client.user.setActivity("Online!");

client.login("yourToken");
```

This code will not work, because `client` is not immediately available after it's been initialized. `client.user` will be undefined in this case, even if we flipped the setActivity and login lines. This is because it takes a small amount of time for discord.js to load its servers, users, channels, and all that jazz. The more servers the bot is on, the longer it takes.

To ensure that `client` and all its "stuff" is ready, we can use the `ready` event. Any code that you want to run on bootup that requires access to the `client` object, will need to be in this event.

Here's a simple example of using the `ready` event handler:

```js
client.on("ready", () => {
client.user.setActivity(`on ${client.guilds.cache.size} servers`);
console.log(`Ready to serve on ${client.guilds.cache.size} servers, for ${client.users.cache.size} users.`);
});
```

## Detecting New Members

Another useful event once you've enabled the privileged intent and added `GUILD_MEMBERS` to your intents array that is, is [`guildMemberAdd`](https://discord.js.org/#/docs/main/master/class/Client?scrollTo=e-guildMemberAdd). Which triggers whenever someone joins any of the servers the bot is on. You'll see this on smaller servers: a bot welcomes every new member in the \#welcome channel. The following code does this.

```js
client.on("guildMemberAdd", (member) => {
console.log(`New User "${member.user.username}" has joined "${member.guild.name}"` );
member.guild.channels.cache.find(c => c.name === "welcome").send(`"${member.user.username}" has joined this server`);
});
```

The objects available for each event are important: they're only available within these contexts. Calling `message` from the `guildMemberAdd` would not work - it's not in context. `client` is always available within all its callbacks, of course.

## Errors, Warn and Debug messages

Yes, bots fail sometimes. And yes, the library can too! There's a little trick we can use, however, to prevent complete crashes sometimes: Capturing the `error` event.

The following small bit of code \(which can be anywhere in your file\) will catch all output message from discord.js. This includes all errors, warning and debug messages.

**NOTE:** The debug event **WILL** output your token partially, so exercise caution when handing over a debug log.

```js
client.on("error", (e) => console.error(e));
client.on("warn", (e) => console.warn(e));
client.on("debug", (e) => console.info(e));
```

## Testing Events

So now you're wondering, how do I test those events? Do I have to join a server with an alternate account to test the guildMemberAdd event? Isn't that, like, super annoying?

Actually, there's an easy way to test almost any event. Without going into too many details, `client` , your Discord Client, extends something called the `EventHandler`. Any time you see `client.on("something")` it means you're handling an `event` called `"something"`. But EventHandler has another function other than `on`. It has `emit`. Emit is the counterpart for `on`. When you `emit` an event, it's handled by the callback for that event in `on`.

So what does it _mean_??? It means that if _you_ emit an event, your code can capture it. I know I know I'm rambling without giving you an example and you're here for examples. Here's one:

```js
client.emit("guildMemberAdd", message.member);
```

This emits the event that normally triggers when a new member joins a server. So it's _pretending_ like this particular member has rejoined the server even if they have not. This obviously works for any event but you have to provide the proper arguments for it. Since `guildMemberAdd` requires only a member, any member will do \(see [FAQ](../frequently-asked-questions.md) to know how to get another member\). I can trigger the `ready` event again by using `client.emit("ready")` \(the ready event does not take any parameter\).

What about other events? Let's see. `guildBanAdd` takes 2 parameters: `guild` and `user` , to simulate that a user was banned. So, you could `client.emit("guildBanAdd", message.guild, message.author)` to simulate banning the person sending a message. Again, getting those things \(Guilds and Users\) is in the FAQ.

You can do all this in a "test" command, or you can do what I do: use `eval`. [Check the Eval command](../examples/making-an-eval-command.md) when you're ready to go that route.
126 changes: 126 additions & 0 deletions understanding/roles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Roles and Permissions

Roles are a powerful feature in Discord, and admittedly have been one of the hardest parts to master in discord.js. This walk through aims at explaining how roles and permissions work. We'll also explore how to use roles to protect your commands.

## Role hierarchy

Let's start with a basic overview of the hierarchy of roles in Discord.

... or actually not, they already explain it better than I here: [Role Management 101](https://support.discord.com/hc/en-us/articles/214836687-Role-Management-101). Read up on that, then come back here. I'll wait.

## Role code

Let's get down to brass tacks. You want to know how to use roles and permissions in your bot.

### Get Role by Name or ID

This is the "easy" part once you actually get used to it. It's just like getting any other Collection element, but here's a reminder anyway!

```js
// get role by ID
let myRole = message.guild.roles.cache.get("264410914592129025");

// get role by name
let myRole = message.guild.roles.cache.find(role => role.name === "Moderators");
```

To get the ID of a role, you can either mention it with a `\` before it, like `\@rolename`, or copy it from the role menu. If you mention it, the ID is the numbers between the `<>`. To get the ID of a role without mentioning it, enable developer mode in the Appearance section of your user settings, then go to the role menu in the server settings and right click on the role you want the ID of, then click "Copy ID".

### Check if a member has a role

In a `messageCreate` handler, you have access to checking the GuildMember class of the message author:

```js
// assuming role.id is an actual ID of a valid role:
if (message.member.roles.cache.has(role.id)) {
console.log("Yay, the author of the message has the role!");
}

else {
console.log("Nope, noppers, nadda.");
}
```

```js
// Check if they have one of many roles
if (message.member.roles.cache.some(r=>["Dev", "Mod", "Server Staff", "Proficient"].includes(r.name)) ) {
// has one of the roles
}

else {
// has none of the roles
}
```

To grab members and users in different ways see the [FAQ Page](../frequently-asked-questions.md).

### Get all members that have a role

```js
let roleID = "264410914592129025";
let membersWithRole = message.guild.roles.cache.get(roleID).members;
console.log(`Got ${membersWithRole.size} members with that role.`);
```

### Add a member to a role

Alright, now that you have roles, you probably want to add a member to a role. Simple enough! Discord.js provides 2 handy methods to add, and remove, a role. Let's look at them!

```js
let role = message.guild.roles.cache.find(r => r.name === "Team Mystic");

// Let's pretend you mentioned the user you want to add a role to (!addrole @user Role Name):
let member = message.mentions.members.first();

// or the person who made the command: let member = message.member;

// Add the role!
member.roles.add(role).catch(console.error);

// Remove a role!
member.roles.remove(role).catch(console.error);
```

Alright I feel like I have to add a _little_ precision here on implementation:

* You can **not** add or remove a role that is higher than the bot's. This should be obvious.
* The bot requires `MANAGE_ROLES` permissions for this. You can check for it using the code further down this page.
* Because of global rate limits, you cannot do 2 role "actions" immediately one after the other. The first action will work, the second will not. You can go around that by using `<GuildMember>.roles.set([array, of, roles])`. This will overwrite all existing roles and only apply the ones in the array so be careful with it.

## Permission code

### Check specific permission of a member on a channel

To check for a single permission override on a channel:

```js
// Getting all permissions for a member on a channel.
let perms = message.channel.permissionsFor(message.member);

// Checks for Manage Messages permissions.
let can_manage_messages = message.channel.permissionsFor(message.member).has("MANAGE_MESSAGES", false);

// View permissions as an object (useful for debugging or eval)
message.channel.permissionsFor(message.member).serialize(false)
```

We pass `false` for the checkAdmin parameter because Administrator channel overwrites don't implicitly grant any permissions, unlike in Roles or when you are the Guild Owner. \(The API will allow you to create an overwrite with Administrator, and even tell D.JS that a channel overwrite has had Administrator permissions set. Discord developers have stated this is [intended behavior](https://github.com/discord/discord-api-docs/issues/640).\)

### Get all permissions of a member on a guild

Just as easy, woah!

```js
let perms = message.member.permissions;

// Check if a member has a specific permission on the guild!
let has_kick = perms.has("KICK_MEMBERS");
```

ezpz, right?

Now get to coding!

## ADDENDUM: Permission Names

Click [here](https://discord.js.org/#/docs/main/master/class/Permissions?scrollTo=s-FLAGS) for the full list of internal permission names, used for `.has(name)` in the above examples
Loading

0 comments on commit 7a19b7c

Please sign in to comment.