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 ") } 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 }