> ## Documentation Index
> Fetch the complete documentation index at: https://www.chapterjs.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Events

> React to what happens on Discord with an event() per reaction: welcome new members, log changes, run code when the bot connects, and see how intents follow your files

Commands run when someone asks. Events run when something **happens**: a member joins, a role is deleted, a channel is renamed, the bot connects. This page makes your bot welcome newcomers, and on the way explains the one thing events need from the Developer Portal.

## A declaration per reaction

To react to an event, declare it with `event()` and export it: `name` is the event, and the rest is one reaction. The file is yours to place and to name; this tutorial keeps its events in `src/events/`. Create `src/events/hello.ts`:

```ts src/events/hello.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { event } from 'chapterjs';

export default event({
  name: 'memberJoin',
  async run({ member, guild }) {
    const channel = guild.systemChannelId
      ? guild.channels.get(guild.systemChannelId)
      : undefined;
    if (!channel?.isTextBased()) return;
    await channel.send(
      `Welcome to ${guild.name}, ${member}! You are member number ${guild.memberCount ?? '?'}.`
    );
  },
});
```

Compare with a command: an event has no description and no options to declare, only its `name` and `run`. And the function receives what the event is about: here a `member` (who joined) and a `guild` (where). Hover `member` in your editor: it is a `GuildMember`, with everything the previous pages showed. The same `import { event } from 'chapterjs'` line, with `name: 'roleDelete'`, would give your function a `role`. Every event works this way: what it is about, and the server, `guild`. Nothing else, so you never search for where something is.

`guild.systemChannelId` is the channel where Discord itself posts "X joined the server", when the server has one: a sensible default for a welcome. `guild.channels.get()` reads it from what the bot knows, and `isTextBased()` makes sure a message can go there.

## The first privileged intent

Save the file. The terminal does not say "Reloaded". It says:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
⚠ Your files need an option that is not enabled for your bot:
  - Server Members Intent, needed by src/events/hello.ts
  Enable it under "Privileged Gateway Intents", then save:
  https://discord.com/developers/applications/123456789012345678/bot
  Waiting for you to enable it...
```

This is the intents mechanism from [How a bot works](https://www.chapterjs.org/tutorial/how-a-bot-works.md), at work. The bot asked Discord for the servers (`GUILDS`) when it connected. Knowing about members joining is another intent, `GUILD_MEMBERS`, and that one is privileged: Discord wants you to enable it by hand, because the list of members of a server is personal data.

Open the link (it goes straight to the **Bot** page of your dev application), turn on **Server Members Intent**, click **Save Changes**. The terminal goes on by itself:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✓ Privileged intents enabled
↻ Your files now need more from Discord (GUILD_MEMBERS): reconnecting...
✓ Reconnected, 1 event, a presence loaded
```

Intents are given to Discord when the bot connects, so needing a new one means reconnecting. The framework did it. From now on, every time someone joins your test server, your file runs. Try it with a second account, or by leaving and joining again.

> [!NOTE]
>
> You never wrote an intent. The framework reads the events your files
> declare, works out what they need, asks Discord for exactly that, and tells
> you when one needs a switch in the Developer Portal. When you delete the
> declaration, the intent is not asked for any more. That is what "intents are computed from your files" means
> on the first line of every start.

## Several files for one event

Declare as many reactions to one event as you want, and **all of them run** when it happens, at the same time, in no particular order. Add `src/events/role.ts`, which gives newcomers a role:

```ts src/events/role.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { event } from 'chapterjs';

export default event({
  name: 'memberJoin',
  async run({ member, guild }) {
    const role = [...guild.roles.values()].find(role => role.name === 'Newcomer');
    if (!role) return;
    await member.addRole(role, 'Just joined');
  },
});
```

Create a role called "Newcomer" in your test server, and make sure the role of the bot is **above** it in the server settings: Discord only lets a bot give roles below its own highest role.

Why one declaration per thing, rather than one big function? Because one that fails never stops the others. If `role.ts` throws (the role was deleted, the bot's role is too low), `hello.ts` still welcomes. And each one stays short enough to read. When two steps must happen in order, keep them in the same function.

## The ready event

`ready` is special: it is not something that happens on Discord, but the moment your bot is connected and knows every server it is in. It runs **once per start**, never on a reload. Create `src/events/ready.ts`:

```ts src/events/ready.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { event } from 'chapterjs';

export default event({
  name: 'ready',
  run({ user, guilds }) {
    console.log(`${user.username} is online in ${guilds.size} server(s)`);
  },
});
```

Save it, then stop the bot and start it again: the terminal prints `buddy is online in 1 server(s)`, once. `user` is the bot itself, `guilds` the servers it is in. Use it for things that must happen once at startup. Do not use it for things that must happen regularly: that is what tasks are for, a few chapters from here.

## The events you can react to

| `name` | Happens when | Your function receives |
| - | - | - |
| `ready` | The bot is connected | `user`, `guilds` |
| `guildJoin`, `guildLeave` | The bot is added to a server, or removed | `guild` |
| `memberJoin`, `memberUpdate`, `memberLeave` | Someone joins, changes (nickname, roles...), leaves | `member`, `guild` (`user` for a leave) |
| `roleCreate`, `roleUpdate`, `roleDelete` | A role is created, changed, deleted | `role`, `guild` |
| `channelCreate`, `channelUpdate`, `channelDelete` | A channel is created, changed, deleted | `channel`, `guild` |
| `messageCreate`, `messageUpdate`, `messageDelete` | A message is sent, edited, deleted | `message`, `guild`: the next page |
| `reactionAdd`, `voiceJoin`, `banAdd`... | A reaction, someone in voice, a ban, and more | Two pages from here |

A `name` that is not an event is underlined in the editor, then reported when it loads, with the closest event name when there is one. The exact contents of each are in the [Reference](https://www.chapterjs.org/reference/events.md).

### What was deleted

For something that no longer exists, the event gives it to you **as it was**: `roleDelete` gives the role, `channelDelete` the channel, `guildLeave` the server. The bot remembered them all, so they are there. `memberLeave` gives the account of who left, `user`: a server can have more members than the bot remembers, so the member they were may be long forgotten, but the account is always there.

```ts src/events/bye.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { event } from 'chapterjs';

export default event({
  name: 'memberLeave',
  async run({ user, guild }) {
    const channel = guild.systemChannelId
      ? guild.channels.get(guild.systemChannelId)
      : undefined;
    if (!channel?.isTextBased()) return;
    const name = user.displayName;
    await channel.send(`${name} left. We are ${guild.memberCount ?? '?'} now.`);
  },
});
```

## Points of attention

* **Everything your function receives is read-only.** Assigning to it throws; it is shared by every reaction to the event.
* **What must exist is there.** `member.guild`, `channel.guild`, `guild.me`: no `?.`. What may really be missing is nullable, like `guild.memberCount`, which Discord does not always send.
* **In dev, only your test server.** `chapterjs dev` ignores what happens in other servers and in private messages. That is why you can run the dev bot and the production bot with one token without them answering twice; `chapterjs start` hears everything.
* **A failing file is reported with the file and the line**, and nothing else stops:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✗ src/events/role.ts:6 Discord refused PUT /guilds/{guild.id}/members/{user.id}/roles/{role.id}: Missing Permissions (50013). The bot is missing a permission for this, or acts on someone above it: the owner of the server, or a member or role higher than the bot's highest role.
```

## Recap

* `event({ name, run })` is one reaction to the event `name`; declare as many as you like, and all run.
* What `run` receives follows `name`, with no type to write.
* Intents are computed from the events you declare; a privileged one is a switch in the Developer Portal, and the terminal waits for you.
* `ready` runs once per start; deleted things come as they were.

> [card] [Messages as events](https://www.chapterjs.org/tutorial/message-events.md)
>
> React to what people write, where, and from whom.

> [card] [Guide: Events](https://www.chapterjs.org/events.md)
>
> The same, in short.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.