Collectors
Now that you’ve learned how to handle components statically, you might have wondered how to get more context about what happened before sending the component.
Seyfert includes message component collectors
, which are an easy way to handle interactions received from a specific message and allow you to get more context about what happened before sending the component.
Building Collectors
Collectors are built using the createComponentCollector
method on a message, which is inherited by BaseMessage
. This method returns an object representing a collector.
Here’s an example of how to build a simple collector after sending a message with an attached button in a “hello world” command.
import { class Button
Represents a button component.
Button, class ActionRow<T extends BuilderComponents>
Represents an Action Row component in a message.
ActionRow, class Command
Command, function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare, type class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext,} from 'seyfert';
import { enum ButtonStyle
ButtonStyle } from 'seyfert/lib/types';
@function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare({ name: string
name: 'hello', description: string
description: 'I will send you a hello world message',})export default class class HelloWorldCommand
HelloWorldCommand extends class Command
Command { async HelloWorldCommand.run(ctx: CommandContext): Promise<void>
run(ctx: CommandContext<{}, never>
ctx: class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext) { const const button: Button
button = new new Button(data?: Partial<APIButtonComponent>): Button
Creates a new Button instance.
Button() .Button.setCustomId(id: string): Button
Sets the custom ID for the button.
setCustomId('hello') .Button.setLabel(label: string): Button
Sets the label for the button.
setLabel('Hello') .Button.setStyle(style: ButtonStyle): Button
setStyle(enum ButtonStyle
ButtonStyle.function (enum member) ButtonStyle.Primary = 1
Primary);
const const row: ActionRow<Button>
row = new new ActionRow<Button>({ components, ...data }?: Partial<APIActionRowComponent<APIActionRowComponentTypes>>): ActionRow<...>
Creates a new instance of the ActionRow class.
ActionRow<class Button
Represents a button component.
Button>().ActionRow<Button>.setComponents(component: (ButtonLink | ButtonID)[]): ActionRow<Button>
Sets the components of the Action Row.
setComponents([const button: Button
button]);
// To get the message with the attached button, you can set the fetchReply to "true". const const message: WebhookMessage
message = await ctx: CommandContext<{}, never>
ctx.CommandContext<{}, never>.write<true>(body: InteractionCreateBodyRequest, withResponse?: true | undefined): Promise<WebhookMessage>
write( { content?: string | undefined
The message contents (up to 2000 characters)
content: 'Do you want a hello world? Click the button below.', ResolverProps.components?: APIActionRowComponent<APIMessageActionRowComponent>[] | ActionRow<BuilderComponents>[] | undefined
components: [const row: ActionRow<Button>
row], }, true );
const const collector: CreateComponentCollectorResult
collector = const message: WebhookMessage
message.BaseMessage.createComponentCollector(options?: ListenerOptions): CreateComponentCollectorResult
createComponentCollector(); }}
Handling Interactions Within a Collector
Once the collector is created from a message, we’ll handle the button interaction with the collector’s run
function.
Here’s an example:
import { class Button
Represents a button component.
Button, class ActionRow<T extends BuilderComponents>
Represents an Action Row component in a message.
ActionRow, class Command
Command, function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare, type class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext,} from 'seyfert';
import { enum ButtonStyle
ButtonStyle } from 'seyfert/lib/types';
@function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare({ name: string
name: 'hello', description: string
description: 'I will send you a hello world message',})export default class class HelloWorldCommand
HelloWorldCommand extends class Command
Command { async HelloWorldCommand.run(ctx: CommandContext): Promise<void>
run(ctx: CommandContext<{}, never>
ctx: class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext) { const const button: Button
button = new new Button(data?: Partial<APIButtonComponent>): Button
Creates a new Button instance.
Button() .Button.setCustomId(id: string): Button
Sets the custom ID for the button.
setCustomId('hello') .Button.setLabel(label: string): Button
Sets the label for the button.
setLabel('Hello') .Button.setStyle(style: ButtonStyle): Button
setStyle(enum ButtonStyle
ButtonStyle.function (enum member) ButtonStyle.Primary = 1
Primary);
const const row: ActionRow<Button>
row = new new ActionRow<Button>({ components, ...data }?: Partial<APIActionRowComponent<APIActionRowComponentTypes>>): ActionRow<...>
Creates a new instance of the ActionRow class.
ActionRow<class Button
Represents a button component.
Button>().ActionRow<Button>.setComponents(component: (ButtonLink | ButtonID)[]): ActionRow<Button>
Sets the components of the Action Row.
setComponents([const button: Button
button]);
const const message: WebhookMessage
message = await ctx: CommandContext<{}, never>
ctx.CommandContext<{}, never>.write<true>(body: InteractionCreateBodyRequest, withResponse?: true | undefined): Promise<WebhookMessage>
write( { content?: string | undefined
The message contents (up to 2000 characters)
content: 'Do you want a hello world? Click the button below.', ResolverProps.components?: APIActionRowComponent<APIMessageActionRowComponent>[] | ActionRow<BuilderComponents>[] | undefined
components: [const row: ActionRow<Button>
row], }, true );
const const collector: CreateComponentCollectorResult
collector = const message: WebhookMessage
message.BaseMessage.createComponentCollector(options?: ListenerOptions): CreateComponentCollectorResult
createComponentCollector();
// we are passing the custom ID set on the button as the first parameter of the function. const collector: CreateComponentCollectorResult
collector.CreateComponentCollectorResult.run<CollectorInteraction>(customId: UserMatches, callback: ComponentCallback<CollectorInteraction>): any
run('hello', async (i: CollectorInteraction
i) => { if (i: CollectorInteraction
i.BaseInteraction<FromGuild extends boolean = boolean, Type extends APIInteraction = APIInteraction>.isButton(): this is ButtonInteraction
isButton()) return i: ButtonInteraction
i.Interaction<boolean, APIMessageComponentInteraction>.write<false>(body: InteractionCreateBodyRequest, withResponse?: false | undefined): Promise<void>
write({ content?: string | undefined
The message contents (up to 2000 characters)
content: 'Hello World 👋' }); }); }}
Filtering Interactions
You might have considered filtering the received interaction in the run function to limit, for example, to the user who is interacting with the button.
You would add a condition inside the run function like this:
if (i.user.id === ctx.author.id) return i.write({ content: 'Do not touch the button' });
This would limit the button’s usage only to the user who executed the command.
However, Seyfert simply implements a filter
option when creating the collector, which expects a return function that returns a boolean.
Let’s implement the filter to restrict the user who executed the interaction and filter the interaction to only button interactions.
import { class Button
Represents a button component.
Button, class ActionRow<T extends BuilderComponents>
Represents an Action Row component in a message.
ActionRow, class Command
Command, function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare, type class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext,} from 'seyfert';
import { enum ButtonStyle
ButtonStyle } from 'seyfert/lib/types';
@function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare({ name: string
name: 'hello', description: string
description: 'I will send you a hello world message',})export default class class HelloWorldCommand
HelloWorldCommand extends class Command
Command { async HelloWorldCommand.run(ctx: CommandContext): Promise<void>
run(ctx: CommandContext<{}, never>
ctx: class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext) { const const button: Button
button = new new Button(data?: Partial<APIButtonComponent>): Button
Creates a new Button instance.
Button() .Button.setCustomId(id: string): Button
Sets the custom ID for the button.
setCustomId('hello') .Button.setLabel(label: string): Button
Sets the label for the button.
setLabel('Hello') .Button.setStyle(style: ButtonStyle): Button
setStyle(enum ButtonStyle
ButtonStyle.function (enum member) ButtonStyle.Primary = 1
Primary);
const const row: ActionRow<Button>
row = new new ActionRow<Button>({ components, ...data }?: Partial<APIActionRowComponent<APIActionRowComponentTypes>>): ActionRow<...>
Creates a new instance of the ActionRow class.
ActionRow<class Button
Represents a button component.
Button>().ActionRow<Button>.setComponents(component: (ButtonLink | ButtonID)[]): ActionRow<Button>
Sets the components of the Action Row.
setComponents([const button: Button
button]);
const const message: WebhookMessage
message = await ctx: CommandContext<{}, never>
ctx.CommandContext<{}, never>.write<true>(body: InteractionCreateBodyRequest, withResponse?: true | undefined): Promise<WebhookMessage>
write( { content?: string | undefined
The message contents (up to 2000 characters)
content: 'Do you want a hello world? Click the button below.', ResolverProps.components?: APIActionRowComponent<APIMessageActionRowComponent>[] | ActionRow<BuilderComponents>[] | undefined
components: [const row: ActionRow<Button>
row], }, true );
const const collector: CreateComponentCollectorResult
collector = const message: WebhookMessage
message.BaseMessage.createComponentCollector(options?: ListenerOptions): CreateComponentCollectorResult
createComponentCollector({ ListenerOptions.filter?: ComponentFilterCallback<ComponentInteraction<boolean, APIMessageComponentInteraction>>
filter: (i: ComponentInteraction<boolean, APIMessageComponentInteraction>
i) => i: ComponentInteraction<boolean, APIMessageComponentInteraction>
i.BaseInteraction<boolean, APIMessageComponentInteraction>.user: User
user.DiscordBase<APIUser>.id: string
id === ctx: CommandContext<{}, never>
ctx.CommandContext<{}, never>.author: User
author.DiscordBase<APIUser>.id: string
id && i: ComponentInteraction<boolean, APIMessageComponentInteraction>
i.BaseInteraction<boolean, APIMessageComponentInteraction>.isButton(): this is ButtonInteraction
isButton(), });
const collector: CreateComponentCollectorResult
collector.CreateComponentCollectorResult.run<CollectorInteraction>(customId: UserMatches, callback: ComponentCallback<CollectorInteraction>): any
run('hello', async (i: CollectorInteraction
i) => { return i: CollectorInteraction
i.Interaction<FromGuild extends boolean = boolean, Type extends APIInteraction = APIInteraction>.write<false>(body: InteractionCreateBodyRequest, withResponse?: false | undefined): Promise<void>
write({ content?: string | undefined
The message contents (up to 2000 characters)
content: 'Hello World 👋' }); }); }}
Handling the Collector’s onStop Event
A collector may stop, meaning it will no longer collect interactions from the message. To handle this stop, we need to pass a return function to the onStop
option when creating the collector.
The return function will take two parameters:
-
reason
. A string indicating the reason why the collector stopped. The most common aretimeout
oridle
if we’ve added a timeout or idle property to our collector. You can set the reason when manually stopping the collector within thecollector.stop()
function. -
refresh
. A function that you can run to refresh the collector, causing it to resume collecting interactions as it did before.
Here’s an example of how we add an idle timeout of 1000ms to the collector and refresh it every time it enters the onStop
return function.
import { class Button
Represents a button component.
Button, class ActionRow<T extends BuilderComponents>
Represents an Action Row component in a message.
ActionRow, class Command
Command, function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare, type class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext,} from 'seyfert';
import { enum ButtonStyle
ButtonStyle } from 'seyfert/lib/types';
@function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare({ name: string
name: 'hello', description: string
description: 'I will send you a hello world message',})export default class class HelloWorldCommand
HelloWorldCommand extends class Command
Command { async HelloWorldCommand.run(ctx: CommandContext): Promise<void>
run(ctx: CommandContext<{}, never>
ctx: class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext) { const const button: Button
button = new new Button(data?: Partial<APIButtonComponent>): Button
Creates a new Button instance.
Button() .Button.setCustomId(id: string): Button
Sets the custom ID for the button.
setCustomId('hello') .Button.setLabel(label: string): Button
Sets the label for the button.
setLabel('Hello') .Button.setStyle(style: ButtonStyle): Button
setStyle(enum ButtonStyle
ButtonStyle.function (enum member) ButtonStyle.Primary = 1
Primary);
const const row: ActionRow<Button>
row = new new ActionRow<Button>({ components, ...data }?: Partial<APIActionRowComponent<APIActionRowComponentTypes>>): ActionRow<...>
Creates a new instance of the ActionRow class.
ActionRow<class Button
Represents a button component.
Button>().ActionRow<Button>.setComponents(component: (ButtonLink | ButtonID)[]): ActionRow<Button>
Sets the components of the Action Row.
setComponents([const button: Button
button]);
const const message: WebhookMessage
message = await ctx: CommandContext<{}, never>
ctx.CommandContext<{}, never>.write<true>(body: InteractionCreateBodyRequest, withResponse?: true | undefined): Promise<WebhookMessage>
write( { content?: string | undefined
The message contents (up to 2000 characters)
content: 'Do you want a hello world? Click the button below.', ResolverProps.components?: APIActionRowComponent<APIMessageActionRowComponent>[] | ActionRow<BuilderComponents>[] | undefined
components: [const row: ActionRow<Button>
row], }, true );
const const collector: CreateComponentCollectorResult
collector = const message: WebhookMessage
message.BaseMessage.createComponentCollector(options?: ListenerOptions): CreateComponentCollectorResult
createComponentCollector({ ListenerOptions.filter?: ComponentFilterCallback<ComponentInteraction<boolean, APIMessageComponentInteraction>>
filter: (i: ComponentInteraction<boolean, APIMessageComponentInteraction>
i) => i: ComponentInteraction<boolean, APIMessageComponentInteraction>
i.BaseInteraction<boolean, APIMessageComponentInteraction>.user: User
user.DiscordBase<APIUser>.id: string
id === ctx: CommandContext<{}, never>
ctx.CommandContext<{}, never>.author: User
author.DiscordBase<APIUser>.id: string
id && i: ComponentInteraction<boolean, APIMessageComponentInteraction>
i.BaseInteraction<boolean, APIMessageComponentInteraction>.isButton(): this is ButtonInteraction
isButton(), ListenerOptions.onStop?: ComponentStopCallback
onStop(reason: "idle" | "messageDelete" | "channelDelete" | "guildDelete" | "timeout" | (string & {}) | undefined
reason, refresh: ComponentRefreshCallback
refresh) { //this will refresh the collector everytime it stops by timeout if (reason: "idle" | "messageDelete" | "channelDelete" | "guildDelete" | "timeout" | (string & {}) | undefined
reason === 'idle') return refresh: () => any
refresh(); }, ListenerOptions.idle?: number
idle: 1e3, //1000ms });
const collector: CreateComponentCollectorResult
collector.CreateComponentCollectorResult.run<CollectorInteraction>(customId: UserMatches, callback: ComponentCallback<CollectorInteraction>): any
run('hello', async (i: CollectorInteraction
i) => { return i: CollectorInteraction
i.Interaction<FromGuild extends boolean = boolean, Type extends APIInteraction = APIInteraction>.write<false>(body: InteractionCreateBodyRequest, withResponse?: false | undefined): Promise<void>
write({ content?: string | undefined
The message contents (up to 2000 characters)
content: 'Hello World 👋' }); }); }}
Handling Modals with Collectors
Since modals are not message components, it’s not possible to create a message component collector
, but Seyfert introduces the ability to create it using the run
method within the modal constructor, which expects a return function that will handle the interactions.
Here’s an example using run
within the modal constructor:
import { class Modal<T extends ModalBuilderComponents = TextInput>
Represents a modal for user interactions.
Modal, class Command
Command, function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare, type class ModalSubmitInteraction<FromGuild extends boolean = boolean>interface ModalSubmitInteraction<FromGuild extends boolean = boolean>
ModalSubmitInteraction, type class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext,} from 'seyfert';
@function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare({ name: string
name: 'hello', description: string
description: 'I will send you a hello world message',})export default class class HelloWorldCommand
HelloWorldCommand extends class Command
Command { async HelloWorldCommand.run(ctx: CommandContext): Promise<void>
run(ctx: CommandContext<{}, never>
ctx: class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext) { const const modal: Modal<TextInput>
modal = new new Modal<TextInput>(data?: Partial<APIModalInteractionResponseCallbackData>): Modal<TextInput>
Creates a new Modal instance.
Modal() .Modal<TextInput>.setCustomId(id: string): Modal<TextInput>
Sets the custom ID of the modal.
setCustomId('hello') .Modal<TextInput>.setTitle(title: string): Modal<TextInput>
Sets the title of the modal.
setTitle('Hello') .Modal<TextInput>.run(func: ModalSubmitCallback): Modal<TextInput>
Sets the callback function to be executed when the modal is submitted.
run(this.HelloWorldCommand.handleModal(i: ModalSubmitInteraction): Promise<void>
handleModal);
await ctx: CommandContext<{}, never>
ctx.CommandContext<{}, never>.interaction: ChatInputCommandInteraction<boolean>
interaction.Interaction<boolean, APIChatInputApplicationCommandInteraction>.modal(body: ModalCreateBodyRequest): Promise<undefined>
modal(const modal: Modal<TextInput>
modal); }
async HelloWorldCommand.handleModal(i: ModalSubmitInteraction): Promise<void>
handleModal(i: ModalSubmitInteraction<boolean>
i: class ModalSubmitInteraction<FromGuild extends boolean = boolean>interface ModalSubmitInteraction<FromGuild extends boolean = boolean>
ModalSubmitInteraction) { return i: ModalSubmitInteraction<boolean>
i.write<false>(body: InteractionCreateBodyRequest, withResponse?: false | undefined): Promise<void>
write({ content?: string | undefined
The message contents (up to 2000 characters)
content: 'Hello World 👋' }); }}