From ca277250be02e4c6c526cb3722eb28167aa8a7f4 Mon Sep 17 00:00:00 2001 From: "Max T. Kristiansen" Date: Mon, 23 Feb 2026 17:16:36 +0100 Subject: [PATCH 1/2] feat(config): added tags configuration to services --- fetcharr.example.yaml | 8 ++++++++ .../src/Configuration/FetcharrServiceConfiguration.cs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/fetcharr.example.yaml b/fetcharr.example.yaml index 6c39d5e..58bfc28 100644 --- a/fetcharr.example.yaml +++ b/fetcharr.example.yaml @@ -85,6 +85,10 @@ sonarr: ## Default: 3 # short_series_threshold: 1 + ## Adds the given tags to any items added by Fetcharr. + ## Default: [] + # tags: [] + ## Example of a Sonarr instance, exclusively for anime. # sonarr_anime: # base_url: http://localhost:8989 @@ -157,6 +161,10 @@ radarr: ## Default: MovieOnly # monitored_items: MovieAndCollection + ## Adds the given tags to any items added by Fetcharr. + ## Default: [] + # tags: [] + ## Example of a Radarr instance, exclusively for anime. # radarr_anime: # base_url: http://localhost:7878 diff --git a/src/Models/src/Configuration/FetcharrServiceConfiguration.cs b/src/Models/src/Configuration/FetcharrServiceConfiguration.cs index d370bfd..1081853 100644 --- a/src/Models/src/Configuration/FetcharrServiceConfiguration.cs +++ b/src/Models/src/Configuration/FetcharrServiceConfiguration.cs @@ -77,5 +77,11 @@ public abstract class FetcharrServiceConfiguration /// [YamlMember(Alias = "update_existing")] public bool UpdateExisting { get; set; } = true; + + /// + /// Gets or sets any tags which should be added to any items added by Fetcharr. + /// + [YamlMember(Alias = "tags")] + public bool Tags { get; set; } = true; } } \ No newline at end of file From 2de8923044493ad9c3c20ebf0e8da764ca863809 Mon Sep 17 00:00:00 2001 From: "Max T. Kristiansen" Date: Mon, 23 Feb 2026 18:37:40 +0100 Subject: [PATCH 2/2] feat(services): add tages to all added items --- .../FetcharrServiceConfiguration.cs | 2 +- src/Provider.Radarr/src/Models/RadarrTag.cs | 18 ++++++++++++ src/Provider.Radarr/src/RadarrClient.cs | 28 ++++++++++++++++++- src/Provider.Sonarr/src/Models/SonarrTag.cs | 18 ++++++++++++ src/Provider.Sonarr/src/SonarrClient.cs | 28 ++++++++++++++++++- 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/Provider.Radarr/src/Models/RadarrTag.cs create mode 100644 src/Provider.Sonarr/src/Models/SonarrTag.cs diff --git a/src/Models/src/Configuration/FetcharrServiceConfiguration.cs b/src/Models/src/Configuration/FetcharrServiceConfiguration.cs index 1081853..a1d30ae 100644 --- a/src/Models/src/Configuration/FetcharrServiceConfiguration.cs +++ b/src/Models/src/Configuration/FetcharrServiceConfiguration.cs @@ -82,6 +82,6 @@ public abstract class FetcharrServiceConfiguration /// Gets or sets any tags which should be added to any items added by Fetcharr. /// [YamlMember(Alias = "tags")] - public bool Tags { get; set; } = true; + public List Tags { get; set; } = []; } } \ No newline at end of file diff --git a/src/Provider.Radarr/src/Models/RadarrTag.cs b/src/Provider.Radarr/src/Models/RadarrTag.cs new file mode 100644 index 0000000..6776bc0 --- /dev/null +++ b/src/Provider.Radarr/src/Models/RadarrTag.cs @@ -0,0 +1,18 @@ +namespace Fetcharr.Provider.Radarr.Models +{ + /// + /// Representation of a Radarr tag. + /// + public class RadarrTag + { + /// + /// Gets or sets the ID of the tag, within Radarr. + /// + public int Id { get; set; } + + /// + /// Gets or sets the label of the tag. + /// + public string Label { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/Provider.Radarr/src/RadarrClient.cs b/src/Provider.Radarr/src/RadarrClient.cs index 0cea6e7..71016ef 100644 --- a/src/Provider.Radarr/src/RadarrClient.cs +++ b/src/Provider.Radarr/src/RadarrClient.cs @@ -142,6 +142,7 @@ await this._client RadarrRootFolder rootFolder = await this.DetermineRootFolderAsync(options); RadarrQualityProfile qualityProfile = await this.DetermineQualityProfileAsync(options); + List tags = await this.EnsureTagsExistsAsync(); object requestBody = new { @@ -162,7 +163,7 @@ await this._client _ => throw new NotSupportedException() }, monitored = options.Monitored ?? configuration.Monitored, - tags = Array.Empty(), + tags, addOptions = new { ignoreEpisodesWithFiles = true, @@ -270,5 +271,30 @@ private async Task DetermineQualityProfileAsync(RadarrMovi v.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) ?? throw new InvalidOperationException($"[{this.Name}] No quality profile defined."); } + + /// + /// Ensures that the tags specified in the configuration exist on the instance, and sets + /// to the IDs of those tags. + /// + private async Task> EnsureTagsExistsAsync() + { + List IDs = []; + + foreach(string tag in configuration.Tags) + { + IFlurlResponse createdTagResponse = await this._client + .Request("/api/v3/tag") + .PostJsonAsync(new + { + label = tag, + }); + + IDs.Add((await createdTagResponse.GetJsonAsync()).Id); + + logger.LogDebug("Added tag '{Tag}' to Radarr instance '{Instance}'.", tag, this.Name); + } + + return IDs; + } } } \ No newline at end of file diff --git a/src/Provider.Sonarr/src/Models/SonarrTag.cs b/src/Provider.Sonarr/src/Models/SonarrTag.cs new file mode 100644 index 0000000..7ac7dcb --- /dev/null +++ b/src/Provider.Sonarr/src/Models/SonarrTag.cs @@ -0,0 +1,18 @@ +namespace Fetcharr.Provider.Sonarr.Models +{ + /// + /// Representation of a Sonarr tag. + /// + public class SonarrTag + { + /// + /// Gets or sets the ID of the tag, within Sonarr. + /// + public int Id { get; set; } + + /// + /// Gets or sets the label of the tag. + /// + public string Label { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/Provider.Sonarr/src/SonarrClient.cs b/src/Provider.Sonarr/src/SonarrClient.cs index e0c40f4..2de38f1 100644 --- a/src/Provider.Sonarr/src/SonarrClient.cs +++ b/src/Provider.Sonarr/src/SonarrClient.cs @@ -104,6 +104,7 @@ await this._client SonarrRootFolder rootFolder = await this.DetermineRootFolderAsync(options); SonarrQualityProfile qualityProfile = await this.DetermineQualityProfileAsync(options); + List tags = await this.EnsureTagsExistsAsync(); object requestBody = new { @@ -117,7 +118,7 @@ await this._client monitorNewItems = (options.MonitorNewItems ?? configuration.MonitorNewItems) ? "all" : "none", seriesType = options.SeriesType ?? configuration.SeriesType, seasons = Array.Empty(), - tags = Array.Empty(), + tags, addOptions = new { ignoreEpisodesWithFiles = true, @@ -231,5 +232,30 @@ private async Task DetermineQualityProfileAsync(SonarrSeri v.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) ?? throw new InvalidOperationException($"[{this.Name}] No quality profile defined."); } + + /// + /// Ensures that the tags specified in the configuration exist on the instance, and sets + /// to the IDs of those tags. + /// + private async Task> EnsureTagsExistsAsync() + { + List IDs = []; + + foreach(string tag in configuration.Tags) + { + IFlurlResponse createdTagResponse = await this._client + .Request("/api/v3/tag") + .PostJsonAsync(new + { + label = tag, + }); + + IDs.Add((await createdTagResponse.GetJsonAsync()).Id); + + logger.LogDebug("Added tag '{Tag}' to Sonarr instance '{Instance}'.", tag, this.Name); + } + + return IDs; + } } } \ No newline at end of file