package cmd import ( "fmt" "os" "strconv" "strings" "gitea.dcglab.co.uk/steve/arrman/sonarr" ) func filterSeries(series []sonarr.Series, term string) []sonarr.Series { lower := strings.ToLower(term) var filtered []sonarr.Series for _, s := range series { if strings.Contains(strings.ToLower(s.Title), lower) { filtered = append(filtered, s) } } return filtered } type SonarrClient interface { List() ([]sonarr.Series, error) Lookup(term string) ([]sonarr.Series, error) Get(id int) (*sonarr.Series, error) QualityProfiles() ([]sonarr.QualityProfile, error) RootFolders() ([]sonarr.RootFolder, error) Add(s sonarr.Series) (*sonarr.Series, error) } func TVList(client SonarrClient, term string, includeExternal bool, jsonOut bool) error { var series []sonarr.Series var err error if term != "" && includeExternal { series, err = client.Lookup(term) } else { series, err = client.List() if err == nil && term != "" { series = filterSeries(series, term) } } if err != nil { return err } if jsonOut { out := make([]jsonSeries, len(series)) for i, s := range series { out[i] = seriesToJSON(s) } return writeJSON(out) } if len(series) == 0 { fmt.Println("No series found") return nil } for _, s := range series { eps := "" if s.Statistics != nil { pct := 0.0 if s.Statistics.EpisodeCount > 0 { pct = float64(s.Statistics.EpisodeFileCount) / float64(s.Statistics.EpisodeCount) * 100 } eps = fmt.Sprintf(" — Episodes: %d/%d (%.0f%%)", s.Statistics.EpisodeFileCount, s.Statistics.EpisodeCount, pct) } fmt.Printf("%s (%d) — %s%s\n", s.Title, s.Year, s.Status, eps) } return nil } func TVAdd(client SonarrClient, 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 series *sonarr.Series // Check if term is all digits (TVDB ID) if isAllDigits(term) { results, err := client.Lookup("tvdb:" + term) if err != nil { return err } if len(results) == 0 { return fmt.Errorf("No series found with TVDB ID %s", term) } series = &results[0] } else { results, err := client.Lookup(term) if err != nil { return err } if len(results) == 0 { return fmt.Errorf("No series found for '%s'", term) } // Rank results by similarity titles := make([]string, len(results)) for i, s := range results { titles[i] = s.Title } ranked := rankMatches(term, titles) if ranked[0].Similarity >= fuzzyThreshold { series = &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 _, m := range ranked[:limit] { s := results[m.Index] suggestions.Suggestions = append(suggestions.Suggestions, jsonSuggestion{ Title: s.Title, Year: s.Year, TvdbID: s.TvdbID, }) } 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 _, m := range ranked[:limit] { s := results[m.Index] fmt.Fprintf(os.Stderr, " %s (%d) — TVDB ID: %d\n", s.Title, s.Year, s.TvdbID) } return fmt.Errorf("no match found") } } if series.ID != 0 { return fmt.Errorf("'%s' (%d) is already in your library", series.Title, series.Year) } series.QualityProfileID = profileID series.RootFolderPath = rootfolder series.Monitored = true series.AddOptions = &sonarr.AddOptions{SearchForMissingEpisodes: true} result, err := client.Add(*series) if err != nil { return err } if jsonOut { return writeJSON(jsonAdded{ Status: "added", Title: result.Title, Year: result.Year, TvdbID: result.TvdbID, }) } fmt.Printf("Added: %s (%d) — TVDB ID: %d\n", result.Title, result.Year, result.TvdbID) return nil } func TVSummary(client SonarrClient, term string, jsonOut bool) error { if term == "" { return fmt.Errorf("usage: arrman tv summary