From 9cf530a6f24b7708d4807bb9be725936d1efebde Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sat, 2 Jul 2022 23:39:24 -0700 Subject: [PATCH 01/26] add missing delete methods, and update a few wrrors --- .golangci.yml | 5 +++++ lidarr/qualityprofile.go | 36 +++++++++++++++++++++++++++--------- radarr/customformat.go | 36 ++++++++++++++++++++++++++++-------- radarr/qualityprofile.go | 36 +++++++++++++++++++++++++++--------- readarr/qualityprofile.go | 36 +++++++++++++++++++++++++++--------- sonarr/qualityprofile.go | 14 +++++++------- sonarr/releaseprofile.go | 14 +++++++------- 7 files changed, 128 insertions(+), 49 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 510c10a..776f667 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,6 +2,10 @@ issues: exclude-rules: # Exclude funlen for testing files. - linters: + - nonamedreturns + path: 'starrcmd/' + - linters: + - forcetypeassert - funlen path: '(.+)_test\.go' @@ -14,6 +18,7 @@ linters: - golint - tagliatelle - exhaustivestruct + - exhaustruct - dupl run: diff --git a/lidarr/qualityprofile.go b/lidarr/qualityprofile.go index 6392a18..d9b3201 100644 --- a/lidarr/qualityprofile.go +++ b/lidarr/qualityprofile.go @@ -5,11 +5,14 @@ import ( "context" "encoding/json" "fmt" + "path" "strconv" "golift.io/starr" ) +const bpQualityProfile = APIver + "/qualityProfile" + // QualityProfile is the /api/v1/qualityprofile endpoint. type QualityProfile struct { ID int64 `json:"id"` @@ -28,9 +31,9 @@ func (l *Lidarr) GetQualityProfiles() ([]*QualityProfile, error) { func (l *Lidarr) GetQualityProfilesContext(ctx context.Context) ([]*QualityProfile, error) { var profiles []*QualityProfile - _, err := l.GetInto(ctx, "v1/qualityprofile", nil, &profiles) + _, err := l.GetInto(ctx, bpQualityProfile, nil, &profiles) if err != nil { - return nil, fmt.Errorf("api.Get(qualityprofile): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } return profiles, nil @@ -45,12 +48,12 @@ func (l *Lidarr) AddQualityProfile(profile *QualityProfile) (int64, error) { func (l *Lidarr) AddQualityProfileContext(ctx context.Context, profile *QualityProfile) (int64, error) { var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return 0, fmt.Errorf("json.Marshal(qualityProfile): %w", err) + return 0, fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } var output QualityProfile - if _, err := l.PostInto(ctx, "v1/qualityProfile", nil, &body, &output); err != nil { - return 0, fmt.Errorf("api.Post(qualityProfile): %w", err) + if _, err := l.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { + return 0, fmt.Errorf("api.Post(%s): %w", bpQualityProfile, err) } return output.ID, nil @@ -65,12 +68,27 @@ func (l *Lidarr) UpdateQualityProfile(profile *QualityProfile) error { func (l *Lidarr) UpdateQualityProfileContext(ctx context.Context, profile *QualityProfile) error { var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return fmt.Errorf("json.Marshal(qualityProfile): %w", err) + return fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } - _, err := l.Put(ctx, "v1/qualityProfile/"+strconv.FormatInt(profile.ID, starr.Base10), nil, &body) - if err != nil { - return fmt.Errorf("api.Put(qualityProfile): %w", err) + uri := path.Join(bpQualityProfile, strconv.FormatInt(profile.ID, starr.Base10)) + if _, err := l.Put(ctx, uri, nil, &body); err != nil { + return fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) + } + + return nil +} + +// DeleteQualityProfile deletes a quality profile. +func (l *Lidarr) DeleteQualityProfile(profileID int64) error { + return l.DeleteQualityProfileContext(context.Background(), profileID) +} + +// DeleteQualityProfileContext deletes a quality profile. +func (l *Lidarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { + uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) + if _, err := l.Delete(ctx, uri, nil); err != nil { + return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } return nil diff --git a/radarr/customformat.go b/radarr/customformat.go index c523b88..2c2f27b 100644 --- a/radarr/customformat.go +++ b/radarr/customformat.go @@ -5,9 +5,12 @@ import ( "context" "encoding/json" "fmt" + "path" "strconv" ) +const bpCustomFormat = APIver + "/customFormat" + // CustomFormat is the api/customformat endpoint payload. type CustomFormat struct { ID int `json:"id"` @@ -45,8 +48,8 @@ func (r *Radarr) GetCustomFormats() ([]*CustomFormat, error) { // GetCustomFormatsContext returns all configured Custom Formats. func (r *Radarr) GetCustomFormatsContext(ctx context.Context) ([]*CustomFormat, error) { var output []*CustomFormat - if _, err := r.GetInto(ctx, "v3/customFormat", nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(customFormat): %w", err) + if _, err := r.GetInto(ctx, bpCustomFormat, nil, &output); err != nil { + return nil, fmt.Errorf("api.Get(%s): %w", bpCustomFormat, err) } return output, nil @@ -69,11 +72,11 @@ func (r *Radarr) AddCustomFormatContext(ctx context.Context, format *CustomForma var body bytes.Buffer if err := json.NewEncoder(&body).Encode(format); err != nil { - return nil, fmt.Errorf("json.Marshal(customFormat): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpCustomFormat, err) } - if _, err := r.PostInto(ctx, "v3/customFormat", nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Post(customFormat): %w", err) + if _, err := r.PostInto(ctx, bpCustomFormat, nil, &body, &output); err != nil { + return nil, fmt.Errorf("api.Post(%s): %w", bpCustomFormat, err) } return &output, nil @@ -92,13 +95,30 @@ func (r *Radarr) UpdateCustomFormatContext(ctx context.Context, format *CustomFo var body bytes.Buffer if err := json.NewEncoder(&body).Encode(format); err != nil { - return nil, fmt.Errorf("json.Marshal(customFormat): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpCustomFormat, err) } var output CustomFormat - if _, err := r.PutInto(ctx, "v3/customFormat/"+strconv.Itoa(cfID), nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Put(customFormat): %w", err) + + uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) + if _, err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { + return nil, fmt.Errorf("api.Put(%s): %w", bpCustomFormat, err) } return &output, nil } + +// DeleteCustomFormat deletes a custom format. +func (r *Radarr) DeleteCustomFormat(cfID int) error { + return r.DeleteCustomFormatContext(context.Background(), cfID) +} + +// DeleteCustomFormatContext deletes a custom format. +func (r *Radarr) DeleteCustomFormatContext(ctx context.Context, cfID int) error { + uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) + if _, err := r.Delete(ctx, uri, nil); err != nil { + return fmt.Errorf("api.Delete(%s): %w", bpCustomFormat, err) + } + + return nil +} diff --git a/radarr/qualityprofile.go b/radarr/qualityprofile.go index 15780ae..10885a5 100644 --- a/radarr/qualityprofile.go +++ b/radarr/qualityprofile.go @@ -5,11 +5,14 @@ import ( "context" "encoding/json" "fmt" + "path" "strconv" "golift.io/starr" ) +const bpQualityProfile = APIver + "/qualityProfile" + // QualityProfile is applied to Movies. type QualityProfile struct { ID int64 `json:"id"` @@ -39,9 +42,9 @@ func (r *Radarr) GetQualityProfiles() ([]*QualityProfile, error) { func (r *Radarr) GetQualityProfilesContext(ctx context.Context) ([]*QualityProfile, error) { var profiles []*QualityProfile - _, err := r.GetInto(ctx, "v3/qualityProfile", nil, &profiles) + _, err := r.GetInto(ctx, bpQualityProfile, nil, &profiles) if err != nil { - return nil, fmt.Errorf("api.Get(qualityProfile): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } return profiles, nil @@ -56,12 +59,12 @@ func (r *Radarr) AddQualityProfile(profile *QualityProfile) (int64, error) { func (r *Radarr) AddQualityProfileContext(ctx context.Context, profile *QualityProfile) (int64, error) { var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return 0, fmt.Errorf("json.Marshal(qualityProfile): %w", err) + return 0, fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } var output QualityProfile - if _, err := r.PostInto(ctx, "v3/qualityProfile", nil, &body, &output); err != nil { - return 0, fmt.Errorf("api.Post(qualityProfile): %w", err) + if _, err := r.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { + return 0, fmt.Errorf("api.Post(%s): %w", bpQualityProfile, err) } return output.ID, nil @@ -76,12 +79,27 @@ func (r *Radarr) UpdateQualityProfile(profile *QualityProfile) error { func (r *Radarr) UpdateQualityProfileContext(ctx context.Context, profile *QualityProfile) error { var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return fmt.Errorf("json.Marshal(qualityProfile): %w", err) + return fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } - _, err := r.Put(ctx, "v3/qualityProfile/"+strconv.FormatInt(profile.ID, starr.Base10), nil, &body) - if err != nil { - return fmt.Errorf("api.Put(qualityProfile): %w", err) + uri := path.Join(bpQualityProfile, strconv.FormatInt(profile.ID, starr.Base10)) + if _, err := r.Put(ctx, uri, nil, &body); err != nil { + return fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) + } + + return nil +} + +// DeleteQualityProfile deletes a quality profile. +func (r *Radarr) DeleteQualityProfile(profileID int64) error { + return r.DeleteQualityProfileContext(context.Background(), profileID) +} + +// DeleteQualityProfileContext deletes a quality profile. +func (r *Radarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { + uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) + if _, err := r.Delete(ctx, uri, nil); err != nil { + return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } return nil diff --git a/readarr/qualityprofile.go b/readarr/qualityprofile.go index 45a6492..4cbd5d8 100644 --- a/readarr/qualityprofile.go +++ b/readarr/qualityprofile.go @@ -5,11 +5,14 @@ import ( "context" "encoding/json" "fmt" + "path" "strconv" "golift.io/starr" ) +const bpQualityProfile = APIver + "/qualityProfile" + // QualityProfile is the /api/v1/qualityprofile endpoint. type QualityProfile struct { Name string `json:"name"` @@ -27,9 +30,9 @@ func (r *Readarr) GetQualityProfiles() ([]*QualityProfile, error) { func (r *Readarr) GetQualityProfilesContext(ctx context.Context) ([]*QualityProfile, error) { var profiles []*QualityProfile - _, err := r.GetInto(ctx, "v1/qualityprofile", nil, &profiles) + _, err := r.GetInto(ctx, bpQualityProfile, nil, &profiles) if err != nil { - return nil, fmt.Errorf("api.Get(qualityprofile): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } return profiles, nil @@ -43,12 +46,12 @@ func (r *Readarr) AddQualityProfile(profile *QualityProfile) (int64, error) { func (r *Readarr) AddQualityProfileContext(ctx context.Context, profile *QualityProfile) (int64, error) { var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return 0, fmt.Errorf("json.Marshal(qualityProfile): %w", err) + return 0, fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } var output QualityProfile - if _, err := r.PostInto(ctx, "v1/qualityProfile", nil, &body, &output); err != nil { - return 0, fmt.Errorf("api.Post(qualityProfile): %w", err) + if _, err := r.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { + return 0, fmt.Errorf("api.Post(%s): %w", bpQualityProfile, err) } return output.ID, nil @@ -62,12 +65,27 @@ func (r *Readarr) UpdateQualityProfile(profile *QualityProfile) error { func (r *Readarr) UpdateQualityProfileContext(ctx context.Context, profile *QualityProfile) error { var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return fmt.Errorf("json.Marshal(qualityProfile): %w", err) + return fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } - _, err := r.Put(ctx, "v1/qualityProfile/"+strconv.FormatInt(profile.ID, starr.Base10), nil, &body) - if err != nil { - return fmt.Errorf("api.Put(qualityProfile): %w", err) + uri := path.Join(bpQualityProfile, strconv.FormatInt(profil.ID, starr.Base10)) + if _, err := r.Put(ctx, uri, nil, &body); err != nil { + return fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) + } + + return nil +} + +// DeleteQualityProfile deletes a quality profile. +func (r *Readarr) DeleteQualityProfile(profileID int64) error { + return r.DeleteQualityProfileContext(context.Background(), profileID) +} + +// DeleteQualityProfileContext deletes a quality profile. +func (r *Readarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { + uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) + if _, err := r.Delete(ctx, uri, nil); err != nil { + return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } return nil diff --git a/sonarr/qualityprofile.go b/sonarr/qualityprofile.go index 05bafcc..6f856de 100644 --- a/sonarr/qualityprofile.go +++ b/sonarr/qualityprofile.go @@ -32,7 +32,7 @@ func (s *Sonarr) GetQualityProfilesContext(ctx context.Context) ([]*QualityProfi var output []*QualityProfile if _, err := s.GetInto(ctx, bpQualityProfile, nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(qualityProfile): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } return output, nil @@ -48,7 +48,7 @@ func (s *Sonarr) GetQualityProfileContext(ctx context.Context, profileID int) (* uri := path.Join(bpQualityProfile, strconv.Itoa(profileID)) if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(qualityProfile): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } return output, nil @@ -64,11 +64,11 @@ func (s *Sonarr) AddQualityProfileContext(ctx context.Context, profile *QualityP var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return nil, fmt.Errorf("json.Marshal(qualityProfile): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } if _, err := s.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Post(qualityProfile): %w", err) + return nil, fmt.Errorf("api.Post(%s): %w", bpQualityProfile, err) } return &output, nil @@ -84,12 +84,12 @@ func (s *Sonarr) UpdateQualityProfileContext(ctx context.Context, profile *Quali var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return nil, fmt.Errorf("json.Marshal(qualityProfile): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } uri := path.Join(bpQualityProfile, strconv.Itoa(int(profile.ID))) if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Put(qualityProfile): %w", err) + return nil, fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) } return &output, nil @@ -103,7 +103,7 @@ func (s *Sonarr) DeleteQualityProfile(profileID int) error { func (s *Sonarr) DeleteQualityProfileContext(ctx context.Context, profileID int) error { uri := path.Join(bpQualityProfile, strconv.Itoa(profileID)) if _, err := s.Delete(ctx, uri, nil); err != nil { - return fmt.Errorf("api.Delete(qualityProfile): %w", err) + return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } return nil diff --git a/sonarr/releaseprofile.go b/sonarr/releaseprofile.go index 0034531..65c47c0 100644 --- a/sonarr/releaseprofile.go +++ b/sonarr/releaseprofile.go @@ -36,7 +36,7 @@ func (s *Sonarr) GetReleaseProfilesContext(ctx context.Context) ([]*ReleaseProfi var output []*ReleaseProfile if _, err := s.GetInto(ctx, bpReleaseProfile, nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(releaseProfile): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpReleaseProfile, err) } return output, nil @@ -52,7 +52,7 @@ func (s *Sonarr) GetReleaseProfileContext(ctx context.Context, profileID int) (* uri := path.Join(bpReleaseProfile, strconv.Itoa(profileID)) if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(releaseProfile): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpReleaseProfile, err) } return output, nil @@ -68,11 +68,11 @@ func (s *Sonarr) AddReleaseProfileContext(ctx context.Context, profile *ReleaseP var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return nil, fmt.Errorf("json.Marshal(releaseProfile): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpReleaseProfile, err) } if _, err := s.PostInto(ctx, bpReleaseProfile, nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Post(releaseProfile): %w", err) + return nil, fmt.Errorf("api.Post(%s): %w", bpReleaseProfile, err) } return &output, nil @@ -88,12 +88,12 @@ func (s *Sonarr) UpdateReleaseProfileContext(ctx context.Context, profile *Relea var body bytes.Buffer if err := json.NewEncoder(&body).Encode(profile); err != nil { - return nil, fmt.Errorf("json.Marshal(releaseProfile): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpReleaseProfile, err) } uri := path.Join(bpReleaseProfile, strconv.Itoa(int(profile.ID))) if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Put(releaseProfile): %w", err) + return nil, fmt.Errorf("api.Put(%s): %w", bpReleaseProfile, err) } return &output, nil @@ -107,7 +107,7 @@ func (s *Sonarr) DeleteReleaseProfile(profileID int) error { func (s *Sonarr) DeleteReleaseProfileContext(ctx context.Context, profileID int) error { uri := path.Join(bpReleaseProfile, strconv.Itoa(profileID)) if _, err := s.Delete(ctx, uri, nil); err != nil { - return fmt.Errorf("api.Delete(releaseProfile): %w", err) + return fmt.Errorf("api.Delete(%s): %w", bpReleaseProfile, err) } return nil From 4ee7ad42703cc66e84d1f38d6bc8c6d72123ae90 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sat, 2 Jul 2022 23:45:29 -0700 Subject: [PATCH 02/26] fix lint issues --- .golangci.yml | 1 + http.go | 27 +++++++++++++++++++++------ interface.go | 34 ++++++++++++++++++++++++++-------- readarr/qualityprofile.go | 2 +- sonarr/episodefile.go | 7 +++++-- sonarr/qualitydefinition.go | 6 ++++-- starr.go | 9 +++++++-- starrcmd/lidarr.go | 1 - 8 files changed, 65 insertions(+), 22 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 776f667..f109cbd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,6 +7,7 @@ issues: - linters: - forcetypeassert - funlen + - maintidx # uhg. path: '(.+)_test\.go' linters: diff --git a/http.go b/http.go index 3b435b4..153a2fa 100644 --- a/http.go +++ b/http.go @@ -19,8 +19,13 @@ const API = "api" /* The methods in this file provide assumption-ridden HTTP calls for Starr apps. */ // Req makes an http request and returns the body in []byte form (already read). -func (c *Config) Req(ctx context.Context, path, method string, params url.Values, - body io.Reader) (int, []byte, http.Header, error) { +func (c *Config) Req( + ctx context.Context, + path string, + method string, + params url.Values, + body io.Reader, +) (int, []byte, http.Header, error) { req, err := c.newReq(ctx, c.SetPath(path), method, params, body) if err != nil { return 0, nil, nil, err @@ -46,8 +51,13 @@ func (c *Config) Req(ctx context.Context, path, method string, params url.Values } // body returns the body in io.ReadCloser form (read and close it yourself). -func (c *Config) body(ctx context.Context, uri, method string, params url.Values, - body io.Reader) (int, io.ReadCloser, http.Header, error) { +func (c *Config) body( + ctx context.Context, + uri string, + method string, + params url.Values, + body io.Reader, +) (int, io.ReadCloser, http.Header, error) { req, err := c.newReq(ctx, c.URL+uri, method, params, body) if err != nil { return 0, nil, nil, err @@ -61,8 +71,13 @@ func (c *Config) body(ctx context.Context, uri, method string, params url.Values return resp.StatusCode, resp.Body, resp.Header, nil } -func (c *Config) newReq(ctx context.Context, path, method string, - params url.Values, body io.Reader) (*http.Request, error) { +func (c *Config) newReq( + ctx context.Context, + path string, + method string, + params url.Values, + body io.Reader, +) (*http.Request, error) { if c.Client == nil { // we must have an http client. return nil, ErrNilClient } diff --git a/interface.go b/interface.go index a43608e..b06bb12 100644 --- a/interface.go +++ b/interface.go @@ -171,8 +171,13 @@ func (c *Config) GetInto(ctx context.Context, path string, params url.Values, ou // PostInto performs an HTTP POST against an API path and // unmarshals the payload into the provided pointer interface. -func (c *Config) PostInto(ctx context.Context, - path string, params url.Values, postBody io.Reader, output interface{}) (int64, error) { +func (c *Config) PostInto( + ctx context.Context, + path string, + params url.Values, + postBody io.Reader, + output interface{}, +) (int64, error) { if c.Debugf == nil { // no log, pass it through. _, data, _, err := c.body(ctx, path, http.MethodPost, params, postBody) @@ -186,8 +191,13 @@ func (c *Config) PostInto(ctx context.Context, // PutInto performs an HTTP PUT against an API path and // unmarshals the payload into the provided pointer interface. -func (c *Config) PutInto(ctx context.Context, - path string, params url.Values, putBody io.Reader, output interface{}) (int64, error) { +func (c *Config) PutInto( + ctx context.Context, + path string, + params url.Values, + putBody io.Reader, + output interface{}, +) (int64, error) { if c.Debugf == nil { // no log, pass it through. _, data, _, err := c.body(ctx, path, http.MethodPut, params, putBody) @@ -232,8 +242,12 @@ func (c *Config) GetBody(ctx context.Context, path string, params url.Values) (i // Always remember to close the io.ReadCloser. // Before you use the returned data, check the HTTP status code. // If it's not 200, it's possible the request had an error or was not authenticated. -func (c *Config) PostBody(ctx context.Context, - path string, params url.Values, postBody io.Reader) (io.ReadCloser, int, error) { +func (c *Config) PostBody( + ctx context.Context, + path string, + params url.Values, + postBody io.Reader, +) (io.ReadCloser, int, error) { code, data, header, err := c.body(ctx, path, http.MethodPost, params, postBody) if c.Debugf != nil { @@ -246,8 +260,12 @@ func (c *Config) PostBody(ctx context.Context, // PutBody makes a PUT http request and returns the resp.Body (io.ReadCloser). // Always remember to close the io.ReadCloser. // Before you use the returned data, check the HTTP status code. -func (c *Config) PutBody(ctx context.Context, - path string, params url.Values, putBody io.Reader) (io.ReadCloser, int, error) { +func (c *Config) PutBody( + ctx context.Context, + path string, + params url.Values, + putBody io.Reader, +) (io.ReadCloser, int, error) { code, data, header, err := c.body(ctx, path, http.MethodPut, params, putBody) if c.Debugf != nil { diff --git a/readarr/qualityprofile.go b/readarr/qualityprofile.go index 4cbd5d8..8b593e6 100644 --- a/readarr/qualityprofile.go +++ b/readarr/qualityprofile.go @@ -68,7 +68,7 @@ func (r *Readarr) UpdateQualityProfileContext(ctx context.Context, profile *Qual return fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } - uri := path.Join(bpQualityProfile, strconv.FormatInt(profil.ID, starr.Base10)) + uri := path.Join(bpQualityProfile, strconv.FormatInt(profile.ID, starr.Base10)) if _, err := r.Put(ctx, uri, nil, &body); err != nil { return fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) } diff --git a/sonarr/episodefile.go b/sonarr/episodefile.go index 67d293b..c3c7970 100644 --- a/sonarr/episodefile.go +++ b/sonarr/episodefile.go @@ -64,8 +64,11 @@ func (s *Sonarr) UpdateEpisodeFileQuality(episodeFileID, qualityID int64) (*Epis } // UpdateEpisodeFileQualityContext updates an episode file, and takes a context. -func (s *Sonarr) UpdateEpisodeFileQualityContext(ctx context.Context, - episodeFileID, qualityID int64) (*EpisodeFile, error) { +func (s *Sonarr) UpdateEpisodeFileQualityContext( + ctx context.Context, + episodeFileID int64, + qualityID int64, +) (*EpisodeFile, error) { var body bytes.Buffer if err := json.NewEncoder(&body).Encode(&EpisodeFile{ ID: episodeFileID, diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index 66d96de..3e3efe4 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -60,8 +60,10 @@ func (s *Sonarr) UpdateQualityDefinition(definition *QualityDefinition) (*Qualit return s.UpdateQualityDefinitionContext(context.Background(), definition) } -func (s *Sonarr) UpdateQualityDefinitionContext(ctx context.Context, - definition *QualityDefinition) (*QualityDefinition, error) { +func (s *Sonarr) UpdateQualityDefinitionContext( + ctx context.Context, + definition *QualityDefinition, +) (*QualityDefinition, error) { var output QualityDefinition var body bytes.Buffer diff --git a/starr.go b/starr.go index 0dd6aaf..ad51c4a 100644 --- a/starr.go +++ b/starr.go @@ -97,10 +97,15 @@ func New(apiKey, appURL string, timeout time.Duration) *Config { } // UnmarshalText parses a duration type from a config file. -func (d *Duration) UnmarshalText(data []byte) (err error) { +func (d *Duration) UnmarshalText(data []byte) error { + var err error + d.Duration, err = time.ParseDuration(string(data)) + if err != nil { + return fmt.Errorf("invalid duration: %w", err) + } - return + return nil } // String returns a Duration as string without trailing zero units. diff --git a/starrcmd/lidarr.go b/starrcmd/lidarr.go index 517f6bf..02f0ad1 100644 --- a/starrcmd/lidarr.go +++ b/starrcmd/lidarr.go @@ -61,7 +61,6 @@ type LidarrAlbumDownload struct { DownloadID string `env:"lidarr_download_id"` // message.DownloadId ?? string.Empty) AddedTrackPaths []string `env:"lidarr_addedtrackpaths,|"` // string.Join("|", message.TrackFiles.Select(e => e.Path))) DeletedPaths []string `env:"lidarr_deletedpaths,|"` // string.Join("|", message.OldFiles.Select(e => e.Path))) - } // LidarrRename is the Rename event. From ddf8c650b02ac2306cbdd56f3cfe8a7e4ff3c2b9 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 02:36:05 -0700 Subject: [PATCH 03/26] Change interface, change debug logging --- .golangci.yml | 1 + debuglog/roundtripper.go | 130 +++++++++++++++++ http.go | 64 ++------- http_test.go | 36 ----- interface.go | 268 ++++++------------------------------ lidarr/album.go | 10 +- lidarr/artist.go | 8 +- lidarr/command.go | 4 +- lidarr/history.go | 8 +- lidarr/lidarr.go | 21 +-- lidarr/metadataprofile.go | 2 +- lidarr/qualitydefinition.go | 2 +- lidarr/qualityprofile.go | 12 +- lidarr/queue.go | 2 +- lidarr/rootfolder.go | 2 +- lidarr/system.go | 4 +- lidarr/tag.go | 12 +- prowlarr/prowlarr.go | 18 +-- prowlarr/system.go | 4 +- prowlarr/tag.go | 12 +- radarr/command.go | 4 +- radarr/customformat.go | 10 +- radarr/exclusions.go | 14 +- radarr/history.go | 8 +- radarr/importlist.go | 12 +- radarr/movie.go | 14 +- radarr/qualityprofile.go | 12 +- radarr/queue.go | 2 +- radarr/radarr.go | 21 +-- radarr/rootfolder.go | 2 +- radarr/system.go | 4 +- radarr/tag.go | 12 +- readarr/author.go | 11 +- readarr/book.go | 15 +- readarr/command.go | 4 +- readarr/history.go | 11 +- readarr/metadataprofile.go | 2 +- readarr/qualityprofile.go | 12 +- readarr/queue.go | 2 +- readarr/readarr.go | 21 +-- readarr/rootfolder.go | 2 +- readarr/system.go | 4 +- readarr/tag.go | 12 +- shared.go | 15 ++ sonarr/command.go | 6 +- sonarr/delayprofile.go | 12 +- sonarr/episode.go | 4 +- sonarr/episodefile.go | 12 +- sonarr/history.go | 7 +- sonarr/indexer.go | 12 +- sonarr/indexerconfig.go | 4 +- sonarr/languageprofile.go | 12 +- sonarr/mediamanagement.go | 4 +- sonarr/naming.go | 4 +- sonarr/qualitydefinition.go | 6 +- sonarr/qualityprofile.go | 12 +- sonarr/releaseprofile.go | 12 +- sonarr/rootfolder.go | 10 +- sonarr/seasonpass.go | 5 +- sonarr/series.go | 14 +- sonarr/sonarr.go | 22 +-- sonarr/system.go | 4 +- sonarr/tag.go | 12 +- starr.go | 72 ++-------- test_methods.go | 4 +- 65 files changed, 456 insertions(+), 640 deletions(-) create mode 100644 debuglog/roundtripper.go delete mode 100644 http_test.go diff --git a/.golangci.yml b/.golangci.yml index f109cbd..851b38f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,6 +21,7 @@ linters: - exhaustivestruct - exhaustruct - dupl + - nlreturn run: timeout: 35m \ No newline at end of file diff --git a/debuglog/roundtripper.go b/debuglog/roundtripper.go new file mode 100644 index 0000000..4515d30 --- /dev/null +++ b/debuglog/roundtripper.go @@ -0,0 +1,130 @@ +// Package debuglog provides a RoundTripper you can put into +// an HTTP client Transport to log requests made with that client. +package debuglog + +import ( + "bytes" + "io" + "log" + "net/http" +) + +// LogConfig is the input data for the logger. +type LogConfig struct { + MaxBody int // Limit payloads to this many bytes. 0=unlimited + Debugf func(string, ...interface{}) // This is where logs go. + Caller func(sentBytes, rcvdBytes int) // This can be used for byte counters. +} + +// LoggingRoundTripper allows us to use a datacounter to log http request data. +type LoggingRoundTripper struct { + next http.RoundTripper // The next Transport to call after logging. + config *LogConfig +} + +type fakeCloser struct { + io.Reader + CloseFn func() error + Body *bytes.Buffer + Sent *bytes.Buffer + Method string + URL string + Status string + Header http.Header + *LogConfig +} + +// NewLoggingRoundTripper returns a round tripper to log requests counts and response sizes. +func NewLoggingRoundTripper(config LogConfig, next http.RoundTripper) *LoggingRoundTripper { + if next == nil { + next = http.DefaultTransport + } + + if config.Debugf == nil { + config.Debugf = log.Printf + } + + return &LoggingRoundTripper{ + next: next, + config: &config, + } +} + +// RoundTrip satisfies the http.RoundTripper interface. +func (rt *LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + defer req.Body.Close() + + buf := bytes.Buffer{} + if req.Body != nil { + sent := io.TeeReader(req.Body, &buf) + req.Body = io.NopCloser(sent) + } + + resp, err := rt.next.RoundTrip(req) + if resp == nil || resp.Body == nil { + return resp, err //nolint:wrapcheck + } + + resp.Body = rt.newFakeCloser(resp, &buf) + + return resp, err //nolint:wrapcheck +} + +func (rt *LoggingRoundTripper) newFakeCloser(resp *http.Response, sent *bytes.Buffer) io.ReadCloser { + var buf bytes.Buffer + + return &fakeCloser{ + CloseFn: resp.Body.Close, + Reader: io.TeeReader(resp.Body, &buf), + Body: &buf, + Method: resp.Request.Method, + Status: resp.Status, + URL: resp.Request.URL.String(), + Sent: sent, + Header: resp.Header, + LogConfig: rt.config, + } +} + +func (f *fakeCloser) Close() error { + sentBytes, rcvdBytes := f.logRequest() + if f.Caller != nil { + f.Caller(sentBytes, rcvdBytes) + } + + return f.CloseFn() +} + +func (f *fakeCloser) logRequest() (int, int) { + var ( + headers = "" + rcvd = "" + sent = "" + rcvdBytes = f.Body.Len() + sentBytes = f.Sent.Len() + ) + + for header, value := range f.Header { + for _, v := range value { + headers += header + ": " + v + "\n" + } + } + + if f.MaxBody > 0 && rcvdBytes > f.MaxBody { + rcvd = string(f.Body.Bytes()[:f.MaxBody]) + " " + } + + if f.MaxBody > 0 && sentBytes > f.MaxBody { + sent = string(f.Sent.Bytes()[:f.MaxBody]) + " " + } + + if sentBytes > 0 { + f.Debugf("Sent (%s) %d bytes to %s: %s\n Response: %s %d bytes\n%s%s)", + f.Method, sentBytes, f.URL, sent, f.Status, rcvdBytes, headers, rcvd) + } else { + f.Debugf("Sent (%s) to %s, Response: %s %d bytes\n%s%s", + f.Method, f.URL, f.Status, rcvdBytes, headers, rcvd) + } + + return sentBytes, rcvdBytes +} diff --git a/http.go b/http.go index 153a2fa..9b23324 100644 --- a/http.go +++ b/http.go @@ -5,10 +5,8 @@ import ( "encoding/base64" "fmt" "io" - "io/ioutil" "net/http" "net/url" - "path" "reflect" "strings" ) @@ -18,57 +16,25 @@ const API = "api" /* The methods in this file provide assumption-ridden HTTP calls for Starr apps. */ -// Req makes an http request and returns the body in []byte form (already read). -func (c *Config) Req( - ctx context.Context, - path string, - method string, - params url.Values, - body io.Reader, -) (int, []byte, http.Header, error) { - req, err := c.newReq(ctx, c.SetPath(path), method, params, body) - if err != nil { - return 0, nil, nil, err - } - - resp, err := c.Client.Do(req) - if err != nil { - return 0, nil, nil, fmt.Errorf("httpClient.Do(req): %w", err) - } - defer resp.Body.Close() - - respBody, err := ioutil.ReadAll(resp.Body) - if err != nil { - return resp.StatusCode, nil, resp.Header, fmt.Errorf("ioutil.ReadAll: %w", err) - } - - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return resp.StatusCode, respBody, resp.Header, fmt.Errorf("failed: %v (status: %s): %w: %s", - resp.Request.RequestURI, resp.Status, ErrInvalidStatusCode, string(respBody)) - } - - return resp.StatusCode, respBody, resp.Header, nil -} - -// body returns the body in io.ReadCloser form (read and close it yourself). -func (c *Config) body( +// req returns the body in io.ReadCloser form (read and close it yourself). +func (c *Config) req( ctx context.Context, uri string, method string, params url.Values, body io.Reader, -) (int, io.ReadCloser, http.Header, error) { +) (*http.Response, error) { req, err := c.newReq(ctx, c.URL+uri, method, params, body) if err != nil { - return 0, nil, nil, err + return nil, err } resp, err := c.Client.Do(req) if err != nil { - return 0, nil, nil, fmt.Errorf("httpClient.Do(req): %w", err) + return nil, fmt.Errorf("httpClient.Do(req): %w", err) } - return resp.StatusCode, resp.Body, resp.Header, nil + return resp, nil } func (c *Config) newReq( @@ -87,7 +53,7 @@ func (c *Config) newReq( return nil, fmt.Errorf("http.NewRequestWithContext(path): %w", err) } - c.SetHeaders(req) + c.setHeaders(req) if params != nil { req.URL.RawQuery = params.Encode() @@ -96,8 +62,8 @@ func (c *Config) newReq( return req, nil } -// SetHeaders sets all our request headers based on method and other data. -func (c *Config) SetHeaders(req *http.Request) { +// setHeaders sets all our request headers based on method and other data. +func (c *Config) setHeaders(req *http.Request) { // This app allows http auth, in addition to api key (nginx proxy). if auth := c.HTTPUser + ":" + c.HTTPPass; auth != ":" { req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) @@ -116,15 +82,3 @@ 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) } - -// SetPath makes sure the path starts with /api and returns the full URL. -func (c *Config) SetPath(uriPath string) string { - if strings.HasPrefix(uriPath, API+"/") || - strings.HasPrefix(uriPath, path.Join("/", API)+"/") { - uriPath = path.Join("/", uriPath) - } else { - uriPath = path.Join("/", API, uriPath) - } - - return strings.TrimSuffix(c.URL, "/") + uriPath -} diff --git a/http_test.go b/http_test.go deleted file mode 100644 index 1b63d98..0000000 --- a/http_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package starr_test - -import ( - "path" - "testing" - - "github.com/stretchr/testify/assert" - "golift.io/starr" -) - -func TestSetPath(t *testing.T) { - t.Parallel() - - api := path.Join("/", starr.API) - cnfg := starr.Config{URL: "http://short.zz"} - - // These must all return the same value... - assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("v1/test")) // no slashes. - assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("v1/test/")) // trailing slash. - assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("/v1/test")) // leading slash. - assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("/v1/test/")) // both slashes. - assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("api/v1/test")) // ...and repeat. - assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("api/v1/test/")) - assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("/api/v1/test")) - assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("/api/v1/test/")) - - // These must all return the same value... - assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("v1/test/another/level")) - assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("v1/test/another/level/")) - assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("/v1/test/another/level")) - assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("/v1/test/another/level/")) - assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("api/v1/test/another/level")) - assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("api/v1/test/another/level/")) - assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("/api/v1/test/another/level")) - assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("/api/v1/test/another/level/")) -} diff --git a/interface.go b/interface.go index b06bb12..6499f90 100644 --- a/interface.go +++ b/interface.go @@ -12,62 +12,27 @@ import ( "strings" "golang.org/x/net/publicsuffix" - "golift.io/datacounter" ) // APIer is used by the sub packages to allow mocking the http methods in tests. // It changes once in a while, so avoid making hard dependencies on it. type APIer interface { Login(ctx context.Context) error - // Normal data, returns response body. - Get(ctx context.Context, path string, params url.Values) (respBody []byte, err error) - Post(ctx context.Context, path string, params url.Values, postBody io.Reader) (respBody []byte, err error) - Put(ctx context.Context, path string, params url.Values, putBody io.Reader) (respBody []byte, err error) - Delete(ctx context.Context, path string, params url.Values) (respBody []byte, err error) - // Normal data, unmarshals into provided interface. - GetInto(ctx context.Context, path string, params url.Values, output interface{}) (int64, error) - PostInto(ctx context.Context, path string, params url.Values, postBody io.Reader, output interface{}) (int64, error) - PutInto(ctx context.Context, path string, params url.Values, putBody io.Reader, output interface{}) (int64, error) - DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) (int64, error) - // Body methods. - GetBody(ctx context.Context, path string, params url.Values) (respBody io.ReadCloser, status int, err error) - PostBody(ctx context.Context, path string, params url.Values, - postBody io.Reader) (respBody io.ReadCloser, status int, err error) - PutBody(ctx context.Context, path string, params url.Values, - putBody io.Reader) (respBody io.ReadCloser, status int, err error) - DeleteBody(ctx context.Context, path string, params url.Values) (respBody io.ReadCloser, status int, err error) + // Normal data, returns response. Do not use these in starr app methods. + Get(ctx context.Context, path string, params url.Values) (*http.Response, error) + Post(ctx context.Context, path string, params url.Values, postBody io.Reader) (*http.Response, error) + Put(ctx context.Context, path string, params url.Values, putBody io.Reader) (*http.Response, error) + Delete(ctx context.Context, path string, params url.Values) (*http.Response, error) + // Normal data, unmarshals into provided interface. Use these because they close the response body. + GetInto(ctx context.Context, path string, params url.Values, output interface{}) error + PostInto(ctx context.Context, path string, params url.Values, postBody io.Reader, output interface{}) error + PutInto(ctx context.Context, path string, params url.Values, putBody io.Reader, output interface{}) error + DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) error } // Config must satify the APIer struct. var _ APIer = (*Config)(nil) -// log the request. Do not call this if c.Debugf is nil. -func (c *Config) log(code int, data, body string, header http.Header, path, method string, err error) { - headers := "" - - for header, value := range header { - for _, v := range value { - headers += header + ": " + v + "\n" - } - } - - if c.MaxBody > 0 && len(body) > c.MaxBody { - body = body[:c.MaxBody] + " " - } - - if c.MaxBody > 0 && len(data) > c.MaxBody { - data = data[:c.MaxBody] + " " - } - - if len(body) > 0 { - c.Debugf("Sent (%s) %d bytes to %s: %s\n Response: %s\n%s%s (err: %v)", - method, len(body), path, body, http.StatusText(code), headers, data, err) - } else { - c.Debugf("Sent (%s) to %s, Response: %s\n%s%s (err: %v)", - method, path, http.StatusText(code), headers, data, err) - } -} - // LoginC POSTs to the login form in a Starr app and saves the authentication cookie for future use. func (c *Config) Login(ctx context.Context) error { if c.Client.Jar == nil { @@ -81,17 +46,15 @@ func (c *Config) Login(ctx context.Context) error { post := "username=" + c.Username + "&password=" + c.Password - code, resp, header, err := c.body(ctx, "/login", http.MethodPost, nil, bytes.NewBufferString(post)) - c.log(code, "", post, header, c.URL+"/login", http.MethodPost, err) - + resp, err := c.req(ctx, "/login", http.MethodPost, nil, bytes.NewBufferString(post)) if err != nil { return fmt.Errorf("authenticating as user '%s' failed: %w", c.Username, err) } - defer resp.Close() + defer resp.Body.Close() - _, _ = io.Copy(io.Discard, resp) + _, _ = io.Copy(io.Discard, resp.Body) - if u, _ := url.Parse(c.URL); strings.Contains(header.Get("location"), "loginFailed") || + if u, _ := url.Parse(c.URL); strings.Contains(resp.Header.Get("location"), "loginFailed") || len(c.Client.Jar.Cookies(u)) == 0 { return fmt.Errorf("%w: authenticating as user '%s' failed", ErrRequestError, c.Username) } @@ -102,71 +65,34 @@ func (c *Config) Login(ctx context.Context) error { } // Get makes a GET http request and returns the body. -func (c *Config) Get(ctx context.Context, path string, params url.Values) ([]byte, error) { - code, data, header, err := c.Req(ctx, path, http.MethodGet, params, nil) - - if c.Debugf != nil { // log the request. - c.log(code, string(data), "", header, c.SetPath(path), http.MethodGet, err) - } - - return data, err +func (c *Config) Get(ctx context.Context, path string, params url.Values) (*http.Response, error) { + resp, err := c.req(ctx, path, http.MethodGet, params, nil) + return resp, err } // Post makes a POST http request and returns the body. -func (c *Config) Post(ctx context.Context, path string, params url.Values, postBody io.Reader) ([]byte, error) { - if c.Debugf == nil { // no log, pass it through. - _, data, _, err := c.Req(ctx, path, http.MethodPost, params, postBody) - - return data, err - } - - var buf bytes.Buffer // split the reader for request and log. - tee := io.TeeReader(postBody, &buf) // must read tee first. - code, data, header, err := c.Req(ctx, path, http.MethodPost, params, tee) - c.log(code, string(data), buf.String(), header, c.SetPath(path), http.MethodPost, err) - - return data, err +func (c *Config) Post(ctx context.Context, path string, params url.Values, postBody io.Reader) (*http.Response, error) { + resp, err := c.req(ctx, path, http.MethodPost, params, postBody) + return resp, err } // Put makes a PUT http request and returns the body. -func (c *Config) Put(ctx context.Context, path string, params url.Values, putBody io.Reader) ([]byte, error) { - if c.Debugf == nil { // no log, pass it through. - _, data, _, err := c.Req(ctx, path, http.MethodPut, params, putBody) - - return data, err - } - - var buf bytes.Buffer // split the reader for request and log. - tee := io.TeeReader(putBody, &buf) // must read tee first. - code, data, header, err := c.Req(ctx, path, http.MethodPut, params, tee) - c.log(code, string(data), buf.String(), header, c.SetPath(path), http.MethodPut, err) - - return data, err +func (c *Config) Put(ctx context.Context, path string, params url.Values, putBody io.Reader) (*http.Response, error) { + resp, err := c.req(ctx, path, http.MethodPut, params, putBody) + return resp, err } // Delete makes a DELETE http request and returns the body. -func (c *Config) Delete(ctx context.Context, path string, params url.Values) ([]byte, error) { - code, data, header, err := c.Req(ctx, path, http.MethodDelete, params, nil) - - if c.Debugf != nil { - c.log(code, string(data), "", header, c.SetPath(path), http.MethodDelete, err) - } - - return data, err +func (c *Config) Delete(ctx context.Context, path string, params url.Values) (*http.Response, error) { + resp, err := c.req(ctx, path, http.MethodDelete, params, nil) + return resp, err } // GetInto performs an HTTP GET against an API path and // unmarshals the payload into the provided pointer interface. -func (c *Config) GetInto(ctx context.Context, path string, params url.Values, output interface{}) (int64, error) { - if c.Debugf == nil { // no log, pass it through. - _, data, _, err := c.body(ctx, path, http.MethodGet, params, nil) - - return unmarshalBody(output, data, err) - } - - data, err := c.Get(ctx, path, params) // log the request - - return unmarshal(output, data, err) +func (c *Config) GetInto(ctx context.Context, path string, params url.Values, output interface{}) error { + resp, err := c.req(ctx, path, http.MethodGet, params, nil) //nolint:bodyclose + return unmarshal(output, resp.Body, err) } // PostInto performs an HTTP POST against an API path and @@ -177,16 +103,9 @@ func (c *Config) PostInto( params url.Values, postBody io.Reader, output interface{}, -) (int64, error) { - if c.Debugf == nil { // no log, pass it through. - _, data, _, err := c.body(ctx, path, http.MethodPost, params, postBody) - - return unmarshalBody(output, data, err) - } - - data, err := c.Post(ctx, path, params, postBody) // log the request - - return unmarshal(output, data, err) +) error { + resp, err := c.req(ctx, path, http.MethodPost, params, postBody) //nolint:bodyclose + return unmarshal(output, resp.Body, err) } // PutInto performs an HTTP PUT against an API path and @@ -197,126 +116,29 @@ func (c *Config) PutInto( params url.Values, putBody io.Reader, output interface{}, -) (int64, error) { - if c.Debugf == nil { // no log, pass it through. - _, data, _, err := c.body(ctx, path, http.MethodPut, params, putBody) - - return unmarshalBody(output, data, err) - } - - data, err := c.Put(ctx, path, params, putBody) // log the request - - return unmarshal(output, data, err) +) error { + resp, err := c.req(ctx, path, http.MethodPut, params, putBody) //nolint:bodyclose + return unmarshal(output, resp.Body, err) } // DeleteInto performs an HTTP DELETE against an API path // and unmarshals the payload into a pointer interface. -func (c *Config) DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) (int64, error) { - if c.Debugf == nil { // no log, pass it through. - _, data, _, err := c.body(ctx, path, http.MethodDelete, params, nil) - - return unmarshalBody(output, data, err) - } - - data, err := c.Delete(ctx, path, params) // log the request - - return unmarshal(output, data, err) -} - -// GetBody makes an http request and returns the resp.Body (io.ReadCloser). -// This is useful for downloading things like backup files, but can also be used to get -// around limitations in this library. Always remember to close the io.ReadCloser. -// Before you use the returned data, check the HTTP status code. -// If it's not 200, it's possible the request had an error or was not authenticated. -func (c *Config) GetBody(ctx context.Context, path string, params url.Values) (io.ReadCloser, int, error) { - code, data, header, err := c.body(ctx, path, http.MethodGet, params, nil) - - if c.Debugf != nil { - c.log(code, "", "", header, c.URL+path, http.MethodGet, err) - } - - return data, code, err -} - -// PostBody makes a POST http request and returns the resp.Body (io.ReadCloser). -// Always remember to close the io.ReadCloser. -// Before you use the returned data, check the HTTP status code. -// If it's not 200, it's possible the request had an error or was not authenticated. -func (c *Config) PostBody( - ctx context.Context, - path string, - params url.Values, - postBody io.Reader, -) (io.ReadCloser, int, error) { - code, data, header, err := c.body(ctx, path, http.MethodPost, params, postBody) - - if c.Debugf != nil { - c.log(code, "", "", header, c.URL+path, http.MethodPost, err) - } - - return data, code, err -} - -// PutBody makes a PUT http request and returns the resp.Body (io.ReadCloser). -// Always remember to close the io.ReadCloser. -// Before you use the returned data, check the HTTP status code. -func (c *Config) PutBody( - ctx context.Context, - path string, - params url.Values, - putBody io.Reader, -) (io.ReadCloser, int, error) { - code, data, header, err := c.body(ctx, path, http.MethodPut, params, putBody) - - if c.Debugf != nil { - c.log(code, "", "", header, c.URL+path, http.MethodPut, err) - } - - return data, code, err +func (c *Config) DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) error { + resp, err := c.req(ctx, path, http.MethodDelete, params, nil) //nolint:bodyclose + return unmarshal(output, resp.Body, err) } -// DeleteBody makes a DELETE http request and returns the resp.Body (io.ReadCloser). -// Always remember to close the io.ReadCloser. -// Before you use the returned data, check the HTTP status code. -// If it's not 200, it's possible the request had an error or was not authenticated. -func (c *Config) DeleteBody(ctx context.Context, path string, params url.Values) (io.ReadCloser, int, error) { - code, data, header, err := c.body(ctx, path, http.MethodDelete, params, nil) - - if c.Debugf != nil { - c.log(code, "", "", header, c.URL+path, http.MethodDelete, err) - } - - return data, code, err -} - -// unmarshal is an extra procedure to check an error and unmarshal the payload. -// This allows the methods above to have all their logic abstracted. -func unmarshal(v interface{}, data []byte, err error) (int64, error) { - if err != nil { - return int64(len(data)), err - } else if v == nil { - return int64(len(data)), fmt.Errorf("this is a code bug: %w", ErrNilInterface) - } else if err = json.Unmarshal(data, v); err != nil { - return int64(len(data)), fmt.Errorf("json parse error: %w", err) - } - - return int64(len(data)), nil -} - -// unmarshalBody is an extra procedure to check an error and unmarshal the resp.Body payload. -// This version unmarshals the resp.Body directly. -func unmarshalBody(output interface{}, data io.ReadCloser, err error) (int64, error) { +// unmarshal is an extra procedure to check an error and unmarshal the resp.Body payload. +func unmarshal(output interface{}, data io.ReadCloser, err error) error { defer data.Close() - counter := datacounter.NewReaderCounter(data) - if err != nil { - return int64(counter.Count()), err + return err } else if output == nil { - return int64(counter.Count()), fmt.Errorf("this is a code bug: %w", ErrNilInterface) - } else if err = json.NewDecoder(counter).Decode(output); err != nil { - return int64(counter.Count()), fmt.Errorf("json parse error: %w", err) + return fmt.Errorf("this is a code bug: %w", ErrNilInterface) + } else if err = json.NewDecoder(data).Decode(output); err != nil { + return fmt.Errorf("json parse error: %w", err) } - return int64(counter.Count()), nil + return nil } diff --git a/lidarr/album.go b/lidarr/album.go index 97a4734..0bfe95b 100644 --- a/lidarr/album.go +++ b/lidarr/album.go @@ -112,7 +112,7 @@ func (l *Lidarr) GetAlbumContext(ctx context.Context, mbID string) ([]*Album, er var albums []*Album - _, err := l.GetInto(ctx, "v1/album", params, &albums) + err := l.GetInto(ctx, "v1/album", params, &albums) if err != nil { return nil, fmt.Errorf("api.Get(album): %w", err) } @@ -129,7 +129,7 @@ func (l *Lidarr) GetAlbumByID(albumID int64) (*Album, error) { func (l *Lidarr) GetAlbumByIDContext(ctx context.Context, albumID int64) (*Album, error) { var album Album - _, err := l.GetInto(ctx, "v1/album/"+strconv.FormatInt(albumID, starr.Base10), nil, &album) + err := l.GetInto(ctx, "v1/album/"+strconv.FormatInt(albumID, starr.Base10), nil, &album) if err != nil { return nil, fmt.Errorf("api.Get(album): %w", err) } @@ -154,7 +154,7 @@ func (l *Lidarr) UpdateAlbumContext(ctx context.Context, albumID int64, album *A var output Album - _, err := l.PutInto(ctx, "v1/album/"+strconv.FormatInt(albumID, starr.Base10), params, &body, &output) + err := l.PutInto(ctx, "v1/album/"+strconv.FormatInt(albumID, starr.Base10), params, &body, &output) if err != nil { return nil, fmt.Errorf("api.Put(album): %w", err) } @@ -182,7 +182,7 @@ func (l *Lidarr) AddAlbumContext(ctx context.Context, album *AddAlbumInput) (*Al } var output Album - if _, err := l.PostInto(ctx, "v1/album", params, &body, &output); err != nil { + if err := l.PostInto(ctx, "v1/album", params, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(album): %w", err) } @@ -205,7 +205,7 @@ func (l *Lidarr) LookupContext(ctx context.Context, term string) ([]*Album, erro params := make(url.Values) params.Set("term", term) - _, err := l.GetInto(ctx, "v1/album/lookup", params, &output) + err := l.GetInto(ctx, "v1/album/lookup", params, &output) if err != nil { return nil, fmt.Errorf("api.Get(album/lookup): %w", err) } diff --git a/lidarr/artist.go b/lidarr/artist.go index 16ddb3f..d893044 100644 --- a/lidarr/artist.go +++ b/lidarr/artist.go @@ -70,7 +70,7 @@ func (l *Lidarr) GetArtistContext(ctx context.Context, mbID string) ([]*Artist, var artist []*Artist - _, err := l.GetInto(ctx, "v1/artist", params, &artist) + err := l.GetInto(ctx, "v1/artist", params, &artist) if err != nil { return artist, fmt.Errorf("api.Get(artist): %w", err) } @@ -87,7 +87,7 @@ func (l *Lidarr) GetArtistByID(artistID int64) (*Artist, error) { func (l *Lidarr) GetArtistByIDContext(ctx context.Context, artistID int64) (*Artist, error) { var artist Artist - _, err := l.GetInto(ctx, "v1/artist/"+strconv.FormatInt(artistID, starr.Base10), nil, &artist) + err := l.GetInto(ctx, "v1/artist/"+strconv.FormatInt(artistID, starr.Base10), nil, &artist) if err != nil { return &artist, fmt.Errorf("api.Get(artist): %w", err) } @@ -112,7 +112,7 @@ func (l *Lidarr) AddArtistContext(ctx context.Context, artist *Artist) (*Artist, var output Artist - _, err := l.PostInto(ctx, "v1/artist", params, &body, &output) + err := l.PostInto(ctx, "v1/artist", params, &body, &output) if err != nil { return nil, fmt.Errorf("api.Post(artist): %w", err) } @@ -137,7 +137,7 @@ func (l *Lidarr) UpdateArtistContext(ctx context.Context, artist *Artist) (*Arti var output Artist - _, err := l.PutInto(ctx, "v1/artist/"+strconv.FormatInt(artist.ID, starr.Base10), params, &body, &output) + err := l.PutInto(ctx, "v1/artist/"+strconv.FormatInt(artist.ID, starr.Base10), params, &body, &output) if err != nil { return nil, fmt.Errorf("api.Put(artist): %w", err) } diff --git a/lidarr/command.go b/lidarr/command.go index 85986ea..4f1e041 100644 --- a/lidarr/command.go +++ b/lidarr/command.go @@ -45,7 +45,7 @@ func (l *Lidarr) GetCommands() ([]*CommandResponse, error) { func (l *Lidarr) GetCommandsContext(ctx context.Context) ([]*CommandResponse, error) { var output []*CommandResponse - if _, err := l.GetInto(ctx, "v1/command", nil, &output); err != nil { + if err := l.GetInto(ctx, "v1/command", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(command): %w", err) } @@ -70,7 +70,7 @@ func (l *Lidarr) SendCommandContext(ctx context.Context, cmd *CommandRequest) (* return nil, fmt.Errorf("json.Marshal(cmd): %w", err) } - if _, err := l.PostInto(ctx, "v1/command", nil, &body, &output); err != nil { + if err := l.PostInto(ctx, "v1/command", nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(command): %w", err) } diff --git a/lidarr/history.go b/lidarr/history.go index 0d76bb2..51cf215 100644 --- a/lidarr/history.go +++ b/lidarr/history.go @@ -108,7 +108,7 @@ func (l *Lidarr) GetHistoryPage(params *starr.Req) (*History, error) { func (l *Lidarr) GetHistoryPageContext(ctx context.Context, params *starr.Req) (*History, error) { var history History - _, err := l.GetInto(ctx, "v1/history", params.Params(), &history) + err := l.GetInto(ctx, "v1/history", params.Params(), &history) if err != nil { return nil, fmt.Errorf("api.Get(history): %w", err) } @@ -127,10 +127,12 @@ func (l *Lidarr) FailContext(ctx context.Context, historyID int64) error { return fmt.Errorf("%w: invalid history ID: %d", starr.ErrRequestError, historyID) } + var output interface{} + post := bytes.NewBufferString("id=" + strconv.FormatInt(historyID, starr.Base10)) - _, err := l.Post(ctx, "v1/history/failed", nil, post) - if err != nil { + uri := "v1/history/failed" + if err := l.PostInto(ctx, uri, nil, post, &output); err != nil { return fmt.Errorf("api.Post(history/failed): %w", err) } diff --git a/lidarr/lidarr.go b/lidarr/lidarr.go index 1bb6c53..fa38827 100644 --- a/lidarr/lidarr.go +++ b/lidarr/lidarr.go @@ -1,9 +1,6 @@ package lidarr import ( - "crypto/tls" - "net/http" - "golift.io/starr" ) @@ -16,8 +13,9 @@ type Lidarr struct { } // Filter values are integers. Given names for ease of discovery. -//nolint:lll // https://github.com/Lidarr/Lidarr/blob/c2adf078345f81012ddb5d2f384e2ee45ff7f1af/src/NzbDrone.Core/History/History.cs#L35-L45 +// +//nolint:lll const ( FilterUnknown starr.Filtering = iota FilterGrabbed @@ -35,20 +33,7 @@ const ( // New returns a Lidarr object used to interact with the Lidarr API. func New(config *starr.Config) *Lidarr { if config.Client == nil { - //nolint:exhaustivestruct,gosec - config.Client = &http.Client{ - Timeout: config.Timeout.Duration, - CheckRedirect: func(r *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.ValidSSL}, - }, - } - } - - if config.Debugf == nil { - config.Debugf = func(string, ...interface{}) {} + config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) } return &Lidarr{APIer: config} diff --git a/lidarr/metadataprofile.go b/lidarr/metadataprofile.go index e8a1118..a3b0ff2 100644 --- a/lidarr/metadataprofile.go +++ b/lidarr/metadataprofile.go @@ -37,7 +37,7 @@ func (l *Lidarr) GetMetadataProfiles() ([]*MetadataProfile, error) { func (l *Lidarr) GetMetadataProfilesContext(ctx context.Context) ([]*MetadataProfile, error) { var profiles []*MetadataProfile - _, err := l.GetInto(ctx, "v1/metadataprofile", nil, &profiles) + err := l.GetInto(ctx, "v1/metadataprofile", nil, &profiles) if err != nil { return nil, fmt.Errorf("api.Get(metadataprofile): %w", err) } diff --git a/lidarr/qualitydefinition.go b/lidarr/qualitydefinition.go index 0e04e22..f05c8d6 100644 --- a/lidarr/qualitydefinition.go +++ b/lidarr/qualitydefinition.go @@ -26,7 +26,7 @@ func (l *Lidarr) GetQualityDefinition() ([]*QualityDefinition, error) { func (l *Lidarr) GetQualityDefinitionContext(ctx context.Context) ([]*QualityDefinition, error) { var definition []*QualityDefinition - _, err := l.GetInto(ctx, "v1/qualitydefinition", nil, &definition) + err := l.GetInto(ctx, "v1/qualitydefinition", nil, &definition) if err != nil { return nil, fmt.Errorf("api.Get(qualitydefinition): %w", err) } diff --git a/lidarr/qualityprofile.go b/lidarr/qualityprofile.go index d9b3201..c90aac4 100644 --- a/lidarr/qualityprofile.go +++ b/lidarr/qualityprofile.go @@ -31,7 +31,7 @@ func (l *Lidarr) GetQualityProfiles() ([]*QualityProfile, error) { func (l *Lidarr) GetQualityProfilesContext(ctx context.Context) ([]*QualityProfile, error) { var profiles []*QualityProfile - _, err := l.GetInto(ctx, bpQualityProfile, nil, &profiles) + err := l.GetInto(ctx, bpQualityProfile, nil, &profiles) if err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } @@ -52,7 +52,7 @@ func (l *Lidarr) AddQualityProfileContext(ctx context.Context, profile *QualityP } var output QualityProfile - if _, err := l.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { + if err := l.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { return 0, fmt.Errorf("api.Post(%s): %w", bpQualityProfile, err) } @@ -71,8 +71,10 @@ func (l *Lidarr) UpdateQualityProfileContext(ctx context.Context, profile *Quali return fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } + var output interface{} + uri := path.Join(bpQualityProfile, strconv.FormatInt(profile.ID, starr.Base10)) - if _, err := l.Put(ctx, uri, nil, &body); err != nil { + if err := l.PutInto(ctx, uri, nil, &body, &output); err != nil { return fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) } @@ -86,8 +88,10 @@ func (l *Lidarr) DeleteQualityProfile(profileID int64) error { // DeleteQualityProfileContext deletes a quality profile. func (l *Lidarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { + var output interface{} + uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) - if _, err := l.Delete(ctx, uri, nil); err != nil { + if err := l.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } diff --git a/lidarr/queue.go b/lidarr/queue.go index fb68a2b..de4d9c9 100644 --- a/lidarr/queue.go +++ b/lidarr/queue.go @@ -96,7 +96,7 @@ func (l *Lidarr) GetQueuePageContext(ctx context.Context, params *starr.Req) (*Q params.CheckSet("sortKey", "timeleft") params.CheckSet("includeUnknownArtistItems", "true") - _, err := l.GetInto(ctx, "v1/queue", params.Params(), &queue) + err := l.GetInto(ctx, "v1/queue", params.Params(), &queue) if err != nil { return nil, fmt.Errorf("api.Get(queue): %w", err) } diff --git a/lidarr/rootfolder.go b/lidarr/rootfolder.go index d0e69f7..bdfd043 100644 --- a/lidarr/rootfolder.go +++ b/lidarr/rootfolder.go @@ -25,7 +25,7 @@ func (l *Lidarr) GetRootFolders() ([]*RootFolder, error) { func (l *Lidarr) GetRootFoldersContext(ctx context.Context) ([]*RootFolder, error) { var folders []*RootFolder - _, err := l.GetInto(ctx, "v1/rootFolder", nil, &folders) + err := l.GetInto(ctx, "v1/rootFolder", nil, &folders) if err != nil { return nil, fmt.Errorf("api.Get(rootFolder): %w", err) } diff --git a/lidarr/system.go b/lidarr/system.go index 75da32c..7a97ccf 100644 --- a/lidarr/system.go +++ b/lidarr/system.go @@ -46,7 +46,7 @@ func (l *Lidarr) GetSystemStatus() (*SystemStatus, error) { func (l *Lidarr) GetSystemStatusContext(ctx context.Context) (*SystemStatus, error) { var status SystemStatus - _, err := l.GetInto(ctx, "v1/system/status", nil, &status) + err := l.GetInto(ctx, "v1/system/status", nil, &status) if err != nil { return nil, fmt.Errorf("api.Get(system/status): %w", err) } @@ -65,7 +65,7 @@ func (l *Lidarr) GetBackupFiles() ([]*starr.BackupFile, error) { func (l *Lidarr) GetBackupFilesContext(ctx context.Context) ([]*starr.BackupFile, error) { var output []*starr.BackupFile - if _, err := l.GetInto(ctx, "v1/system/backup", nil, &output); err != nil { + if err := l.GetInto(ctx, "v1/system/backup", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(system/backup): %w", err) } diff --git a/lidarr/tag.go b/lidarr/tag.go index 4bbdbd4..256d767 100644 --- a/lidarr/tag.go +++ b/lidarr/tag.go @@ -21,7 +21,7 @@ func (l *Lidarr) GetTags() ([]*starr.Tag, error) { func (l *Lidarr) GetTagsContext(ctx context.Context) ([]*starr.Tag, error) { var output []*starr.Tag - if _, err := l.GetInto(ctx, bpTag, nil, &output); err != nil { + if err := l.GetInto(ctx, bpTag, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -37,7 +37,7 @@ func (l *Lidarr) GetTagContext(ctx context.Context, tagID int) (*starr.Tag, erro var output *starr.Tag uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := l.GetInto(ctx, uri, nil, &output); err != nil { + if err := l.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -57,7 +57,7 @@ func (l *Lidarr) AddTagContext(ctx context.Context, tag *starr.Tag) (*starr.Tag, return nil, fmt.Errorf("json.Marshal(tag): %w", err) } - if _, err := l.PostInto(ctx, bpTag, nil, &body, &output); err != nil { + if err := l.PostInto(ctx, bpTag, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(tag): %w", err) } @@ -78,7 +78,7 @@ func (l *Lidarr) UpdateTagContext(ctx context.Context, tag *starr.Tag) (*starr.T } uri := path.Join(bpTag, strconv.Itoa(tag.ID)) - if _, err := l.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := l.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(tag): %w", err) } @@ -91,8 +91,10 @@ func (l *Lidarr) DeleteTag(tagID int) error { } func (l *Lidarr) DeleteTagContext(ctx context.Context, tagID int) error { + var output interface{} + uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := l.Delete(ctx, uri, nil); err != nil { + if err := l.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/prowlarr/prowlarr.go b/prowlarr/prowlarr.go index b384d8d..505a04f 100644 --- a/prowlarr/prowlarr.go +++ b/prowlarr/prowlarr.go @@ -1,9 +1,6 @@ package prowlarr import ( - "crypto/tls" - "net/http" - "golift.io/starr" ) @@ -18,20 +15,7 @@ const APIver = "v1" // New returns a Prowlarr object used to interact with the Prowlarr API. func New(config *starr.Config) *Prowlarr { if config.Client == nil { - //nolint:exhaustivestruct,gosec - config.Client = &http.Client{ - Timeout: config.Timeout.Duration, - CheckRedirect: func(r *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.ValidSSL}, - }, - } - } - - if config.Debugf == nil { - config.Debugf = func(string, ...interface{}) {} + config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) } return &Prowlarr{APIer: config} diff --git a/prowlarr/system.go b/prowlarr/system.go index 7375803..ef79a34 100644 --- a/prowlarr/system.go +++ b/prowlarr/system.go @@ -50,7 +50,7 @@ func (p *Prowlarr) GetSystemStatus() (*SystemStatus, error) { func (p *Prowlarr) GetSystemStatusContext(ctx context.Context) (*SystemStatus, error) { var status SystemStatus - _, err := p.GetInto(ctx, "v1/system/status", nil, &status) + err := p.GetInto(ctx, "v1/system/status", nil, &status) if err != nil { return nil, fmt.Errorf("api.Get(system/status): %w", err) } @@ -69,7 +69,7 @@ func (p *Prowlarr) GetBackupFiles() ([]*starr.BackupFile, error) { func (p *Prowlarr) GetBackupFilesContext(ctx context.Context) ([]*starr.BackupFile, error) { var output []*starr.BackupFile - if _, err := p.GetInto(ctx, "v1/system/backup", nil, &output); err != nil { + if err := p.GetInto(ctx, "v1/system/backup", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(system/backup): %w", err) } diff --git a/prowlarr/tag.go b/prowlarr/tag.go index 7ee569f..587676c 100644 --- a/prowlarr/tag.go +++ b/prowlarr/tag.go @@ -21,7 +21,7 @@ func (p *Prowlarr) GetTags() ([]*starr.Tag, error) { func (p *Prowlarr) GetTagsContext(ctx context.Context) ([]*starr.Tag, error) { var output []*starr.Tag - if _, err := p.GetInto(ctx, bpTag, nil, &output); err != nil { + if err := p.GetInto(ctx, bpTag, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -37,7 +37,7 @@ func (p *Prowlarr) GetTagContext(ctx context.Context, tagID int) (*starr.Tag, er var output *starr.Tag uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := p.GetInto(ctx, uri, nil, &output); err != nil { + if err := p.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -57,7 +57,7 @@ func (p *Prowlarr) AddTagContext(ctx context.Context, tag *starr.Tag) (*starr.Ta return nil, fmt.Errorf("json.Marshal(tag): %w", err) } - if _, err := p.PostInto(ctx, bpTag, nil, &body, &output); err != nil { + if err := p.PostInto(ctx, bpTag, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(tag): %w", err) } @@ -78,7 +78,7 @@ func (p *Prowlarr) UpdateTagContext(ctx context.Context, tag *starr.Tag) (*starr } uri := path.Join(bpTag, strconv.Itoa(tag.ID)) - if _, err := p.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := p.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(tag): %w", err) } @@ -91,8 +91,10 @@ func (p *Prowlarr) DeleteTag(tagID int) error { } func (p *Prowlarr) DeleteTagContext(ctx context.Context, tagID int) error { + var output interface{} + uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := p.Delete(ctx, uri, nil); err != nil { + if err := p.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/radarr/command.go b/radarr/command.go index 18f5240..854118a 100644 --- a/radarr/command.go +++ b/radarr/command.go @@ -44,7 +44,7 @@ func (r *Radarr) GetCommands() ([]*CommandResponse, error) { func (r *Radarr) GetCommandsContext(ctx context.Context) ([]*CommandResponse, error) { var output []*CommandResponse - if _, err := r.GetInto(ctx, "v3/command", nil, &output); err != nil { + if err := r.GetInto(ctx, "v3/command", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(command): %w", err) } @@ -69,7 +69,7 @@ func (r *Radarr) SendCommandContext(ctx context.Context, cmd *CommandRequest) (* return nil, fmt.Errorf("json.Marshal(cmd): %w", err) } - if _, err := r.PostInto(ctx, "v3/command", nil, &body, &output); err != nil { + if err := r.PostInto(ctx, "v3/command", nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(command): %w", err) } diff --git a/radarr/customformat.go b/radarr/customformat.go index 2c2f27b..ca499b9 100644 --- a/radarr/customformat.go +++ b/radarr/customformat.go @@ -48,7 +48,7 @@ func (r *Radarr) GetCustomFormats() ([]*CustomFormat, error) { // GetCustomFormatsContext returns all configured Custom Formats. func (r *Radarr) GetCustomFormatsContext(ctx context.Context) ([]*CustomFormat, error) { var output []*CustomFormat - if _, err := r.GetInto(ctx, bpCustomFormat, nil, &output); err != nil { + if err := r.GetInto(ctx, bpCustomFormat, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpCustomFormat, err) } @@ -75,7 +75,7 @@ func (r *Radarr) AddCustomFormatContext(ctx context.Context, format *CustomForma return nil, fmt.Errorf("json.Marshal(%s): %w", bpCustomFormat, err) } - if _, err := r.PostInto(ctx, bpCustomFormat, nil, &body, &output); err != nil { + if err := r.PostInto(ctx, bpCustomFormat, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(%s): %w", bpCustomFormat, err) } @@ -101,7 +101,7 @@ func (r *Radarr) UpdateCustomFormatContext(ctx context.Context, format *CustomFo var output CustomFormat uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) - if _, err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(%s): %w", bpCustomFormat, err) } @@ -115,8 +115,10 @@ func (r *Radarr) DeleteCustomFormat(cfID int) error { // DeleteCustomFormatContext deletes a custom format. func (r *Radarr) DeleteCustomFormatContext(ctx context.Context, cfID int) error { + var output interface{} + uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) - if _, err := r.Delete(ctx, uri, nil); err != nil { + if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpCustomFormat, err) } diff --git a/radarr/exclusions.go b/radarr/exclusions.go index 743c516..db498ac 100644 --- a/radarr/exclusions.go +++ b/radarr/exclusions.go @@ -27,7 +27,7 @@ func (r *Radarr) GetExclusions() ([]*Exclusion, error) { func (r *Radarr) GetExclusionsContext(ctx context.Context) ([]*Exclusion, error) { var exclusions []*Exclusion - _, err := r.GetInto(ctx, "v3/exclusions", nil, &exclusions) + err := r.GetInto(ctx, "v3/exclusions", nil, &exclusions) if err != nil { return nil, fmt.Errorf("api.Get(exclusions): %w", err) } @@ -45,8 +45,10 @@ func (r *Radarr) DeleteExclusionsContext(ctx context.Context, ids []int64) error var errs string for _, id := range ids { - _, err := r.Delete(ctx, "v3/exclusions/"+strconv.FormatInt(id, starr.Base10), nil) - if err != nil { + var output interface{} + + uri := "v3/exclusions/" + strconv.FormatInt(id, starr.Base10) + if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { errs += err.Error() + " " } } @@ -74,8 +76,10 @@ func (r *Radarr) AddExclusionsContext(ctx context.Context, exclusions []*Exclusi return fmt.Errorf("json.Marshal(exclusions): %w", err) } - _, err := r.Post(ctx, "v3/exclusions/bulk", nil, &body) - if err != nil { + var output interface{} + + uri := "v3/exclusions/bulk" + if err := r.PostInto(ctx, uri, nil, &body, &output); err != nil { return fmt.Errorf("api.Post(exclusions): %w", err) } diff --git a/radarr/history.go b/radarr/history.go index 33e9f80..d24720f 100644 --- a/radarr/history.go +++ b/radarr/history.go @@ -110,7 +110,7 @@ func (r *Radarr) GetHistoryPage(params *starr.Req) (*History, error) { func (r *Radarr) GetHistoryPageContext(ctx context.Context, params *starr.Req) (*History, error) { var history History - _, err := r.GetInto(ctx, "v3/history", params.Params(), &history) + err := r.GetInto(ctx, "v3/history", params.Params(), &history) if err != nil { return nil, fmt.Errorf("api.Get(history): %w", err) } @@ -129,9 +129,11 @@ func (r *Radarr) FailContext(ctx context.Context, historyID int64) error { return fmt.Errorf("%w: invalid history ID: %d", starr.ErrRequestError, historyID) } + var output interface{} + // Strangely uses a POST without a payload. - _, err := r.Post(ctx, "v3/history/failed/"+strconv.FormatInt(historyID, starr.Base10), nil, nil) - if err != nil { + uri := "v3/history/failed/" + strconv.FormatInt(historyID, starr.Base10) + if err := r.PostInto(ctx, uri, nil, nil, &output); err != nil { return fmt.Errorf("api.Post(history/failed): %w", err) } diff --git a/radarr/importlist.go b/radarr/importlist.go index 1535155..145b52e 100644 --- a/radarr/importlist.go +++ b/radarr/importlist.go @@ -59,7 +59,7 @@ func (r *Radarr) GetImportLists() ([]*ImportList, error) { // GetImportListsContext returns all import lists. func (r *Radarr) GetImportListsContext(ctx context.Context) ([]*ImportList, error) { var output []*ImportList - if _, err := r.GetInto(ctx, "v3/importlist", nil, &output); err != nil { + if err := r.GetInto(ctx, "v3/importlist", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(importlist): %w", err) } @@ -81,7 +81,7 @@ func (r *Radarr) CreateImportListContext(ctx context.Context, list *ImportList) } var output ImportList - if _, err := r.PostInto(ctx, "v3/importlist", nil, &body, &output); err != nil { + if err := r.PostInto(ctx, "v3/importlist", nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(importlist): %w", err) } @@ -98,8 +98,10 @@ func (r *Radarr) DeleteImportListContext(ctx context.Context, ids []int64) error var errs string for _, id := range ids { - _, err := r.Delete(ctx, "v3/importlist/"+strconv.FormatInt(id, starr.Base10), nil) - if err != nil { + var output interface{} + + uri := "v3/importlist/" + strconv.FormatInt(id, starr.Base10) + if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { errs += fmt.Errorf("api.Delete(importlist): %w", err).Error() + " " } } @@ -125,7 +127,7 @@ func (r *Radarr) UpdateImportListContext(ctx context.Context, list *ImportList) var output ImportList - _, err := r.PutInto(ctx, "v3/importlist/"+strconv.FormatInt(list.ID, starr.Base10), nil, &body, &output) + err := r.PutInto(ctx, "v3/importlist/"+strconv.FormatInt(list.ID, starr.Base10), nil, &body, &output) if err != nil { return nil, fmt.Errorf("api.Put(importlist): %w", err) } diff --git a/radarr/movie.go b/radarr/movie.go index b7ec579..cdd5eaa 100644 --- a/radarr/movie.go +++ b/radarr/movie.go @@ -179,7 +179,7 @@ func (r *Radarr) GetMovieContext(ctx context.Context, tmdbID int64) ([]*Movie, e var movie []*Movie - _, err := r.GetInto(ctx, "v3/movie", params, &movie) + err := r.GetInto(ctx, "v3/movie", params, &movie) if err != nil { return nil, fmt.Errorf("api.Get(movie): %w", err) } @@ -196,7 +196,7 @@ func (r *Radarr) GetMovieByID(movieID int64) (*Movie, error) { func (r *Radarr) GetMovieByIDContext(ctx context.Context, movieID int64) (*Movie, error) { var movie Movie - _, err := r.GetInto(ctx, "v3/movie/"+strconv.FormatInt(movieID, starr.Base10), nil, &movie) + err := r.GetInto(ctx, "v3/movie/"+strconv.FormatInt(movieID, starr.Base10), nil, &movie) if err != nil { return nil, fmt.Errorf("api.Get(movie): %w", err) } @@ -219,8 +219,10 @@ func (r *Radarr) UpdateMovieContext(ctx context.Context, movieID int64, movie *M return fmt.Errorf("json.Marshal(movie): %w", err) } - _, err := r.Put(ctx, "v3/movie/"+strconv.FormatInt(movieID, starr.Base10), params, &body) - if err != nil { + var output interface{} + + uri := "v3/movie/" + strconv.FormatInt(movieID, starr.Base10) + if err := r.PutInto(ctx, uri, params, &body, &output); err != nil { return fmt.Errorf("api.Put(movie): %w", err) } @@ -243,7 +245,7 @@ func (r *Radarr) AddMovieContext(ctx context.Context, movie *AddMovieInput) (*Ad } var output AddMovieOutput - if _, err := r.PostInto(ctx, "v3/movie", params, &body, &output); err != nil { + if err := r.PostInto(ctx, "v3/movie", params, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(movie): %w", err) } @@ -266,7 +268,7 @@ func (r *Radarr) LookupContext(ctx context.Context, term string) ([]*Movie, erro params := make(url.Values) params.Set("term", term) - _, err := r.GetInto(ctx, "v3/movie/lookup", params, &output) + err := r.GetInto(ctx, "v3/movie/lookup", params, &output) if err != nil { return nil, fmt.Errorf("api.Get(movie/lookup): %w", err) } diff --git a/radarr/qualityprofile.go b/radarr/qualityprofile.go index 10885a5..59831b4 100644 --- a/radarr/qualityprofile.go +++ b/radarr/qualityprofile.go @@ -42,7 +42,7 @@ func (r *Radarr) GetQualityProfiles() ([]*QualityProfile, error) { func (r *Radarr) GetQualityProfilesContext(ctx context.Context) ([]*QualityProfile, error) { var profiles []*QualityProfile - _, err := r.GetInto(ctx, bpQualityProfile, nil, &profiles) + err := r.GetInto(ctx, bpQualityProfile, nil, &profiles) if err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } @@ -63,7 +63,7 @@ func (r *Radarr) AddQualityProfileContext(ctx context.Context, profile *QualityP } var output QualityProfile - if _, err := r.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { + if err := r.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { return 0, fmt.Errorf("api.Post(%s): %w", bpQualityProfile, err) } @@ -82,8 +82,10 @@ func (r *Radarr) UpdateQualityProfileContext(ctx context.Context, profile *Quali return fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } + var output interface{} + uri := path.Join(bpQualityProfile, strconv.FormatInt(profile.ID, starr.Base10)) - if _, err := r.Put(ctx, uri, nil, &body); err != nil { + if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { return fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) } @@ -97,8 +99,10 @@ func (r *Radarr) DeleteQualityProfile(profileID int64) error { // DeleteQualityProfileContext deletes a quality profile. func (r *Radarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { + var output interface{} + uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) - if _, err := r.Delete(ctx, uri, nil); err != nil { + if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } diff --git a/radarr/queue.go b/radarr/queue.go index e2f03c5..6b28d67 100644 --- a/radarr/queue.go +++ b/radarr/queue.go @@ -97,7 +97,7 @@ func (r *Radarr) GetQueuePageContext(ctx context.Context, params *starr.Req) (*Q params.CheckSet("sortKey", "timeleft") params.CheckSet("includeUnknownMovieItems", "true") - _, err := r.GetInto(ctx, "v3/queue", params.Params(), &queue) + err := r.GetInto(ctx, "v3/queue", params.Params(), &queue) if err != nil { return nil, fmt.Errorf("api.Get(queue): %w", err) } diff --git a/radarr/radarr.go b/radarr/radarr.go index 7ef16ae..4e17d93 100644 --- a/radarr/radarr.go +++ b/radarr/radarr.go @@ -1,9 +1,6 @@ package radarr import ( - "crypto/tls" - "net/http" - "golift.io/starr" ) @@ -16,8 +13,9 @@ type Radarr struct { } // Filter values are integers. Given names for ease of discovery. -//nolint:lll // https://github.com/Radarr/Radarr/blob/2bca1a71a2ed5130ea642343cb76250f3bf5bc4e/src/NzbDrone.Core/History/History.cs#L33-L44 +// +//nolint:lll const ( FilterUnknown starr.Filtering = iota FilterGrabbed @@ -34,20 +32,7 @@ const ( // New returns a Radarr object used to interact with the Radarr API. func New(config *starr.Config) *Radarr { if config.Client == nil { - //nolint:exhaustivestruct,gosec - config.Client = &http.Client{ - Timeout: config.Timeout.Duration, - CheckRedirect: func(r *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.ValidSSL}, - }, - } - } - - if config.Debugf == nil { - config.Debugf = func(string, ...interface{}) {} + config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) } return &Radarr{APIer: config} diff --git a/radarr/rootfolder.go b/radarr/rootfolder.go index b4bf232..24f6b31 100644 --- a/radarr/rootfolder.go +++ b/radarr/rootfolder.go @@ -25,7 +25,7 @@ func (r *Radarr) GetRootFolders() ([]*RootFolder, error) { func (r *Radarr) GetRootFoldersContext(ctx context.Context) ([]*RootFolder, error) { var folders []*RootFolder - _, err := r.GetInto(ctx, "v3/rootFolder", nil, &folders) + err := r.GetInto(ctx, "v3/rootFolder", nil, &folders) if err != nil { return nil, fmt.Errorf("api.Get(rootFolder): %w", err) } diff --git a/radarr/system.go b/radarr/system.go index 1565d84..9660048 100644 --- a/radarr/system.go +++ b/radarr/system.go @@ -43,7 +43,7 @@ func (r *Radarr) GetSystemStatus() (*SystemStatus, error) { func (r *Radarr) GetSystemStatusContext(ctx context.Context) (*SystemStatus, error) { var status SystemStatus - _, err := r.GetInto(ctx, "v3/system/status", nil, &status) + err := r.GetInto(ctx, "v3/system/status", nil, &status) if err != nil { return nil, fmt.Errorf("api.Get(system/status): %w", err) } @@ -62,7 +62,7 @@ func (r *Radarr) GetBackupFiles() ([]*starr.BackupFile, error) { func (r *Radarr) GetBackupFilesContext(ctx context.Context) ([]*starr.BackupFile, error) { var output []*starr.BackupFile - if _, err := r.GetInto(ctx, "v3/system/backup", nil, &output); err != nil { + if err := r.GetInto(ctx, "v3/system/backup", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(system/backup): %w", err) } diff --git a/radarr/tag.go b/radarr/tag.go index e50d770..4b2d27d 100644 --- a/radarr/tag.go +++ b/radarr/tag.go @@ -21,7 +21,7 @@ func (r *Radarr) GetTags() ([]*starr.Tag, error) { func (r *Radarr) GetTagsContext(ctx context.Context) ([]*starr.Tag, error) { var output []*starr.Tag - if _, err := r.GetInto(ctx, bpTag, nil, &output); err != nil { + if err := r.GetInto(ctx, bpTag, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -37,7 +37,7 @@ func (r *Radarr) GetTagContext(ctx context.Context, tagID int) (*starr.Tag, erro var output *starr.Tag uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := r.GetInto(ctx, uri, nil, &output); err != nil { + if err := r.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -57,7 +57,7 @@ func (r *Radarr) AddTagContext(ctx context.Context, tag *starr.Tag) (*starr.Tag, return nil, fmt.Errorf("json.Marshal(tag): %w", err) } - if _, err := r.PostInto(ctx, bpTag, nil, &body, &output); err != nil { + if err := r.PostInto(ctx, bpTag, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(tag): %w", err) } @@ -78,7 +78,7 @@ func (r *Radarr) UpdateTagContext(ctx context.Context, tag *starr.Tag) (*starr.T } uri := path.Join(bpTag, strconv.Itoa(tag.ID)) - if _, err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(tag): %w", err) } @@ -91,8 +91,10 @@ func (r *Radarr) DeleteTag(tagID int) error { } func (r *Radarr) DeleteTagContext(ctx context.Context, tagID int) error { + var output interface{} + uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := r.Delete(ctx, uri, nil); err != nil { + if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/readarr/author.go b/readarr/author.go index b4afc28..f20d07f 100644 --- a/readarr/author.go +++ b/readarr/author.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "log" "net/url" "strconv" "time" @@ -82,7 +81,7 @@ func (r *Readarr) GetAuthorByID(authorID int64) (*Author, error) { func (r *Readarr) GetAuthorByIDContext(ctx context.Context, authorID int64) (*Author, error) { var author Author - _, err := r.GetInto(ctx, "v1/author/"+strconv.FormatInt(authorID, starr.Base10), nil, &author) + err := r.GetInto(ctx, "v1/author/"+strconv.FormatInt(authorID, starr.Base10), nil, &author) if err != nil { return nil, fmt.Errorf("api.Get(author): %w", err) } @@ -105,12 +104,12 @@ func (r *Readarr) UpdateAuthorContext(ctx context.Context, authorID int64, autho return fmt.Errorf("json.Marshal(author): %w", err) } - b, err := r.Put(ctx, "v1/author/"+strconv.FormatInt(authorID, starr.Base10), params, &body) - if err != nil { + var output interface{} + + uri := "v1/author/" + strconv.FormatInt(authorID, starr.Base10) + if err := r.PutInto(ctx, uri, params, &body, &output); err != nil { return fmt.Errorf("api.Put(author): %w", err) } - log.Println("SHOW THIS TO CAPTAIN plz:", string(b)) - return nil } diff --git a/readarr/book.go b/readarr/book.go index 00e4d16..9e8f4f9 100644 --- a/readarr/book.go +++ b/readarr/book.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "log" "net/url" "strconv" "time" @@ -165,7 +164,7 @@ func (r *Readarr) GetBookContext(ctx context.Context, gridID string) ([]*Book, e var books []*Book - _, err := r.GetInto(ctx, "v1/book", params, &books) + err := r.GetInto(ctx, "v1/book", params, &books) if err != nil { return nil, fmt.Errorf("api.Get(book): %w", err) } @@ -181,7 +180,7 @@ func (r *Readarr) GetBookByID(bookID int64) (*Book, error) { func (r *Readarr) GetBookByIDContext(ctx context.Context, bookID int64) (*Book, error) { var book Book - _, err := r.GetInto(ctx, "v1/book/"+strconv.FormatInt(bookID, starr.Base10), nil, &book) + err := r.GetInto(ctx, "v1/book/"+strconv.FormatInt(bookID, starr.Base10), nil, &book) if err != nil { return nil, fmt.Errorf("api.Get(book): %w", err) } @@ -203,13 +202,13 @@ func (r *Readarr) UpdateBookContext(ctx context.Context, bookID int64, book *Boo return fmt.Errorf("json.Marshal(book): %w", err) } - b, err := r.Put(ctx, "v1/book/"+strconv.FormatInt(bookID, starr.Base10), params, &body) + var unknown interface{} + + err := r.PutInto(ctx, "v1/book/"+strconv.FormatInt(bookID, starr.Base10), params, &body, &unknown) if err != nil { return fmt.Errorf("api.Put(book): %w", err) } - log.Println("SHOW THIS TO CAPTAIN plz:", string(b)) - return nil } @@ -228,7 +227,7 @@ func (r *Readarr) AddBookContext(ctx context.Context, book *AddBookInput) (*AddB } var output AddBookOutput - if _, err := r.PostInto(ctx, "v1/book", params, &body, &output); err != nil { + if err := r.PostInto(ctx, "v1/book", params, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(book): %w", err) } @@ -250,7 +249,7 @@ func (r *Readarr) LookupContext(ctx context.Context, term string) ([]*Book, erro params := make(url.Values) params.Set("term", term) - _, err := r.GetInto(ctx, "v1/book/lookup", params, &output) + err := r.GetInto(ctx, "v1/book/lookup", params, &output) if err != nil { return nil, fmt.Errorf("api.Get(book/lookup): %w", err) } diff --git a/readarr/command.go b/readarr/command.go index 88624c4..4e1275c 100644 --- a/readarr/command.go +++ b/readarr/command.go @@ -45,7 +45,7 @@ func (r *Readarr) GetCommands() ([]*CommandResponse, error) { func (r *Readarr) GetCommandsContext(ctx context.Context) ([]*CommandResponse, error) { var output []*CommandResponse - if _, err := r.GetInto(ctx, "v1/command", nil, &output); err != nil { + if err := r.GetInto(ctx, "v1/command", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(command): %w", err) } @@ -69,7 +69,7 @@ func (r *Readarr) SendCommandContext(ctx context.Context, cmd *CommandRequest) ( return nil, fmt.Errorf("json.Marshal(cmd): %w", err) } - if _, err := r.PostInto(ctx, "v1/command", nil, &body, &output); err != nil { + if err := r.PostInto(ctx, "v1/command", nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(command): %w", err) } diff --git a/readarr/history.go b/readarr/history.go index 5bc8e99..f56a0d9 100644 --- a/readarr/history.go +++ b/readarr/history.go @@ -104,7 +104,7 @@ func (r *Readarr) GetHistoryPage(params *starr.Req) (*History, error) { func (r *Readarr) GetHistoryPageContext(ctx context.Context, params *starr.Req) (*History, error) { var history History - _, err := r.GetInto(ctx, "v1/history", params.Params(), &history) + err := r.GetInto(ctx, "v1/history", params.Params(), &history) if err != nil { return nil, fmt.Errorf("api.Get(history): %w", err) } @@ -122,9 +122,12 @@ func (r *Readarr) FailContext(ctx context.Context, historyID int64) error { return fmt.Errorf("%w: invalid history ID: %d", starr.ErrRequestError, historyID) } - _, err := r.Post(ctx, - "v1/history/failed", nil, bytes.NewBufferString("id="+strconv.FormatInt(historyID, starr.Base10))) - if err != nil { + var output interface{} + + body := bytes.NewBufferString("id=" + strconv.FormatInt(historyID, starr.Base10)) + + uri := "v1/history/failed" + if err := r.PostInto(ctx, uri, nil, body, &output); err != nil { return fmt.Errorf("api.Post(history/failed): %w", err) } diff --git a/readarr/metadataprofile.go b/readarr/metadataprofile.go index 5e7a846..109a547 100644 --- a/readarr/metadataprofile.go +++ b/readarr/metadataprofile.go @@ -25,7 +25,7 @@ func (r *Readarr) GetMetadataProfiles() ([]*MetadataProfile, error) { func (r *Readarr) GetMetadataProfilesContext(ctx context.Context) ([]*MetadataProfile, error) { var profiles []*MetadataProfile - _, err := r.GetInto(ctx, "v1/metadataprofile", nil, &profiles) + err := r.GetInto(ctx, "v1/metadataprofile", nil, &profiles) if err != nil { return nil, fmt.Errorf("api.Get(metadataprofile): %w", err) } diff --git a/readarr/qualityprofile.go b/readarr/qualityprofile.go index 8b593e6..19be744 100644 --- a/readarr/qualityprofile.go +++ b/readarr/qualityprofile.go @@ -30,7 +30,7 @@ func (r *Readarr) GetQualityProfiles() ([]*QualityProfile, error) { func (r *Readarr) GetQualityProfilesContext(ctx context.Context) ([]*QualityProfile, error) { var profiles []*QualityProfile - _, err := r.GetInto(ctx, bpQualityProfile, nil, &profiles) + err := r.GetInto(ctx, bpQualityProfile, nil, &profiles) if err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } @@ -50,7 +50,7 @@ func (r *Readarr) AddQualityProfileContext(ctx context.Context, profile *Quality } var output QualityProfile - if _, err := r.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { + if err := r.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { return 0, fmt.Errorf("api.Post(%s): %w", bpQualityProfile, err) } @@ -68,8 +68,10 @@ func (r *Readarr) UpdateQualityProfileContext(ctx context.Context, profile *Qual return fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } + var output interface{} + uri := path.Join(bpQualityProfile, strconv.FormatInt(profile.ID, starr.Base10)) - if _, err := r.Put(ctx, uri, nil, &body); err != nil { + if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { return fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) } @@ -83,8 +85,10 @@ func (r *Readarr) DeleteQualityProfile(profileID int64) error { // DeleteQualityProfileContext deletes a quality profile. func (r *Readarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { + var output interface{} + uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) - if _, err := r.Delete(ctx, uri, nil); err != nil { + if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } diff --git a/readarr/queue.go b/readarr/queue.go index cdccfb6..827a7b3 100644 --- a/readarr/queue.go +++ b/readarr/queue.go @@ -94,7 +94,7 @@ func (r *Readarr) GetQueuePageContext(ctx context.Context, params *starr.Req) (* params.CheckSet("sortKey", "timeleft") params.CheckSet("includeUnknownAuthorItems", "true") - _, err := r.GetInto(ctx, "v1/queue", params.Params(), &queue) + err := r.GetInto(ctx, "v1/queue", params.Params(), &queue) if err != nil { return nil, fmt.Errorf("api.Get(queue): %w", err) } diff --git a/readarr/readarr.go b/readarr/readarr.go index 818ca55..e08dce2 100644 --- a/readarr/readarr.go +++ b/readarr/readarr.go @@ -1,9 +1,6 @@ package readarr import ( - "crypto/tls" - "net/http" - "golift.io/starr" ) @@ -16,8 +13,9 @@ type Readarr struct { } // Filter values are integers. Given names for ease of discovery. -//nolint:lll // https://github.com/Readarr/Readarr/blob/de72cfcaaa22495c7ce9fcb596a93beff6efb3d6/src/NzbDrone.Core/History/EntityHistory.cs#L31-L43 +// +//nolint:lll const ( FilterAll starr.Filtering = iota FilterGrabbed @@ -35,20 +33,7 @@ const ( // New returns a Readarr object used to interact with the Readarr API. func New(config *starr.Config) *Readarr { if config.Client == nil { - //nolint:exhaustivestruct,gosec - config.Client = &http.Client{ - Timeout: config.Timeout.Duration, - CheckRedirect: func(r *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.ValidSSL}, - }, - } - } - - if config.Debugf == nil { - config.Debugf = func(string, ...interface{}) {} + config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) } return &Readarr{APIer: config} diff --git a/readarr/rootfolder.go b/readarr/rootfolder.go index cc484d4..94e0c6d 100644 --- a/readarr/rootfolder.go +++ b/readarr/rootfolder.go @@ -31,7 +31,7 @@ func (r *Readarr) GetRootFolders() ([]*RootFolder, error) { func (r *Readarr) GetRootFoldersContext(ctx context.Context) ([]*RootFolder, error) { var folders []*RootFolder - _, err := r.GetInto(ctx, "v1/rootFolder", nil, &folders) + err := r.GetInto(ctx, "v1/rootFolder", nil, &folders) if err != nil { return nil, fmt.Errorf("api.Get(rootFolder): %w", err) } diff --git a/readarr/system.go b/readarr/system.go index 83835ad..c4c7f91 100644 --- a/readarr/system.go +++ b/readarr/system.go @@ -48,7 +48,7 @@ func (r *Readarr) GetSystemStatus() (*SystemStatus, error) { func (r *Readarr) GetSystemStatusContext(ctx context.Context) (*SystemStatus, error) { var status SystemStatus - _, err := r.GetInto(ctx, "v1/system/status", nil, &status) + err := r.GetInto(ctx, "v1/system/status", nil, &status) if err != nil { return &status, fmt.Errorf("api.Get(system/status): %w", err) } @@ -65,7 +65,7 @@ func (r *Readarr) GetBackupFiles() ([]*starr.BackupFile, error) { func (r *Readarr) GetBackupFilesContext(ctx context.Context) ([]*starr.BackupFile, error) { var output []*starr.BackupFile - if _, err := r.GetInto(ctx, "v1/system/backup", nil, &output); err != nil { + if err := r.GetInto(ctx, "v1/system/backup", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(system/backup): %w", err) } diff --git a/readarr/tag.go b/readarr/tag.go index 7ca523d..c73a5dd 100644 --- a/readarr/tag.go +++ b/readarr/tag.go @@ -21,7 +21,7 @@ func (r *Readarr) GetTags() ([]*starr.Tag, error) { func (r *Readarr) GetTagsContext(ctx context.Context) ([]*starr.Tag, error) { var output []*starr.Tag - if _, err := r.GetInto(ctx, bpTag, nil, &output); err != nil { + if err := r.GetInto(ctx, bpTag, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -37,7 +37,7 @@ func (r *Readarr) GetTagContext(ctx context.Context, tagID int) (*starr.Tag, err var output *starr.Tag uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := r.GetInto(ctx, uri, nil, &output); err != nil { + if err := r.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -57,7 +57,7 @@ func (r *Readarr) AddTagContext(ctx context.Context, tag *starr.Tag) (*starr.Tag return nil, fmt.Errorf("json.Marshal(tag): %w", err) } - if _, err := r.PostInto(ctx, bpTag, nil, &body, &output); err != nil { + if err := r.PostInto(ctx, bpTag, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(tag): %w", err) } @@ -78,7 +78,7 @@ func (r *Readarr) UpdateTagContext(ctx context.Context, tag *starr.Tag) (*starr. } uri := path.Join(bpTag, strconv.Itoa(tag.ID)) - if _, err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(tag): %w", err) } @@ -91,8 +91,10 @@ func (r *Readarr) DeleteTag(tagID int) error { } func (r *Readarr) DeleteTagContext(ctx context.Context, tagID int) error { + var output interface{} + uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := r.Delete(ctx, uri, nil); err != nil { + if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/shared.go b/shared.go index 5b9ca71..ed2d214 100644 --- a/shared.go +++ b/shared.go @@ -1,7 +1,9 @@ package starr import ( + "crypto/tls" "encoding/json" + "net/http" "strconv" "strings" "time" @@ -40,6 +42,19 @@ func (a App) Lower() string { return strings.ToLower(string(a)) } +// Client returns the default client, and is used if one is not passed in. +func Client(timeout time.Duration, verifySSL bool) *http.Client { + return &http.Client{ + Timeout: timeout, + CheckRedirect: func(r *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifySSL}, //nolint:gosec + }, + } +} + // StatusMessage represents the status of the item. All apps use this. type StatusMessage struct { Title string `json:"title"` diff --git a/sonarr/command.go b/sonarr/command.go index 3771b46..fb7f1ee 100644 --- a/sonarr/command.go +++ b/sonarr/command.go @@ -49,7 +49,7 @@ func (s *Sonarr) GetCommands() ([]*CommandResponse, error) { func (s *Sonarr) GetCommandsContext(ctx context.Context) ([]*CommandResponse, error) { var output []*CommandResponse - if _, err := s.GetInto(ctx, "v3/command", nil, &output); err != nil { + if err := s.GetInto(ctx, "v3/command", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(command): %w", err) } @@ -73,7 +73,7 @@ func (s *Sonarr) SendCommandContext(ctx context.Context, cmd *CommandRequest) (* return nil, fmt.Errorf("json.Marshal(cmd): %w", err) } - if _, err := s.PostInto(ctx, "v3/command", nil, &body, &output); err != nil { + if err := s.PostInto(ctx, "v3/command", nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(command): %w", err) } @@ -92,7 +92,7 @@ func (s *Sonarr) GetCommandStatusContext(ctx context.Context, commandID int64) ( return &output, nil } - _, err := s.GetInto(ctx, "v3/command/"+strconv.FormatInt(commandID, starr.Base10), nil, &output) + err := s.GetInto(ctx, "v3/command/"+strconv.FormatInt(commandID, starr.Base10), nil, &output) if err != nil { return nil, fmt.Errorf("api.Post(command): %w", err) } diff --git a/sonarr/delayprofile.go b/sonarr/delayprofile.go index f676a5b..ef05be4 100644 --- a/sonarr/delayprofile.go +++ b/sonarr/delayprofile.go @@ -33,7 +33,7 @@ func (s *Sonarr) GetDelayProfiles() ([]*DelayProfile, error) { func (s *Sonarr) GetDelayProfilesContext(ctx context.Context) ([]*DelayProfile, error) { var output []*DelayProfile - if _, err := s.GetInto(ctx, bpDelayProfile, nil, &output); err != nil { + if err := s.GetInto(ctx, bpDelayProfile, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(delayProfile): %w", err) } @@ -49,7 +49,7 @@ func (s *Sonarr) GetDelayProfileContext(ctx context.Context, profileID int) (*De var output *DelayProfile uri := path.Join(bpDelayProfile, strconv.Itoa(profileID)) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(delayProfile): %w", err) } @@ -69,7 +69,7 @@ func (s *Sonarr) AddDelayProfileContext(ctx context.Context, profile *DelayProfi return nil, fmt.Errorf("json.Marshal(delayProfile): %w", err) } - if _, err := s.PostInto(ctx, bpDelayProfile, nil, &body, &output); err != nil { + if err := s.PostInto(ctx, bpDelayProfile, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(delayProfile): %w", err) } @@ -90,7 +90,7 @@ func (s *Sonarr) UpdateDelayProfileContext(ctx context.Context, profile *DelayPr } uri := path.Join(bpDelayProfile, strconv.Itoa(int(profile.ID))) - if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(delayProfile): %w", err) } @@ -103,8 +103,10 @@ func (s *Sonarr) DeleteDelayProfile(profileID int) error { } func (s *Sonarr) DeleteDelayProfileContext(ctx context.Context, profileID int) error { + var output interface{} + uri := path.Join(bpDelayProfile, strconv.Itoa(profileID)) - if _, err := s.Delete(ctx, uri, nil); err != nil { + if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(delayProfile): %w", err) } diff --git a/sonarr/episode.go b/sonarr/episode.go index 8ba3131..1e1d7f9 100644 --- a/sonarr/episode.go +++ b/sonarr/episode.go @@ -23,7 +23,7 @@ func (s *Sonarr) GetSeriesEpisodesContext(ctx context.Context, seriesID int64) ( params := make(url.Values) params.Add("seriesId", strconv.FormatInt(seriesID, starr.Base10)) - _, err := s.GetInto(ctx, "v3/episode?seriesId", params, &output) + err := s.GetInto(ctx, "v3/episode?seriesId", params, &output) if err != nil { return nil, fmt.Errorf("api.Get(episode): %w", err) } @@ -47,7 +47,7 @@ func (s *Sonarr) MonitorEpisodeContext(ctx context.Context, episodeIDs []int64, } var output []*Episode - if _, err := s.PutInto(ctx, "v3/episode/monitor", nil, &body, &output); err != nil { + if err := s.PutInto(ctx, "v3/episode/monitor", nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(episode/monitor): %w", err) } diff --git a/sonarr/episodefile.go b/sonarr/episodefile.go index c3c7970..ebdc712 100644 --- a/sonarr/episodefile.go +++ b/sonarr/episodefile.go @@ -30,7 +30,7 @@ func (s *Sonarr) GetEpisodeFilesContext(ctx context.Context, episodeFileIDs ...i params := make(url.Values) params.Add("episodeFileIds", ids) - _, err := s.GetInto(ctx, "v3/episodeFile", params, &output) + err := s.GetInto(ctx, "v3/episodeFile", params, &output) if err != nil { return nil, fmt.Errorf("api.Get(episodeFile): %w", err) } @@ -50,7 +50,7 @@ func (s *Sonarr) GetSeriesEpisodeFilesContext(ctx context.Context, seriesID int6 params := make(url.Values) params.Add("seriesId", strconv.FormatInt(seriesID, starr.Base10)) - _, err := s.GetInto(ctx, "v3/episodeFile", params, &output) + err := s.GetInto(ctx, "v3/episodeFile", params, &output) if err != nil { return nil, fmt.Errorf("api.Get(episodeFile): %w", err) } @@ -79,7 +79,7 @@ func (s *Sonarr) UpdateEpisodeFileQualityContext( var output EpisodeFile - _, err := s.PutInto(ctx, "v3/episodeFile/"+strconv.FormatInt(episodeFileID, starr.Base10), nil, &body, &output) + err := s.PutInto(ctx, "v3/episodeFile/"+strconv.FormatInt(episodeFileID, starr.Base10), nil, &body, &output) if err != nil { return nil, fmt.Errorf("api.Put(episodeFile): %w", err) } @@ -94,8 +94,10 @@ func (s *Sonarr) DeleteEpisodeFile(episodeFileID int64) error { // DeleteEpisodeFileContext deletes an episode file, and takes a context. func (s *Sonarr) DeleteEpisodeFileContext(ctx context.Context, episodeFileID int64) error { - _, err := s.Delete(ctx, "v3/episodeFile/"+strconv.FormatInt(episodeFileID, starr.Base10), nil) - if err != nil { + var output interface{} + + url := "v3/episodeFile/" + strconv.FormatInt(episodeFileID, starr.Base10) + if err := s.DeleteInto(ctx, url, nil, &output); err != nil { return fmt.Errorf("api.Delete(episodeFile): %w", err) } diff --git a/sonarr/history.go b/sonarr/history.go index c07246c..e5e9703 100644 --- a/sonarr/history.go +++ b/sonarr/history.go @@ -57,7 +57,7 @@ func (s *Sonarr) GetHistoryPage(params *starr.Req) (*History, error) { func (s *Sonarr) GetHistoryPageContext(ctx context.Context, params *starr.Req) (*History, error) { var history History - _, err := s.GetInto(ctx, "v3/history", params.Params(), &history) + err := s.GetInto(ctx, "v3/history", params.Params(), &history) if err != nil { return nil, fmt.Errorf("api.Get(history): %w", err) } @@ -75,9 +75,10 @@ func (s *Sonarr) FailContext(ctx context.Context, historyID int64) error { return fmt.Errorf("%w: invalid history ID: %d", starr.ErrRequestError, historyID) } + var output interface{} // Strangely uses a POST without a payload. - _, err := s.Post(ctx, "v3/history/failed/"+strconv.FormatInt(historyID, starr.Base10), nil, nil) - if err != nil { + uri := "v3/history/failed/" + strconv.FormatInt(historyID, starr.Base10) + if err := s.PostInto(ctx, uri, nil, nil, &output); err != nil { return fmt.Errorf("api.Post(history/failed): %w", err) } diff --git a/sonarr/indexer.go b/sonarr/indexer.go index 5570c6e..04176a5 100644 --- a/sonarr/indexer.go +++ b/sonarr/indexer.go @@ -55,7 +55,7 @@ func (s *Sonarr) GetIndexers() ([]*IndexerOutput, error) { func (s *Sonarr) GetIndexersContext(ctx context.Context) ([]*IndexerOutput, error) { var output []*IndexerOutput - if _, err := s.GetInto(ctx, bpIndexer, nil, &output); err != nil { + if err := s.GetInto(ctx, bpIndexer, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(qualityProfile): %w", err) } @@ -71,7 +71,7 @@ func (s *Sonarr) GetIndexerContext(ctx context.Context, indexerID int) (*Indexer var output *IndexerOutput uri := path.Join(bpIndexer, strconv.Itoa(indexerID)) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(indexer): %w", err) } @@ -91,7 +91,7 @@ func (s *Sonarr) AddIndexerContext(ctx context.Context, indexer *IndexerInput) ( return nil, fmt.Errorf("json.Marshal(indexer): %w", err) } - if _, err := s.PostInto(ctx, bpIndexer, nil, &body, &output); err != nil { + if err := s.PostInto(ctx, bpIndexer, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(indexer): %w", err) } @@ -112,7 +112,7 @@ func (s *Sonarr) UpdateIndexerContext(ctx context.Context, indexer *IndexerInput } uri := path.Join(bpIndexer, strconv.Itoa(int(indexer.ID))) - if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(Indexer): %w", err) } @@ -125,8 +125,10 @@ func (s *Sonarr) DeleteIndexer(indexerID int) error { } func (s *Sonarr) DeleteIndexerContext(ctx context.Context, indexerID int) error { + var output interface{} + uri := path.Join(bpIndexer, strconv.Itoa(indexerID)) - if _, err := s.Delete(ctx, uri, nil); err != nil { + if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(Indexer): %w", err) } diff --git a/sonarr/indexerconfig.go b/sonarr/indexerconfig.go index 06422b8..ffda357 100644 --- a/sonarr/indexerconfig.go +++ b/sonarr/indexerconfig.go @@ -27,7 +27,7 @@ func (s *Sonarr) GetIndexerConfig() (*IndexerConfig, error) { func (s *Sonarr) GetIndexerConfigContext(ctx context.Context) (*IndexerConfig, error) { var output *IndexerConfig - if _, err := s.GetInto(ctx, bpIndexerConfig, nil, &output); err != nil { + if err := s.GetInto(ctx, bpIndexerConfig, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(indexerConfig): %w", err) } @@ -48,7 +48,7 @@ func (s *Sonarr) UpdateIndexerConfigContext(ctx context.Context, indexerConfig * } uri := path.Join(bpIndexerConfig, strconv.Itoa(int(indexerConfig.ID))) - if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(indexerConfig): %w", err) } diff --git a/sonarr/languageprofile.go b/sonarr/languageprofile.go index 6d3a246..e893de3 100644 --- a/sonarr/languageprofile.go +++ b/sonarr/languageprofile.go @@ -37,7 +37,7 @@ func (s *Sonarr) GetLanguageProfiles() ([]*LanguageProfile, error) { func (s *Sonarr) GetLanguageProfilesContext(ctx context.Context) ([]*LanguageProfile, error) { var output []*LanguageProfile - if _, err := s.GetInto(ctx, bpLanguageProfile, nil, &output); err != nil { + if err := s.GetInto(ctx, bpLanguageProfile, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(languageProfile): %w", err) } @@ -53,7 +53,7 @@ func (s *Sonarr) GetLanguageProfileContext(ctx context.Context, profileID int) ( var output *LanguageProfile uri := path.Join(bpLanguageProfile, strconv.Itoa(profileID)) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(languageProfile): %w", err) } @@ -73,7 +73,7 @@ func (s *Sonarr) AddLanguageProfileContext(ctx context.Context, profile *Languag return nil, fmt.Errorf("json.Marshal(languageProfile): %w", err) } - if _, err := s.PostInto(ctx, bpLanguageProfile, nil, &body, &output); err != nil { + if err := s.PostInto(ctx, bpLanguageProfile, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(languageProfile): %w", err) } @@ -94,7 +94,7 @@ func (s *Sonarr) UpdateLanguageProfileContext(ctx context.Context, profile *Lang } uri := path.Join(bpLanguageProfile, strconv.Itoa(int(profile.ID))) - if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(languageProfile): %w", err) } @@ -107,8 +107,10 @@ func (s *Sonarr) DeleteLanguageProfile(profileID int) error { } func (s *Sonarr) DeleteLanguageProfileContext(ctx context.Context, profileID int) error { + var output interface{} + uri := path.Join(bpLanguageProfile, strconv.Itoa(profileID)) - if _, err := s.Delete(ctx, uri, nil); err != nil { + if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(languageProfile): %w", err) } diff --git a/sonarr/mediamanagement.go b/sonarr/mediamanagement.go index 3563715..6a6b7d5 100644 --- a/sonarr/mediamanagement.go +++ b/sonarr/mediamanagement.go @@ -40,7 +40,7 @@ func (s *Sonarr) GetMediaManagement() (*MediaManagement, error) { func (s *Sonarr) GetMediaManagementContext(ctx context.Context) (*MediaManagement, error) { var output *MediaManagement - if _, err := s.GetInto(ctx, bpMediaManagement, nil, &output); err != nil { + if err := s.GetInto(ctx, bpMediaManagement, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(mediaManagement): %w", err) } @@ -60,7 +60,7 @@ func (s *Sonarr) UpdateMediaManagementContext(ctx context.Context, mMgt *MediaMa return nil, fmt.Errorf("json.Marshal(mediaManagement): %w", err) } - if _, err := s.PutInto(ctx, bpMediaManagement, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, bpMediaManagement, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(mediaManagement): %w", err) } diff --git a/sonarr/naming.go b/sonarr/naming.go index 8523549..edf4179 100644 --- a/sonarr/naming.go +++ b/sonarr/naming.go @@ -37,7 +37,7 @@ func (s *Sonarr) GetNaming() (*Naming, error) { func (s *Sonarr) GetNamingContext(ctx context.Context) (*Naming, error) { var output *Naming - if _, err := s.GetInto(ctx, bpNaming, nil, &output); err != nil { + if err := s.GetInto(ctx, bpNaming, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(naming): %w", err) } @@ -57,7 +57,7 @@ func (s *Sonarr) UpdateNamingContext(ctx context.Context, naming *Naming) (*Nami return nil, fmt.Errorf("json.Marshal(naming): %w", err) } - if _, err := s.PutInto(ctx, bpNaming, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, bpNaming, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(naming): %w", err) } diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index 3e3efe4..5452647 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -32,7 +32,7 @@ func (s *Sonarr) GetQualityDefinitions() ([]*QualityDefinition, error) { func (s *Sonarr) GetQualityDefinitionsContext(ctx context.Context) ([]*QualityDefinition, error) { var output []*QualityDefinition - if _, err := s.GetInto(ctx, bpQualityDefinition, nil, &output); err != nil { + if err := s.GetInto(ctx, bpQualityDefinition, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) } @@ -48,7 +48,7 @@ func (s *Sonarr) GetQualityDefinitionContext(ctx context.Context, qualityDefinit var output *QualityDefinition uri := path.Join(bpQualityDefinition, strconv.Itoa(qualityDefinitionID)) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) } @@ -72,7 +72,7 @@ func (s *Sonarr) UpdateQualityDefinitionContext( } uri := path.Join(bpQualityDefinition, strconv.Itoa(int(definition.ID))) - if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(qualityDefinition): %w", err) } diff --git a/sonarr/qualityprofile.go b/sonarr/qualityprofile.go index 6f856de..f5cf7a6 100644 --- a/sonarr/qualityprofile.go +++ b/sonarr/qualityprofile.go @@ -31,7 +31,7 @@ func (s *Sonarr) GetQualityProfiles() ([]*QualityProfile, error) { func (s *Sonarr) GetQualityProfilesContext(ctx context.Context) ([]*QualityProfile, error) { var output []*QualityProfile - if _, err := s.GetInto(ctx, bpQualityProfile, nil, &output); err != nil { + if err := s.GetInto(ctx, bpQualityProfile, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } @@ -47,7 +47,7 @@ func (s *Sonarr) GetQualityProfileContext(ctx context.Context, profileID int) (* var output *QualityProfile uri := path.Join(bpQualityProfile, strconv.Itoa(profileID)) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpQualityProfile, err) } @@ -67,7 +67,7 @@ func (s *Sonarr) AddQualityProfileContext(ctx context.Context, profile *QualityP return nil, fmt.Errorf("json.Marshal(%s): %w", bpQualityProfile, err) } - if _, err := s.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { + if err := s.PostInto(ctx, bpQualityProfile, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(%s): %w", bpQualityProfile, err) } @@ -88,7 +88,7 @@ func (s *Sonarr) UpdateQualityProfileContext(ctx context.Context, profile *Quali } uri := path.Join(bpQualityProfile, strconv.Itoa(int(profile.ID))) - if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(%s): %w", bpQualityProfile, err) } @@ -101,8 +101,10 @@ func (s *Sonarr) DeleteQualityProfile(profileID int) error { } func (s *Sonarr) DeleteQualityProfileContext(ctx context.Context, profileID int) error { + var output interface{} + uri := path.Join(bpQualityProfile, strconv.Itoa(profileID)) - if _, err := s.Delete(ctx, uri, nil); err != nil { + if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } diff --git a/sonarr/releaseprofile.go b/sonarr/releaseprofile.go index 65c47c0..914e04b 100644 --- a/sonarr/releaseprofile.go +++ b/sonarr/releaseprofile.go @@ -35,7 +35,7 @@ func (s *Sonarr) GetReleaseProfiles() ([]*ReleaseProfile, error) { func (s *Sonarr) GetReleaseProfilesContext(ctx context.Context) ([]*ReleaseProfile, error) { var output []*ReleaseProfile - if _, err := s.GetInto(ctx, bpReleaseProfile, nil, &output); err != nil { + if err := s.GetInto(ctx, bpReleaseProfile, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpReleaseProfile, err) } @@ -51,7 +51,7 @@ func (s *Sonarr) GetReleaseProfileContext(ctx context.Context, profileID int) (* var output *ReleaseProfile uri := path.Join(bpReleaseProfile, strconv.Itoa(profileID)) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpReleaseProfile, err) } @@ -71,7 +71,7 @@ func (s *Sonarr) AddReleaseProfileContext(ctx context.Context, profile *ReleaseP return nil, fmt.Errorf("json.Marshal(%s): %w", bpReleaseProfile, err) } - if _, err := s.PostInto(ctx, bpReleaseProfile, nil, &body, &output); err != nil { + if err := s.PostInto(ctx, bpReleaseProfile, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(%s): %w", bpReleaseProfile, err) } @@ -92,7 +92,7 @@ func (s *Sonarr) UpdateReleaseProfileContext(ctx context.Context, profile *Relea } uri := path.Join(bpReleaseProfile, strconv.Itoa(int(profile.ID))) - if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(%s): %w", bpReleaseProfile, err) } @@ -105,8 +105,10 @@ func (s *Sonarr) DeleteReleaseProfile(profileID int) error { } func (s *Sonarr) DeleteReleaseProfileContext(ctx context.Context, profileID int) error { + var output interface{} + uri := path.Join(bpReleaseProfile, strconv.Itoa(profileID)) - if _, err := s.Delete(ctx, uri, nil); err != nil { + if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpReleaseProfile, err) } diff --git a/sonarr/rootfolder.go b/sonarr/rootfolder.go index a813250..cc5901f 100644 --- a/sonarr/rootfolder.go +++ b/sonarr/rootfolder.go @@ -31,7 +31,7 @@ func (s *Sonarr) GetRootFolders() ([]*RootFolder, error) { func (s *Sonarr) GetRootFoldersContext(ctx context.Context) ([]*RootFolder, error) { var output []*RootFolder - if _, err := s.GetInto(ctx, bpRootFolder, nil, &output); err != nil { + if err := s.GetInto(ctx, bpRootFolder, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(rootFolder): %w", err) } @@ -47,7 +47,7 @@ func (s *Sonarr) GetRootFolderContext(ctx context.Context, folderID int) (*RootF var output *RootFolder uri := path.Join(bpRootFolder, strconv.Itoa(folderID)) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(rootFolder): %w", err) } @@ -67,7 +67,7 @@ func (s *Sonarr) AddRootFolderContext(ctx context.Context, folder *RootFolder) ( return nil, fmt.Errorf("json.Marshal(rootFolder): %w", err) } - if _, err := s.PostInto(ctx, bpRootFolder, nil, &body, &output); err != nil { + if err := s.PostInto(ctx, bpRootFolder, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(rootFolder): %w", err) } @@ -80,8 +80,10 @@ func (s *Sonarr) DeleteRootFolder(folderID int) error { } func (s *Sonarr) DeleteRootFolderContext(ctx context.Context, folderID int) error { + var output interface{} + uri := path.Join(bpRootFolder, strconv.Itoa(folderID)) - if _, err := s.Delete(ctx, uri, nil); err != nil { + if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(rootFolder): %w", err) } diff --git a/sonarr/seasonpass.go b/sonarr/seasonpass.go index a22516f..69af552 100644 --- a/sonarr/seasonpass.go +++ b/sonarr/seasonpass.go @@ -39,7 +39,10 @@ func (s *Sonarr) UpdateSeasonPassContext(ctx context.Context, seasonPass *Season return fmt.Errorf("json.Marshal(seasonPass): %w", err) } - if _, err := s.Post(ctx, "v3/seasonPass", nil, &body); err != nil { + var output interface{} + + uri := "v3/seasonPass" + if err := s.PostInto(ctx, uri, nil, &body, &output); err != nil { return fmt.Errorf("api.Post(seasonPass): %w", err) } diff --git a/sonarr/series.go b/sonarr/series.go index 5f5e6d9..f2d40c3 100644 --- a/sonarr/series.go +++ b/sonarr/series.go @@ -135,7 +135,7 @@ func (s *Sonarr) GetSeriesContext(ctx context.Context, tvdbID int64) ([]*Series, params.Add("tvdbId", strconv.FormatInt(tvdbID, starr.Base10)) } - if _, err := s.GetInto(ctx, bpSeries, params, &output); err != nil { + if err := s.GetInto(ctx, bpSeries, params, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", bpSeries, err) } @@ -159,7 +159,7 @@ func (s *Sonarr) UpdateSeriesContext(ctx context.Context, series *AddSeriesInput } uri := path.Join(bpSeries, strconv.Itoa(int(series.ID))) - if _, err := s.PutInto(ctx, uri, params, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, params, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(%s): %w", uri, err) } @@ -182,7 +182,7 @@ func (s *Sonarr) AddSeriesContext(ctx context.Context, series *AddSeriesInput) ( return nil, fmt.Errorf("json.Marshal(series): %w", err) } - if _, err := s.PostInto(ctx, bpSeries, params, &body, &output); err != nil { + if err := s.PostInto(ctx, bpSeries, params, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(%s): %w", bpSeries, err) } @@ -198,7 +198,7 @@ func (s *Sonarr) GetSeriesByIDContext(ctx context.Context, seriesID int64) (*Ser var output Series uri := path.Join(bpSeries, strconv.Itoa(int(seriesID))) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", uri, err) } @@ -222,7 +222,7 @@ func (s *Sonarr) GetSeriesLookupContext(ctx context.Context, term string, tvdbID } uri := path.Join(bpSeries, "lookup") - if _, err := s.GetInto(ctx, uri, params, &output); err != nil { + if err := s.GetInto(ctx, uri, params, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", uri, err) } @@ -247,12 +247,14 @@ func (s *Sonarr) DeleteSeries(seriesID int, deleteFiles bool, importExclude bool } func (s *Sonarr) DeleteSeriesContext(ctx context.Context, seriesID int, deleteFiles bool, importExclude bool) error { + var output interface{} + params := make(url.Values) params.Add("deleteFiles", strconv.FormatBool(deleteFiles)) params.Add("addImportListExclusion", strconv.FormatBool(importExclude)) uri := path.Join(bpSeries, strconv.Itoa(seriesID)) - if _, err := s.Delete(ctx, uri, params); err != nil { + if err := s.DeleteInto(ctx, uri, params, &output); err != nil { return fmt.Errorf("api.Delete(%s): %w", uri, err) } diff --git a/sonarr/sonarr.go b/sonarr/sonarr.go index 9eef4c7..a06e1a8 100644 --- a/sonarr/sonarr.go +++ b/sonarr/sonarr.go @@ -2,9 +2,7 @@ package sonarr import ( "context" - "crypto/tls" "fmt" - "net/http" "golift.io/starr" ) @@ -15,8 +13,9 @@ type Sonarr struct { } // Filter values are integers. Given names for ease of discovery. -//nolint:lll // https://github.com/Sonarr/Sonarr/blob/0cb8d93069d6310abd39ee2fe73219e17aa83fe6/src/NzbDrone.Core/History/EpisodeHistory.cs#L34-L41 +// +//nolint:lll const ( FilterUnknown starr.Filtering = iota FilterGrabbed @@ -31,20 +30,7 @@ const ( // New returns a Sonarr object used to interact with the Sonarr API. func New(config *starr.Config) *Sonarr { if config.Client == nil { - //nolint:exhaustivestruct,gosec - config.Client = &http.Client{ - Timeout: config.Timeout.Duration, - CheckRedirect: func(r *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.ValidSSL}, - }, - } - } - - if config.Debugf == nil { - config.Debugf = func(string, ...interface{}) {} + config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) } return &Sonarr{APIer: config} @@ -102,7 +88,7 @@ func (s *Sonarr) GetQueuePageContext(ctx context.Context, params *starr.Req) (*Q params.CheckSet("sortKey", "timeleft") params.CheckSet("includeUnknownSeriesItems", "true") - _, err := s.GetInto(ctx, "v3/queue", params.Params(), &queue) + err := s.GetInto(ctx, "v3/queue", params.Params(), &queue) if err != nil { return nil, fmt.Errorf("api.Get(queue): %w", err) } diff --git a/sonarr/system.go b/sonarr/system.go index dd8874f..d18d475 100644 --- a/sonarr/system.go +++ b/sonarr/system.go @@ -15,7 +15,7 @@ func (s *Sonarr) GetSystemStatus() (*SystemStatus, error) { func (s *Sonarr) GetSystemStatusContext(ctx context.Context) (*SystemStatus, error) { var status SystemStatus - _, err := s.GetInto(ctx, "v3/system/status", nil, &status) + err := s.GetInto(ctx, "v3/system/status", nil, &status) if err != nil { return nil, fmt.Errorf("api.Get(system/status): %w", err) } @@ -32,7 +32,7 @@ func (s *Sonarr) GetBackupFiles() ([]*starr.BackupFile, error) { func (s *Sonarr) GetBackupFilesContext(ctx context.Context) ([]*starr.BackupFile, error) { var output []*starr.BackupFile - if _, err := s.GetInto(ctx, "v3/system/backup", nil, &output); err != nil { + if err := s.GetInto(ctx, "v3/system/backup", nil, &output); err != nil { return nil, fmt.Errorf("api.Get(system/backup): %w", err) } diff --git a/sonarr/tag.go b/sonarr/tag.go index c9c36c2..f0d5eee 100644 --- a/sonarr/tag.go +++ b/sonarr/tag.go @@ -21,7 +21,7 @@ func (s *Sonarr) GetTags() ([]*starr.Tag, error) { func (s *Sonarr) GetTagsContext(ctx context.Context) ([]*starr.Tag, error) { var output []*starr.Tag - if _, err := s.GetInto(ctx, bpTag, nil, &output); err != nil { + if err := s.GetInto(ctx, bpTag, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -37,7 +37,7 @@ func (s *Sonarr) GetTagContext(ctx context.Context, tagID int) (*starr.Tag, erro var output *starr.Tag uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := s.GetInto(ctx, uri, nil, &output); err != nil { + if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(tag): %w", err) } @@ -57,7 +57,7 @@ func (s *Sonarr) AddTagContext(ctx context.Context, tag *starr.Tag) (*starr.Tag, return nil, fmt.Errorf("json.Marshal(tag): %w", err) } - if _, err := s.PostInto(ctx, bpTag, nil, &body, &output); err != nil { + if err := s.PostInto(ctx, bpTag, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Post(tag): %w", err) } @@ -78,7 +78,7 @@ func (s *Sonarr) UpdateTagContext(ctx context.Context, tag *starr.Tag) (*starr.T } uri := path.Join(bpTag, strconv.Itoa(tag.ID)) - if _, err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(tag): %w", err) } @@ -91,8 +91,10 @@ func (s *Sonarr) DeleteTag(tagID int) error { } func (s *Sonarr) DeleteTagContext(ctx context.Context, tagID int) error { + var output interface{} + uri := path.Join(bpTag, strconv.Itoa(tagID)) - if _, err := s.Delete(ctx, uri, nil); err != nil { + if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/starr.go b/starr.go index ad51c4a..b43afec 100644 --- a/starr.go +++ b/starr.go @@ -14,16 +14,11 @@ // covers about 10% of those. You can retrieve things like movies, albums, series // and books. You can retrieve server status, authors, artists and items in queues. // You can also add new media to each application with this library. -// package starr import ( - "context" - "crypto/tls" "fmt" - "io" "net/http" - "strings" "time" ) @@ -56,18 +51,16 @@ var ( // pointer, or you can create your own http.Client before calling subpackage.New(). // MaxBody is only used if a DebugLog is provided, and causes payloads to truncate. type Config struct { - APIKey string `json:"apiKey" toml:"api_key" xml:"api_key" yaml:"apiKey"` - URL string `json:"url" toml:"url" xml:"url" yaml:"url"` - HTTPPass string `json:"httpPass" toml:"http_pass" xml:"http_pass" yaml:"httpPass"` - HTTPUser string `json:"httpUser" toml:"http_user" xml:"http_user" yaml:"httpUser"` - Username string `json:"username" toml:"username" xml:"username" yaml:"username"` - Password string `json:"password" toml:"password" xml:"password" yaml:"password"` - Timeout Duration `json:"timeout" toml:"timeout" xml:"timeout" yaml:"timeout"` - ValidSSL bool `json:"validSsl" toml:"valid_ssl" xml:"valid_ssl" yaml:"validSsl"` - MaxBody int `json:"maxBody" toml:"max_body" xml:"max_body" yaml:"maxBody"` - Client *http.Client `json:"-" toml:"-" xml:"-" yaml:"-"` - Debugf func(string, ...interface{}) `json:"-" toml:"-" xml:"-" yaml:"-"` - cookie bool + APIKey string `json:"apiKey" toml:"api_key" xml:"api_key" yaml:"apiKey"` + URL string `json:"url" toml:"url" xml:"url" yaml:"url"` + HTTPPass string `json:"httpPass" toml:"http_pass" xml:"http_pass" yaml:"httpPass"` + HTTPUser string `json:"httpUser" toml:"http_user" xml:"http_user" yaml:"httpUser"` + Username string `json:"username" toml:"username" xml:"username" yaml:"username"` + Password string `json:"password" toml:"password" xml:"password" yaml:"password"` + Timeout Duration `json:"timeout" toml:"timeout" xml:"timeout" yaml:"timeout"` + ValidSSL bool `json:"validSsl" toml:"valid_ssl" xml:"valid_ssl" yaml:"validSsl"` + Client *http.Client `json:"-" toml:"-" xml:"-" yaml:"-"` + cookie bool // this probably doesn't work right. } // Duration is used to Unmarshal text into a time.Duration value. @@ -88,11 +81,9 @@ func New(apiKey, appURL string, timeout time.Duration) *Config { HTTPPass: "", Username: "", Password: "", - MaxBody: 0, ValidSSL: false, Timeout: Duration{Duration: timeout}, Client: nil, // Let each sub package handle its own client. - Debugf: nil, } } @@ -121,46 +112,3 @@ func (d Duration) String() string { return dur } - -// GetURL attempts to fix the URL for a starr app. -// If the url base is missing it is added; this only checks the Location header. -// You should call this once at startup and update the URL provided. -func (c *Config) GetURL() (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), c.Timeout.Duration) - defer cancel() - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.URL, nil) - if err != nil { - return c.URL, fmt.Errorf("creating request: %w", err) - } - - client := &http.Client{ - CheckRedirect: func(r *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: !c.ValidSSL}, // nolint:gosec - }, - } - - req.Header.Add("X-API-Key", c.APIKey) - - resp, err := client.Do(req) - if err != nil { - return c.URL, fmt.Errorf("making request: %w", err) - } - defer resp.Body.Close() - - _, _ = io.Copy(io.Discard, resp.Body) // read the whole body to avoid memory leaks. - - location, err := resp.Location() - if err != nil { - return c.URL, nil //nolint:nilerr // no location header, no error returned. - } - - if strings.Contains(location.String(), "/login") { - return c.URL, fmt.Errorf("redirected to login page while checking URL %s: %w", c.URL, ErrInvalidAPIKey) - } - - return location.String(), nil -} diff --git a/test_methods.go b/test_methods.go index 01311fb..2f8d334 100644 --- a/test_methods.go +++ b/test_methods.go @@ -1,7 +1,7 @@ package starr import ( - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -42,7 +42,7 @@ func (test *TestMockData) GetMockServer(t *testing.T) *httptest.Server { assert.EqualValues(t, req.Method, test.ExpectedMethod) - body, err := ioutil.ReadAll(req.Body) + body, err := io.ReadAll(req.Body) assert.NoError(t, err) assert.EqualValues(t, test.ExpectedRequest, string(body)) From a5a408e15d452dbeba56c7ce3624a4a23f6a1a1e Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 03:21:33 -0700 Subject: [PATCH 04/26] fix what I broke --- .golangci.yml | 3 +++ .travis.yml | 2 +- http.go | 55 ++++++++++++++++++++++++++++++--------------------- interface.go | 46 +++++++++++++++++++++--------------------- 4 files changed, 59 insertions(+), 47 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 851b38f..fbae98a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,10 +13,13 @@ issues: linters: enable-all: true disable: + # deprecated - maligned - scopelint - interfacer - golint + - ifshort + # unused - tagliatelle - exhaustivestruct - exhaustruct diff --git a/.travis.yml b/.travis.yml index 1104d74..06924fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,6 @@ language: go go: - 1.18.x install: - - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.2 + - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin 1.48.0 script: - make test diff --git a/http.go b/http.go index 9b23324..eaa44ae 100644 --- a/http.go +++ b/http.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/url" + "path" "reflect" "strings" ) @@ -24,31 +25,11 @@ func (c *Config) req( params url.Values, body io.Reader, ) (*http.Response, error) { - req, err := c.newReq(ctx, c.URL+uri, method, params, body) - if err != nil { - return nil, err - } - - resp, err := c.Client.Do(req) - if err != nil { - return nil, fmt.Errorf("httpClient.Do(req): %w", err) - } - - return resp, nil -} - -func (c *Config) newReq( - ctx context.Context, - path string, - method string, - params url.Values, - body io.Reader, -) (*http.Request, error) { if c.Client == nil { // we must have an http client. return nil, ErrNilClient } - req, err := http.NewRequestWithContext(ctx, method, path, body) + req, err := http.NewRequestWithContext(ctx, method, c.setPath(uri), body) if err != nil { return nil, fmt.Errorf("http.NewRequestWithContext(path): %w", err) } @@ -59,7 +40,25 @@ func (c *Config) newReq( req.URL.RawQuery = params.Encode() } - return req, nil + resp, err := c.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("httpClient.Do(req): %w", err) + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + closeResp(resp) + return nil, fmt.Errorf("failed: %v (status: %s): %w", + req.RequestURI, resp.Status, ErrInvalidStatusCode) + } + + return resp, nil +} + +func closeResp(resp *http.Response) { + if resp != nil && resp.Body != nil { + _, _ = io.ReadAll(resp.Body) + resp.Body.Close() + } } // setHeaders sets all our request headers based on method and other data. @@ -82,3 +81,15 @@ 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) } + +// setPath makes sure the path starts with /api and returns the full URL. +func (c *Config) setPath(uriPath string) string { + if strings.HasPrefix(uriPath, API+"/") || + strings.HasPrefix(uriPath, path.Join("/", API)+"/") { + uriPath = path.Join("/", uriPath) + } else { + uriPath = path.Join("/", API, uriPath) + } + + return strings.TrimSuffix(c.URL, "/") + uriPath +} diff --git a/interface.go b/interface.go index 6499f90..274b579 100644 --- a/interface.go +++ b/interface.go @@ -50,9 +50,8 @@ func (c *Config) Login(ctx context.Context) error { if err != nil { return fmt.Errorf("authenticating as user '%s' failed: %w", c.Username, err) } - defer resp.Body.Close() - _, _ = io.Copy(io.Discard, resp.Body) + closeResp(resp) if u, _ := url.Parse(c.URL); strings.Contains(resp.Header.Get("location"), "loginFailed") || len(c.Client.Jar.Cookies(u)) == 0 { @@ -66,33 +65,29 @@ func (c *Config) Login(ctx context.Context) error { // Get makes a GET http request and returns the body. func (c *Config) Get(ctx context.Context, path string, params url.Values) (*http.Response, error) { - resp, err := c.req(ctx, path, http.MethodGet, params, nil) - return resp, err + return c.req(ctx, path, http.MethodGet, params, nil) } // Post makes a POST http request and returns the body. func (c *Config) Post(ctx context.Context, path string, params url.Values, postBody io.Reader) (*http.Response, error) { - resp, err := c.req(ctx, path, http.MethodPost, params, postBody) - return resp, err + return c.req(ctx, path, http.MethodPost, params, postBody) } // Put makes a PUT http request and returns the body. func (c *Config) Put(ctx context.Context, path string, params url.Values, putBody io.Reader) (*http.Response, error) { - resp, err := c.req(ctx, path, http.MethodPut, params, putBody) - return resp, err + return c.req(ctx, path, http.MethodPut, params, putBody) } // Delete makes a DELETE http request and returns the body. func (c *Config) Delete(ctx context.Context, path string, params url.Values) (*http.Response, error) { - resp, err := c.req(ctx, path, http.MethodDelete, params, nil) - return resp, err + return c.req(ctx, path, http.MethodDelete, params, nil) } // GetInto performs an HTTP GET against an API path and // unmarshals the payload into the provided pointer interface. func (c *Config) GetInto(ctx context.Context, path string, params url.Values, output interface{}) error { - resp, err := c.req(ctx, path, http.MethodGet, params, nil) //nolint:bodyclose - return unmarshal(output, resp.Body, err) + resp, err := c.req(ctx, path, http.MethodGet, params, nil) + return decode(output, resp, err) } // PostInto performs an HTTP POST against an API path and @@ -104,8 +99,8 @@ func (c *Config) PostInto( postBody io.Reader, output interface{}, ) error { - resp, err := c.req(ctx, path, http.MethodPost, params, postBody) //nolint:bodyclose - return unmarshal(output, resp.Body, err) + resp, err := c.req(ctx, path, http.MethodPost, params, postBody) + return decode(output, resp, err) } // PutInto performs an HTTP PUT against an API path and @@ -117,27 +112,30 @@ func (c *Config) PutInto( putBody io.Reader, output interface{}, ) error { - resp, err := c.req(ctx, path, http.MethodPut, params, putBody) //nolint:bodyclose - return unmarshal(output, resp.Body, err) + resp, err := c.req(ctx, path, http.MethodPut, params, putBody) + return decode(output, resp, err) } // DeleteInto performs an HTTP DELETE against an API path // and unmarshals the payload into a pointer interface. func (c *Config) DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) error { - resp, err := c.req(ctx, path, http.MethodDelete, params, nil) //nolint:bodyclose - return unmarshal(output, resp.Body, err) + resp, err := c.req(ctx, path, http.MethodDelete, params, nil) + return decode(output, resp, err) } -// unmarshal is an extra procedure to check an error and unmarshal the resp.Body payload. -func unmarshal(output interface{}, data io.ReadCloser, err error) error { - defer data.Close() - +// decode is an extra procedure to check an error and decode the JSON resp.Body payload. +func decode(output interface{}, resp *http.Response, err error) error { if err != nil { return err } else if output == nil { + closeResp(resp) // read the body and close it. return fmt.Errorf("this is a code bug: %w", ErrNilInterface) - } else if err = json.NewDecoder(data).Decode(output); err != nil { - return fmt.Errorf("json parse error: %w", err) + } + + defer resp.Body.Close() + + if err = json.NewDecoder(resp.Body).Decode(output); err != nil { + return fmt.Errorf("decoding json response body: %w", err) } return nil From 5fb473ccc80153652ead07c8b3657cdd6bd626ee Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 03:24:10 -0700 Subject: [PATCH 05/26] add back the test --- http.go | 6 +++--- http_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 http_test.go diff --git a/http.go b/http.go index eaa44ae..bfae960 100644 --- a/http.go +++ b/http.go @@ -29,7 +29,7 @@ func (c *Config) req( return nil, ErrNilClient } - req, err := http.NewRequestWithContext(ctx, method, c.setPath(uri), body) + req, err := http.NewRequestWithContext(ctx, method, c.SetPath(uri), body) if err != nil { return nil, fmt.Errorf("http.NewRequestWithContext(path): %w", err) } @@ -82,8 +82,8 @@ func (c *Config) setHeaders(req *http.Request) { req.Header.Set("X-API-Key", c.APIKey) } -// setPath makes sure the path starts with /api and returns the full URL. -func (c *Config) setPath(uriPath string) string { +// SetPath makes sure the path starts with /api and returns the full URL. +func (c *Config) SetPath(uriPath string) string { if strings.HasPrefix(uriPath, API+"/") || strings.HasPrefix(uriPath, path.Join("/", API)+"/") { uriPath = path.Join("/", uriPath) diff --git a/http_test.go b/http_test.go new file mode 100644 index 0000000..1b63d98 --- /dev/null +++ b/http_test.go @@ -0,0 +1,36 @@ +package starr_test + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "golift.io/starr" +) + +func TestSetPath(t *testing.T) { + t.Parallel() + + api := path.Join("/", starr.API) + cnfg := starr.Config{URL: "http://short.zz"} + + // These must all return the same value... + assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("v1/test")) // no slashes. + assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("v1/test/")) // trailing slash. + assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("/v1/test")) // leading slash. + assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("/v1/test/")) // both slashes. + assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("api/v1/test")) // ...and repeat. + assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("api/v1/test/")) + assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("/api/v1/test")) + assert.Equal(t, cnfg.URL+api+"/v1/test", cnfg.SetPath("/api/v1/test/")) + + // These must all return the same value... + assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("v1/test/another/level")) + assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("v1/test/another/level/")) + assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("/v1/test/another/level")) + assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("/v1/test/another/level/")) + assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("api/v1/test/another/level")) + assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("api/v1/test/another/level/")) + assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("/api/v1/test/another/level")) + assert.Equal(t, cnfg.URL+api+"/v1/test/another/level", cnfg.SetPath("/api/v1/test/another/level/")) +} From 7e3e927de481df043ea9394fa71d69cfb2089acf Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 03:29:23 -0700 Subject: [PATCH 06/26] re-export methods, and update deps --- go.mod | 11 ++++------- go.sum | 14 ++++++++++---- http.go | 10 +++++----- interface.go | 18 +++++++++--------- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 5019edd..002dca7 100644 --- a/go.mod +++ b/go.mod @@ -2,15 +2,12 @@ module golift.io/starr go 1.17 -require ( - golang.org/x/net v0.0.0-20220225172249-27dd8689420f // publicsuffix, cookiejar. - golift.io/datacounter v1.0.3 // Counts bytes read from starr apps. -) +require golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // publicsuffix, cookiejar. // All of this is for the tests. require ( - github.com/stretchr/testify v1.7.0 // assert! - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + github.com/stretchr/testify v1.8.0 // assert! + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1fa3177..45baab5 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,28 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golift.io/datacounter v1.0.3 h1:qDIkyEMimhgWIbPoIZ3mjOP9hiwtO6yBcuyNYjea3l0= -golift.io/datacounter v1.0.3/go.mod h1:79Yf1ucynYvZzVS/hpfrAFt6y/w82FMlOJgh+MBaZvs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/http.go b/http.go index bfae960..08ea3f0 100644 --- a/http.go +++ b/http.go @@ -17,8 +17,8 @@ const API = "api" /* The methods in this file provide assumption-ridden HTTP calls for Starr apps. */ -// req returns the body in io.ReadCloser form (read and close it yourself). -func (c *Config) req( +// Req returns the body in io.ReadCloser form (read and close it yourself). +func (c *Config) Req( ctx context.Context, uri string, method string, @@ -34,7 +34,7 @@ func (c *Config) req( return nil, fmt.Errorf("http.NewRequestWithContext(path): %w", err) } - c.setHeaders(req) + c.SetHeaders(req) if params != nil { req.URL.RawQuery = params.Encode() @@ -61,8 +61,8 @@ func closeResp(resp *http.Response) { } } -// setHeaders sets all our request headers based on method and other data. -func (c *Config) setHeaders(req *http.Request) { +// SetHeaders sets all our request headers based on method and other data. +func (c *Config) SetHeaders(req *http.Request) { // This app allows http auth, in addition to api key (nginx proxy). if auth := c.HTTPUser + ":" + c.HTTPPass; auth != ":" { req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) diff --git a/interface.go b/interface.go index 274b579..4882634 100644 --- a/interface.go +++ b/interface.go @@ -46,7 +46,7 @@ func (c *Config) Login(ctx context.Context) error { post := "username=" + c.Username + "&password=" + c.Password - resp, err := c.req(ctx, "/login", http.MethodPost, nil, bytes.NewBufferString(post)) + resp, err := c.Req(ctx, "/login", http.MethodPost, nil, bytes.NewBufferString(post)) if err != nil { return fmt.Errorf("authenticating as user '%s' failed: %w", c.Username, err) } @@ -65,28 +65,28 @@ func (c *Config) Login(ctx context.Context) error { // Get makes a GET http request and returns the body. func (c *Config) Get(ctx context.Context, path string, params url.Values) (*http.Response, error) { - return c.req(ctx, path, http.MethodGet, params, nil) + return c.Req(ctx, path, http.MethodGet, params, nil) } // Post makes a POST http request and returns the body. func (c *Config) Post(ctx context.Context, path string, params url.Values, postBody io.Reader) (*http.Response, error) { - return c.req(ctx, path, http.MethodPost, params, postBody) + return c.Req(ctx, path, http.MethodPost, params, postBody) } // Put makes a PUT http request and returns the body. func (c *Config) Put(ctx context.Context, path string, params url.Values, putBody io.Reader) (*http.Response, error) { - return c.req(ctx, path, http.MethodPut, params, putBody) + return c.Req(ctx, path, http.MethodPut, params, putBody) } // Delete makes a DELETE http request and returns the body. func (c *Config) Delete(ctx context.Context, path string, params url.Values) (*http.Response, error) { - return c.req(ctx, path, http.MethodDelete, params, nil) + return c.Req(ctx, path, http.MethodDelete, params, nil) } // GetInto performs an HTTP GET against an API path and // unmarshals the payload into the provided pointer interface. func (c *Config) GetInto(ctx context.Context, path string, params url.Values, output interface{}) error { - resp, err := c.req(ctx, path, http.MethodGet, params, nil) + resp, err := c.Req(ctx, path, http.MethodGet, params, nil) return decode(output, resp, err) } @@ -99,7 +99,7 @@ func (c *Config) PostInto( postBody io.Reader, output interface{}, ) error { - resp, err := c.req(ctx, path, http.MethodPost, params, postBody) + resp, err := c.Req(ctx, path, http.MethodPost, params, postBody) return decode(output, resp, err) } @@ -112,14 +112,14 @@ func (c *Config) PutInto( putBody io.Reader, output interface{}, ) error { - resp, err := c.req(ctx, path, http.MethodPut, params, putBody) + resp, err := c.Req(ctx, path, http.MethodPut, params, putBody) return decode(output, resp, err) } // DeleteInto performs an HTTP DELETE against an API path // and unmarshals the payload into a pointer interface. func (c *Config) DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) error { - resp, err := c.req(ctx, path, http.MethodDelete, params, nil) + resp, err := c.Req(ctx, path, http.MethodDelete, params, nil) return decode(output, resp, err) } From 862523420e13ed5b75057209ecf95ec33631d4e7 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 03:35:31 -0700 Subject: [PATCH 07/26] fix linter --- .golangci.yml | 4 ++++ .travis.yml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index fbae98a..c1266f4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,10 @@ linters: - interfacer - golint - ifshort + - deadcode + - nosnakecase + - structcheck + - varcheck # unused - tagliatelle - exhaustivestruct diff --git a/.travis.yml b/.travis.yml index 06924fb..6c05889 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ os: linux dist: bionic language: go go: - - 1.18.x + - 1.19.x install: - - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin 1.48.0 + - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.49.0 script: - make test From 2230da3fefb4ee3c10c0cc1c92095e65f3bfec5b Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 03:53:58 -0700 Subject: [PATCH 08/26] remove timeout and ssl from config, use Client() --- lidarr/lidarr.go | 2 +- prowlarr/prowlarr.go | 2 +- radarr/radarr.go | 2 +- readarr/readarr.go | 2 +- shared.go | 10 +++++++++ sonarr/sonarr.go | 2 +- starr.go | 49 +++----------------------------------------- 7 files changed, 18 insertions(+), 51 deletions(-) diff --git a/lidarr/lidarr.go b/lidarr/lidarr.go index fa38827..9955da4 100644 --- a/lidarr/lidarr.go +++ b/lidarr/lidarr.go @@ -33,7 +33,7 @@ const ( // New returns a Lidarr object used to interact with the Lidarr API. func New(config *starr.Config) *Lidarr { if config.Client == nil { - config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) + config.Client = starr.Client(0, false) } return &Lidarr{APIer: config} diff --git a/prowlarr/prowlarr.go b/prowlarr/prowlarr.go index 505a04f..802c5b3 100644 --- a/prowlarr/prowlarr.go +++ b/prowlarr/prowlarr.go @@ -15,7 +15,7 @@ const APIver = "v1" // New returns a Prowlarr object used to interact with the Prowlarr API. func New(config *starr.Config) *Prowlarr { if config.Client == nil { - config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) + config.Client = starr.Client(0, false) } return &Prowlarr{APIer: config} diff --git a/radarr/radarr.go b/radarr/radarr.go index 4e17d93..3ffcd74 100644 --- a/radarr/radarr.go +++ b/radarr/radarr.go @@ -32,7 +32,7 @@ const ( // New returns a Radarr object used to interact with the Radarr API. func New(config *starr.Config) *Radarr { if config.Client == nil { - config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) + config.Client = starr.Client(0, false) } return &Radarr{APIer: config} diff --git a/readarr/readarr.go b/readarr/readarr.go index e08dce2..dae60bd 100644 --- a/readarr/readarr.go +++ b/readarr/readarr.go @@ -33,7 +33,7 @@ const ( // New returns a Readarr object used to interact with the Readarr API. func New(config *starr.Config) *Readarr { if config.Client == nil { - config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) + config.Client = starr.Client(0, false) } return &Readarr{APIer: config} diff --git a/shared.go b/shared.go index ed2d214..f8a252c 100644 --- a/shared.go +++ b/shared.go @@ -7,6 +7,8 @@ import ( "strconv" "strings" "time" + + "golift.io/starr/debuglog" ) /* This file contains shared structs or constants for all the *arr apps. */ @@ -55,6 +57,14 @@ func Client(timeout time.Duration, verifySSL bool) *http.Client { } } +// ClientWithDebug returns an http client with a debug logger enabled. +func ClientWithDebug(timeout time.Duration, verifySSL bool, logConfig debuglog.LogConfig) *http.Client { + client := Client(timeout, verifySSL) + client.Transport = debuglog.NewLoggingRoundTripper(logConfig, nil) + + return client +} + // StatusMessage represents the status of the item. All apps use this. type StatusMessage struct { Title string `json:"title"` diff --git a/sonarr/sonarr.go b/sonarr/sonarr.go index a06e1a8..f4667be 100644 --- a/sonarr/sonarr.go +++ b/sonarr/sonarr.go @@ -30,7 +30,7 @@ const ( // New returns a Sonarr object used to interact with the Sonarr API. func New(config *starr.Config) *Sonarr { if config.Client == nil { - config.Client = starr.Client(config.Timeout.Duration, config.ValidSSL) + config.Client = starr.Client(0, false) } return &Sonarr{APIer: config} diff --git a/starr.go b/starr.go index b43afec..9d6d6f8 100644 --- a/starr.go +++ b/starr.go @@ -43,13 +43,8 @@ var ( // Config is the data needed to poll Radarr or Sonarr or Lidarr or Readarr. // At a minimum, provide a URL and API Key. -// Set ValidSSL to true if the app has a valid SSL certificate. // HTTPUser and HTTPPass are used for Basic HTTP auth, if enabled (not common). // Username and Password are for non-API paths with native authentication enabled. -// Timeout and ValidSSL are used to create the http Client by sub packages. You -// may set those and call New() in the sub packages to create the http.Client -// pointer, or you can create your own http.Client before calling subpackage.New(). -// MaxBody is only used if a DebugLog is provided, and causes payloads to truncate. type Config struct { APIKey string `json:"apiKey" toml:"api_key" xml:"api_key" yaml:"apiKey"` URL string `json:"url" toml:"url" xml:"url" yaml:"url"` @@ -57,58 +52,20 @@ type Config struct { HTTPUser string `json:"httpUser" toml:"http_user" xml:"http_user" yaml:"httpUser"` Username string `json:"username" toml:"username" xml:"username" yaml:"username"` Password string `json:"password" toml:"password" xml:"password" yaml:"password"` - Timeout Duration `json:"timeout" toml:"timeout" xml:"timeout" yaml:"timeout"` - ValidSSL bool `json:"validSsl" toml:"valid_ssl" xml:"valid_ssl" yaml:"validSsl"` Client *http.Client `json:"-" toml:"-" xml:"-" yaml:"-"` cookie bool // this probably doesn't work right. } -// Duration is used to Unmarshal text into a time.Duration value. -type Duration struct{ time.Duration } - // New returns a *starr.Config pointer. This pointer is safe to modify // further before passing it into one of the arr app New() procedures. -// Set Debugf if you want this library to print debug messages (payloads, etc). func New(apiKey, appURL string, timeout time.Duration) *Config { if timeout == 0 { timeout = DefaultTimeout } return &Config{ - APIKey: apiKey, - URL: appURL, - HTTPUser: "", - HTTPPass: "", - Username: "", - Password: "", - ValidSSL: false, - Timeout: Duration{Duration: timeout}, - Client: nil, // Let each sub package handle its own client. - } -} - -// UnmarshalText parses a duration type from a config file. -func (d *Duration) UnmarshalText(data []byte) error { - var err error - - d.Duration, err = time.ParseDuration(string(data)) - if err != nil { - return fmt.Errorf("invalid duration: %w", err) - } - - return nil -} - -// String returns a Duration as string without trailing zero units. -func (d Duration) String() string { - dur := d.Duration.String() - if len(dur) > 3 && dur[len(dur)-3:] == "m0s" { - dur = dur[:len(dur)-2] + APIKey: apiKey, + URL: appURL, + Client: Client(timeout, false), } - - if len(dur) > 3 && dur[len(dur)-3:] == "h0m" { - dur = dur[:len(dur)-2] - } - - return dur } From b65a7bbe86535488b84b279568cd7c5a047c9758 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 13:10:08 -0700 Subject: [PATCH 09/26] A few fixes/updates --- debuglog/roundtripper.go | 48 +++++++++++++++++++++++----------------- interface.go | 2 +- shared.go | 2 +- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/debuglog/roundtripper.go b/debuglog/roundtripper.go index 4515d30..1838f3d 100644 --- a/debuglog/roundtripper.go +++ b/debuglog/roundtripper.go @@ -9,17 +9,20 @@ import ( "net/http" ) -// LogConfig is the input data for the logger. -type LogConfig struct { - MaxBody int // Limit payloads to this many bytes. 0=unlimited - Debugf func(string, ...interface{}) // This is where logs go. - Caller func(sentBytes, rcvdBytes int) // This can be used for byte counters. +// Config is the input data for the logger. +type Config struct { + MaxBody int // Limit payloads to this many bytes. 0=unlimited + Debugf func(string, ...interface{}) // This is where logs go. + Caller Caller // This can be used for byte counters. } +// Caller is a callback function you may use to collect statistics. +type Caller func(status, method string, sentBytes, rcvdBytes int, err error) + // LoggingRoundTripper allows us to use a datacounter to log http request data. type LoggingRoundTripper struct { next http.RoundTripper // The next Transport to call after logging. - config *LogConfig + config *Config } type fakeCloser struct { @@ -31,11 +34,11 @@ type fakeCloser struct { URL string Status string Header http.Header - *LogConfig + *Config } // NewLoggingRoundTripper returns a round tripper to log requests counts and response sizes. -func NewLoggingRoundTripper(config LogConfig, next http.RoundTripper) *LoggingRoundTripper { +func NewLoggingRoundTripper(config Config, next http.RoundTripper) *LoggingRoundTripper { if next == nil { next = http.DefaultTransport } @@ -61,35 +64,40 @@ func (rt *LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, err } resp, err := rt.next.RoundTrip(req) - if resp == nil || resp.Body == nil { + if err != nil { + if rt.config.Caller != nil { + // Send this report now since .Close() will never be called. + rt.config.Caller("000 Failed", req.Method, buf.Len(), 0, err) + } + return resp, err //nolint:wrapcheck } resp.Body = rt.newFakeCloser(resp, &buf) - return resp, err //nolint:wrapcheck + return resp, nil } func (rt *LoggingRoundTripper) newFakeCloser(resp *http.Response, sent *bytes.Buffer) io.ReadCloser { var buf bytes.Buffer return &fakeCloser{ - CloseFn: resp.Body.Close, - Reader: io.TeeReader(resp.Body, &buf), - Body: &buf, - Method: resp.Request.Method, - Status: resp.Status, - URL: resp.Request.URL.String(), - Sent: sent, - Header: resp.Header, - LogConfig: rt.config, + CloseFn: resp.Body.Close, + Reader: io.TeeReader(resp.Body, &buf), + Body: &buf, + Method: resp.Request.Method, + Status: resp.Status, + URL: resp.Request.URL.String(), + Sent: sent, + Header: resp.Header, + Config: rt.config, } } func (f *fakeCloser) Close() error { sentBytes, rcvdBytes := f.logRequest() if f.Caller != nil { - f.Caller(sentBytes, rcvdBytes) + f.Caller(f.Status, f.Method, sentBytes, rcvdBytes, nil) } return f.CloseFn() diff --git a/interface.go b/interface.go index 4882634..8f9ecba 100644 --- a/interface.go +++ b/interface.go @@ -33,7 +33,7 @@ type APIer interface { // Config must satify the APIer struct. var _ APIer = (*Config)(nil) -// LoginC POSTs to the login form in a Starr app and saves the authentication cookie for future use. +// Login POSTs to the login form in a Starr app and saves the authentication cookie for future use. func (c *Config) Login(ctx context.Context) error { if c.Client.Jar == nil { jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) diff --git a/shared.go b/shared.go index f8a252c..fde1d80 100644 --- a/shared.go +++ b/shared.go @@ -58,7 +58,7 @@ func Client(timeout time.Duration, verifySSL bool) *http.Client { } // ClientWithDebug returns an http client with a debug logger enabled. -func ClientWithDebug(timeout time.Duration, verifySSL bool, logConfig debuglog.LogConfig) *http.Client { +func ClientWithDebug(timeout time.Duration, verifySSL bool, logConfig debuglog.Config) *http.Client { client := Client(timeout, verifySSL) client.Transport = debuglog.NewLoggingRoundTripper(logConfig, nil) From dbc17c657a4da3c6d7e2af3181a822f1764098af Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 13:16:12 -0700 Subject: [PATCH 10/26] quality defs and custom formats: radarr and sonarr --- radarr/qualitydefinition.go | 81 +++++++++++++++++++++++ sonarr/customformat.go | 126 ++++++++++++++++++++++++++++++++++++ sonarr/qualitydefinition.go | 11 ++-- 3 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 radarr/qualitydefinition.go create mode 100644 sonarr/customformat.go diff --git a/radarr/qualitydefinition.go b/radarr/qualitydefinition.go new file mode 100644 index 0000000..d342d60 --- /dev/null +++ b/radarr/qualitydefinition.go @@ -0,0 +1,81 @@ +package radarr + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "path" + "strconv" + + "golift.io/starr" +) + +// QualityDefinition is the /api/v3/qualitydefinition endpoint. +type QualityDefinition struct { + ID int64 `json:"id,omitempty"` + Weight int64 `json:"weight"` // This should not be changed. + MinSize float64 `json:"minSize"` + MaxSize float64 `json:"maxSize"` + PrefSize float64 `json:"preferredSize"` + Title string `json:"title"` + Qualities []*starr.Quality `json:"quality"` +} + +// Define Base Path for Quality Definition calls. +const bpQualityDefinition = APIver + "/qualityDefinition" + +// GetQualityDefinitions returns all configured quality definitions. +func (r *Radarr) GetQualityDefinitions() ([]*QualityDefinition, error) { + return r.GetQualityDefinitionsContext(context.Background()) +} + +func (r *Radarr) GetQualityDefinitionsContext(ctx context.Context) ([]*QualityDefinition, error) { + var output []*QualityDefinition + + if err := r.GetInto(ctx, bpQualityDefinition, nil, &output); err != nil { + return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) + } + + return output, nil +} + +// GetQualityDefinition returns a single quality definition. +func (r *Radarr) GetQualityDefinition(qualityDefinitionID int64) (*QualityDefinition, error) { + return r.GetQualityDefinitionContext(context.Background(), qualityDefinitionID) +} + +func (r *Radarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (*QualityDefinition, error) { + var output *QualityDefinition + + uri := path.Join(bpQualityDefinition, strconv.FormatInt(qdID, starr.Base10)) + if err := r.GetInto(ctx, uri, nil, &output); err != nil { + return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) + } + + return output, nil +} + +// UpdateQualityDefinition updates the quality definition. +func (r *Radarr) UpdateQualityDefinition(definition *QualityDefinition) (*QualityDefinition, error) { + return r.UpdateQualityDefinitionContext(context.Background(), definition) +} + +func (r *Radarr) UpdateQualityDefinitionContext( + ctx context.Context, + definition *QualityDefinition, +) (*QualityDefinition, error) { + var output QualityDefinition + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(definition); err != nil { + return nil, fmt.Errorf("json.Marshal(qualityDefinition): %w", err) + } + + uri := path.Join(bpQualityDefinition, strconv.Itoa(int(definition.ID))) + if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { + return nil, fmt.Errorf("api.Put(qualityDefinition): %w", err) + } + + return &output, nil +} diff --git a/sonarr/customformat.go b/sonarr/customformat.go new file mode 100644 index 0000000..9ead515 --- /dev/null +++ b/sonarr/customformat.go @@ -0,0 +1,126 @@ +package sonarr + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "path" + "strconv" +) + +const bpCustomFormat = APIver + "/customFormat" + +// CustomFormat is the api/customformat endpoint payload. +type CustomFormat struct { + ID int `json:"id"` + Name string `json:"name"` + IncludeCFWhenRenaming bool `json:"includeCustomFormatWhenRenaming"` + Specifications []*CustomFormatSpec `json:"specifications"` +} + +// CustomFormatSpec is part of a CustomFormat. +type CustomFormatSpec struct { + Name string `json:"name"` + Implementation string `json:"implementation"` + Implementationname string `json:"implementationName"` + Infolink string `json:"infoLink"` + Negate bool `json:"negate"` + Required bool `json:"required"` + Fields []*CustomFormatField `json:"fields"` +} + +// CustomFormatField is part of a CustomFormat Specification. +type CustomFormatField struct { + Order int `json:"order"` + Name string `json:"name"` + Label string `json:"label"` + Value interface{} `json:"value"` // should be a string, but sometimes it's a number. + Type string `json:"type"` + Advanced bool `json:"advanced"` +} + +// GetCustomFormats returns all configured Custom Formats. +func (s *Sonarr) GetCustomFormats() ([]*CustomFormat, error) { + return s.GetCustomFormatsContext(context.Background()) +} + +// GetCustomFormatsContext returns all configured Custom Formats. +func (s *Sonarr) GetCustomFormatsContext(ctx context.Context) ([]*CustomFormat, error) { + var output []*CustomFormat + if err := s.GetInto(ctx, bpCustomFormat, nil, &output); err != nil { + return nil, fmt.Errorf("api.Get(%s): %w", bpCustomFormat, err) + } + + return output, nil +} + +// AddCustomFormat creates a new custom format and returns the response (with ID). +func (s *Sonarr) AddCustomFormat(format *CustomFormat) (*CustomFormat, error) { + return s.AddCustomFormatContext(context.Background(), format) +} + +// AddCustomFormatContext creates a new custom format and returns the response (with ID). +func (s *Sonarr) AddCustomFormatContext(ctx context.Context, format *CustomFormat) (*CustomFormat, error) { + var output CustomFormat + + if format == nil { + return &output, nil + } + + format.ID = 0 // ID must be zero when adding. + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(format); err != nil { + return nil, fmt.Errorf("json.Marshal(%s): %w", bpCustomFormat, err) + } + + if err := s.PostInto(ctx, bpCustomFormat, nil, &body, &output); err != nil { + return nil, fmt.Errorf("api.Post(%s): %w", bpCustomFormat, err) + } + + return &output, nil +} + +// UpdateCustomFormat updates an existing custom format and returns the response. +func (s *Sonarr) UpdateCustomFormat(cf *CustomFormat, cfID int) (*CustomFormat, error) { + return s.UpdateCustomFormatContext(context.Background(), cf, cfID) +} + +// UpdateCustomFormatContext updates an existing custom format and returns the response. +func (s *Sonarr) UpdateCustomFormatContext(ctx context.Context, format *CustomFormat, cfID int) (*CustomFormat, error) { + if cfID == 0 { + cfID = format.ID + } + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(format); err != nil { + return nil, fmt.Errorf("json.Marshal(%s): %w", bpCustomFormat, err) + } + + var output CustomFormat + + uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + return nil, fmt.Errorf("api.Put(%s): %w", bpCustomFormat, err) + } + + return &output, nil +} + +// DeleteCustomFormat deletes a custom format. +func (s *Sonarr) DeleteCustomFormat(cfID int) error { + return s.DeleteCustomFormatContext(context.Background(), cfID) +} + +// DeleteCustomFormatContext deletes a custom format. +func (s *Sonarr) DeleteCustomFormatContext(ctx context.Context, cfID int) error { + var output interface{} + + uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) + if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + return fmt.Errorf("api.Delete(%s): %w", bpCustomFormat, err) + } + + return nil +} diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index 5452647..9bba283 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -14,11 +14,12 @@ import ( // QualityDefinition is the /api/v3/qualitydefinition endpoint. type QualityDefinition struct { ID int64 `json:"id,omitempty"` - Weight int64 `json:"weight"` + Weight int64 `json:"weight"` // This should not be changed. MinSize float64 `json:"minSize"` MaxSize float64 `json:"maxSize"` + PrefSize float64 `json:"preferredSize"` // v4 only. Title string `json:"title"` - Qualities []*starr.Quality `json:"items"` + Qualities []*starr.Quality `json:"quality"` } // Define Base Path for Quality Definition calls. @@ -40,14 +41,14 @@ func (s *Sonarr) GetQualityDefinitionsContext(ctx context.Context) ([]*QualityDe } // GetQualityDefinition returns a single quality definition. -func (s *Sonarr) GetQualityDefinition(qualityDefinitionID int) (*QualityDefinition, error) { +func (s *Sonarr) GetQualityDefinition(qualityDefinitionID int64) (*QualityDefinition, error) { return s.GetQualityDefinitionContext(context.Background(), qualityDefinitionID) } -func (s *Sonarr) GetQualityDefinitionContext(ctx context.Context, qualityDefinitionID int) (*QualityDefinition, error) { +func (s *Sonarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (*QualityDefinition, error) { var output *QualityDefinition - uri := path.Join(bpQualityDefinition, strconv.Itoa(qualityDefinitionID)) + uri := path.Join(bpQualityDefinition, strconv.FormatInt(qdID, starr.Base10)) if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) } From 3e41c556ecdccdadfee033b1207897bd33c5a1ba Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 14:00:10 -0700 Subject: [PATCH 11/26] Add more comments --- sonarr/customformat.go | 11 +++++++++++ sonarr/releaseprofile.go | 12 ++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/sonarr/customformat.go b/sonarr/customformat.go index 9ead515..7e240d7 100644 --- a/sonarr/customformat.go +++ b/sonarr/customformat.go @@ -9,9 +9,12 @@ import ( "strconv" ) +/* Custom Formats do not exist in Sonarr v3; this is v4 only. */ + const bpCustomFormat = APIver + "/customFormat" // CustomFormat is the api/customformat endpoint payload. +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. type CustomFormat struct { ID int `json:"id"` Name string `json:"name"` @@ -41,11 +44,13 @@ type CustomFormatField struct { } // GetCustomFormats returns all configured Custom Formats. +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) GetCustomFormats() ([]*CustomFormat, error) { return s.GetCustomFormatsContext(context.Background()) } // GetCustomFormatsContext returns all configured Custom Formats. +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) GetCustomFormatsContext(ctx context.Context) ([]*CustomFormat, error) { var output []*CustomFormat if err := s.GetInto(ctx, bpCustomFormat, nil, &output); err != nil { @@ -56,11 +61,13 @@ func (s *Sonarr) GetCustomFormatsContext(ctx context.Context) ([]*CustomFormat, } // AddCustomFormat creates a new custom format and returns the response (with ID). +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) AddCustomFormat(format *CustomFormat) (*CustomFormat, error) { return s.AddCustomFormatContext(context.Background(), format) } // AddCustomFormatContext creates a new custom format and returns the response (with ID). +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) AddCustomFormatContext(ctx context.Context, format *CustomFormat) (*CustomFormat, error) { var output CustomFormat @@ -83,11 +90,13 @@ func (s *Sonarr) AddCustomFormatContext(ctx context.Context, format *CustomForma } // UpdateCustomFormat updates an existing custom format and returns the response. +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) UpdateCustomFormat(cf *CustomFormat, cfID int) (*CustomFormat, error) { return s.UpdateCustomFormatContext(context.Background(), cf, cfID) } // UpdateCustomFormatContext updates an existing custom format and returns the response. +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) UpdateCustomFormatContext(ctx context.Context, format *CustomFormat, cfID int) (*CustomFormat, error) { if cfID == 0 { cfID = format.ID @@ -109,11 +118,13 @@ func (s *Sonarr) UpdateCustomFormatContext(ctx context.Context, format *CustomFo } // DeleteCustomFormat deletes a custom format. +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) DeleteCustomFormat(cfID int) error { return s.DeleteCustomFormatContext(context.Background(), cfID) } // DeleteCustomFormatContext deletes a custom format. +// This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) DeleteCustomFormatContext(ctx context.Context, cfID int) error { var output interface{} diff --git a/sonarr/releaseprofile.go b/sonarr/releaseprofile.go index 914e04b..1c09ca8 100644 --- a/sonarr/releaseprofile.go +++ b/sonarr/releaseprofile.go @@ -13,15 +13,15 @@ import ( // ReleaseProfile defines a release profile's data from Sonarr. type ReleaseProfile struct { - Enabled bool `json:"enabled"` - IncPrefOnRename bool `json:"includePreferredWhenRenaming"` - ID int64 `json:"id,omitempty"` - IndexerID int64 `json:"indexerId"` Name string `json:"name"` - Tags []int `json:"tags"` + Enabled bool `json:"enabled"` Required []string `json:"required"` Ignored []string `json:"ignored"` - Preferred []*starr.KeyValue `json:"preferred"` + IndexerID int64 `json:"indexerId"` + Tags []int `json:"tags"` + ID int64 `json:"id,omitempty"` + IncPrefOnRename *bool `json:"includePreferredWhenRenaming,omitempty"` // V3 only, removed from v4. + Preferred []*starr.KeyValue `json:"preferred,omitempty"` // V3 only, removed from v4. } // Define Base Path for Release Profile calls. From f6b0ae1f7872a8b4cff46b58b3aa0334ce447082 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 14:20:20 -0700 Subject: [PATCH 12/26] bug fix --- debuglog/roundtripper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debuglog/roundtripper.go b/debuglog/roundtripper.go index 1838f3d..e774cf7 100644 --- a/debuglog/roundtripper.go +++ b/debuglog/roundtripper.go @@ -55,12 +55,12 @@ func NewLoggingRoundTripper(config Config, next http.RoundTripper) *LoggingRound // RoundTrip satisfies the http.RoundTripper interface. func (rt *LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - defer req.Body.Close() - buf := bytes.Buffer{} if req.Body != nil { sent := io.TeeReader(req.Body, &buf) req.Body = io.NopCloser(sent) + + defer req.Body.Close() } resp, err := rt.next.RoundTrip(req) From 9bcb4423da713bf094457d05677e3b8f5973b2ae Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 14:54:08 -0700 Subject: [PATCH 13/26] make Req method work with non-api paths --- http.go | 27 +++++++++++++++++++++++++-- interface.go | 18 +++++++++--------- lidarr/lidarr.go | 4 ++++ prowlarr/prowlarr.go | 4 ++++ radarr/radarr.go | 4 ++++ readarr/readarr.go | 4 ++++ sonarr/sonarr.go | 3 +++ starr.go | 4 ++-- 8 files changed, 55 insertions(+), 13 deletions(-) diff --git a/http.go b/http.go index 08ea3f0..5b1d61c 100644 --- a/http.go +++ b/http.go @@ -17,19 +17,42 @@ const API = "api" /* The methods in this file provide assumption-ridden HTTP calls for Starr apps. */ -// Req returns the body in io.ReadCloser form (read and close it yourself). +// Req makes an authenticated request to a starr application and returns the response. +// Do not forget to read and close the response Body if there is no error. func (c *Config) Req( ctx context.Context, uri string, method string, params url.Values, body io.Reader, +) (*http.Response, error) { + return c.req(ctx, c.URL+uri, method, params, body) +} + +// api is an internal function to call an api path. +func (c *Config) api( + ctx context.Context, + uri string, + method string, + params url.Values, + body io.Reader, +) (*http.Response, error) { + return c.req(ctx, c.SetPath(uri), method, params, body) +} + +// req is our abstraction method for calling a starr application. +func (c *Config) req( + ctx context.Context, + url string, + method string, + params url.Values, + body io.Reader, ) (*http.Response, error) { if c.Client == nil { // we must have an http client. return nil, ErrNilClient } - req, err := http.NewRequestWithContext(ctx, method, c.SetPath(uri), body) + req, err := http.NewRequestWithContext(ctx, method, url, body) if err != nil { return nil, fmt.Errorf("http.NewRequestWithContext(path): %w", err) } diff --git a/interface.go b/interface.go index 8f9ecba..f6e9334 100644 --- a/interface.go +++ b/interface.go @@ -46,7 +46,7 @@ func (c *Config) Login(ctx context.Context) error { post := "username=" + c.Username + "&password=" + c.Password - resp, err := c.Req(ctx, "/login", http.MethodPost, nil, bytes.NewBufferString(post)) + resp, err := c.api(ctx, "/login", http.MethodPost, nil, bytes.NewBufferString(post)) if err != nil { return fmt.Errorf("authenticating as user '%s' failed: %w", c.Username, err) } @@ -65,28 +65,28 @@ func (c *Config) Login(ctx context.Context) error { // Get makes a GET http request and returns the body. func (c *Config) Get(ctx context.Context, path string, params url.Values) (*http.Response, error) { - return c.Req(ctx, path, http.MethodGet, params, nil) + return c.api(ctx, path, http.MethodGet, params, nil) } // Post makes a POST http request and returns the body. func (c *Config) Post(ctx context.Context, path string, params url.Values, postBody io.Reader) (*http.Response, error) { - return c.Req(ctx, path, http.MethodPost, params, postBody) + return c.api(ctx, path, http.MethodPost, params, postBody) } // Put makes a PUT http request and returns the body. func (c *Config) Put(ctx context.Context, path string, params url.Values, putBody io.Reader) (*http.Response, error) { - return c.Req(ctx, path, http.MethodPut, params, putBody) + return c.api(ctx, path, http.MethodPut, params, putBody) } // Delete makes a DELETE http request and returns the body. func (c *Config) Delete(ctx context.Context, path string, params url.Values) (*http.Response, error) { - return c.Req(ctx, path, http.MethodDelete, params, nil) + return c.api(ctx, path, http.MethodDelete, params, nil) } // GetInto performs an HTTP GET against an API path and // unmarshals the payload into the provided pointer interface. func (c *Config) GetInto(ctx context.Context, path string, params url.Values, output interface{}) error { - resp, err := c.Req(ctx, path, http.MethodGet, params, nil) + resp, err := c.api(ctx, path, http.MethodGet, params, nil) return decode(output, resp, err) } @@ -99,7 +99,7 @@ func (c *Config) PostInto( postBody io.Reader, output interface{}, ) error { - resp, err := c.Req(ctx, path, http.MethodPost, params, postBody) + resp, err := c.api(ctx, path, http.MethodPost, params, postBody) return decode(output, resp, err) } @@ -112,14 +112,14 @@ func (c *Config) PutInto( putBody io.Reader, output interface{}, ) error { - resp, err := c.Req(ctx, path, http.MethodPut, params, putBody) + resp, err := c.api(ctx, path, http.MethodPut, params, putBody) return decode(output, resp, err) } // DeleteInto performs an HTTP DELETE against an API path // and unmarshals the payload into a pointer interface. func (c *Config) DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) error { - resp, err := c.Req(ctx, path, http.MethodDelete, params, nil) + resp, err := c.api(ctx, path, http.MethodDelete, params, nil) return decode(output, resp, err) } diff --git a/lidarr/lidarr.go b/lidarr/lidarr.go index 9955da4..6ebd64c 100644 --- a/lidarr/lidarr.go +++ b/lidarr/lidarr.go @@ -1,6 +1,8 @@ package lidarr import ( + "strings" + "golift.io/starr" ) @@ -36,5 +38,7 @@ func New(config *starr.Config) *Lidarr { config.Client = starr.Client(0, false) } + config.URL = strings.TrimSuffix(config.URL, "/") + return &Lidarr{APIer: config} } diff --git a/prowlarr/prowlarr.go b/prowlarr/prowlarr.go index 802c5b3..bec5c43 100644 --- a/prowlarr/prowlarr.go +++ b/prowlarr/prowlarr.go @@ -1,6 +1,8 @@ package prowlarr import ( + "strings" + "golift.io/starr" ) @@ -18,5 +20,7 @@ func New(config *starr.Config) *Prowlarr { config.Client = starr.Client(0, false) } + config.URL = strings.TrimSuffix(config.URL, "/") + return &Prowlarr{APIer: config} } diff --git a/radarr/radarr.go b/radarr/radarr.go index 3ffcd74..084cbf8 100644 --- a/radarr/radarr.go +++ b/radarr/radarr.go @@ -1,6 +1,8 @@ package radarr import ( + "strings" + "golift.io/starr" ) @@ -35,5 +37,7 @@ func New(config *starr.Config) *Radarr { config.Client = starr.Client(0, false) } + config.URL = strings.TrimSuffix(config.URL, "/") + return &Radarr{APIer: config} } diff --git a/readarr/readarr.go b/readarr/readarr.go index dae60bd..bac6b79 100644 --- a/readarr/readarr.go +++ b/readarr/readarr.go @@ -1,6 +1,8 @@ package readarr import ( + "strings" + "golift.io/starr" ) @@ -36,5 +38,7 @@ func New(config *starr.Config) *Readarr { config.Client = starr.Client(0, false) } + config.URL = strings.TrimSuffix(config.URL, "/") + return &Readarr{APIer: config} } diff --git a/sonarr/sonarr.go b/sonarr/sonarr.go index f4667be..fbb9bd7 100644 --- a/sonarr/sonarr.go +++ b/sonarr/sonarr.go @@ -3,6 +3,7 @@ package sonarr import ( "context" "fmt" + "strings" "golift.io/starr" ) @@ -33,6 +34,8 @@ func New(config *starr.Config) *Sonarr { config.Client = starr.Client(0, false) } + config.URL = strings.TrimSuffix(config.URL, "/") + return &Sonarr{APIer: config} } diff --git a/starr.go b/starr.go index 9d6d6f8..60029e3 100644 --- a/starr.go +++ b/starr.go @@ -24,7 +24,7 @@ import ( // Defaults for New(). const ( - DefaultTimeout = 10 * time.Second + DefaultTimeout = 30 * time.Second ) // Errors you may receive from this package. @@ -57,7 +57,7 @@ type Config struct { } // New returns a *starr.Config pointer. This pointer is safe to modify -// further before passing it into one of the arr app New() procedures. +// further before passing it into one of the starr app New() procedures. func New(apiKey, appURL string, timeout time.Duration) *Config { if timeout == 0 { timeout = DefaultTimeout From 63f0b78af59f97dd2eb5913313f77f381c06df8a Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 15:07:43 -0700 Subject: [PATCH 14/26] allow non-api paths --- interface.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/interface.go b/interface.go index f6e9334..65ea8a5 100644 --- a/interface.go +++ b/interface.go @@ -19,6 +19,7 @@ import ( type APIer interface { Login(ctx context.Context) error // Normal data, returns response. Do not use these in starr app methods. + // These methods are generally for non-api paths and will not ensure an /api uri prefix. Get(ctx context.Context, path string, params url.Values) (*http.Response, error) Post(ctx context.Context, path string, params url.Values, postBody io.Reader) (*http.Response, error) Put(ctx context.Context, path string, params url.Values, putBody io.Reader) (*http.Response, error) @@ -65,22 +66,22 @@ func (c *Config) Login(ctx context.Context) error { // Get makes a GET http request and returns the body. func (c *Config) Get(ctx context.Context, path string, params url.Values) (*http.Response, error) { - return c.api(ctx, path, http.MethodGet, params, nil) + return c.Req(ctx, path, http.MethodGet, params, nil) } // Post makes a POST http request and returns the body. func (c *Config) Post(ctx context.Context, path string, params url.Values, postBody io.Reader) (*http.Response, error) { - return c.api(ctx, path, http.MethodPost, params, postBody) + return c.Req(ctx, path, http.MethodPost, params, postBody) } // Put makes a PUT http request and returns the body. func (c *Config) Put(ctx context.Context, path string, params url.Values, putBody io.Reader) (*http.Response, error) { - return c.api(ctx, path, http.MethodPut, params, putBody) + return c.Req(ctx, path, http.MethodPut, params, putBody) } // Delete makes a DELETE http request and returns the body. func (c *Config) Delete(ctx context.Context, path string, params url.Values) (*http.Response, error) { - return c.api(ctx, path, http.MethodDelete, params, nil) + return c.Req(ctx, path, http.MethodDelete, params, nil) } // GetInto performs an HTTP GET against an API path and From 0479d917f457b29347a2c8c0cfc4b6a455bb166c Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 15:45:13 -0700 Subject: [PATCH 15/26] add missing bodies --- debuglog/roundtripper.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/debuglog/roundtripper.go b/debuglog/roundtripper.go index e774cf7..e038436 100644 --- a/debuglog/roundtripper.go +++ b/debuglog/roundtripper.go @@ -105,25 +105,29 @@ func (f *fakeCloser) Close() error { func (f *fakeCloser) logRequest() (int, int) { var ( - headers = "" - rcvd = "" - sent = "" + sent string + rcvd string + headers string rcvdBytes = f.Body.Len() sentBytes = f.Sent.Len() ) - for header, value := range f.Header { - for _, v := range value { - headers += header + ": " + v + "\n" - } + if f.MaxBody > 0 && sentBytes > f.MaxBody { + sent = string(f.Sent.Bytes()[:f.MaxBody]) + " " + } else { + sent = f.Sent.String() } if f.MaxBody > 0 && rcvdBytes > f.MaxBody { rcvd = string(f.Body.Bytes()[:f.MaxBody]) + " " + } else { + rcvd = f.Body.String() } - if f.MaxBody > 0 && sentBytes > f.MaxBody { - sent = string(f.Sent.Bytes()[:f.MaxBody]) + " " + for header, value := range f.Header { + for _, v := range value { + headers += header + ": " + v + "\n" + } } if sentBytes > 0 { From ae96ec4294fa293e9401cc4a312d4598a44886fa Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 16:23:34 -0700 Subject: [PATCH 16/26] restrict debug output to json payloads --- debuglog/roundtripper.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/debuglog/roundtripper.go b/debuglog/roundtripper.go index e038436..9735a7b 100644 --- a/debuglog/roundtripper.go +++ b/debuglog/roundtripper.go @@ -7,6 +7,7 @@ import ( "io" "log" "net/http" + "strings" ) // Config is the input data for the logger. @@ -118,9 +119,12 @@ func (f *fakeCloser) logRequest() (int, int) { sent = f.Sent.String() } - if f.MaxBody > 0 && rcvdBytes > f.MaxBody { + switch ctype := f.Header.Get("content-type"); { + case !strings.Contains(ctype, "json"): + rcvd = "" + case f.MaxBody > 0 && rcvdBytes > f.MaxBody: rcvd = string(f.Body.Bytes()[:f.MaxBody]) + " " - } else { + default: rcvd = f.Body.String() } From 02e1b03bb86e67c1dfa3824de69570ec542ce008 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 2 Sep 2022 19:44:07 -0700 Subject: [PATCH 17/26] fix qualities --- radarr/qualitydefinition.go | 14 +++++++------- sonarr/qualitydefinition.go | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/radarr/qualitydefinition.go b/radarr/qualitydefinition.go index d342d60..aa9253f 100644 --- a/radarr/qualitydefinition.go +++ b/radarr/qualitydefinition.go @@ -13,13 +13,13 @@ import ( // QualityDefinition is the /api/v3/qualitydefinition endpoint. type QualityDefinition struct { - ID int64 `json:"id,omitempty"` - Weight int64 `json:"weight"` // This should not be changed. - MinSize float64 `json:"minSize"` - MaxSize float64 `json:"maxSize"` - PrefSize float64 `json:"preferredSize"` - Title string `json:"title"` - Qualities []*starr.Quality `json:"quality"` + ID int64 `json:"id,omitempty"` + Weight int64 `json:"weight"` // This should not be changed. + MinSize float64 `json:"minSize"` + MaxSize float64 `json:"maxSize"` + PrefSize float64 `json:"preferredSize"` + Title string `json:"title"` + Quality *starr.Quality `json:"quality"` } // Define Base Path for Quality Definition calls. diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index 9bba283..56e2e7f 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -13,13 +13,13 @@ import ( // QualityDefinition is the /api/v3/qualitydefinition endpoint. type QualityDefinition struct { - ID int64 `json:"id,omitempty"` - Weight int64 `json:"weight"` // This should not be changed. - MinSize float64 `json:"minSize"` - MaxSize float64 `json:"maxSize"` - PrefSize float64 `json:"preferredSize"` // v4 only. - Title string `json:"title"` - Qualities []*starr.Quality `json:"quality"` + ID int64 `json:"id,omitempty"` + Weight int64 `json:"weight"` // This should not be changed. + MinSize float64 `json:"minSize"` + MaxSize float64 `json:"maxSize"` + PrefSize float64 `json:"preferredSize"` // v4 only. + Title string `json:"title"` + Quality *starr.Quality `json:"quality"` } // Define Base Path for Quality Definition calls. From 492ee3212209cb936bf9802189bb815386095bf0 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sat, 3 Sep 2022 13:22:29 -0700 Subject: [PATCH 18/26] use bas quality for quality defs --- radarr/qualitydefinition.go | 14 +++++++------- sonarr/qualitydefinition.go | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/radarr/qualitydefinition.go b/radarr/qualitydefinition.go index aa9253f..daa157f 100644 --- a/radarr/qualitydefinition.go +++ b/radarr/qualitydefinition.go @@ -13,13 +13,13 @@ import ( // QualityDefinition is the /api/v3/qualitydefinition endpoint. type QualityDefinition struct { - ID int64 `json:"id,omitempty"` - Weight int64 `json:"weight"` // This should not be changed. - MinSize float64 `json:"minSize"` - MaxSize float64 `json:"maxSize"` - PrefSize float64 `json:"preferredSize"` - Title string `json:"title"` - Quality *starr.Quality `json:"quality"` + ID int64 `json:"id,omitempty"` + Weight int64 `json:"weight"` // This should not be changed. + MinSize float64 `json:"minSize"` + MaxSize float64 `json:"maxSize"` + PrefSize float64 `json:"preferredSize"` + Title string `json:"title"` + Quality *starr.BaseQuality `json:"quality"` } // Define Base Path for Quality Definition calls. diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index 56e2e7f..a7fc00b 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -13,13 +13,13 @@ import ( // QualityDefinition is the /api/v3/qualitydefinition endpoint. type QualityDefinition struct { - ID int64 `json:"id,omitempty"` - Weight int64 `json:"weight"` // This should not be changed. - MinSize float64 `json:"minSize"` - MaxSize float64 `json:"maxSize"` - PrefSize float64 `json:"preferredSize"` // v4 only. - Title string `json:"title"` - Quality *starr.Quality `json:"quality"` + ID int64 `json:"id,omitempty"` + Weight int64 `json:"weight"` // This should not be changed. + MinSize float64 `json:"minSize"` + MaxSize float64 `json:"maxSize"` + PrefSize float64 `json:"preferredSize"` // v4 only. + Title string `json:"title"` + Quality *starr.BaseQuality `json:"quality"` } // Define Base Path for Quality Definition calls. From 9eb56a8fcde9148951b928f1a1e16558ca4338ba Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sat, 3 Sep 2022 14:34:44 -0700 Subject: [PATCH 19/26] update all defs at once --- radarr/qualitydefinition.go | 25 ++++++++++++++----------- sonarr/qualitydefinition.go | 29 ++++++++++++++++------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/radarr/qualitydefinition.go b/radarr/qualitydefinition.go index daa157f..e831b7c 100644 --- a/radarr/qualitydefinition.go +++ b/radarr/qualitydefinition.go @@ -30,6 +30,7 @@ func (r *Radarr) GetQualityDefinitions() ([]*QualityDefinition, error) { return r.GetQualityDefinitionsContext(context.Background()) } +// GetQualityDefinitionsContext returns all configured quality definitions. func (r *Radarr) GetQualityDefinitionsContext(ctx context.Context) ([]*QualityDefinition, error) { var output []*QualityDefinition @@ -45,37 +46,39 @@ func (r *Radarr) GetQualityDefinition(qualityDefinitionID int64) (*QualityDefini return r.GetQualityDefinitionContext(context.Background(), qualityDefinitionID) } +// GetQualityDefinitionContext returns a single quality definition. func (r *Radarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (*QualityDefinition, error) { - var output *QualityDefinition + var output QualityDefinition uri := path.Join(bpQualityDefinition, strconv.FormatInt(qdID, starr.Base10)) if err := r.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) } - return output, nil + return &output, nil } -// UpdateQualityDefinition updates the quality definition. -func (r *Radarr) UpdateQualityDefinition(definition *QualityDefinition) (*QualityDefinition, error) { - return r.UpdateQualityDefinitionContext(context.Background(), definition) +// UpdateQualityDefinitions updates the quality definition. +func (r *Radarr) UpdateQualityDefinitions(definition []*QualityDefinition) ([]*QualityDefinition, error) { + return r.UpdateQualityDefinitionsContext(context.Background(), definition) } -func (r *Radarr) UpdateQualityDefinitionContext( +// UpdateQualityDefinitionsContext updates the quality definition. +func (r *Radarr) UpdateQualityDefinitionsContext( ctx context.Context, - definition *QualityDefinition, -) (*QualityDefinition, error) { - var output QualityDefinition + definition []*QualityDefinition, +) ([]*QualityDefinition, error) { + var output []*QualityDefinition var body bytes.Buffer if err := json.NewEncoder(&body).Encode(definition); err != nil { return nil, fmt.Errorf("json.Marshal(qualityDefinition): %w", err) } - uri := path.Join(bpQualityDefinition, strconv.Itoa(int(definition.ID))) + uri := path.Join(bpQualityDefinition, "update") if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(qualityDefinition): %w", err) } - return &output, nil + return output, nil } diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index a7fc00b..0b827fb 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -30,6 +30,7 @@ func (s *Sonarr) GetQualityDefinitions() ([]*QualityDefinition, error) { return s.GetQualityDefinitionsContext(context.Background()) } +// GetQualityDefinitionsContext returns all configured quality definitions. func (s *Sonarr) GetQualityDefinitionsContext(ctx context.Context) ([]*QualityDefinition, error) { var output []*QualityDefinition @@ -45,37 +46,39 @@ func (s *Sonarr) GetQualityDefinition(qualityDefinitionID int64) (*QualityDefini return s.GetQualityDefinitionContext(context.Background(), qualityDefinitionID) } +// GetQualityDefinitionContext returns a single quality definition. func (s *Sonarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (*QualityDefinition, error) { - var output *QualityDefinition + var output QualityDefinition uri := path.Join(bpQualityDefinition, strconv.FormatInt(qdID, starr.Base10)) if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) } - return output, nil + return &output, nil } -// UpdateQualityDefinition updates the quality definition. -func (s *Sonarr) UpdateQualityDefinition(definition *QualityDefinition) (*QualityDefinition, error) { - return s.UpdateQualityDefinitionContext(context.Background(), definition) +// UpdateQualityDefinition updates all quality definitions. +func (s *Sonarr) UpdateQualityDefinitions(definitions []*QualityDefinition) ([]*QualityDefinition, error) { + return s.UpdateQualityDefinitionsContext(context.Background(), definitions) } -func (s *Sonarr) UpdateQualityDefinitionContext( +// UpdateQualityDefinitionsContext updates all quality definitions. +func (s *Sonarr) UpdateQualityDefinitionsContext( ctx context.Context, - definition *QualityDefinition, -) (*QualityDefinition, error) { - var output QualityDefinition + definitions []*QualityDefinition, +) ([]*QualityDefinition, error) { + var output []*QualityDefinition var body bytes.Buffer - if err := json.NewEncoder(&body).Encode(definition); err != nil { - return nil, fmt.Errorf("json.Marshal(qualityDefinition): %w", err) + if err := json.NewEncoder(&body).Encode(definitions); err != nil { + return nil, fmt.Errorf("json.Marshal(qualityDefinitions): %w", err) } - uri := path.Join(bpQualityDefinition, strconv.Itoa(int(definition.ID))) + uri := path.Join(bpQualityDefinition, "update") if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { return nil, fmt.Errorf("api.Put(qualityDefinition): %w", err) } - return &output, nil + return output, nil } From 52426082f3304af8566e781f14504dc36422d207 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sat, 3 Sep 2022 15:19:04 -0700 Subject: [PATCH 20/26] Add non-200 error data --- http.go | 36 +++++++++++++++++++++++++++++++++--- radarr/qualitydefinition.go | 6 +++--- sonarr/qualitydefinition.go | 6 +++--- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/http.go b/http.go index 5b1d61c..562e5a4 100644 --- a/http.go +++ b/http.go @@ -3,6 +3,7 @@ package starr import ( "context" "encoding/base64" + "encoding/json" "fmt" "io" "net/http" @@ -69,14 +70,43 @@ func (c *Config) req( } if resp.StatusCode < 200 || resp.StatusCode > 299 { - closeResp(resp) - return nil, fmt.Errorf("failed: %v (status: %s): %w", - req.RequestURI, resp.Status, ErrInvalidStatusCode) + return nil, parseNon200(req, resp) } return resp, nil } +// parseNon200 attempts to extract an error message from a non-200 response. +func parseNon200(req *http.Request, resp *http.Response) error { + defer resp.Body.Close() + + var msg struct { + Msg string `json:"message"` + } + + reply, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed: %v (status: %s): %w", + req.RequestURI, resp.Status, ErrInvalidStatusCode) + } + + if err := json.Unmarshal(reply, &msg); err == nil { + return fmt.Errorf("failed: %v (status: %s): %w: %s", + req.RequestURI, resp.Status, ErrInvalidStatusCode, msg.Msg) + } + + const maxSize = 200 // arbitrary max size + + replyStr := string(reply) + if len(replyStr) > maxSize { + return fmt.Errorf("failed: %v (status: %s): %w: %s", + req.RequestURI, resp.Status, ErrInvalidStatusCode, replyStr[:maxSize]) + } + + return fmt.Errorf("failed: %v (status: %s): %w: %s", + req.RequestURI, resp.Status, ErrInvalidStatusCode, replyStr) +} + func closeResp(resp *http.Response) { if resp != nil && resp.Body != nil { _, _ = io.ReadAll(resp.Body) diff --git a/radarr/qualitydefinition.go b/radarr/qualitydefinition.go index e831b7c..3827159 100644 --- a/radarr/qualitydefinition.go +++ b/radarr/qualitydefinition.go @@ -35,7 +35,7 @@ func (r *Radarr) GetQualityDefinitionsContext(ctx context.Context) ([]*QualityDe var output []*QualityDefinition if err := r.GetInto(ctx, bpQualityDefinition, nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpQualityDefinition, err) } return output, nil @@ -52,7 +52,7 @@ func (r *Radarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (* uri := path.Join(bpQualityDefinition, strconv.FormatInt(qdID, starr.Base10)) if err := r.GetInto(ctx, uri, nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", uri, err) } return &output, nil @@ -77,7 +77,7 @@ func (r *Radarr) UpdateQualityDefinitionsContext( uri := path.Join(bpQualityDefinition, "update") if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Put(qualityDefinition): %w", err) + return nil, fmt.Errorf("api.Put(%s): %w", uri, err) } return output, nil diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index 0b827fb..1582f2b 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -35,7 +35,7 @@ func (s *Sonarr) GetQualityDefinitionsContext(ctx context.Context) ([]*QualityDe var output []*QualityDefinition if err := s.GetInto(ctx, bpQualityDefinition, nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", bpQualityDefinition, err) } return output, nil @@ -52,7 +52,7 @@ func (s *Sonarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (* uri := path.Join(bpQualityDefinition, strconv.FormatInt(qdID, starr.Base10)) if err := s.GetInto(ctx, uri, nil, &output); err != nil { - return nil, fmt.Errorf("api.Get(qualityDefinition): %w", err) + return nil, fmt.Errorf("api.Get(%s): %w", uri, err) } return &output, nil @@ -77,7 +77,7 @@ func (s *Sonarr) UpdateQualityDefinitionsContext( uri := path.Join(bpQualityDefinition, "update") if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Put(qualityDefinition): %w", err) + return nil, fmt.Errorf("api.Put(%s): %w", uri, err) } return output, nil From 879511c0c1e842a667f9547f56b6b2b2f5266d94 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 4 Sep 2022 11:47:52 -0700 Subject: [PATCH 21/26] ignore output on delete requests --- interface.go | 11 ++++++----- lidarr/qualityprofile.go | 4 +--- lidarr/tag.go | 4 +--- prowlarr/tag.go | 4 +--- radarr/customformat.go | 4 +--- radarr/exclusions.go | 4 +--- radarr/importlist.go | 4 +--- radarr/qualityprofile.go | 4 +--- radarr/tag.go | 4 +--- readarr/qualityprofile.go | 4 +--- readarr/tag.go | 4 +--- sonarr/delayprofile.go | 4 +--- sonarr/episodefile.go | 4 +--- sonarr/indexer.go | 4 +--- sonarr/languageprofile.go | 4 +--- sonarr/qualityprofile.go | 4 +--- sonarr/releaseprofile.go | 4 +--- sonarr/rootfolder.go | 4 +--- sonarr/series.go | 4 +--- sonarr/tag.go | 4 +--- 20 files changed, 25 insertions(+), 62 deletions(-) diff --git a/interface.go b/interface.go index 65ea8a5..7f0695f 100644 --- a/interface.go +++ b/interface.go @@ -28,7 +28,7 @@ type APIer interface { GetInto(ctx context.Context, path string, params url.Values, output interface{}) error PostInto(ctx context.Context, path string, params url.Values, postBody io.Reader, output interface{}) error PutInto(ctx context.Context, path string, params url.Values, putBody io.Reader, output interface{}) error - DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) error + DeleteAny(ctx context.Context, path string, params url.Values) error } // Config must satify the APIer struct. @@ -117,11 +117,12 @@ func (c *Config) PutInto( return decode(output, resp, err) } -// DeleteInto performs an HTTP DELETE against an API path -// and unmarshals the payload into a pointer interface. -func (c *Config) DeleteInto(ctx context.Context, path string, params url.Values, output interface{}) error { +// DeleteAny performs an HTTP DELETE against an API path, output is ignored. +func (c *Config) DeleteAny(ctx context.Context, path string, params url.Values) error { resp, err := c.api(ctx, path, http.MethodDelete, params, nil) - return decode(output, resp, err) + closeResp(resp) + + return err } // decode is an extra procedure to check an error and decode the JSON resp.Body payload. diff --git a/lidarr/qualityprofile.go b/lidarr/qualityprofile.go index c90aac4..67e6865 100644 --- a/lidarr/qualityprofile.go +++ b/lidarr/qualityprofile.go @@ -88,10 +88,8 @@ func (l *Lidarr) DeleteQualityProfile(profileID int64) error { // DeleteQualityProfileContext deletes a quality profile. func (l *Lidarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { - var output interface{} - uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) - if err := l.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := l.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } diff --git a/lidarr/tag.go b/lidarr/tag.go index 256d767..750a7f3 100644 --- a/lidarr/tag.go +++ b/lidarr/tag.go @@ -91,10 +91,8 @@ func (l *Lidarr) DeleteTag(tagID int) error { } func (l *Lidarr) DeleteTagContext(ctx context.Context, tagID int) error { - var output interface{} - uri := path.Join(bpTag, strconv.Itoa(tagID)) - if err := l.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := l.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/prowlarr/tag.go b/prowlarr/tag.go index 587676c..cad2ba5 100644 --- a/prowlarr/tag.go +++ b/prowlarr/tag.go @@ -91,10 +91,8 @@ func (p *Prowlarr) DeleteTag(tagID int) error { } func (p *Prowlarr) DeleteTagContext(ctx context.Context, tagID int) error { - var output interface{} - uri := path.Join(bpTag, strconv.Itoa(tagID)) - if err := p.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := p.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/radarr/customformat.go b/radarr/customformat.go index ca499b9..9e85870 100644 --- a/radarr/customformat.go +++ b/radarr/customformat.go @@ -115,10 +115,8 @@ func (r *Radarr) DeleteCustomFormat(cfID int) error { // DeleteCustomFormatContext deletes a custom format. func (r *Radarr) DeleteCustomFormatContext(ctx context.Context, cfID int) error { - var output interface{} - uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) - if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := r.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpCustomFormat, err) } diff --git a/radarr/exclusions.go b/radarr/exclusions.go index db498ac..15994c5 100644 --- a/radarr/exclusions.go +++ b/radarr/exclusions.go @@ -45,10 +45,8 @@ func (r *Radarr) DeleteExclusionsContext(ctx context.Context, ids []int64) error var errs string for _, id := range ids { - var output interface{} - uri := "v3/exclusions/" + strconv.FormatInt(id, starr.Base10) - if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := r.DeleteAny(ctx, uri, nil); err != nil { errs += err.Error() + " " } } diff --git a/radarr/importlist.go b/radarr/importlist.go index 145b52e..59b7085 100644 --- a/radarr/importlist.go +++ b/radarr/importlist.go @@ -98,10 +98,8 @@ func (r *Radarr) DeleteImportListContext(ctx context.Context, ids []int64) error var errs string for _, id := range ids { - var output interface{} - uri := "v3/importlist/" + strconv.FormatInt(id, starr.Base10) - if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := r.DeleteAny(ctx, uri, nil); err != nil { errs += fmt.Errorf("api.Delete(importlist): %w", err).Error() + " " } } diff --git a/radarr/qualityprofile.go b/radarr/qualityprofile.go index 59831b4..ec26cb7 100644 --- a/radarr/qualityprofile.go +++ b/radarr/qualityprofile.go @@ -99,10 +99,8 @@ func (r *Radarr) DeleteQualityProfile(profileID int64) error { // DeleteQualityProfileContext deletes a quality profile. func (r *Radarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { - var output interface{} - uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) - if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := r.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } diff --git a/radarr/tag.go b/radarr/tag.go index 4b2d27d..96bd120 100644 --- a/radarr/tag.go +++ b/radarr/tag.go @@ -91,10 +91,8 @@ func (r *Radarr) DeleteTag(tagID int) error { } func (r *Radarr) DeleteTagContext(ctx context.Context, tagID int) error { - var output interface{} - uri := path.Join(bpTag, strconv.Itoa(tagID)) - if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := r.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/readarr/qualityprofile.go b/readarr/qualityprofile.go index 19be744..3ef7684 100644 --- a/readarr/qualityprofile.go +++ b/readarr/qualityprofile.go @@ -85,10 +85,8 @@ func (r *Readarr) DeleteQualityProfile(profileID int64) error { // DeleteQualityProfileContext deletes a quality profile. func (r *Readarr) DeleteQualityProfileContext(ctx context.Context, profileID int64) error { - var output interface{} - uri := path.Join(bpQualityProfile, strconv.FormatInt(profileID, starr.Base10)) - if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := r.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } diff --git a/readarr/tag.go b/readarr/tag.go index c73a5dd..b2b6c52 100644 --- a/readarr/tag.go +++ b/readarr/tag.go @@ -91,10 +91,8 @@ func (r *Readarr) DeleteTag(tagID int) error { } func (r *Readarr) DeleteTagContext(ctx context.Context, tagID int) error { - var output interface{} - uri := path.Join(bpTag, strconv.Itoa(tagID)) - if err := r.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := r.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } diff --git a/sonarr/delayprofile.go b/sonarr/delayprofile.go index ef05be4..fb04a2a 100644 --- a/sonarr/delayprofile.go +++ b/sonarr/delayprofile.go @@ -103,10 +103,8 @@ func (s *Sonarr) DeleteDelayProfile(profileID int) error { } func (s *Sonarr) DeleteDelayProfileContext(ctx context.Context, profileID int) error { - var output interface{} - uri := path.Join(bpDelayProfile, strconv.Itoa(profileID)) - if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := s.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(delayProfile): %w", err) } diff --git a/sonarr/episodefile.go b/sonarr/episodefile.go index ebdc712..d426d5b 100644 --- a/sonarr/episodefile.go +++ b/sonarr/episodefile.go @@ -94,10 +94,8 @@ func (s *Sonarr) DeleteEpisodeFile(episodeFileID int64) error { // DeleteEpisodeFileContext deletes an episode file, and takes a context. func (s *Sonarr) DeleteEpisodeFileContext(ctx context.Context, episodeFileID int64) error { - var output interface{} - url := "v3/episodeFile/" + strconv.FormatInt(episodeFileID, starr.Base10) - if err := s.DeleteInto(ctx, url, nil, &output); err != nil { + if err := s.DeleteAny(ctx, url, nil); err != nil { return fmt.Errorf("api.Delete(episodeFile): %w", err) } diff --git a/sonarr/indexer.go b/sonarr/indexer.go index 04176a5..53a5651 100644 --- a/sonarr/indexer.go +++ b/sonarr/indexer.go @@ -125,10 +125,8 @@ func (s *Sonarr) DeleteIndexer(indexerID int) error { } func (s *Sonarr) DeleteIndexerContext(ctx context.Context, indexerID int) error { - var output interface{} - uri := path.Join(bpIndexer, strconv.Itoa(indexerID)) - if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := s.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(Indexer): %w", err) } diff --git a/sonarr/languageprofile.go b/sonarr/languageprofile.go index e893de3..fc34713 100644 --- a/sonarr/languageprofile.go +++ b/sonarr/languageprofile.go @@ -107,10 +107,8 @@ func (s *Sonarr) DeleteLanguageProfile(profileID int) error { } func (s *Sonarr) DeleteLanguageProfileContext(ctx context.Context, profileID int) error { - var output interface{} - uri := path.Join(bpLanguageProfile, strconv.Itoa(profileID)) - if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := s.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(languageProfile): %w", err) } diff --git a/sonarr/qualityprofile.go b/sonarr/qualityprofile.go index f5cf7a6..02d2ff9 100644 --- a/sonarr/qualityprofile.go +++ b/sonarr/qualityprofile.go @@ -101,10 +101,8 @@ func (s *Sonarr) DeleteQualityProfile(profileID int) error { } func (s *Sonarr) DeleteQualityProfileContext(ctx context.Context, profileID int) error { - var output interface{} - uri := path.Join(bpQualityProfile, strconv.Itoa(profileID)) - if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := s.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpQualityProfile, err) } diff --git a/sonarr/releaseprofile.go b/sonarr/releaseprofile.go index 914e04b..2581e6e 100644 --- a/sonarr/releaseprofile.go +++ b/sonarr/releaseprofile.go @@ -105,10 +105,8 @@ func (s *Sonarr) DeleteReleaseProfile(profileID int) error { } func (s *Sonarr) DeleteReleaseProfileContext(ctx context.Context, profileID int) error { - var output interface{} - uri := path.Join(bpReleaseProfile, strconv.Itoa(profileID)) - if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := s.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpReleaseProfile, err) } diff --git a/sonarr/rootfolder.go b/sonarr/rootfolder.go index cc5901f..51ac28c 100644 --- a/sonarr/rootfolder.go +++ b/sonarr/rootfolder.go @@ -80,10 +80,8 @@ func (s *Sonarr) DeleteRootFolder(folderID int) error { } func (s *Sonarr) DeleteRootFolderContext(ctx context.Context, folderID int) error { - var output interface{} - uri := path.Join(bpRootFolder, strconv.Itoa(folderID)) - if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := s.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(rootFolder): %w", err) } diff --git a/sonarr/series.go b/sonarr/series.go index f2d40c3..ab36934 100644 --- a/sonarr/series.go +++ b/sonarr/series.go @@ -247,14 +247,12 @@ func (s *Sonarr) DeleteSeries(seriesID int, deleteFiles bool, importExclude bool } func (s *Sonarr) DeleteSeriesContext(ctx context.Context, seriesID int, deleteFiles bool, importExclude bool) error { - var output interface{} - params := make(url.Values) params.Add("deleteFiles", strconv.FormatBool(deleteFiles)) params.Add("addImportListExclusion", strconv.FormatBool(importExclude)) uri := path.Join(bpSeries, strconv.Itoa(seriesID)) - if err := s.DeleteInto(ctx, uri, params, &output); err != nil { + if err := s.DeleteAny(ctx, uri, params); err != nil { return fmt.Errorf("api.Delete(%s): %w", uri, err) } diff --git a/sonarr/tag.go b/sonarr/tag.go index f0d5eee..36dffc8 100644 --- a/sonarr/tag.go +++ b/sonarr/tag.go @@ -91,10 +91,8 @@ func (s *Sonarr) DeleteTag(tagID int) error { } func (s *Sonarr) DeleteTagContext(ctx context.Context, tagID int) error { - var output interface{} - uri := path.Join(bpTag, strconv.Itoa(tagID)) - if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := s.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(tag): %w", err) } From 1bde41dc66699451aa895cb6fdf6c51a61a5f74a Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 4 Sep 2022 11:49:02 -0700 Subject: [PATCH 22/26] merge --- sonarr/customformat.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sonarr/customformat.go b/sonarr/customformat.go index 7e240d7..869a090 100644 --- a/sonarr/customformat.go +++ b/sonarr/customformat.go @@ -126,10 +126,8 @@ func (s *Sonarr) DeleteCustomFormat(cfID int) error { // DeleteCustomFormatContext deletes a custom format. // This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) DeleteCustomFormatContext(ctx context.Context, cfID int) error { - var output interface{} - uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) - if err := s.DeleteInto(ctx, uri, nil, &output); err != nil { + if err := s.DeleteAny(ctx, uri, nil); err != nil { return fmt.Errorf("api.Delete(%s): %w", bpCustomFormat, err) } From b121085b6dddca896f9b0e832317b46b195e7ee4 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 4 Sep 2022 18:00:41 -0700 Subject: [PATCH 23/26] fix quality profiles --- radarr/qualityprofile.go | 25 +++++++++---------------- shared.go | 7 +++++++ sonarr/qualityprofile.go | 14 +++++++++----- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/radarr/qualityprofile.go b/radarr/qualityprofile.go index ec26cb7..5ffac74 100644 --- a/radarr/qualityprofile.go +++ b/radarr/qualityprofile.go @@ -15,22 +15,15 @@ const bpQualityProfile = APIver + "/qualityProfile" // QualityProfile is applied to Movies. type QualityProfile struct { - ID int64 `json:"id"` - Name string `json:"name"` - UpgradeAllowed bool `json:"upgradeAllowed"` - Cutoff int64 `json:"cutoff"` - Qualities []*starr.Quality `json:"items"` - MinFormatScore int64 `json:"minFormatScore"` - CutoffFormatScore int64 `json:"cutoffFormatScore"` - FormatItems []*FormatItem `json:"formatItems,omitempty"` - Language *starr.Value `json:"language"` -} - -// FormatItem is part of a QualityProfile. -type FormatItem struct { - Format int `json:"format"` - Name string `json:"name"` - Score int `json:"score"` + ID int64 `json:"id"` + Name string `json:"name"` + UpgradeAllowed bool `json:"upgradeAllowed"` + Cutoff int64 `json:"cutoff"` + Qualities []*starr.Quality `json:"items"` + MinFormatScore int64 `json:"minFormatScore"` + CutoffFormatScore int64 `json:"cutoffFormatScore"` + FormatItems []*starr.FormatItem `json:"formatItems,omitempty"` + Language *starr.Value `json:"language"` } // GetQualityProfiles returns all configured quality profiles. diff --git a/shared.go b/shared.go index fde1d80..83584d2 100644 --- a/shared.go +++ b/shared.go @@ -195,6 +195,13 @@ type PlayTime struct { time.Duration } +// FormatItem is part of a quality definition. +type FormatItem struct { + Format int64 `json:"format"` + Name string `json:"name"` + Score int64 `json:"score"` +} + // UnmarshalJSON parses a run time duration in format hh:mm:ss. func (d *PlayTime) UnmarshalJSON(b []byte) error { d.Original = strings.Trim(string(b), `"'`) diff --git a/sonarr/qualityprofile.go b/sonarr/qualityprofile.go index 02d2ff9..9de0147 100644 --- a/sonarr/qualityprofile.go +++ b/sonarr/qualityprofile.go @@ -13,11 +13,15 @@ import ( // QualityProfile is the /api/v3/qualityprofile endpoint. type QualityProfile struct { - UpgradeAllowed bool `json:"upgradeAllowed"` - ID int64 `json:"id,omitempty"` - Cutoff int64 `json:"cutoff"` - Name string `json:"name"` - Qualities []*starr.Quality `json:"items"` + UpgradeAllowed bool `json:"upgradeAllowed"` + ID int64 `json:"id"` + Cutoff int64 `json:"cutoff"` + Name string `json:"name"` + Qualities []*starr.Quality `json:"items"` + MinFormatScore int64 `json:"minFormatScore"` // v4 only. + CutoffFormatScore int64 `json:"cutoffFormatScore"` // v4 only. + FormatItems []*starr.FormatItem `json:"formatItems,omitempty"` // v4 only. + Language *starr.Value `json:"language,omitempty"` // v4 only. } // Define Base Path for Quality Profile calls. From 142e8a5aa6ad1cd07421b888ede872f4b00dece9 Mon Sep 17 00:00:00 2001 From: David Newhall Date: Sun, 4 Sep 2022 19:45:28 -0700 Subject: [PATCH 24/26] Update shared.go --- shared.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared.go b/shared.go index 83584d2..dcdc056 100644 --- a/shared.go +++ b/shared.go @@ -195,7 +195,7 @@ type PlayTime struct { time.Duration } -// FormatItem is part of a quality definition. +// FormatItem is part of a quality profile. type FormatItem struct { Format int64 `json:"format"` Name string `json:"name"` From f2428827dc328a631e8fa0406d8f11fb1f9ef4b9 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 4 Sep 2022 23:59:18 -0700 Subject: [PATCH 25/26] do not remove updatequalitydefinition --- radarr/qualitydefinition.go | 32 ++++++++++++++++++++++++++++---- sonarr/qualitydefinition.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/radarr/qualitydefinition.go b/radarr/qualitydefinition.go index 3827159..2294930 100644 --- a/radarr/qualitydefinition.go +++ b/radarr/qualitydefinition.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "path" - "strconv" "golift.io/starr" ) @@ -50,7 +49,7 @@ func (r *Radarr) GetQualityDefinition(qualityDefinitionID int64) (*QualityDefini func (r *Radarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (*QualityDefinition, error) { var output QualityDefinition - uri := path.Join(bpQualityDefinition, strconv.FormatInt(qdID, starr.Base10)) + uri := path.Join(bpQualityDefinition, fmt.Sprint(qdID)) if err := r.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", uri, err) } @@ -58,12 +57,37 @@ func (r *Radarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (* return &output, nil } -// UpdateQualityDefinitions updates the quality definition. +// UpdateQualityDefinition updates a quality definition. +func (r *Radarr) UpdateQualityDefinition(definition *QualityDefinition) (*QualityDefinition, error) { + return r.UpdateQualityDefinitionContext(context.Background(), definition) +} + +// UpdateQualityDefinitionContext updates a quality definition. +func (r *Radarr) UpdateQualityDefinitionContext( + ctx context.Context, + definition *QualityDefinition, +) (*QualityDefinition, error) { + var output QualityDefinition + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(definition); err != nil { + return nil, fmt.Errorf("json.Marshal(qualityDefinition): %w", err) + } + + uri := path.Join(bpQualityDefinition, fmt.Sprint(definition.ID)) + if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { + return nil, fmt.Errorf("api.Put(%s): %w", uri, err) + } + + return &output, nil +} + +// UpdateQualityDefinitions updates all quality definitions. func (r *Radarr) UpdateQualityDefinitions(definition []*QualityDefinition) ([]*QualityDefinition, error) { return r.UpdateQualityDefinitionsContext(context.Background(), definition) } -// UpdateQualityDefinitionsContext updates the quality definition. +// UpdateQualityDefinitionsContext updates all quality definitions. func (r *Radarr) UpdateQualityDefinitionsContext( ctx context.Context, definition []*QualityDefinition, diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index 1582f2b..ea72c2d 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "path" - "strconv" "golift.io/starr" ) @@ -50,7 +49,7 @@ func (s *Sonarr) GetQualityDefinition(qualityDefinitionID int64) (*QualityDefini func (s *Sonarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (*QualityDefinition, error) { var output QualityDefinition - uri := path.Join(bpQualityDefinition, strconv.FormatInt(qdID, starr.Base10)) + uri := path.Join(bpQualityDefinition, fmt.Sprint(qdID)) if err := s.GetInto(ctx, uri, nil, &output); err != nil { return nil, fmt.Errorf("api.Get(%s): %w", uri, err) } @@ -58,6 +57,31 @@ func (s *Sonarr) GetQualityDefinitionContext(ctx context.Context, qdID int64) (* return &output, nil } +// UpdateQualityDefinition updates a quality definition. +func (s *Sonarr) UpdateQualityDefinition(definition *QualityDefinition) (*QualityDefinition, error) { + return s.UpdateQualityDefinitionContext(context.Background(), definition) +} + +// UpdateQualityDefinitionContext updates a quality definition. +func (s *Sonarr) UpdateQualityDefinitionContext( + ctx context.Context, + definition *QualityDefinition, +) (*QualityDefinition, error) { + var output QualityDefinition + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(definition); err != nil { + return nil, fmt.Errorf("json.Marshal(qualityDefinition): %w", err) + } + + uri := path.Join(bpQualityDefinition, fmt.Sprint(definition.ID)) + if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { + return nil, fmt.Errorf("api.Put(%s): %w", uri, err) + } + + return &output, nil +} + // UpdateQualityDefinition updates all quality definitions. func (s *Sonarr) UpdateQualityDefinitions(definitions []*QualityDefinition) ([]*QualityDefinition, error) { return s.UpdateQualityDefinitionsContext(context.Background(), definitions) From cd08067dbf0b6cc1a946fde2737d74064da7a254 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 5 Sep 2022 00:06:26 -0700 Subject: [PATCH 26/26] fix up a few more misses --- radarr/customformat.go | 9 ++++----- radarr/qualitydefinition.go | 4 ++-- sonarr/customformat.go | 9 ++++----- sonarr/qualitydefinition.go | 4 ++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/radarr/customformat.go b/radarr/customformat.go index 9e85870..111f114 100644 --- a/radarr/customformat.go +++ b/radarr/customformat.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "path" - "strconv" ) const bpCustomFormat = APIver + "/customFormat" @@ -100,9 +99,9 @@ func (r *Radarr) UpdateCustomFormatContext(ctx context.Context, format *CustomFo var output CustomFormat - uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) + uri := path.Join(bpCustomFormat, fmt.Sprint(cfID)) if err := r.PutInto(ctx, uri, nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Put(%s): %w", bpCustomFormat, err) + return nil, fmt.Errorf("api.Put(%s): %w", uri, err) } return &output, nil @@ -115,9 +114,9 @@ func (r *Radarr) DeleteCustomFormat(cfID int) error { // DeleteCustomFormatContext deletes a custom format. func (r *Radarr) DeleteCustomFormatContext(ctx context.Context, cfID int) error { - uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) + uri := path.Join(bpCustomFormat, fmt.Sprint(cfID)) if err := r.DeleteAny(ctx, uri, nil); err != nil { - return fmt.Errorf("api.Delete(%s): %w", bpCustomFormat, err) + return fmt.Errorf("api.Delete(%s): %w", uri, err) } return nil diff --git a/radarr/qualitydefinition.go b/radarr/qualitydefinition.go index 2294930..45cc968 100644 --- a/radarr/qualitydefinition.go +++ b/radarr/qualitydefinition.go @@ -71,7 +71,7 @@ func (r *Radarr) UpdateQualityDefinitionContext( var body bytes.Buffer if err := json.NewEncoder(&body).Encode(definition); err != nil { - return nil, fmt.Errorf("json.Marshal(qualityDefinition): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpQualityDefinition, err) } uri := path.Join(bpQualityDefinition, fmt.Sprint(definition.ID)) @@ -96,7 +96,7 @@ func (r *Radarr) UpdateQualityDefinitionsContext( var body bytes.Buffer if err := json.NewEncoder(&body).Encode(definition); err != nil { - return nil, fmt.Errorf("json.Marshal(qualityDefinition): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpQualityDefinition, err) } uri := path.Join(bpQualityDefinition, "update") diff --git a/sonarr/customformat.go b/sonarr/customformat.go index 869a090..d7c5f51 100644 --- a/sonarr/customformat.go +++ b/sonarr/customformat.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "path" - "strconv" ) /* Custom Formats do not exist in Sonarr v3; this is v4 only. */ @@ -109,9 +108,9 @@ func (s *Sonarr) UpdateCustomFormatContext(ctx context.Context, format *CustomFo var output CustomFormat - uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) + uri := path.Join(bpCustomFormat, fmt.Sprint(cfID)) if err := s.PutInto(ctx, uri, nil, &body, &output); err != nil { - return nil, fmt.Errorf("api.Put(%s): %w", bpCustomFormat, err) + return nil, fmt.Errorf("api.Put(%s): %w", uri, err) } return &output, nil @@ -126,9 +125,9 @@ func (s *Sonarr) DeleteCustomFormat(cfID int) error { // DeleteCustomFormatContext deletes a custom format. // This data and these endpoints do not exist in Sonarr v3; this is v4 only. func (s *Sonarr) DeleteCustomFormatContext(ctx context.Context, cfID int) error { - uri := path.Join(bpCustomFormat, strconv.Itoa(cfID)) + uri := path.Join(bpCustomFormat, fmt.Sprint(cfID)) if err := s.DeleteAny(ctx, uri, nil); err != nil { - return fmt.Errorf("api.Delete(%s): %w", bpCustomFormat, err) + return fmt.Errorf("api.Delete(%s): %w", uri, err) } return nil diff --git a/sonarr/qualitydefinition.go b/sonarr/qualitydefinition.go index ea72c2d..3880085 100644 --- a/sonarr/qualitydefinition.go +++ b/sonarr/qualitydefinition.go @@ -71,7 +71,7 @@ func (s *Sonarr) UpdateQualityDefinitionContext( var body bytes.Buffer if err := json.NewEncoder(&body).Encode(definition); err != nil { - return nil, fmt.Errorf("json.Marshal(qualityDefinition): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpQualityDefinition, err) } uri := path.Join(bpQualityDefinition, fmt.Sprint(definition.ID)) @@ -96,7 +96,7 @@ func (s *Sonarr) UpdateQualityDefinitionsContext( var body bytes.Buffer if err := json.NewEncoder(&body).Encode(definitions); err != nil { - return nil, fmt.Errorf("json.Marshal(qualityDefinitions): %w", err) + return nil, fmt.Errorf("json.Marshal(%s): %w", bpQualityDefinition, err) } uri := path.Join(bpQualityDefinition, "update")