Files
scrabble-game/gateway/internal/botlink/command.go
T
Ilia Denisov e71e40eef5
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
feat(telegram): promo bot + channel-chat moderation gate
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.
2026-06-21 14:46:51 +02:00

51 lines
1.6 KiB
Go

package botlink
import (
botlinkv1 "scrabble/pkg/proto/botlink/v1"
telegramv1 "scrabble/pkg/proto/telegram/v1"
)
// NotifyCommand builds an out-of-app push command rendered by the bot in the
// recipient's interface language.
func NotifyCommand(externalID, kind string, payload []byte, language string) *botlinkv1.Command {
return &botlinkv1.Command{
Payload: &botlinkv1.Command_Notify{Notify: &telegramv1.NotifyRequest{
ExternalId: externalID,
Kind: kind,
Payload: payload,
Language: language,
}},
}
}
// SendToUserCommand builds an admin text message addressed to one user.
func SendToUserCommand(externalID, text string) *botlinkv1.Command {
return &botlinkv1.Command{
Payload: &botlinkv1.Command_SendToUser{SendToUser: &telegramv1.SendToUserRequest{
ExternalId: externalID,
Text: text,
}},
}
}
// SendToGameChannelCommand builds an admin text message for the bot's game channel.
func SendToGameChannelCommand(text string) *botlinkv1.Command {
return &botlinkv1.Command{
Payload: &botlinkv1.Command_SendToChannel{SendToChannel: &telegramv1.SendToGameChannelRequest{
Text: text,
}},
}
}
// ChatGateCommand builds a chat-gate command that sets whether the Telegram user
// identified by externalID may write in the moderated discussion chat. The bot
// applies it only to a member currently in the chat (guarded on getChatMember).
func ChatGateCommand(externalID string, allow bool) *botlinkv1.Command {
return &botlinkv1.Command{
Payload: &botlinkv1.Command_ChatGate{ChatGate: &botlinkv1.ChatGateCommand{
ExternalId: externalID,
Allow: allow,
}},
}
}