8.1 KiB
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
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
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
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:
- Caller must be an administrator or owner of the source group.
- The bot must be an administrator in both the
destination_chat_idandreview_group_id.
What happens:
- A 16-character alphanumeric
codeis generated (generate_forward_code). - A row is inserted into
forward_definitionswith:source_chat_id= current chatshare_mode='b'(blacklist mode)
- 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:
- The code is looked up in
forward_definitions. - If the forward has been revoked (
revoked_at IS NOT NULL), the user is told the link is revoked. - 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 ablacklistentry. - In whitelist mode (
'w'): allowed only if the user has anallowentry.
- If allowed, the bot enters
BotState::SubmitMode { forward_id, code }and presents Continue / Exit buttons.
Submission Flow
- 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. - Upload — The user stages files (media, documents, or text) and confirms options just like a normal upload.
- Finalize — When the user confirms,
finalize_upload:- Creates and encrypts the content entry.
- Inserts a row into
forward_submissionswithstatus = 'pending'. - Posts a review message to the
review_group_idwith inline buttons:[ Approve ]→ callbackv1:fwd:approve:{submission_id}[ Ignore ]→ callbackv1:fwd:ignore:{submission_id}[ Blacklist User ]→ callbackv1:fwd:blk:{submission_id}
- Stores the sent message ID back into
forward_submissions.review_message_id.
- 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)
- Generates a random 12-character direct-access password (
generate_direct_password). - Hashes the password with Argon2 and stores it in
contents.password_hash. - Builds the direct link:
{base_url}/?cxid={content_id}&sc={password}. - Posts the link to the destination chat, prefixed with
forward_message(if set). - DM the submitter:
- "Your submission was approved."
- Includes the posted message URL and the direct access link.
- Edits the review message to show
[ APPROVED ]and the moderator ID. - Sets
forward_submissions.status = 'approved'.
Ignore (v1:fwd:ignore)
- DM the submitter: "Your submission was rejected."
- Edits the review message to show
[ IGNORED ]and the moderator ID. - Sets
forward_submissions.status = 'ignored'.
Blacklist User (v1:fwd:blk)
- Adds the submitter to
forward_listswithlist_type = 'blacklist'for this forward. - Edits the review message to show
[ BLACKLISTED ]and the moderator ID. - Sets
forward_submissions.status = 'blacklisted'. - 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 (
ActiveorRevoked). - 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.