-
Notifications
You must be signed in to change notification settings - Fork 0
02 ‐ Project Layout
Every file in the template exists for a reason, and most of them you will edit. This page walks the tree top to bottom and says what to do with each entry.
handler/ interaction manifests, generated + deployed by `dim`
commands/ slash commands (prod)
commands_dev/ slash commands (dev), different `name`
context_menu/ context menus (prod)
context_menu_dev/ context menus (dev)
src/
index.ts entry point : builds the Bot and registers everything
client.ts the discord.js Client and its intents
constantes.ts channel / guild ids, switched on DISCORD_BOT_DEV
activities.ts activities randomly displayed by the bot
interactions/ the code behind each interaction
commands/ context-menu/ modal/ selectmenu/ buttons/
modules/ toggleable features listening to Discord events
utils/
HandlersPath.ts loads handler json, dev/prod aware
RegisterInteractions.ts maps interaction names -> functions
RegisterModules.ts registers and enables the modules
rateLimiter.ts per-user rate limit helper
Only interactions/commands/ ships with a file. Create the other interaction folders
when you actually need them.
Three things happen here, in this order.
1. dotenv runs before the framework is imported. This is not cosmetic :
BotEnv reads process.env at import time, so a dotenv.config() placed after the
import would come too late.
import dotenv from "dotenv";
// Loaded before importing the framework: BotEnv reads process.env at import time.
dotenv.config({path: "./.env"});
import {Bot, BotConfig, Time} from "@spatulox/simplediscordbot"2. The BotConfig. Name of the bot and where each log level goes : console, a Discord
channel, or both. Flip discord: true on the levels you want mirrored into Discord.
const config: BotConfig = {
botName: "My Bot",
log: {
info: {channelId: CHANNELS.log, console: true, discord: false},
error: {channelId: CHANNELS.error, console: true, discord: false},
warn: {channelId: CHANNELS.error, console: true, discord: false},
debug: {channelId: CHANNELS.error, console: true, discord: false}
}
};
const bot = new Bot(client, config);new Bot(...) performs the login on its own, there is no client.login() to call.
The config also accepts defaultSimpleColor; see the
SimpleDiscordBot wiki.
3. Registration, inside ClientReady.
bot.client.once(Events.ClientReady, async () => {
try {
// Modules first : an interaction may need a module instance
await RegisterModules.create()
await RegisterInteraction.create()
new ModuleUI(client, CHANNELS.module_ui)
Bot.setRandomActivity(activities, Time.hour.HOUR_01.toMilliseconds())
} catch (error) {
Bot.log.error(`Failed to start the bot : ${error}`)
}
})Modules are registered before interactions, on purpose. A slash command handler is allowed to reach for a module instance through the
ModuleRegistry, and that only works if the module already exists.
A plain discord.js Client. The template enables a broad set of intents so the examples
work; trim it to what your bot actually needs.
export const client = new Client({ intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
...
],
partials: [Partials.Channel]
});
MessageContentandGuildMembersare privileged intents : enable them in the Discord Dev Portal too, or the login fails.ExampleModuleneedsMessageContent.
Two frozen objects, DEV_CHANNELS and PROD_CHANNELS, and BotEnv.dev picks one.
Add your own ids here rather than scattering literals across the code.
Discord regexes and the zero-width
SPACEconstant already exist inDiscordRegex, exported by@spatulox/simplediscordbot. Import it instead of redefining them.
A RandomBotActivity array, rotated by Bot.setRandomActivity(activities, interval).
The interval in index.ts uses Time.hour.HOUR_01.toMilliseconds().
export const activities: RandomBotActivity = [
{type: ActivityType.Playing, message: "with the Discord API"},
{type: ActivityType.Watching, message: "the server"}
];| File | Role |
|---|---|
HandlersPath.ts |
Handlers.load(category, name) reads the interaction json, dev or prod, with an in-memory cache and a typed whitelist |
RegisterInteractions.ts |
one private method per interaction type, all called by RegisterInteraction.create()
|
RegisterModules.ts |
register() every module, then enableAll()
|
rateLimiter.ts |
isUserRateLimited(interaction, limiter, ms), replies ephemerally and returns true so a command can early-return |
RegisterInteractions and RegisterModules share the same shape : a private constructor
plus a static async create(), so the async work is done before the object is handed
back.
Json files describing your slash commands and context menus to Discord. You do not write
them by hand. The dim CLI generates them and writes the Discord-assigned id back
into them after the first deploy. See
03 - Interactions.
tsconfig.json is deliberately strict : strict, noUnusedLocals, noUnusedParameters,
noUncheckedIndexedAccess, noFallthroughCasesInSwitch, module: nodenext.
Expect the compiler to complain about an unused import; that is the point.
.github/workflows/ci.yml runs on main and feat/multi-bot, on Node 22 and 24, and :
-
npm ci, thennpx tsc --noEmit, thennpm run build - parses every
handler/**/*.jsonand fails if one is invalid or has noname - checks that the entry point declared in
package.json#mainwas actually produced
.github/dependabot.yml and .github/workflows/audit-fix.yml keep the dependencies and
the audit clean on their own.
- 01 - Getting Started
- 02 - Project Layout
- 03 - Interactions
- 04 - Modules
- 05 - Dev vs Prod
- 06 - Multi Bot
- 07 - The Ecosystem
Ecosystem
-
SimpleDiscordBot :
Bot, managers - discord-module : modules, interactions
-
DiscordInteractionManager : the
dimCLI -
utils :
Time,Log, caches