# 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}` - `[ Blackl. ]` → callback `v1:fwd:blk:{submission_id}` - `[ Ban ]` → callback `v1:fwd:ban:{submission_id}` - `[ Ban/BL u. ]` → callback `v1:fwd:banblk:{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. **Note:** Media batching is implemented for both review group presentation and approved destination posts. The bot decrypts and sends up to 10 files per media group, attaching the review text or approval caption to the last item. --- ## 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). ### Ban (`v1:fwd:ban`) 1. Bans the submitter in both the destination chat and the review group. 2. Records `ban` punishments in the `punishments` table for both chats. 3. Edits the review message to show `[ BANNED ]` and the moderator ID. 4. Sets `forward_submissions.status = 'banned'`. ### Ban + Blacklist (`v1:fwd:banblk`) 1. Bans the submitter in both the destination chat and the review group (same as `v1:fwd:ban`). 2. Records `ban` punishments in the `punishments` table for both chats. 3. Also adds the submitter to `forward_lists` with `list_type = 'blacklist'` (same as `v1:fwd:blk`). 4. Edits the review message to show `[ BAN/BL ]` and the moderator ID. 5. Sets `forward_submissions.status = 'banblk'`. --- ## 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.