mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(lists): integrate Omegabrr (#1885)
* feat(lists): integrate Omegabrr * feat(lists): add missing lists index * feat(lists): add db repo * feat(lists): add db migrations * feat(lists): labels * feat(lists): url lists and more arrs * fix(lists): db migrations client_id wrong type * fix(lists): db fields * feat(lists): create list form wip * feat(lists): show in list and create * feat(lists): update and delete * feat(lists): trigger via webhook * feat(lists): add webhook handler * fix(arr): encode json to pointer * feat(lists): rename endpoint to lists * feat(lists): fetch tags from arr * feat(lists): process plaintext lists * feat(lists): add background refresh job * run every 6th hour with a random start delay between 1-35 seconds * feat(lists): refresh on save and improve logging * feat(lists): cast arr client to pointer * feat(lists): improve error handling * feat(lists): reset shows field with match release * feat(lists): filter opts all lists * feat(lists): trigger on update if enabled * feat(lists): update option for lists * feat(lists): show connected filters in list * feat(lists): missing listSvc dep * feat(lists): cleanup * feat(lists): typo arr list * feat(lists): radarr include original * feat(lists): rename ExcludeAlternateTitle to IncludeAlternateTitle * fix(lists): arr client type conversion to pointer * fix(actions): only log panic recover if err not nil * feat(lists): show spinner on save * feat(lists): show icon in filters list * feat(lists): change icon color in filters list * feat(lists): delete relations on filter delete
This commit is contained in:
parent
b68ae334ca
commit
221bc35371
77 changed files with 5025 additions and 254 deletions
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error) {
|
||||
func (c *Client) get(ctx context.Context, endpoint string) (int, []byte, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -26,7 +26,7 @@ func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error)
|
|||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "lidarr client request error : %v", reqUrl)
|
||||
return 0, nil, errors.Wrap(err, "lidarr Client request error : %v", reqUrl)
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
|
@ -54,7 +54,47 @@ func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error)
|
|||
return resp.StatusCode, buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*http.Response, error) {
|
||||
func (c *Client) getJSON(ctx context.Context, endpoint string, params url.Values, data any) error {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, "/api/v1/", endpoint)
|
||||
reqUrl := u.String()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
req.SetBasicAuth(c.config.Username, c.config.Password)
|
||||
}
|
||||
|
||||
c.setHeaders(req)
|
||||
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "lidarr.http.Do(req): %+v", req)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.Body == nil {
|
||||
return errors.New("response body is nil")
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) post(ctx context.Context, endpoint string, data interface{}) (*http.Response, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -65,12 +105,12 @@ func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*
|
|||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "lidarr client could not marshal data: %v", reqUrl)
|
||||
return nil, errors.Wrap(err, "lidarr Client could not marshal data: %v", reqUrl)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "lidarr client request error: %v", reqUrl)
|
||||
return nil, errors.Wrap(err, "lidarr Client request error: %v", reqUrl)
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
|
@ -83,7 +123,7 @@ func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*
|
|||
|
||||
res, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "lidarr client request error: %v", reqUrl)
|
||||
return res, errors.Wrap(err, "lidarr Client request error: %v", reqUrl)
|
||||
}
|
||||
|
||||
// validate response
|
||||
|
@ -97,7 +137,7 @@ func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (c *client) postBody(ctx context.Context, endpoint string, data interface{}) (int, []byte, error) {
|
||||
func (c *Client) postBody(ctx context.Context, endpoint string, data interface{}) (int, []byte, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -108,12 +148,12 @@ func (c *client) postBody(ctx context.Context, endpoint string, data interface{}
|
|||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "lidarr client could not marshal data: %v", reqUrl)
|
||||
return 0, nil, errors.Wrap(err, "lidarr Client could not marshal data: %v", reqUrl)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "lidarr client request error: %v", reqUrl)
|
||||
return 0, nil, errors.Wrap(err, "lidarr Client request error: %v", reqUrl)
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
|
@ -147,7 +187,7 @@ func (c *client) postBody(ctx context.Context, endpoint string, data interface{}
|
|||
return resp.StatusCode, buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) setHeaders(req *http.Request) {
|
||||
func (c *Client) setHeaders(req *http.Request) {
|
||||
if req.Body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
|
@ -6,10 +6,11 @@ package lidarr
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -29,26 +30,26 @@ type Config struct {
|
|||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
type ClientInterface interface {
|
||||
Test(ctx context.Context) (*SystemStatusResponse, error)
|
||||
Push(ctx context.Context, release Release) ([]string, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
type Client struct {
|
||||
config Config
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
// New create new lidarr client
|
||||
func New(config Config) Client {
|
||||
// New create new lidarr Client
|
||||
func New(config Config) *Client {
|
||||
httpClient := &http.Client{
|
||||
Timeout: time.Second * 120,
|
||||
Transport: sharedhttp.Transport,
|
||||
}
|
||||
|
||||
c := &client{
|
||||
c := &Client{
|
||||
config: config,
|
||||
http: httpClient,
|
||||
Log: log.New(io.Discard, "", log.LstdFlags),
|
||||
|
@ -61,47 +62,10 @@ func New(config Config) Client {
|
|||
return c
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
InfoUrl string `json:"infoUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
MagnetUrl string `json:"magnetUrl,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Indexer string `json:"indexer"`
|
||||
DownloadProtocol string `json:"downloadProtocol"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
DownloadClientId int `json:"downloadClientId,omitempty"`
|
||||
DownloadClient string `json:"downloadClient,omitempty"`
|
||||
}
|
||||
|
||||
type PushResponse struct {
|
||||
Approved bool `json:"approved"`
|
||||
Rejected bool `json:"rejected"`
|
||||
TempRejected bool `json:"temporarilyRejected"`
|
||||
Rejections []string `json:"rejections"`
|
||||
}
|
||||
|
||||
type BadRequestResponse struct {
|
||||
PropertyName string `json:"propertyName"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
AttemptedValue string `json:"attemptedValue"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
func (r BadRequestResponse) String() string {
|
||||
return fmt.Sprintf("[%s: %s] %s: %s - got value: %s", r.Severity, r.ErrorCode, r.PropertyName, r.ErrorMessage, r.AttemptedValue)
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||
func (c *Client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||
status, res, err := c.get(ctx, "system/status")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "lidarr client get error")
|
||||
return nil, errors.Wrap(err, "lidarr Client get error")
|
||||
}
|
||||
|
||||
if status == http.StatusUnauthorized {
|
||||
|
@ -113,16 +77,16 @@ func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
|||
response := SystemStatusResponse{}
|
||||
err = json.Unmarshal(res, &response)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "lidarr client error json unmarshal")
|
||||
return nil, errors.Wrap(err, "lidarr Client error json unmarshal")
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||
func (c *Client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||
status, res, err := c.postBody(ctx, "release/push", release)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "lidarr client post error")
|
||||
return nil, errors.Wrap(err, "lidarr Client post error")
|
||||
}
|
||||
|
||||
c.Log.Printf("lidarr release/push response status: %v body: %v", status, string(res))
|
||||
|
@ -143,7 +107,7 @@ func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
|||
|
||||
pushResponse := PushResponse{}
|
||||
if err = json.Unmarshal(res, &pushResponse); err != nil {
|
||||
return nil, errors.Wrap(err, "lidarr client error json unmarshal")
|
||||
return nil, errors.Wrap(err, "lidarr Client error json unmarshal")
|
||||
}
|
||||
|
||||
// log and return if rejected
|
||||
|
@ -156,3 +120,28 @@ func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
|||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetAlbums(ctx context.Context, mbID int64) ([]Album, error) {
|
||||
params := make(url.Values)
|
||||
if mbID != 0 {
|
||||
params.Set("ForeignAlbumId", strconv.FormatInt(mbID, 10))
|
||||
}
|
||||
|
||||
data := make([]Album, 0)
|
||||
err := c.getJSON(ctx, "album", params, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get tags")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetArtistByID(ctx context.Context, artistID int64) (*Artist, error) {
|
||||
var data Artist
|
||||
err := c.getJSON(ctx, "artist/"+strconv.FormatInt(artistID, 10), nil, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get tags")
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
150
pkg/arr/lidarr/types.go
Normal file
150
pkg/arr/lidarr/types.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package lidarr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/arr"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
InfoUrl string `json:"infoUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
MagnetUrl string `json:"magnetUrl,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Indexer string `json:"indexer"`
|
||||
DownloadProtocol string `json:"downloadProtocol"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
DownloadClientId int `json:"downloadClientId,omitempty"`
|
||||
DownloadClient string `json:"downloadClient,omitempty"`
|
||||
}
|
||||
|
||||
type PushResponse struct {
|
||||
Approved bool `json:"approved"`
|
||||
Rejected bool `json:"rejected"`
|
||||
TempRejected bool `json:"temporarilyRejected"`
|
||||
Rejections []string `json:"rejections"`
|
||||
}
|
||||
|
||||
type BadRequestResponse struct {
|
||||
PropertyName string `json:"propertyName"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
AttemptedValue string `json:"attemptedValue"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
func (r BadRequestResponse) String() string {
|
||||
return fmt.Sprintf("[%s: %s] %s: %s - got value: %s", r.Severity, r.ErrorCode, r.PropertyName, r.ErrorMessage, r.AttemptedValue)
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
AlbumCount int `json:"albumCount,omitempty"`
|
||||
TrackFileCount int `json:"trackFileCount"`
|
||||
TrackCount int `json:"trackCount"`
|
||||
TotalTrackCount int `json:"totalTrackCount"`
|
||||
SizeOnDisk int `json:"sizeOnDisk"`
|
||||
PercentOfTracks float64 `json:"percentOfTracks"`
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
ID int64 `json:"id"`
|
||||
Status string `json:"status,omitempty"`
|
||||
LastInfoSync time.Time `json:"lastInfoSync,omitempty"`
|
||||
ArtistName string `json:"artistName,omitempty"`
|
||||
ForeignArtistID string `json:"foreignArtistId,omitempty"`
|
||||
TadbID int64 `json:"tadbId,omitempty"`
|
||||
DiscogsID int64 `json:"discogsId,omitempty"`
|
||||
QualityProfileID int64 `json:"qualityProfileId,omitempty"`
|
||||
MetadataProfileID int64 `json:"metadataProfileId,omitempty"`
|
||||
Overview string `json:"overview,omitempty"`
|
||||
ArtistType string `json:"artistType,omitempty"`
|
||||
Disambiguation string `json:"disambiguation,omitempty"`
|
||||
RootFolderPath string `json:"rootFolderPath,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
CleanName string `json:"cleanName,omitempty"`
|
||||
SortName string `json:"sortName,omitempty"`
|
||||
Links []*arr.Link `json:"links,omitempty"`
|
||||
Images []*arr.Image `json:"images,omitempty"`
|
||||
Genres []string `json:"genres,omitempty"`
|
||||
Tags []int `json:"tags,omitempty"`
|
||||
Added time.Time `json:"added,omitempty"`
|
||||
Ratings *arr.Ratings `json:"ratings,omitempty"`
|
||||
Statistics *Statistics `json:"statistics,omitempty"`
|
||||
LastAlbum *Album `json:"lastAlbum,omitempty"`
|
||||
NextAlbum *Album `json:"nextAlbum,omitempty"`
|
||||
AddOptions *ArtistAddOptions `json:"addOptions,omitempty"`
|
||||
AlbumFolder bool `json:"albumFolder,omitempty"`
|
||||
Monitored bool `json:"monitored"`
|
||||
Ended bool `json:"ended,omitempty"`
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
Overview string `json:"overview"`
|
||||
ArtistID int64 `json:"artistId"`
|
||||
ForeignAlbumID string `json:"foreignAlbumId"`
|
||||
ProfileID int64 `json:"profileId"`
|
||||
Duration int `json:"duration"`
|
||||
AlbumType string `json:"albumType"`
|
||||
SecondaryTypes []interface{} `json:"secondaryTypes"`
|
||||
MediumCount int `json:"mediumCount"`
|
||||
Ratings *arr.Ratings `json:"ratings"`
|
||||
ReleaseDate time.Time `json:"releaseDate"`
|
||||
Releases []*AlbumRelease `json:"releases"`
|
||||
Genres []interface{} `json:"genres"`
|
||||
Media []*Media `json:"media"`
|
||||
Artist *Artist `json:"artist"`
|
||||
Links []*arr.Link `json:"links"`
|
||||
Images []*arr.Image `json:"images"`
|
||||
Statistics *Statistics `json:"statistics"`
|
||||
RemoteCover string `json:"remoteCover,omitempty"`
|
||||
AddOptions *AlbumAddOptions `json:"addOptions,omitempty"`
|
||||
Monitored bool `json:"monitored"`
|
||||
AnyReleaseOk bool `json:"anyReleaseOk"`
|
||||
Grabbed bool `json:"grabbed"`
|
||||
}
|
||||
|
||||
// Release is part of an Album.
|
||||
type AlbumRelease struct {
|
||||
ID int64 `json:"id"`
|
||||
AlbumID int64 `json:"albumId"`
|
||||
ForeignReleaseID string `json:"foreignReleaseId"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Duration int `json:"duration"`
|
||||
TrackCount int `json:"trackCount"`
|
||||
Media []*Media `json:"media"`
|
||||
MediumCount int `json:"mediumCount"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
Country []string `json:"country"`
|
||||
Label []string `json:"label"`
|
||||
Format string `json:"format"`
|
||||
Monitored bool `json:"monitored"`
|
||||
}
|
||||
|
||||
// Media is part of an Album.
|
||||
type Media struct {
|
||||
MediumNumber int64 `json:"mediumNumber"`
|
||||
MediumName string `json:"mediumName"`
|
||||
MediumFormat string `json:"mediumFormat"`
|
||||
}
|
||||
|
||||
// ArtistAddOptions is part of an artist and an album.
|
||||
type ArtistAddOptions struct {
|
||||
Monitor string `json:"monitor,omitempty"`
|
||||
Monitored bool `json:"monitored,omitempty"`
|
||||
SearchForMissingAlbums bool `json:"searchForMissingAlbums,omitempty"`
|
||||
}
|
||||
|
||||
type AlbumAddOptions struct {
|
||||
SearchForNewAlbum bool `json:"searchForNewAlbum,omitempty"`
|
||||
}
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error) {
|
||||
func (c *Client) get(ctx context.Context, endpoint string) (int, []byte, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -54,7 +54,47 @@ func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error)
|
|||
return resp.StatusCode, buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*http.Response, error) {
|
||||
func (c *Client) getJSON(ctx context.Context, endpoint string, params url.Values, data any) error {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, "/api/v3/", endpoint)
|
||||
reqUrl := u.String()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
req.SetBasicAuth(c.config.Username, c.config.Password)
|
||||
}
|
||||
|
||||
c.setHeaders(req)
|
||||
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "radarr.http.Do(req): %+v", req)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.Body == nil {
|
||||
return errors.New("response body is nil")
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) post(ctx context.Context, endpoint string, data interface{}) (*http.Response, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -99,7 +139,7 @@ func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (c *client) postBody(ctx context.Context, endpoint string, data interface{}) (int, []byte, error) {
|
||||
func (c *Client) postBody(ctx context.Context, endpoint string, data interface{}) (int, []byte, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -149,7 +189,7 @@ func (c *client) postBody(ctx context.Context, endpoint string, data interface{}
|
|||
return resp.StatusCode, buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) setHeaders(req *http.Request) {
|
||||
func (c *Client) setHeaders(req *http.Request) {
|
||||
if req.Body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
|
@ -6,13 +6,15 @@ package radarr
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/arr"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
"github.com/autobrr/autobrr/pkg/sharedhttp"
|
||||
)
|
||||
|
@ -29,25 +31,25 @@ type Config struct {
|
|||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
type ClientInterface interface {
|
||||
Test(ctx context.Context) (*SystemStatusResponse, error)
|
||||
Push(ctx context.Context, release Release) ([]string, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
type Client struct {
|
||||
config Config
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
func New(config Config) Client {
|
||||
func New(config Config) *Client {
|
||||
httpClient := &http.Client{
|
||||
Timeout: time.Second * 120,
|
||||
Transport: sharedhttp.Transport,
|
||||
}
|
||||
|
||||
c := &client{
|
||||
c := &Client{
|
||||
config: config,
|
||||
http: httpClient,
|
||||
Log: log.New(io.Discard, "", log.LstdFlags),
|
||||
|
@ -60,44 +62,7 @@ func New(config Config) Client {
|
|||
return c
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
InfoUrl string `json:"infoUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
MagnetUrl string `json:"magnetUrl,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Indexer string `json:"indexer"`
|
||||
DownloadProtocol string `json:"downloadProtocol"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
DownloadClientId int `json:"downloadClientId,omitempty"`
|
||||
DownloadClient string `json:"downloadClient,omitempty"`
|
||||
}
|
||||
|
||||
type PushResponse struct {
|
||||
Approved bool `json:"approved"`
|
||||
Rejected bool `json:"rejected"`
|
||||
TempRejected bool `json:"temporarilyRejected"`
|
||||
Rejections []string `json:"rejections"`
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type BadRequestResponse struct {
|
||||
Severity string `json:"severity"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
PropertyName string `json:"propertyName"`
|
||||
AttemptedValue string `json:"attemptedValue"`
|
||||
}
|
||||
|
||||
func (r *BadRequestResponse) String() string {
|
||||
return fmt.Sprintf("[%s: %s] %s: %s - got value: %s", r.Severity, r.ErrorCode, r.PropertyName, r.ErrorMessage, r.AttemptedValue)
|
||||
}
|
||||
|
||||
func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||
func (c *Client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||
status, res, err := c.get(ctx, "system/status")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "radarr error running test")
|
||||
|
@ -117,7 +82,7 @@ func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
|||
return &response, nil
|
||||
}
|
||||
|
||||
func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||
func (c *Client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||
status, res, err := c.postBody(ctx, "release/push", release)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error push release")
|
||||
|
@ -155,3 +120,28 @@ func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
|||
// success true
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetMovies(ctx context.Context, tmdbID int64) ([]Movie, error) {
|
||||
params := make(url.Values)
|
||||
if tmdbID != 0 {
|
||||
params.Set("tmdbId", strconv.FormatInt(tmdbID, 10))
|
||||
}
|
||||
|
||||
data := make([]Movie, 0)
|
||||
err := c.getJSON(ctx, "movie", params, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get tags")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetTags(ctx context.Context) ([]*arr.Tag, error) {
|
||||
data := make([]*arr.Tag, 0)
|
||||
err := c.getJSON(ctx, "tag", nil, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get tags")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
135
pkg/arr/radarr/types.go
Normal file
135
pkg/arr/radarr/types.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package radarr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/arr"
|
||||
)
|
||||
|
||||
type Movie struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
MinimumAvailability string `json:"minimumAvailability,omitempty"`
|
||||
QualityProfileID int64 `json:"qualityProfileId,omitempty"`
|
||||
TmdbID int64 `json:"tmdbId,omitempty"`
|
||||
OriginalTitle string `json:"originalTitle,omitempty"`
|
||||
AlternateTitles []*AlternativeTitle `json:"alternateTitles,omitempty"`
|
||||
SecondaryYearSourceID int `json:"secondaryYearSourceId,omitempty"`
|
||||
SortTitle string `json:"sortTitle,omitempty"`
|
||||
SizeOnDisk int64 `json:"sizeOnDisk,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Overview string `json:"overview,omitempty"`
|
||||
InCinemas time.Time `json:"inCinemas,omitempty"`
|
||||
PhysicalRelease time.Time `json:"physicalRelease,omitempty"`
|
||||
DigitalRelease time.Time `json:"digitalRelease,omitempty"`
|
||||
Images []*arr.Image `json:"images,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
Year int `json:"year,omitempty"`
|
||||
YouTubeTrailerID string `json:"youTubeTrailerId,omitempty"`
|
||||
Studio string `json:"studio,omitempty"`
|
||||
FolderName string `json:"folderName,omitempty"`
|
||||
Runtime int `json:"runtime,omitempty"`
|
||||
CleanTitle string `json:"cleanTitle,omitempty"`
|
||||
ImdbID string `json:"imdbId,omitempty"`
|
||||
TitleSlug string `json:"titleSlug,omitempty"`
|
||||
Certification string `json:"certification,omitempty"`
|
||||
Genres []string `json:"genres,omitempty"`
|
||||
Tags []int `json:"tags,omitempty"`
|
||||
Added time.Time `json:"added,omitempty"`
|
||||
Ratings *arr.Ratings `json:"ratings,omitempty"`
|
||||
MovieFile *MovieFile `json:"movieFile,omitempty"`
|
||||
Collection *Collection `json:"collection,omitempty"`
|
||||
HasFile bool `json:"hasFile,omitempty"`
|
||||
IsAvailable bool `json:"isAvailable,omitempty"`
|
||||
Monitored bool `json:"monitored"`
|
||||
}
|
||||
|
||||
type AlternativeTitle struct {
|
||||
MovieID int `json:"movieId"`
|
||||
Title string `json:"title"`
|
||||
SourceType string `json:"sourceType"`
|
||||
SourceID int `json:"sourceId"`
|
||||
Votes int `json:"votes"`
|
||||
VoteCount int `json:"voteCount"`
|
||||
Language *arr.Value `json:"language"`
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
type MovieFile struct {
|
||||
ID int64 `json:"id"`
|
||||
MovieID int64 `json:"movieId"`
|
||||
RelativePath string `json:"relativePath"`
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
DateAdded time.Time `json:"dateAdded"`
|
||||
SceneName string `json:"sceneName"`
|
||||
IndexerFlags int64 `json:"indexerFlags"`
|
||||
Quality *arr.Quality `json:"quality"`
|
||||
MediaInfo *MediaInfo `json:"mediaInfo"`
|
||||
QualityCutoffNotMet bool `json:"qualityCutoffNotMet"`
|
||||
Languages []*arr.Value `json:"languages"`
|
||||
ReleaseGroup string `json:"releaseGroup"`
|
||||
Edition string `json:"edition"`
|
||||
}
|
||||
|
||||
type MediaInfo struct {
|
||||
AudioAdditionalFeatures string `json:"audioAdditionalFeatures"`
|
||||
AudioBitrate int `json:"audioBitrate"`
|
||||
AudioChannels float64 `json:"audioChannels"`
|
||||
AudioCodec string `json:"audioCodec"`
|
||||
AudioLanguages string `json:"audioLanguages"`
|
||||
AudioStreamCount int `json:"audioStreamCount"`
|
||||
VideoBitDepth int `json:"videoBitDepth"`
|
||||
VideoBitrate int `json:"videoBitrate"`
|
||||
VideoCodec string `json:"videoCodec"`
|
||||
VideoFps float64 `json:"videoFps"`
|
||||
Resolution string `json:"resolution"`
|
||||
RunTime string `json:"runTime"`
|
||||
ScanType string `json:"scanType"`
|
||||
Subtitles string `json:"subtitles"`
|
||||
}
|
||||
|
||||
type Collection struct {
|
||||
Name string `json:"name"`
|
||||
TmdbID int64 `json:"tmdbId"`
|
||||
Images []*arr.Image `json:"images"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
InfoUrl string `json:"infoUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
MagnetUrl string `json:"magnetUrl,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Indexer string `json:"indexer"`
|
||||
DownloadProtocol string `json:"downloadProtocol"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
DownloadClientId int `json:"downloadClientId,omitempty"`
|
||||
DownloadClient string `json:"downloadClient,omitempty"`
|
||||
}
|
||||
|
||||
type PushResponse struct {
|
||||
Approved bool `json:"approved"`
|
||||
Rejected bool `json:"rejected"`
|
||||
TempRejected bool `json:"temporarilyRejected"`
|
||||
Rejections []string `json:"rejections"`
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type BadRequestResponse struct {
|
||||
Severity string `json:"severity"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
PropertyName string `json:"propertyName"`
|
||||
AttemptedValue string `json:"attemptedValue"`
|
||||
}
|
||||
|
||||
func (r *BadRequestResponse) String() string {
|
||||
return fmt.Sprintf("[%s: %s] %s: %s - got value: %s", r.Severity, r.ErrorCode, r.PropertyName, r.ErrorMessage, r.AttemptedValue)
|
||||
}
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error) {
|
||||
func (c *Client) get(ctx context.Context, endpoint string) (int, []byte, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -54,7 +54,47 @@ func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error)
|
|||
return resp.StatusCode, buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*http.Response, error) {
|
||||
func (c *Client) getJSON(ctx context.Context, endpoint string, params url.Values, data any) error {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, "/api/v1/", endpoint)
|
||||
reqUrl := u.String()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
req.SetBasicAuth(c.config.Username, c.config.Password)
|
||||
}
|
||||
|
||||
c.setHeaders(req)
|
||||
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "readarr.http.Do(req): %+v", req)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.Body == nil {
|
||||
return errors.New("response body is nil")
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) post(ctx context.Context, endpoint string, data interface{}) (*http.Response, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -97,7 +137,7 @@ func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (c *client) postBody(ctx context.Context, endpoint string, data interface{}) (int, []byte, error) {
|
||||
func (c *Client) postBody(ctx context.Context, endpoint string, data interface{}) (int, []byte, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -149,7 +189,7 @@ func (c *client) postBody(ctx context.Context, endpoint string, data interface{}
|
|||
return resp.StatusCode, buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) setHeaders(req *http.Request) {
|
||||
func (c *Client) setHeaders(req *http.Request) {
|
||||
if req.Body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
|
@ -6,14 +6,15 @@ package readarr
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/arr"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
"github.com/autobrr/autobrr/pkg/sharedhttp"
|
||||
)
|
||||
|
@ -30,26 +31,26 @@ type Config struct {
|
|||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
type ClientInterface interface {
|
||||
Test(ctx context.Context) (*SystemStatusResponse, error)
|
||||
Push(ctx context.Context, release Release) ([]string, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
type Client struct {
|
||||
config Config
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
// New create new readarr client
|
||||
func New(config Config) Client {
|
||||
// New create new readarr Client
|
||||
func New(config Config) *Client {
|
||||
httpClient := &http.Client{
|
||||
Timeout: time.Second * 120,
|
||||
Transport: sharedhttp.Transport,
|
||||
}
|
||||
|
||||
c := &client{
|
||||
c := &Client{
|
||||
config: config,
|
||||
http: httpClient,
|
||||
Log: log.New(io.Discard, "", log.LstdFlags),
|
||||
|
@ -62,45 +63,7 @@ func New(config Config) Client {
|
|||
return c
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
InfoUrl string `json:"infoUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
MagnetUrl string `json:"magnetUrl,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Indexer string `json:"indexer"`
|
||||
DownloadProtocol string `json:"downloadProtocol"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
DownloadClientId int `json:"downloadClientId,omitempty"`
|
||||
DownloadClient string `json:"downloadClient,omitempty"`
|
||||
}
|
||||
|
||||
type PushResponse struct {
|
||||
Approved bool `json:"approved"`
|
||||
Rejected bool `json:"rejected"`
|
||||
TempRejected bool `json:"temporarilyRejected"`
|
||||
Rejections []string `json:"rejections"`
|
||||
}
|
||||
|
||||
type BadRequestResponse struct {
|
||||
PropertyName string `json:"propertyName"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
AttemptedValue string `json:"attemptedValue"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
func (r *BadRequestResponse) String() string {
|
||||
return fmt.Sprintf("[%s: %s] %s: %s - got value: %s", r.Severity, r.ErrorCode, r.PropertyName, r.ErrorMessage, r.AttemptedValue)
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
AppName string `json:"appName"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||
func (c *Client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||
status, res, err := c.get(ctx, "system/status")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not make Test")
|
||||
|
@ -120,7 +83,7 @@ func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
|||
return &response, nil
|
||||
}
|
||||
|
||||
func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||
func (c *Client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||
status, res, err := c.postBody(ctx, "release/push", release)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not push release to readarr")
|
||||
|
@ -160,3 +123,28 @@ func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
|||
// successful push
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetBooks(ctx context.Context, gridID string) ([]Book, error) {
|
||||
params := make(url.Values)
|
||||
if gridID != "" {
|
||||
params.Set("titleSlug", gridID)
|
||||
}
|
||||
|
||||
data := make([]Book, 0)
|
||||
err := c.getJSON(ctx, "book", params, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get tags")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetTags(ctx context.Context) ([]*arr.Tag, error) {
|
||||
data := make([]*arr.Tag, 0)
|
||||
err := c.getJSON(ctx, "tag", nil, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get tags")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
122
pkg/arr/readarr/types.go
Normal file
122
pkg/arr/readarr/types.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package readarr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/autobrr/autobrr/pkg/arr"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
InfoUrl string `json:"infoUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
MagnetUrl string `json:"magnetUrl,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Indexer string `json:"indexer"`
|
||||
DownloadProtocol string `json:"downloadProtocol"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
DownloadClientId int `json:"downloadClientId,omitempty"`
|
||||
DownloadClient string `json:"downloadClient,omitempty"`
|
||||
}
|
||||
|
||||
type PushResponse struct {
|
||||
Approved bool `json:"approved"`
|
||||
Rejected bool `json:"rejected"`
|
||||
TempRejected bool `json:"temporarilyRejected"`
|
||||
Rejections []string `json:"rejections"`
|
||||
}
|
||||
|
||||
type BadRequestResponse struct {
|
||||
PropertyName string `json:"propertyName"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
AttemptedValue string `json:"attemptedValue"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
func (r *BadRequestResponse) String() string {
|
||||
return fmt.Sprintf("[%s: %s] %s: %s - got value: %s", r.Severity, r.ErrorCode, r.PropertyName, r.ErrorMessage, r.AttemptedValue)
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
AppName string `json:"appName"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type Book struct {
|
||||
Title string `json:"title"`
|
||||
SeriesTitle string `json:"seriesTitle"`
|
||||
Overview string `json:"overview"`
|
||||
AuthorID int64 `json:"authorId"`
|
||||
ForeignBookID string `json:"foreignBookId"`
|
||||
TitleSlug string `json:"titleSlug"`
|
||||
Monitored bool `json:"monitored"`
|
||||
AnyEditionOk bool `json:"anyEditionOk"`
|
||||
Ratings *arr.Ratings `json:"ratings"`
|
||||
ReleaseDate time.Time `json:"releaseDate"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Genres []interface{} `json:"genres"`
|
||||
Author *BookAuthor `json:"author,omitempty"`
|
||||
Images []*arr.Image `json:"images"`
|
||||
Links []*arr.Link `json:"links"`
|
||||
Statistics *Statistics `json:"statistics,omitempty"`
|
||||
Editions []*Edition `json:"editions"`
|
||||
ID int64 `json:"id"`
|
||||
Disambiguation string `json:"disambiguation,omitempty"`
|
||||
}
|
||||
|
||||
// Statistics for a Book, or maybe an author.
|
||||
type Statistics struct {
|
||||
BookCount int `json:"bookCount"`
|
||||
BookFileCount int `json:"bookFileCount"`
|
||||
TotalBookCount int `json:"totalBookCount"`
|
||||
SizeOnDisk int `json:"sizeOnDisk"`
|
||||
PercentOfBooks float64 `json:"percentOfBooks"`
|
||||
}
|
||||
|
||||
// BookAuthor of a Book.
|
||||
type BookAuthor struct {
|
||||
ID int64 `json:"id"`
|
||||
Status string `json:"status"`
|
||||
AuthorName string `json:"authorName"`
|
||||
ForeignAuthorID string `json:"foreignAuthorId"`
|
||||
TitleSlug string `json:"titleSlug"`
|
||||
Overview string `json:"overview"`
|
||||
Links []*arr.Link `json:"links"`
|
||||
Images []*arr.Image `json:"images"`
|
||||
Path string `json:"path"`
|
||||
QualityProfileID int64 `json:"qualityProfileId"`
|
||||
MetadataProfileID int64 `json:"metadataProfileId"`
|
||||
Genres []interface{} `json:"genres"`
|
||||
CleanName string `json:"cleanName"`
|
||||
SortName string `json:"sortName"`
|
||||
Tags []int `json:"tags"`
|
||||
Added time.Time `json:"added"`
|
||||
Ratings *arr.Ratings `json:"ratings"`
|
||||
Statistics *Statistics `json:"statistics"`
|
||||
Monitored bool `json:"monitored"`
|
||||
Ended bool `json:"ended"`
|
||||
}
|
||||
|
||||
// Edition is more Book meta data.
|
||||
type Edition struct {
|
||||
ID int64 `json:"id"`
|
||||
BookID int64 `json:"bookId"`
|
||||
ForeignEditionID string `json:"foreignEditionId"`
|
||||
TitleSlug string `json:"titleSlug"`
|
||||
Isbn13 string `json:"isbn13"`
|
||||
Asin string `json:"asin"`
|
||||
Title string `json:"title"`
|
||||
Overview string `json:"overview"`
|
||||
Format string `json:"format"`
|
||||
Publisher string `json:"publisher"`
|
||||
PageCount int `json:"pageCount"`
|
||||
ReleaseDate time.Time `json:"releaseDate"`
|
||||
Images []*arr.Image `json:"images"`
|
||||
Links []*arr.Link `json:"links"`
|
||||
Ratings *arr.Ratings `json:"ratings"`
|
||||
Monitored bool `json:"monitored"`
|
||||
ManualAdd bool `json:"manualAdd"`
|
||||
IsEbook bool `json:"isEbook"`
|
||||
}
|
57
pkg/arr/shared.go
Normal file
57
pkg/arr/shared.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package arr
|
||||
|
||||
type Tag struct {
|
||||
ID int
|
||||
Label string
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
CoverType string `json:"coverType"`
|
||||
URL string `json:"url"`
|
||||
RemoteURL string `json:"remoteUrl,omitempty"`
|
||||
Extension string `json:"extension,omitempty"`
|
||||
}
|
||||
|
||||
type Ratings struct {
|
||||
Votes int64 `json:"votes"`
|
||||
Value float64 `json:"value"`
|
||||
Popularity float64 `json:"popularity,omitempty"`
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// BaseQuality is a base quality profile.
|
||||
type BaseQuality struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Resolution int `json:"resolution,omitempty"`
|
||||
Modifier string `json:"modifier,omitempty"`
|
||||
}
|
||||
|
||||
// Quality is a download quality profile attached to a movie, book, track or series.
|
||||
// It may contain 1 or more profiles.
|
||||
// Sonarr nor Readarr use Name or ID in this struct.
|
||||
type Quality struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ID int `json:"id,omitempty"`
|
||||
Quality *BaseQuality `json:"quality,omitempty"`
|
||||
Items []*Quality `json:"items,omitempty"`
|
||||
Allowed bool `json:"allowed"`
|
||||
Revision *QualityRevision `json:"revision,omitempty"` // Not sure which app had this....
|
||||
}
|
||||
|
||||
// QualityRevision is probably used in Sonarr.
|
||||
type QualityRevision struct {
|
||||
Version int64 `json:"version"`
|
||||
Real int64 `json:"real"`
|
||||
IsRepack bool `json:"isRepack,omitempty"`
|
||||
}
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error) {
|
||||
func (c *Client) get(ctx context.Context, endpoint string) (int, []byte, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -54,7 +54,47 @@ func (c *client) get(ctx context.Context, endpoint string) (int, []byte, error)
|
|||
return resp.StatusCode, buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*http.Response, error) {
|
||||
func (c *Client) getJSON(ctx context.Context, endpoint string, params url.Values, data any) error {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, "/api/v3/", endpoint)
|
||||
reqUrl := u.String()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
req.SetBasicAuth(c.config.Username, c.config.Password)
|
||||
}
|
||||
|
||||
c.setHeaders(req)
|
||||
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "sonarr.http.Do(req): %+v", req)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.Body == nil {
|
||||
return errors.New("response body is nil")
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) post(ctx context.Context, endpoint string, data interface{}) (*http.Response, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -97,7 +137,7 @@ func (c *client) post(ctx context.Context, endpoint string, data interface{}) (*
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (c *client) postBody(ctx context.Context, endpoint string, data interface{}) (int, []byte, error) {
|
||||
func (c *Client) postBody(ctx context.Context, endpoint string, data interface{}) (int, []byte, error) {
|
||||
u, err := url.Parse(c.config.Hostname)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "could not parse url: %s", c.config.Hostname)
|
||||
|
@ -147,7 +187,7 @@ func (c *client) postBody(ctx context.Context, endpoint string, data interface{}
|
|||
return resp.StatusCode, buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) setHeaders(req *http.Request) {
|
||||
func (c *Client) setHeaders(req *http.Request) {
|
||||
if req.Body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
|
@ -6,14 +6,16 @@ package sonarr
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/arr"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
"github.com/autobrr/autobrr/pkg/sharedhttp"
|
||||
)
|
||||
|
@ -30,26 +32,26 @@ type Config struct {
|
|||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
type ClientInterface interface {
|
||||
Test(ctx context.Context) (*SystemStatusResponse, error)
|
||||
Push(ctx context.Context, release Release) ([]string, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
type Client struct {
|
||||
config Config
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
// New create new sonarr client
|
||||
func New(config Config) Client {
|
||||
// New create new sonarr Client
|
||||
func New(config Config) *Client {
|
||||
httpClient := &http.Client{
|
||||
Timeout: time.Second * 120,
|
||||
Transport: sharedhttp.Transport,
|
||||
}
|
||||
|
||||
c := &client{
|
||||
c := &Client{
|
||||
config: config,
|
||||
http: httpClient,
|
||||
Log: log.New(io.Discard, "", log.LstdFlags),
|
||||
|
@ -62,44 +64,7 @@ func New(config Config) Client {
|
|||
return c
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
InfoUrl string `json:"infoUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
MagnetUrl string `json:"magnetUrl,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Indexer string `json:"indexer"`
|
||||
DownloadProtocol string `json:"downloadProtocol"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
DownloadClientId int `json:"downloadClientId,omitempty"`
|
||||
DownloadClient string `json:"downloadClient,omitempty"`
|
||||
}
|
||||
|
||||
type PushResponse struct {
|
||||
Approved bool `json:"approved"`
|
||||
Rejected bool `json:"rejected"`
|
||||
TempRejected bool `json:"temporarilyRejected"`
|
||||
Rejections []string `json:"rejections"`
|
||||
}
|
||||
|
||||
type BadRequestResponse struct {
|
||||
PropertyName string `json:"propertyName"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
AttemptedValue string `json:"attemptedValue"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
func (r *BadRequestResponse) String() string {
|
||||
return fmt.Sprintf("[%s: %s] %s: %s - got value: %s", r.Severity, r.ErrorCode, r.PropertyName, r.ErrorMessage, r.AttemptedValue)
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||
func (c *Client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||
status, res, err := c.get(ctx, "system/status")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not make Test")
|
||||
|
@ -119,7 +84,7 @@ func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
|||
return &response, nil
|
||||
}
|
||||
|
||||
func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||
func (c *Client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||
status, res, err := c.postBody(ctx, "release/push", release)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not push release to sonarr")
|
||||
|
@ -158,3 +123,32 @@ func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
|||
// successful push
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetAllSeries(ctx context.Context) ([]Series, error) {
|
||||
return c.GetSeries(ctx, 0)
|
||||
}
|
||||
|
||||
func (c *Client) GetSeries(ctx context.Context, tvdbID int64) ([]Series, error) {
|
||||
params := make(url.Values)
|
||||
if tvdbID != 0 {
|
||||
params.Set("tvdbId", strconv.FormatInt(tvdbID, 10))
|
||||
}
|
||||
|
||||
data := make([]Series, 0)
|
||||
err := c.getJSON(ctx, "series", params, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get tags")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetTags(ctx context.Context) ([]*arr.Tag, error) {
|
||||
data := make([]*arr.Tag, 0)
|
||||
err := c.getJSON(ctx, "tag", nil, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get tags")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
105
pkg/arr/sonarr/types.go
Normal file
105
pkg/arr/sonarr/types.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package sonarr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/arr"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
InfoUrl string `json:"infoUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
MagnetUrl string `json:"magnetUrl,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Indexer string `json:"indexer"`
|
||||
DownloadProtocol string `json:"downloadProtocol"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
DownloadClientId int `json:"downloadClientId,omitempty"`
|
||||
DownloadClient string `json:"downloadClient,omitempty"`
|
||||
}
|
||||
|
||||
type PushResponse struct {
|
||||
Approved bool `json:"approved"`
|
||||
Rejected bool `json:"rejected"`
|
||||
TempRejected bool `json:"temporarilyRejected"`
|
||||
Rejections []string `json:"rejections"`
|
||||
}
|
||||
|
||||
type BadRequestResponse struct {
|
||||
PropertyName string `json:"propertyName"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
AttemptedValue string `json:"attemptedValue"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
func (r *BadRequestResponse) String() string {
|
||||
return fmt.Sprintf("[%s: %s] %s: %s - got value: %s", r.Severity, r.ErrorCode, r.PropertyName, r.ErrorMessage, r.AttemptedValue)
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type AlternateTitle struct {
|
||||
Title string `json:"title"`
|
||||
SeasonNumber int `json:"seasonNumber"`
|
||||
}
|
||||
|
||||
type Season struct {
|
||||
SeasonNumber int `json:"seasonNumber"`
|
||||
Monitored bool `json:"monitored"`
|
||||
Statistics *Statistics `json:"statistics,omitempty"`
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
SeasonCount int `json:"seasonCount"`
|
||||
PreviousAiring time.Time `json:"previousAiring"`
|
||||
EpisodeFileCount int `json:"episodeFileCount"`
|
||||
EpisodeCount int `json:"episodeCount"`
|
||||
TotalEpisodeCount int `json:"totalEpisodeCount"`
|
||||
SizeOnDisk int64 `json:"sizeOnDisk"`
|
||||
PercentOfEpisodes float64 `json:"percentOfEpisodes"`
|
||||
}
|
||||
|
||||
type Series struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title,omitempty"`
|
||||
AlternateTitles []*AlternateTitle `json:"alternateTitles,omitempty"`
|
||||
SortTitle string `json:"sortTitle,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Overview string `json:"overview,omitempty"`
|
||||
PreviousAiring time.Time `json:"previousAiring,omitempty"`
|
||||
Network string `json:"network,omitempty"`
|
||||
Images []*arr.Image `json:"images,omitempty"`
|
||||
Seasons []*Season `json:"seasons,omitempty"`
|
||||
Year int `json:"year,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
QualityProfileID int64 `json:"qualityProfileId,omitempty"`
|
||||
LanguageProfileID int64 `json:"languageProfileId,omitempty"`
|
||||
Runtime int `json:"runtime,omitempty"`
|
||||
TvdbID int64 `json:"tvdbId,omitempty"`
|
||||
TvRageID int64 `json:"tvRageId,omitempty"`
|
||||
TvMazeID int64 `json:"tvMazeId,omitempty"`
|
||||
FirstAired time.Time `json:"firstAired,omitempty"`
|
||||
SeriesType string `json:"seriesType,omitempty"`
|
||||
CleanTitle string `json:"cleanTitle,omitempty"`
|
||||
ImdbID string `json:"imdbId,omitempty"`
|
||||
TitleSlug string `json:"titleSlug,omitempty"`
|
||||
RootFolderPath string `json:"rootFolderPath,omitempty"`
|
||||
Certification string `json:"certification,omitempty"`
|
||||
Genres []string `json:"genres,omitempty"`
|
||||
Tags []int `json:"tags,omitempty"`
|
||||
Added time.Time `json:"added,omitempty"`
|
||||
Ratings *arr.Ratings `json:"ratings,omitempty"`
|
||||
Statistics *Statistics `json:"statistics,omitempty"`
|
||||
NextAiring time.Time `json:"nextAiring,omitempty"`
|
||||
AirTime string `json:"airTime,omitempty"`
|
||||
Ended bool `json:"ended,omitempty"`
|
||||
SeasonFolder bool `json:"seasonFolder,omitempty"`
|
||||
Monitored bool `json:"monitored"`
|
||||
UseSceneNumbering bool `json:"useSceneNumbering,omitempty"`
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue