A dynamically-loaded custom command manager for phase two of The Forest
Discord server.
- Install dependencies using
poetry
.
$ poetry install
-
Create a configuration file at
config/config.yaml
. You can seeconfig/config.example.yaml
for an example. NEVER commit yourconfig.yaml
file to version control or you will leak your client token! -
Run the bot.
$ poetry run birdsong
- Create a configuration file at
supervisord.conf
. It should look something like this:
[supervisorctl]
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisord]
logfile=/home/rsarvaria/birdsong/logs/supervisord.log
[inet_http_server]
port=127.0.0.1:9001
[program:birdsong]
command=poetry run birdsong
redirect_stderr=true
stdout_logfile=/home/rsarvaria/birdsong/logs/process.log
- Start
supervisord
.
$ poetry run supervisord
- Ensure that the process we created started successfully.
$ poetry run supervisorctl
birdsong RUNNING pid 548159, uptime 3 days, 17:59:32
supervisor>
birdsong
commands consist of two parts: a command specfile and a command script.
Specfiles are written in YAML and dictate how the bot should respond to a particular trigger in a particular context. Each time a user sends a message, each command is tested against its specfile to see if the command should run in the current context. Each specfile that matches is then run in sequence for that message.
Here is an example of a simple specfile that defines a command:
cmdtype: "command"
trigger: "slap"
require:
channels:
- "bots"
roles:
- "Slapper"
actions: "./slap.py"
Specfiles contain four blocks:
cmdtype
: The type of the custom command. Can be one of the following:command
: matches the classic command form{prefix}{cmd} [args]
.contains
: triggers if the trigger is anywhere in the message (case insensitive).exact
: triggers if the message content is exactly the same as the trigger.regex
: triggers depending on the given regular expression.
trigger
: The string that the bot matches according to the rules set bycmdtype
; for instance, the name of the command.require
: A block of lists of conditions that the trigger message has to satisfy. Options include the following:channels
: A list of channels where the command can run.categories
: A list of categories where the command can run.roles
: The user must have at least one of these roles to use the command.
actions
: A path to the Python file that implements the command. More on this below.
Scripts are written in Python and interact directly with the birdsong
client to interact
with Discord or the database.
Each script must have the following basic structure:
import discord
async def action(birdsong: any, context: discord.Message, cmd: str, args: list[str]) -> bool:
"""
Implements an action.
"""
await do_things()
The return value of action
should be true
if you wish to delete the context
message, and false otherwise.
- if any
action
triggered by a message returnstrue
, the message is deleted at the end of execution - DO NOT delete the message directly or other trigger commands will lose access to the command context!
The birdsong
parameter provides access to the following:
birdsong.assetmanager
: An asset manager that handles loading and storing files from yourassets
andstore
paths.birdsong.builtins
: A collection of helper functions that are super helpful for simplifying script files.birdsong.ccmanager
: Underlying access tobirdsong
's custom command framework. (Use with caution.)birdsong.dbmanager
: A wrapper aroundbirdsong
's connection tomongodb
.birdsong.modules
: A collection of modules loaded from themodules
path. Modules are globally available in scripts.
Here is an example of a single-project layout using birdsong
.
birdsong
├── birdsong
├── config
├── .gitignore
├── poetry.lock
├── pyproject.toml
├── README.md
└── supervisord.conf
my-command-project
├── docs
│ └── README.md
└── impl
├── assets
├── commands
│ ├── slap.py
│ └── slap.yaml
├── commands
│ └── mymodule.py
└── store
If you are interested in a multi-project layout instead, you might want to do something like this:
birdsong
├── birdsong
├── config
├── .gitignore
├── poetry.lock
├── pyproject.toml
├── README.md
└── supervisord.conf
my-projects
├── assets
│ ├── project-a
│ │ └── image_a.png
│ └── project-b
│ └── image_b.png
├── commands
│ ├── project-a
│ │ ├── command_a.py
│ │ └── command_a.yaml
│ └── project-b
│ ├── command_b.py
│ └── command_b.yaml
├── docs
│ ├── project-a
│ │ └── README.md
│ └── project-b
│ └── README.md
├── modules
│ ├── project-a.py
│ └── project-b.py
└── store
├── project-a
│ └── generated_a.png
└── project-b
└── generated_b.png