diff --git a/internal/app/bots/gifbot.go b/internal/app/bots/gifbot.go index 0443511d..f5d2a01b 100644 --- a/internal/app/bots/gifbot.go +++ b/internal/app/bots/gifbot.go @@ -19,11 +19,16 @@ func (s *Service) HandlesInlineBot(botUserID int64) bool { } // OnInlineQuery serves @gif's admin-curated catalog as inline gif results, -// ordered by title relevance to query (see rankGifCatalogEntries). +// ordered by title relevance to query (see rankGifCatalogEntries) or, for a +// category-icon tap, filtered to that category (see gifCategoryFromQuery). // -// offset/paging is not implemented: the catalog is bounded by -// MaxGifCatalogEntries, which is MaxBotInlineResults, so one response always -// carries all of it. +// offset/paging is not implemented: filtering/ranking runs over the whole +// catalog (an operator's library can run into the thousands, see +// files.Service.ListGifCatalog's doc comment for why that fetch is +// deliberately unbounded), then the result is truncated to +// MaxGifCatalogEntries -- the real per-response cap the client enforces -- +// so one response always carries as much of the *relevant* slice as fits, +// not an arbitrary (sort_order, id) prefix of the whole catalog. // // Note TDesktop only ever calls this with a non-empty query: its GIF tab has // no trending panel, and GifsListWidget::searchForGifs returns early on an @@ -52,6 +57,9 @@ func (s *Service) OnInlineQuery(ctx context.Context, botUserID, _ int64, query, if len(entries) == 0 { return domain.BotInlineResults{Gallery: true}, true, nil } + if len(entries) > domain.MaxGifCatalogEntries { + entries = entries[:domain.MaxGifCatalogEntries] + } ids := make([]int64, len(entries)) for i, e := range entries { ids[i] = e.DocumentID diff --git a/internal/app/files/gif_admin.go b/internal/app/files/gif_admin.go index b9711087..c7d8ff37 100644 --- a/internal/app/files/gif_admin.go +++ b/internal/app/files/gif_admin.go @@ -152,13 +152,18 @@ func (s *Service) AdminListGifCatalog(ctx context.Context) ([]domain.GifCatalogE } // ListGifCatalog is bots.gifCatalogSource's read: onlyEnabled=true is what -// @gif actually serves, capped at domain.MaxGifCatalogEntries -- the real -// per-response limit the client enforces. +// @gif actually serves. Deliberately unbounded, not capped at +// domain.MaxGifCatalogEntries here -- bots.Service filters/ranks by category +// or query text over the *whole* catalog and only then takes the top +// MaxGifCatalogEntries for one response. Capping the fetch itself would +// silently limit that filtering to an arbitrary (sort_order, id) slice of a +// large catalog, e.g. a category tap finding none of its members among the +// first 50 rows and falling back to showing the unfiltered catalog instead. func (s *Service) ListGifCatalog(ctx context.Context, onlyEnabled bool) ([]domain.GifCatalogEntry, error) { if s.gifCatalog == nil { return nil, nil } - return s.gifCatalog.ListGifCatalog(ctx, onlyEnabled, domain.MaxGifCatalogEntries) + return s.gifCatalog.ListGifCatalog(ctx, onlyEnabled, 0) } // AdminSetGifCatalogEnabled toggles whether an entry is served.