feat(telegram): promo deep-link seeds English Scrabble for new users
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 50s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 50s
The promo bot button carries a configurable variant-seed start-param (default verudit_ru-scrabble_en). The gateway parses start_param from the validated initData and forwards it; the backend, on first contact only, seeds the new account variant_preferences from it (English Scrabble alongside the default Erudit). No schema change (the scrabble_en CHECK is already in the baseline) and the gateway<->backend REST field is additive, so the rolling deploy is safe in either order. TELEGRAM_PROMO_START_PARAM configures the payload (empty forwards the user own /start payload). Covered by account unit tests, a gateway transcode test, and an integration test asserting new-only seeding.
This commit is contained in:
@@ -65,6 +65,12 @@ type BotConfig struct {
|
||||
// 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
|
||||
// PromoStartParam is the promo button's launch payload, appended as
|
||||
// ?startapp=<PromoStartParam> (TELEGRAM_PROMO_START_PARAM, default
|
||||
// "verudit_ru-scrabble_en"). It is a variant-seed deep link the backend decodes to
|
||||
// seed a brand-new user's variant preferences; its labels must match the backend's
|
||||
// known variants. Empty falls back to forwarding the user's own /start payload.
|
||||
PromoStartParam 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
|
||||
@@ -113,6 +119,10 @@ const (
|
||||
defaultValidatorGRPCAddr = ":9091"
|
||||
defaultBotReconnectDelay = 2 * time.Second
|
||||
defaultSendRatePerSecond = 25
|
||||
// defaultPromoStartParam is the promo button's default launch payload: a variant-seed
|
||||
// deep link the backend decodes to add English Scrabble to a brand-new user's variant
|
||||
// preferences alongside the default Erudit. Override with TELEGRAM_PROMO_START_PARAM.
|
||||
defaultPromoStartParam = "verudit_ru-scrabble_en"
|
||||
)
|
||||
|
||||
// LoadValidator reads the validator configuration from the environment.
|
||||
@@ -145,6 +155,7 @@ func LoadBot() (BotConfig, error) {
|
||||
PromoBotToken: os.Getenv("TELEGRAM_PROMO_BOT_TOKEN"),
|
||||
BotUsername: strings.TrimPrefix(os.Getenv("TELEGRAM_BOT_USERNAME"), "@"),
|
||||
BotLinkURL: os.Getenv("TELEGRAM_BOT_LINK"),
|
||||
PromoStartParam: envOr("TELEGRAM_PROMO_START_PARAM", defaultPromoStartParam),
|
||||
SupportStateDir: envOr("TELEGRAM_SUPPORT_STATE_DIR", "/data"),
|
||||
LogLevel: envOr("TELEGRAM_LOG_LEVEL", "info"),
|
||||
BotLink: BotLinkClientConfig{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package promobot
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -33,6 +34,11 @@ type Config struct {
|
||||
// BotLinkURL is the main bot's Mini App direct link; the button appends
|
||||
// ?startapp=<payload> to it.
|
||||
BotLinkURL string
|
||||
// StartParam is the campaign payload appended as ?startapp=<StartParam> to the launch
|
||||
// button — e.g. a variant-seed deep link ("verudit_ru-scrabble_en") the backend
|
||||
// decodes to seed a brand-new user's variant preferences. Empty falls back to
|
||||
// forwarding the user's own /start payload.
|
||||
StartParam string
|
||||
// SendRatePerSecond caps outbound sends to respect the Bot API flood limits; 0
|
||||
// disables the limiter. The burst equals the per-second rate.
|
||||
SendRatePerSecond int
|
||||
@@ -40,11 +46,12 @@ type Config struct {
|
||||
|
||||
// Bot is the promo bot wrapper around a Telegram Bot API client.
|
||||
type Bot struct {
|
||||
api *tgbot.Bot
|
||||
username string
|
||||
linkURL string
|
||||
log *zap.Logger
|
||||
limiter *rate.Limiter
|
||||
api *tgbot.Bot
|
||||
username string
|
||||
linkURL string
|
||||
startParam string
|
||||
log *zap.Logger
|
||||
limiter *rate.Limiter
|
||||
}
|
||||
|
||||
// New builds the promo bot, registering a /start (and default) handler that replies
|
||||
@@ -53,7 +60,7 @@ func New(cfg Config, log *zap.Logger) (*Bot, error) {
|
||||
if log == nil {
|
||||
log = zap.NewNop()
|
||||
}
|
||||
t := &Bot{username: cfg.BotUsername, linkURL: cfg.BotLinkURL, log: log}
|
||||
t := &Bot{username: cfg.BotUsername, linkURL: cfg.BotLinkURL, startParam: cfg.StartParam, log: log}
|
||||
if cfg.SendRatePerSecond > 0 {
|
||||
t.limiter = rate.NewLimiter(rate.Limit(cfg.SendRatePerSecond), cfg.SendRatePerSecond)
|
||||
}
|
||||
@@ -87,7 +94,8 @@ func (t *Bot) Run(ctx context.Context) {
|
||||
}
|
||||
|
||||
// handleStart replies to any message (typically /start) with the localized promo text
|
||||
// and a button that opens the main bot's Mini App, forwarding any /start payload.
|
||||
// and a button that opens the main bot's Mini App at the configured campaign payload
|
||||
// (falling back to forwarding any /start payload the user arrived with).
|
||||
func (t *Bot) handleStart(ctx context.Context, api *tgbot.Bot, update *models.Update) {
|
||||
if update.Message == nil {
|
||||
return
|
||||
@@ -106,9 +114,11 @@ func (t *Bot) handleStart(ctx context.Context, api *tgbot.Bot, update *models.Up
|
||||
}
|
||||
text, button := promoText(lang, t.username)
|
||||
if _, err := api.SendMessage(ctx, &tgbot.SendMessageParams{
|
||||
ChatID: update.Message.Chat.ID,
|
||||
Text: text,
|
||||
ReplyMarkup: t.launchMarkup(button, startPayload(update.Message.Text)),
|
||||
ChatID: update.Message.Chat.ID,
|
||||
Text: text,
|
||||
// The configured campaign payload (a variant-seed deep link) takes precedence;
|
||||
// absent one, fall back to forwarding any /start payload the user arrived with.
|
||||
ReplyMarkup: t.launchMarkup(button, cmp.Or(t.startParam, startPayload(update.Message.Text))),
|
||||
}); err != nil {
|
||||
t.log.Warn("promo: reply to start failed", zap.Error(err))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user