Initial release
This commit is contained in:
+302
@@ -0,0 +1,302 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user