- Install Node.js (v16.9+), npm (v8+) and Postgres
- Install GTK2 and libjpeg-turbo by following this guide
- Install Redis
- Set up a psql user and DB schema for the bot
- Run
npm i
to install dependencies - Run
npx prisma migrate reset
to initialize the DB schema - Copy
.env.template
to.env
and enter all values - See CLI for further commands
Commands, events and context menus have template files in their respective folders called Template.ts
, use these to create new commands and events.
In order for a command or event to be initialized on startup, add it to the array of the index.ts
file in the respective folder.
At the moment, src/bot.ts
only sets guild-specific commands as these update pretty quickly (global commands take up to an hour to propagate).
The regular Discord client doesn't update the locally saved slash commands when the bot restarts, so it's sometimes necessary to reload the Discord app with Ctrl+R
If that still didn't work, or when you just want to remove a command or change its arguments, it's sometimes also necessary to delete them from Discord servers entirely.
The bot inserts a fresh copy of all commands at next startup. To clear all global commands and guild commands, use npm run clearCommands
There are two types of context menus; message and user. This dictates where they will be situated in the Discord client and what properties are passed.
Use int.isUserContextMenu()
and int.isMessageContextMenu()
to tell TS what type of context menu it's dealing with, then you will be able to access the int.targetMessage
or int.targetUser
and int.targetMember
props.
Other than how the commands are sent and that a user can't pass any arguments, context commands are pretty much the same as slash commands.
To create a new context menu, use the Template.ts in src/context/
to create a sub-class of src/CtxMenu.ts
Make sure the file and class name are changed, and the meta object in the constructor is filled out.
To specify who can use the context command, use the memberPerms
meta prop.
Lastly, as with slash commands, add the class to the array in src/context/index.ts
Install these VS Code extensions for code auto-fix on save and special text highlighting:
dbaeumer.vscode-eslint
fabiospampinato.vscode-highlight
A mixture of Prisma with a Postgres database, and Redis is used.
All database utils can be found in /src/database
, the functions are organized in files based on what part of the database they are associated with, i.e. all user related functions such as creating a new user or deleting a user are in /src/database/users.ts
. When creating new utils, please follow this standard accordingly.
See prisma docs here and a reference to the node client library here.
Redis is an in-memory cache to keep highly accessed values in a place that is fast to access and update. Setting up redis is easy for your distro of linux and if you are developing on Windows, can be installed through WSL, see docs for installation here.
Launching redis is as simple as running redis-server
.
When launching to production, you want redis to be daemonized, this can be done with systemd1 by default on most linux distributions using sudo systemctl start redis
and sudo systemctl enable redis
to enable launch on reboot, or you can do that with something like pm2 or screen or Windows Task Scheduler (see below), but can also be done manually with some configuration, see details here.
In our specific application, your redis-server must be running on 127.0.0.1:6379
which is the default for redis-server, and if you are on windows, make sure to add the line localhostForwarding=true
to your .wslconfig located in %UserProfile%\.wslconfig
, if this file does not exist, please create one and be sure to add the header [wsl2]
or [wsl1]
. Also if you are on windows, be aware that WSL does not keep applications alive without a bash terminal running, so do not close the WSL window while developing.
Another thing is to have a config for your instance of Redis, in testing/development you can do something like this to have an instance that does not save to disk. On WSL, you must do this because redis will not have write permissions to save to disk.
cd ~
mkdir redis
cd redis
touch redis.conf
redis-server redis.conf # run this in a separate window
redis-cli config set save ""
redis-cli config rewrite
After it was installed on the default WSL shell, if you want Redis to automatically launch on Windows startup, you can use Task Scheduler.
Create a new task (not basic task) in Task Scheduler that runs src/tools/redis.bat
whenever your user logs on, see guide.
Then, right-click the finished task and click "Run" to test it.
Run npm link
to globally register the brewbot
command, which you can use for some special commands.
See brewbot -h
for help.
Command | Description |
---|---|
npm start |
start the bot regularly |
npm run watch |
start the bot and watch for file changes to recompile automatically |
npm run lint |
lints the code with eslint |
npm test |
runs the script at src/test.ts |
npm run clearCommands |
clears all global and guild commands (takes a few minutes) |
npm run deploy |
runs prisma deployment scripts and launches the bot without watching |
npm run predeploy |
only runs prisma deployment scripts without launching the bot |
npm run win-redis |
use this command on Windows devices to launch redis-server in a WSL window |
Command | Description |
---|---|
npx prisma db push |
this will update your local db to reflect any changes in your schema.prisma file, use this while making changes that you want to see |
npx prisma migrate dev --name "describe_change_short" |
creates a database migration and updates the local database if there is one, use this once your changes to the schema.prisma file are done, do not use constantly for little changes, use the above command instead |
npx prisma migrate deploy |
this will deploy any changes to the local database, this is how you deploy migrations in production |
npx prisma migrate reset |
this will reset the localdatabase and re-apply any migrations, use this in testing if you make breaking changes or need a reset |
npx prisma migrate dev --create-only |
not usually needed, this will create a migration without applying it incase you need to manually change the SQL in the migration file |
npx prisma format |
this formats the schema.prisma file and can also auto-complete foreign key association |
npx prisma db seed |
this command seeds the database according to prisma/seed.ts |
Debugging through VS Code works just fine, including breakpoints.
Select "Launch" in the debugger pane, then press F5 to launch the debugger.
Select the "test.ts" profile to debug the script at src/test.ts
- (Sub)Command names and command option names need to be lowercase only with no spaces (underscores are fine).
- If you get ANY discord api related error then any changes that have been made to commands won't be registered until the error is fixed.
Discord has multiple subsets of markdown, depending on the type of message.
They can be combined, but monospace and code blocks need to be on the innermost "layer".
All messages can have:
- Bold (
**text**
), italic (*text*
), underline (__text__
), strikethrough (~~text~~
) - Monospace (`text`), code blocks (```text```)
- Quotes (
> text
), multiline quotes (>>> text
)
Interaction replies and MessageEmbeds (title, fields & description) can have:
- Hyperlinks (
[text](url)
)
Markdown that isn't allowed anywhere:
- Tables
- HTML mixins (
<details>
etc)
What | Limit |
---|---|
username | 1-80 chars |
message content | 2000 chars |
files per message | 10 files |
embeds per message | 10 embeds |
embed title | 256 chars |
embed description | 2048 chars |
embed author name | 256 chars |
embed fields | 25 fields |
embed field name | 256 chars |
embed field value | 1024 chars |
embed footer text | 2048 chars |
sum of all chars in embed | 6000 chars |
slash command description | 100 chars |
slash command arg description | 100 chars |
To use the new message buttons, this class handles the communication between the
bot.ts
event listener and the slash command's scope.For the
buttons
constructor param, don't set acustomId
as the registry assigns own IDs automatically.
destroy()
emits thedestroy
event, removes all event listeners and tells the registry to deregister this BtnMsggetReplyOpts()
returns properties that can be spread onto an interaction reply likeint.reply({ ...btmsg.getReplyOpts(), foo: "bar" })
getMsgOpts()
same asgetReplyOpts()
but for sending a message withchannel.send()
sendIn()
sends this BtnMsg in the provided channelUse
BtnMsg.on("name", (args) => {})
to subscribe to them
press
is emitted whenever the button is pressed by a user and gets passed the MessageButton and ButtonInteraction instancestimeout
is emitted when the timeout of the BtnMsg, set in the settings object, is reached. After the timeout, the.destroy()
method is automatically calleddestroy
is emitted whenever the.destroy()
method is called and it prompts the registry to deregister this BtnMsg instance. It gets passed an array ofMessageButton.customId
's. After this event is emitted, all previously registered event listeners will be removed and will never receive evetns again.
This class is a wrapper for MessageEmbed that handles scrolling through multiple of them via MessageButtons.
It offers lots of configurability and dynamically changeable pages.
sendIn()
sends this PageEmbed in the provided channel. If you want a custom message implementation, usesetMsg()
andgetMsg()
useInt()
instead replies to or edits a Command-/ButtonInteractiongetMsgOpts()
returns properties that can be passed to achannel.send()
or(msg | int).reply()
methodsetPages()
is for dynamically changing the pages of the instance. If the current page index is out of range after the pages have changed, it gets lowered automatically.first()
,prev()
,next()
andlast()
can be used just like the users use the MessageButtons to navigate the PageEmbed.destroy()
edits the message to remove the buttons, emits thedestroy
event, removes all event listeners and deregisters the buttonsupdateMsg()
can be called to update the message on the Discord API with what's currently stored in the PageEmbed.setAllowAllUsers()
allows setting whether users other than the author can interact with the buttonsaskGoToPage()
opens a prompt in a channel to type the new page number to navigate to.Use
PageEmbed.on("name", (args) => {})
to subscribe to them
press
is emitted whenever a button is pressed by a user and gets passed the ButtonInteraction instance and a string telling you which button was pressed.timeout
is emitted when the timeout of the PageEmbed, set in the settings object, is reached. After the timeout, the.destroy()
method is automatically called.destroy
is emitted whenever the.destroy()
method is called.update
is emitted after the associated message or interaction on the Discord API has been edited.
The modal is a configurable form dialog that gets shown to a user.
Modals only work in the desktop client, not the mobile app.This is an abstract class, so you need to create a sub-class extending
Modal
submit()
is executed when the user submitted the modal - this method needs to be overridden in a sub-classgetInternalModal()
returns the modal object that needs to be passed to an interaction'sshowModal()
method
destroy
gets emitted when.destroy()
is called to trigger the registry to delete this instance
Footnotes
-
If you are using systemd, create a
redis.conf
in/etc/redis/redis.conf
and make sure to add the linesupervised systemd
. ↩