# Handling messages

Read what people type in chat, like a !ping prefix command, in a gateway event handler, and turn on the Message Content intent Discord requires.

Most bots start by reacting to what people type. With the `GuildMessages` intent, a `messageCreate` handler runs for every message in every server your bot is in. Reading what that message says is a separate permission Discord controls. If `@RegisterEvent` and `this.event` are new to you, start with [Your first event handler](https://next.seedcord.org/guide/events/).

```ts title="src/events/Echo.ts"
import { EventHandler, RegisterEvent } from '@seedcord/gateway';
import { Events } from 'discord.js';

@RegisterEvent([Events.MessageCreate])
export class Echo extends EventHandler<Events.MessageCreate> {
    public async execute(): Promise<void> {
        const [message] = this.event;
        const text = message.content;

        this.logger.info(`content: "${text}"`);
    }
}
```

Without the Message Content intent, `Echo` logs an empty string every time.

```txt output
content: ""
```

Nothing throws, so you only notice when a feature quietly never matches. The compiler can't warn you either, since `text` is typed `string` with or without the intent.

## What the intent gates

If your app doesn't have the intent, Discord empties five fields on every message object: `content`, `embeds`, `attachments`, `components`, and `poll`. Discord applies this to every API that returns a message, so the same fields come back empty wherever your bot reads one.

## The exceptions

Discord sends the real content in these five cases, whatever intents you set. If your bot only reads messages like these, it can skip the intent entirely. Discord's [privileged intent guide](https://docs.discord.com/developers/gateway/you-might-not-need-a-privileged-intent#exceptions-when-you-get-message-content-without-the-privileged-intent) covers them too.

{/* prettier-ignore-start */}

| case              | what it covers                                                                                               |
| ----------------- | ------------------------------------------------------------------------------------------------------------ |
| your own messages | anything your app sent. A message from a webhook your app created counts only if it mentions your app.       |
| direct messages   | a DM to your app                                                                                             |
| a mention         | a message that mentions your app as `<@id>`. Mentioning your app's role doesn't count.                       |
| a reply           | a reply to a regular message from your app, with ping on. A reply to a slash command response doesn't count. |
| a context menu    | the message a message context menu command ran on                                                            |

{/* prettier-ignore-end */}

Discord sends the full text of a message that mentions your bot, even without a privileged intent. Attach [`MentionPrefix`](https://seedcord.org/docs/packages/gateway/latest/variables/mention-prefix) to a handler that needs to read the message content to answer commands like `@bot ping`.

```ts title="src/events/Mentioned.ts"
@Gated(IgnoreBots, MentionPrefix)
@RegisterEvent([Events.MessageCreate])
export class Mentioned extends EventHandler<Events.MessageCreate> {
    public async execute(): Promise<void> {
        const [message] = this.event;
        await message.reply(`hey ${message.author.displayName}`);
    }
}
```

Your handler only runs for a message that starts with a mention of your bot. It ignores a reply that pings your bot and a message that mentions your bot later in the text. If an edit should run the command, register `messageUpdate` too and handle both events with [`this.match()`](https://next.seedcord.org/guide/events/several-events/). The gate then passes an edit only when the edit adds the mention.

If the message already started with the mention, editing it doesn't run the command again. With `Partials.Message` on, discord.js also emits an edit to a message it didn't cache. That edit runs the command again.

[`@Gated`](https://seedcord.org/docs/packages/gateway/latest/functions/gated) with [`IgnoreBots`](https://seedcord.org/docs/packages/gateway/latest/variables/ignore-bots) stops the handler on a message from any bot, including your own. Without it, `Mentioned` would answer another bot that mentions yours. Two bots like that can reply to each other forever. The [gate catalog](https://next.seedcord.org/guide/checks/gates/) lists the other checks.

## Turning the intent on

When your bot does need every message's text, like an auto-moderator scanning for banned words, you turn the intent on in two places. Turn on **Message Content** in the developer portal under your application's **Bot** tab, then add the intent to `clientOptions`. If you flip only the portal switch, the text stays empty. If you add only the client intent, Discord closes the connection with close code 4014, "Disallowed intent(s)".

```ts title="src/bot.ts"
export const seedcord = new Seedcord({
    bot: {
        ...rest,
        clientOptions: {
            intents: [
                GatewayIntentBits.Guilds,
                GatewayIntentBits.GuildMessages,
                GatewayIntentBits.MessageContent
            ]
        },
        events: { path: resolve(import.meta.dirname, './events') }
    },
    subscribers: { path: null }
});
```

`GuildMessages` delivers the event. `MessageContent` fills in the five fields.

## Prefix commands and the intent review

Message Content is a privileged intent. Once more than 10,000 unique users can see your app, Discord requires you to apply for it. You reapply every year to keep it. When slash commands could do the same job, Discord can deny the request.

Don't build commands on a prefix like `!help` or `?play`. Make them slash commands. Discord's page on avoiding a privileged intent opens its checklist with this question: "Is my bot using prefix commands (`!help`, `?play`) that could be migrated to slash commands?"

A slash command runs without a privileged intent. It also gives you typed options and a place in Discord's command picker. [Commands](https://next.seedcord.org/guide/commands/) covers slash commands. A message context menu command is the other route, since it receives the full message, content included.
