fix(messages): sync Android pinned message search

This commit is contained in:
iamxvbaba 2026-07-27 13:49:41 +08:00
parent 90649b5b67
commit 5401c9a364
4 changed files with 35 additions and 5 deletions

View file

@ -97,12 +97,17 @@ func TestChannelMultiPinAndroidOpenAndJump(t *testing.T) {
}
// ① 打开聊天MediaDataController.loadPinnedMessages → messages.search filterPinned。
searchEnc := dispatch(&tg.MessagesSearchRequest{
androidPinnedSearch := &tg.MessagesSearchRequest{
Peer: peer,
Q: "",
Filter: &tg.InputMessagesFilterPinned{},
Limit: 40,
})
}
// DrKLO initializes saved_reaction to an empty non-nil ArrayList and its
// serializer consequently emits flags.3 + Vector length 0 on every
// messages.search, including channel filterPinned.
androidPinnedSearch.SetSavedReaction([]tg.ReactionClass{})
searchEnc := dispatch(androidPinnedSearch)
channelMessages, ok := searchEnc.(*tg.MessagesChannelMessages)
if !ok {
t.Fatalf("pinned search response = %T, want messages.channelMessages", searchEnc)

View file

@ -804,8 +804,13 @@ func (r *Router) messageFilterFromSearchRequest(ctx context.Context, userID int6
filter.Peer = peer
}
savedReactions, hasSavedReactions := req.GetSavedReaction()
// An empty optional vector carries no reaction-filtering semantics. Some TL
// clients emit flags.3 with a zero-length vector on ordinary peer searches.
// Keep the wire presence intact at the TL edge, but only apply Saved
// Messages scope and reaction validation when the vector has values.
hasSavedReactionFilter := hasSavedReactions && len(savedReactions) > 0
savedPeerInput, hasSavedPeer := req.GetSavedPeerID()
if hasSavedReactions || hasSavedPeer {
if hasSavedReactionFilter || hasSavedPeer {
if !filter.HasPeer ||
filter.Peer != (domain.Peer{Type: domain.PeerTypeUser, ID: userID}) {
return domain.MessageFilter{}, peerIDInvalidErr()
@ -821,8 +826,8 @@ func (r *Router) messageFilterFromSearchRequest(ctx context.Context, userID int6
}
filter.SavedPeer = savedPeer
}
if hasSavedReactions {
if len(savedReactions) == 0 || len(savedReactions) > maxReactionVector {
if hasSavedReactionFilter {
if len(savedReactions) > maxReactionVector {
return domain.MessageFilter{}, reactionInvalidErr()
}
seen := make(map[string]struct{}, len(savedReactions))

View file

@ -225,6 +225,9 @@ func TestMessagesSearchChannelPeerReturnsSingleCopyMessages(t *testing.T) {
Filter: &tg.InputMessagesFilterPhotos{},
Limit: 0,
}
// Match DrKLO's ordinary messages.search wire shape: flags.3 is present
// even though the saved_reaction vector is empty.
mediaCountReq.SetSavedReaction([]tg.ReactionClass{})
in.Reset()
if err := mediaCountReq.Encode(&in); err != nil {
t.Fatalf("encode shared media count search: %v", err)

View file

@ -234,6 +234,23 @@ func TestMessageFilterFromSearchRequestParsesSavedTagsAndPeer(t *testing.T) {
if _, err := r.messageFilterFromSearchRequest(WithUserID(context.Background(), userID), userID, req); !tgerr.Is(err, "PEER_ID_INVALID") {
t.Fatalf("non-self saved search err = %v, want PEER_ID_INVALID", err)
}
emptyTagReq := &tg.MessagesSearchRequest{
Peer: &tg.InputPeerUser{UserID: userID + 1, AccessHash: 1},
Q: "ordinary",
Filter: &tg.InputMessagesFilterEmpty{},
Limit: 20,
}
emptyTagReq.SetSavedReaction([]tg.ReactionClass{})
ordinary, err := r.messageFilterFromSearchRequest(WithUserID(context.Background(), userID), userID, emptyTagReq)
if err != nil {
t.Fatalf("empty saved reaction on ordinary peer search: %v", err)
}
if !ordinary.HasPeer ||
ordinary.Peer != (domain.Peer{Type: domain.PeerTypeUser, ID: userID + 1}) ||
len(ordinary.SavedReactions) != 0 {
t.Fatalf("ordinary peer filter with empty saved reaction = %+v", ordinary)
}
}
func TestMessagesGetDefaultTagReactionsReturnsHashableCatalog(t *testing.T) {