Добавил поиск метаданных по каталогам
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user