Skip to content
Open
Show file tree
Hide file tree
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
181 changes: 181 additions & 0 deletions documentation/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<!-- SPDX-License-Identifier: Attribution-NonCommercial-ShareAlike (CC BY-NC-SA) -->

# Architecture

This document describes how ERM is structured and how its layers
interact at runtime.

---

## Entry Points

- `main.py` — Thin entrypoint. Calls `run()` from `erm.py`.
- `erm.py` — Defines the `Bot` class, initializes all database
collections, loads cogs, and starts the task scheduler.

---

## Directory Structure

```
erm/
├── cogs/ # Discord command groups (slash commands)
├── datamodels/ # MongoDB collection wrappers and typed data classes
├── events/ # Discord gateway event handlers
├── tasks/ # Background loops (discord.ext.tasks)
├── ui/ # Discord UI components (Views, Selects, Modals)
├── utils/ # Shared utilities, API clients, and helpers
├── menus.py # Large collection of interactive menus and flows
├── helpers.py # Test helpers and mock infrastructure
├── erm.py # Bot class and startup logic
└── main.py # Entrypoint
```

---

## Cogs

Cogs live in `cogs/` and contain the bot's slash command groups.
They are loaded automatically on startup via `pkgutil.iter_modules`.

| Cog | Responsibility |
|-----|----------------|
| `Actions` | Automated server actions triggered by conditions |
| `ActivityMonitoring` | Staff activity monitoring and reporting |
| `ActivityNotices` | Leave of absence and reduced activity requests |
| `Configuration` | Server setup and settings management |
| `CustomCommands` | User-defined slash commands per server |
| `ERLC` | ER:LC in-game integration (bans, kicks, player lookup) |
| `GameLogging` | In-game event log forwarding to Discord channels |
| `Infractions` | Staff infraction tracking and management |
| `Jishaku` | Developer/debug REPL (conditionally loaded) |
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not conditionally loaded

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still needs to be fixed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "not conditionally loaded"

| `MC` | MapleCounty game integration |
| `OAuth2` | Discord OAuth2 flow handling |
| `Privacy` | User data and consent management |
| `Punishments` | Player punishment logging (warns, bans, kicks, BOLOs) |
| `Reminders` | Scheduled reminder creation and delivery |
| `Search` | Cross-guild search for punishments and shift records |
| `ShiftLogging` | Staff shift start, end, break, and quota tracking |
| `StaffConduct` | Staff conduct reviews and documentation |
| `Utility` | General-purpose utility commands |

---

## Events

Event handlers live in `events/` and are named after the Discord
gateway event they handle. Each file is a standalone module registered
on the bot at startup.

Notable events:

- `on_message` — Handles custom command dispatch and message-based triggers
- `on_shift_start` / `on_shift_end` / `on_shift_edit` / `on_shift_void` — Custom
events dispatched internally when shift state changes
- `on_punishment` / `on_punishment_delete` — Custom events for punishment lifecycle
- `on_infraction_create` / `on_infraction_revoke` — Custom events for infraction lifecycle
- `on_loa_accept` / `on_loa_deny` — Custom events for LOA request resolution
- `on_member_remove` — Cleans up shift state when a member leaves

---

## Tasks

Background tasks live in `tasks/` and use `discord.ext.tasks.loop`.
They are registered in `utils/task_loader.py` and started with a
staggered 30-second delay between each to avoid startup load spikes.

| Task | Interval | Purpose |
|------|----------|---------|
| `check_reminders` | Periodic | Delivers due reminders to users |
| `check_loa` | Periodic | Expires LOA requests past their end date |
| `iterate_ics` | Periodic | Processes integration command storage |
| `iterate_prc_logs` | Periodic | Polls PRC API and forwards logs to Discord |
| `statistics_check` | Periodic | Updates analytics records |
| `tempban_checks` | Periodic | Lifts expired temporary bans |
| `check_whitelisted_car` | Periodic | Enforces whitelisted vehicle rules in-game |
| `change_status` | Periodic | Rotates the bot's Discord presence status |
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runs once

| `process_scheduled_pms` | Periodic | Delivers scheduled direct messages |
| `sync_weather` | Periodic | Syncs real-world weather to ERLC in-game weather |
| `iterate_conditions` | Periodic | Evaluates server conditions and triggers actions |
| `check_infractions` | Hourly | Reverts expired temporary role changes from infractions |
| `prc_automations` | Periodic | Runs configured PRC automation rules |
| `mc_discord_checks` | Periodic | Runs MapleCounty Discord integration checks |

`check_reminders` and `iterate_conditions` are controlled by
`REMINDERS_ENABLED` and `ACTIONS_ENABLED` respectively and will not
start if those are set to `FALSE`.

`change_status` does not run when `ENVIRONMENT=CUSTOM`.

---

## Data Layer

### `datamodels/`

Each file in `datamodels/` wraps a MongoDB collection as a typed
class. They inherit from `utils/mongo.py`'s `Document` class, which
provides standard async CRUD operations (`find_by_id`, `insert`,
`update_by_id`, `delete_by_id`, etc.).

All datamodel instances are attached to the `Bot` object at startup
and accessed via `bot.db.<collection>` throughout the codebase.

### `utils/mongo.py`

Provides the `Document` base class used by all datamodels. Wraps
`pymongo`'s async client with convenience methods.

---

## Utils

| Module | Purpose |
|--------|---------|
| `api.py` | FastAPI application exposing internal HTTP endpoints |
| `prc_api.py` | Client for the ER:LC PRC API with typed response models |
| `mc_api.py` | Client for the MapleCounty API |
| `utils.py` | General shared helpers used across cogs and tasks |
| `autocompletes.py` | Discord slash command autocomplete handlers |
| `conditions.py` | Condition evaluation logic used by the Actions system |
| `constants.py` | Base server config schema, colour constants, condition |
| | and weather code mappings |
| `emojis.py` | `EmojiController` for resolving custom emoji by name |
| `paginators.py` | Reusable paginated embed components |
| `bloxlink.py` | Bloxlink API client for Roblox username resolution |
| `accounts.py` | Staff account lookup helpers |
| `AI.py` | AI API client wrapper |
| `hot_reload.py` | Development cog reloading utility |
| `log_tracker.py` | `LogTracker` for structured internal logging |
| `timestamp.py` | Time delta formatting helpers |
| `username_check.py` | `UsernameChecker` for flagging suspicious Roblox usernames |
| `task_loader.py` | Registers and starts all background tasks |
| `basedataclass.py` | `BaseDataClass` used by PRC and MC API response models |

---

## Internal API

`utils/api.py` runs a FastAPI application alongside the bot using
`uvicorn`. It exposes HTTP endpoints used by the ERM website and
panel. It is not required for basic bot operation and can be ignored
for most development work.

---

## Menus




`menus.py` is a large single file containing the majority of the
bot's interactive Discord UI flows — multi-step modals, confirmation
prompts, paginated views, and context-sensitive menus. When adding
new interactive flows, check `menus.py` first for existing patterns
to follow or reuse.
Comment thread
Netplayz marked this conversation as resolved.

Do not use this file for any new views, instead, please create a new file
in the `ui` folder and reference that. `menus.py` will be removed in a later
version of ERM.

45 changes: 45 additions & 0 deletions documentation/coding-assistants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!-- SPDX-License-Identifier: Attribution-NonCommercial-ShareAlike (CC BY-NC-SA) -->

# AI Coding Assistants

This document provides guidance for AI tools and developers using AI
assistance when contributing to ERM.

AI tools helping with ERM development should follow the standard
development process.
---

## Licensing and Legal Requirements

All contributions must comply with ERM's (Emergency Reponse Management) licensing requirements:

- All code must be compatible with the Attribution-NonCommercial-ShareAlike (CC BY-NC-SA) license
- Use appropriate SPDX license identifiers

---

## Signed-off-by and Developer Certificate of Origin

AI agents **MUST NOT** add Signed-off-by tags. Only humans can legally
certify the Developer Certificate of Origin (DCO). The human submitter
is responsible for:

- Reviewing all AI-generated code
- Ensuring compliance with licensing requirements
- Adding their own Signed-off-by tag to certify the DCO (Developer Certificate of Origin)
- Taking full responsibility for the contribution

---

## Attribution

When AI tools contribute to ERM development, proper attribution
helps track the evolving role of AI in the development process.
Contributions should include an Assisted-by tag in the following format:


*Assisted-by: AGENT_NAME:MODEL_VERSION*

Example: *Assisted-by: Claude Haiku 4.5*

This document was taken as a example from https://github.com/torvalds/linux
98 changes: 98 additions & 0 deletions documentation/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<!-- SPDX-License-Identifier: Attribution-NonCommercial-ShareAlike (CC BY-NC-SA) -->

# Contributing

This document covers the process for contributing to ERM.

---

## Before You Start

- Read the [license](../LICENSE). All contributions must be compatible
with the Attribution-NonCommercial-ShareAlike (CC BY-NC-SA) license.
- If you are using an AI coding assistant, read
[coding-assistants.md](coding-assistants.md) before submitting.
- Set up your local environment using [setup.md](setup.md).

---

## Workflow

1. Fork the repository and create a branch from `Development`.
2. Make your changes.
3. Run the test suite locally with `pytest` before pushing.
4. Open a pull request against `Development` with a clear description of
what changed and why.

---

## Branch Naming

Use a short, descriptive name prefixed with the type of change:

- `fix/` — Bug fixes
- `feat/` — New features
- `refactor/` — Code restructuring without behavior changes
- `docs/` — Documentation changes only
- `chore/` — Dependency updates, CI changes, and similar maintenance

Example: `fix/shift-end-break-overlap`

---

## Commit Messages

Write commits in the imperative mood and keep the subject line
under 72 characters.

```
Add quota enforcement to shift types
Fix break duration calculation on overnight shifts
Remove unused LOA reminder fallback
```

Do not use `Signed-off-by` tags unless you are a human contributor
certifying the Developer Certificate of Origin (DCO). See
[coding-assistants.md](coding-assistants.md) for details on AI attribution.

---

## Code Style

- Python 3.12. Match the style of the surrounding code.
- All database calls must be `async`. Use `await` throughout — do not
use blocking pymongo calls.
- Ensure the use of the `Document` class whenever accessing different collections.
- Do not create Documents in commands, define them on the instance method `self` in the setup hook.
- Use `discord.ext.tasks.loop` for new background tasks.
- New interactive Discord flows must have their own UI file in `ui/`. Do not add new enties to `menus.py`.
- Use `utils/constants.py` for shared colour values (`BLANK_COLOR`,
`GREEN_COLOR`, `RED_COLOR`) rather than hardcoding hex values.
- Use `decouple.config()` for all environment variable access.
- Do not use inline ENV comments
Comment on lines +62 to +72
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm


---

## Tests

The test suite lives in `test_erm.py`. `helpers.py` provides mock
infrastructure for the `Bot` class and Discord objects.

Run all tests:

```bash
pytest
```

CI runs `pytest` and `flake8` (with `--exit-zero`) on every push
via the GitHub Actions workflow in `.github/workflows/app.yaml`.

---

## Pull Request Checklist

- [ ] `pytest` passes locally
- [ ] No new blocking `flake8` errors introduced
- [ ] New environment variables added to `.env.template` with a comment
- [ ] New background tasks registered in `utils/task_loader.py`
- [ ] DCO certified with a `Signed-off-by` tag (human contributors only)
Loading