303 lines
7.0 KiB
Go
303 lines
7.0 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitea.dcglab.co.uk/steve/arrman/radarr"
|
|
)
|
|
|
|
type RadarrClient interface {
|
|
List() ([]radarr.Movie, error)
|
|
Lookup(term string) ([]radarr.Movie, error)
|
|
LookupIMDB(imdbID string) (*radarr.Movie, error)
|
|
Get(id int) (*radarr.Movie, error)
|
|
QualityProfiles() ([]radarr.QualityProfile, error)
|
|
RootFolders() ([]radarr.RootFolder, error)
|
|
Add(m radarr.Movie) (*radarr.Movie, error)
|
|
}
|
|
|
|
func filterMovies(movies []radarr.Movie, term string) []radarr.Movie {
|
|
lower := strings.ToLower(term)
|
|
var filtered []radarr.Movie
|
|
for _, m := range movies {
|
|
if strings.Contains(strings.ToLower(m.Title), lower) {
|
|
filtered = append(filtered, m)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func FilmList(client RadarrClient, term string, includeExternal bool, jsonOut bool) error {
|
|
var movies []radarr.Movie
|
|
var err error
|
|
|
|
if term != "" && includeExternal {
|
|
movies, err = client.Lookup(term)
|
|
} else {
|
|
movies, err = client.List()
|
|
if err == nil && term != "" {
|
|
movies = filterMovies(movies, term)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if jsonOut {
|
|
out := make([]jsonMovie, len(movies))
|
|
for i, m := range movies {
|
|
out[i] = movieToJSON(m)
|
|
}
|
|
return writeJSON(out)
|
|
}
|
|
|
|
if len(movies) == 0 {
|
|
fmt.Println("No films found")
|
|
return nil
|
|
}
|
|
|
|
for _, m := range movies {
|
|
hasFile := "no"
|
|
if m.HasFile {
|
|
hasFile = "yes"
|
|
}
|
|
fmt.Printf("%s (%d) — %s — HasFile: %s\n", m.Title, m.Year, m.Status, hasFile)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func FilmAdd(client RadarrClient, args []string, jsonOut bool) error {
|
|
term, profile, rootfolder, err := parseAddArgs(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
profileID, err := strconv.Atoi(profile)
|
|
if err != nil {
|
|
return fmt.Errorf("--profile must be a number")
|
|
}
|
|
|
|
var movie *radarr.Movie
|
|
|
|
// Check if term looks like an IMDB ID (starts with "tt")
|
|
if strings.HasPrefix(term, "tt") {
|
|
m, err := client.LookupIMDB(term)
|
|
if err != nil {
|
|
return fmt.Errorf("No film found with IMDB ID %s", term)
|
|
}
|
|
if m.Title == "" {
|
|
return fmt.Errorf("No film found with IMDB ID %s", term)
|
|
}
|
|
movie = m
|
|
} else {
|
|
results, err := client.Lookup(term)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) == 0 {
|
|
return fmt.Errorf("No films found for '%s'", term)
|
|
}
|
|
|
|
// Rank results by similarity
|
|
titles := make([]string, len(results))
|
|
for i, m := range results {
|
|
titles[i] = m.Title
|
|
}
|
|
ranked := rankMatches(term, titles)
|
|
|
|
if ranked[0].Similarity >= fuzzyThreshold {
|
|
movie = &results[ranked[0].Index]
|
|
} else {
|
|
if jsonOut {
|
|
suggestions := jsonSuggestions{
|
|
Error: fmt.Sprintf("no match for '%s'", term),
|
|
Suggestions: make([]jsonSuggestion, 0),
|
|
}
|
|
limit := 5
|
|
if len(ranked) < limit {
|
|
limit = len(ranked)
|
|
}
|
|
for _, rm := range ranked[:limit] {
|
|
m := results[rm.Index]
|
|
suggestions.Suggestions = append(suggestions.Suggestions, jsonSuggestion{
|
|
Title: m.Title,
|
|
Year: m.Year,
|
|
ImdbID: m.ImdbID,
|
|
})
|
|
}
|
|
writeJSON(suggestions)
|
|
return fmt.Errorf("no match found")
|
|
}
|
|
fmt.Fprintf(os.Stderr, "No match for '%s'. Did you mean:\n", term)
|
|
limit := 5
|
|
if len(ranked) < limit {
|
|
limit = len(ranked)
|
|
}
|
|
for _, rm := range ranked[:limit] {
|
|
m := results[rm.Index]
|
|
fmt.Fprintf(os.Stderr, " %s (%d) — IMDB ID: %s\n", m.Title, m.Year, m.ImdbID)
|
|
}
|
|
return fmt.Errorf("no match found")
|
|
}
|
|
}
|
|
|
|
if movie.ID != 0 {
|
|
return fmt.Errorf("'%s' (%d) is already in your library", movie.Title, movie.Year)
|
|
}
|
|
|
|
movie.QualityProfileID = profileID
|
|
movie.RootFolderPath = rootfolder
|
|
movie.Monitored = true
|
|
movie.MinimumAvailability = "released"
|
|
movie.AddOptions = &radarr.AddOptions{SearchForMovie: true}
|
|
|
|
result, err := client.Add(*movie)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if jsonOut {
|
|
return writeJSON(jsonAdded{
|
|
Status: "added",
|
|
Title: result.Title,
|
|
Year: result.Year,
|
|
ImdbID: result.ImdbID,
|
|
})
|
|
}
|
|
|
|
fmt.Printf("Added: %s (%d) — IMDB ID: %s\n", result.Title, result.Year, result.ImdbID)
|
|
return nil
|
|
}
|
|
|
|
func FilmSummary(client RadarrClient, term string, jsonOut bool) error {
|
|
if term == "" {
|
|
return fmt.Errorf("usage: arrman film summary <title or IMDB ID>")
|
|
}
|
|
|
|
var m *radarr.Movie
|
|
|
|
if strings.HasPrefix(term, "tt") {
|
|
var err error
|
|
m, err = client.LookupIMDB(term)
|
|
if err != nil {
|
|
return fmt.Errorf("No film found with IMDB ID %s", term)
|
|
}
|
|
if m.Title == "" {
|
|
return fmt.Errorf("No film found with IMDB ID %s", term)
|
|
}
|
|
} else {
|
|
results, err := client.Lookup(term)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) == 0 {
|
|
return fmt.Errorf("No films found for '%s'", term)
|
|
}
|
|
titles := make([]string, len(results))
|
|
for i, r := range results {
|
|
titles[i] = r.Title
|
|
}
|
|
ranked := rankMatches(term, titles)
|
|
m = &results[ranked[0].Index]
|
|
}
|
|
|
|
if jsonOut {
|
|
return writeJSON(movieToJSON(*m))
|
|
}
|
|
|
|
fmt.Printf("Title: %s\n", m.Title)
|
|
fmt.Printf("Year: %d\n", m.Year)
|
|
fmt.Printf("Status: %s\n", m.Status)
|
|
if m.Studio != "" {
|
|
fmt.Printf("Studio: %s\n", m.Studio)
|
|
}
|
|
if m.Runtime > 0 {
|
|
fmt.Printf("Runtime: %d min\n", m.Runtime)
|
|
}
|
|
if m.ImdbID != "" {
|
|
fmt.Printf("IMDB ID: %s\n", m.ImdbID)
|
|
}
|
|
hasFile := m.HasFile || (m.MovieFile != nil && m.MovieFile.RelativePath != "")
|
|
if hasFile {
|
|
fmt.Println("HasFile: yes")
|
|
} else {
|
|
fmt.Println("HasFile: no")
|
|
}
|
|
if m.MovieFile != nil {
|
|
fmt.Printf("File: %s\n", m.MovieFile.RelativePath)
|
|
fmt.Printf("Size: %s\n", formatBytes(m.MovieFile.Size))
|
|
if m.MovieFile.Quality != nil {
|
|
fmt.Printf("Quality: %s\n", m.MovieFile.Quality.Quality.Name)
|
|
}
|
|
}
|
|
if m.Added != "" {
|
|
fmt.Printf("Added: %s\n", m.Added)
|
|
}
|
|
if m.Overview != "" {
|
|
fmt.Printf("Overview: %s\n", m.Overview)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func FilmProfiles(client RadarrClient, jsonOut bool) error {
|
|
profiles, err := client.QualityProfiles()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if jsonOut {
|
|
out := make([]jsonProfile, len(profiles))
|
|
for i, p := range profiles {
|
|
out[i] = jsonProfile{ID: p.ID, Name: p.Name}
|
|
}
|
|
return writeJSON(out)
|
|
}
|
|
for _, p := range profiles {
|
|
fmt.Printf("ID: %d — Name: %s\n", p.ID, p.Name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func FilmRootFolders(client RadarrClient, jsonOut bool) error {
|
|
folders, err := client.RootFolders()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if jsonOut {
|
|
out := make([]jsonRootFolder, len(folders))
|
|
for i, f := range folders {
|
|
out[i] = jsonRootFolder{ID: f.ID, Path: f.Path, FreeSpace: f.FreeSpace, FreeHuman: formatBytes(f.FreeSpace)}
|
|
}
|
|
return writeJSON(out)
|
|
}
|
|
for _, f := range folders {
|
|
fmt.Printf("ID: %d — Path: %s — Free: %s\n", f.ID, f.Path, formatBytes(f.FreeSpace))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func movieToJSON(m radarr.Movie) jsonMovie {
|
|
jm := jsonMovie{
|
|
Title: m.Title,
|
|
Year: m.Year,
|
|
Status: m.Status,
|
|
ImdbID: m.ImdbID,
|
|
Studio: m.Studio,
|
|
Runtime: m.Runtime,
|
|
HasFile: m.HasFile || (m.MovieFile != nil && m.MovieFile.RelativePath != ""),
|
|
Overview: m.Overview,
|
|
Added: m.Added,
|
|
}
|
|
if m.MovieFile != nil {
|
|
jm.File = m.MovieFile.RelativePath
|
|
jm.Size = m.MovieFile.Size
|
|
if m.MovieFile.Quality != nil {
|
|
jm.Quality = m.MovieFile.Quality.Quality.Name
|
|
}
|
|
}
|
|
return jm
|
|
}
|