Huge refactor, submission system addition & security improvements. +Implementation of moderation cmds.
This commit is contained in:
149
docs/MODERATION.md
Normal file
149
docs/MODERATION.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Moderation & Punishment System
|
||||
|
||||
This document describes the punishment system implemented in the bot.
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
Defined in `migrations/004_punishments.sql`.
|
||||
|
||||
```sql
|
||||
CREATE TABLE punishments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chat_id INTEGER NOT NULL,
|
||||
target_user_id INTEGER NOT NULL,
|
||||
action_type TEXT NOT NULL, -- 'ban', 'mute', 'kick'
|
||||
duration_seconds INTEGER, -- NULL = permanent / indefinite
|
||||
reason TEXT,
|
||||
created_by INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT datetime('now'),
|
||||
revoked_at TEXT,
|
||||
revoked_by INTEGER,
|
||||
active INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE INDEX idx_punishments_chat_target ON punishments(chat_id, target_user_id);
|
||||
CREATE INDEX idx_punishments_active ON punishments(active);
|
||||
```
|
||||
|
||||
### Field Reference
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | `INTEGER` | Primary key. |
|
||||
| `chat_id` | `INTEGER` | The Telegram group/chat where the punishment was applied. |
|
||||
| `target_user_id` | `INTEGER` | The punished user's Telegram ID. |
|
||||
| `action_type` | `TEXT` | One of `ban`, `mute`, or `kick`. |
|
||||
| `duration_seconds` | `INTEGER` | Duration before auto-expiration. `NULL` means **permanent / indefinite**. |
|
||||
| `reason` | `TEXT` | Optional moderator-provided reason. |
|
||||
| `created_by` | `INTEGER` | Telegram ID of the moderator who issued the punishment. |
|
||||
| `created_at` | `TEXT` | ISO timestamp when the punishment was created. |
|
||||
| `revoked_at` | `TEXT` | ISO timestamp when the punishment was manually or automatically revoked. |
|
||||
| `revoked_by` | `INTEGER` | Telegram ID of the revoker, or `0` for the system (auto-expiry). |
|
||||
| `active` | `INTEGER` | `1` if the punishment is still in effect, `0` if revoked. |
|
||||
|
||||
---
|
||||
|
||||
## Command Reference
|
||||
|
||||
All punishment commands require the caller to be an **administrator or owner** of the group.
|
||||
|
||||
### Duration-based Punishments
|
||||
|
||||
| Command | Syntax | Description |
|
||||
|---------|--------|-------------|
|
||||
| `/sban` | `/sban @user <dur> <unit> [reason]` | Ban the user for a specific duration. |
|
||||
| `/smute` | `/smute @user <dur> <unit> [reason]` | Mute the user for a specific duration. |
|
||||
|
||||
### Permanent / Indefinite Punishments
|
||||
|
||||
| Command | Syntax | Description |
|
||||
|---------|--------|-------------|
|
||||
| `/mute` | `/mute @user [reason]` | Mute the user **indefinitely** (`duration_seconds = NULL`). |
|
||||
| `/pban` | `/pban @user [reason]` | Permanently ban the user (`duration_seconds = NULL`). |
|
||||
| `/kick` | `/kick @user [reason]` | Kick the user from the group. Always recorded with `NULL` duration. |
|
||||
|
||||
### Revoke Commands
|
||||
|
||||
| Command | Syntax | Description |
|
||||
|---------|--------|-------------|
|
||||
| `/rmute` | `/rmute @user` | Revoke the active mute for this user and restore chat permissions. |
|
||||
| `/rban` | `/rban @user` | Revoke the active ban for this user and unban them. |
|
||||
|
||||
### Target Resolution
|
||||
|
||||
The `@user` argument is resolved in the following order:
|
||||
1. Numeric user ID.
|
||||
2. `@username` — matched against chat administrators.
|
||||
|
||||
If the target cannot be resolved, the bot replies with *"Could not resolve target user."*
|
||||
|
||||
---
|
||||
|
||||
## Duration Units Reference
|
||||
|
||||
The `parse_duration` function in `crates/cgcx-bot/src/main.rs` accepts the following units (case-insensitive):
|
||||
|
||||
| Unit(s) | Seconds | Example |
|
||||
|---------|---------|---------|
|
||||
| `s`, `sec`, `secs`, `second`, `seconds` | 1 | `/sban @user 30 s spam` |
|
||||
| `m`, `min`, `mins`, `minute`, `minutes` | 60 | `/smute @user 10 m offtopic` |
|
||||
| `h`, `hr`, `hrs`, `hour`, `hours` | 3,600 | `/sban @user 24 h raid` |
|
||||
| `d`, `day`, `days` | 86,400 | `/sban @user 7 d trolling` |
|
||||
| `w`, `week`, `weeks` | 604,800 | `/smute @user 2 w` |
|
||||
| `mo`, `month`, `months` | 2,592,000 (30 days) | `/sban @user 1 mo` |
|
||||
| `y`, `year`, `years` | 31,536,000 (365 days) | `/pban @user 1 y` |
|
||||
|
||||
---
|
||||
|
||||
## How Expiration Works (Background Task)
|
||||
|
||||
When the bot starts, a background Tokio task is spawned that runs every **60 seconds**:
|
||||
|
||||
1. **Query** — Calls `PunishmentRepo::list_expired()`, which selects rows where:
|
||||
- `active = 1`
|
||||
- `duration_seconds IS NOT NULL`
|
||||
- `datetime(created_at, '+' || duration_seconds || ' seconds') <= datetime('now')`
|
||||
|
||||
2. **Action per expired punishment**:
|
||||
- **`ban`** — Calls `unban_chat_member(chat_id, target_user_id)` to lift the ban.
|
||||
- **`mute`** — Calls `restrict_chat_member` with restored permissions:
|
||||
- `SEND_MESSAGES`
|
||||
- `SEND_MEDIA_MESSAGES`
|
||||
- `SEND_OTHER_MESSAGES`
|
||||
- `ADD_WEB_PAGE_PREVIEWS`
|
||||
- **Other types** — No automatic Telegram action is taken.
|
||||
|
||||
3. **Record update** — Calls `repo.revoke(p.id, 0)`:
|
||||
- Sets `active = 0`
|
||||
- Sets `revoked_at = datetime('now')`
|
||||
- Sets `revoked_by = 0` (system)
|
||||
|
||||
---
|
||||
|
||||
## How Revoke Works
|
||||
|
||||
### Manual Revoke (`/rmute`, `/rban`)
|
||||
|
||||
1. The bot resolves the target user.
|
||||
2. Queries `get_active_for_chat_target(chat_id, target_user_id, action_type)` to find the active punishment.
|
||||
3. If found:
|
||||
- **`/rmute`** — Restores the user's chat permissions via `restrict_chat_member(...)`.
|
||||
- **`/rban`** — Unbans the user via `unban_chat_member(...)`.
|
||||
- Calls `repo.revoke(p.id, admin_user_id)` to mark the punishment inactive.
|
||||
4. If no active punishment is found, the bot replies with *"No active mute/ban found for this user."*
|
||||
|
||||
### Revoke via `PunishmentRepo::revoke(id, revoked_by)`
|
||||
|
||||
```sql
|
||||
UPDATE punishments
|
||||
SET active = 0,
|
||||
revoked_at = datetime('now'),
|
||||
revoked_by = ?1
|
||||
WHERE id = ?2;
|
||||
```
|
||||
|
||||
This is used both for:
|
||||
- **Automatic expiration** (`revoked_by = 0`)
|
||||
- **Manual moderator revocation** (`revoked_by = moderator_user_id`)
|
||||
Reference in New Issue
Block a user