From 34f20ac21e25e7bbad4b9b4e9a407a311f8ef70f Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Thu, 4 Jul 2024 11:03:19 -0700 Subject: [PATCH 1/5] add Prowlarr Search endpoint --- lidarr/blocklist.go | 2 +- lidarr/downloadclient.go | 4 +- lidarr/history.go | 38 +++++++------- lidarr/indexer.go | 4 +- lidarr/queue.go | 2 +- prowlarr/downloadclient.go | 4 +- prowlarr/indexer.go | 4 +- prowlarr/search.go | 102 +++++++++++++++++++++++++++++++++++++ radarr/blocklist.go | 2 +- radarr/delayprofile.go | 18 +++---- radarr/downloadclient.go | 4 +- radarr/history.go | 44 ++++++++-------- radarr/indexer.go | 4 +- radarr/queue.go | 2 +- readarr/blocklist.go | 2 +- readarr/downloadclient.go | 4 +- readarr/history.go | 38 +++++++------- readarr/indexer.go | 4 +- readarr/queue.go | 2 +- shared.go | 10 ++++ sonarr/blocklist.go | 2 +- sonarr/delayprofile.go | 18 +++---- sonarr/downloadclient.go | 4 +- sonarr/history.go | 44 ++++++++-------- sonarr/indexer.go | 4 +- sonarr/queue.go | 2 +- 26 files changed, 240 insertions(+), 128 deletions(-) create mode 100644 prowlarr/search.go diff --git a/lidarr/blocklist.go b/lidarr/blocklist.go index daf8149e..538ef9f8 100644 --- a/lidarr/blocklist.go +++ b/lidarr/blocklist.go @@ -34,7 +34,7 @@ type BlockListRecord struct { ArtistID int64 `json:"artistId"` Date time.Time `json:"date"` SourceTitle string `json:"sourceTitle"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Indexer string `json:"indexer"` Message string `json:"message"` } diff --git a/lidarr/downloadclient.go b/lidarr/downloadclient.go index 7156032b..f5a593c1 100644 --- a/lidarr/downloadclient.go +++ b/lidarr/downloadclient.go @@ -24,7 +24,7 @@ type DownloadClientInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -41,7 +41,7 @@ type DownloadClientOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/lidarr/history.go b/lidarr/history.go index 8f38b289..488ce96e 100644 --- a/lidarr/history.go +++ b/lidarr/history.go @@ -36,25 +36,25 @@ type HistoryRecord struct { DownloadID string `json:"downloadId"` EventType string `json:"eventType"` Data struct { - Age string `json:"age"` - AgeHours string `json:"ageHours"` - AgeMinutes string `json:"ageMinutes"` - DownloadClient string `json:"downloadClient"` - DownloadForced string `json:"downloadForced"` - DownloadURL string `json:"downloadUrl"` - DroppedPath string `json:"droppedPath"` - GUID string `json:"guid"` - ImportedPath string `json:"importedPath"` - Indexer string `json:"indexer"` - Message string `json:"message"` - NzbInfoURL string `json:"nzbInfoUrl"` - Protocol string `json:"protocol"` - PublishedDate time.Time `json:"publishedDate"` - Reason string `json:"reason"` - ReleaseGroup string `json:"releaseGroup"` - Size string `json:"size"` - StatusMessages string `json:"statusMessages"` - TorrentInfoHash string `json:"torrentInfoHash"` + Age string `json:"age"` + AgeHours string `json:"ageHours"` + AgeMinutes string `json:"ageMinutes"` + DownloadClient string `json:"downloadClient"` + DownloadForced string `json:"downloadForced"` + DownloadURL string `json:"downloadUrl"` + DroppedPath string `json:"droppedPath"` + GUID string `json:"guid"` + ImportedPath string `json:"importedPath"` + Indexer string `json:"indexer"` + Message string `json:"message"` + NzbInfoURL string `json:"nzbInfoUrl"` + Protocol starr.Protocol `json:"protocol"` + PublishedDate time.Time `json:"publishedDate"` + Reason string `json:"reason"` + ReleaseGroup string `json:"releaseGroup"` + Size string `json:"size"` + StatusMessages string `json:"statusMessages"` + TorrentInfoHash string `json:"torrentInfoHash"` } `json:"data"` } diff --git a/lidarr/indexer.go b/lidarr/indexer.go index ed01b73c..1bed18b3 100644 --- a/lidarr/indexer.go +++ b/lidarr/indexer.go @@ -23,7 +23,7 @@ type IndexerInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -42,7 +42,7 @@ type IndexerOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/lidarr/queue.go b/lidarr/queue.go index 635cf67e..55640be1 100644 --- a/lidarr/queue.go +++ b/lidarr/queue.go @@ -38,7 +38,7 @@ type QueueRecord struct { TrackedDownloadStatus string `json:"trackedDownloadStatus"` StatusMessages []*starr.StatusMessage `json:"statusMessages"` DownloadID string `json:"downloadId"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` DownloadClient string `json:"downloadClient"` Indexer string `json:"indexer"` OutputPath string `json:"outputPath"` diff --git a/prowlarr/downloadclient.go b/prowlarr/downloadclient.go index 23db7371..cb4148af 100644 --- a/prowlarr/downloadclient.go +++ b/prowlarr/downloadclient.go @@ -22,7 +22,7 @@ type DownloadClientInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -37,7 +37,7 @@ type DownloadClientOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/prowlarr/indexer.go b/prowlarr/indexer.go index 0509886e..dcd4c57c 100644 --- a/prowlarr/indexer.go +++ b/prowlarr/indexer.go @@ -24,7 +24,7 @@ type IndexerInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags,omitempty"` Fields []*starr.FieldInput `json:"fields"` } @@ -41,7 +41,7 @@ type IndexerOutput struct { Priority int64 `json:"priority"` SortName string `json:"sortName"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Privacy string `json:"privacy"` DefinitionName string `json:"definitionName"` Description string `json:"description"` diff --git a/prowlarr/search.go b/prowlarr/search.go new file mode 100644 index 00000000..bbf692c7 --- /dev/null +++ b/prowlarr/search.go @@ -0,0 +1,102 @@ +package prowlarr + +import ( + "context" + "fmt" + "net/url" + "time" + + "golift.io/starr" +) + +const bpSearch = APIver + "/search" + +// Search is the output from the Prowlarr search endpoint. +type Search struct { + GUID string `json:"guid"` + Age int64 `json:"age"` + AgeHours float64 `json:"ageHours"` + AgeMinutes float64 `json:"ageMinutes"` + Size int64 `json:"size"` + Files int `json:"files"` + Grabs int `json:"grabs"` + IndexerID int64 `json:"indexerId"` + Indexer string `json:"indexer"` + Title string `json:"title"` + SortTitle string `json:"sortTitle"` + ImdbID int64 `json:"imdbId"` + TmdbID int64 `json:"tmdbId"` + TvdbID int64 `json:"tvdbId"` + TvMazeID int64 `json:"tvMazeId"` + PublishDate time.Time `json:"publishDate"` + CommentURL string `json:"commentUrl"` + DownloadURL string `json:"downloadUrl"` + InfoURL string `json:"infoUrl"` + IndexerFlags []string `json:"indexerFlags"` + Categories []*Category `json:"categories"` + Protocol starr.Protocol `json:"protocol"` + FileName string `json:"fileName"` + InfoHash string `json:"infoHash"` + Seeders int `json:"seeders"` + Leechers int `json:"leechers"` +} + +// Category is part of the Search output. +type Category struct { + ID int64 `json:"id"` + Name string `json:"name"` + SubCategories []*Category `json:"subCategories"` +} + +// SearchInput is the input to the search endpoint. +type SearchInput struct { + Query string `json:"query"` // Query is required. Fill it in. + Type string `json:"type"` // defaults to "search" if left empty + IndexerIDs []int64 `json:"indexerIds"` + Categories []int64 `json:"categories"` + Limit int `json:"limit"` // Defaults to 100 if left empty or less than 1. + Offset int `json:"offset"` // Skip this many records. +} + +// Search the Prowlarr indexers for media and content. Must provide a Query in the SearchInput. +func (p *Prowlarr) Search(search SearchInput) ([]*Search, error) { + return p.SearchContext(context.Background(), search) +} + +// SearchContext searches the Prowlarr indexers for media and content. +func (p *Prowlarr) SearchContext(ctx context.Context, search SearchInput) ([]*Search, error) { + const defaultSearchLimit = 100 + + if search.Type == "" { + search.Type = "search" + } + + if search.Limit < 1 { + search.Limit = defaultSearchLimit + } + + if search.Limit < 0 { + search.Limit = 0 + } + + req := starr.Request{URI: bpSearch, Query: make(url.Values)} + req.Query.Set("query", search.Query) + req.Query.Set("type", search.Type) + req.Query.Set("limit", starr.Itoa(int64(search.Limit))) + req.Query.Set("offset", starr.Itoa(int64(search.Offset))) + + for _, val := range search.Categories { + req.Query.Add("categories", starr.Itoa(val)) + } + + for _, val := range search.IndexerIDs { + req.Query.Add("indexerIds", starr.Itoa(val)) + } + + var output []*Search + if err := p.GetInto(ctx, req, &output); err != nil { + return nil, fmt.Errorf("api.Get(%s): %w", &req, err) + } + + return output, nil +} diff --git a/radarr/blocklist.go b/radarr/blocklist.go index 6c721568..adce7b9a 100644 --- a/radarr/blocklist.go +++ b/radarr/blocklist.go @@ -34,7 +34,7 @@ type BlockListRecord struct { ID int64 `json:"id"` Date time.Time `json:"date"` SourceTitle string `json:"sourceTitle"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Indexer string `json:"indexer"` Message string `json:"message"` } diff --git a/radarr/delayprofile.go b/radarr/delayprofile.go index e159c6ca..05651d97 100644 --- a/radarr/delayprofile.go +++ b/radarr/delayprofile.go @@ -15,15 +15,15 @@ const bpDelayProfile = APIver + "/delayProfile" // DelayProfile is the /api/v3/delayprofile endpoint. type DelayProfile struct { - EnableUsenet bool `json:"enableUsenet,omitempty"` - EnableTorrent bool `json:"enableTorrent,omitempty"` - BypassIfHighestQuality bool `json:"bypassIfHighestQuality,omitempty"` - UsenetDelay int64 `json:"usenetDelay,omitempty"` - TorrentDelay int64 `json:"torrentDelay,omitempty"` - ID int64 `json:"id,omitempty"` - Order int64 `json:"order,omitempty"` - Tags []int `json:"tags"` - PreferredProtocol string `json:"preferredProtocol,omitempty"` + EnableUsenet bool `json:"enableUsenet,omitempty"` + EnableTorrent bool `json:"enableTorrent,omitempty"` + BypassIfHighestQuality bool `json:"bypassIfHighestQuality,omitempty"` + UsenetDelay int64 `json:"usenetDelay,omitempty"` + TorrentDelay int64 `json:"torrentDelay,omitempty"` + ID int64 `json:"id,omitempty"` + Order int64 `json:"order,omitempty"` + Tags []int `json:"tags"` + PreferredProtocol starr.Protocol `json:"preferredProtocol,omitempty"` } // GetDelayProfiles returns all configured delay profiles. diff --git a/radarr/downloadclient.go b/radarr/downloadclient.go index b585ec13..497d906a 100644 --- a/radarr/downloadclient.go +++ b/radarr/downloadclient.go @@ -24,7 +24,7 @@ type DownloadClientInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -41,7 +41,7 @@ type DownloadClientOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/radarr/history.go b/radarr/history.go index f5f0aaab..014add1e 100644 --- a/radarr/history.go +++ b/radarr/history.go @@ -35,28 +35,28 @@ type HistoryRecord struct { DownloadID string `json:"downloadId"` EventType string `json:"eventType"` Data struct { - Age string `json:"age"` - AgeHours string `json:"ageHours"` - AgeMinutes string `json:"ageMinutes"` - DownloadClient string `json:"downloadClient"` - DownloadClientName string `json:"downloadClientName"` - DownloadURL string `json:"downloadUrl"` - DroppedPath string `json:"droppedPath"` - FileID string `json:"fileId"` - GUID string `json:"guid"` - ImportedPath string `json:"importedPath"` - Indexer string `json:"indexer"` - IndexerFlags string `json:"indexerFlags"` - IndexerID string `json:"indexerId"` - Message string `json:"message"` - NzbInfoURL string `json:"nzbInfoUrl"` - Protocol string `json:"protocol"` - PublishedDate time.Time `json:"publishedDate"` - Reason string `json:"reason"` - ReleaseGroup string `json:"releaseGroup"` - Size string `json:"size"` - TmdbID string `json:"tmdbId"` - TorrentInfoHash string `json:"torrentInfoHash"` + Age string `json:"age"` + AgeHours string `json:"ageHours"` + AgeMinutes string `json:"ageMinutes"` + DownloadClient string `json:"downloadClient"` + DownloadClientName string `json:"downloadClientName"` + DownloadURL string `json:"downloadUrl"` + DroppedPath string `json:"droppedPath"` + FileID string `json:"fileId"` + GUID string `json:"guid"` + ImportedPath string `json:"importedPath"` + Indexer string `json:"indexer"` + IndexerFlags string `json:"indexerFlags"` + IndexerID string `json:"indexerId"` + Message string `json:"message"` + NzbInfoURL string `json:"nzbInfoUrl"` + Protocol starr.Protocol `json:"protocol"` + PublishedDate time.Time `json:"publishedDate"` + Reason string `json:"reason"` + ReleaseGroup string `json:"releaseGroup"` + Size string `json:"size"` + TmdbID string `json:"tmdbId"` + TorrentInfoHash string `json:"torrentInfoHash"` } `json:"data"` } diff --git a/radarr/indexer.go b/radarr/indexer.go index bf5ebe96..b819fa32 100644 --- a/radarr/indexer.go +++ b/radarr/indexer.go @@ -24,7 +24,7 @@ type IndexerInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -44,7 +44,7 @@ type IndexerOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/radarr/queue.go b/radarr/queue.go index 4f854b47..20ff1084 100644 --- a/radarr/queue.go +++ b/radarr/queue.go @@ -40,7 +40,7 @@ type QueueRecord struct { TrackedDownloadState string `json:"trackedDownloadState"` StatusMessages []*starr.StatusMessage `json:"statusMessages"` DownloadID string `json:"downloadId"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` DownloadClient string `json:"downloadClient"` Indexer string `json:"indexer"` OutputPath string `json:"outputPath"` diff --git a/readarr/blocklist.go b/readarr/blocklist.go index cd0a7939..dd93a300 100644 --- a/readarr/blocklist.go +++ b/readarr/blocklist.go @@ -34,7 +34,7 @@ type BlockListRecord struct { AuthorID int64 `json:"authorId"` Date time.Time `json:"date"` SourceTitle string `json:"sourceTitle"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Indexer string `json:"indexer"` Message string `json:"message"` } diff --git a/readarr/downloadclient.go b/readarr/downloadclient.go index 913d10f1..5fc993d9 100644 --- a/readarr/downloadclient.go +++ b/readarr/downloadclient.go @@ -23,7 +23,7 @@ type DownloadClientInput struct { Implementation string `json:"implementation"` ImplementationName string `json:"implementationName"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -38,7 +38,7 @@ type DownloadClientOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/readarr/history.go b/readarr/history.go index 5bab2d1f..ed4f87bf 100644 --- a/readarr/history.go +++ b/readarr/history.go @@ -35,25 +35,25 @@ type HistoryRecord struct { DownloadID string `json:"downloadId"` EventType string `json:"eventType"` Data struct { - Age string `json:"age"` - AgeHours string `json:"ageHours"` - AgeMinutes string `json:"ageMinutes"` - DownloadClient string `json:"downloadClient"` - DownloadForced string `json:"downloadForced"` - DownloadURL string `json:"downloadUrl"` - DroppedPath string `json:"droppedPath"` - GUID string `json:"guid"` - ImportedPath string `json:"importedPath"` - Indexer string `json:"indexer"` - Message string `json:"message"` - NzbInfoURL string `json:"nzbInfoUrl"` - Protocol string `json:"protocol"` - PublishedDate time.Time `json:"publishedDate"` - Reason string `json:"reason"` - ReleaseGroup string `json:"releaseGroup"` - Size string `json:"size"` - StatusMessages string `json:"statusMessages"` - TorrentInfoHash string `json:"torrentInfoHash"` + Age string `json:"age"` + AgeHours string `json:"ageHours"` + AgeMinutes string `json:"ageMinutes"` + DownloadClient string `json:"downloadClient"` + DownloadForced string `json:"downloadForced"` + DownloadURL string `json:"downloadUrl"` + DroppedPath string `json:"droppedPath"` + GUID string `json:"guid"` + ImportedPath string `json:"importedPath"` + Indexer string `json:"indexer"` + Message string `json:"message"` + NzbInfoURL string `json:"nzbInfoUrl"` + Protocol starr.Protocol `json:"protocol"` + PublishedDate time.Time `json:"publishedDate"` + Reason string `json:"reason"` + ReleaseGroup string `json:"releaseGroup"` + Size string `json:"size"` + StatusMessages string `json:"statusMessages"` + TorrentInfoHash string `json:"torrentInfoHash"` } `json:"data"` } diff --git a/readarr/indexer.go b/readarr/indexer.go index 31439f54..285f5868 100644 --- a/readarr/indexer.go +++ b/readarr/indexer.go @@ -23,7 +23,7 @@ type IndexerInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -42,7 +42,7 @@ type IndexerOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/readarr/queue.go b/readarr/queue.go index fc9dc8fe..3cf517a9 100644 --- a/readarr/queue.go +++ b/readarr/queue.go @@ -39,7 +39,7 @@ type QueueRecord struct { TrackedDownloadState string `json:"trackedDownloadState,omitempty"` StatusMessages []*starr.StatusMessage `json:"statusMessages,omitempty"` DownloadID string `json:"downloadId,omitempty"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` DownloadClient string `json:"downloadClient,omitempty"` Indexer string `json:"indexer"` OutputPath string `json:"outputPath,omitempty"` diff --git a/shared.go b/shared.go index 7e733362..ff36a65d 100644 --- a/shared.go +++ b/shared.go @@ -314,3 +314,13 @@ func Itoa(val int64) string { const base10 = 10 return strconv.FormatInt(val, base10) } + +// Protocol used to download media. Comes with enum constants. +type Protocol string + +// These are all the starr-supported protocols. +const ( + ProtocolUnknown Protocol = "unknown" + ProtocolUsenet Protocol = "usenet" + ProtocolTorrent Protocol = "torrent" +) diff --git a/sonarr/blocklist.go b/sonarr/blocklist.go index 2190e0d0..327fa402 100644 --- a/sonarr/blocklist.go +++ b/sonarr/blocklist.go @@ -35,7 +35,7 @@ type BlockListRecord struct { SeriesID int64 `json:"seriesId"` Date time.Time `json:"date"` SourceTitle string `json:"sourceTitle"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Indexer string `json:"indexer"` Message string `json:"message"` } diff --git a/sonarr/delayprofile.go b/sonarr/delayprofile.go index 0ae6c435..6c49f9de 100644 --- a/sonarr/delayprofile.go +++ b/sonarr/delayprofile.go @@ -15,15 +15,15 @@ const bpDelayProfile = APIver + "/delayProfile" // DelayProfile is the /api/v3/delayprofile endpoint. type DelayProfile struct { - EnableUsenet bool `json:"enableUsenet,omitempty"` - EnableTorrent bool `json:"enableTorrent,omitempty"` - BypassIfHighestQuality bool `json:"bypassIfHighestQuality,omitempty"` - UsenetDelay int64 `json:"usenetDelay,omitempty"` - TorrentDelay int64 `json:"torrentDelay,omitempty"` - ID int64 `json:"id,omitempty"` - Order int64 `json:"order,omitempty"` - Tags []int `json:"tags"` - PreferredProtocol string `json:"preferredProtocol,omitempty"` + EnableUsenet bool `json:"enableUsenet,omitempty"` + EnableTorrent bool `json:"enableTorrent,omitempty"` + BypassIfHighestQuality bool `json:"bypassIfHighestQuality,omitempty"` + UsenetDelay int64 `json:"usenetDelay,omitempty"` + TorrentDelay int64 `json:"torrentDelay,omitempty"` + ID int64 `json:"id,omitempty"` + Order int64 `json:"order,omitempty"` + Tags []int `json:"tags"` + PreferredProtocol starr.Protocol `json:"preferredProtocol,omitempty"` } // GetDelayProfiles returns all configured delay profiles. diff --git a/sonarr/downloadclient.go b/sonarr/downloadclient.go index 672dd039..b5be0fdb 100644 --- a/sonarr/downloadclient.go +++ b/sonarr/downloadclient.go @@ -24,7 +24,7 @@ type DownloadClientInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -41,7 +41,7 @@ type DownloadClientOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/sonarr/history.go b/sonarr/history.go index 0f940ada..c381d469 100644 --- a/sonarr/history.go +++ b/sonarr/history.go @@ -36,28 +36,28 @@ type HistoryRecord struct { DownloadID string `json:"downloadId,omitempty"` EventType string `json:"eventType"` Data struct { - Age string `json:"age"` - AgeHours string `json:"ageHours"` - AgeMinutes string `json:"ageMinutes"` - DownloadClient string `json:"downloadClient"` - DownloadClientName string `json:"downloadClientName"` - DownloadURL string `json:"downloadUrl"` - DroppedPath string `json:"droppedPath"` - FileID string `json:"fileId"` - GUID string `json:"guid"` - ImportedPath string `json:"importedPath"` - Indexer string `json:"indexer"` - Message string `json:"message"` - NzbInfoURL string `json:"nzbInfoUrl"` - PreferredWordScore string `json:"preferredWordScore"` - Protocol string `json:"protocol"` - PublishedDate time.Time `json:"publishedDate"` - Reason string `json:"reason"` - ReleaseGroup string `json:"releaseGroup"` - Size string `json:"size"` - TorrentInfoHash string `json:"torrentInfoHash"` - TvRageID string `json:"tvRageId"` - TvdbID string `json:"tvdbId"` + Age string `json:"age"` + AgeHours string `json:"ageHours"` + AgeMinutes string `json:"ageMinutes"` + DownloadClient string `json:"downloadClient"` + DownloadClientName string `json:"downloadClientName"` + DownloadURL string `json:"downloadUrl"` + DroppedPath string `json:"droppedPath"` + FileID string `json:"fileId"` + GUID string `json:"guid"` + ImportedPath string `json:"importedPath"` + Indexer string `json:"indexer"` + Message string `json:"message"` + NzbInfoURL string `json:"nzbInfoUrl"` + PreferredWordScore string `json:"preferredWordScore"` + Protocol starr.Protocol `json:"protocol"` + PublishedDate time.Time `json:"publishedDate"` + Reason string `json:"reason"` + ReleaseGroup string `json:"releaseGroup"` + Size string `json:"size"` + TorrentInfoHash string `json:"torrentInfoHash"` + TvRageID string `json:"tvRageId"` + TvdbID string `json:"tvdbId"` } `json:"data"` } diff --git a/sonarr/indexer.go b/sonarr/indexer.go index 492381a7..a8a02c07 100644 --- a/sonarr/indexer.go +++ b/sonarr/indexer.go @@ -24,7 +24,7 @@ type IndexerInput struct { ConfigContract string `json:"configContract"` Implementation string `json:"implementation"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldInput `json:"fields"` } @@ -44,7 +44,7 @@ type IndexerOutput struct { ImplementationName string `json:"implementationName"` InfoLink string `json:"infoLink"` Name string `json:"name"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` Tags []int `json:"tags"` Fields []*starr.FieldOutput `json:"fields"` } diff --git a/sonarr/queue.go b/sonarr/queue.go index 9651e027..5c286c1c 100644 --- a/sonarr/queue.go +++ b/sonarr/queue.go @@ -41,7 +41,7 @@ type QueueRecord struct { TrackedDownloadState string `json:"trackedDownloadState"` StatusMessages []*starr.StatusMessage `json:"statusMessages"` DownloadID string `json:"downloadId"` - Protocol string `json:"protocol"` + Protocol starr.Protocol `json:"protocol"` DownloadClient string `json:"downloadClient"` Indexer string `json:"indexer"` OutputPath string `json:"outputPath"` From b81b97a92294bafec66889ff75ac241118c0326a Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Thu, 4 Jul 2024 12:07:01 -0700 Subject: [PATCH 2/5] add sonarr release methods --- sonarr/parse.go | 2 +- sonarr/release.go | 184 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 sonarr/release.go diff --git a/sonarr/parse.go b/sonarr/parse.go index 273704d8..4190bd84 100644 --- a/sonarr/parse.go +++ b/sonarr/parse.go @@ -27,7 +27,7 @@ type SeriesTitleInfo struct { type ParsedEpisodeInfo struct { EpisodeNumbers []int `json:"episodeNumbers"` AbsoluteEpisodeNumbers []int `json:"absoluteEpisodeNumbers"` - SpecialAbsoluteEpisodeNumbers []interface{} `json:"specialAbsoluteEpisodeNumbers"` + SpecialAbsoluteEpisodeNumbers []int `json:"specialAbsoluteEpisodeNumbers"` Languages []*starr.Value `json:"languages"` SeasonNumber int `json:"seasonNumber"` SeasonPart int64 `json:"seasonPart"` diff --git a/sonarr/release.go b/sonarr/release.go new file mode 100644 index 00000000..ec54ec17 --- /dev/null +++ b/sonarr/release.go @@ -0,0 +1,184 @@ +package sonarr + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/url" + "strconv" + "time" + + "golift.io/starr" +) + +const bpRelease = APIver + "/release" + +// Release is the output from the Sonarr release endpoint. +type Release struct { + ID int64 `json:"id"` + GUID string `json:"guid"` + Quality starr.Quality `json:"quality"` + QualityWeight int64 `json:"qualityWeight"` + Age int64 `json:"age"` + AgeHours int `json:"ageHours"` + AgeMinutes int `json:"ageMinutes"` + Size int `json:"size"` + IndexerID int64 `json:"indexerId"` + Indexer string `json:"indexer"` + ReleaseGroup string `json:"releaseGroup"` + SubGroup string `json:"subGroup"` + ReleaseHash string `json:"releaseHash"` + Title string `json:"title"` + FullSeason bool `json:"fullSeason"` + SceneSource bool `json:"sceneSource"` + SeasonNumber int `json:"seasonNumber"` + Languages []*starr.Value `json:"languages"` + LanguageWeight int64 `json:"languageWeight"` + AirDate string `json:"airDate"` + SeriesTitle string `json:"seriesTitle"` + EpisodeNumbers []int `json:"episodeNumbers"` + AbsoluteEpisodeNumbers []int `json:"absoluteEpisodeNumbers"` + MappedSeasonNumber int `json:"mappedSeasonNumber"` + MappedEpisodeNumbers []int `json:"mappedEpisodeNumbers"` + MappedAbsoluteEpisodeNumbers []int `json:"mappedAbsoluteEpisodeNumbers"` + MappedSeriesID int64 `json:"mappedSeriesId"` + MappedEpisodeInfo []*ReleaseEpisodeInfo `json:"mappedEpisodeInfo"` + Approved bool `json:"approved"` + TemporarilyRejected bool `json:"temporarilyRejected"` + Rejected bool `json:"rejected"` + TvdbID int64 `json:"tvdbId"` + TvRageID int `json:"tvRageId"` + Rejections []string `json:"rejections"` + PublishDate time.Time `json:"publishDate"` + CommentURL string `json:"commentUrl"` + DownloadURL string `json:"downloadUrl"` + InfoURL string `json:"infoUrl"` + EpisodeRequested bool `json:"episodeRequested"` + DownloadAllowed bool `json:"downloadAllowed"` + ReleaseWeight int64 `json:"releaseWeight"` + CustomFormats []*CustomFormatOutput `json:"customFormats"` + CustomFormatScore int64 `json:"customFormatScore"` + SceneMapping ReleaseSceneMapping `json:"sceneMapping"` + MagnetURL string `json:"magnetUrl"` + InfoHash string `json:"infoHash"` + Seeders int `json:"seeders"` + Leechers int `json:"leechers"` + Protocol starr.Protocol `json:"protocol"` + IndexerFlags int64 `json:"indexerFlags"` + IsDaily bool `json:"isDaily"` + IsAbsoluteNumbering bool `json:"isAbsoluteNumbering"` + IsPossibleSpecialEpisode bool `json:"isPossibleSpecialEpisode"` + Special bool `json:"special"` + SeriesID int64 `json:"seriesId"` + EpisodeID int64 `json:"episodeId"` + EpisodeIDs []int64 `json:"episodeIds"` + DownloadClientID int64 `json:"downloadClientId"` + DownloadClient string `json:"downloadClient"` + ShouldOverride bool `json:"shouldOverride"` +} + +// ReleaseSceneMapping is part of a release. +type ReleaseSceneMapping struct { + Title string `json:"title"` + SeasonNumber int `json:"seasonNumber"` + SceneSeasonNumber int `json:"sceneSeasonNumber"` + SceneOrigin string `json:"sceneOrigin"` + Comment string `json:"comment"` +} + +// ReleaseEpisodeInfo is part of a release. +type ReleaseEpisodeInfo struct { + ID int64 `json:"id"` + SeasonNumber int `json:"seasonNumber"` + EpisodeNumber int `json:"episodeNumber"` + AbsoluteEpisodeNumber int `json:"absoluteEpisodeNumber"` + Title string `json:"title"` +} + +// SearchRelease is the input needed to search for releases through Sonarr. +type SearchRelease struct { + SeriesID int64 `json:"seriesId"` + EpisodeID int64 `json:"episodeId"` + SeasonNumber int `json:"seasonNumber"` +} + +// SearchRelease searches for and returns a list releases available for download. +func (s *Sonarr) SearchRelease(input *SearchRelease) ([]*Release, error) { + return s.SearchReleaseContext(context.Background(), input) +} + +// SearchReleaseContext searches for and returns a list releases available for download. +func (s *Sonarr) SearchReleaseContext(ctx context.Context, input *SearchRelease) ([]*Release, error) { + req := starr.Request{URI: bpRelease, Query: make(url.Values)} + req.Query.Set("seriesId", starr.Itoa(input.SeriesID)) + req.Query.Set("episodeId", starr.Itoa(input.EpisodeID)) + req.Query.Set("seasonNumber", strconv.Itoa(input.SeasonNumber)) + + var output []*Release + if err := s.GetInto(ctx, req, &output); err != nil { + return nil, fmt.Errorf("api.Get(%s): %w", &req, err) + } + + return output, nil +} + +// Grab is the output from the GrabRelease method. +type Grab struct { + GUID string `json:"guid"` + QualityWeight int64 `json:"qualityWeight"` + Age int64 `json:"age"` + AgeHours int `json:"ageHours"` + AgeMinutes int `json:"ageMinutes"` + Size int `json:"size"` + IndexerID int64 `json:"indexerId"` + FullSeason bool `json:"fullSeason"` + SceneSource bool `json:"sceneSource"` + SeasonNumber int `json:"seasonNumber"` + LanguageWeight int64 `json:"languageWeight"` + Approved bool `json:"approved"` + TemporarilyRejected bool `json:"temporarilyRejected"` + Rejected bool `json:"rejected"` + TvdbID int64 `json:"tvdbId"` + TvRageID int64 `json:"tvRageId"` + PublishDate time.Time `json:"publishDate"` + EpisodeRequested bool `json:"episodeRequested"` + DownloadAllowed bool `json:"downloadAllowed"` + ReleaseWeight int64 `json:"releaseWeight"` + CustomFormatScore int64 `json:"customFormatScore"` + Protocol starr.Protocol `json:"protocol"` + IndexerFlags int64 `json:"indexerFlags"` + IsDaily bool `json:"isDaily"` + IsAbsoluteNumbering bool `json:"isAbsoluteNumbering"` + IsPossibleSpecialEpisode bool `json:"isPossibleSpecialEpisode"` + Special bool `json:"special"` +} + +// GrabRelease adds a release and attempts to download it. +// Pass the release for the item from the SearchRelease output. +func (s *Sonarr) GrabRelease(release *Release) (*Grab, error) { + return s.GrabReleaseContext(context.Background(), release) +} + +// GrabReleaseContext adds a release and attempts to download it. +// Pass the release for the item from the SearchRelease output. +func (s *Sonarr) GrabReleaseContext(ctx context.Context, release *Release) (*Grab, error) { + var grab = struct { // We only use/need the guid and indexerID from the release. + G string `json:"guid"` + I int64 `json:"indexerId"` + }{G: release.GUID, I: release.IndexerID} + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(&grab); err != nil { + return nil, fmt.Errorf("json.Marshal(%s): %w", bpRelease, err) + } + + var output Grab + + req := starr.Request{URI: bpRelease, Body: &body} + if err := s.PostInto(ctx, req, &output); err != nil { + return nil, fmt.Errorf("api.Post(%s): %w", &req, err) + } + + return &output, nil +} From 54fa6fe269983ea19a84f2236789d10ad53229aa Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Thu, 4 Jul 2024 12:14:51 -0700 Subject: [PATCH 3/5] add more grab methods --- sonarr/release.go | 50 ++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/sonarr/release.go b/sonarr/release.go index ec54ec17..ef7f0556 100644 --- a/sonarr/release.go +++ b/sonarr/release.go @@ -123,35 +123,45 @@ func (s *Sonarr) SearchReleaseContext(ctx context.Context, input *SearchRelease) return output, nil } -// Grab is the output from the GrabRelease method. +// Grab is the output from the Grab* methods. type Grab struct { - GUID string `json:"guid"` - QualityWeight int64 `json:"qualityWeight"` - Age int64 `json:"age"` + Approved bool `json:"approved"` + DownloadAllowed bool `json:"downloadAllowed"` + EpisodeRequested bool `json:"episodeRequested"` + FullSeason bool `json:"fullSeason"` + Special bool `json:"special"` + TemporarilyRejected bool `json:"temporarilyRejected"` + IsAbsoluteNumbering bool `json:"isAbsoluteNumbering"` + IsDaily bool `json:"isDaily"` + IsPossibleSpecialEpisode bool `json:"isPossibleSpecialEpisode"` + Rejected bool `json:"rejected"` + SceneSource bool `json:"sceneSource"` AgeHours int `json:"ageHours"` AgeMinutes int `json:"ageMinutes"` + SeasonNumber int `json:"seasonNumber"` Size int `json:"size"` + Age int64 `json:"age"` + CustomFormatScore int64 `json:"customFormatScore"` + IndexerFlags int64 `json:"indexerFlags"` IndexerID int64 `json:"indexerId"` - FullSeason bool `json:"fullSeason"` - SceneSource bool `json:"sceneSource"` - SeasonNumber int `json:"seasonNumber"` LanguageWeight int64 `json:"languageWeight"` - Approved bool `json:"approved"` - TemporarilyRejected bool `json:"temporarilyRejected"` - Rejected bool `json:"rejected"` - TvdbID int64 `json:"tvdbId"` + QualityWeight int64 `json:"qualityWeight"` + ReleaseWeight int64 `json:"releaseWeight"` TvRageID int64 `json:"tvRageId"` + TvdbID int64 `json:"tvdbId"` PublishDate time.Time `json:"publishDate"` - EpisodeRequested bool `json:"episodeRequested"` - DownloadAllowed bool `json:"downloadAllowed"` - ReleaseWeight int64 `json:"releaseWeight"` - CustomFormatScore int64 `json:"customFormatScore"` + GUID string `json:"guid"` Protocol starr.Protocol `json:"protocol"` - IndexerFlags int64 `json:"indexerFlags"` - IsDaily bool `json:"isDaily"` - IsAbsoluteNumbering bool `json:"isAbsoluteNumbering"` - IsPossibleSpecialEpisode bool `json:"isPossibleSpecialEpisode"` - Special bool `json:"special"` +} + +// Grab adds a release and attempts to download it. Use this with Prowlarr search output. +func (s *Sonarr) Grab(guid string, indexerID int64) (*Grab, error) { + return s.GrabContext(context.Background(), guid, indexerID) +} + +// GrabContext adds a release and attempts to download it. Use this with Prowlarr search output. +func (s *Sonarr) GrabContext(ctx context.Context, guid string, indexerID int64) (*Grab, error) { + return s.GrabReleaseContext(ctx, &Release{IndexerID: indexerID, GUID: guid}) } // GrabRelease adds a release and attempts to download it. From c572cd72a549e8d9ed16620b2d1e01afb38f0dd0 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Thu, 4 Jul 2024 12:21:49 -0700 Subject: [PATCH 4/5] lint --- sonarr/release.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sonarr/release.go b/sonarr/release.go index ef7f0556..4d579d1d 100644 --- a/sonarr/release.go +++ b/sonarr/release.go @@ -154,12 +154,12 @@ type Grab struct { Protocol starr.Protocol `json:"protocol"` } -// Grab adds a release and attempts to download it. Use this with Prowlarr search output. +// Grab adds a release and attempts to download it. Use this with Prowl*rr search output. func (s *Sonarr) Grab(guid string, indexerID int64) (*Grab, error) { return s.GrabContext(context.Background(), guid, indexerID) } -// GrabContext adds a release and attempts to download it. Use this with Prowlarr search output. +// GrabContext adds a release and attempts to download it. Use this with Prowl*rr search output. func (s *Sonarr) GrabContext(ctx context.Context, guid string, indexerID int64) (*Grab, error) { return s.GrabReleaseContext(ctx, &Release{IndexerID: indexerID, GUID: guid}) } @@ -173,7 +173,7 @@ func (s *Sonarr) GrabRelease(release *Release) (*Grab, error) { // GrabReleaseContext adds a release and attempts to download it. // Pass the release for the item from the SearchRelease output. func (s *Sonarr) GrabReleaseContext(ctx context.Context, release *Release) (*Grab, error) { - var grab = struct { // We only use/need the guid and indexerID from the release. + grab := struct { // We only use/need the guid and indexerID from the release. G string `json:"guid"` I int64 `json:"indexerId"` }{G: release.GUID, I: release.IndexerID} From ce43360c555916f41972982ef3598ba0396129d3 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Thu, 4 Jul 2024 12:26:01 -0700 Subject: [PATCH 5/5] more lint --- debuglog/roundtripper.go | 2 +- http.go | 2 +- interface.go | 2 +- sonarr/release.go | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/debuglog/roundtripper.go b/debuglog/roundtripper.go index dacd67df..16fbcad8 100644 --- a/debuglog/roundtripper.go +++ b/debuglog/roundtripper.go @@ -133,7 +133,7 @@ func (f *fakeCloser) logRequest() (int, int) { sent = sent[:f.MaxBody] + " " } - switch ctype := f.Header.Get("content-type"); { + switch ctype := f.Header.Get("Content-Type"); { case !strings.Contains(ctype, "json"): // We only log JSON. Need something else? Ask! rcvd = "" diff --git a/http.go b/http.go index 9d695792..e0448b5a 100644 --- a/http.go +++ b/http.go @@ -148,7 +148,7 @@ func (c *Config) SetHeaders(req *http.Request) { } req.Header.Set("User-Agent", "go-starr: https://"+reflect.TypeOf(Config{}).PkgPath()) //nolint:exhaustivestruct - req.Header.Set("X-API-Key", c.APIKey) + req.Header.Set("X-Api-Key", c.APIKey) } // SetAPIPath makes sure the path starts with /api. diff --git a/interface.go b/interface.go index 3f5472ea..87e89b07 100644 --- a/interface.go +++ b/interface.go @@ -84,7 +84,7 @@ func (c *Config) Login(ctx context.Context) error { closeResp(resp) - if u, _ := url.Parse(c.URL); strings.Contains(codeErr.Get("location"), "loginFailed") || + if u, _ := url.Parse(c.URL); strings.Contains(codeErr.Get("Location"), "loginFailed") || len(c.Client.Jar.Cookies(u)) == 0 { return fmt.Errorf("%w: authenticating as user '%s' failed", ErrRequestError, c.Username) } diff --git a/sonarr/release.go b/sonarr/release.go index 4d579d1d..d8340879 100644 --- a/sonarr/release.go +++ b/sonarr/release.go @@ -154,12 +154,12 @@ type Grab struct { Protocol starr.Protocol `json:"protocol"` } -// Grab adds a release and attempts to download it. Use this with Prowl*rr search output. +// Grab adds a release and attempts to download it. Use this with Pr*wlarr search output. func (s *Sonarr) Grab(guid string, indexerID int64) (*Grab, error) { return s.GrabContext(context.Background(), guid, indexerID) } -// GrabContext adds a release and attempts to download it. Use this with Prowl*rr search output. +// GrabContext adds a release and attempts to download it. Use this with Pr*wlarr search output. func (s *Sonarr) GrabContext(ctx context.Context, guid string, indexerID int64) (*Grab, error) { return s.GrabReleaseContext(ctx, &Release{IndexerID: indexerID, GUID: guid}) }