Comprehensive event system for Discord gateway events and collectors for gathering user interactions, messages, and reactions with filtering and time-based collection.
Core events emitted by the Discord client for real-time Discord activity.
/**
* Events emitted by the Discord client
*/
interface ClientEvents {
/** Emitted when the client becomes ready to start working */
ready: [client: Client<true>];
/** Emitted when a message is created */
messageCreate: [message: Message];
/** Emitted when a message is updated */
messageUpdate: [oldMessage: Message | PartialMessage, newMessage: Message | PartialMessage];
/** Emitted when a message is deleted */
messageDelete: [message: Message | PartialMessage];
/** Emitted when messages are bulk deleted */
messageDeleteBulk: [messages: Collection<Snowflake, Message | PartialMessage>, channel: GuildTextBasedChannel];
/** Emitted when a reaction is added to a message */
messageReactionAdd: [reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser];
/** Emitted when a reaction is removed from a message */
messageReactionRemove: [reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser];
/** Emitted when all reactions are removed from a message */
messageReactionRemoveAll: [message: Message | PartialMessage, reactions: Collection<string | Snowflake, MessageReaction>];
/** Emitted when reactions of a specific emoji are removed from a message */
messageReactionRemoveEmoji: [reaction: MessageReaction | PartialMessageReaction];
/** Emitted when an interaction is created */
interactionCreate: [interaction: Interaction];
/** Emitted when a guild becomes available */
guildCreate: [guild: Guild];
/** Emitted when a guild is updated */
guildUpdate: [oldGuild: Guild, newGuild: Guild];
/** Emitted when a guild becomes unavailable or the client leaves */
guildDelete: [guild: Guild];
/** Emitted when a user joins a guild */
guildMemberAdd: [member: GuildMember];
/** Emitted when a guild member is updated */
guildMemberUpdate: [oldMember: GuildMember | PartialGuildMember, newMember: GuildMember];
/** Emitted when a user leaves a guild */
guildMemberRemove: [member: GuildMember | PartialGuildMember];
/** Emitted when a guild role is created */
roleCreate: [role: Role];
/** Emitted when a guild role is updated */
roleUpdate: [oldRole: Role, newRole: Role];
/** Emitted when a guild role is deleted */
roleDelete: [role: Role];
/** Emitted when a channel is created */
channelCreate: [channel: NonThreadGuildBasedChannel];
/** Emitted when a channel is updated */
channelUpdate: [oldChannel: DMChannel | NonThreadGuildBasedChannel, newChannel: DMChannel | NonThreadGuildBasedChannel];
/** Emitted when a channel is deleted */
channelDelete: [channel: DMChannel | NonThreadGuildBasedChannel];
/** Emitted when a thread is created */
threadCreate: [thread: ThreadChannel, newlyCreated: boolean];
/** Emitted when a thread is updated */
threadUpdate: [oldThread: ThreadChannel, newThread: ThreadChannel];
/** Emitted when a thread is deleted */
threadDelete: [thread: ThreadChannel];
/** Emitted when a user starts typing */
typingStart: [typing: Typing];
/** Emitted when a user's presence is updated */
presenceUpdate: [oldPresence: Presence | null, newPresence: Presence];
/** Emitted when a voice state is updated */
voiceStateUpdate: [oldState: VoiceState, newState: VoiceState];
/** Emitted when an invite is created */
inviteCreate: [invite: Invite];
/** Emitted when an invite is deleted */
inviteDelete: [invite: Invite];
/** Emitted when a guild ban is added */
guildBanAdd: [ban: GuildBan];
/** Emitted when a guild ban is removed */
guildBanRemove: [ban: GuildBan];
/** Emitted when an emoji is created */
emojiCreate: [emoji: GuildEmoji];
/** Emitted when an emoji is updated */
emojiUpdate: [oldEmoji: GuildEmoji, newEmoji: GuildEmoji];
/** Emitted when an emoji is deleted */
emojiDelete: [emoji: GuildEmoji];
/** Emitted when a sticker is created */
stickerCreate: [sticker: Sticker];
/** Emitted when a sticker is updated */
stickerUpdate: [oldSticker: Sticker, newSticker: Sticker];
/** Emitted when a sticker is deleted */
stickerDelete: [sticker: Sticker];
/** Emitted when a scheduled event is created */
guildScheduledEventCreate: [guildScheduledEvent: GuildScheduledEvent];
/** Emitted when a scheduled event is updated */
guildScheduledEventUpdate: [oldGuildScheduledEvent: GuildScheduledEvent | null, newGuildScheduledEvent: GuildScheduledEvent];
/** Emitted when a scheduled event is deleted */
guildScheduledEventDelete: [guildScheduledEvent: GuildScheduledEvent];
/** Emitted when a user subscribes to a scheduled event */
guildScheduledEventUserAdd: [guildScheduledEvent: GuildScheduledEvent, user: User];
/** Emitted when a user unsubscribes from a scheduled event */
guildScheduledEventUserRemove: [guildScheduledEvent: GuildScheduledEvent, user: User];
/** Emitted for debug information */
debug: [message: string];
/** Emitted for warnings */
warn: [message: string];
/** Emitted when an error occurs */
error: [error: Error];
}
/**
* Event name constants
*/
const Events: {
Ready: 'ready';
MessageCreate: 'messageCreate';
MessageUpdate: 'messageUpdate';
MessageDelete: 'messageDelete';
MessageDeleteBulk: 'messageDeleteBulk';
MessageReactionAdd: 'messageReactionAdd';
MessageReactionRemove: 'messageReactionRemove';
MessageReactionRemoveAll: 'messageReactionRemoveAll';
MessageReactionRemoveEmoji: 'messageReactionRemoveEmoji';
InteractionCreate: 'interactionCreate';
GuildCreate: 'guildCreate';
GuildUpdate: 'guildUpdate';
GuildDelete: 'guildDelete';
GuildMemberAdd: 'guildMemberAdd';
GuildMemberUpdate: 'guildMemberUpdate';
GuildMemberRemove: 'guildMemberRemove';
RoleCreate: 'roleCreate';
RoleUpdate: 'roleUpdate';
RoleDelete: 'roleDelete';
ChannelCreate: 'channelCreate';
ChannelUpdate: 'channelUpdate';
ChannelDelete: 'channelDelete';
ThreadCreate: 'threadCreate';
ThreadUpdate: 'threadUpdate';
ThreadDelete: 'threadDelete';
TypingStart: 'typingStart';
PresenceUpdate: 'presenceUpdate';
VoiceStateUpdate: 'voiceStateUpdate';
InviteCreate: 'inviteCreate';
InviteDelete: 'inviteDelete';
GuildBanAdd: 'guildBanAdd';
GuildBanRemove: 'guildBanRemove';
EmojiCreate: 'emojiCreate';
EmojiUpdate: 'emojiUpdate';
EmojiDelete: 'emojiDelete';
StickerCreate: 'stickerCreate';
StickerUpdate: 'stickerUpdate';
StickerDelete: 'stickerDelete';
GuildScheduledEventCreate: 'guildScheduledEventCreate';
GuildScheduledEventUpdate: 'guildScheduledEventUpdate';
GuildScheduledEventDelete: 'guildScheduledEventDelete';
GuildScheduledEventUserAdd: 'guildScheduledEventUserAdd';
GuildScheduledEventUserRemove: 'guildScheduledEventUserRemove';
Debug: 'debug';
Warn: 'warn';
Error: 'error';
};Usage Examples:
import { Client, Events, GatewayIntentBits } from 'discord.js';
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
// Basic event handling
client.on(Events.Ready, (client) => {
console.log(`Logged in as ${client.user.tag}!`);
});
client.on(Events.MessageCreate, (message) => {
if (message.author.bot) return;
if (message.content === '!ping') {
message.reply('Pong!');
}
});
// Guild member events
client.on(Events.GuildMemberAdd, (member) => {
const channel = member.guild.systemChannel;
if (channel) {
channel.send(`Welcome ${member} to ${member.guild.name}!`);
}
});
client.on(Events.GuildMemberRemove, (member) => {
console.log(`${member.user.tag} left ${member.guild.name}`);
});
// Reaction events
client.on(Events.MessageReactionAdd, async (reaction, user) => {
if (reaction.partial) {
try {
await reaction.fetch();
} catch (error) {
console.error('Something went wrong when fetching the message:', error);
return;
}
}
if (reaction.emoji.name === 'โญ' && reaction.count >= 5) {
console.log('Message starred!');
}
});Base collector functionality for gathering events over time with filtering.
/**
* Abstract base class for collectors
* @template Key The key type for collected items
* @template Value The value type for collected items
* @template Args The arguments passed to event handlers
*/
abstract class Collector<Key, Value, Args extends unknown[] = []> extends EventEmitter {
constructor(client: Client, options?: CollectorOptions<[Key, Value, ...Args]>);
readonly client: Client;
readonly collected: Collection<Key, Value>;
readonly ended: boolean;
readonly endReason: string | null;
readonly filter: CollectorFilter<Args>;
readonly options: CollectorOptions<[Key, Value, ...Args]>;
// Event handlers
on(event: 'collect', listener: (value: Value, ...args: Args) => Awaitable<void>): this;
on(event: 'dispose', listener: (value: Value, ...args: Args) => Awaitable<void>): this;
on(event: 'end', listener: (collected: Collection<Key, Value>, reason: string) => Awaitable<void>): this;
// Collector control
stop(reason?: string): void;
resetTimer(options?: CollectorResetTimerOptions): void;
checkEnd(): boolean;
// Abstract methods
abstract collect(...args: Args): Key | Promise<Key> | null;
abstract dispose(...args: Args): Key | Promise<Key> | null;
// Static methods
static key(...args: unknown[]): unknown;
}
interface CollectorOptions<T extends unknown[]> {
filter?: CollectorFilter<T>;
time?: number;
idle?: number;
max?: number;
maxProcessed?: number;
dispose?: boolean;
}
type CollectorFilter<T extends unknown[]> = (...args: T) => boolean | Promise<boolean>;
interface CollectorResetTimerOptions {
time?: number;
idle?: number;
}
interface CollectorEvents<Key, Value> {
collect: [value: Value];
dispose: [value: Value];
end: [collected: Collection<Key, Value>, reason: string];
}Collect messages from a channel with filtering and time limits.
/**
* Collects messages from a channel
* @extends Collector
*/
class MessageCollector extends Collector<Snowflake, Message, [Collection<Snowflake, Message>]> {
constructor(channel: TextBasedChannel, options?: MessageCollectorOptions);
readonly channel: TextBasedChannel;
readonly received: number;
collect(message: Message): Snowflake | null;
dispose(message: Message): Snowflake | null;
static key(message: Message): Snowflake;
}
interface MessageCollectorOptions extends CollectorOptions<[Message]> {
max?: number;
maxProcessed?: number;
}
/**
* Await messages utility for simple message collection
*/
interface AwaitMessagesOptions extends MessageCollectorOptions {
errors?: string[];
}Usage Examples:
// Basic message collector
const filter = (message: Message) => message.author.id === interaction.user.id;
const collector = channel.createMessageCollector({
filter,
time: 15000, // 15 seconds
max: 1 // Collect only 1 message
});
collector.on('collect', (message) => {
console.log(`Collected: ${message.content}`);
});
collector.on('end', (collected, reason) => {
if (reason === 'time') {
channel.send('Time ran out!');
} else {
channel.send(`Collection ended: ${reason}`);
}
});
// Await messages (promise-based)
try {
const collected = await channel.awaitMessages({
filter: (msg) => msg.author.id === user.id,
max: 1,
time: 30000,
errors: ['time']
});
const message = collected.first();
await channel.send(`You said: ${message?.content}`);
} catch (error) {
await channel.send('No response received in time.');
}
// Advanced message collector with multiple filters
const advancedCollector = channel.createMessageCollector({
filter: (message) => {
// Only collect messages from non-bots
if (message.author.bot) return false;
// Only collect messages that start with specific prefix
if (!message.content.startsWith('!vote')) return false;
// Only collect from specific users
const allowedUsers = ['123456789012345678', '876543210987654321'];
return allowedUsers.includes(message.author.id);
},
time: 60000, // 1 minute
max: 10, // Maximum 10 messages
idle: 10000 // Stop if no messages for 10 seconds
});
advancedCollector.on('collect', (message) => {
console.log(`Vote collected from ${message.author.tag}: ${message.content}`);
});Collect interactions (buttons, select menus) from messages.
/**
* Collects interactions from message components
* @extends Collector
*/
class InteractionCollector extends Collector<Snowflake, Interaction, [Collection<Snowflake, Interaction>]> {
constructor(client: Client, options?: InteractionCollectorOptions);
readonly message: Message | null;
readonly messageId: Snowflake | null;
readonly messageInteractionId: Snowflake | null;
readonly channelId: Snowflake | null;
readonly guildId: Snowflake | null;
readonly interactionType: InteractionType | null;
readonly componentType: ComponentType | null;
readonly total: number;
readonly users: Collection<Snowflake, User>;
collect(interaction: Interaction): Snowflake | null;
dispose(interaction: Interaction): Snowflake | null;
empty(): void;
static key(interaction: Interaction): Snowflake;
}
interface InteractionCollectorOptions extends CollectorOptions<[Interaction]> {
message?: Message;
messageId?: Snowflake;
messageInteractionId?: Snowflake;
channel?: TextBasedChannel;
channelId?: Snowflake;
guild?: Guild;
guildId?: Snowflake;
interactionType?: InteractionType | InteractionType[];
componentType?: ComponentType | ComponentType[];
}
/**
* Await interactions utility for simple interaction collection
*/
interface AwaitInteractionsOptions extends InteractionCollectorOptions {
errors?: string[];
}Usage Examples:
// Button interaction collector
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId('yes')
.setLabel('Yes')
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId('no')
.setLabel('No')
.setStyle(ButtonStyle.Danger)
);
const message = await channel.send({
content: 'Do you agree?',
components: [row]
});
const collector = message.createInteractionCollector({
componentType: ComponentType.Button,
time: 15000 // 15 seconds
});
collector.on('collect', async (interaction) => {
if (interaction.customId === 'yes') {
await interaction.reply('You agreed!');
} else if (interaction.customId === 'no') {
await interaction.reply('You disagreed!');
}
collector.stop('answered');
});
collector.on('end', async (collected, reason) => {
// Disable all buttons when collector ends
const disabledRow = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
ButtonBuilder.from(row.components[0]).setDisabled(true),
ButtonBuilder.from(row.components[1]).setDisabled(true)
);
await message.edit({ components: [disabledRow] });
if (reason === 'time') {
await message.followUp('Time ran out! No response received.');
}
});
// Select menu interaction collector
const selectCollector = message.createInteractionCollector({
componentType: ComponentType.StringSelect,
filter: (interaction) => interaction.user.id === targetUserId,
max: 1
});
selectCollector.on('collect', async (interaction) => {
const selectedValues = interaction.values;
await interaction.reply(`You selected: ${selectedValues.join(', ')}`);
});Collect reactions added to messages with emoji filtering.
/**
* Collects reactions on messages
* @extends Collector
*/
class ReactionCollector extends Collector<string | Snowflake, MessageReaction, [User]> {
constructor(message: Message, options?: ReactionCollectorOptions);
readonly message: Message;
readonly users: Collection<Snowflake, User>;
readonly total: number;
collect(reaction: MessageReaction, user: User): string | Snowflake | null;
dispose(reaction: MessageReaction, user: User): string | Snowflake | null;
empty(): void;
static key(reaction: MessageReaction): string | Snowflake;
}
interface ReactionCollectorOptions extends CollectorOptions<[MessageReaction, User]> {
max?: number;
maxEmojis?: number;
maxUsers?: number;
}
/**
* Await reactions utility for simple reaction collection
*/
interface AwaitReactionsOptions extends ReactionCollectorOptions {
errors?: string[];
}Usage Examples:
// Reaction-based poll
const pollMessage = await channel.send('React with ๐ for yes, ๐ for no');
await pollMessage.react('๐');
await pollMessage.react('๐');
const collector = pollMessage.createReactionCollector({
filter: (reaction, user) => {
return ['๐', '๐'].includes(reaction.emoji.name!) && !user.bot;
},
time: 60000 // 1 minute
});
collector.on('collect', (reaction, user) => {
console.log(`${user.tag} reacted with ${reaction.emoji.name}`);
});
collector.on('end', (collected) => {
const yesReaction = collected.get('๐');
const noReaction = collected.get('๐');
const yesCount = yesReaction ? yesReaction.count - 1 : 0; // -1 to exclude bot's reaction
const noCount = noReaction ? noReaction.count - 1 : 0;
channel.send(`Poll ended! Yes: ${yesCount}, No: ${noCount}`);
});
// Reaction role system
const reactionRoleMessage = await channel.send({
embeds: [
new EmbedBuilder()
.setTitle('Reaction Roles')
.setDescription('React to get roles!\n๐ฎ - Gamer\n๐ต - Music Lover\n๐ - Reader')
]
});
await reactionRoleMessage.react('๐ฎ');
await reactionRoleMessage.react('๐ต');
await reactionRoleMessage.react('๐');
const reactionRoleCollector = reactionRoleMessage.createReactionCollector({
dispose: true // Also listen for reaction removals
});
const roleMap = new Map([
['๐ฎ', 'gamer-role-id'],
['๐ต', 'music-role-id'],
['๐', 'reader-role-id']
]);
reactionRoleCollector.on('collect', async (reaction, user) => {
if (user.bot) return;
const roleId = roleMap.get(reaction.emoji.name!);
if (!roleId) return;
const member = await reaction.message.guild?.members.fetch(user.id);
const role = reaction.message.guild?.roles.cache.get(roleId);
if (member && role) {
await member.roles.add(role);
console.log(`Added ${role.name} role to ${user.tag}`);
}
});
reactionRoleCollector.on('dispose', async (reaction, user) => {
if (user.bot) return;
const roleId = roleMap.get(reaction.emoji.name!);
if (!roleId) return;
const member = await reaction.message.guild?.members.fetch(user.id);
const role = reaction.message.guild?.roles.cache.get(roleId);
if (member && role) {
await member.roles.remove(role);
console.log(`Removed ${role.name} role from ${user.tag}`);
}
});Complex event handling patterns and utilities for sophisticated bot behavior.
/**
* Event handling utilities and patterns
*/
interface EventHandlingPatterns {
// Event listener management
once<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): Client;
off<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): Client;
removeAllListeners<K extends keyof ClientEvents>(event?: K): Client;
// Event emitter utilities
listenerCount<K extends keyof ClientEvents>(event: K): number;
listeners<K extends keyof ClientEvents>(event: K): Function[];
rawListeners<K extends keyof ClientEvents>(event: K): Function[];
// Custom event handling
prependListener<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): Client;
prependOnceListener<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): Client;
}
/**
* WebSocket shard events for advanced shard management
*/
const WebSocketShardEvents: {
Close: 'close';
Debug: 'debug';
Error: 'error';
Hello: 'hello';
Open: 'open';
Ready: 'ready';
Resumed: 'resumed';
};Advanced Usage Examples:
// Event-driven command cooldown system
const cooldowns = new Map<string, Map<string, number>>();
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName, user } = interaction;
const cooldownTime = 5000; // 5 seconds
if (!cooldowns.has(commandName)) {
cooldowns.set(commandName, new Map());
}
const timestamps = cooldowns.get(commandName)!;
const now = Date.now();
if (timestamps.has(user.id)) {
const expirationTime = timestamps.get(user.id)! + cooldownTime;
if (now < expirationTime) {
const timeLeft = (expirationTime - now) / 1000;
return interaction.reply({
content: `Please wait ${timeLeft.toFixed(1)} more second(s) before reusing the \`${commandName}\` command.`,
ephemeral: true
});
}
}
timestamps.set(user.id, now);
setTimeout(() => timestamps.delete(user.id), cooldownTime);
// Execute command...
});
// Multi-step interaction flow
const multiStepFlows = new Map<string, { step: number; data: any }>();
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isButton()) return;
const flowId = `${interaction.user.id}_setup`;
if (interaction.customId === 'start_setup') {
multiStepFlows.set(flowId, { step: 1, data: {} });
// Continue with step 1...
const modal = new ModalBuilder()
.setCustomId('setup_step1')
.setTitle('Setup - Step 1')
.addComponents(/* ... */);
await interaction.showModal(modal);
}
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isModalSubmit()) return;
const flowId = `${interaction.user.id}_setup`;
const flow = multiStepFlows.get(flowId);
if (!flow) return;
if (interaction.customId === 'setup_step1') {
flow.data.step1 = interaction.fields.getTextInputValue('input1');
flow.step = 2;
// Continue to step 2...
}
});
// Event-based auto-moderation
client.on(Events.MessageCreate, async (message) => {
if (message.author.bot || !message.guild) return;
const badWords = ['spam', 'inappropriate']; // Example list
const containsBadWords = badWords.some(word =>
message.content.toLowerCase().includes(word)
);
if (containsBadWords) {
await message.delete();
const warningMessage = await message.channel.send({
content: `${message.author}, your message was removed for inappropriate content.`,
allowedMentions: { users: [message.author.id] }
});
// Auto-delete warning after 5 seconds
setTimeout(() => warningMessage.delete().catch(() => {}), 5000);
// Log to moderation channel
const modChannel = message.guild.channels.cache.find(
ch => ch.name === 'mod-logs'
) as TextChannel;
if (modChannel) {
await modChannel.send({
embeds: [
new EmbedBuilder()
.setTitle('Message Deleted')
.setDescription(`Message from ${message.author} deleted for inappropriate content`)
.addFields(
{ name: 'Channel', value: message.channel.toString() },
{ name: 'Content', value: message.content || 'No content' }
)
.setTimestamp()
.setColor(Colors.Red)
]
});
}
}
});