5.4 KiB
5.4 KiB
Moderation & Punishment System
This document describes the punishment system implemented in the bot.
Database Schema
Defined in migrations/004_punishments.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:
- Numeric user ID.
@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:
-
Query — Calls
PunishmentRepo::list_expired(), which selects rows where:active = 1duration_seconds IS NOT NULLdatetime(created_at, '+' || duration_seconds || ' seconds') <= datetime('now')
-
Action per expired punishment:
ban— Callsunban_chat_member(chat_id, target_user_id)to lift the ban.mute— Callsrestrict_chat_memberwith restored permissions:SEND_MESSAGESSEND_MEDIA_MESSAGESSEND_OTHER_MESSAGESADD_WEB_PAGE_PREVIEWS
- Other types — No automatic Telegram action is taken.
-
Record update — Calls
repo.revoke(p.id, 0):- Sets
active = 0 - Sets
revoked_at = datetime('now') - Sets
revoked_by = 0(system)
- Sets
How Revoke Works
Manual Revoke (/rmute, /rban)
- The bot resolves the target user.
- Queries
get_active_for_chat_target(chat_id, target_user_id, action_type)to find the active punishment. - If found:
/rmute— Restores the user's chat permissions viarestrict_chat_member(...)./rban— Unbans the user viaunban_chat_member(...).- Calls
repo.revoke(p.id, admin_user_id)to mark the punishment inactive.
- 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)
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)