Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grab and release endpoints for radarr/prowlarr, indexer for all five starrs. #166

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
24 changes: 24 additions & 0 deletions lidarr/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,27 @@ func (l *Lidarr) DeleteIndexerContext(ctx context.Context, indexerID int64) erro

return nil
}

// UpdateIndexers bulk updates indexers.
func (l *Lidarr) UpdateIndexers(indexer *starr.BulkIndexer) (*IndexerOutput, error) {
return l.UpdateIndexersContext(context.Background(), indexer)
}

// UpdateIndexersContext bulk updates indexers.
func (l *Lidarr) UpdateIndexersContext(ctx context.Context, indexer *starr.BulkIndexer) (*IndexerOutput, error) {
var (
output IndexerOutput
body bytes.Buffer
)

if err := json.NewEncoder(&body).Encode(indexer); err != nil {
return nil, fmt.Errorf("json.Marshal(%s): %w", bpIndexer, err)
}

req := starr.Request{URI: path.Join(bpIndexer, "bulk"), Body: &body}
if err := l.PutInto(ctx, req, &output); err != nil {
return nil, fmt.Errorf("api.Put(%s): %w", &req, err)
}

return &output, nil
}
38 changes: 38 additions & 0 deletions prowlarr/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,41 @@ func (p *Prowlarr) DeleteIndexerContext(ctx context.Context, indexerID int64) er

return nil
}

// BulkIndexer is the input to UpdateIndexers. Use the starr.Ptr(any) func to create the pointers.
type BulkIndexer struct {
IDs []int64 `json:"ids"`
Tags []int `json:"tags,omitempty"`
ApplyTags starr.ApplyTags `json:"applyTags,omitempty"`
Enable *bool `json:"enable,omitempty"`
AppProfileID *int64 `json:"appProfileId,omitempty"`
Priority *int64 `json:"priority,omitempty"`
MinimumSeeders *int `json:"minimumSeeders,omitempty"`
SeedRatio *int `json:"seedRatio,omitempty"`
SeedTime *int `json:"seedTime,omitempty"`
PackSeedTime *int `json:"packSeedTime,omitempty"`
}

// UpdateIndexers bulk updates indexers.
func (p *Prowlarr) UpdateIndexers(indexer *BulkIndexer) (*IndexerOutput, error) {
return p.UpdateIndexersContext(context.Background(), indexer)
}

// UpdateIndexersContext bulk updates indexers.
func (p *Prowlarr) UpdateIndexersContext(ctx context.Context, indexer *BulkIndexer) (*IndexerOutput, error) {
var (
output IndexerOutput
body bytes.Buffer
)

if err := json.NewEncoder(&body).Encode(indexer); err != nil {
return nil, fmt.Errorf("json.Marshal(%s): %w", bpIndexer, err)
}

req := starr.Request{URI: path.Join(bpIndexer, "bulk"), Body: &body}
if err := p.PutInto(ctx, req, &output); err != nil {
return nil, fmt.Errorf("api.Put(%s): %w", &req, err)
}

return &output, nil
}
47 changes: 44 additions & 3 deletions prowlarr/search.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package prowlarr

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/url"
"time"
Expand Down Expand Up @@ -32,7 +34,7 @@ type Search struct {
CommentURL string `json:"commentUrl"`
DownloadURL string `json:"downloadUrl"`
InfoURL string `json:"infoUrl"`
IndexerFlags []string `json:"indexerFlags"`
IndexerFlags []string `json:"indexerFlags,omitempty"`
Categories []*Category `json:"categories"`
Protocol starr.Protocol `json:"protocol"`
FileName string `json:"fileName"`
Expand Down Expand Up @@ -82,8 +84,8 @@ func (p *Prowlarr) SearchContext(ctx context.Context, search SearchInput) ([]*Se
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.Str(int64(search.Limit)))
req.Query.Set("offset", starr.Str(int64(search.Offset)))
req.Query.Set("limit", starr.Str(search.Limit))
req.Query.Set("offset", starr.Str(search.Offset))

for _, val := range search.Categories {
req.Query.Add("categories", starr.Str(val))
Expand All @@ -100,3 +102,42 @@ func (p *Prowlarr) SearchContext(ctx context.Context, search SearchInput) ([]*Se

return output, nil
}

// Grab attempts to download a searched item by guid. Use this with Pr*wlarr search output.
func (p *Prowlarr) Grab(guid string, indexerID int64) (*Search, error) {
return p.GrabContext(context.Background(), guid, indexerID)
}

// GrabContext attempts to download a searched item by guid. Use this with Pr*wlarr search output.
func (p *Prowlarr) GrabContext(ctx context.Context, guid string, indexerID int64) (*Search, error) {
return p.GrabSearchContext(ctx, &Search{IndexerID: indexerID, GUID: guid})
}

// GrabSearch attempts to download an item returned from a search.
// Pass the search for the item from the Search() output.
func (p *Prowlarr) GrabSearch(search *Search) (*Search, error) {
return p.GrabSearchContext(context.Background(), search)
}

// GrabSearchContext attempts to download an item returned from a search.
// Pass the search for the item from the Search() output.
func (p *Prowlarr) GrabSearchContext(ctx context.Context, search *Search) (*Search, error) {
grab := struct { // We only use/need the guid and indexerID from the search.
G string `json:"guid"`
I int64 `json:"indexerId"`
}{G: search.GUID, I: search.IndexerID}

var body bytes.Buffer
if err := json.NewEncoder(&body).Encode(&grab); err != nil {
return nil, fmt.Errorf("json.Marshal(%s): %w", bpSearch, err)
}

var output Search

req := starr.Request{URI: bpSearch, Body: &body}
if err := p.PostInto(ctx, req, &output); err != nil {
return nil, fmt.Errorf("api.Post(%s): %w", &req, err)
}

return &output, nil
}
2 changes: 1 addition & 1 deletion radarr/calendar.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (r *Radarr) GetCalendarID(calendarID int64) (*Movie, error) {
func (r *Radarr) GetCalendarIDContext(ctx context.Context, calendarID int64) (*Movie, error) {
var output *Movie

req := starr.Request{URI: path.Join(bpCalendar, starr.Itoa(calendarID))}
req := starr.Request{URI: path.Join(bpCalendar, starr.Str(calendarID))}
if err := r.GetInto(ctx, req, &output); err != nil {
return nil, fmt.Errorf("api.Get(%s): %w", &req, err)
}
Expand Down
24 changes: 24 additions & 0 deletions radarr/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,27 @@ func (r *Radarr) DeleteIndexerContext(ctx context.Context, indexerID int64) erro

return nil
}

// UpdateIndexers bulk updates indexers.
func (r *Radarr) UpdateIndexers(indexer *starr.BulkIndexer) ([]*IndexerOutput, error) {
return r.UpdateIndexersContext(context.Background(), indexer)
}

// UpdateIndexersContext bulk updates indexers.
func (r *Radarr) UpdateIndexersContext(ctx context.Context, indexer *starr.BulkIndexer) ([]*IndexerOutput, error) {
var (
output []*IndexerOutput
body bytes.Buffer
)

if err := json.NewEncoder(&body).Encode(indexer); err != nil {
return nil, fmt.Errorf("json.Marshal(%s): %w", bpIndexer, err)
}

req := starr.Request{URI: path.Join(bpIndexer, "bulk"), Body: &body}
if err := r.PutInto(ctx, req, &output); err != nil {
return nil, fmt.Errorf("api.Put(%s): %w", &req, err)
}

return output, nil
}
2 changes: 1 addition & 1 deletion radarr/manualimport.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type ManualImportOutput struct {
RelativePath string `json:"relativePath"`
FolderName string `json:"folderName"`
Name string `json:"name"`
Size int `json:"size"`
Size int64 `json:"size"`
Movie *Movie `json:"movie"`
Quality *starr.Quality `json:"quality"`
Languages []*starr.Value `json:"languages"`
Expand Down
3 changes: 1 addition & 2 deletions radarr/movieeditor.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import (
const bpMovieEditor = bpMovie + "/editor"

// BulkEdit is the input for the bulk movie editor endpoint.
// You may use starr.True(), starr.False(), starr.Int64(), and starr.String() to add data to the struct members.
// Use Availability.Ptr() to add a value to minimum availability, and starr.ApplyTags.Ptr() for apply tags.
// You may use starr.True(), starr.False(), and starr.Ptr() to add data to the pointer members.
type BulkEdit struct {
MovieIDs []int64 `json:"movieIds"`
Monitored *bool `json:"monitored,omitempty"`
Expand Down
139 changes: 139 additions & 0 deletions radarr/release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package radarr

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/url"
"time"

"golift.io/starr"
)

const bpRelease = APIver + "/release"

// Release is the output from the Radarr release endpoint.
type Release struct {
ID int64 `json:"id"`
GUID string `json:"guid"`
Quality *starr.Quality `json:"quality"`
CustomFormats []any `json:"customFormats"`
CustomFormatScore int64 `json:"customFormatScore"`
QualityWeight int64 `json:"qualityWeight"`
Age int64 `json:"age"`
AgeHours float64 `json:"ageHours"`
AgeMinutes float64 `json:"ageMinutes"`
Size int64 `json:"size"`
IndexerID int64 `json:"indexerId"`
Indexer string `json:"indexer"`
ReleaseGroup string `json:"releaseGroup"`
ReleaseHash string `json:"releaseHash"`
Title string `json:"title"`
SceneSource bool `json:"sceneSource"`
MovieTitles []string `json:"movieTitles"`
Languages []*starr.Value `json:"languages"`
MappedMovieID int64 `json:"mappedMovieId"`
Approved bool `json:"approved"`
TemporarilyRejected bool `json:"temporarilyRejected"`
Rejected bool `json:"rejected"`
TmdbID int64 `json:"tmdbId"`
ImdbID int64 `json:"imdbId"`
Rejections []string `json:"rejections"`
PublishDate time.Time `json:"publishDate"`
CommentURL string `json:"commentUrl"`
DownloadURL string `json:"downloadUrl"`
InfoURL string `json:"infoUrl"`
DownloadAllowed bool `json:"downloadAllowed"`
ReleaseWeight int64 `json:"releaseWeight"`
Edition string `json:"edition"`
MagnetURL string `json:"magnetUrl"`
InfoHash string `json:"infoHash"`
SubGroup string `json:"subGroup"`
Seeders int `json:"seeders"`
Leechers int `json:"leechers"`
Protocol starr.Protocol `json:"protocol"`
IndexerFlags []string `json:"indexerFlags,omitempty"`
MovieID int64 `json:"movieId"`
DownloadClientID int64 `json:"downloadClientId"`
DownloadClient string `json:"downloadClient"`
ShouldOverride bool `json:"shouldOverride"`
}

// SearchRelease searches for and returns a list releases available for download.
func (r *Radarr) SearchRelease(movieID int64) ([]*Release, error) {
return r.SearchReleaseContext(context.Background(), movieID)
}

// SearchReleaseContext searches for and returns a list releases available for download.
func (r *Radarr) SearchReleaseContext(ctx context.Context, movieID int64) ([]*Release, error) {
req := starr.Request{URI: bpRelease, Query: make(url.Values)}
req.Query.Set("movieId", starr.Str(movieID))

var output []*Release
if err := r.GetInto(ctx, req, &output); err != nil {
return nil, fmt.Errorf("api.Get(%s): %w", &req, err)
}

return output, nil
}

// Grab attempts to download a release by GUID.
func (r *Radarr) Grab(guid string, indexerID int64) (*Release, error) {
return r.GrabReleaseContext(context.Background(), &Release{IndexerID: indexerID, GUID: guid})
}

// GrabContext attempts to download a release by GUID.
func (r *Radarr) GrabContext(ctx context.Context, guid string, indexerID int64) (*Release, error) {
return r.GrabReleaseContext(ctx, &Release{IndexerID: indexerID, GUID: guid})
}

// GrabRelease attempts to download a release for a movie from a search.
// Pass the release for the item from the SearchRelease output for the release you wish to download.
// If the release.MovieID is 0 then release.MappedMovieID is used. Both may be 0, and that's OK unless
// release.ShouldOverride is true. If release.ShouldOverride is true, then Languages, MovieID and Quality
// must be present in the release.
func (r *Radarr) GrabRelease(release *Release) (*Release, error) {
return r.GrabReleaseContext(context.Background(), release)
}

// GrabReleaseContext attempts to download a release for a movie from a search.
// Pass the release for the item from the SearchRelease output for the release you wish to download.
// If the release.MovieID is 0 then release.MappedMovieID is used. Both may be 0, and that's OK unless
// release.ShouldOverride is true. If release.ShouldOverride is true, then Languages, MovieID and Quality
// must be present in the release.
func (r *Radarr) GrabReleaseContext(ctx context.Context, release *Release) (*Release, error) {
grab := struct { // These are the required fields on the Radarr POST /release endpoint.
GUID string `json:"guid"`
Indexer int64 `json:"indexerId"`
Override bool `json:"shouldOverride"`
Language []*starr.Value `json:"languages,omitempty"`
MovieID int64 `json:"movieId,omitempty"`
Quality *starr.Quality `json:"quality,omitempty"`
}{
GUID: release.GUID,
Indexer: release.IndexerID,
Override: release.ShouldOverride,
Language: release.Languages,
MovieID: release.MovieID,
Quality: release.Quality,
}

if grab.MovieID == 0 { // Best effort?
grab.MovieID = release.MappedMovieID
}

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 Release

req := starr.Request{URI: bpRelease, Body: &body}
if err := r.PostInto(ctx, req, &output); err != nil {
return nil, fmt.Errorf("api.Post(%s): %w", &req, err)
}

return &output, nil
}
24 changes: 24 additions & 0 deletions readarr/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,27 @@ func (r *Readarr) DeleteIndexerContext(ctx context.Context, indexerID int64) err

return nil
}

// UpdateIndexers bulk updates indexers.
func (r *Readarr) UpdateIndexers(indexer *starr.BulkIndexer) ([]*IndexerOutput, error) {
return r.UpdateIndexersContext(context.Background(), indexer)
}

// UpdateIndexersContext bulk updates indexers.
func (r *Readarr) UpdateIndexersContext(ctx context.Context, indexer *starr.BulkIndexer) ([]*IndexerOutput, error) {
var (
output []*IndexerOutput
body bytes.Buffer
)

if err := json.NewEncoder(&body).Encode(indexer); err != nil {
return nil, fmt.Errorf("json.Marshal(%s): %w", bpIndexer, err)
}

req := starr.Request{URI: path.Join(bpIndexer, "bulk"), Body: &body}
if err := r.PutInto(ctx, req, &output); err != nil {
return nil, fmt.Errorf("api.Put(%s): %w", &req, err)
}

return output, nil
}
12 changes: 12 additions & 0 deletions shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,15 @@ const (
ProtocolUsenet Protocol = "usenet"
ProtocolTorrent Protocol = "torrent"
)

// BulkIndexer is the input to UpdateIndexers on all apps except Prowlarr.
// Use the starr.True/False/Ptr() funcs to create the pointers.
type BulkIndexer struct {
IDs []int64 `json:"ids"`
Tags []int `json:"tags,omitempty"`
ApplyTags ApplyTags `json:"applyTags,omitempty"`
EnableRss *bool `json:"enableRss,omitempty"`
EnableAutomaticSearch *bool `json:"enableAutomaticSearch,omitempty"`
EnableInteractiveSearch *bool `json:"enableInteractiveSearch,omitempty"`
Priority *int64 `json:"priority,omitempty"`
}
3 changes: 1 addition & 2 deletions sonarr/episode.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"fmt"
"net/url"
"path"
"strconv"
"time"

"golift.io/starr"
Expand Down Expand Up @@ -67,7 +66,7 @@ func (s *Sonarr) GetSeriesEpisodesContext(ctx context.Context, getEpisode *GetEp
}

if getEpisode.SeasonNumber > 0 {
params.Set("seasonNumber", strconv.Itoa(getEpisode.SeasonNumber))
params.Set("seasonNumber", starr.Str(getEpisode.SeasonNumber))
}

for _, id := range getEpisode.EpisodeIDs {
Expand Down
Loading
Loading