Huge refactor, submission system addition & security improvements. +Implementation of moderation cmds.
This commit is contained in:
223
docs/FORWARD_SYSTEM.md
Normal file
223
docs/FORWARD_SYSTEM.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Forward Submission System
|
||||
|
||||
This document describes the submission-forward flow that allows users to upload content through the bot for moderator review before it is posted to a destination channel or group.
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
Defined in `migrations/003_forward_system.sql`.
|
||||
|
||||
### `forward_definitions`
|
||||
|
||||
```sql
|
||||
CREATE TABLE forward_definitions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_user_id INTEGER NOT NULL,
|
||||
source_chat_id INTEGER NOT NULL,
|
||||
destination_chat_id INTEGER NOT NULL,
|
||||
review_group_id INTEGER NOT NULL,
|
||||
forward_message TEXT NOT NULL DEFAULT '',
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
share_mode TEXT NOT NULL DEFAULT 'b',
|
||||
revoked_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT datetime('now')
|
||||
);
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `id` | Primary key. |
|
||||
| `creator_user_id` | Telegram ID of the admin who created the forward. |
|
||||
| `source_chat_id` | The group/chat where `/create_submit_forward` was invoked. |
|
||||
| `destination_chat_id` | The target channel/group where approved content is posted. |
|
||||
| `review_group_id` | The moderator group where submissions are sent for review. |
|
||||
| `forward_message` | Optional template text prepended to approved posts. |
|
||||
| `code` | Unique 16-character alphanumeric access code. |
|
||||
| `share_mode` | `'b'` = blacklist mode (default), `'w'` = whitelist mode. |
|
||||
| `revoked_at` | Timestamp if the forward link was revoked; `NULL` while active. |
|
||||
|
||||
**Indexes:** `idx_forward_code`, `idx_forward_source`.
|
||||
|
||||
### `forward_submissions`
|
||||
|
||||
```sql
|
||||
CREATE TABLE forward_submissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
forward_id INTEGER NOT NULL REFERENCES forward_definitions(id),
|
||||
user_id INTEGER NOT NULL,
|
||||
content_id TEXT NOT NULL REFERENCES contents(id),
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
review_message_id INTEGER,
|
||||
created_at TEXT NOT NULL DEFAULT datetime('now'),
|
||||
resolved_at TEXT,
|
||||
resolver_id INTEGER
|
||||
);
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `id` | Primary key (submission number). |
|
||||
| `forward_id` | The forward this submission belongs to. |
|
||||
| `user_id` | Telegram ID of the submitting user. |
|
||||
| `content_id` | The uploaded content entry (`contents.id`). |
|
||||
| `status` | `pending`, `approved`, `ignored`, or `blacklisted`. |
|
||||
| `review_message_id` | Telegram message ID of the review post in the review group. |
|
||||
| `resolved_at` | Timestamp when a moderator acted on the submission. |
|
||||
| `resolver_id` | Telegram ID of the moderator who resolved it. |
|
||||
|
||||
**Indexes:** `idx_fwd_sub_forward`, `idx_fwd_sub_user`, `idx_fwd_sub_status`.
|
||||
|
||||
### `forward_lists`
|
||||
|
||||
```sql
|
||||
CREATE TABLE forward_lists (
|
||||
forward_id INTEGER NOT NULL REFERENCES forward_definitions(id),
|
||||
user_id INTEGER NOT NULL,
|
||||
list_type TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT datetime('now'),
|
||||
PRIMARY KEY (forward_id, user_id, list_type)
|
||||
);
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `forward_id` | The forward definition. |
|
||||
| `user_id` | The affected user. |
|
||||
| `list_type` | `blacklist` or `allow`. |
|
||||
|
||||
This table implements **per-forward** scoped access control.
|
||||
|
||||
---
|
||||
|
||||
## Creating a Forward (`/create_submit_forward`)
|
||||
|
||||
**Usage (group-only, admin-gated):**
|
||||
```
|
||||
/create_submit_forward <destination_chat_id> <review_group_id> [forward_message]
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
1. Caller must be an **administrator or owner** of the source group.
|
||||
2. The bot must be an **administrator** in both the `destination_chat_id` and `review_group_id`.
|
||||
|
||||
**What happens:**
|
||||
1. A 16-character alphanumeric `code` is generated (`generate_forward_code`).
|
||||
2. A row is inserted into `forward_definitions` with:
|
||||
- `source_chat_id` = current chat
|
||||
- `share_mode` = `'b'` (blacklist mode)
|
||||
3. The bot replies with a deep-link URL:
|
||||
```
|
||||
https://t.me/<bot_username>?start=submitfwdid<code>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entering Submission Mode
|
||||
|
||||
Users click the deep link or send:
|
||||
```
|
||||
/start submitfwdid<CODE>
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
1. The code is looked up in `forward_definitions`.
|
||||
2. If the forward has been revoked (`revoked_at IS NOT NULL`), the user is told the link is revoked.
|
||||
3. The scoped access check `ForwardRepo::is_allowed(forward_id, user_id)` is performed:
|
||||
- The creator is always allowed.
|
||||
- In **blacklist mode** (`'b'`): allowed unless the user has a `blacklist` entry.
|
||||
- In **whitelist mode** (`'w'`): allowed only if the user has an `allow` entry.
|
||||
4. If allowed, the bot enters `BotState::SubmitMode { forward_id, code }` and presents **Continue / Exit** buttons.
|
||||
|
||||
---
|
||||
|
||||
## Submission Flow
|
||||
|
||||
1. **Continue** — The user is transitioned to `BotState::MainMenu { pending_forward_id: Some(forward_id) }`. All uploads staged from this point are tagged with the pending forward ID.
|
||||
2. **Upload** — The user stages files (media, documents, or text) and confirms options just like a normal upload.
|
||||
3. **Finalize** — When the user confirms, `finalize_upload`:
|
||||
- Creates and encrypts the content entry.
|
||||
- Inserts a row into `forward_submissions` with `status = 'pending'`.
|
||||
- Posts a review message to the `review_group_id` with inline buttons:
|
||||
- `[ Approve ]` → callback `v1:fwd:approve:{submission_id}`
|
||||
- `[ Ignore ]` → callback `v1:fwd:ignore:{submission_id}`
|
||||
- `[ Blacklist User ]` → callback `v1:fwd:blk:{submission_id}`
|
||||
- Stores the sent message ID back into `forward_submissions.review_message_id`.
|
||||
4. **Review** — Moderators in the review group click the buttons to act.
|
||||
|
||||
---
|
||||
|
||||
## Review Actions
|
||||
|
||||
All review callbacks require the clicking user to be an **administrator in the review group** (`is_admin_in_chat`).
|
||||
|
||||
### Approve (`v1:fwd:approve`)
|
||||
|
||||
1. Generates a random 12-character direct-access password (`generate_direct_password`).
|
||||
2. Hashes the password with Argon2 and stores it in `contents.password_hash`.
|
||||
3. Builds the direct link: `{base_url}/?cxid={content_id}&sc={password}`.
|
||||
4. Posts the link to the destination chat, prefixed with `forward_message` (if set).
|
||||
5. DM the submitter:
|
||||
- "Your submission was approved."
|
||||
- Includes the posted message URL and the direct access link.
|
||||
6. Edits the review message to show `[ APPROVED ]` and the moderator ID.
|
||||
7. Sets `forward_submissions.status = 'approved'`.
|
||||
|
||||
### Ignore (`v1:fwd:ignore`)
|
||||
|
||||
1. DM the submitter: "Your submission was rejected."
|
||||
2. Edits the review message to show `[ IGNORED ]` and the moderator ID.
|
||||
3. Sets `forward_submissions.status = 'ignored'`.
|
||||
|
||||
### Blacklist User (`v1:fwd:blk`)
|
||||
|
||||
1. Adds the submitter to `forward_lists` with `list_type = 'blacklist'` for this forward.
|
||||
2. Edits the review message to show `[ BLACKLISTED ]` and the moderator ID.
|
||||
3. Sets `forward_submissions.status = 'blacklisted'`.
|
||||
4. The user is now blocked from using this forward link again (until removed).
|
||||
|
||||
---
|
||||
|
||||
## Management Commands
|
||||
|
||||
All group-only, admin-gated.
|
||||
|
||||
### `/show_c_forward [page]`
|
||||
|
||||
Lists forward links created in the current source chat (5 per page).
|
||||
- Shows code, destination chat ID, review group ID, and status (`Active` or `Revoked`).
|
||||
- Active forwards include an inline `[ Revoke ]` button.
|
||||
- Pagination via `<<` / `>>` buttons.
|
||||
|
||||
### `/add_blacklist <user_id>`
|
||||
|
||||
Iterates all **active** forwards for the current source chat and inserts the user into each forward's `forward_lists` as `blacklist`.
|
||||
Replies with the count of forwards affected.
|
||||
|
||||
### `/rm_blacklist <user_id>`
|
||||
|
||||
Iterates all **active** forwards for the current source chat and removes the user from each forward's `forward_lists` where `list_type = 'blacklist'`.
|
||||
Replies with the count of forwards affected.
|
||||
|
||||
---
|
||||
|
||||
## Scoped Access Model
|
||||
|
||||
Each forward has its own independent access list stored in `forward_lists`.
|
||||
|
||||
| `share_mode` | Behavior |
|
||||
|--------------|----------|
|
||||
| `'b'` (blacklist) | **Default.** Everyone is allowed unless explicitly blacklisted. |
|
||||
| `'w'` (whitelist) | Only explicitly allowed users (and the creator) may submit. |
|
||||
|
||||
**Note:** The `share_mode` is stored per forward but there is currently no admin command to change it after creation; it defaults to `'b'` at creation time.
|
||||
|
||||
---
|
||||
|
||||
## Revoking a Forward
|
||||
|
||||
Admins or the creator can revoke a forward via the `[ Revoke ]` button on `/show_c_forward`.
|
||||
- Validates the forward belongs to the current chat.
|
||||
- Requires creator status **or** admin status.
|
||||
- Sets `revoked_at = datetime('now')`.
|
||||
- Revoked forwards reject new submissions immediately.
|
||||
Reference in New Issue
Block a user