or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client.mdcomponents.mdevents.mdguilds.mdindex.mdinteractions.mdmessages.mdutilities.md
tile.json

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