Chapter 5 · Own build5•1
Discord bot
A Discord bot for one community of about 2,000 members, built three times. V1 ran games, a points economy and roles; V3 is the rework, a small dictionary bot built to get the structure right.
What changed most is the shape of a command. In V1 every command was handed the same eight arguments, used or not; in V3 a command is one object with a name, a run and its help, and the router hands each the message, parsed once.
It was also Kevin's first GitHub Actions workflow: pushing a version tag rebuilds and restarts the bot on a self-hosted runner. Retired now, kept here as it was.
- Entrant
- Kevin, own build
- Period
- 2017–2022
- Status
- Retired
Wiring key
Discord bot5•2
How a message runs through each build of the bot, stage for stage.
Three diagrams side by side, one per build, each following a Discord message from top to bottom. V1, April 2018: the message goes into one entry file, which does everything in one housing: it checks which channel the message is in, picks a command from three lists, runs it with the same eight arguments as every other command, and also gives points, reacts and tells dad jokes; after every message it writes the members file back to disk. V2, late 2018: the work is split into a message handler, a command handler that reads each server's prefix from the database, and a function handler; commands are moving to run with only the message, 12 of 17 done; members move to MongoDB, one database per server. But the lead from Discord into the handlers is broken: the entry file never wires them in, 133 of its 162 lines are commented out. V3, 2021 to 2022: index.js parses the message once into details, the dictionary module matches the prefix d!, the matching command of three runs with the message and its details and fetches its own answer, and the answer goes back by direct message while the command is deleted from the channel. Nothing is stored.
A command is the same shape every time, so the router never needs to know one. In V1 that shape was a run function with eight pins, all of them wired whether the command used them or not.
V1, every command: run(Discord, client, message, words, currencyMembers, axios, cleverbot, he)
| Command | Discord | client | message | words | currencyMembers | axios | cleverbot | he |
|---|---|---|---|---|---|---|---|---|
| ask | ||||||||
| color | ||||||||
| currency | ||||||||
| gamble | ||||||||
| give | ||||||||
| hangman | ||||||||
| highlow | ||||||||
| iam | ||||||||
| iamnot | ||||||||
| leaderboard | ||||||||
| quiz | ||||||||
| slots | ||||||||
| snowball | ||||||||
| stats | ||||||||
| time | ||||||||
| timeout | ||||||||
| toptalkers | ||||||||
| Used by | 11 | 1 | 17 | 9 | 9 | 2 | 1 | 1 |
used handed over, unused51 of 136 pins used
V3, every command: { name, run(msg, details), help }
// index.js, shortened
modules.forEach(mod => mod.commands.forEach(command => {
if (mod.prefix + command.name !== details.firstWord) return;
command.run(msg, details);
}));- details is parsed once, before any command runs:
args,firstWord,mentionOrAuthor,isOwner,hasMentions. - A command imports what it needs itself, axios or the HTML parser, instead of being handed everything.
- A new command is one file, and one line in its module’s list.
Kevin’s first GitHub Actions workflow. The result: pushing a version tag rebuilds and restarts the bot on a self-hosted runner.
Pushing a tag starting with v starts the workflow on a self-hosted runner. It stops the old container, clones the repo, writes the bot token from the repository's secrets into a .env file, builds the Docker image tagged with the version, and runs it.
Notes
- Every count on these pages is read off the public repos of the three builds.
- Source: V3, on GitHub