Skip to content

Repository files navigation

NerdBot: Discord bot for music, RPG dice and utilities

A Discord bot that plays music, rolls RPG dice and answers for your server. The entire brand — name, colors, logo — comes out of a single file.

License Node Stack Commands

Swap the brand · Commands · Run it · Under the hood · License


What it is

A Discord bot in Node.js, built on discord.js v14 and Lavalink v4, that does three things: plays music (YouTube, SoundCloud, Spotify), rolls RPG dice in tabletop notation (2d6+3, advantage, initiative) and reports latency and server state.

What sets it apart from any other music bot is that it was built to be handed to different clients. Changing the visual identity doesn't mean hunting for strings inside commands: it's one file, or not even that — just variables in .env.


Swap the brand

The same installation serves different brands. No fork per client.

# .env — and that's all
BRAND_NAME=ACME Bot
BRAND_URL=https://acme.com
BRAND_COLOR=#B8860B
without .env                    with the .env above
──────────────────              ──────────────────
NerdBot                         ACME Bot
#7C3AED  (NerdResolve violet)   #B8860B  (ACME gold)

What changes: the color and footer of every embed, the /help and /status titles, the /roll header and the identifier sent to the GIF API.

The four colors

BRAND is the identity and carries most of the replies. The other three are semantic — they keep their meaning under any brand, which is why they rarely change from client to client.

Token Default Where it shows up
BRAND_COLOR #7C3AED Ordinary replies: queue, help, rolls, initiative
BRAND_COLOR_SUCCESS #22C55E Now playing, session ended, /status
BRAND_COLOR_WARNING #F59E0B Warnings: no active session, nothing queued
BRAND_COLOR_ERROR #EF4444 Command and playback failures

An invalid color doesn't take the bot down: it warns in the log and falls back to the default.

The logo

Replace the two files, keeping the names — or point somewhere else with BRAND_LOGO and BRAND_BANNER.

File What for
assets/logo.webp The mark, used as the embed thumbnail
assets/banner.webp The wide artwork in this README

The vector sources live in docs/brand/. npm run check fails if a referenced file doesn't exist.

Rather edit than configure? src/config/brand.js is the first file a fork opens: it names and colors the entire product, and carries the step-by-step at the top. Once you've swapped it, grep -ri nerdbot src/ should only find that file.


Commands

Every one of them exists as a slash command (/play) and as a prefix command (!play).

Music

Command What it does
/play Plays or queues a track from YouTube, SoundCloud or Spotify
/pause · /resume Pauses and resumes
/skip Skips the current track
/stop Stops and clears the queue
/queue Shows the queue, paginated 10 at a time
/disconnect Leaves the voice channel

RPG dice

Command What it does
/roll Rolls in tabletop notation: 2d6+3, 4d6kh3, d20
/initiative Opens an initiative session, or rolls yours
/end Ends the session and shows the turn order

Crits and critical failures get a GIF, if GIF_API_KEY is configured.

Utilities

Command What it does
/ping Latency
/status Uptime, memory and state
/help Lists everything, assembled from the loaded commands

Run it

You need your own Discord application — it's free, at discord.com/developers.

git clone https://github.com/nerdresolve/NerdBot.git
cd NerdBot
npm install
cp .env.example .env     # fill in DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID

With Docker (Linux, macOS, Windows)

Lavalink comes up in a container, with no Java to install and no .jar to download.

docker compose up -d     # Lavalink on 127.0.0.1:2333
npm run deploy           # registers the commands on your server
npm start

Or in one go, which also installs dependencies and waits for Lavalink to become healthy:

./start.sh               # Linux and macOS
start.bat                # Windows

Without Docker

Download Lavalink v4 and the YouTube plugin. On Windows, start.bat looks in C:\Lavalink; point it elsewhere with set LAVALINK_HOME=D:\Lavalink. On any system:

java -jar Lavalink.jar

Lavalink listens only on 127.0.0.1 under Compose: it should never be exposed to the internet.

Validate

npm run check

Three gates, all offline and token-free: syntax across all of src/, the brand (valid colors, images present) and a sweep that fails the build if there's a credential in a tracked file. It's the same command CI runs.


Under the hood

src/
  config/
    brand.js        the brand: name, colors, files. What a fork edits
    constants.js    dice and queue limits; re-exports the brand colors
    environment.js  reads and validates .env, reporting what stayed off
  commands/         one file per command, loaded by scanning the folder
  events/           Discord handlers: interaction, message, voice, error
  handlers/         discover and register commands and events
  music/            Lavalink, the queue and Spotify link resolution
  dice/             initiative session and roll presentation
  utils/            embeds, logging, dice parser, GIFs
lavalink/           application.yml, mounted into the container
docs/brand/         the brand vectors

A new command is a new file in src/commands/: the commandHandler scans the folder and registers whatever it finds, and /help builds itself from what was loaded. There's no central list to keep updated.

Decisions worth recording

Decision Why
The brand in one file, overridden by the environment The file serves the fork; the environment serves operations. With both layers, the same image serves different clients without a rebuild — and without anyone editing a command to change a color.
Semantic colors, not decorative ones SUCCESS/WARNING/ERROR mean the same thing under any brand. Only BRAND changes per client, so rebranding means supplying one color, not four.
Lavalink via Docker The previous version required Java and a .jar in C:\Lavalink: it didn't run outside Windows. Compose pins the version and the plugin, and binds the port to 127.0.0.1.
Secret sweep in check The repository is public. A check that only runs during human review fails the first time someone is in a hurry; this one fails CI.
The GIF key is masked in the log Klipy's API takes the key in the URL path. Without the masking, a network failure would print it in full to the log.

Configuration

No .env ever enters the repository; .env.example is the template, with every variable empty and commented.

Group Required Without it
DISCORD_* Yes The bot doesn't start
BRAND_* No Uses the NerdResolve identity
LAVALINK_* For music The music commands stay off, everything else works
SPOTIFY_* No Spotify links don't resolve; YouTube and SoundCloud carry on
GIF_API_KEY No /roll replies without a GIF

On startup, the bot says in the log what stayed off and why.


Known gaps

  • No automated tests. npm run check validates syntax, brand and secrets, but there's no unit test for the dice parser — which is the part with real rules (4d6kh3, advantage, exploding dice) and the one that would most deserve one.
  • The queue lives in memory. Restarting the bot empties the queue and ends any open initiative sessions.
  • A single Lavalink node. There's no failover: if the node goes down, the music stops until healthCheck reconnects.
  • start.bat doesn't download Lavalink. It checks and warns; anyone not using Docker downloads the .jar and the plugin by hand.

License

MIT. See LICENSE. Use it, modify it, redistribute it, including commercially — including with your brand in place of ours, which is exactly what the project was built to allow.

The NerdResolve identity (name, glasses, violet) belongs to NerdResolve: it ships as the default so the bot has a face when cloned, not so it can be used as if it were yours. Swap it out — it's one command.


NerdBot, built by NerdResolve

About

White-label Discord bot: music, RPG dice and server utilities. The entire brand comes from a single file.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages