From 4e09b1431858d399491e808be1b55aacd85831ca Mon Sep 17 00:00:00 2001 From: WSHaRK93 Date: Sat, 3 Jan 2026 11:07:48 +0100 Subject: [PATCH] Add tag support to Sonarr/Radarr requests --- pvr.go | 8 +++++++ radarr/api.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ radarr/queue.go | 3 +++ radarr/radarr.go | 2 ++ radarr/struct.go | 6 +++++ sonarr/api.go | 8 ++++++- sonarr/queue.go | 3 +++ sonarr/sonarr.go | 2 ++ 8 files changed, 93 insertions(+), 1 deletion(-) diff --git a/pvr.go b/pvr.go index f0fd13f..d2e0271 100644 --- a/pvr.go +++ b/pvr.go @@ -23,6 +23,7 @@ type PvrConfig struct { } `yaml:"options"` Filters PvrFilters `yaml:"filters"` CacheDuration time.Duration `yaml:"cache_duration"` + Tag string `yaml:"tag,omitempty"` Verbosity string `yaml:"verbosity,omitempty"` } @@ -40,6 +41,7 @@ type PvrOptions struct { AddMonitored bool SearchMissing bool + Tag string } func BuildPvrOptions(opts ...PvrOption) (*PvrOptions, error) { @@ -69,3 +71,9 @@ func WithSearchMissing(missing bool) PvrOption { opts.SearchMissing = missing } } + +func WithTag(tag string) PvrOption { + return func(opts *PvrOptions) { + opts.Tag = tag + } +} diff --git a/radarr/api.go b/radarr/api.go index 4e56ad6..ff840dc 100644 --- a/radarr/api.go +++ b/radarr/api.go @@ -148,6 +148,57 @@ func (c *Client) getExclusions() (map[int]exclusion, error) { 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 { // prepare options 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) } + // 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 req := addRequest{ Title: item.Title, @@ -162,6 +223,7 @@ func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error Year: item.Year, QualityProfileId: c.qualityProfileId, Images: []string{}, + Tags: tags, Monitored: o.AddMonitored, RootFolderPath: c.rootFolder, MinimumAvailability: "released", diff --git a/radarr/queue.go b/radarr/queue.go index d31ddc9..74df080 100644 --- a/radarr/queue.go +++ b/radarr/queue.go @@ -223,6 +223,9 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { nabarr.WithAddMonitored(c.addMonitored), nabarr.WithSearchMissing(c.searchMissing), } + if c.tag != "" { + opts = append(opts, nabarr.WithTag(c.tag)) + } if err := c.AddMediaItem(mediaItem, opts...); err != nil { c.log.Error(). diff --git a/radarr/radarr.go b/radarr/radarr.go index cb0b685..3dca18f 100644 --- a/radarr/radarr.go +++ b/radarr/radarr.go @@ -27,6 +27,7 @@ type Client struct { // options searchMissing bool addMonitored bool + tag string apiURL 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, searchMissing: util.BoolOrDefault(c.Options.SearchMissing, true), addMonitored: util.BoolOrDefault(c.Options.AddMonitored, true), + tag: c.Tag, cache: cc, cacheTempDuration: c.CacheDuration, diff --git a/radarr/struct.go b/radarr/struct.go index 518296f..34df2fc 100644 --- a/radarr/struct.go +++ b/radarr/struct.go @@ -9,6 +9,11 @@ type qualityProfile struct { Id int } +type tag struct { + Id int `json:"id"` + Label string `json:"label"` +} + type exclusion struct { TmdbId int `json:"tmdbId"` MovieTitle string `json:"movieTitle"` @@ -31,6 +36,7 @@ type addRequest struct { Year int `json:"year"` QualityProfileId int `json:"qualityProfileId"` Images []string `json:"images"` + Tags []int `json:"tags"` Monitored bool `json:"monitored"` RootFolderPath string `json:"rootFolderPath"` MinimumAvailability string `json:"minimumAvailability"` diff --git a/sonarr/api.go b/sonarr/api.go index 78b395f..744ac15 100644 --- a/sonarr/api.go +++ b/sonarr/api.go @@ -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) } + // prepare tags + tags := []string{} + if o.Tag != "" { + tags = []string{o.Tag} + } + req := addRequest{ Title: item.Title, TitleSlug: item.Slug, @@ -190,7 +196,7 @@ func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error QualityProfileId: c.qualityProfileId, LanguageProfileId: c.languageProfileId, Images: []string{}, - Tags: []string{}, + Tags: tags, Monitored: o.AddMonitored, RootFolderPath: c.rootFolder, AddOptions: addOptions{ diff --git a/sonarr/queue.go b/sonarr/queue.go index c6e0030..ee2b0b1 100644 --- a/sonarr/queue.go +++ b/sonarr/queue.go @@ -229,6 +229,9 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { nabarr.WithAddMonitored(c.addMonitored), nabarr.WithSearchMissing(c.searchMissing), } + if c.tag != "" { + opts = append(opts, nabarr.WithTag(c.tag)) + } if err := c.AddMediaItem(mediaItem, opts...); err != nil { c.log.Error(). diff --git a/sonarr/sonarr.go b/sonarr/sonarr.go index 023415f..994beaa 100644 --- a/sonarr/sonarr.go +++ b/sonarr/sonarr.go @@ -29,6 +29,7 @@ type Client struct { searchMissing bool addMonitored bool skipAnime bool + tag string apiURL 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), addMonitored: util.BoolOrDefault(c.Options.AddMonitored, true), skipAnime: util.BoolOrDefault(c.Options.SkipAnime, true), + tag: c.Tag, cache: cc, cacheTempDuration: c.CacheDuration,