# Permissions in a handler

Check permissions inside a handler with assertPermissions() and the gateway helpers, for a member, a role, or your own bot.

A permission gate checks the caller and your bot before `execute()` starts. `/ban @someone` needs the target checked too. You get the target from the options inside `execute()`, after every gate has run. By hand, that check means comparing bitfields and writing a refusal message that lists what's missing.

The helpers on this page do that comparison inside your handler and throw the same refusal card a gate would. [`assertPermissions`](https://seedcord.org/docs/packages/core/latest/functions/assert-permissions) runs on both transports. [`checkPermissions`](https://seedcord.org/docs/packages/gateway/latest/functions/check-permissions) and [`checkBotPermissions`](https://seedcord.org/docs/packages/gateway/latest/functions/check-bot-permissions) take discord.js objects, so they are gateway only.

## Asserting a bitfield

`assertPermissions` takes one object. It returns when the subject holds every bit in `scope`. Otherwise it throws a [`Notice`](https://seedcord.org/docs/packages/core/latest/classes/notice) that lists what's missing.

{/* prettier-ignore-start */}

| field         | type                     | holds                                                                                                                    |
| ------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `subject`     | `string \| null`         | who is checked, printed at the start of the refusal. `null` addresses the caller as "You"                                |
| `permissions` | `bigint`                 | the permission bits the subject holds, which you compute                                                                 |
| `scope`       | `readonly bigint[]`      | the `PermissionFlagsBits` values to check                                                                                |
| `inverse`     | `boolean`, optional      | refuses when the subject holds a bit in `scope`, see [refusing a permission they hold](#refusing-a-permission-they-hold) |
| `notice`      | `Notice` class, optional | replaces the refusal, see [replacing the refusal card](#replacing-the-refusal-card)                                      |

{/* prettier-ignore-end */}

You compute `permissions` yourself, so `assertPermissions` works on either transport. Here `/maintenance` checks that the person it is about to notify can see the channel.

```ts title="src/handlers/Maintenance.ts"
import {
    assertPermissions,
    SlashHandler,
    SlashRoute
} from '@seedcord/gateway';
import { PermissionFlagsBits } from 'discord.js';

@SlashRoute('maintenance')
export class Maintenance extends SlashHandler<'maintenance'> {
    public async execute(): Promise<void> {
        const notify = this.options.getMember('notify');
        const channel = this.options.getChannel('target');

        if (notify === null) {
            await this.reply('That user is not in this server.');
            return;
        }

        assertPermissions({
            subject: `<@${notify.id}>`,
            permissions: channel.permissionsFor(notify, true)
                .bitfield,
            scope: [PermissionFlagsBits.ViewChannel]
        });

        await this.reply('Notified.');
    }
}
```

If the notified member can't see the channel, the default refusal card starts with `subject` and lists what's missing.

```txt output
### Cannot Proceed
<@123456789> is missing the following permission entries:

• View Channel
```

`subject` goes into the card exactly as you pass it. To show a mention there, pass `<@id>` for a member or `<@&id>` for a role.

`getMember` can return `null` even for a required user option. It reads the member from the interaction Discord sends. Discord leaves the member out in two cases.

* The command ran in a direct message.
* The chosen user isn't a member of the server.

> **Gateway and http differ**
>
> `getMember` returns a different shape on each transport, so you read the bits differently.
>
> {/* prettier-ignore-start */}
>
> | transport | `getMember` returns                     | the bits                      |
> | --------- | --------------------------------------- | ----------------------------- |
> | gateway   | discord.js `GuildMember`                | `member.permissions.bitfield` |
> | http      | `APIInteractionDataResolvedGuildMember` | `BigInt(member.permissions)`  |
>
> {/* prettier-ignore-end */}
>
> Both shapes carry the target's permissions, so `assertPermissions` works on either without a REST call. On http, Discord computes those bits for the channel the command ran in.

## Refusing a permission they hold

`inverse: true` flips the check. It throws when the subject holds any bit in `scope`, and returns otherwise.

Use it when the target must not hold a permission. `banMember` checks the target before it bans them.

```ts
async function banMember(target: GuildMember): Promise<void> {
    assertPermissions({
        subject: `<@${target.id}>`,
        permissions: target.permissions.bitfield,
        scope: [PermissionFlagsBits.Administrator],
        inverse: true
    });

    await target.ban();
}
```

If the target holds Administrator, `assertPermissions` throws before `target.ban()` runs.

> **Warning**
>
> The Administrator rule from [permissions](https://next.seedcord.org/guide/checks/permissions/) applies here too. The inverse check reports every bit in `scope` as present for anyone holding Administrator. Put only the bits you mean in `scope`, and read the refusal as "holds at least one of these".

## Replacing the refusal card

The default card says which permissions are missing. When your command needs its own wording, like "You can't ban a moderator", pass a `Notice` class you wrote as `notice`. It replaces the refusal with or without `inverse: true`. seedcord constructs it with `subject` and the permission names: the missing ones, or the held ones under `inverse: true`.

[Throwing](https://next.seedcord.org/guide/replying/throwing/) shows how to write one.

```ts
assertPermissions({
    subject: `<@${target.id}>`,
    permissions: target.permissions.bitfield,
    scope: [PermissionFlagsBits.BanMembers],
    notice: CannotBan
});
```

## Checking a role or member

On gateway you already hold a discord.js member or role, so you can skip reading its bitfield and writing its mention. [`checkPermissions`](https://seedcord.org/docs/packages/gateway/latest/functions/check-permissions) takes the member or role as `for`, computes the bitfield, builds `subject` as `<@id>` or `<@&id>`, and calls `assertPermissions` with both. Its other fields match `assertPermissions`.

Leave `in` out for a server-wide permission like Kick Members, and `checkPermissions` reads the base set from the target's roles. Pass a channel or thread as `in` for a permission its overwrites can change, like Send Messages.

```ts
checkPermissions({
    for: member,
    scope: [PermissionFlagsBits.KickMembers]
});

checkPermissions({
    for: member,
    in: channel,
    scope: [PermissionFlagsBits.SendMessages]
});

checkPermissions({
    for: member,
    scope: [PermissionFlagsBits.Administrator],
    inverse: true
});
```

## Checking your own bot

A gate checks your bot's permissions in the channel where the command ran. If your handler posts in a different channel, check that channel. [`checkBotPermissions`](https://seedcord.org/docs/packages/gateway/latest/functions/check-bot-permissions) runs the same check on your bot's member. It doesn't take `for`, and its `in` is required: the guild for your bot's base set, or a channel or thread.

```ts
checkBotPermissions({
    in: channel,
    scope: [
        PermissionFlagsBits.SendMessages,
        PermissionFlagsBits.ViewChannel
    ]
});
```

If your bot's own member isn't in the member cache, `checkBotPermissions` throws `CoreBotMemberMissing`. The error says how to keep it there.

A passing check for Manage Roles still leaves role position to check. Discord rejects a role your bot assigns if that role is at or above the bot's highest role. [Changing roles](https://next.seedcord.org/guide/checks/changing-roles/) covers that check.
