diff --git a/platform/telegram/internal/bot/bot.go b/platform/telegram/internal/bot/bot.go index 4b5e56d..6cde9d4 100644 --- a/platform/telegram/internal/bot/bot.go +++ b/platform/telegram/internal/bot/bot.go @@ -191,6 +191,13 @@ func (t *Bot) handleStart(ctx context.Context, api *tgbot.Bot, update *models.Up if update.Message == nil { return } + // Reply only in a private chat: the Mini App launch button is an inline web_app + // button, which Telegram permits only in private chats — replying to a group message + // (the bot is an admin in the moderated chat and now receives its messages) fails with + // BUTTON_TYPE_INVALID. In the group the bot only manages permissions, it never chats. + if update.Message.Chat.Type != models.ChatTypePrivate { + return + } startParam := startPayload(update.Message.Text) if _, err := api.SendMessage(ctx, &tgbot.SendMessageParams{ ChatID: update.Message.Chat.ID, diff --git a/platform/telegram/internal/bot/bot_test.go b/platform/telegram/internal/bot/bot_test.go index 57b6521..15e883a 100644 --- a/platform/telegram/internal/bot/bot_test.go +++ b/platform/telegram/internal/bot/bot_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/go-telegram/bot/models" "go.uber.org/zap" ) @@ -103,6 +104,29 @@ func TestTestEnvironmentRoutesGetMe(t *testing.T) { } } +func TestHandleStartRepliesPrivateOnly(t *testing.T) { + t.Run("private replies", func(t *testing.T) { + api := &fakeBotAPI{} + b := newTestBot(t, api) + b.handleStart(context.Background(), b.api, &models.Update{Message: &models.Message{ + Chat: models.Chat{ID: 42, Type: models.ChatTypePrivate}, Text: "/start g7", + }}) + if api.chatID != "42" || !strings.Contains(api.replyMarkup, "web_app") { + t.Errorf("private /start: chat=%q markup=%q, want a web_app reply", api.chatID, api.replyMarkup) + } + }) + t.Run("group ignored", func(t *testing.T) { + api := &fakeBotAPI{} + b := newTestBot(t, api) + b.handleStart(context.Background(), b.api, &models.Update{Message: &models.Message{ + Chat: models.Chat{ID: -100, Type: models.ChatTypeSupergroup}, Text: "/start", + }}) + if api.chatID != "" { + t.Errorf("group /start got a reply (chat=%q); an inline web_app button is invalid in groups", api.chatID) + } + }) +} + func TestStartPayload(t *testing.T) { cases := map[string]string{ "/start g123": "g123", diff --git a/platform/telegram/internal/promobot/promobot.go b/platform/telegram/internal/promobot/promobot.go index f4911ff..4fe20a6 100644 --- a/platform/telegram/internal/promobot/promobot.go +++ b/platform/telegram/internal/promobot/promobot.go @@ -92,6 +92,11 @@ func (t *Bot) handleStart(ctx context.Context, api *tgbot.Bot, update *models.Up if update.Message == nil { return } + // Only respond to a private /start: the promo bot is a one-on-one onboarding entry + // point and should never reply to group messages. + if update.Message.Chat.Type != models.ChatTypePrivate { + return + } if err := t.throttle(ctx); err != nil { return } diff --git a/platform/telegram/internal/promobot/promobot_test.go b/platform/telegram/internal/promobot/promobot_test.go index 459d650..4786ed9 100644 --- a/platform/telegram/internal/promobot/promobot_test.go +++ b/platform/telegram/internal/promobot/promobot_test.go @@ -85,7 +85,7 @@ func TestHandleStartReplies(t *testing.T) { } b.handleStart(context.Background(), b.api, &models.Update{Message: &models.Message{ - Chat: models.Chat{ID: 42}, + Chat: models.Chat{ID: 42, Type: models.ChatTypePrivate}, From: &models.User{LanguageCode: "ru"}, Text: "/start f99", }}) @@ -103,3 +103,19 @@ func TestHandleStartReplies(t *testing.T) { t.Errorf("reply_markup = %q, want startapp=f99 (the /start payload forwarded)", api.replyMarkup) } } + +func TestHandleStartIgnoresGroup(t *testing.T) { + api := &fakeAPI{} + srv := httptest.NewServer(api) + t.Cleanup(srv.Close) + b, err := New(Config{Token: "1:2", APIBaseURL: srv.URL, BotUsername: "B", BotLinkURL: "https://t.me/b/a"}, zap.NewNop()) + if err != nil { + t.Fatalf("new: %v", err) + } + b.handleStart(context.Background(), b.api, &models.Update{Message: &models.Message{ + Chat: models.Chat{ID: -100, Type: models.ChatTypeSupergroup}, Text: "/start", + }}) + if api.chatID != "" { + t.Errorf("replied to a group message (chat=%q); want none", api.chatID) + } +}