fix: only answer unknown /commands, not ordinary messages

The default handler fired for every update the command matchers didn't
claim, so plain chatter, service messages, media, and replies to the bot
all drew a "Nieznana komenda" response — spammy in group chats. Gate the
fallback on a leading bot_command entity so only real (unrecognized)
commands get the help reply.
This commit is contained in:
Kamil 'Kamaś' Bruchal 2026-06-22 19:58:13 +02:00
parent 41980e4f6f
commit 5a10eda3e1
2 changed files with 43 additions and 7 deletions

View File

@ -156,14 +156,29 @@ func (h *botHandlers) sync(ctx context.Context, b *bot.Bot, update *models.Updat
"Zsynchronizowano: %d tygodni, %d osób.", len(rota.Weeks), len(rota.People)))
}
// unknown is the fallback for any text the bot does not recognize.
// unknown is the fallback for any update the command matchers don't claim. It
// only answers messages that actually carry a bot_command entity (a mistyped or
// unsupported /command); plain chatter, service messages (joins/pins), media,
// and replies to the bot fall through here too but get no response, so the bot
// stays quiet in groups instead of "Nieznana komenda"-spamming every message.
func (h *botHandlers) unknown(ctx context.Context, b *bot.Bot, update *models.Update) {
if update.Message == nil {
if update.Message == nil || !hasCommandEntity(update.Message.Entities) {
return
}
h.reply(ctx, b, update, "Nieznana komenda. Użyj /kto_sprzata lub /sync.")
}
// hasCommandEntity reports whether the message carries any bot_command entity,
// i.e. the user typed a /command (recognized or not) rather than plain text.
func hasCommandEntity(entities []models.MessageEntity) bool {
for _, e := range entities {
if e.Type == models.MessageEntityTypeBotCommand {
return true
}
}
return false
}
// reply sends text back to the originating chat, logging send failures.
func (h *botHandlers) reply(ctx context.Context, b *bot.Bot, update *models.Update, text string) {
if update.Message == nil {

View File

@ -293,18 +293,39 @@ func TestSyncReplyFailure(t *testing.T) {
}
}
// TestUnknownReply checks the fallback handler answers unrecognized text.
// TestUnknownReply checks the fallback answers an unrecognized /command (a
// message carrying a bot_command entity that no matcher claimed).
func TestUnknownReply(t *testing.T) {
svc := testService(t, Rota{}, nil)
b, sent := newTestBot(t)
h := &botHandlers{svc: svc, now: fixedNow()}
cmd := &models.Update{Message: &models.Message{
Text: "/foo",
Chat: models.Chat{ID: 123},
Entities: cmdEntity("/foo"),
}}
h.unknown(context.Background(), b, cmd)
got := sent()
want := "Nieznana komenda. Użyj /kto_sprzata lub /sync."
if len(got) != 1 || got[0] != want {
t.Fatalf("reply = %v, want [%q]", got, want)
}
}
// TestUnknownPlainTextIgnored checks ordinary chatter (no bot_command entity)
// gets no reply, so the bot doesn't spam "Nieznana komenda" on every group
// message, service notice, or reply to its own messages.
func TestUnknownPlainTextIgnored(t *testing.T) {
svc := testService(t, Rota{}, nil)
b, sent := newTestBot(t)
h := &botHandlers{svc: svc, now: fixedNow()}
h.unknown(context.Background(), b, updateMsg(123, "siema"))
got := sent()
want := "Nieznana komenda. Użyj /kto_sprzata lub /sync."
if len(got) != 1 || got[0] != want {
t.Fatalf("reply = %v, want [%q]", got, want)
if got := sent(); len(got) != 0 {
t.Fatalf("plain text should not reply, got %v", got)
}
}