Games: Support searching with Year or FirstYear filters

This commit is contained in:
IRHM
2026-08-02 23:53:12 +01:00
committed by momi
parent dc2b9a1a63
commit 92edb75de9
3 changed files with 70 additions and 11 deletions
+1
View File
@@ -10,6 +10,7 @@ These changes are awaiting release:
- Import(text list): Support parsing a rating (out of 10, supporting decimals) in square brackets. - Import(text list): Support parsing a rating (out of 10, supporting decimals) in square brackets.
- Show `End Year` for TV Shows that have ended or been canceled. - Show `End Year` for TV Shows that have ended or been canceled.
- Add full release date and last release date to their respective year elements as titles (browser tooltip). - Add full release date and last release date to their respective year elements as titles (browser tooltip).
- Search: Support inline filters for Non-Multi searches (ex: You can now search `Spider Man year:2026` or `Spider Man fyear:2026` if you care strictly about the first release year).
## Changed ## Changed
+16 -7
View File
@@ -11,6 +11,7 @@ import (
"github.com/sbondCo/Watcharr/config" "github.com/sbondCo/Watcharr/config"
"github.com/sbondCo/Watcharr/database/entity" "github.com/sbondCo/Watcharr/database/entity"
"github.com/sbondCo/Watcharr/domain" "github.com/sbondCo/Watcharr/domain"
"github.com/sbondCo/Watcharr/media/igdb"
"github.com/sbondCo/Watcharr/media/tmdb" "github.com/sbondCo/Watcharr/media/tmdb"
"github.com/sbondCo/Watcharr/util" "github.com/sbondCo/Watcharr/util"
"gorm.io/gorm" "gorm.io/gorm"
@@ -80,7 +81,10 @@ func (s *Service) Search(
Page: pp.Page, Page: pp.Page,
Adult: qfilters.Adult, Adult: qfilters.Adult,
} }
if err := s.searchMulti(sreq, &resp); err != nil { greq := igdb.SearchOptions{
Query: query,
}
if err := s.searchMulti(sreq, greq, &resp); err != nil {
return resp, errors.New("multi search failed") return resp, errors.New("multi search failed")
} }
case domain.SearchTypeMovie: case domain.SearchTypeMovie:
@@ -119,7 +123,12 @@ func (s *Service) Search(
return resp, errors.New("person search failed") return resp, errors.New("person search failed")
} }
case domain.SearchTypeGame: case domain.SearchTypeGame:
if err := s.searchGame(r.Query, pp.Page, &resp); err != nil { greq := igdb.SearchOptions{
Query: query,
Year: qfilters.Year,
PrimaryYear: qfilters.FirstYear,
}
if err := s.searchGame(greq, &resp); err != nil {
return resp, errors.New("game search failed") return resp, errors.New("game search failed")
} }
} }
@@ -132,6 +141,7 @@ func (s *Service) Search(
// SearchMulti is TMDB Multi search but with game data added to first page. // SearchMulti is TMDB Multi search but with game data added to first page.
func (s *Service) searchMulti( func (s *Service) searchMulti(
req tmdb.SearchUniversalOptions, req tmdb.SearchUniversalOptions,
igdbReq igdb.SearchOptions,
resp *domain.SearchResponse, resp *domain.SearchResponse,
) error { ) error {
slog.Debug("searchMulti: Running.", "req", req) slog.Debug("searchMulti: Running.", "req", req)
@@ -149,7 +159,7 @@ func (s *Service) searchMulti(
} }
// IGDB (we will only get results for the first page) // IGDB (we will only get results for the first page)
if req.Page == 1 && s.cfg.TwitchEnabled() { if req.Page == 1 && s.cfg.TwitchEnabled() {
igdbRes, err := s.cfg.TWITCH.Search(req.Query) igdbRes, err := s.cfg.TWITCH.Search(igdbReq)
if err != nil { if err != nil {
slog.Error("SearchMulti: Failed to search igdb!", "error", err) slog.Error("SearchMulti: Failed to search igdb!", "error", err)
return errors.New("content request failed") return errors.New("content request failed")
@@ -278,12 +288,11 @@ func (s *Service) searchPeople(
} }
func (s *Service) searchGame( func (s *Service) searchGame(
query string, req igdb.SearchOptions,
page int,
resp *domain.SearchResponse, resp *domain.SearchResponse,
) error { ) error {
slog.Debug("searchGame: Running.", "query", query, "page", page) slog.Debug("searchGame: Running.", "req", req)
igdbRes, err := s.cfg.TWITCH.Search(query) igdbRes, err := s.cfg.TWITCH.Search(req)
if err != nil { if err != nil {
slog.Error("searchGame: Failed to search igdb!", "error", err) slog.Error("searchGame: Failed to search igdb!", "error", err)
return errors.New("content request failed") return errors.New("content request failed")
+53 -4
View File
@@ -183,19 +183,68 @@ func (i *IGDB) Init() error {
return nil return nil
} }
func (i *IGDB) Search(q string) (GameSearchResponse, error) { type SearchOptions struct {
slog.Debug("Search:", "query", q) Query string
Year int
PrimaryYear int
}
// Turn supported filters from the SearchOptions struct into a `where` we can
// send to APICalypse.
func (o SearchOptions) AsWhere() string {
f := []string{}
if o.Year != 0 {
y := strconv.Itoa(o.Year)
// If `any` release of the game was on this year.
f = append(f, "release_dates.y = "+y)
}
if o.PrimaryYear != 0 {
start := strconv.FormatInt(
time.
Date(o.PrimaryYear, time.January, 1, 0, 0, 0, 0, time.UTC).
Unix(),
10)
end := strconv.FormatInt(
time.
// We add one to the year and get the first day of the next year
// BUT because day=0, we go back one day and end up with the
// very last day of `PrimaryYear`.
Date(o.PrimaryYear+1, time.January, 0, 23, 59, 59, 0, time.UTC).
Unix(),
10)
// If `first` release of the game was on this year.
f = append(f, "first_release_date > "+start)
f = append(f, "first_release_date < "+end)
}
if len(f) <= 0 {
return ""
}
return "where " + strings.Join(f, " & ") + ";"
}
func (i *IGDB) Search(o SearchOptions) (GameSearchResponse, error) {
slog.Debug("Search:", "query", o.Query)
var resp GameSearchResponse var resp GameSearchResponse
cacheKey := cache.CreateCacheKey("Search", q) cacheKey := cache.CreateCacheKey(
"Search",
o.Query,
o.Year,
o.PrimaryYear)
if cache.GetCache(GameStore, cacheKey, &resp) { if cache.GetCache(GameStore, cacheKey, &resp) {
slog.Debug("Search: Returning cache.") slog.Debug("Search: Returning cache.")
return resp, nil return resp, nil
} }
apiQuery := "fields " + fieldsForSearch + "; search \"" + o.Query + "\"; limit 40;"
whereQ := o.AsWhere()
if whereQ != "" {
apiQuery += whereQ
}
err := i.req( err := i.req(
igdbHost, igdbHost,
"/games", "/games",
map[string]string{}, map[string]string{},
"fields "+fieldsForSearch+"; search \""+q+"\"; limit 40;", apiQuery,
&resp, &resp,
) )
if err != nil { if err != nil {