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_here CLIENT_ID=your_client_id_here SERVER_ID=your_guild_id_here SPAM_DETECTION_CHANNEL_ID=your_spam_detection_channel_id_here ROLE_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";
export const ping = createSlashCommand({
data: {
name: "ping", // Must be lowercase
description: "Replies with Pong!",
options: [ // Optional: command options
{
name: "message",
description: "Optional message to include",
type: ApplicationCommandOptionType.String,
required: false,
}
]
},
execute: async (interaction) => {
await interaction.reply("Pong!");
},
});import { createUserContextMenuCommand } from "../commands/helpers.js";
export const userInfo = createUserContextMenuCommand({
data: {
name: "Get User Info", // Can contain spaces and capitals
},
execute: async (interaction) => {
const targetUser = interaction.targetUser;
await interaction.reply(`User: ${targetUser.tag}`);
},
});import { createMessageContextMenuCommand } from "../commands/helpers.js";
export const reportMessage = createMessageContextMenuCommand({
data: {
name: "Report to Moderators",
},
execute: async (interaction) => {
const targetMessage = interaction.targetMessage;
// Handle message reporting logic
await interaction.reply("Message reported!");
},
});Add your commands to src/commands/index.ts:
import { ping } from "./ping.js";
import { reportMessage } from "./reportMessage.js";
import type { Command } from "./types.js";
export const commands = new Map<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";
export const messageCreateEvent = createEvent(
{
name: Events.MessageCreate,
},
async (message) => {
if (message.author.bot) return;
// Handle message logic
console.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";
import type { DiscordEvent } from "./types.js";
export const events: 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