Files
2026-03-12 22:13:57 +00:00

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
}