183 lines
6.1 KiB
Go
183 lines
6.1 KiB
Go
// Package radarr is a client for the Radarr v3 API.
|
|
package radarr
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
"git.stinnesbeck.com/nils/arr-tui/internal/arr"
|
|
)
|
|
|
|
// Client talks to a Radarr instance. The embedded arr.Client provides
|
|
// SystemStatus, QualityProfiles and RootFolders.
|
|
type Client struct {
|
|
*arr.Client
|
|
}
|
|
|
|
// NewClient returns a Radarr client.
|
|
func NewClient(baseURL, apiKey string) *Client {
|
|
return &Client{arr.NewClient(baseURL, apiKey)}
|
|
}
|
|
|
|
// Ratings holds the per-source ratings Radarr reports.
|
|
type Ratings struct {
|
|
IMDB *Rating `json:"imdb,omitempty"`
|
|
TMDB *Rating `json:"tmdb,omitempty"`
|
|
Metacritic *Rating `json:"metacritic,omitempty"`
|
|
RottenTomatoes *Rating `json:"rottenTomatoes,omitempty"`
|
|
}
|
|
|
|
// Rating is a single rating value.
|
|
type Rating struct {
|
|
Votes int `json:"votes"`
|
|
Value float64 `json:"value"`
|
|
}
|
|
|
|
// Movie is a lookup result or library movie. ID is 0 when the movie is not
|
|
// in the library yet.
|
|
type Movie struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"`
|
|
OriginalTitle string `json:"originalTitle,omitempty"`
|
|
Year int `json:"year"`
|
|
Overview string `json:"overview,omitempty"`
|
|
Runtime int `json:"runtime"`
|
|
Genres []string `json:"genres,omitempty"`
|
|
Studio string `json:"studio,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
TMDBID int `json:"tmdbId"`
|
|
IMDBID string `json:"imdbId,omitempty"`
|
|
TitleSlug string `json:"titleSlug,omitempty"`
|
|
Images []arr.Image `json:"images,omitempty"`
|
|
Ratings Ratings `json:"ratings"`
|
|
Monitored bool `json:"monitored"`
|
|
HasFile bool `json:"hasFile"`
|
|
QualityProfileID int `json:"qualityProfileId"`
|
|
SizeOnDisk int64 `json:"sizeOnDisk"`
|
|
IsAvailable bool `json:"isAvailable"`
|
|
Path string `json:"path,omitempty"`
|
|
MovieFile *MovieFile `json:"movieFile,omitempty"`
|
|
}
|
|
|
|
// MovieFile is the downloaded file of a library movie.
|
|
type MovieFile struct {
|
|
Size int64 `json:"size"`
|
|
Quality struct {
|
|
Quality struct {
|
|
Name string `json:"name"`
|
|
} `json:"quality"`
|
|
} `json:"quality"`
|
|
}
|
|
|
|
// QualityName returns the quality of the downloaded file, or "".
|
|
func (m Movie) QualityName() string {
|
|
if m.MovieFile == nil {
|
|
return ""
|
|
}
|
|
return m.MovieFile.Quality.Quality.Name
|
|
}
|
|
|
|
// InLibrary reports whether the movie has already been added to Radarr.
|
|
func (m Movie) InLibrary() bool { return m.ID != 0 }
|
|
|
|
// Rating returns the best available rating (IMDb, then TMDb), or 0.
|
|
func (m Movie) Rating() float64 {
|
|
switch {
|
|
case m.Ratings.IMDB != nil && m.Ratings.IMDB.Value > 0:
|
|
return m.Ratings.IMDB.Value
|
|
case m.Ratings.TMDB != nil:
|
|
return m.Ratings.TMDB.Value
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Lookup searches for movies by free text ("The Matrix"), "tmdb:603" or "imdb:tt0133093".
|
|
func (c *Client) Lookup(ctx context.Context, term string) ([]Movie, error) {
|
|
var movies []Movie
|
|
err := c.Do(ctx, http.MethodGet, "movie/lookup", url.Values{"term": {term}}, nil, &movies)
|
|
return movies, err
|
|
}
|
|
|
|
// Movies lists the movies in the library.
|
|
func (c *Client) Movies(ctx context.Context) ([]Movie, error) {
|
|
var movies []Movie
|
|
err := c.Do(ctx, http.MethodGet, "movie", nil, nil, &movies)
|
|
return movies, err
|
|
}
|
|
|
|
// AddOptions are the choices from the add form.
|
|
type AddOptions struct {
|
|
QualityProfileID int
|
|
RootFolderPath string
|
|
Monitored bool
|
|
SearchNow bool
|
|
MinimumAvailability string // "announced", "inCinemas" or "released"; defaults to "released"
|
|
}
|
|
|
|
type addMovieRequest struct {
|
|
Title string `json:"title"`
|
|
Year int `json:"year"`
|
|
TMDBID int `json:"tmdbId"`
|
|
TitleSlug string `json:"titleSlug,omitempty"`
|
|
Images []arr.Image `json:"images,omitempty"`
|
|
QualityProfileID int `json:"qualityProfileId"`
|
|
RootFolderPath string `json:"rootFolderPath"`
|
|
Monitored bool `json:"monitored"`
|
|
MinimumAvailability string `json:"minimumAvailability"`
|
|
AddOptions struct {
|
|
SearchForMovie bool `json:"searchForMovie"`
|
|
} `json:"addOptions"`
|
|
}
|
|
|
|
// AddMovie adds a lookup result to Radarr and returns the created movie.
|
|
func (c *Client) AddMovie(ctx context.Context, m Movie, opts AddOptions) (Movie, error) {
|
|
req := addMovieRequest{
|
|
Title: m.Title,
|
|
Year: m.Year,
|
|
TMDBID: m.TMDBID,
|
|
TitleSlug: m.TitleSlug,
|
|
Images: m.Images,
|
|
QualityProfileID: opts.QualityProfileID,
|
|
RootFolderPath: opts.RootFolderPath,
|
|
Monitored: opts.Monitored,
|
|
MinimumAvailability: opts.MinimumAvailability,
|
|
}
|
|
if req.MinimumAvailability == "" {
|
|
req.MinimumAvailability = "released"
|
|
}
|
|
req.AddOptions.SearchForMovie = opts.SearchNow
|
|
|
|
var created Movie
|
|
err := c.Do(ctx, http.MethodPost, "movie", nil, req, &created)
|
|
return created, err
|
|
}
|
|
|
|
// SearchMovie triggers an automatic search for an existing library movie.
|
|
func (c *Client) SearchMovie(ctx context.Context, movieID int) error {
|
|
return c.Command(ctx, map[string]any{"name": "MoviesSearch", "movieIds": []int{movieID}})
|
|
}
|
|
|
|
// TMDBTerm builds a lookup term for a TMDb id.
|
|
func TMDBTerm(tmdbID int) string { return "tmdb:" + strconv.Itoa(tmdbID) }
|
|
|
|
// Queue lists Radarr's download queue, including movie info.
|
|
func (c *Client) Queue(ctx context.Context) ([]arr.QueueItem, error) {
|
|
return c.QueueWith(ctx, url.Values{"includeMovie": {"true"}})
|
|
}
|
|
|
|
// Releases runs an interactive search for a library movie.
|
|
func (c *Client) Releases(ctx context.Context, movieID int) ([]arr.Release, error) {
|
|
return c.Client.Releases(ctx, url.Values{"movieId": {strconv.Itoa(movieID)}})
|
|
}
|
|
|
|
// SetMonitored changes the monitored flag of a library movie.
|
|
func (c *Client) SetMonitored(ctx context.Context, movieID int, monitored bool) error {
|
|
return c.Update(ctx, "movie", movieID, func(o map[string]json.RawMessage) error {
|
|
arr.SetBool(o, "monitored", monitored)
|
|
return nil
|
|
})
|
|
}
|