Add queue listing and removal/blocklisting to the shared arr client. The Downloads view now fetches SABnzbd and both arr queues in parallel, shows status, progress and import problems, flags items needing attention in the summary, and supports removing or blocklisting items. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
148 lines
5.0 KiB
Go
148 lines
5.0 KiB
Go
// 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) }
|
|
|
|
// 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"}})
|
|
}
|