Initial release
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
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 <title or TVDB ID>")
|
||||
}
|
||||
|
||||
var s *sonarr.Series
|
||||
|
||||
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)
|
||||
}
|
||||
s = &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)
|
||||
}
|
||||
// Use best fuzzy match
|
||||
titles := make([]string, len(results))
|
||||
for i, r := range results {
|
||||
titles[i] = r.Title
|
||||
}
|
||||
ranked := rankMatches(term, titles)
|
||||
s = &results[ranked[0].Index]
|
||||
}
|
||||
|
||||
if jsonOut {
|
||||
return writeJSON(seriesToJSON(*s))
|
||||
}
|
||||
|
||||
fmt.Printf("Title: %s\n", s.Title)
|
||||
fmt.Printf("Year: %d\n", s.Year)
|
||||
fmt.Printf("Status: %s\n", s.Status)
|
||||
if s.Network != "" {
|
||||
fmt.Printf("Network: %s\n", s.Network)
|
||||
}
|
||||
fmt.Printf("Seasons: %d\n", s.SeasonCount)
|
||||
if s.Statistics != nil {
|
||||
pct := 0.0
|
||||
if s.Statistics.EpisodeCount > 0 {
|
||||
pct = float64(s.Statistics.EpisodeFileCount) / float64(s.Statistics.EpisodeCount) * 100
|
||||
}
|
||||
fmt.Printf("Episodes: %d/%d (%.0f%%)\n", s.Statistics.EpisodeFileCount, s.Statistics.EpisodeCount, pct)
|
||||
}
|
||||
if s.NextAiring != "" {
|
||||
fmt.Printf("Next Airing: %s\n", s.NextAiring)
|
||||
}
|
||||
if s.Added != "" {
|
||||
fmt.Printf("Added: %s\n", s.Added)
|
||||
}
|
||||
if s.Overview != "" {
|
||||
fmt.Printf("Overview: %s\n", s.Overview)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TVProfiles(client SonarrClient, 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 TVRootFolders(client SonarrClient, 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 seriesToJSON(s sonarr.Series) jsonSeries {
|
||||
js := jsonSeries{
|
||||
Title: s.Title,
|
||||
Year: s.Year,
|
||||
Status: s.Status,
|
||||
TvdbID: s.TvdbID,
|
||||
Network: s.Network,
|
||||
SeasonCount: s.SeasonCount,
|
||||
NextAiring: s.NextAiring,
|
||||
Overview: s.Overview,
|
||||
Added: s.Added,
|
||||
}
|
||||
if s.Statistics != nil {
|
||||
js.EpisodesHave = s.Statistics.EpisodeFileCount
|
||||
js.EpisodesTotal = s.Statistics.EpisodeCount
|
||||
}
|
||||
return js
|
||||
}
|
||||
Reference in New Issue
Block a user