Skip to main content
Three events are about messages: messageCreate, messageUpdate and messageDelete. They are the most frequent events a bot receives, and the ones where a careless bot does the most damage: answering itself forever, or pinging everyone. This page writes a small reaction, and shows the safety nets the framework puts under it.

Reacting to a word

Create src/events/thanks.ts:
src/events/thanks.ts
Save it. Like the members intent, this needs a switch:
Reading what people write is the most personal thing a bot can do, so Discord makes it a privileged intent: Message Content Intent. Without it, a bot still receives the messages, but message.content is empty for everyone but itself. Enable it, save, and the bot reconnects. Then write “thanks” in your test server: the bot reacts.
Why did /dice not need this? Because a slash command is sent to the bot by Discord as a command, with its options, and not as a message. That is one more reason to prefer slash commands for anything that is an order to the bot, and keep message events for reactions to what people say.

Safe by default

Look at what the file does not do. It does not check if (message.author.bot) return;. It does not check that the message is in a server. Both are the default:
  • Messages of bots and webhooks are ignored, yours included. A bot that reacts to messages and answers with a message will see its own answer, react to it, and so on forever; a server with two such bots has them talking to each other all night. So by default your file only runs for messages written by people. When you really want the messages of bots, say it: bots: true, written next to name.
  • Only the messages of servers. Private messages are a different place, with no server, no member and no channel of a server; a file that handles both has to tell them apart. So by default your file listens in servers, where message.guild, message.channel and message.member are always there.
The safe behavior is the default, and the opt-out is explicit. Here is how the options written next to name change it, and what they change in what your function receives: The types follow the options: in a 'dm' file, the editor does not even offer message.guild; in a 'both' file, one if (message.guild) narrows everything inside it to a message of a server. Each file decides for itself: two files of the same folder can listen in different places.
chapterjs dev only runs your bot in your test server, so a file with where: 'dm' can’t be tried there, and the terminal tells you once. Private messages reach your bot with chapterjs start.

Answering a message

message.reply() answers in the channel, with the original shown above the answer. Here is a file that answers a question about the rules:
src/events/rules.ts
message.guild is used without a check: this file listens in servers only, so it is always there. allowedMentions: { repliedUser: false } answers without pinging the author: a bot that pings people every time it helps gets muted fast.

Deleted and edited messages

messageUpdate gives the message after the edit. messageDelete is different: the message no longer exists, so its message is what is left of it: its id, the channel it was in, and before, the message itself, only when the bot remembered it, which it does not by default (messages are what a bot receives the most, and most bots never read an old one). Always use message.id:
src/events/audit.ts
messageDelete does not need the Message Content Intent.

Points of attention

  • Never answer a message with a message without thinking about loops. With the default (no bots), you are safe. With bots: true, make sure your file can’t react to its own answer.
  • A reaction to a word runs on every message of every channel. Keep the check cheap and first, and return early.
  • Private messages are for production. A support bot that listens in private messages is tried with chapterjs start, on the dev application if you like.
  • A thread the bot never saw. A message can arrive in a channel the bot does not know yet (a thread created before it connected). The framework asks Discord for it once, before running your files, so message.channel is always there. When Discord refuses (the bot can’t see that channel), the event is not delivered and the terminal says so with ⚠.

Recap

  • A messageCreate event runs for messages written by people, in servers, by default: no author.bot check to write.
  • The Message Content Intent is needed to read message.content for messageCreate and messageUpdate.
  • bots: true and where: 'dm' or where: 'both' widen what a file hears, and the types follow.
  • messageDelete gives what is left of the message (id, channel) and the server; message.before is null.

Reactions and voice

React to reactions, and to who joins a voice channel.

Guide: Messages

The three events and their options, in short.