A TypeScript Discord bot built with Discord.js v14, designed for server moderation with a clean, type-safe architecture.
- Slash Commands: Traditional
/commandstyle interactions - Context Menu Commands: Right-click context menus for users and messages
- Event-Driven Architecture: Modular event handling system
- Type-Safe: Full TypeScript support with discriminated unions
- Environment Configuration: Secure configuration management
- Command Helpers: Easy-to-use helpers for creating different command types
- Node.js 22.20.0+
- pnpm (recommended) or npm
- A Discord bot token
Clone the repository
git clone <repository-url>cd moderation-tool
Install dependencies
pnpm install
Configure environment
cp .env.local.example .env.local
Edit
.env.localwith your bot credentials:DISCORD_TOKEN=your_bot_token_hereCLIENT_ID=your_client_id_hereSERVER_ID=your_guild_id_hereSPAM_DETECTION_CHANNEL_ID=your_spam_detection_channel_id_hereROLE_REGULAR_ID=your_regular_role_id_here
Build and run
# Development (with hot reload) pnpm dev # Production build pnpm build:ci pnpm start:ci
Use createSlashCommand helper for traditional /command interactions:
import{createSlashCommand}from"../commands/helpers.js";exportconstping=createSlashCommand({data: {name: "ping",// Must be lowercasedescription: "Replies with Pong!",options: [// Optional: command options{name: "message",description: "Optional message to include",type: ApplicationCommandOptionType.String,required: false,}]},execute: async(interaction)=>{awaitinteraction.reply("Pong!");},});import{createUserContextMenuCommand}from"../commands/helpers.js";exportconstuserInfo=createUserContextMenuCommand({data: {name: "Get User Info",// Can contain spaces and capitals},execute: async(interaction)=>{consttargetUser=interaction.targetUser;awaitinteraction.reply(`User: ${targetUser.tag}`);},});import{createMessageContextMenuCommand}from"../commands/helpers.js";exportconstreportMessage=createMessageContextMenuCommand({data: {name: "Report to Moderators",},execute: async(interaction)=>{consttargetMessage=interaction.targetMessage;// Handle message reporting logicawaitinteraction.reply("Message reported!");},});Add your commands to src/commands/index.ts:
import{ping}from"./ping.js";import{reportMessage}from"./reportMessage.js";importtype{Command}from"./types.js";exportconstcommands=newMap<string,Command>([ping,reportMessage].flat().map((command)=>[command.data.name,command]));Events are handled automatically through the event system. Create new events using the createEvent helper:
import{Events}from"discord.js";import{createEvent}from"../events/helpers.js";exportconstmessageCreateEvent=createEvent({name: Events.MessageCreate,},async(message)=>{if(message.author.bot)return;// Handle message logicconsole.log(`Message from ${message.author.tag}: ${message.content}`);});Add your events to src/events/index.ts:
import{messageCreateEvent}from"./message-create/index.js";import{readyEvent}from"./ready/index.js";importtype{DiscordEvent}from"./types.js";exportconstevents: DiscordEvent[]=[readyEvent,messageCreateEvent];The bot uses a centralized configuration system in src/env.ts. Environment variables are validated and logged on startup.
DISCORD_TOKEN: Your Discord bot tokenCLIENT_ID: Your Discord application client IDSERVER_ID: Guild ID (Server ID) for guild-specific command registration
pnpm dev- Start development server with hot reloadpnpm build:ci- Build for productionpnpm start:ci- Run production buildpnpm typecheck- Run TypeScript type checkingpnpm lint- Lint code with Biomepnpm format- Format code with Biome
This project uses:
- Biome for linting and formatting
- TypeScript for type safety
- Husky + lint-staged for pre-commit hooks
- Commands: Create in
src/commands/and register insrc/commands/index.ts - Events: Create in
src/events/and register insrc/events/index.ts - Utilities: Add to
src/utils/ - Configuration: Update
src/env.tsfor new environment variables
This project includes automated deployment using GitHub Actions and Docker with direct SSH deployment to your VPS.
- A VPS with Docker and Docker Compose installed
- SSH access to your VPS
- GitHub repository with Actions enabled
- Discord bot credentials
- Set up repository secrets
- Go to Settings → Secrets and variables → Actions
- Add the following secrets:
VPS_HOST: Your VPS IP address or hostnameVPS_USER: SSH username for your VPSVPS_SSH_KEY: Private SSH key for authenticationDISCORD_TOKEN: Your Discord bot tokenCLIENT_ID: Your Discord application client IDSERVER_ID: Your Discord server (guild) ID
The CI/CD pipeline handles all the setup automatically. The first deployment will:
- Create the
moderation-tool-botdirectory on your VPS - Clone the repository and set up the environment
- Subsequent deployments will simply pull the latest changes
The GitHub Actions workflow will automatically:
- Build and test on every push/PR to main branch
- SSH directly to your VPS
- Create project directory if it doesn't exist
- Clone repository on first deployment
- Pull latest code changes on subsequent deployments
- Build Docker image on the VPS
- Deploy using Docker Compose with production profile
- Verify deployment success and show container status
# View logs
docker compose logs -f
# Check container status
docker compose ps
# Restart the bot
docker compose restart moderation-tool-bot
# Stop the bot
docker compose down- Fork the repository
- Create a feature branch
- Make your changes
- Run tests and linting
- Submit a pull request
MIT