133 lines
3.5 KiB
Go
133 lines
3.5 KiB
Go
package metadata
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const tmdbDefaultBaseURL = "https://api.themoviedb.org/3"
|
|
|
|
// TMDBConfig — настройки клиента TMDB.
|
|
type TMDBConfig struct {
|
|
APIKey string
|
|
Proxy string
|
|
Timeout time.Duration
|
|
BaseURL string // пусто → api.themoviedb.org; задаётся в тестах
|
|
}
|
|
|
|
// TMDB — клиент The Movie Database (API v3, авторизация по api_key).
|
|
type TMDB struct {
|
|
apiKey string
|
|
baseURL string
|
|
hc *http.Client
|
|
}
|
|
|
|
// NewTMDB собирает клиент TMDB.
|
|
func NewTMDB(cfg TMDBConfig) (*TMDB, error) {
|
|
if cfg.APIKey == "" {
|
|
return nil, fmt.Errorf("metadata: tmdb api_key required")
|
|
}
|
|
hc, err := newHTTPClient(cfg.Proxy, cfg.Timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
base := cfg.BaseURL
|
|
if base == "" {
|
|
base = tmdbDefaultBaseURL
|
|
}
|
|
return &TMDB{apiKey: cfg.APIKey, baseURL: strings.TrimRight(base, "/"), hc: hc}, nil
|
|
}
|
|
|
|
func (t *TMDB) Name() string { return "tmdb" }
|
|
|
|
type tmdbSearchResp struct {
|
|
Results []struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"` // movie
|
|
OriginalTitle string `json:"original_title"` // movie
|
|
Name string `json:"name"` // tv
|
|
OriginalName string `json:"original_name"` // tv
|
|
ReleaseDate string `json:"release_date"` // movie
|
|
FirstAirDate string `json:"first_air_date"` // tv
|
|
} `json:"results"`
|
|
}
|
|
|
|
// Search ищет фильм/сериал по названию и году.
|
|
func (t *TMDB) Search(ctx context.Context, q Query) ([]Candidate, error) {
|
|
var path string
|
|
params := url.Values{"api_key": {t.apiKey}, "query": {q.Title}, "include_adult": {"false"}}
|
|
switch q.Type {
|
|
case Movie:
|
|
path = "/search/movie"
|
|
if q.Year > 0 {
|
|
params.Set("year", strconv.Itoa(q.Year))
|
|
}
|
|
case Series:
|
|
path = "/search/tv"
|
|
if q.Year > 0 {
|
|
params.Set("first_air_date_year", strconv.Itoa(q.Year))
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("metadata: tmdb: неизвестный тип %q", q.Type)
|
|
}
|
|
|
|
var resp tmdbSearchResp
|
|
if err := getJSON(ctx, t.hc, t.baseURL+path+"?"+params.Encode(), nil, &resp); err != nil {
|
|
return nil, fmt.Errorf("tmdb search: %w", err)
|
|
}
|
|
|
|
out := make([]Candidate, 0, len(resp.Results))
|
|
for _, r := range resp.Results {
|
|
title, orig, date := r.Title, r.OriginalTitle, r.ReleaseDate
|
|
if q.Type == Series {
|
|
title, orig, date = r.Name, r.OriginalName, r.FirstAirDate
|
|
}
|
|
out = append(out, Candidate{
|
|
Provider: "tmdb",
|
|
ID: strconv.Itoa(r.ID),
|
|
Title: title,
|
|
OriginalTitle: orig,
|
|
Year: yearOf(date),
|
|
})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
type tmdbTVResp struct {
|
|
Seasons []struct {
|
|
SeasonNumber int `json:"season_number"`
|
|
EpisodeCount int `json:"episode_count"`
|
|
} `json:"seasons"`
|
|
}
|
|
|
|
// SeasonEpisodeCounts возвращает число серий по сезонам сериала.
|
|
func (t *TMDB) SeasonEpisodeCounts(ctx context.Context, id string) (map[int]int, error) {
|
|
params := url.Values{"api_key": {t.apiKey}}
|
|
var resp tmdbTVResp
|
|
if err := getJSON(ctx, t.hc, t.baseURL+"/tv/"+url.PathEscape(id)+"?"+params.Encode(), nil, &resp); err != nil {
|
|
return nil, fmt.Errorf("tmdb tv %s: %w", id, err)
|
|
}
|
|
out := make(map[int]int, len(resp.Seasons))
|
|
for _, s := range resp.Seasons {
|
|
out[s.SeasonNumber] = s.EpisodeCount
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// yearOf достаёт год из даты вида "1999-03-31".
|
|
func yearOf(date string) int {
|
|
if len(date) < 4 {
|
|
return 0
|
|
}
|
|
y, err := strconv.Atoi(date[:4])
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return y
|
|
}
|