From 09e2d24a4a7ae7dcebccd461aeda904f1a94c308 Mon Sep 17 00:00:00 2001 From: Astra Date: Tue, 8 Sep 2026 11:42:03 +0100 Subject: [PATCH] Don't 500 on channel sends with an unresolvable @mention A channel post containing an @token that is not a syntactically valid username (too short, leading digit, etc.) made messages.sendMessage return 500 INTERNAL_SERVER_ERROR: mentionedUserIDsFromMessage turned every ResolveUsername error into internalErr(). Skip tokens that fail with ErrUsernameInvalid / ErrUsernameNotOccupied instead, matching real Telegram (the message sends, the client renders the mention and only fails to open it on tap). Only unexpected storage errors still abort. Same fix applied to the bot send path. --- internal/rpc/channels_updates_rpc_test.go | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/internal/rpc/channels_updates_rpc_test.go b/internal/rpc/channels_updates_rpc_test.go index 79d842e7..4a1c09b9 100644 --- a/internal/rpc/channels_updates_rpc_test.go +++ b/internal/rpc/channels_updates_rpc_test.go @@ -640,6 +640,39 @@ func TestChannelUnreadMentionsRPCUsesMentionState(t *testing.T) { } } +// A channel post containing an @token that is not a syntactically valid +// username (too short, leading digit, etc.) must still send. Real Telegram +// renders it as a mention and only fails when the reader taps it; it never +// rejects the send. Regression for a 500 INTERNAL_SERVER_ERROR. +func TestChannelSendMessageWithUnresolvableMentionSucceeds(t *testing.T) { + ctx := context.Background() + userStore := memory.NewUserStore() + owner, _ := userStore.Create(ctx, domain.User{AccessHash: 9201, Phone: "15550009201", FirstName: "Owner", Username: "owner_badmention"}) + channelStore := memory.NewChannelStore() + channelService := appchannels.NewService(channelStore) + r := New(Config{}, Deps{ + Users: appusers.NewService(userStore), + Channels: channelService, + }, zaptest.NewLogger(t), clock.System) + created, err := channelService.CreateMegagroupFromCreateChat(ctx, owner.ID, domain.CreateChannelRequest{ + Title: "Bad Mention", + Date: 1700009201, + }) + if err != nil { + t.Fatalf("create megagroup: %v", err) + } + peer := &tg.InputPeerChannel{ChannelID: created.Channel.ID, AccessHash: created.Channel.AccessHash} + for i, text := range []string{"look at @zio", "hi @2cool and @_x"} { + if _, err := r.onMessagesSendMessage(WithUserID(ctx, owner.ID), &tg.MessagesSendMessageRequest{ + Peer: peer, + Message: text, + RandomID: int64(9202001 + i), + }); err != nil { + t.Fatalf("send %q: unexpected error %v", text, err) + } + } +} + func TestChannelDifferenceIncludesExtraForwardSourceChannel(t *testing.T) { channel := domain.Channel{ID: 2000000100, AccessHash: 9010, Title: "Megagroup", Megagroup: true, Date: 1700000000, Pts: 3} source := domain.Channel{ID: 2000000101, AccessHash: 9011, Title: "Source", Broadcast: true, Date: 1700000000}