package client import ( "encoding/json" "fmt" "net/http" "net/url" "strconv" "github.com/fjurmanovic/sugarr/internal/model" "github.com/fjurmanovic/sugarr/internal/utl/configs" ) const tmdbBaseURL = "https://api.themoviedb.org" type TMDbClient struct { apiKey string http *http.Client } func NewTMDbClient(cfg *configs.Config) *TMDbClient { return &TMDbClient{ apiKey: cfg.TMDbAPIKey, http: &http.Client{}, } } type tmdbSearchResponse struct { Results []tmdbMovieJSON `json:"results"` } type tmdbMovieJSON struct { ID int `json:"id"` Title string `json:"title"` ReleaseDate string `json:"release_date"` } func (m tmdbMovieJSON) toModel() model.TMDbMovie { year := 0 if len(m.ReleaseDate) >= 4 { year, _ = strconv.Atoi(m.ReleaseDate[:4]) } return model.TMDbMovie{ID: m.ID, Title: m.Title, Year: year} } func (c *TMDbClient) get(path string, params url.Values) (*http.Response, error) { params.Set("api_key", c.apiKey) u := fmt.Sprintf("%s%s?%s", tmdbBaseURL, path, params.Encode()) req, err := http.NewRequest(http.MethodGet, u, nil) if err != nil { return nil, err } req.Header.Set("Accept", "application/json") resp, err := c.http.Do(req) if err != nil { return nil, fmt.Errorf("tmdb request failed: %w", err) } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, fmt.Errorf("tmdb returned status %d for %s", resp.StatusCode, path) } return resp, nil } func (c *TMDbClient) SearchMovie(title string, year int) (*model.TMDbMovie, error) { params := url.Values{"query": {title}} if year > 0 { params.Set("year", strconv.Itoa(year)) } resp, err := c.get("/3/search/movie", params) if err != nil { return nil, err } defer resp.Body.Close() var result tmdbSearchResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("decoding tmdb search response: %w", err) } if len(result.Results) == 0 { return nil, nil } m := result.Results[0].toModel() return &m, nil } func (c *TMDbClient) getMovieList(path string) ([]model.TMDbMovie, error) { resp, err := c.get(path, url.Values{}) if err != nil { return nil, err } defer resp.Body.Close() var result tmdbSearchResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("decoding tmdb response: %w", err) } movies := make([]model.TMDbMovie, 0, len(result.Results)) for _, r := range result.Results { movies = append(movies, r.toModel()) } return movies, nil } func (c *TMDbClient) GetSimilar(tmdbID int) ([]model.TMDbMovie, error) { return c.getMovieList(fmt.Sprintf("/3/movie/%d/similar", tmdbID)) } func (c *TMDbClient) GetRecommendations(tmdbID int) ([]model.TMDbMovie, error) { return c.getMovieList(fmt.Sprintf("/3/movie/%d/recommendations", tmdbID)) }