Huge refactor, submission system addition & security improvements. +Implementation of moderation cmds.

This commit is contained in:
unknown
2026-05-22 21:46:06 +02:00
parent 12a0035699
commit 2129081599
32 changed files with 3426 additions and 106 deletions

149
docs/MODERATION.md Normal file
View 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`)