A modular and extendable Discord bot built with Node.js, designed for server management, moderation, and fun interactions. Customize and enhance it with ease to fit your community's vibe.
- 🛠️ Server Management: Automate tasks and keep your server organized.
- 🛡️ Moderation Tools: Keep your community safe with robust moderation commands.
- 🎉 Fun Commands: Engage your members with interactive and entertaining features.
- 🔌 Modular Architecture: Easily add or remove features to tailor the bot to your needs.
- 🤖 Natural AI Channel Participant: Optional channel-aware AI (gated behind
AI_NATURAL_MODE=1) that observes messages, debounces bursts, and decides when to engage. Acts via Discord API tools (send, reply, react, inspect).
- Node.js (>= 22.3.0)
- npm
- A Discord Application with a bot token
-
Clone the repository:
git clone https://github.com/af-t/disco.git cd disco -
Install dependencies:
npm install
-
Configure the bot:
Copy
.env.exampleto.envand fill in your values:cp .env.example .env
Environment variables
Variable Required Default Description DISCORD_TOKENYes — Your Discord bot token STORE_SERVER_URLNo http://localhost:3000URL of the internal store server (bot runs in memory-only mode if unreachable) STORE_SERVER_PORTNo 3000Port for the store server OPENROUTER_API_KEYNo — OpenRouter API key (for AI commands) OPENROUTER_MODELNo google/gemini-2.0-flash-001AI model to use for the agent AI_NATURAL_MODENo 01enables the channel-participant AI runtime;0disables.ai/.openrouter/.chatentirelyAI_DEBOUNCE_MSNo 4000Debounce window for normal messages before flushing to the AI AI_FORCE_DEBOUNCE_MSNo 500Debounce when the bot is directly addressed (mention / reply / DM) AI_CHANNEL_COOLDOWN_MSNo 20000Post-response cooldown per channel before the bot may speak again AI_DAILY_LIMITNo 500Per-guild LLM call cap per UTC day; mentions still bypass AI_BUFFER_SIZENo 30Rolling buffer size per channel (messages retained as context) AI_COMPACT_THRESHOLDNo 100Auto-compact agent.messagesonce length exceeds thisAI_FETCH_HISTORY_MAXNo 50Hard cap for the discord_fetch_historytoolSPOTIFY_CLIENT_IDNo — Spotify API client ID (for music downloads) SPOTIFY_CLIENT_SECRETNo — Spotify API client secret TAVILY_API_KEYNo — Tavily API key (for web-search tool in AI agents) STORE_DATA_PATHNo ./storage/dbServer-side disk path for the store database OPENROUTER_ORDERNo — Comma-separated provider priority order OPENROUTER_ONLYNo — Restrict inference to specific providers only OPENROUTER_MAX_COMPLETION_TOKENSNo model default Max output tokens per agent turn OPENROUTER_MAX_TURNSNo 120Per-loop turn cap for pooled agent processes OPENROUTER_EMBEDDING_MODELNo openai/text-embedding-3-smallEmbedding model for semantic memory recall AI_MAX_AGENTSNo 16Max concurrent forked agent child processes AI_AGENT_IDLE_MSNo 300000Evict an idle agent child after this many ms AI_AGENT_RESPAWN_COOLDOWN_MSNo 30000Cooldown per agent key after a startup crash before respawn DISCORD_GATEWAY_URLNo wss://gateway.discord.ggDiscord WebSocket gateway URL (advanced) DISCORD_API_BASENo https://discord.com/api/v10Discord REST API base URL (advanced) DISCORD_INTENTSNo 13 essential intents combined Comma-separated intent names (e.g., GUILDS,GUILD_MEMBERS,GUILD_MESSAGES). See lib/intents.js for all optionsDISCORD_RECONNECT_DELAYNo 5000Milliseconds between reconnect attempts DISCORD_RECONNECT_LIMITNo 5Max reconnect attempts before giving up DISCORD_MAX_RETRIESNo 3Max API request retries -
Start the bot:
The bot and its persistent store run as two separate processes. Start them in order:
# Terminal 1 — store server (disk persistence) npm run serve # Terminal 2 — bot npm start
The bot will start without the store server (memory-only mode), but persisted state (mod cases, log channel config, etc.) will be lost on restart.
Production (PM2):
ecosystem.config.cjsmanages both processes together:npm run pm2:start # start both disco-store and disco-bot npm run pm2:logs # tail combined logs npm run pm2:status # inspect process state npm run pm2:restart # rolling restart npm run pm2:stop # stop both
When AI_NATURAL_MODE=1, the bot acts as a normal channel member: it observes every message, applies a heuristic prefilter (direct address — mention / reply / DM — bypasses cooldown and budget), and lets the LLM decide whether to respond or stay silent. Actions are taken through 10 Discord tools (discord_send, discord_reply, discord_react, and seven inspect tools).
.ai-mute on | off | status— admins withMANAGE_CHANNELScan silence the bot in a specific channel..ai/.openrouter/.chat— direct invocation; the bot is forced to respond and keeps per-user history (session:openrouter:{guild}:{user}, 2h TTL).- Image attachments are sent inline as multimodal blocks; non-image attachments are saved under
workspaces/channel-{id}/orworkspaces/command-{guild}-{user}/and exposed to the LLM via the built-inReadtool. Workspace directories are cleaned up automatically when the matching store key expires. - Persistent memory is scoped per guild (
workspaces/memory/guild-{id}, shared by all channels in the guild; DMs usedm-{channel},.aisessions usecommand-{guild}-{user}) and survives workspace cleanup. The agent recalls memories semantically via the SDK'sRecallMemorytool — embeddings-ranked when the OpenRouter API is reachable (model set byOPENROUTER_EMBEDDING_MODEL), with a lexical fallback otherwise.
When AI_NATURAL_MODE=0 (default), the .ai / .openrouter / .chat commands return a disabled message — there is no parallel legacy path.
The bot's modular structure allows you to easily add, remove, or modify commands and features. Explore the src directory to see how commands are implemented and create your own to extend the bot's functionality.
Contributions are welcome! Feel free to fork the repository, make changes, and submit a pull request. Please ensure your code follows the project's coding standards and includes appropriate tests.
This project is licensed under the MIT License.