Combining gates
Combine gates with or() and and(), for example to let a command run when any one gate passes, or to group checks you reuse. Covers which handlers a combined gate fits, when an or moves on to its next arm, and what the user sees when every arm refuses.
@Gated requires every gate you list. A command that a bot owner or a moderator can run needs only one of those two checks to pass.
or combines gates into one that passes when any arm passes. and combines them into one that needs every arm. The result goes on a handler like any other gate, or inside another combinator. Both take two arms or more.
or, when any one of them is enough
or runs each arm in order and stops at the first one that passes. The rest never run.
import {
Gated,
OwnerOnly,
RequireRole,
SlashHandler,
SlashRoute,
or
} from '@seedcord/gateway';
@Gated(or(OwnerOnly(), RequireRole('221439658686939136')))
@SlashRoute('award')
export class Award extends SlashHandler<'award'> {
public async execute(): Promise<void> {
const member = this.options.getUser('member');
await this.reply(`Awarded ${member.username}.`);
}
}A bot owner passes the first arm and never reaches the role check. Anyone holding the role passes the second.
and, for a reusable pair
@Gated(A(), B()) and and(A(), B()) refuse under the same conditions. Use and when you want that pair as one value, usually to put it inside an or.
const staff = and(GuildOnly(), RequireRole('221439658686939136'));
const staffOrOwner = or(staff, OwnerOnly());staff runs GuildOnly before RequireRole, since arms run left to right. The first refusal stops the rest.
The combined gate carries the names of its arms, joined with & for an and and | for an or. A combinator nested inside another gets parentheses around its name, which is why staffOrOwner above reads (GuildOnly & RequireRole) | OwnerOnly.
const guildOwner = and(GuildOnly(), OwnerOnly());A compile error prints that name when the gate goes on a handler it doesn't fit.
What each one accepts
The arms decide which handlers the combined gate fits.
| required context | a handler fits when | |
|---|---|---|
and | the intersection of every arm | it satisfies all of them |
or | the union of every arm | it satisfies at least one |
An and of an event-only gate and an interaction-only gate doesn't fit any handler, since a handler can't provide both contexts. The compiler catches it at the @Gated line.
What counts as declining
An or arm declines only when it throws a Notice with report left at false. Anything else stops the whole or, and the user sees whatever that throw produces.
Setting report = true on your own Notice to get a refusal into your logs takes that arm out of the decline path.
The refusal when every arm refuses
or picks what to throw in three steps.
- The
failoption, when you passed one. - A list of what each arm needed, when every refusal carried a
summary. - A generic refusal reading "You are not allowed to use this command."
Every catalog gate that refuses with a Notice sets a summary, so an or built from those alone reaches step two. A notice you pass to a catalog gate replaces its default, summary included.
You need to meet at least one of these:
• be the bot owner
• hold the <@&221439658686939136> roleA gate of your own joins that list once its Notice sets summary. A single arm without a summary drops the whole list to the generic refusal, since a partial list would mislead by leaving an option out.
Wording it yourself
The summary list reads as a checklist in seedcord's wording. When the caller needs one sentence instead, like "Staff only" or a note on how to get the role, pass fail in a trailing options object after the arms. Pass a Notice for a fixed refusal.
or(OwnerOnly(), RequireRole('221439658686939136'), {
fail: new StaffOnly('caller is neither')
});Pass a function when the refusal needs to read the context. It receives the same ctx the arms did.
or(OwnerOnly(), RequireRole('221439658686939136'), {
fail: (ctx) => new StaffOnly(`${ctx.userId ?? 'nobody'} tried`)
});or throws fail first and never builds the summary list.
Two arms, at least
An or with one arm is that gate on its own, so the compiler rejects it.
or(OwnerOnly());The options object never counts as an arm, so or(A(), { fail }) fails the same way.