diff --git a/platform/telegram/internal/promobot/promobot.go b/platform/telegram/internal/promobot/promobot.go
index 821e6dd..2109a0a 100644
--- a/platform/telegram/internal/promobot/promobot.go
+++ b/platform/telegram/internal/promobot/promobot.go
@@ -11,6 +11,7 @@ package promobot
import (
"cmp"
"context"
+ "html"
"net/url"
"strings"
@@ -112,13 +113,16 @@ func (t *Bot) handleStart(ctx context.Context, api *tgbot.Bot, update *models.Up
if update.Message.From != nil {
lang = update.Message.From.LanguageCode
}
- text, button := promoText(lang, t.username)
+ // The configured campaign payload (a variant-seed deep link) takes precedence; absent
+ // 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{
- 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))),
+ ChatID: update.Message.Chat.ID,
+ Text: text,
+ ParseMode: models.ParseModeHTML,
+ ReplyMarkup: t.launchMarkup(button, param),
}); err != nil {
t.log.Warn("promo: reply to start failed", zap.Error(err))
}
@@ -169,11 +173,15 @@ func startPayload(text string) string {
return strings.TrimSpace(strings.TrimPrefix(text, cmd))
}
-// promoText returns the localized message body and button label, naming the main bot
-// (Russian for a "ru" language code, English otherwise).
-func promoText(lang, username string) (text, button string) {
+// promoText returns the localized message body and button label. The body names the main
+// bot as a clickable @username whose link is the Mini App deep link (launchURL, the same
+// target as the button), so tapping the mention opens the seeded Mini App rather than the
+// 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 := `@` + html.EscapeString(username) + ``
if strings.HasPrefix(strings.ToLower(lang), "ru") {
- return "Откройте @" + username + " и выберите в настройках профиля нужный вариант игры.", "🤩 Хочу играть!"
+ return "Откройте " + mention + " и выберите в настройках профиля нужный вариант игры.", "🤩 Хочу играть!"
}
- return "Open @" + username + " and choose your game variant in the profile settings.", "🤩 I want to play!"
+ return "Open " + mention + " and choose your game variant in the profile settings.", "🤩 I want to play!"
}
diff --git a/platform/telegram/internal/promobot/promobot_test.go b/platform/telegram/internal/promobot/promobot_test.go
index 4786ed9..4e719d4 100644
--- a/platform/telegram/internal/promobot/promobot_test.go
+++ b/platform/telegram/internal/promobot/promobot_test.go
@@ -13,25 +13,30 @@ import (
)
func TestPromoTextLocalization(t *testing.T) {
- en, enBtn := promoText("en", "ScrabbleBot")
- if !strings.Contains(en, "@ScrabbleBot") || !strings.Contains(en, "profile settings") {
+ const url = "https://t.me/bot/app?startapp=verudit_ru-scrabble_en"
+ // The @username is rendered as a clickable deep link (the same target as the button),
+ // not a plain mention, so tapping it opens the seeded Mini App.
+ wantLink := `@ScrabbleBot`
+
+ en, enBtn := promoText("en", "ScrabbleBot", url)
+ if !strings.Contains(en, wantLink) || !strings.Contains(en, "profile settings") {
t.Errorf("en text = %q", en)
}
if enBtn != "🤩 I want to play!" {
t.Errorf("en button = %q", enBtn)
}
- ru, ruBtn := promoText("ru-RU", "ScrabbleBot")
- if !strings.Contains(ru, "@ScrabbleBot") || !strings.Contains(ru, "Откройте") {
+ ru, ruBtn := promoText("ru-RU", "ScrabbleBot", url)
+ if !strings.Contains(ru, wantLink) || !strings.Contains(ru, "Откройте") {
t.Errorf("ru text = %q", ru)
}
if ruBtn != "🤩 Хочу играть!" {
t.Errorf("ru button = %q", ruBtn)
}
- // An unknown language falls back to English.
- if got, _ := promoText("de", "B"); !strings.Contains(got, "Open @B") {
- t.Errorf("fallback text = %q, want English", got)
+ // An unknown language falls back to English, still with the linked mention.
+ if got, _ := promoText("de", "B", url); !strings.Contains(got, "Open ") || !strings.Contains(got, `">@B`) {
+ t.Errorf("fallback text = %q, want English with a linked mention", got)
}
}
@@ -93,8 +98,8 @@ func TestHandleStartReplies(t *testing.T) {
if api.chatID != "42" {
t.Errorf("chat_id = %q, want 42", api.chatID)
}
- if !strings.Contains(api.text, "@ScrabbleBot") {
- t.Errorf("text = %q, want the @mention", api.text)
+ if !strings.Contains(api.text, `@ScrabbleBot`) {
+ t.Errorf("text = %q, want the @mention linked to the startapp deep link", api.text)
}
if strings.Contains(api.replyMarkup, "web_app") {
t.Errorf("reply_markup = %q has a web_app button; want a url button", api.replyMarkup)