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.
This commit is contained in:
parent
2cfc7a0539
commit
80e99e8781
3 changed files with 52 additions and 2 deletions
|
|
@ -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) {
|
func TestChannelDifferenceIncludesExtraForwardSourceChannel(t *testing.T) {
|
||||||
channel := domain.Channel{ID: 2000000100, AccessHash: 9010, Title: "Megagroup", Megagroup: true, Date: 1700000000, Pts: 3}
|
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}
|
source := domain.Channel{ID: 2000000101, AccessHash: 9011, Title: "Source", Broadcast: true, Date: 1700000000}
|
||||||
|
|
|
||||||
|
|
@ -341,7 +341,10 @@ func (r *Router) mentionedUserIDsFromDomainMessage(ctx context.Context, currentU
|
||||||
for _, username := range extractMentionUsernames(message, domain.MaxChannelMentionRecipients-len(out)) {
|
for _, username := range extractMentionUsernames(message, domain.MaxChannelMentionRecipients-len(out)) {
|
||||||
user, found, err := identity.ResolveUsername(ctx, currentUserID, username)
|
user, found, err := identity.ResolveUsername(ctx, currentUserID, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, internalErr()
|
if mentionResolveFatal(err) {
|
||||||
|
return nil, internalErr()
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
add(user.ID)
|
add(user.ID)
|
||||||
|
|
|
||||||
|
|
@ -437,7 +437,10 @@ func (r *Router) mentionedUserIDsFromMessage(ctx context.Context, currentUserID
|
||||||
for _, username := range extractMentionUsernames(message, domain.MaxChannelMentionRecipients-len(out)) {
|
for _, username := range extractMentionUsernames(message, domain.MaxChannelMentionRecipients-len(out)) {
|
||||||
user, found, err := identity.ResolveUsername(ctx, currentUserID, username)
|
user, found, err := identity.ResolveUsername(ctx, currentUserID, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, internalErr()
|
if mentionResolveFatal(err) {
|
||||||
|
return nil, internalErr()
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
add(user.ID)
|
add(user.ID)
|
||||||
|
|
@ -450,6 +453,17 @@ func (r *Router) mentionedUserIDsFromMessage(ctx context.Context, currentUserID
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mentionResolveFatal reports whether a ResolveUsername error while scanning
|
||||||
|
// message text for @mentions should abort the send. A syntactically invalid or
|
||||||
|
// unoccupied @token is not a failure: real Telegram sends the message, renders
|
||||||
|
// the token as a mention, and only fails to open it when the reader taps it.
|
||||||
|
// Only an unexpected (storage) error aborts the RPC.
|
||||||
|
func mentionResolveFatal(err error) bool {
|
||||||
|
return err != nil &&
|
||||||
|
!errors.Is(err, domain.ErrUsernameInvalid) &&
|
||||||
|
!errors.Is(err, domain.ErrUsernameNotOccupied)
|
||||||
|
}
|
||||||
|
|
||||||
func extractMentionUsernames(message string, limit int) []string {
|
func extractMentionUsernames(message string, limit int) []string {
|
||||||
if limit <= 0 || message == "" {
|
if limit <= 0 || message == "" {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue