feat(telegram): promo bot + channel-chat moderation gate
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
Add a second standalone promo bot to the bot container (answers /start with a localized message + a URL button into the main bot's Mini App) and gate write access in a channel's linked discussion chat: grant on join when the Telegram user is registered and neither admin-suspended nor holding a new chat_muted role, and revoke/grant on the matching moderation change for a member currently in the chat. Eligibility (registered AND NOT suspended AND NOT chat_muted; the game suspension dominates) is resolved once in the backend and reached two ways: the bot's join-time unary ResolveChatEligibility over the existing mTLS bot-link, and a backend chat_access_changed event -> gateway -> ChatGate command (idempotent; a temporary-block-expiry sweeper may over-emit). The bot guards the block/unblock path with getChatMember, since bots cannot list members. A web_app button cannot open another bot's Mini App (it signs initData with the sending bot's token), so the promo button is a t.me ?startapp URL reusing the UI's VITE_TELEGRAM_LINK. The bot must be a chat admin with the restrict-members right and chat_member in its allowed updates. No schema change: chat_muted reuses the data-driven account_roles table.
This commit is contained in:
@@ -37,6 +37,24 @@ type BotConfig struct {
|
||||
// GameChannelID is the chat id of the bot's game channel for the admin channel
|
||||
// post (TELEGRAM_GAME_CHANNEL_ID, optional; 0 disables channel posts).
|
||||
GameChannelID int64
|
||||
// ChatID is the chat id of the moderated discussion supergroup (a channel's linked
|
||||
// chat) whose write access the bot gates by registration and moderation
|
||||
// (TELEGRAM_CHAT_ID, optional; 0 disables chat gating). The bot must be an admin
|
||||
// there with the "Ban users" right, and "chat_member" in its allowed updates.
|
||||
ChatID int64
|
||||
// PromoBotToken is the API token of the optional standalone promo bot run in this
|
||||
// container — a second bot whose only job is to answer /start with a button that
|
||||
// opens the main bot's Mini App (TELEGRAM_PROMO_BOT_TOKEN, optional; empty disables
|
||||
// the promo bot).
|
||||
PromoBotToken string
|
||||
// BotUsername is the main bot's @username without the leading @, used in the promo
|
||||
// bot's message text (TELEGRAM_BOT_USERNAME; required when the promo bot runs).
|
||||
BotUsername string
|
||||
// BotLinkURL is the main bot's Mini App direct link — the same value the UI builds
|
||||
// share links from (VITE_TELEGRAM_LINK), e.g. https://t.me/<bot>/<app>. The promo
|
||||
// button appends ?startapp=<payload> to it (TELEGRAM_BOT_LINK; required when the
|
||||
// promo bot runs). It is distinct from the BotLink mTLS dial config below.
|
||||
BotLinkURL string
|
||||
// MiniAppURL is the HTTPS origin of the Mini App registered with BotFather; it is
|
||||
// the base of every launch button (TELEGRAM_MINIAPP_URL, required).
|
||||
MiniAppURL string
|
||||
@@ -114,6 +132,9 @@ func LoadBot() (BotConfig, error) {
|
||||
TestEnv: os.Getenv("TELEGRAM_TEST_ENV") == "true",
|
||||
OwnsUpdates: os.Getenv("TELEGRAM_OWNS_UPDATES") != "false",
|
||||
SendRatePerSecond: defaultSendRatePerSecond,
|
||||
PromoBotToken: os.Getenv("TELEGRAM_PROMO_BOT_TOKEN"),
|
||||
BotUsername: strings.TrimPrefix(os.Getenv("TELEGRAM_BOT_USERNAME"), "@"),
|
||||
BotLinkURL: os.Getenv("TELEGRAM_BOT_LINK"),
|
||||
LogLevel: envOr("TELEGRAM_LOG_LEVEL", "info"),
|
||||
BotLink: BotLinkClientConfig{
|
||||
GatewayAddr: os.Getenv("TELEGRAM_GATEWAY_ADDR"),
|
||||
@@ -128,6 +149,9 @@ func LoadBot() (BotConfig, error) {
|
||||
if cfg.GameChannelID, err = envInt64("TELEGRAM_GAME_CHANNEL_ID", 0); err != nil {
|
||||
return BotConfig{}, err
|
||||
}
|
||||
if cfg.ChatID, err = envInt64("TELEGRAM_CHAT_ID", 0); err != nil {
|
||||
return BotConfig{}, err
|
||||
}
|
||||
if cfg.SendRatePerSecond, err = envInt("TELEGRAM_SEND_RATE_PER_SECOND", defaultSendRatePerSecond); err != nil {
|
||||
return BotConfig{}, err
|
||||
}
|
||||
@@ -155,6 +179,9 @@ func LoadBot() (BotConfig, error) {
|
||||
if cfg.BotLink.CertFile == "" || cfg.BotLink.KeyFile == "" || cfg.BotLink.CAFile == "" {
|
||||
return BotConfig{}, fmt.Errorf("config: TELEGRAM_BOTLINK_TLS_CERT, _KEY and _CA are required")
|
||||
}
|
||||
if cfg.PromoBotToken != "" && (cfg.BotUsername == "" || cfg.BotLinkURL == "") {
|
||||
return BotConfig{}, fmt.Errorf("config: TELEGRAM_BOT_USERNAME and TELEGRAM_BOT_LINK are required when TELEGRAM_PROMO_BOT_TOKEN is set")
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user