Vibeship-spawner-skills discord-bot-architect

Discord Bot Architect Skill

install
source · Clone the upstream repo
git clone https://github.com/vibeforge1111/vibeship-spawner-skills
manifest: integrations/discord-bot-architect/skill.yaml
source content

Discord Bot Architect Skill

Expert-level Discord bot development with Discord.js v14 and Pycord

id: discord-bot-architect name: Discord Bot Architect description: | Specialized skill for building production-ready Discord bots. Covers Discord.js (JavaScript) and Pycord (Python), gateway intents, slash commands, interactive components, rate limiting, and sharding.

version: 1.0.0 category: integrations tags:

  • discord
  • bots
  • slash-commands
  • interactions
  • real-time

principles:

  • Slash commands over message parsing (Message Content Intent deprecated)
  • Acknowledge interactions within 3 seconds, always
  • Request only required intents (minimize privileged intents)
  • Handle rate limits gracefully with exponential backoff
  • Plan for sharding from the start (required at 2500+ guilds)
  • Use components (buttons, selects, modals) for rich UX
  • Test with guild commands first, deploy global when ready

patterns:

  • name: Discord.js v14 Foundation description: Modern Discord bot setup with Discord.js v14 and slash commands when_to_use:

    • Building Discord bots with JavaScript/TypeScript
    • Need full gateway connection with events
    • Building bots with complex interactions structure: | discord-bot/ ├── src/ │ ├── index.js # Main entry point │ ├── deploy-commands.js # Command registration script │ ├── commands/ # Slash command handlers │ │ └── ping.js │ └── events/ # Event handlers │ ├── ready.js │ └── interactionCreate.js ├── .env └── package.json implementation: |
    // src/index.js
    const { Client, Collection, GatewayIntentBits, Events } = require('discord.js');
    const fs = require('node:fs');
    const path = require('node:path');
    require('dotenv').config();
    
    // Create client with minimal required intents
    const client = new Client({
      intents: [
        GatewayIntentBits.Guilds,
        // Add only what you need:
        // GatewayIntentBits.GuildMessages,
        // GatewayIntentBits.MessageContent,  // PRIVILEGED - avoid if possible
      ]
    });
    
    // Load commands
    client.commands = new Collection();
    const commandsPath = path.join(__dirname, 'commands');
    const commandFiles = fs.readdirSync(commandsPath).filter(f => f.endsWith('.js'));
    
    for (const file of commandFiles) {
      const filePath = path.join(commandsPath, file);
      const command = require(filePath);
      if ('data' in command && 'execute' in command) {
        client.commands.set(command.data.name, command);
      }
    }
    
    // Load events
    const eventsPath = path.join(__dirname, 'events');
    const eventFiles = fs.readdirSync(eventsPath).filter(f => f.endsWith('.js'));
    
    for (const file of eventFiles) {
      const filePath = path.join(eventsPath, file);
      const event = require(filePath);
      if (event.once) {
        client.once(event.name, (...args) => event.execute(...args));
      } else {
        client.on(event.name, (...args) => event.execute(...args));
      }
    }
    
    client.login(process.env.DISCORD_TOKEN);
    
    // src/commands/ping.js
    const { SlashCommandBuilder } = require('discord.js');
    
    module.exports = {
      data: new SlashCommandBuilder()
        .setName('ping')
        .setDescription('Replies with Pong!'),
    
      async execute(interaction) {
        const sent = await interaction.reply({
          content: 'Pinging...',
          fetchReply: true
        });
    
        const latency = sent.createdTimestamp - interaction.createdTimestamp;
        await interaction.editReply(`Pong! Latency: ${latency}ms`);
      }
    };
    
    // src/events/interactionCreate.js
    const { Events } = require('discord.js');
    
    module.exports = {
      name: Events.InteractionCreate,
      async execute(interaction) {
        if (!interaction.isChatInputCommand()) return;
    
        const command = interaction.client.commands.get(interaction.commandName);
        if (!command) {
          console.error(`No command matching ${interaction.commandName}`);
          return;
        }
    
        try {
          await command.execute(interaction);
        } catch (error) {
          console.error(error);
          const reply = {
            content: 'There was an error executing this command!',
            ephemeral: true
          };
    
          if (interaction.replied || interaction.deferred) {
            await interaction.followUp(reply);
          } else {
            await interaction.reply(reply);
          }
        }
      }
    };
    
    // src/deploy-commands.js
    const { REST, Routes } = require('discord.js');
    const fs = require('node:fs');
    const path = require('node:path');
    require('dotenv').config();
    
    const commands = [];
    const commandsPath = path.join(__dirname, 'commands');
    const commandFiles = fs.readdirSync(commandsPath).filter(f => f.endsWith('.js'));
    
    for (const file of commandFiles) {
      const command = require(path.join(commandsPath, file));
      commands.push(command.data.toJSON());
    }
    
    const rest = new REST().setToken(process.env.DISCORD_TOKEN);
    
    (async () => {
      try {
        console.log(`Refreshing ${commands.length} commands...`);
    
        // Guild commands (instant, for testing)
        // const data = await rest.put(
        //   Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID),
        //   { body: commands }
        // );
    
        // Global commands (can take up to 1 hour to propagate)
        const data = await rest.put(
          Routes.applicationCommands(process.env.CLIENT_ID),
          { body: commands }
        );
    
        console.log(`Successfully registered ${data.length} commands`);
      } catch (error) {
        console.error(error);
      }
    })();
    

    example: |

    .env

    DISCORD_TOKEN=your_bot_token CLIENT_ID=your_application_id

    package.json dependencies

    { "dependencies": { "discord.js": "^14.14.1", "dotenv": "^16.3.1" } }

  • name: Pycord Bot Foundation description: Discord bot with Pycord (Python) and application commands when_to_use:

    • Building Discord bots with Python
    • Prefer async/await patterns
    • Need good slash command support structure: | discord-bot/ ├── main.py # Main bot file ├── cogs/ # Command groups │ └── general.py ├── .env └── requirements.txt implementation: |
    # main.py
    import os
    import discord
    from discord.ext import commands
    from dotenv import load_dotenv
    
    load_dotenv()
    
    # Configure intents - only enable what you need
    intents = discord.Intents.default()
    # intents.message_content = True  # PRIVILEGED - avoid if possible
    # intents.members = True          # PRIVILEGED
    
    bot = commands.Bot(
        command_prefix="!",  # Legacy, prefer slash commands
        intents=intents
    )
    
    @bot.event
    async def on_ready():
        print(f"Logged in as {bot.user}")
        # Sync commands (do this carefully - see sharp edges)
        # await bot.sync_commands()
    
    # Slash command
    @bot.slash_command(name="ping", description="Check bot latency")
    async def ping(ctx: discord.ApplicationContext):
        latency = round(bot.latency * 1000)
        await ctx.respond(f"Pong! Latency: {latency}ms")
    
    # Slash command with options
    @bot.slash_command(name="greet", description="Greet a user")
    async def greet(
        ctx: discord.ApplicationContext,
        user: discord.Option(discord.Member, "User to greet"),
        message: discord.Option(str, "Custom message", required=False)
    ):
        msg = message or "Hello!"
        await ctx.respond(f"{user.mention}, {msg}")
    
    # Load cogs
    for filename in os.listdir("./cogs"):
        if filename.endswith(".py"):
            bot.load_extension(f"cogs.{filename[:-3]}")
    
    bot.run(os.environ["DISCORD_TOKEN"])
    
    # cogs/general.py
    import discord
    from discord.ext import commands
    
    class General(commands.Cog):
        def __init__(self, bot):
            self.bot = bot
    
        @commands.slash_command(name="info", description="Bot information")
        async def info(self, ctx: discord.ApplicationContext):
            embed = discord.Embed(
                title="Bot Info",
                description="A helpful Discord bot",
                color=discord.Color.blue()
            )
            embed.add_field(name="Servers", value=len(self.bot.guilds))
            embed.add_field(name="Latency", value=f"{round(self.bot.latency * 1000)}ms")
            await ctx.respond(embed=embed)
    
        @commands.Cog.listener()
        async def on_member_join(self, member: discord.Member):
            # Requires Members intent (PRIVILEGED)
            channel = member.guild.system_channel
            if channel:
                await channel.send(f"Welcome {member.mention}!")
    
    def setup(bot):
        bot.add_cog(General(bot))
    

    example: |

    requirements.txt

    py-cord>=2.6.0 python-dotenv>=1.0.0

  • name: Interactive Components Pattern description: Using buttons, select menus, and modals for rich UX when_to_use:

    • Need interactive user interfaces
    • Collecting user input beyond slash command options
    • Building menus, confirmations, or forms implementation: |
    // Discord.js - Buttons and Select Menus
    const {
      SlashCommandBuilder,
      ActionRowBuilder,
      ButtonBuilder,
      ButtonStyle,
      StringSelectMenuBuilder,
      ModalBuilder,
      TextInputBuilder,
      TextInputStyle
    } = require('discord.js');
    
    module.exports = {
      data: new SlashCommandBuilder()
        .setName('menu')
        .setDescription('Shows an interactive menu'),
    
      async execute(interaction) {
        // Button row
        const buttonRow = new ActionRowBuilder()
          .addComponents(
            new ButtonBuilder()
              .setCustomId('confirm')
              .setLabel('Confirm')
              .setStyle(ButtonStyle.Primary),
            new ButtonBuilder()
              .setCustomId('cancel')
              .setLabel('Cancel')
              .setStyle(ButtonStyle.Danger),
            new ButtonBuilder()
              .setLabel('Documentation')
              .setURL('https://discord.js.org')
              .setStyle(ButtonStyle.Link)  // Link buttons don't emit events
          );
    
        // Select menu row (one per row, takes all 5 slots)
        const selectRow = new ActionRowBuilder()
          .addComponents(
            new StringSelectMenuBuilder()
              .setCustomId('select-role')
              .setPlaceholder('Select a role')
              .setMinValues(1)
              .setMaxValues(3)
              .addOptions([
                { label: 'Developer', value: 'dev', emoji: '💻' },
                { label: 'Designer', value: 'design', emoji: '🎨' },
                { label: 'Community', value: 'community', emoji: '🎉' }
              ])
          );
    
        await interaction.reply({
          content: 'Choose an option:',
          components: [buttonRow, selectRow]
        });
    
        // Collect responses
        const collector = interaction.channel.createMessageComponentCollector({
          filter: i => i.user.id === interaction.user.id,
          time: 60_000  // 60 seconds timeout
        });
    
        collector.on('collect', async i => {
          if (i.customId === 'confirm') {
            await i.update({ content: 'Confirmed!', components: [] });
            collector.stop();
          } else if (i.customId === 'cancel') {
            await i.update({ content: 'Cancelled', components: [] });
            collector.stop();
          } else if (i.customId === 'select-role') {
            await i.update({ content: `You selected: ${i.values.join(', ')}` });
          }
        });
      }
    };
    
    // Modals (forms)
    module.exports = {
      data: new SlashCommandBuilder()
        .setName('feedback')
        .setDescription('Submit feedback'),
    
      async execute(interaction) {
        const modal = new ModalBuilder()
          .setCustomId('feedback-modal')
          .setTitle('Submit Feedback');
    
        const titleInput = new TextInputBuilder()
          .setCustomId('feedback-title')
          .setLabel('Title')
          .setStyle(TextInputStyle.Short)
          .setRequired(true)
          .setMaxLength(100);
    
        const bodyInput = new TextInputBuilder()
          .setCustomId('feedback-body')
          .setLabel('Your feedback')
          .setStyle(TextInputStyle.Paragraph)
          .setRequired(true)
          .setMaxLength(1000)
          .setPlaceholder('Describe your feedback...');
    
        modal.addComponents(
          new ActionRowBuilder().addComponents(titleInput),
          new ActionRowBuilder().addComponents(bodyInput)
        );
    
        // Show modal - MUST be first response
        await interaction.showModal(modal);
      }
    };
    
    // Handle modal submission in interactionCreate
    if (interaction.isModalSubmit()) {
      if (interaction.customId === 'feedback-modal') {
        const title = interaction.fields.getTextInputValue('feedback-title');
        const body = interaction.fields.getTextInputValue('feedback-body');
    
        await interaction.reply({
          content: `Thanks for your feedback!\n**${title}**\n${body}`,
          ephemeral: true
        });
      }
    }
    
    # Pycord - Buttons and Views
    import discord
    
    class ConfirmView(discord.ui.View):
        def __init__(self):
            super().__init__(timeout=60)
            self.value = None
    
        @discord.ui.button(label="Confirm", style=discord.ButtonStyle.green)
        async def confirm(self, button, interaction):
            self.value = True
            await interaction.response.edit_message(content="Confirmed!", view=None)
            self.stop()
    
        @discord.ui.button(label="Cancel", style=discord.ButtonStyle.red)
        async def cancel(self, button, interaction):
            self.value = False
            await interaction.response.edit_message(content="Cancelled", view=None)
            self.stop()
    
    @bot.slash_command(name="confirm")
    async def confirm_cmd(ctx: discord.ApplicationContext):
        view = ConfirmView()
        await ctx.respond("Are you sure?", view=view)
    
        await view.wait()  # Wait for user interaction
        if view.value is None:
            await ctx.followup.send("Timed out")
    
    # Select Menu
    class RoleSelect(discord.ui.Select):
        def __init__(self):
            options = [
                discord.SelectOption(label="Developer", value="dev", emoji="💻"),
                discord.SelectOption(label="Designer", value="design", emoji="🎨"),
            ]
            super().__init__(
                placeholder="Select roles...",
                min_values=1,
                max_values=2,
                options=options
            )
    
        async def callback(self, interaction):
            await interaction.response.send_message(
                f"You selected: {', '.join(self.values)}",
                ephemeral=True
            )
    
    class RoleView(discord.ui.View):
        def __init__(self):
            super().__init__()
            self.add_item(RoleSelect())
    
    # Modal
    class FeedbackModal(discord.ui.Modal):
        def __init__(self):
            super().__init__(title="Submit Feedback")
    
            self.add_item(discord.ui.InputText(
                label="Title",
                style=discord.InputTextStyle.short,
                required=True,
                max_length=100
            ))
            self.add_item(discord.ui.InputText(
                label="Feedback",
                style=discord.InputTextStyle.long,
                required=True,
                max_length=1000
            ))
    
        async def callback(self, interaction):
            title = self.children[0].value
            body = self.children[1].value
            await interaction.response.send_message(
                f"Thanks!\n**{title}**\n{body}",
                ephemeral=True
            )
    
    @bot.slash_command(name="feedback")
    async def feedback(ctx: discord.ApplicationContext):
        await ctx.send_modal(FeedbackModal())
    

    limits:

    • 5 ActionRows per message/modal
    • 5 buttons per ActionRow
    • 1 select menu per ActionRow (takes all 5 slots)
    • 5 select menus max per message
    • 25 options per select menu
    • Modal must be first response (cannot defer first)
  • name: Deferred Response Pattern description: Handle slow operations without timing out when_to_use:

    • Operation takes more than 3 seconds
    • Database queries, API calls, LLM responses
    • File processing or generation implementation: |
    // Discord.js - Deferred response
    module.exports = {
      data: new SlashCommandBuilder()
        .setName('slow-task')
        .setDescription('Performs a slow operation'),
    
      async execute(interaction) {
        // Defer immediately - you have 3 seconds!
        await interaction.deferReply();
        // For ephemeral: await interaction.deferReply({ ephemeral: true });
    
        try {
          // Now you have 15 minutes to complete
          const result = await slowDatabaseQuery();
          const aiResponse = await callOpenAI(result);
    
          // Edit the deferred reply
          await interaction.editReply({
            content: `Result: ${aiResponse}`,
            embeds: [resultEmbed]
          });
        } catch (error) {
          await interaction.editReply({
            content: 'An error occurred while processing your request.'
          });
        }
      }
    };
    
    // For components (buttons, select menus)
    collector.on('collect', async i => {
      await i.deferUpdate();  // Acknowledge without visual change
      // Or: await i.deferReply({ ephemeral: true });
    
      const result = await slowOperation();
      await i.editReply({ content: result });
    });
    
    # Pycord - Deferred response
    @bot.slash_command(name="slow-task")
    async def slow_task(ctx: discord.ApplicationContext):
        # Defer immediately
        await ctx.defer()
        # For ephemeral: await ctx.defer(ephemeral=True)
    
        try:
            result = await slow_database_query()
            ai_response = await call_openai(result)
    
            await ctx.followup.send(f"Result: {ai_response}")
        except Exception as e:
            await ctx.followup.send("An error occurred")
    

    timing: initial_response: 3 seconds deferred_followup: 15 minutes ephemeral_note: "Can only be set on initial response, not changed later"

  • name: Embed Builder Pattern description: Rich embedded messages for professional-looking content when_to_use:

    • Displaying formatted information
    • Status updates, help menus, logs
    • Data with structure (fields, images) implementation: |
    const { EmbedBuilder, Colors } = require('discord.js');
    
    // Basic embed
    const embed = new EmbedBuilder()
      .setColor(Colors.Blue)
      .setTitle('Bot Status')
      .setURL('https://example.com')
      .setAuthor({
        name: 'Bot Name',
        iconURL: client.user.displayAvatarURL()
      })
      .setDescription('Current status and statistics')
      .addFields(
        { name: 'Servers', value: `${client.guilds.cache.size}`, inline: true },
        { name: 'Users', value: `${client.users.cache.size}`, inline: true },
        { name: 'Uptime', value: formatUptime(), inline: true }
      )
      .setThumbnail(client.user.displayAvatarURL())
      .setImage('https://example.com/banner.png')
      .setTimestamp()
      .setFooter({
        text: 'Requested by User',
        iconURL: interaction.user.displayAvatarURL()
      });
    
    await interaction.reply({ embeds: [embed] });
    
    // Multiple embeds (max 10)
    await interaction.reply({ embeds: [embed1, embed2, embed3] });
    
    # Pycord
    embed = discord.Embed(
        title="Bot Status",
        description="Current status and statistics",
        color=discord.Color.blue(),
        url="https://example.com"
    )
    embed.set_author(
        name="Bot Name",
        icon_url=bot.user.display_avatar.url
    )
    embed.add_field(name="Servers", value=len(bot.guilds), inline=True)
    embed.add_field(name="Users", value=len(bot.users), inline=True)
    embed.set_thumbnail(url=bot.user.display_avatar.url)
    embed.set_image(url="https://example.com/banner.png")
    embed.set_footer(text="Requested by User", icon_url=ctx.author.display_avatar.url)
    embed.timestamp = discord.utils.utcnow()
    
    await ctx.respond(embed=embed)
    

    limits:

    • 10 embeds per message
    • 6000 characters total across all embeds
    • 256 characters for title
    • 4096 characters for description
    • 25 fields per embed
    • 256 characters per field name
    • 1024 characters per field value
  • name: Rate Limit Handling Pattern description: Gracefully handle Discord API rate limits when_to_use:

    • High-volume operations
    • Bulk messaging or role assignments
    • Any repeated API calls implementation: |
    // Discord.js handles rate limits automatically, but for custom handling:
    const { REST } = require('discord.js');
    
    const rest = new REST({ version: '10' })
      .setToken(process.env.DISCORD_TOKEN);
    
    rest.on('rateLimited', (info) => {
      console.log(`Rate limited! Retry after ${info.retryAfter}ms`);
      console.log(`Route: ${info.route}`);
      console.log(`Global: ${info.global}`);
    });
    
    // Queue pattern for bulk operations
    class RateLimitQueue {
      constructor() {
        this.queue = [];
        this.processing = false;
        this.requestsPerSecond = 40; // Safe margin below 50
      }
    
      async add(operation) {
        return new Promise((resolve, reject) => {
          this.queue.push({ operation, resolve, reject });
          this.process();
        });
      }
    
      async process() {
        if (this.processing || this.queue.length === 0) return;
        this.processing = true;
    
        while (this.queue.length > 0) {
          const { operation, resolve, reject } = this.queue.shift();
    
          try {
            const result = await operation();
            resolve(result);
          } catch (error) {
            reject(error);
          }
    
          // Throttle: ~40 requests per second
          await new Promise(r => setTimeout(r, 1000 / this.requestsPerSecond));
        }
    
        this.processing = false;
      }
    }
    
    const queue = new RateLimitQueue();
    
    // Usage: Send 200 messages without hitting rate limits
    for (const user of users) {
      await queue.add(() => user.send('Welcome!'));
    }
    
    # Pycord/discord.py handles rate limits automatically
    # For custom handling:
    import asyncio
    from collections import deque
    
    class RateLimitQueue:
        def __init__(self, requests_per_second=40):
            self.queue = deque()
            self.processing = False
            self.delay = 1 / requests_per_second
    
        async def add(self, coro):
            future = asyncio.Future()
            self.queue.append((coro, future))
            if not self.processing:
                asyncio.create_task(self._process())
            return await future
    
        async def _process(self):
            self.processing = True
            while self.queue:
                coro, future = self.queue.popleft()
                try:
                    result = await coro
                    future.set_result(result)
                except Exception as e:
                    future.set_exception(e)
                await asyncio.sleep(self.delay)
            self.processing = False
    
    queue = RateLimitQueue()
    
    # Usage
    for member in guild.members:
        await queue.add(member.send("Welcome!"))
    

    rate_limits: global: 50 requests per second gateway: 120 requests per 60 seconds specific: - "Messages to same channel: 5/5s" - "Bulk delete: 1/1s" - "Guild member requests: varies by guild size"

  • name: Sharding Pattern description: Scale bots to 2500+ servers with sharding when_to_use:

    • Bot approaching 2500 guilds (required)
    • Want horizontal scaling
    • Memory optimization for large bots implementation: |
    // Discord.js Sharding Manager
    // shard.js (main entry)
    const { ShardingManager } = require('discord.js');
    
    const manager = new ShardingManager('./bot.js', {
      token: process.env.DISCORD_TOKEN,
      totalShards: 'auto',  // Discord determines optimal count
      // Or specify: totalShards: 4
    });
    
    manager.on('shardCreate', shard => {
      console.log(`Launched shard ${shard.id}`);
    
      shard.on('ready', () => {
        console.log(`Shard ${shard.id} ready`);
      });
    
      shard.on('disconnect', () => {
        console.log(`Shard ${shard.id} disconnected`);
      });
    });
    
    manager.spawn();
    
    // bot.js - Modified for sharding
    const { Client } = require('discord.js');
    
    const client = new Client({ intents: [...] });
    
    // Get shard info
    client.on('ready', () => {
      console.log(`Shard ${client.shard.ids[0]} ready with ${client.guilds.cache.size} guilds`);
    });
    
    // Cross-shard data
    async function getTotalGuilds() {
      const results = await client.shard.fetchClientValues('guilds.cache.size');
      return results.reduce((acc, count) => acc + count, 0);
    }
    
    // Broadcast to all shards
    async function broadcastMessage(channelId, message) {
      await client.shard.broadcastEval(
        (c, { channelId, message }) => {
          const channel = c.channels.cache.get(channelId);
          if (channel) channel.send(message);
        },
        { context: { channelId, message } }
      );
    }
    
    # Pycord - AutoShardedBot
    import discord
    from discord.ext import commands
    
    # Automatically handles sharding
    bot = commands.AutoShardedBot(
        command_prefix="!",
        intents=discord.Intents.default(),
        shard_count=None  # Auto-determine
    )
    
    @bot.event
    async def on_ready():
        print(f"Logged in on {len(bot.shards)} shards")
        for shard_id, shard in bot.shards.items():
            print(f"Shard {shard_id}: {shard.latency * 1000:.2f}ms")
    
    @bot.event
    async def on_shard_ready(shard_id):
        print(f"Shard {shard_id} is ready")
    
    # Get guilds per shard
    for shard_id, guilds in bot.guilds_by_shard().items():
        print(f"Shard {shard_id}: {len(guilds)} guilds")
    

    scaling_guide:

    • "1-2500 guilds: No sharding required"
    • "2500+ guilds: Sharding required by Discord"
    • "Recommended: ~1000 guilds per shard"
    • "Memory: Each shard runs in separate process"

anti_patterns:

  • name: Message Content for Commands description: Don't parse message content for commands why_bad: | Message Content Intent is privileged and deprecated for bot commands. Slash commands are the intended approach. bad_example: | // DON'T - Requires privileged Message Content intent client.on('messageCreate', message => { if (message.content.startsWith('!ping')) { message.reply('Pong!'); } }); good_example: | // DO - Use slash commands await client.application.commands.create({ name: 'ping', description: 'Pong!' });

    client.on('interactionCreate', async interaction => { if (interaction.commandName === 'ping') { await interaction.reply('Pong!'); } });

  • name: Syncing Commands on Every Start description: Don't register commands on every bot startup why_bad: | Command registration is rate limited. Global commands take up to 1 hour to propagate. Syncing on every start wastes API calls and can hit limits. bad_example: | // DON'T - Syncs on every restart client.on('ready', async () => { await client.application.commands.set(commands); }); good_example: | // DO - Separate deploy script, run manually // deploy-commands.js await rest.put( Routes.applicationCommands(CLIENT_ID), { body: commands } ); // Run with: node deploy-commands.js

  • name: Blocking the Event Loop description: Don't run synchronous operations in event handlers why_bad: | Discord gateway requires regular heartbeats. Blocking operations cause missed heartbeats and disconnections. bad_example: | client.on('messageCreate', message => { const data = fs.readFileSync('large-file.json'); // BLOCKS // Gateway heartbeat might be missed }); good_example: | client.on('messageCreate', async message => { const data = await fs.promises.readFile('large-file.json'); });

references: