# 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 [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/?start=submitfwdid ``` --- ## Entering Submission Mode Users click the deep link or send: ``` /start submitfwdid ``` **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 ` 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 ` 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.