// Package radarr is a client for the Radarr v3 API. package radarr import ( "context" "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"` } // 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 { body := map[string]any{"name": "MoviesSearch", "movieIds": []int{movieID}} return c.Do(ctx, http.MethodPost, "command", nil, body, nil) } // TMDBTerm builds a lookup term for a TMDb id. func TMDBTerm(tmdbID int) string { return "tmdb:" + strconv.Itoa(tmdbID) }