Добавил еще провайдер TVMaze
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const tvmazeDefaultBaseURL = "https://api.tvmaze.com"
|
||||
|
||||
// TVMazeConfig — настройки клиента TVMaze.
|
||||
type TVMazeConfig struct {
|
||||
Proxy string
|
||||
Timeout time.Duration
|
||||
BaseURL string // пусто → api.tvmaze.com; задаётся в тестах
|
||||
}
|
||||
|
||||
// TVMaze — клиент TVMaze. Открытое API без ключа (лимит ~20 запросов/10с).
|
||||
// Покрывает только сериалы; для фильмов поиск возвращает пусто. В externals
|
||||
// отдаёт TVDB/IMDb-id, который используем как тег папки Jellyfin.
|
||||
type TVMaze struct {
|
||||
baseURL string
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// NewTVMaze собирает клиент TVMaze (ключ не нужен).
|
||||
func NewTVMaze(cfg TVMazeConfig) (*TVMaze, error) {
|
||||
hc, err := newHTTPClient(cfg.Proxy, cfg.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base := cfg.BaseURL
|
||||
if base == "" {
|
||||
base = tvmazeDefaultBaseURL
|
||||
}
|
||||
return &TVMaze{baseURL: strings.TrimRight(base, "/"), hc: hc}, nil
|
||||
}
|
||||
|
||||
func (t *TVMaze) Name() string { return "tvmaze" }
|
||||
|
||||
type tvmazeShow struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Premiered string `json:"premiered"`
|
||||
Externals struct {
|
||||
TheTVDB int `json:"thetvdb"`
|
||||
IMDb string `json:"imdb"`
|
||||
} `json:"externals"`
|
||||
}
|
||||
|
||||
// Search ищет сериал по названию. Фильмы TVMaze не покрывает — для них
|
||||
// возвращает пусто. Год не сужаем в запросе (TVMaze не фильтрует по году),
|
||||
// отбор по году делает вызывающий.
|
||||
func (t *TVMaze) Search(ctx context.Context, q Query) ([]Candidate, error) {
|
||||
if q.Type != Series {
|
||||
return nil, nil
|
||||
}
|
||||
var resp []struct {
|
||||
Show tvmazeShow `json:"show"`
|
||||
}
|
||||
rawURL := t.baseURL + "/search/shows?q=" + url.QueryEscape(q.Title)
|
||||
if err := getJSON(ctx, t.hc, rawURL, nil, &resp); err != nil {
|
||||
return nil, fmt.Errorf("tvmaze search: %w", err)
|
||||
}
|
||||
|
||||
out := make([]Candidate, 0, len(resp))
|
||||
for _, r := range resp {
|
||||
s := r.Show
|
||||
c := Candidate{
|
||||
Provider: "tvmaze",
|
||||
ID: strconv.Itoa(s.ID),
|
||||
Title: s.Name,
|
||||
Year: yearOf(s.Premiered),
|
||||
}
|
||||
// Тег папки — привычный TVDB-id, если есть; иначе IMDb.
|
||||
switch {
|
||||
case s.Externals.TheTVDB != 0:
|
||||
c.TagProvider, c.TagID = "tvdb", strconv.Itoa(s.Externals.TheTVDB)
|
||||
case s.Externals.IMDb != "":
|
||||
c.TagProvider, c.TagID = "imdb", s.Externals.IMDb
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type tvmazeEpisode struct {
|
||||
Season int `json:"season"`
|
||||
Number int `json:"number"`
|
||||
}
|
||||
|
||||
// SeasonEpisodeCounts считает число серий по сезонам (нативный id TVMaze).
|
||||
func (t *TVMaze) SeasonEpisodeCounts(ctx context.Context, id string) (map[int]int, error) {
|
||||
var eps []tvmazeEpisode
|
||||
rawURL := t.baseURL + "/shows/" + url.PathEscape(id) + "/episodes"
|
||||
if err := getJSON(ctx, t.hc, rawURL, nil, &eps); err != nil {
|
||||
return nil, fmt.Errorf("tvmaze episodes %s: %w", id, err)
|
||||
}
|
||||
out := map[int]int{}
|
||||
for _, e := range eps {
|
||||
out[e.Season]++
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user