From 5ada1f72e48c7f4fae94d3c1c1e5f4189e6877ff Mon Sep 17 00:00:00 2001 From: mkelvers Date: Thu, 11 Jun 2026 12:27:56 +0200 Subject: [PATCH] feat: add shared query param helpers for jikan --- integrations/jikan/episodes.go | 6 ++- integrations/jikan/more.go | 6 ++- integrations/jikan/producers.go | 9 ++-- integrations/jikan/query_params.go | 43 ++++++++++++++++ integrations/jikan/search.go | 78 +++++++++++++++--------------- integrations/jikan/seasons.go | 6 ++- 6 files changed, 103 insertions(+), 45 deletions(-) create mode 100644 integrations/jikan/query_params.go diff --git a/integrations/jikan/episodes.go b/integrations/jikan/episodes.go index 130c454..c8e8592 100644 --- a/integrations/jikan/episodes.go +++ b/integrations/jikan/episodes.go @@ -3,6 +3,8 @@ package jikan import ( "context" "fmt" + "net/url" + "strconv" "sync" "time" ) @@ -15,7 +17,9 @@ func (c *Client) GetEpisodes(ctx context.Context, animeID int, page int) (Episod cacheKey := fmt.Sprintf("anime:%d:episodes:%d", animeID, page) var result EpisodesResponse - reqURL := fmt.Sprintf("%s/anime/%d/episodes?page=%d", c.baseURL, animeID, page) + params := url.Values{} + params.Set("page", strconv.Itoa(page)) + reqURL := buildRequestURL(c.baseURL, fmt.Sprintf("/anime/%d/episodes", animeID), params) err := c.getWithCache(ctx, cacheKey, 12*time.Hour, reqURL, &result) return result, err diff --git a/integrations/jikan/more.go b/integrations/jikan/more.go index be34d65..6aea3a0 100644 --- a/integrations/jikan/more.go +++ b/integrations/jikan/more.go @@ -3,6 +3,8 @@ package jikan import ( "context" "fmt" + "net/url" + "strconv" ) func (c *Client) GetAnimeStaff(ctx context.Context, id int) ([]StaffEntry, error) { @@ -46,7 +48,9 @@ func (c *Client) GetAnimeReviews(ctx context.Context, id int, page int) ([]Revie page = 1 } - url := fmt.Sprintf("%s/anime/%d/reviews?page=%d", c.baseURL, id, page) + params := url.Values{} + params.Set("page", strconv.Itoa(page)) + url := buildRequestURL(c.baseURL, fmt.Sprintf("/anime/%d/reviews", id), params) cacheKey := fmt.Sprintf("anime:reviews:%d:%d", id, page) var resp ReviewsResponse diff --git a/integrations/jikan/producers.go b/integrations/jikan/producers.go index 3ab0542..a29b6a8 100644 --- a/integrations/jikan/producers.go +++ b/integrations/jikan/producers.go @@ -56,10 +56,11 @@ func (c *Client) GetProducers(ctx context.Context, query string, page int, limit func (c *Client) fetchProducersPage(ctx context.Context, query string, page int, limit int) (ProducerListResult, error) { q := strings.TrimSpace(query) cacheKey := fmt.Sprintf("producers:%s:%d:%d", q, page, limit) - reqURL := fmt.Sprintf("%s/producers?page=%d&limit=%d", c.baseURL, page, limit) - if q != "" { - reqURL += "&q=" + url.QueryEscape(q) - } + params := url.Values{} + params.Set("page", strconv.Itoa(page)) + params.Set("limit", strconv.Itoa(limit)) + setQueryValue(params, "q", q) + reqURL := buildRequestURL(c.baseURL, "/producers", params) var result ProducersResponse if err := c.getWithCache(ctx, cacheKey, producerCacheTTL, reqURL, &result); err != nil { diff --git a/integrations/jikan/query_params.go b/integrations/jikan/query_params.go new file mode 100644 index 0000000..18b7495 --- /dev/null +++ b/integrations/jikan/query_params.go @@ -0,0 +1,43 @@ +package jikan + +import ( + "fmt" + "net/url" + "strconv" +) + +func buildRequestURL(baseURL, path string, params url.Values) string { + encoded := params.Encode() + if encoded == "" { + return fmt.Sprintf("%s%s", baseURL, path) + } + + return fmt.Sprintf("%s%s?%s", baseURL, path, encoded) +} + +func setQueryValue(values url.Values, key, value string) { + if value == "" { + values.Del(key) + return + } + + values.Set(key, value) +} + +func setPositiveIntQueryValue(values url.Values, key string, value int) { + if value <= 0 { + values.Del(key) + return + } + + values.Set(key, strconv.Itoa(value)) +} + +func setTrueQueryValue(values url.Values, key string, enabled bool) { + if !enabled { + values.Del(key) + return + } + + values.Set(key, "true") +} diff --git a/integrations/jikan/search.go b/integrations/jikan/search.go index f0774cb..770c2f2 100644 --- a/integrations/jikan/search.go +++ b/integrations/jikan/search.go @@ -8,8 +8,7 @@ import ( "strings" ) -// SearchAdvanced performs a filtered anime search with type, status, ordering, genre filters, and studio (producer) filters. -func (c *Client) SearchAdvanced(ctx context.Context, query, animeType, status, orderBy, sort string, genres []int, studioID int, sfw bool, page, limit int) (SearchResult, error) { +func normalizeSearchPagination(page, limit int) (int, int) { if page < 1 { page = 1 } @@ -17,46 +16,47 @@ func (c *Client) SearchAdvanced(ctx context.Context, query, animeType, status, o limit = 0 } - genresParam := "" - if len(genres) > 0 { - ids := make([]string, len(genres)) - for i, g := range genres { - ids[i] = strconv.Itoa(g) - } - genresParam = strings.Join(ids, ",") + return page, limit +} + +func joinGenreIDs(genres []int) string { + if len(genres) == 0 { + return "" } + ids := make([]string, len(genres)) + for i, g := range genres { + ids[i] = strconv.Itoa(g) + } + + return strings.Join(ids, ",") +} + +func buildAdvancedSearchURL(baseURL, query, animeType, status, orderBy, sort, genres string, studioID int, sfw bool, page, limit int) string { + params := url.Values{} + params.Set("page", strconv.Itoa(page)) + setTrueQueryValue(params, "sfw", sfw) + setQueryValue(params, "q", query) + setQueryValue(params, "type", animeType) + setQueryValue(params, "status", status) + setPositiveIntQueryValue(params, "producers", studioID) + setQueryValue(params, "order_by", orderBy) + setQueryValue(params, "sort", sort) + setQueryValue(params, "genres", genres) + setPositiveIntQueryValue(params, "limit", limit) + + return buildRequestURL(baseURL, "/anime", params) +} + +// SearchAdvanced performs a filtered anime search with type, status, ordering, genre filters, and studio (producer) filters. +func (c *Client) SearchAdvanced(ctx context.Context, query, animeType, status, orderBy, sort string, genres []int, studioID int, sfw bool, page, limit int) (SearchResult, error) { + page, limit = normalizeSearchPagination(page, limit) + genresParam := joinGenreIDs(genres) + cacheKey := fmt.Sprintf("search:%s:%s:%s:%s:%s:%s:%d:%v:%d:%d", query, animeType, status, orderBy, sort, genresParam, studioID, sfw, page, limit) var result SearchResponse - reqURL := fmt.Sprintf("%s/anime?page=%d", c.baseURL, page) - if sfw { - reqURL += "&sfw=true" - } - if query != "" { - reqURL += "&q=" + url.QueryEscape(query) - } - if animeType != "" { - reqURL += "&type=" + url.QueryEscape(animeType) - } - if status != "" { - reqURL += "&status=" + url.QueryEscape(status) - } - if studioID > 0 { - reqURL += "&producers=" + strconv.Itoa(studioID) - } - if orderBy != "" { - reqURL += "&order_by=" + url.QueryEscape(orderBy) - } - if sort != "" { - reqURL += "&sort=" + url.QueryEscape(sort) - } - if genresParam != "" { - reqURL += "&genres=" + genresParam - } - if limit > 0 { - reqURL += fmt.Sprintf("&limit=%d", limit) - } + reqURL := buildAdvancedSearchURL(c.baseURL, query, animeType, status, orderBy, sort, genresParam, studioID, sfw, page, limit) if err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result); err != nil { return SearchResult{}, err @@ -76,7 +76,9 @@ func (c *Client) GetTopAnime(ctx context.Context, page int) (TopAnimeResult, err cacheKey := fmt.Sprintf("top:%d", page) var result TopAnimeResponse - reqURL := fmt.Sprintf("%s/top/anime?page=%d", c.baseURL, page) + params := url.Values{} + params.Set("page", strconv.Itoa(page)) + reqURL := buildRequestURL(c.baseURL, "/top/anime", params) if err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result); err != nil { return TopAnimeResult{}, err diff --git a/integrations/jikan/seasons.go b/integrations/jikan/seasons.go index 1584989..32088de 100644 --- a/integrations/jikan/seasons.go +++ b/integrations/jikan/seasons.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "math/rand" + "net/url" + "strconv" "time" ) @@ -30,7 +32,9 @@ func (c *Client) getSeasonList(ctx context.Context, page int, season string) (To cacheKey := fmt.Sprintf("seasons_%s:%d", season, page) var result TopAnimeResponse - reqURL := fmt.Sprintf("%s/seasons/%s?page=%d", c.baseURL, season, page) + params := url.Values{} + params.Set("page", strconv.Itoa(page)) + reqURL := buildRequestURL(c.baseURL, fmt.Sprintf("/seasons/%s", season), params) err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result) if err != nil {