A modular Discord bot built on discord.js v14. The core is a thin host that discovers, loads, and wires up self-contained feature modules — almost all functionality lives in modules/, and adding a feature is just dropping a new file there.
The bot ships with music playback, text-to-speech, a Vietnamese-language Werewolf/Mafia game with AI-driven discussion, a Gemini chat command, plus utility commands (clear, help, invite, reload, logger).
- Modular architecture — every feature is a self-contained module under
modules/. No core changes required to add one. - Slash and legacy prefix commands — both interaction styles are dispatched from the same module definition.
- Per-module npm dependencies — modules declare their own
package.json(ormeta.npmDependencies) and are installed automatically on boot. - Hot reload — the
reloadmodule exposes/reloadand/deployadmin commands for development without restarting. - Optional persistence — sqlite (default) or MongoDB behind a single interface.
- Music — YouTube, Spotify, SoundCloud, Apple Music, Vimeo, Reverbnation, and attachment playback via
@discordjs/voice+discord-player. - Werewolf/Mafia game — full game loop with roles (Werewolf, Seer, Bodyguard, Witch, Hunter, Cursed Werewolf, Villager), AI players, and Vietnamese UI.
- Text-to-speech — voice channel TTS with per-channel queueing.
- Gemini chat — per-user conversation history backed by Google Generative AI.
- Node.js 18+ (Node 20 LTS recommended)
- A Discord application + bot token — see the Discord developer portal
- FFmpeg available on
PATHfor music playback and TTS - Optional: Google Generative AI API key for the Gemini module
git clone <repo-url> DCBot
cd DCBot
npm installThat's it for the root. Module-specific dependencies are installed automatically by the DependencyManager on first boot — you do not run npm install inside a module directory by hand.
The bot will not start without config/config.json. If the file is missing, index.js creates a default config/config.json and config/modules.json with placeholder values on first run. Edit config/config.json and set at minimum bot.token, bot.clientId, and bot.ownerIds before restarting.
Loaded at startup. A working example lives at config/config.example.json.
| Key | Purpose |
|---|---|
bot.token |
Discord bot token. Required. |
bot.clientId |
Bot application client ID. |
bot.ownerIds |
User IDs granted owner-only command access. |
bot.prefix |
Legacy prefix command prefix (default: !). |
commands.registerSlashCommands |
Whether to register slash commands on boot. |
commands.enableLegacyCommands |
Whether legacy !command style works. |
commands.cooldown |
Per-user command cooldown in seconds. |
development.testGuildId |
If set, slash commands register to this guild only — instant dev updates, no global cache delay. |
database.enabled |
Enable persistence (off by default). Guild-specific prefixes and getGuildSettings/setModuleData require this. |
database.type |
sqlite (default, ./data/database.sqlite) or mongodb. |
gemini.apiKey |
Google Generative AI key. Can also come from the GEMINI_API_KEY env var. |
music.spotify.clientId / clientSecret |
Required for Spotify playback. URL detection and search work without them; streaming does not. |
permissions.adminRoles / moderationRoles / djRoles |
Role names resolved per guild. |
Controls module enablement. A module is enabled unless its name appears in the top-level disabled array:
{
"disabled": ["werewolf"]
}Modules can also carry per-module settings and permissions blocks here — see an enabled module's source for the shape it expects.
Security: anything placed in
modules/is auto-required at boot and executes immediately. Treat the directory as trusted code only. Native dependency installs are blocked from running scripts unless the package is on the explicit allowlist insrc/core/dependencyManager.js— add new native packages there before depending on them.
npm start # Run the bot
npm test # Run the test suite (node --test)
npm run icons:upload # Upload the music module's UI icons to DiscordLogs go to ./logs/<YYYY-MM-DD>.log and ./logs/<YYYY-MM-DD>-error.log by default. Set logging.fileOutput: false to disable.
Discord caches global slash commands and updates can take up to an hour to propagate. For instant dev updates, either:
- Set
development.testGuildIdinconfig/config.jsonto your dev guild's ID, or - Run
/deployin the target guild (requires Administrator).
Both register directly to the guild and bypass the global cache.
Every module is either modules/<name>.js (single file) or modules/<name>/index.js (directory). Each exports:
module.exports = {
meta: {
name, type, version, description,
dependencies: [], // other modules that must load first
npmDependencies: { ... } // for single-file modules
},
async init(client, bot) { ... },
async shutdown() { ... },
commands: [ /* see Module contract below */ ],
events: [ { name, once?, execute(...args, bot) } ]
};| Module | What it does |
|---|---|
clear |
Bulk message deletion. |
gemini |
Google Generative AI chat with per-user conversation history. Requires gemini.apiKey. |
help |
/help command — lists available commands. |
invite |
/invite command — generates an OAuth invite link. |
logger |
Per-guild event logging to a configured channel. |
reload |
/reload [module] and /deploy — admin commands for hot-reloading modules and re-registering slash commands to the current guild. |
music |
Full voice playback. YouTube, Spotify (with creds), SoundCloud, Apple Music, Vimeo, Reverbnation, Attachment. Use /providers to inspect what's loaded. Has its own package.json/node_modules. |
tts |
Voice channel text-to-speech with per-channel queues. |
werewolf |
Vietnamese-language Werewolf/Mafia game. Roles in roles/, button/select-menu/command handlers in handlers/, AI players in ai/. |
Create a new file or directory under modules/. Give it a meta.name, declare any npm deps, implement init, and add commands/events. It is picked up automatically on next start (no core changes needed) unless its name is in modules.json's disabled array.
- Connect database (if enabled) —
DatabaseManager - Check/install module npm dependencies —
DependencyManager - Load modules (dependency-ordered) —
ModuleLoader - Register commands from modules —
CommandHandler - Register events from modules —
EventHandler - Wire core Discord events, then
login()
bot.js— owns the discord.jsClient, holds references to all managers, sets presence, handles graceful shutdown.moduleLoader.js— discovers modules, topologically sorts bymeta.dependencies(with cycle detection), loads each via a custom per-modulerequirethat resolves npm packages from the module's ownnode_modulesfirst, then the sharedmodules/node_modules, then the root.commandHandler.js— collects commands from all modules, registers slash commands with Discord (todevelopment.testGuildIdif set, otherwise globally), and routesinteractionCreateandmessageCreateto the right handler. Enforces cooldowns and role/permission checks.eventHandler.js— registers moduleeventson the client. Always appends thebotinstance as the last argument to each handler.dependencyManager.js— runsnpm installfor directory modules with apackage.jsonand missing/stalenode_modules, and aggregates single-file modules'meta.npmDependenciesintomodules/package.jsonfor a shared install. Runs with--ignore-scripts; native deps must be on the explicit allowlist (update the file when adding new native modules).databaseManager.js— optional persistence layer; supports sqlite (default) and MongoDB behind one interface (getGuildSettings,setGuildSettings,getModuleData,setModuleData).
A command declares both interaction styles and is dispatched by CommandHandler:
{
name, description,
slash: true, data: { /* Discord application command JSON */ },
async execute(interaction, bot) { ... },
legacy: true, async legacyExecute(message, args, bot) { ... },
cooldown,
requiredPermissions,
permissions: ['@everyone' | roleName | roleId]
}- Command cooldowns are in-memory (
commandHandler.js) and reset on restart. Not a security boundary — this is intentional. - Global slash command cache delay. When
development.testGuildIdis unset, slash commands register globally and Discord's cache can take up to ~1 hour to reflect new/updated commands (the API call succeeds, but clients see stale lists until the cache refreshes). SettestGuildIdor use/deployfor instant visibility. - Database is disabled by default. Guild-specific prefixes and
getGuildSettings/setModuleDataonly work when a database is enabled.
- Fork and create a feature branch.
- Make your changes. Match existing module patterns — read one or two neighboring modules before adding a new one.
- Run
npm testto make sure the suite still passes. - Open a pull request with a clear description of what changed and why.
When adding a native dependency, update the allowlist in src/core/dependencyManager.js. When adding a new module, prefer a directory module with its own package.json if it pulls in heavy npm deps — the per-module install path is wired up for exactly this case.
GNU GPLv3. See LICENSE for the full text.