Skip to content

Refactor configuration #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 10, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -2,11 +2,10 @@
.secrets
.idea
.vscode
README.md
src/europython_discord/config.local.toml
__pycache__
.DS_Store
registered_log.txt
schedule.json
schedule_cache.json
pretix_cache.json
*.egg-info/
livestreams.toml
4 changes: 1 addition & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -8,11 +8,9 @@ RUN groupadd --gid 1000 bot && \
USER bot
WORKDIR /home/bot

ENV PATH="/home/bot/.local/bin:$PATH"

COPY --chown=bot:bot pyproject.toml uv.lock ./
COPY --chown=bot:bot src ./src

RUN uv sync

ENTRYPOINT ["uv", "run", "run-bot"]
ENTRYPOINT ["uv", "run", "run-bot", "--config-file", "prod-config.toml"]
121 changes: 71 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,95 @@
# Europython Discord Bot
# Europython Discord

An easy to deploy conference bot that manages roles for attendees via registration, notifies about upcoming sessions.
Exposes Discord server statistics to organizers.
We hosted the bot on Hetzner. And deployed with a single click Action from GitHub 😎.
A suite of tools for managing the EuroPython Conference Discord server:

![registration_view.png](./img/registration_view.png)
* [src/europython_discord](./src/europython_discord): Discord bot
* [scripts/configure-guild.py](./scripts/configure-guild.py): Configure channels and roles of a Discord server
* [scripts/export-members.py](./scripts/export-members.py): Export a list of all server members and their roles

## Overview
The scripts work standalone and only require an Auth token. Please find more documentation in the respective files.

The `main` method in `src/europython_discord/bot.py` is the entry point for the bot.
I't a good starting point to start browsing the codebase.
It requires a `.secrets` file in the root of the repository with `DISCORD_BOT_TOKEN` and `PRETIX_TOKEN` environment variables.
The bot has the following extensions ("Cogs"):

### Registration
* Ping: To check if the bot is running, write `$ping` in any channel. The bot will respond with `Pong!`.
* Guild Statistics: As an organizer, write `$participants` in an organizer-only channel. The bot will respond with a list of roles, and the number of members per role.
* Registration: On startup, the bot posts a registration form. New users must register using their Pretix ticket data. On success, the bot assigns the appropriate roles.
* Programme Notifications: Before each session, the bot posts a session summary and updates the livestream URLs.

At EuroPython, we use [pretix](https://pretix.eu/about/en/) as our ticketing system.
## Screenshots
### Registration Channel:
![Registration Channel](./img/registration-channel.png)

The bot utilizes the Pretix API to fetch ticket information and creates an in-memory key-value store to retrieve the ticket type for a given Discord user. The mapping between ticket types and Discord roles is defined in a JSON file, such as ticket_to_roles_prod.json, and is used by the bot to assign roles to users.
### Registration Form:
![Registration Form](./img/registration-form.png)

There are safeguard methods in place to prevent users from registering multiple times and to make a direct Pretix API call in case the user information is not available in the in-memory store.
### Programme Notification:
![Programme Notification](./img/programme-notification.png)

## Configuration

### Program notifications
All configuration is server-agnostic. You can set up your own Discord server and use the included configuration.

Is a service to push the programme notification to Discord. Pretalx API is used to fetch the programme information, and `config.toml` holds information about livestream URLs.
Arguments and environment variables:

### Organizers extension
* Argument `--config-file`: Path to .toml configuration file
* Environment variable `DISCORD_BOT_TOKEN`: Discord bot auth token (with Admin and `GUILD_MEMBERS` privileges)
* Environment variable `PRETIX_TOKEN`: Pretix access token (preferably read-only)

A set of commands that are available only for organizers that are allowing to get statistics about the Discord server.
Included example configuration files:

* [`prod-config.toml`](./prod-config.toml) or [`test-config.toml`](./test-config.toml): Prod/Test configuration
* [`test-livestreams.toml`](./test-livestreams.toml): Test livestream URL configuration

Used cache and log files (will be created if necessary):

* `pretix_cache.json`: Local cache of Pretix ticket data
* `registered_log.txt`: Log of registered users
* `schedule_cache.json`: Local cache of [programapi](https://github.com/europython/programapi) schedule

## Setup
### Quickstart using `pip`

This project uses [uv](https://github.com/astral-sh/uv) for managing dependencies.
If you just want to try the bot and skip all the development setup,
If you just want to try the bot and skip the development setup,
you can use `pip` instead of `uv` (requires Python >= 3.11):

```shell
# create and activate virtual environment (optional, but recommended)
python -m venv .venv
. .venv/bin/activate # Windows: '.venv/Scripts/activate'
. .venv/bin/activate # Windows: .venv/Scripts/activate

# install this package
# install this package (use '-e' for 'editable mode' if you plan to modify the code)
pip install .

# run the bot
run-bot
# set environment variables
export DISCORD_BOT_TOKEN=... # Windows: $env:DISCORD_BOT_TOKEN = '...'
export PRETIX_TOKEN=... # Windows: $env:PRETIX_TOKEN = '...'

# run the bot with a given config file
run-bot --config your-config-file.toml
```

### Development setup using `uv`

Install `uv` as documented [here](https://docs.astral.sh/uv/getting-started/installation/), then
create/update virtual environment with all dependencies according to [`uv.lock`](./uv.lock)
with `uv sync --dev`.
Install `uv` as documented [here](https://docs.astral.sh/uv/getting-started/installation/), then run `uv sync --dev` to create/update a
virtual environment with all dependencies according to [`uv.lock`](./uv.lock).

If required, `uv` will download the required Python version, as specified in
[`.python-version`](./.python-version).

### Using `uv`
To run the bot, use the following:

```shell
# set environment variables
export DISCORD_BOT_TOKEN=... # Windows: $env:DISCORD_BOT_TOKEN = '...'
export PRETIX_TOKEN=... # Windows: $env:PRETIX_TOKEN = '...'

# run the bot with a given config file
uv run run-bot --config your-config-file.toml
```

#### Useful `uv` commands

This is a summary of useful `uv` commands.
Please refer to the [uv documentation](https://docs.astral.sh/uv) or `uv help` for details.

```shell
@@ -70,19 +100,22 @@ uv sync --dev # include dev dependencies
# activate uv-generated venv
. .venv/bin/activate # Windows: '.venv/Scripts/activate'

# execute command inside uv-generated venv
uv run [command]

# reset all packages to versions pinned in uv.lock
uv sync
uv sync --dev # include dev dependencies

# add package
uv add package
uv add --dev package # install as dev dependency
uv add [package]
uv add --dev [package] # install as dev dependency

# upgrade packages
uv lock --upgrade

# remove package
uv remove package
uv remove [package]
```

### Development tools
@@ -93,26 +126,14 @@ uv remove package
* Check code style: `uv run --dev ruff check .`
* Run tests: `uv run --dev pytest .`

### Configuration

Create `config.local.toml` file in the `src/europython_discord` directory, it would be used instead of `config.toml` if exists.

Add `.secrets` file to the root of the repository with the following content:

```shell
DISCORD_BOT_TOKEN=<EuroPythonTestBotToken_from_1Password>
PRETIX_TOKEN=<PretixStagingToken_from_1Password>
````

After you have added the `.secrets` file, you can run the bot with the following command:
### Deployment

```shell
run-bot
```
The bot is deployed on a VPS using a GitHub Action.
It uses Ansible to configure the VPS, and Docker Compose to run the bot.

or with docker:
Related files:

```shell
docker build --tag discord_bot .
docker run --interactive --tty --env DISCORD_BOT_TOKEN=$DISCORD_BOT_TOKEN --env PRETIX_TOKEN=$PRETIX_TOKEN discord_bot
```
* [.github/workflows/deploy.yml](./.github/workflows/deploy.yml): The GitHub Action
* [ansible/deploy-playbook.yml](./ansible/deploy-playbook.yml): The Ansible Playbook
* [Dockerfile](./Dockerfile): The Docker container recipe
* [compose.yaml](./compose.yaml): The Docker Compose recipe
6 changes: 2 additions & 4 deletions ansible/deploy-playbook.yml
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@
repository_url: https://github.com/EuroPython/discord.git

tasks:

- name: Enable persistent logging for journald
ini_file:
path: /etc/systemd/journald.conf
@@ -15,7 +14,6 @@
no_extra_spaces: true
backup: true


- name: reload systemd-journald
systemd:
name: systemd-journald
@@ -94,9 +92,9 @@
owner: bot
group: bot

- name: Create schedule.json in bot's home directory
- name: Create schedule_cache.json in bot's home directory
file:
path: /home/bot/schedule.json
path: /home/bot/schedule_cache.json
state: touch
owner: bot
group: bot
15 changes: 9 additions & 6 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
services:
EuroPythonBot:
image: europythonbot
build: .
build:
context: .
env_file:
- /root/.secrets
volumes:
- type: bind
source: /etc/EuroPython/discord/.secrets
target: /home/bot/.secrets
source: prod-config.toml
target: /home/bot/prod-config.toml
read_only: true

- type: bind
source: /etc/EuroPython/livestreams/livestreams.toml
source: /root/livestreams.toml
target: /home/bot/livestreams.toml
read_only: true

@@ -19,8 +22,8 @@ services:
read_only: false

- type: bind
source: /home/bot/schedule.json
target: /home/bot/schedule.json
source: /home/bot/schedule_cache.json
target: /home/bot/schedule_cache.json
read_only: false

- type: bind
Binary file added img/programme-notification.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/registration-channel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/registration-form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed img/registration_view.png
Binary file not shown.
57 changes: 57 additions & 0 deletions prod-config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
log_level = "INFO"

[registration]
registration_form_channel_name = "registration-form"
registration_help_channel_name = "registration-help"
registration_log_channel_name = "registration-log"

pretix_base_url = "https://pretix.eu/api/v1/organizers/europython/events/ep2025"

registered_cache_file = "registered_log.txt"
pretix_cache_file = "pretix_cache.json"

[registration.item_to_roles]
# onsite participants
"Business" = ["Participants", "Onsite Participants"]
"Personal" = ["Participants", "Onsite Participants"]
"Education" = ["Participants", "Onsite Participants"]
"Community Contributors" = ["Participants", "Onsite Participants"]
"Grant ticket" = ["PARTICIPANTS", "Onsite Participants"]
# remote participants
"Remote Participation Ticket" = ["Participants", "Remote Participants"]
"Remote Grant ticket" = ["Participants", "Remote Participants"]
"Remote Community Organiser" = ["Participants", "Remote Participants"]
# sponsors
"Sponsor Conference Pass" = ["Participants", "Onsite Participants", "Sponsors"]
# speakers
"Presenter" = ["Participants", "Onsite Participants", "Speakers"]

[registration.variation_to_roles]
"Volunteer" = ["Volunteers"]

[program_notifications]
# UTC offset in hours (e.g. 2 for CEST)
timezone_offset = 2
api_url = "https://static.europython.eu/programme/ep2025/releases/current/schedule.json"
schedule_cache_file = "schedule_cache.json"
livestream_url_file = "livestreams.toml"
main_notification_channel_name = "programme-notifications"

# optional simulated start time for testing program notifications
# simulated_start_time = "2024-07-10T07:30:00+02:00"

# optional fast mode for faster testing of program notifications
# will only take effect if simulated_start_time is set
# fast_mode = true

[program_notifications.rooms_to_channel_names]
"Forum Hall" = "forum-hall"
"South Hall 2A" = "south-hall-2a"
"South Hall 2B" = "south-hall-2b"
"North Hall" = "north-hall"
"Terrace 2A" = "terrace-2a"
"Terrace 2B" = "terrace-2b"
"Exhibit Hall" = "exhibit-hall"

[guild_statistics]
required_role = "Organizers"
5 changes: 0 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -14,17 +14,12 @@ dependencies = [
"discord-py>=2.3.1",
"aiofiles>=24.1.0",
"aiohttp>=3.11.16",
"arrow>=1.3.0",
"certifi>=2024.7.4",
"python-dotenv>=1.0.1",
"yarl>=1.19.0",
"pydantic>=2.8.2",
"unidecode>=1.3.8",
]

[dependency-groups]
dev = [
"ansible>=10.2.0",
"pytest>=8.3.5",
"pytest-aiohttp>=1.1.0",
"pytest-asyncio>=0.26.0",
Loading