Skip to main content
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:
src/events/hello.ts
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:
This is the intents mechanism from How a bot works, 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:
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.
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:
src/events/role.ts
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:
src/events/ready.ts
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

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.

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.
src/events/bye.ts

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:

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.

Messages as events

React to what people write, where, and from whom.

Guide: Events

The same, in short.