-
Notifications
You must be signed in to change notification settings - Fork 0
Development Guide
Welcome to the Master-Bot Developer Guide! This comprehensive manual details the architecture, coding standards, and step-by-step processes for extending Master-Bot with new slash commands, event listeners, background monitors, and full-stack features.
- Monorepo Architecture & Code Standards
- Building a New Slash Command
- Modular Option Submodules Pattern
- Standardized Rich Embeds
- Building an Event Listener
- Creating Features & Background Monitors
- Database & State Management
- Writing Unit Tests with Vitest
- Related Guides
Master-Bot is organized as a unified Turborepo workspace powered by pnpm:
Master-Bot/
βββ apps/
β βββ bot/ # Discord.js v14 & Sapphire Framework Bot Gateway
β βββ dashboard/ # Next.js 15 App Router & tRPC v11 Web Dashboard
βββ packages/
β βββ auth/ # Shared Discord OAuth configuration (NextAuth.js)
β βββ config/ # Shared ESLint and Tailwind tooling
β βββ db/ # Shared Prisma ORM client & ioredis-mock fallback
βββ tests/unit/ # Vitest monorepo testing suite (@helix-origin/vitest-suite)
βββ wiki/ # Complete project documentation
Before contributing code, ensure you adhere to our unified project conventions:
-
CamelCase File Naming: All TypeScript source and test files must use
camelCase.ts(e.g.youtubeAPI.ts,streamAlerts.ts,youtubeAlerts.test.ts). -
Zero
index.tsFiles inapps/botSubdirectories: The Sapphire Framework piece loader automatically scans directory trees for runnable pieces. Naming filesindex.tsinside subdirectories causes conflicts; useregistry.tsoroptionRegistry.tsfor modules. -
Submodules in
lib/: Complex command options, API helpers, and handlers must be modularized into discrete files undersrc/lib/<feature>/rather than stuffed into a monolithic command file. -
Typed Imports & Exports: Use explicit type imports (
import type { ... } from '...') to ensure clean compilation under TypeScript with zero type pollution.
flowchart TD
subgraph SapphireArchitecture [Sapphire Piece Architecture]
Command[commands/category/commandName.ts]
Listener[listeners/eventCategory/listenerName.ts]
Submodules[lib/feature/options/optionSubmodules.ts]
Embeds[lib/embeds/commands/featureEmbed.ts]
Session[lib/session/SessionManager.ts]
DB[(packages/db: Prisma ORM)]
end
Command --> Submodules
Command --> Embeds
Command --> Session
Listener --> Session
Session --> DB
Commands in Master-Bot are built on the Sapphire Framework and discord.js v14.
Create your command file under apps/bot/src/commands/<category>/<commandName>.ts:
- Music commands go to
apps/bot/src/commands/music/ - Moderation commands go to
apps/bot/src/commands/moderation/ - Fun commands go to
apps/bot/src/commands/fun/ - General/Utility commands go to
apps/bot/src/commands/other/
import { ApplyOptions } from '@sapphire/decorators';
import { Command } from '@sapphire/framework';
import type { ChatInputCommandInteraction } from 'discord.js';
@ApplyOptions<Command.Options>({
name: 'example',
description: 'An example slash command demonstrating standard architecture',
preconditions: ['isCommandDisabled']
})
export class ExampleCommand extends Command {
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(builder =>
builder
.setName(this.name)
.setDescription(this.description)
.addStringOption(option =>
option
.setName('message')
.setDescription('Message to echo')
.setRequired(false)
)
);
}
public override async chatInputRun(interaction: ChatInputCommandInteraction) {
const message = interaction.options.getString('message') || 'Hello World!';
return interaction.reply({
content: `Echo: **${message}**`,
ephemeral: true
});
}
}Note
Always include the isCommandDisabled precondition. This enables guild administrators to disable or enable individual slash commands from the dashboard or via configuration.
To comply with Discord's command limits and keep commands maintainable, we consolidate related subcommands into a single command with options, splitting individual handlers into discrete files in src/lib/<feature>/options/.
apps/bot/src/lib/custom/options/
βββ registry.ts # Central option registry and execution router
βββ types.ts # Option interfaces and context types
βββ firstOption.ts # First option implementation
βββ secondOption.ts # Second option implementation
In apps/bot/src/lib/custom/options/types.ts:
import type { ChatInputCommandInteraction } from 'discord.js';
export interface CustomOptionContext {
interaction: ChatInputCommandInteraction;
value?: string | null;
}
export interface CustomOptionHandler {
name: string;
description: string;
execute(context: CustomOptionContext): Promise<unknown>;
}In apps/bot/src/lib/custom/options/firstOption.ts:
import type { CustomOptionHandler, CustomOptionContext } from './types';
export const firstOption: CustomOptionHandler = {
name: 'first',
description: 'Executes the first operation',
async execute({ interaction, value }: CustomOptionContext) {
return interaction.reply({
content: `First option executed with value: ${value ?? 'None'}`,
ephemeral: true
});
}
};In apps/bot/src/lib/custom/options/registry.ts:
import type { CustomOptionHandler, CustomOptionContext } from './types';
import { firstOption } from './firstOption';
export const customOptions: Record<string, CustomOptionHandler> = {
first: firstOption
};
export async function executeCustomOption(
optionName: string,
context: CustomOptionContext
): Promise<unknown> {
const handler = customOptions[optionName];
if (!handler) {
return context.interaction.reply({
content: `:x: Unknown option \`${optionName}\`.`,
ephemeral: true
});
}
return handler.execute(context);
}All embeds should be generated through factory functions located in apps/bot/src/lib/embeds/ to preserve unified branding, colors, and timestamps across the bot.
import { EmbedBuilder } from 'discord.js';
import type { User } from 'discord.js';
export interface AlertEmbedOptions {
title: string;
description: string;
user?: User;
url?: string;
}
export function createAlertEmbed(options: AlertEmbedOptions): EmbedBuilder {
const embed = new EmbedBuilder()
.setColor('#5865F2') // Unified Discord Blurple branding
.setTitle(options.title)
.setDescription(options.description)
.setTimestamp();
if (options.url) embed.setURL(options.url);
if (options.user) {
embed.setFooter({
text: `Requested by ${options.user.username}`,
iconURL: options.user.displayAvatarURL()
});
}
return embed;
}Listeners react to Discord gateway events, interaction events, or internal Sapphire events.
Create your listener under apps/bot/src/listeners/<category>/<listenerName>.ts.
import { ApplyOptions } from '@sapphire/decorators';
import { Listener, Events } from '@sapphire/framework';
import type { GuildMember } from 'discord.js';
import Logger from '../../lib/logger';
@ApplyOptions<Listener.Options>({
event: Events.GuildMemberAdd
})
export class MemberJoinListener extends Listener<typeof Events.GuildMemberAdd> {
public override async run(member: GuildMember) {
Logger.info(`[MemberJoin] User ${member.user.tag} joined ${member.guild.name}`);
const welcomeConfig = member.client.session.guilds.get(member.guild.id)?.welcome;
if (!welcomeConfig?.enabled || !welcomeConfig.channelId) return;
const channel = member.guild.channels.cache.get(welcomeConfig.channelId);
if (channel && 'send' in channel) {
const formattedMessage = (welcomeConfig.message || 'Welcome {user} to {server}!')
.replace('{user}', `<@${member.id}>`)
.replace('{server}', member.guild.name)
.replace('{memberCount}', String(member.guild.memberCount));
await channel.send({ content: formattedMessage });
}
}
}Master-Bot features background pollers (such as YouTube and Twitch stream alerts, and reminder schedulers).
sequenceDiagram
autonumber
participant Engine as Scheduler / Loop
participant Session as SessionManager
participant API as External API / RSS
participant Discord as Discord Channels (Text/Forum)
Engine->>Session: Get active subscriptions
Session-->>Engine: Return target channels
Engine->>API: Fetch latest streams / uploads
API-->>Engine: Return metadata
alt New Live Broadcast or Video Upload
Engine->>Discord: Send alert (Text Msg or Forum Thread)
Engine->>Session: Update lastStreamId & status
end
-
Concurrency Control: Use an
isRunninglock boolean to prevent overlapping execution cycles if an API response is slow. - Quota & Rate-Limit Preservation: Utilize fast, free XML feeds (e.g. YouTube RSS feeds) for routine checks, saving API keys or OAuth tokens for enrichment when new items appear.
-
Forum Channel Routing: Always inspect
channel.type:- If
ChannelType.GuildForum, create a thread withforum.threads.create({ name, message }). - If standard
TextChannel, send directly viachannel.send({ content, embeds }).
- If
-
Lifecycle Hooks: Export
startMonitor(client)andstopMonitor()functions so processes are gracefully cleaned up during shutdown.
Master-Bot uses a dual database and caching tier:
-
DB_URI: Automatically resolves connection strings. Defaults to zero-ops SQLite (file:/data/db.sqlite) with production scaling to PostgreSQL (postgresql://...). -
Prisma Client: Shared via
@master-bot/db. Runpnpm db:generateto regenerate types when schemas change. -
SessionManager(client.session): Provides instantaneous in-memory caching and Redis synchronization (ioredis-mockor external Redis).
// Accessing database models via Prisma:
import prisma from '@master-bot/db';
// Accessing live session state:
const guildData = client.session.guilds.get(guildId);Master-Bot uses @helix-origin/vitest-suite to provide mocks for Discord clients, interactions, and Redis.
Create tests/unit/<featureName>.test.ts:
import { describe, it, expect, beforeEach } from 'vitest';
import { createMockClient } from '@helix-origin/vitest-suite';
describe('Custom Feature Suite', () => {
let mockClient: ReturnType<typeof createMockClient>;
beforeEach(() => {
mockClient = createMockClient();
});
it('should perform expected validation', () => {
expect(mockClient).toBeDefined();
});
});Always run the complete quality pipeline before submitting a pull request:
# 1. Run unit test suite
pnpm test
# 2. Type-check all workspace packages
pnpm type-check
# 3. Verify linting and workspace boundary rules
pnpm lint- Home β Return to wiki main page
- Architecture β Deep dive into system architecture and data flows
- Commands Reference β Full breakdown of existing slash commands
- Stream Alerts β YouTube and Twitch notification configuration
- Deployment β Production self-hosting and deployment guide
Home β’ Documentation Index β’ GitHub Repository
π Home β’ ποΈ Architecture β’ βοΈ Configuration β’ π΅ Music β’ π» Dashboard β’ βοΈ Deployment β’ β FAQ
Master-Bot Documentation β’ Licensed under MIT β’ Maintained by galnir and the Master-Bot community
Master-Bot β Unified Discord music, moderation & utility bot with embedded Next.js 15 Web Dashboard, dual PostgreSQL/SQLite fallbacks, and embedded Lavalink v4 Audio.
- π Home β Project overview & quick start
- β‘οΈ Getting Started β Prerequisites, installation & startup
-
βοΈ Configuration β Complete
.envreference & fallbacks
- ποΈ Architecture β Unified single-process runtime & storage
- ποΈ Database & Fallbacks β Dual PostgreSQL & SQLite
-
β‘οΈ Cache & State β External Redis &
ioredis-mock -
π Commands Reference β Complete catalog of slash commands &
/setoptions
- π΅ Music & Audio β Embedded Lavalink v4, filters & playlists
- π‘οΈ Moderation β Ban, timeout, slowmode & audit logging
-
π« Support Tickets β Thread tickets &
.txttranscripts - π Welcome & Temp Channels β Greetings & dynamic voice hubs
- β° Reminders & Stream Alerts β Scheduled reminders, YouTube & Twitch alerts
- π» Web Dashboard β Next.js 15 App Router, tRPC v11 & 9 Studios
- π Deployment Guide β Docker, Low-Cost VPS & Self-Hosting
- π οΈ Developer Guide β Creating commands, listeners & features
-
π§ͺ Testing Suite β
@helix-origin/vitest-suitetesting toolkit - π‘οΈ Privacy Policy β Data collection, retention & deletion rights
- π Terms of Service β Service terms, permitted use & liability
- π Security Policy β Vulnerability reporting, advisories & hardening
- β FAQ & Troubleshooting β Common questions & solutions
- π€ Contributing β Contributor guide & commit standards
- π¦ Standalone Audio: HELIX-Origin/Lavalink-Server
- π§ͺ Vitest Suite: HELIX-Origin/vitest-suite
- π€ GitHub Repository: galnir/Master-Bot