Compare commits

..

1 Commits

Author SHA1 Message Date
Ilia Denisov 33064d22a3 tmp: debug-log telegram language_code on contour (REVERT before merge)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m14s
Temporary: traces the language_code Telegram sends + the seeded variant_preferences, to settle why a new account lands in Russian despite an English interface. Test contour only; force-pushed away before merge so it never reaches master.
2026-06-23 22:22:22 +02:00
4 changed files with 30 additions and 36 deletions
+9
View File
@@ -46,6 +46,15 @@ func (s *Server) handleTelegramAuth(c *gin.Context) {
s.abortErr(c, err) s.abortErr(c, err)
return return
} }
// TEMP DEBUG (revert before merge): trace the language_code Telegram actually sent and
// what we seeded, to settle the "ru interface" question on the test contour.
s.log.Info("telegram auth debug",
zap.String("external_id", req.ExternalID),
zap.String("language_code", req.LanguageCode),
zap.String("username", req.Username),
zap.String("start_param", req.StartParam),
zap.Bool("created", created),
zap.Strings("variant_preferences", acc.VariantPreferences))
if created { if created {
// First registration: re-evaluate moderated-chat write access, so a user who // First registration: re-evaluate moderated-chat write access, so a user who
// joined the chat before registering is granted on the spot (no chat_member // joined the chat before registering is granted on the spot (no chat_member
+1 -3
View File
@@ -86,9 +86,7 @@ Telegram identity to an account from a browser. Both map a rejection to gRPC
self-contained: no bot-link, no gateway, no game. Its `?startapp` payload self-contained: no bot-link, no gateway, no game. Its `?startapp` payload
(`TELEGRAM_PROMO_START_PARAM`, default `verudit_ru-scrabble_en`) is a variant-seed deep (`TELEGRAM_PROMO_START_PARAM`, default `verudit_ru-scrabble_en`) is a variant-seed deep
link the backend decodes to seed a brand-new user's variant preferences (English Scrabble link the backend decodes to seed a brand-new user's variant preferences (English Scrabble
alongside the default Erudit). The message body also renders the bot's `@username` as that alongside the default Erudit).
same deep link (an HTML `text_link`), so tapping the mention — not just the button — opens
the seeded Mini App rather than the bot profile.
- **Rate limiting.** Outbound sends are throttled (`TELEGRAM_SEND_RATE_PER_SECOND`, - **Rate limiting.** Outbound sends are throttled (`TELEGRAM_SEND_RATE_PER_SECOND`,
default 25) to respect the Bot API flood limits. default 25) to respect the Bot API flood limits.
+11 -19
View File
@@ -11,7 +11,6 @@ package promobot
import ( import (
"cmp" "cmp"
"context" "context"
"html"
"net/url" "net/url"
"strings" "strings"
@@ -113,16 +112,13 @@ func (t *Bot) handleStart(ctx context.Context, api *tgbot.Bot, update *models.Up
if update.Message.From != nil { if update.Message.From != nil {
lang = update.Message.From.LanguageCode lang = update.Message.From.LanguageCode
} }
// The configured campaign payload (a variant-seed deep link) takes precedence; absent text, button := promoText(lang, t.username)
// one, fall back to forwarding any /start payload the user arrived with. The same
// payload backs both the inline button and the @username link in the body.
param := cmp.Or(t.startParam, startPayload(update.Message.Text))
text, button := promoText(lang, t.username, t.launchURL(param))
if _, err := api.SendMessage(ctx, &tgbot.SendMessageParams{ if _, err := api.SendMessage(ctx, &tgbot.SendMessageParams{
ChatID: update.Message.Chat.ID, ChatID: update.Message.Chat.ID,
Text: text, Text: text,
ParseMode: models.ParseModeHTML, // The configured campaign payload (a variant-seed deep link) takes precedence;
ReplyMarkup: t.launchMarkup(button, param), // 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 { }); err != nil {
t.log.Warn("promo: reply to start failed", zap.Error(err)) t.log.Warn("promo: reply to start failed", zap.Error(err))
} }
@@ -173,15 +169,11 @@ func startPayload(text string) string {
return strings.TrimSpace(strings.TrimPrefix(text, cmd)) return strings.TrimSpace(strings.TrimPrefix(text, cmd))
} }
// promoText returns the localized message body and button label. The body names the main // promoText returns the localized message body and button label, naming the main bot
// bot as a clickable @username whose link is the Mini App deep link (launchURL, the same // (Russian for a "ru" language code, English otherwise).
// target as the button), so tapping the mention opens the seeded Mini App rather than the func promoText(lang, username string) (text, button string) {
// bot profile. The body is sent with ParseMode HTML; only the link carries markup, so the
// static sentences need no escaping. Russian for a "ru" language code, English otherwise.
func promoText(lang, username, launchURL string) (text, button string) {
mention := `<a href="` + html.EscapeString(launchURL) + `">@` + html.EscapeString(username) + `</a>`
if strings.HasPrefix(strings.ToLower(lang), "ru") { if strings.HasPrefix(strings.ToLower(lang), "ru") {
return "Откройте " + mention + " и выберите в настройках профиля нужный вариант игры.", "🤩 Хочу играть!" return "Откройте @" + username + " и выберите в настройках профиля нужный вариант игры.", "🤩 Хочу играть!"
} }
return "Open " + mention + " and choose your game variant in the profile settings.", "🤩 I want to play!" return "Open @" + username + " and choose your game variant in the profile settings.", "🤩 I want to play!"
} }
@@ -13,30 +13,25 @@ import (
) )
func TestPromoTextLocalization(t *testing.T) { func TestPromoTextLocalization(t *testing.T) {
const url = "https://t.me/bot/app?startapp=verudit_ru-scrabble_en" en, enBtn := promoText("en", "ScrabbleBot")
// The @username is rendered as a clickable deep link (the same target as the button), if !strings.Contains(en, "@ScrabbleBot") || !strings.Contains(en, "profile settings") {
// not a plain mention, so tapping it opens the seeded Mini App.
wantLink := `<a href="` + url + `">@ScrabbleBot</a>`
en, enBtn := promoText("en", "ScrabbleBot", url)
if !strings.Contains(en, wantLink) || !strings.Contains(en, "profile settings") {
t.Errorf("en text = %q", en) t.Errorf("en text = %q", en)
} }
if enBtn != "🤩 I want to play!" { if enBtn != "🤩 I want to play!" {
t.Errorf("en button = %q", enBtn) t.Errorf("en button = %q", enBtn)
} }
ru, ruBtn := promoText("ru-RU", "ScrabbleBot", url) ru, ruBtn := promoText("ru-RU", "ScrabbleBot")
if !strings.Contains(ru, wantLink) || !strings.Contains(ru, "Откройте") { if !strings.Contains(ru, "@ScrabbleBot") || !strings.Contains(ru, "Откройте") {
t.Errorf("ru text = %q", ru) t.Errorf("ru text = %q", ru)
} }
if ruBtn != "🤩 Хочу играть!" { if ruBtn != "🤩 Хочу играть!" {
t.Errorf("ru button = %q", ruBtn) t.Errorf("ru button = %q", ruBtn)
} }
// An unknown language falls back to English, still with the linked mention. // An unknown language falls back to English.
if got, _ := promoText("de", "B", url); !strings.Contains(got, "Open ") || !strings.Contains(got, `">@B</a>`) { if got, _ := promoText("de", "B"); !strings.Contains(got, "Open @B") {
t.Errorf("fallback text = %q, want English with a linked mention", got) t.Errorf("fallback text = %q, want English", got)
} }
} }
@@ -98,8 +93,8 @@ func TestHandleStartReplies(t *testing.T) {
if api.chatID != "42" { if api.chatID != "42" {
t.Errorf("chat_id = %q, want 42", api.chatID) t.Errorf("chat_id = %q, want 42", api.chatID)
} }
if !strings.Contains(api.text, `<a href="https://t.me/bot/app?startapp=f99">@ScrabbleBot</a>`) { if !strings.Contains(api.text, "@ScrabbleBot") {
t.Errorf("text = %q, want the @mention linked to the startapp deep link", api.text) t.Errorf("text = %q, want the @mention", api.text)
} }
if strings.Contains(api.replyMarkup, "web_app") { if strings.Contains(api.replyMarkup, "web_app") {
t.Errorf("reply_markup = %q has a web_app button; want a url button", api.replyMarkup) t.Errorf("reply_markup = %q has a web_app button; want a url button", api.replyMarkup)