Merge PR #27: preserve verification icons in message envelopes
Reviewed against current telesrv/main as PASS_WITH_FOLLOW_UP. Preserve the contributor commit; maintainer canonicalization will complete the bounded response-boundary coverage and tests.
This commit is contained in:
commit
7713847b2a
8 changed files with 138 additions and 19 deletions
|
|
@ -679,3 +679,106 @@ func TestUsersGetUsersResolvesBotVerificationInOneBatch(t *testing.T) {
|
|||
verify.batchCalls, verify.peerCalls)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOpeningChatMessageLookupsKeepBotVerificationIcons covers the supplemental
|
||||
// message lookups official clients issue while opening a chat. These responses
|
||||
// update the same peer cache as messages.getDialogs, so returning an unstamped
|
||||
// user/channel here makes a visible badge disappear until the dialogs response is
|
||||
// loaded again.
|
||||
func TestOpeningChatMessageLookupsKeepBotVerificationIcons(t *testing.T) {
|
||||
t.Run("private messages.getMessages", func(t *testing.T) {
|
||||
const (
|
||||
viewerID = int64(1000000001)
|
||||
targetID = int64(1000000002)
|
||||
iconID = int64(8800010)
|
||||
)
|
||||
verify := newFakeBotVerifications()
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: targetID}
|
||||
verify.marks[peer] = domain.CustomVerification{
|
||||
VerifierBotID: 777000123,
|
||||
Peer: peer,
|
||||
IconDocumentID: iconID,
|
||||
Description: "Verified by Acme Trust",
|
||||
}
|
||||
r := New(Config{}, Deps{
|
||||
Messages: &captureMessages{list: domain.MessageList{
|
||||
Messages: []domain.Message{{
|
||||
ID: 7,
|
||||
OwnerUserID: viewerID,
|
||||
Peer: peer,
|
||||
From: peer,
|
||||
Date: 1700000000,
|
||||
Body: "reply source",
|
||||
}},
|
||||
Count: 1,
|
||||
}},
|
||||
Users: mapUsersService{users: map[int64]domain.User{
|
||||
viewerID: {ID: viewerID, FirstName: "Viewer"},
|
||||
targetID: {ID: targetID, FirstName: "Target"},
|
||||
}},
|
||||
BotVerifications: verify,
|
||||
}, zaptest.NewLogger(t), clock.System)
|
||||
|
||||
result, err := r.onMessagesGetMessages(
|
||||
WithUserID(context.Background(), viewerID),
|
||||
[]tg.InputMessageClass{&tg.InputMessageID{ID: 7}},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("messages.getMessages: %v", err)
|
||||
}
|
||||
box := result.(*tg.MessagesMessages)
|
||||
if len(box.Users) != 1 {
|
||||
t.Fatalf("users = %d, want target user", len(box.Users))
|
||||
}
|
||||
user := &tg.User{}
|
||||
tlRoundTrip(t, box.Users[0].(*tg.User), user)
|
||||
if icon, ok := user.GetBotVerificationIcon(); !ok || icon != iconID {
|
||||
t.Fatalf("opening-chat user bot_verification_icon = %d, ok=%v, want %d", icon, ok, iconID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("channel channels.getMessages", func(t *testing.T) {
|
||||
const iconID = int64(8800011)
|
||||
f := newBotVerificationFixture(t, newFakeBotVerifications())
|
||||
group := f.botVerificationGroup(t, "Verified Group")
|
||||
sent, err := f.router.onMessagesSendMessage(
|
||||
WithUserID(context.Background(), f.owner.ID),
|
||||
&tg.MessagesSendMessageRequest{
|
||||
Peer: &tg.InputPeerChannel{ChannelID: group.ID, AccessHash: group.AccessHash},
|
||||
Message: "pinned source",
|
||||
RandomID: 8800011,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("send channel message: %v", err)
|
||||
}
|
||||
messageID := sent.(*tg.Updates).Updates[1].(*tg.UpdateNewChannelMessage).Message.(*tg.Message).ID
|
||||
peer := domain.Peer{Type: domain.PeerTypeChannel, ID: group.ID}
|
||||
f.verify.marks[peer] = domain.CustomVerification{
|
||||
VerifierBotID: f.bot.ID,
|
||||
Peer: peer,
|
||||
IconDocumentID: iconID,
|
||||
Description: "Verified by Acme Trust",
|
||||
}
|
||||
|
||||
result, err := f.router.onChannelsGetMessages(
|
||||
WithUserID(context.Background(), f.owner.ID),
|
||||
&tg.ChannelsGetMessagesRequest{
|
||||
Channel: &tg.InputChannel{ChannelID: group.ID, AccessHash: group.AccessHash},
|
||||
ID: []tg.InputMessageClass{&tg.InputMessageID{ID: messageID}},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("channels.getMessages: %v", err)
|
||||
}
|
||||
box := result.(*tg.MessagesMessages)
|
||||
if len(box.Chats) != 1 {
|
||||
t.Fatalf("chats = %d, want target channel", len(box.Chats))
|
||||
}
|
||||
channel := &tg.Channel{}
|
||||
tlRoundTrip(t, box.Chats[0].(*tg.Channel), channel)
|
||||
if icon, ok := channel.GetBotVerificationIcon(); !ok || icon != iconID {
|
||||
t.Fatalf("opening-chat channel bot_verification_icon = %d, ok=%v, want %d", icon, ok, iconID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,11 +293,13 @@ func (r *Router) onChannelsGetMessages(ctx context.Context, req *tg.ChannelsGetM
|
|||
messages = append(messages, &tg.MessageEmpty{ID: id})
|
||||
}
|
||||
}
|
||||
return &tg.MessagesMessages{
|
||||
result := &tg.MessagesMessages{
|
||||
Messages: messages,
|
||||
Chats: tgChannels(userID, []domain.Channel{history.Channel}),
|
||||
Users: r.tgUsersForViewer(userID, history.Users), // viewer 补拉自己的消息(含置顶)须带 self
|
||||
}, nil
|
||||
}
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Router) onChannelsDeleteMessages(ctx context.Context, req *tg.ChannelsDeleteMessagesRequest) (*tg.MessagesAffectedMessages, error) {
|
||||
|
|
|
|||
|
|
@ -490,11 +490,13 @@ func (r *Router) onMessagesGetMessages(ctx context.Context, ids []tg.InputMessag
|
|||
out = append(out, tgMessage(msg))
|
||||
}
|
||||
chats := r.chatsForMessageUpdates(ctx, userID, found)
|
||||
return &tg.MessagesMessages{
|
||||
result := &tg.MessagesMessages{
|
||||
Messages: out,
|
||||
Users: r.usersForMessageUpdates(ctx, userID, found),
|
||||
Chats: chats,
|
||||
}, nil
|
||||
}
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// onMessagesGetRichMessage 返回单条消息的完整富文本(Layer 227 richMessage)。消息列表
|
||||
|
|
@ -535,11 +537,13 @@ func (r *Router) onMessagesGetRichMessage(ctx context.Context, req *tg.MessagesG
|
|||
if len(out) == 0 {
|
||||
out = append(out, &tg.MessageEmpty{ID: req.ID})
|
||||
}
|
||||
return &tg.MessagesMessages{
|
||||
result := &tg.MessagesMessages{
|
||||
Messages: out,
|
||||
Users: r.usersForMessageUpdates(ctx, userID, found),
|
||||
Chats: r.chatsForMessageUpdates(ctx, userID, found),
|
||||
}, nil
|
||||
}
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesSearchGlobal(ctx context.Context, req *tg.MessagesSearchGlobalRequest) (tg.MessagesMessagesClass, error) {
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ func (r *Router) onMessagesGetUnreadReactions(ctx context.Context, req *tg.Messa
|
|||
out.Messages = append(out.Messages, item)
|
||||
}
|
||||
}
|
||||
r.applyStoryMaxIDsToMessages(ctx, userID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, out)
|
||||
return out, nil
|
||||
}
|
||||
out := &tg.MessagesMessages{
|
||||
|
|
@ -310,7 +310,7 @@ func (r *Router) onMessagesGetUnreadReactions(ctx context.Context, req *tg.Messa
|
|||
Chats: r.chatsForInputPeer(ctx, userID, req.Peer),
|
||||
Users: []tg.UserClass{},
|
||||
}
|
||||
r.applyStoryMaxIDsToMessages(ctx, userID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ func (r *Router) onMessagesGetUnreadMentions(ctx context.Context, req *tg.Messag
|
|||
Chats: r.chatsForInputPeer(ctx, userID, req.Peer),
|
||||
Users: []tg.UserClass{},
|
||||
}
|
||||
r.applyStoryMaxIDsToMessages(ctx, userID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -726,7 +726,7 @@ func (r *Router) registerMessages(d *tlprofile.Dispatcher) {
|
|||
Chats: []tg.ChatClass{tgChannelChatForView(userID, view)},
|
||||
Users: []tg.UserClass{},
|
||||
}
|
||||
r.applyStoryMaxIDsToMessages(ctx, userID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, out)
|
||||
return out, nil
|
||||
}
|
||||
if searchFilterNeedsMediaStore(req.Filter) {
|
||||
|
|
@ -746,7 +746,7 @@ func (r *Router) registerMessages(d *tlprofile.Dispatcher) {
|
|||
Chats: []tg.ChatClass{tgChannelChatForView(userID, view)},
|
||||
Users: []tg.UserClass{},
|
||||
}
|
||||
r.applyStoryMaxIDsToMessages(ctx, userID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, out)
|
||||
return out, nil
|
||||
}
|
||||
if err := r.validateInputPeerChannelAccess(ctx, userID, req.Peer, filter.Peer.ID); err != nil {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ func (r *Router) onMessagesGetScheduledMessages(ctx context.Context, req *tg.Mes
|
|||
}
|
||||
scheduledSvc, ok := r.deps.Messages.(scheduledMessagesService)
|
||||
if r.deps.Messages == nil || !ok {
|
||||
return &tg.MessagesMessages{Chats: r.chatsForMessageUpdate(ctx, userID, domain.Message{Peer: peer})}, nil
|
||||
result := &tg.MessagesMessages{Chats: r.chatsForMessageUpdate(ctx, userID, domain.Message{Peer: peer})}
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, result)
|
||||
return result, nil
|
||||
}
|
||||
list, err := scheduledSvc.GetScheduledMessages(ctx, userID, domain.ScheduledMessageFilter{
|
||||
OwnerUserID: userID,
|
||||
|
|
@ -48,7 +50,9 @@ func (r *Router) onMessagesGetScheduledHistory(ctx context.Context, req *tg.Mess
|
|||
if req.Hash != 0 {
|
||||
return &tg.MessagesMessagesNotModified{Count: 0}, nil
|
||||
}
|
||||
return &tg.MessagesMessages{Chats: r.chatsForMessageUpdate(ctx, userID, domain.Message{Peer: peer})}, nil
|
||||
result := &tg.MessagesMessages{Chats: r.chatsForMessageUpdate(ctx, userID, domain.Message{Peer: peer})}
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, result)
|
||||
return result, nil
|
||||
}
|
||||
list, err := scheduledSvc.ListScheduledMessages(ctx, userID, domain.ScheduledMessageFilter{
|
||||
OwnerUserID: userID,
|
||||
|
|
@ -284,11 +288,13 @@ func (r *Router) tgScheduledMessages(ctx context.Context, userID int64, peer dom
|
|||
if len(chats) == 0 && peer.Type == domain.PeerTypeChannel {
|
||||
chats = r.chatsForMessageUpdate(ctx, userID, domain.Message{Peer: peer})
|
||||
}
|
||||
return &tg.MessagesMessages{
|
||||
result := &tg.MessagesMessages{
|
||||
Messages: out,
|
||||
Chats: chats,
|
||||
Users: r.usersForMessageUpdates(ctx, userID, messages),
|
||||
}
|
||||
r.applyPeerReadModelsToMessages(ctx, userID, result)
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Router) tgNewScheduledMessageUpdates(ctx context.Context, userID int64, msg domain.ScheduledMessage, randomID int64, date int) *tg.Updates {
|
||||
|
|
|
|||
|
|
@ -113,13 +113,13 @@ func (r *Router) tgResolvedChannelPeerWithStories(ctx context.Context, viewerUse
|
|||
|
||||
func (r *Router) tgGlobalChannelMessages(ctx context.Context, viewerUserID int64, history domain.ChannelHistory) tg.MessagesMessagesClass {
|
||||
out := tgGlobalChannelMessages(viewerUserID, history)
|
||||
r.applyStoryMaxIDsToMessages(ctx, viewerUserID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, viewerUserID, out)
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Router) tgMessagesMessages(ctx context.Context, viewerUserID int64, list domain.MessageList) tg.MessagesMessagesClass {
|
||||
out := tgMessagesMessages(viewerUserID, list)
|
||||
r.applyStoryMaxIDsToMessages(ctx, viewerUserID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, viewerUserID, out)
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ func (r *Router) tgChannelHistoryMessages(ctx context.Context, viewerUserID int6
|
|||
value.Chats = replaceTGChat(value.Chats, linked)
|
||||
}
|
||||
}
|
||||
r.applyStoryMaxIDsToMessages(ctx, viewerUserID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, viewerUserID, out)
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
@ -174,11 +174,15 @@ func (r *Router) applyStoryMaxIDsToMessageReactionsList(ctx context.Context, vie
|
|||
|
||||
func (r *Router) tgGlobalSearchMessages(ctx context.Context, viewerUserID int64, limit int, private domain.MessageList, channel domain.ChannelHistory) tg.MessagesMessagesClass {
|
||||
out := tgGlobalSearchMessages(viewerUserID, limit, private, channel)
|
||||
r.applyStoryMaxIDsToMessages(ctx, viewerUserID, out)
|
||||
r.applyPeerReadModelsToMessages(ctx, viewerUserID, out)
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Router) applyStoryMaxIDsToMessages(ctx context.Context, viewerUserID int64, out tg.MessagesMessagesClass) {
|
||||
// applyPeerReadModelsToMessages stamps every user/channel carried by a messages
|
||||
// envelope. Supplemental lookups such as messages.getMessages and
|
||||
// channels.getMessages update the same client-side peer cache as getDialogs, so
|
||||
// they must use the same response-boundary overlays as history and search.
|
||||
func (r *Router) applyPeerReadModelsToMessages(ctx context.Context, viewerUserID int64, out tg.MessagesMessagesClass) {
|
||||
switch v := out.(type) {
|
||||
case *tg.MessagesMessages:
|
||||
r.applyPeerReadModels(ctx, viewerUserID, v.Users, v.Chats)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue