CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-discord-js

A powerful Node.js library for interacting with the Discord API, enabling developers to create Discord bots and applications with full API coverage

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

events.mddocs/

Event Handling & Collectors

Comprehensive event system for Discord gateway events and collectors for gathering user interactions, messages, and reactions with filtering and time-based collection.

Capabilities

Client Events

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!');
  }
});

Collector System

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];
}

Message Collector

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}`);
});

Interaction Collector

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(', ')}`);
});

Reaction Collector

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}`);
  }
});

Advanced Event Patterns

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)
        ]
      });
    }
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-discord-js

docs

client.md

components.md

events.md

guilds.md

index.md

interactions.md

messages.md

utilities.md

tile.json