Skip to main content
The Discord client is the community bot for SkyTeam ROBLOX, built with Discord.js and discordx for command handling.

Overview

The bot provides community management, information distribution, and interactive features for the SkyTeam Discord server. Package: @skyteam/client
Location: apps/client
Type: ES Module

Technology Stack

  • Discord Library: Discord.js v14
  • Command Framework: discordx v11
  • Utilities: @discordx/pagination, @discordx/utilities
  • Database: Prisma via @skyteam/database
  • Build Tool: tsup with ESM output

Project Structure

apps/client/
├── src/
│   ├── commands/
│   │   ├── public/
│   │   │   └── ping.ts           # Public commands
│   │   └── private/
│   │       └── setupchannel.ts   # Admin commands
│   ├── util/
│   │   └── emojis.ts             # Custom emoji helpers
│   └── index.ts                  # Bot initialization
└── package.json

Bot Configuration

Client Setup

The bot client (src/index.ts:12) is configured with required intents and settings:
apps/client/src/index.ts
import { dirname, importx } from "@discordx/importer";
import { NotBot } from "@discordx/utilities";
import { ActivityType, IntentsBitField, Partials } from "discord.js";
import { Client } from "discordx";

export const bot = new Client({
  intents: [
    IntentsBitField.Flags.Guilds,
    IntentsBitField.Flags.GuildMembers,
    IntentsBitField.Flags.GuildMessages,
    IntentsBitField.Flags.GuildMessageReactions,
    IntentsBitField.Flags.GuildVoiceStates,
    IntentsBitField.Flags.MessageContent,
    IntentsBitField.Flags.DirectMessages,
    IntentsBitField.Flags.DirectMessageTyping,
  ],
  partials: [
    Partials.Message,
    Partials.Channel,
    Partials.Reaction,
    Partials.User,
  ],
  botGuilds: [process.env.DISCORD_HOME_GUILD_ID],
  silent: false,
  guards: [NotBot],
  simpleCommand: {
    prefix: "st!",
  },
});

Bot Lifecycle

apps/client/src/index.ts
bot.once("ready", async () => {
  await bot.initApplicationCommands();
  bot.user?.setActivity({
    type: ActivityType.Custom,
    name: "⬇️ DM the bot below for help",
  });

  console.log("Bot initalized.");
});

Event Handlers

apps/client/src/index.ts
bot.on("interactionCreate", (interaction) => {
  try {
    bot.executeInteraction(interaction);
  } catch (error) {
    const errEmbed = new EmbedBuilder()
      .setDescription(
        `<:warning:1204923305895665705>_ _ An error occured whilst trying to run this command; please try again later.`,
      )
      .setTimestamp();

    if (interaction.isRepliable()) {
      interaction.reply({ embeds: [errEmbed], ephemeral: true });
    }

    const log = new EmbedBuilder()
      .setDescription(
        "<:alert:1255679013158916177>_ _ An error occured :( \n**Error:** ```\n" +
          error +
          "\n```",
      )
      .setFields([
        { name: "User", value: `<@${interaction.user.id}>`, inline: true },
        { name: "Channel", value: `<#${interaction.channel?.id}>`, inline: true },
      ]);
  }
});

bot.on("messageCreate", async (message: Message) => {
  await bot.executeCommand(message);
});

Commands

Public Commands

Ping Command

Simple latency test command (src/commands/public/ping.ts:6):
apps/client/src/commands/public/ping.ts
import { CommandInteraction, ContainerBuilder, MessageFlags, TextDisplayBuilder } from "discord.js";
import { Discord, Slash } from "discordx";

@Discord()
export class PingCommand {
  @Slash({
    name: "ping",
    description: "Test ping command.",
    dmPermission: false,
  })
  async ping(interaction: CommandInteraction) {
    await interaction.reply({
      flags: [MessageFlags.IsComponentsV2, MessageFlags.Ephemeral],
      components: [
        new ContainerBuilder().addTextDisplayComponents(
          new TextDisplayBuilder({
            content: `Ping: ${interaction.client.ws.ping}ms`,
          }),
        ),
      ],
    });
  }
}

Admin Commands

Setup Channel Command

Creates formatted channel content with embedded media and interactive components (src/commands/private/setupchannel.ts:17):
apps/client/src/commands/private/setupchannel.ts
import {
  CommandInteraction,
  ContainerBuilder,
  MediaGalleryBuilder,
  MediaGalleryItemBuilder,
  MessageFlags,
  TextDisplayBuilder,
  SeparatorBuilder,
  SectionBuilder,
  ButtonBuilder,
  ButtonStyle,
} from "discord.js";
import { Discord, Slash } from "discordx";

@Discord()
export class SetupChannelCommand {
  @Slash({
    name: "setupchannel",
    description: "Sets up a channel.",
    dmPermission: false,
    defaultMemberPermissions: ["Administrator"],
  })
  async setupchannel(interaction: CommandInteraction) {
    const channelName = interaction.channel?.name;

    interaction.deferReply({
      flags: [MessageFlags.Ephemeral],
    });

    switch (channelName) {
      case "portal":
        await interaction.channel?.send({
          content: "https://files.skyteam.dev/api/public/dl/dir8x9Bc/Assets/Portal.png",
        });

        await interaction.channel?.send({
          flags: [MessageFlags.IsComponentsV2],
          components: [
            new ContainerBuilder().addTextDisplayComponents(
              new TextDisplayBuilder({
                content:
                  "> SkyTeam ROBLOX is an alliance of exceptional virtual airlines...",
              }),
            ),
            // ... more components
          ],
        });
        break;
      
      case "affiliates":
        await interaction.channel?.send({
          content: "https://files.skyteam.dev/api/public/dl/5GAYQdYA/Assets/Affiliates.png",
        });
    }

    interaction.editReply({
      flags: [MessageFlags.IsComponentsV2],
      components: [
        new ContainerBuilder().addTextDisplayComponents(
          new TextDisplayBuilder({
            content: "Channel setup complete.",
          }),
        ),
      ],
    });
  }
}

Channel Setup Features

The setupchannel command creates rich portal messages with: Portal Channel Setup (src/commands/private/setupchannel.ts:32):
  • Alliance description and branding
  • Community rules and guidelines
  • Social media links (Discord, Twitter, ROBLOX)
  • Interactive buttons for external links
  • Media galleries with branded images
Rules Section (src/commands/private/setupchannel.ts:64):
new TextDisplayBuilder({
  content: `
### 1. Mutual Respect
You must respect all users within our community...

### 2. No Spamming
Please do not send repeated or excessive messages...

### 3. SFW Content
We ask that you do not send any NSFW content...

### 4. No advertising
Please refrain from unsolicited advertising...

### 5. No Malicious Content
This server strictly prohibits content which is malicious...

### 6. Politics
We ask that you do not discuss any insensitive political topics...

### 7. Voice Chat
We ask that you be respectful and courteous...
  `,
})
Social Links (src/commands/private/setupchannel.ts:92):
new SectionBuilder()
  .addTextDisplayComponents(
    new TextDisplayBuilder({
      content: "<:discord:1231369743257043006>_ _ **Discord**",
    }),
  )
  .setButtonAccessory(
    new ButtonBuilder()
      .setLabel("Join (.gg/skyteam)")
      .setURL("https://discord.gg/skyteam")
      .setStyle(ButtonStyle.Link),
  )

UI Components

Discord Components V2

The bot uses Discord’s new Components V2 system:
  • ContainerBuilder: Layout containers
  • TextDisplayBuilder: Text content blocks
  • MediaGalleryBuilder: Image galleries with captions
  • SectionBuilder: Structured sections with accessories
  • ButtonBuilder: Interactive buttons
  • SeparatorBuilder: Visual separators with spacing control

Component Example

const container = new ContainerBuilder()
  .addTextDisplayComponents(
    new TextDisplayBuilder({
      content: "Your text here",
    }),
  )
  .addMediaGalleryComponents(
    new MediaGalleryBuilder().addItems(
      new MediaGalleryItemBuilder({
        description: "Image caption",
        media: { url: "https://example.com/image.png" },
      }),
    ),
  )
  .addSectionComponents(
    new SectionBuilder()
      .addTextDisplayComponents(
        new TextDisplayBuilder({ content: "Section title" }),
      )
      .setButtonAccessory(
        new ButtonBuilder()
          .setLabel("Click me")
          .setURL("https://example.com")
          .setStyle(ButtonStyle.Link),
      ),
  );

Error Handling

Graceful Error Recovery

The bot catches command errors and displays user-friendly messages:
try {
  bot.executeInteraction(interaction);
} catch (error) {
  const errEmbed = new EmbedBuilder()
    .setDescription(
      `<:warning:1204923305895665705>_ _ An error occured whilst trying to run this command; please try again later.`,
    )
    .setTimestamp();

  if (interaction.isRepliable()) {
    interaction.reply({ embeds: [errEmbed], ephemeral: true });
  }
}

Development

Scripts

# Development with hot reload
pnpm dev

# Production build
pnpm build

# Start production server
pnpm start

# Lint code
pnpm lint

Environment Variables

Required environment variables:
DISCORD_TOKEN=your_bot_token
DISCORD_HOME_GUILD_ID=your_guild_id

Dependencies

apps/client/package.json
{
  "dependencies": {
    "@discordx/importer": "^1.3.1",
    "@discordx/pagination": "^3.5.6",
    "@discordx/utilities": "^7.0.3",
    "@skyteam/database": "workspace:*",
    "discord.js": "^14.19.3",
    "discordx": "^11.12.4"
  }
}

Command Loading

Commands are automatically imported using discordx importer:
apps/client/src/index.ts
await importx(
  `${dirname(import.meta.url)}/{events,commands,guards}/**/*.{ts,js}`,
);
This automatically loads:
  • Commands: All files in commands/ subdirectories
  • Events: Custom event handlers
  • Guards: Permission and validation guards

Integration with SkyTeam

The bot integrates with:
  • @skyteam/database - User and airline data queries
  • @skyteam/api - Real-time flight and status updates
  • External APIs - ROBLOX user data, avatar lookups

Bot Permissions

Required Discord permissions:
  • Read Messages/View Channels
  • Send Messages
  • Embed Links
  • Attach Files
  • Use External Emojis
  • Add Reactions
  • Manage Messages (for moderation)
  • Read Message History

Deployment

Recommended deployment:
  • Hosting: Railway, Heroku, or VPS
  • Process Manager: PM2 or systemd
  • Monitoring: Error tracking and uptime monitoring
  • Logging: Structured logging for debugging

Build docs developers (and LLMs) love