237 lines
11 KiB
Markdown
237 lines
11 KiB
Markdown
# Geas Server Bot
|
|
|
|
This is a bot I wrote to manage the Discord server for Geas, the Edinburgh University Table-Top Role-Playing Society, during our move to an on-line format.
|
|
The bot is designed to create and manage channels and roles for gaming groups in order to replicate our in-person pitch events on a Discord space as far as possible.
|
|
The bot is written in Python, and was the first Python coding project I wrote, so it has a special place in my heart.
|
|
The first version I committed to the repository is version 2.1, and I previously handled the version control manually, so migrating old versions to Git would be a pain.
|
|
Version 3 was the second major upgrade, taking advantage of some of the recent changes to the Discord API.
|
|
|
|
## Setup
|
|
|
|
The Bot is dockerised and uses docker-compose for deployment, so it is fairly straightforward to deploy an instance of.
|
|
Clone the repository, install Docker and Docker Compose, navigate to the root directory (that contains the `docker-compose.yml` file), and use `docker-compose up -d` to set up and run the bot.
|
|
The bot runs on one Docker container with the instance of the app as well as storage for its data and configuration.
|
|
The bot uses docker-compose to mount an external volume to allow for persisting file storage and easy migration.
|
|
It no longer uses a database engine because it never really benefitted from the various database manipulation tools in the earlier version, and was not worth the complexity.
|
|
|
|
The bot authenticates using an API key, which I have kept private in a `.env` file that I have not uploaded to the repository.
|
|
In order to set up your own instance of the bot, you will need to provide the following values in a `.env` file in the root directory.
|
|
|
|
The following is a step-by-step guide on installation:
|
|
|
|
### Prerequisites
|
|
|
|
1. **Prerequisites**:
|
|
1. Command line access to your computer.
|
|
2. Make sure that the Bot has the correct authorisation scopes in your Discord Developer section. The bot needs to have `application.commands` and `bot` enabled.
|
|
3. Invite the Bot to join your guild.
|
|
4. Install and run [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git), [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
|
2. Make a folder to house the project. `mkdir geas-bot && cd geas-bot`
|
|
3. Clone the repository. Type `git clone https://git.vsnt.uk/viveksantayana/geas-bot.git .`
|
|
4. Navigate to the `app` folder and use the `.env.example` file to make the environment variables file: `sudo mv .env.example .env`
|
|
5. Edit the `.env` file and add the relevant API keys (see below). You can do this using any text editor, like `sudo nano .env`.
|
|
6. Place any data files from a previous install in the relevant `/app/data` folder. They will be copied in to the Docker container in the next step. Make sure the `.env` files refer to them correctly.
|
|
7. Navigate back to the root folder `geas-bot` and execute docker-compose to spin up the Bot. Do this in detached mode so you do not get locked into the terminal (unless you want to debug and read the console outputs.) `sudo docker-compose up -d`
|
|
8. Sign in to Discord and finish configuring the bot using the various `/config` commands.
|
|
|
|
The following is the template for the `.env` file, with the variable names as are referenced in the bot's code.
|
|
You can find an example template of this in the `.env.example` file.
|
|
|
|
`.env` file:
|
|
|
|
```DotENV
|
|
BOT_TOKEN=(API token for the production version of the bot.)
|
|
TEST_TOKEN=(API token for any test instance.)
|
|
CONFIG=(Path to config file. The bot defaults to './data/config.yml' if not provided.)
|
|
DATA=(Path to data file. The bot defaults to './data/data.yml' if not provided.)
|
|
LOOKUP=(Path to the game role lookup file. The bot defaults to './data/lookup.yml' if not provided.)
|
|
GM=(Path to the GM lookup file. The bot defaults to './data/gm.yml' if not provided.)
|
|
CATEGORIES=(Path to the channel category lookup file. The bot defaults to './data/categories.yml' if not provided.)
|
|
PITCHES=(Path to the pitches data file. The bot defaults to './data/pitches.yml' if not provided.)
|
|
BOT_VERSION=(verson string)
|
|
```
|
|
|
|
The correct API keys need to be entered in the environment variables in the `.env` file, and for a copy of this file to be placed in the root and the `app` directories.
|
|
|
|
**N.B.**: You can switch between which Both the code executes, the main bot or the test bot, by changing the variable referenced in the `client.run()` command in the `bot.py` file:
|
|
|
|
```py
|
|
client.run(os.getenv('TEST_TOKEN'))
|
|
```
|
|
|
|
to
|
|
|
|
```py
|
|
client.run(os.getenv('BOT_TOKEN'))
|
|
```
|
|
|
|
in order for to authenticate as the correct bot.
|
|
|
|
## File Structure
|
|
|
|
```
|
|
|-- app
|
|
| |-- .env.example
|
|
| |-- assets
|
|
| | `-- tcard.wav
|
|
| |-- bot.py
|
|
| |-- cogs
|
|
| | |-- botcommands
|
|
| | | `-- prefix.py
|
|
| | |-- controlcommands
|
|
| | | `-- debug.py
|
|
| | |-- events
|
|
| | | |-- on_command_error.py
|
|
| | | |-- on_connect.py
|
|
| | | |-- on_guild_channel_delete.py
|
|
| | | |-- on_guild_join.py
|
|
| | | |-- on_guild_remove.py
|
|
| | | |-- on_guild_role_create.py
|
|
| | | |-- on_guild_role_delete.py
|
|
| | | |-- on_guild_role_update.py
|
|
| | | |-- on_guild_update.py
|
|
| | | |-- on_message.py
|
|
| | | |-- on_ready.py
|
|
| | | `-- secondary
|
|
| | | `-- pitch_listener.py
|
|
| | |-- membership
|
|
| | | |-- membership_verification.py
|
|
| | | `-- restriction_listener.py
|
|
| | `-- slashcommands
|
|
| | |-- config.py
|
|
| | `-- secondary
|
|
| | |-- edit_membership.py
|
|
| | |-- game_create.py
|
|
| | |-- game_management.py
|
|
| | |-- manipulate_timeslots.py
|
|
| | |-- pitch.py
|
|
| | |-- player_commands.py
|
|
| | `-- tcard.py
|
|
| |-- data
|
|
| | |-- .gitkeep
|
|
| | |-- categories.yml
|
|
| | |-- config_blueprint.yml
|
|
| | |-- config.yml
|
|
| | |-- data.yml
|
|
| | |-- gm.yml
|
|
| | `-- lookup.yml
|
|
| |-- Dockerfile
|
|
| `-- requirements.txt
|
|
|-- CHANGELOG.md
|
|
|-- COMMANDS.md
|
|
|-- docker-compse.yml
|
|
|-- LICENSE
|
|
|-- README.md
|
|
|-- resources.md
|
|
`-- TODO.md
|
|
```
|
|
|
|
The `COMMANDS.md` file gives a list of all the commands the Bot uses, as well as a reference to the various cogs or base commands that are associated with them. The code for the command should be housed in the respective files within the file tree.
|
|
|
|
## Data Structure
|
|
|
|
The bot holds data in two `.yml` files, `config.yml` for client configurations for each guild it is in and `data.yml` to hold the actual data regarding game and channel set-up.
|
|
I was considering merging them into one file, but given how different the two concerns were I ended up splitting the files.
|
|
I had initially considered a `.ini` file for the configuration settings and `.json` for the data, but I decided to use `.yml` for both just to avoid unnecessary complexity.
|
|
|
|
### `config.yml` Structure
|
|
|
|
This tree gives the list of various keys for the `.yml` dictionary as well as the types of different data expected.
|
|
The entire configuration file is essentially a dictionary with other dictionaries, strings, integers, and lists as values.
|
|
All values in the dictionary are referenced first by a string of the guild id integer.
|
|
Remember to convert the guild ID to strings during several operations, and be careful to compare like for like in any logics.
|
|
|
|
```yml
|
|
guild id string:
|
|
channels:
|
|
help: int
|
|
mod: int
|
|
signup: int
|
|
configured: bool
|
|
membership:
|
|
- role id int
|
|
name: str
|
|
notifications:
|
|
help: bool
|
|
signup: bpp;
|
|
owner: owner id int
|
|
prefix: '-' by default
|
|
roles:
|
|
admin:
|
|
- role id int
|
|
bot:
|
|
committee:
|
|
newcomer:
|
|
returning_player:
|
|
student:
|
|
timeslots:
|
|
key: name
|
|
```
|
|
|
|
### `data.yml` Structure
|
|
|
|
Just like above, the `data.yml` file is also a dictionary of dictionaries that is indexed by a string of the guild id.
|
|
It stores only the relevant data necessary for the code to function.
|
|
It only holds, for instance, ID numbers rather than user handles, Discord discriminators, or names.
|
|
|
|
```yml
|
|
guild id string:
|
|
timeslot:
|
|
role:
|
|
category: category id int
|
|
current_players: int
|
|
header_message: message id int
|
|
game_title: str
|
|
gm: gm role id int
|
|
max_players: int
|
|
min_players: int
|
|
platform: str
|
|
role: role id int
|
|
system: str
|
|
text_channel: channel id int
|
|
```
|
|
|
|
### Other Data Files
|
|
|
|
In addition to the above data file, the bot also uses storage in additional reference files to quickly look up values when needed for its various functions.
|
|
The purpose of these lookup files is more to act as dictionaries facilitating arbitrary look-ups of key information when required.
|
|
They are not intended to act as storage.
|
|
Most of these lookup files are not particularly readable because they have raw values without informative keys.
|
|
They are constructed and manipulated in tandem with the core data files.
|
|
|
|
## In the Future
|
|
|
|
### Restructure command execution using global event listeners
|
|
|
|
As it stands, there is a conundrum with the Bot:
|
|
any kind of manual interaction to manipulate roles or categories will cause conflicts to emerge between the Bot's data and the guild settings.
|
|
In order for the bot to be adaptable, and to respond to user interactions, it will need event listeners for things like channel, role, or category changes/creation/depetion, etc.
|
|
Having such listeners will cause a circularity between the Bot's edit actions, which would then trigger the listener.
|
|
There is currently no way of having an exception for the Bot's edits.
|
|
|
|
To reconcile this, the bot would need to work such that the command process that modified games only acted upon the roles, which would then trigger the event listeners to synchronise these changes with the categories, and subsequently the data.
|
|
Having the bot edit the data in the main command process would mean that there would be conflicts with the simuntaneous execution of parallel threads.
|
|
|
|
This works for individual commands, but it breaks down when trying to use the `purge` command because of conflicts causedb by simultaneous changes to the data files.
|
|
Programming around this will need a further layer of complexity, involving flags checking for R/W operations and a time-out.
|
|
|
|
### Membership sign up performance issues
|
|
|
|
I have set the member verification prompt to use a global listener to avoid a situation where it creates several backlogged processes when multiple people post sign-ups at the same time.
|
|
This should also mean that the sign-up prompts should persist over reboots.
|
|
Other developers for Discord Components recommended the use of dynamic call-backs, however these do not persist after reboots so are not suitable for this purpose.
|
|
Discord will need to make substantial API updates in the future for rolling out the Threads feature.
|
|
The bot will need updating again then.
|
|
|
|
|
|
## Remote Deployment to docker server
|
|
|
|
Configure local env to know about the server
|
|
- ` docker context create remote --docker "host=tcp://<hostname>:2376,ca=<full path>/ca.pem,cert=<full path>/cert.pem,key=<full path>/key.pem"`
|
|
- create a folder on the server to use for the config
|
|
- create/amend a docker compose for that env (eg docker-compose-nas)
|
|
- put the config in that folder
|
|
- deploy using that config `docker compose -f docker-compose-nas.yml up -d`
|
|
- hope
|
|
- if its a QNAP nas, manually copy the docker compose file to `/share/CACHEDEV1_DATA/VMs/container-station-data/application/geas-bot` because its stupid
|
|
- |