Add tag support to Sonarr/Radarr requests

This commit is contained in:
WSHaRK93
2026-01-03 11:07:48 +01:00
parent 083e806d43
commit 4e09b14318
8 changed files with 93 additions and 1 deletions

8
pvr.go
View File

@@ -23,6 +23,7 @@ type PvrConfig struct {
} `yaml:"options"` } `yaml:"options"`
Filters PvrFilters `yaml:"filters"` Filters PvrFilters `yaml:"filters"`
CacheDuration time.Duration `yaml:"cache_duration"` CacheDuration time.Duration `yaml:"cache_duration"`
Tag string `yaml:"tag,omitempty"`
Verbosity string `yaml:"verbosity,omitempty"` Verbosity string `yaml:"verbosity,omitempty"`
} }
@@ -40,6 +41,7 @@ type PvrOptions struct {
AddMonitored bool AddMonitored bool
SearchMissing bool SearchMissing bool
Tag string
} }
func BuildPvrOptions(opts ...PvrOption) (*PvrOptions, error) { func BuildPvrOptions(opts ...PvrOption) (*PvrOptions, error) {
@@ -69,3 +71,9 @@ func WithSearchMissing(missing bool) PvrOption {
opts.SearchMissing = missing opts.SearchMissing = missing
} }
} }
func WithTag(tag string) PvrOption {
return func(opts *PvrOptions) {
opts.Tag = tag
}
}

View File

@@ -148,6 +148,57 @@ func (c *Client) getExclusions() (map[int]exclusion, error) {
return exclusions, nil return exclusions, nil
} }
func (c *Client) getOrCreateTag(tagName string) (int, error) {
// get all tags
resp, err := rek.Get(util.JoinURL(c.apiURL, "tag"), rek.Client(c.http), rek.Headers(c.apiHeaders))
if err != nil {
return 0, fmt.Errorf("request tags: %w", err)
}
defer resp.Body().Close()
// validate response
if resp.StatusCode() != 200 {
return 0, fmt.Errorf("validate tags response: %s", resp.Status())
}
// decode response
tags := new([]tag)
if err := json.NewDecoder(resp.Body()).Decode(tags); err != nil {
return 0, fmt.Errorf("decode tags response: %w", err)
}
// find tag by name
for _, t := range *tags {
if strings.EqualFold(t.Label, tagName) {
return t.Id, nil
}
}
// tag not found, create it
createReq := tag{
Label: tagName,
}
createResp, err := rek.Post(util.JoinURL(c.apiURL, "tag"), rek.Client(c.http), rek.Headers(c.apiHeaders),
rek.Json(createReq))
if err != nil {
return 0, fmt.Errorf("request create tag: %w", err)
}
defer createResp.Body().Close()
// validate response
if createResp.StatusCode() != 200 && createResp.StatusCode() != 201 {
return 0, fmt.Errorf("validate create tag response: %s", createResp.Status())
}
// decode created tag
createdTag := new(tag)
if err := json.NewDecoder(createResp.Body()).Decode(createdTag); err != nil {
return 0, fmt.Errorf("decode create tag response: %w", err)
}
return createdTag.Id, nil
}
func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error { func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error {
// prepare options // prepare options
o, err := nabarr.BuildPvrOptions(opts...) o, err := nabarr.BuildPvrOptions(opts...)
@@ -155,6 +206,16 @@ func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error
return fmt.Errorf("build options: %v: %w", item.TmdbId, err) return fmt.Errorf("build options: %v: %w", item.TmdbId, err)
} }
// prepare tags
tags := []int{}
if o.Tag != "" {
tagId, err := c.getOrCreateTag(o.Tag)
if err != nil {
return fmt.Errorf("get or create tag %q: %w", o.Tag, err)
}
tags = []int{tagId}
}
// prepare request // prepare request
req := addRequest{ req := addRequest{
Title: item.Title, Title: item.Title,
@@ -162,6 +223,7 @@ func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error
Year: item.Year, Year: item.Year,
QualityProfileId: c.qualityProfileId, QualityProfileId: c.qualityProfileId,
Images: []string{}, Images: []string{},
Tags: tags,
Monitored: o.AddMonitored, Monitored: o.AddMonitored,
RootFolderPath: c.rootFolder, RootFolderPath: c.rootFolder,
MinimumAvailability: "released", MinimumAvailability: "released",

View File

@@ -223,6 +223,9 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) {
nabarr.WithAddMonitored(c.addMonitored), nabarr.WithAddMonitored(c.addMonitored),
nabarr.WithSearchMissing(c.searchMissing), nabarr.WithSearchMissing(c.searchMissing),
} }
if c.tag != "" {
opts = append(opts, nabarr.WithTag(c.tag))
}
if err := c.AddMediaItem(mediaItem, opts...); err != nil { if err := c.AddMediaItem(mediaItem, opts...); err != nil {
c.log.Error(). c.log.Error().

View File

@@ -27,6 +27,7 @@ type Client struct {
// options // options
searchMissing bool searchMissing bool
addMonitored bool addMonitored bool
tag string
apiURL string apiURL string
apiHeaders map[string]string apiHeaders map[string]string
@@ -77,6 +78,7 @@ func New(c nabarr.PvrConfig, mode string, m *media.Client, cc *cache.Client) (*C
rootFolder: c.RootFolder, rootFolder: c.RootFolder,
searchMissing: util.BoolOrDefault(c.Options.SearchMissing, true), searchMissing: util.BoolOrDefault(c.Options.SearchMissing, true),
addMonitored: util.BoolOrDefault(c.Options.AddMonitored, true), addMonitored: util.BoolOrDefault(c.Options.AddMonitored, true),
tag: c.Tag,
cache: cc, cache: cc,
cacheTempDuration: c.CacheDuration, cacheTempDuration: c.CacheDuration,

View File

@@ -9,6 +9,11 @@ type qualityProfile struct {
Id int Id int
} }
type tag struct {
Id int `json:"id"`
Label string `json:"label"`
}
type exclusion struct { type exclusion struct {
TmdbId int `json:"tmdbId"` TmdbId int `json:"tmdbId"`
MovieTitle string `json:"movieTitle"` MovieTitle string `json:"movieTitle"`
@@ -31,6 +36,7 @@ type addRequest struct {
Year int `json:"year"` Year int `json:"year"`
QualityProfileId int `json:"qualityProfileId"` QualityProfileId int `json:"qualityProfileId"`
Images []string `json:"images"` Images []string `json:"images"`
Tags []int `json:"tags"`
Monitored bool `json:"monitored"` Monitored bool `json:"monitored"`
RootFolderPath string `json:"rootFolderPath"` RootFolderPath string `json:"rootFolderPath"`
MinimumAvailability string `json:"minimumAvailability"` MinimumAvailability string `json:"minimumAvailability"`

View File

@@ -183,6 +183,12 @@ func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error
return fmt.Errorf("converting tvdb id to int: %q", item.TvdbId) return fmt.Errorf("converting tvdb id to int: %q", item.TvdbId)
} }
// prepare tags
tags := []string{}
if o.Tag != "" {
tags = []string{o.Tag}
}
req := addRequest{ req := addRequest{
Title: item.Title, Title: item.Title,
TitleSlug: item.Slug, TitleSlug: item.Slug,
@@ -190,7 +196,7 @@ func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error
QualityProfileId: c.qualityProfileId, QualityProfileId: c.qualityProfileId,
LanguageProfileId: c.languageProfileId, LanguageProfileId: c.languageProfileId,
Images: []string{}, Images: []string{},
Tags: []string{}, Tags: tags,
Monitored: o.AddMonitored, Monitored: o.AddMonitored,
RootFolderPath: c.rootFolder, RootFolderPath: c.rootFolder,
AddOptions: addOptions{ AddOptions: addOptions{

View File

@@ -229,6 +229,9 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) {
nabarr.WithAddMonitored(c.addMonitored), nabarr.WithAddMonitored(c.addMonitored),
nabarr.WithSearchMissing(c.searchMissing), nabarr.WithSearchMissing(c.searchMissing),
} }
if c.tag != "" {
opts = append(opts, nabarr.WithTag(c.tag))
}
if err := c.AddMediaItem(mediaItem, opts...); err != nil { if err := c.AddMediaItem(mediaItem, opts...); err != nil {
c.log.Error(). c.log.Error().

View File

@@ -29,6 +29,7 @@ type Client struct {
searchMissing bool searchMissing bool
addMonitored bool addMonitored bool
skipAnime bool skipAnime bool
tag string
apiURL string apiURL string
apiHeaders map[string]string apiHeaders map[string]string
@@ -81,6 +82,7 @@ func New(c nabarr.PvrConfig, mode string, m *media.Client, cc *cache.Client) (*C
searchMissing: util.BoolOrDefault(c.Options.SearchMissing, true), searchMissing: util.BoolOrDefault(c.Options.SearchMissing, true),
addMonitored: util.BoolOrDefault(c.Options.AddMonitored, true), addMonitored: util.BoolOrDefault(c.Options.AddMonitored, true),
skipAnime: util.BoolOrDefault(c.Options.SkipAnime, true), skipAnime: util.BoolOrDefault(c.Options.SkipAnime, true),
tag: c.Tag,
cache: cc, cache: cc,
cacheTempDuration: c.CacheDuration, cacheTempDuration: c.CacheDuration,