- Backend: Add Content-Type: application/json to all JSON API endpoints - Android TV: Reduce HomeViewModel memory usage (load 4 categories only, limit 15 items each) - Android TV: Prevent OOM kill on TV devices with limited RAM - Updated APK, docker-compose, health endpoint to v3.9.2
137 lines
3.3 KiB
Go
137 lines
3.3 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
TMDBBaseURL = "https://api.themoviedb.org/3"
|
|
TMDBImageBaseURL = "https://image.tmdb.org/t/p"
|
|
)
|
|
|
|
type TMDBService struct {
|
|
client *http.Client
|
|
apiKey string
|
|
}
|
|
|
|
func NewTMDBService() *TMDBService {
|
|
return &TMDBService{
|
|
client: &http.Client{Timeout: 10 * time.Second},
|
|
apiKey: os.Getenv("TMDB_API_KEY"),
|
|
}
|
|
}
|
|
|
|
type TMDBMovieResult struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"`
|
|
Overview string `json:"overview"`
|
|
PosterPath string `json:"poster_path"`
|
|
BackdropPath string `json:"backdrop_path"`
|
|
ReleaseDate string `json:"release_date"`
|
|
VoteAverage float64 `json:"vote_average"`
|
|
}
|
|
|
|
type TMDBSearchResponse struct {
|
|
Results []TMDBMovieResult `json:"results"`
|
|
}
|
|
|
|
type TMDBMovieDetails struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"`
|
|
Overview string `json:"overview"`
|
|
Runtime int `json:"runtime"`
|
|
Budget int64 `json:"budget"`
|
|
Revenue int64 `json:"revenue"`
|
|
Tagline string `json:"tagline"`
|
|
VoteAverage float64 `json:"vote_average"`
|
|
PosterPath string `json:"poster_path"`
|
|
BackdropPath string `json:"backdrop_path"`
|
|
Credits struct {
|
|
Cast []struct {
|
|
Name string `json:"name"`
|
|
Character string `json:"character"`
|
|
ProfilePath string `json:"profile_path"`
|
|
} `json:"cast"`
|
|
Crew []struct {
|
|
Name string `json:"name"`
|
|
Job string `json:"job"`
|
|
} `json:"crew"`
|
|
} `json:"credits"`
|
|
}
|
|
|
|
func (s *TMDBService) SearchMovie(title string, year int) (*TMDBMovieResult, error) {
|
|
if s.apiKey == "" {
|
|
return nil, fmt.Errorf("TMDB_API_KEY not set")
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Add("api_key", s.apiKey)
|
|
params.Add("query", title)
|
|
params.Add("language", "en-US")
|
|
if year > 0 {
|
|
params.Add("year", fmt.Sprintf("%d", year))
|
|
}
|
|
|
|
resp, err := s.client.Get(fmt.Sprintf("%s/search/movie?%s", TMDBBaseURL, params.Encode()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("TMDB API returned status: %d", resp.StatusCode)
|
|
}
|
|
|
|
var searchResp TMDBSearchResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&searchResp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(searchResp.Results) > 0 {
|
|
return &searchResp.Results[0], nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *TMDBService) GetMovieDetails(tmdbID int) (*TMDBMovieDetails, error) {
|
|
if s.apiKey == "" {
|
|
return nil, fmt.Errorf("TMDB_API_KEY not set")
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Add("api_key", s.apiKey)
|
|
params.Add("append_to_response", "credits")
|
|
params.Add("language", "en-US")
|
|
|
|
resp, err := s.client.Get(fmt.Sprintf("%s/movie/%d?%s", TMDBBaseURL, tmdbID, params.Encode()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("TMDB API returned status: %d", resp.StatusCode)
|
|
}
|
|
|
|
var details TMDBMovieDetails
|
|
if err := json.NewDecoder(resp.Body).Decode(&details); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &details, nil
|
|
}
|
|
|
|
func (s *TMDBService) GetPosterURL(path string, size string) string {
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
if size == "" {
|
|
size = "w500"
|
|
}
|
|
return fmt.Sprintf("%s/%s%s", TMDBImageBaseURL, size, path)
|
|
}
|