From 1d5fabd317e3ce8f6dd31f06d15dc81277f39dbd Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 17 Nov 2022 22:56:54 +0000 Subject: [PATCH] feat(sonarr): :sparkles: Add the username to a Sonarr tag when sent to Sonarr (#4802) --- src/Ombi.Api.Sonarr/ISonarrV3Api.cs | 3 +- src/Ombi.Api.Sonarr/Models/NewSeries.cs | 1 + src/Ombi.Api.Sonarr/Models/SonarrSeries.cs | 2 +- src/Ombi.Api.Sonarr/SonarrV3Api.cs | 18 ++++- src/Ombi.Core/Senders/SonarrSendOptions.cs | 10 +++ src/Ombi.Core/Senders/TvSender.cs | 69 ++++++++++++++++--- .../Models/External/SonarrSettings.cs | 4 ++ .../ClientApp/src/app/interfaces/ISettings.ts | 3 + .../app/settings/sonarr/sonarr.component.html | 4 ++ .../app/settings/sonarr/sonarr.component.ts | 1 + 10 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 src/Ombi.Core/Senders/SonarrSendOptions.cs diff --git a/src/Ombi.Api.Sonarr/ISonarrV3Api.cs b/src/Ombi.Api.Sonarr/ISonarrV3Api.cs index 1d3ea3468..da7b1ae91 100644 --- a/src/Ombi.Api.Sonarr/ISonarrV3Api.cs +++ b/src/Ombi.Api.Sonarr/ISonarrV3Api.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Ombi.Api.Sonarr.Models; -using System.Net.Http; using Ombi.Api.Sonarr.Models.V3; namespace Ombi.Api.Sonarr @@ -9,5 +8,7 @@ namespace Ombi.Api.Sonarr public interface ISonarrV3Api : ISonarrApi { Task> LanguageProfiles(string apiKey, string baseUrl); + Task CreateTag(string apiKey, string baseUrl, string tagName); + Task GetTag(int tagId, string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Sonarr/Models/NewSeries.cs b/src/Ombi.Api.Sonarr/Models/NewSeries.cs index d6f721b0b..9d4a894af 100644 --- a/src/Ombi.Api.Sonarr/Models/NewSeries.cs +++ b/src/Ombi.Api.Sonarr/Models/NewSeries.cs @@ -26,6 +26,7 @@ namespace Ombi.Api.Sonarr.Models public string seriesType { get; set; } public int id { get; set; } public List images { get; set; } + public List tags { get; set; } // V3 Property public int languageProfileId { get; set; } diff --git a/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs b/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs index 3ade006d5..7c9b80f5c 100644 --- a/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs +++ b/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs @@ -40,7 +40,7 @@ namespace Ombi.Api.Sonarr.Models public string titleSlug { get; set; } public string certification { get; set; } public string[] genres { get; set; } - public object[] tags { get; set; } + public List tags { get; set; } public DateTime added { get; set; } public Ratings ratings { get; set; } public int qualityProfileId { get; set; } diff --git a/src/Ombi.Api.Sonarr/SonarrV3Api.cs b/src/Ombi.Api.Sonarr/SonarrV3Api.cs index 71c230f35..dbc87eee1 100644 --- a/src/Ombi.Api.Sonarr/SonarrV3Api.cs +++ b/src/Ombi.Api.Sonarr/SonarrV3Api.cs @@ -11,7 +11,6 @@ namespace Ombi.Api.Sonarr { public SonarrV3Api(IApi api) : base(api) { - } protected override string ApiBaseUrl => "/api/v3/"; @@ -30,5 +29,22 @@ namespace Ombi.Api.Sonarr request.AddHeader("X-Api-Key", apiKey); return await Api.Request>(request); } + + public Task CreateTag(string apiKey, string baseUrl, string tagName) + { + var request = new Request($"{ApiBaseUrl}tag", baseUrl, HttpMethod.Post); + request.AddHeader("X-Api-Key", apiKey); + request.AddJsonBody(new { Label = tagName }); + + return Api.Request(request); + } + + public Task GetTag(int tagId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiBaseUrl}tag/{tagId}", baseUrl, HttpMethod.Get); + request.AddHeader("X-Api-Key", apiKey); + + return Api.Request(request); + } } } diff --git a/src/Ombi.Core/Senders/SonarrSendOptions.cs b/src/Ombi.Core/Senders/SonarrSendOptions.cs new file mode 100644 index 000000000..1bad4604e --- /dev/null +++ b/src/Ombi.Core/Senders/SonarrSendOptions.cs @@ -0,0 +1,10 @@ +using Ombi.Api.Sonarr.Models; +using System.Collections.Generic; + +namespace Ombi.Core.Senders +{ + internal class SonarrSendOptions + { + public List Tags { get; set; } = new List(); + } +} diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 1daa1bf0f..dda7d305d 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Microsoft.VisualBasic; using Ombi.Api.DogNzb; using Ombi.Api.DogNzb.Models; using Ombi.Api.SickRage; @@ -155,11 +157,13 @@ namespace Ombi.Core.Senders { return null; } + var options = new SonarrSendOptions(); int qualityToUse; var languageProfileId = s.LanguageProfile; string rootFolderPath; string seriesType; + int? tagToUse = null; var profiles = await UserQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId); @@ -190,6 +194,7 @@ namespace Ombi.Core.Senders } } seriesType = "anime"; + tagToUse = s.AnimeTag; } else { @@ -209,6 +214,7 @@ namespace Ombi.Core.Senders } } seriesType = "standard"; + tagToUse = s.Tag; } // Overrides on the request take priority @@ -240,6 +246,16 @@ namespace Ombi.Core.Senders try { + if (tagToUse.HasValue) + { + options.Tags.Add(tagToUse.Value); + } + if (s.SendUserTags) + { + var userTag = await GetOrCreateTag(model, s); + options.Tags.Add(userTag.id); + } + // Does the series actually exist? var allSeries = await SonarrApi.GetSeries(s.ApiKey, s.FullUri); var existingSeries = allSeries.FirstOrDefault(x => x.tvdbId == model.ParentRequest.TvDbId); @@ -265,10 +281,10 @@ namespace Ombi.Core.Senders ignoreEpisodesWithoutFiles = false, // We want all missing searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly. }, - languageProfileId = languageProfileId - }; + languageProfileId = languageProfileId, + tags = options.Tags + }; - // Montitor the correct seasons, // If we have that season in the model then it's monitored! @@ -280,11 +296,11 @@ namespace Ombi.Core.Senders throw new Exception(string.Join(',', result.ErrorMessages)); } existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri); - await SendToSonarr(model, existingSeries, s); + await SendToSonarr(model, existingSeries, s, options); } else { - await SendToSonarr(model, existingSeries, s); + await SendToSonarr(model, existingSeries, s, options); } return new NewSeries @@ -303,7 +319,30 @@ namespace Ombi.Core.Senders } } - private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s) + private async Task GetOrCreateTag(ChildRequests model, SonarrSettings s) + { + var tagName = model.RequestedUser.UserName; + // Does tag exist? + + var allTags = await SonarrV3Api.GetTags(s.ApiKey, s.FullUri); + var existingTag = allTags.FirstOrDefault(x => x.label.Equals(tagName, StringComparison.InvariantCultureIgnoreCase)); + existingTag ??= await SonarrV3Api.CreateTag(s.ApiKey, s.FullUri, tagName); + + return existingTag; + } + + private async Task GetTag(int tagId, SonarrSettings s) + { + var tag = await SonarrV3Api.GetTag(tagId, s.ApiKey, s.FullUri); + if (tag == null) + { + Logger.LogError($"Tag ID {tagId} does not exist in sonarr. Please update the settings"); + return null; + } + return tag; + } + + private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s, SonarrSendOptions options) { // Check to ensure we have the all the seasons, ensure the Sonarr metadata has grabbed all the data Season existingSeason = null; @@ -321,15 +360,27 @@ namespace Ombi.Core.Senders } } - var episodesToUpdate = new List(); - // Ok, now let's sort out the episodes. + // Does the show have the correct tags we are expecting + if (options.Tags.Any()) + { + result.tags ??= options.Tags; + var tagsToAdd = options.Tags.Except(result.tags); + + if (tagsToAdd.Any()) + { + result.tags.AddRange(tagsToAdd); + } + result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); + } if (model.SeriesType == SeriesType.Anime) { result.seriesType = "anime"; - await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); + result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); } + var episodesToUpdate = new List(); + // Ok, now let's sort out the episodes. var sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri); var sonarrEpList = sonarrEpisodes.ToList() ?? new List(); while (!sonarrEpList.Any()) diff --git a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs index c367cb48e..8e0b37524 100644 --- a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs @@ -17,9 +17,13 @@ public string QualityProfileAnime { get; set; } public string RootPathAnime { get; set; } + public int? AnimeTag { get; set; } + public int? Tag { get; set; } public bool AddOnly { get; set; } public int LanguageProfile { get; set; } public int LanguageProfileAnime { get; set; } public bool ScanForAvailability { get; set; } + + public bool SendUserTags { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index dd9156102..cd7956fdd 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -146,6 +146,9 @@ export interface ISonarrSettings extends IExternalSettings { languageProfile: number; languageProfileAnime: number; scanForAvailability: boolean; + sendUserTags: boolean; + tag: number | null; + animeTag: number | null; } export interface IRadarrSettings extends IExternalSettings { diff --git a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html index b6c9cb7cd..a3602b393 100644 --- a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html +++ b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html @@ -16,6 +16,10 @@
Scan for Availability
+
+ Add the user as a tag +
This will add the username of the requesting user as a tag in Sonarr. If the tag doesn't exist, Ombi will create it.
+
diff --git a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts index 2095028a2..f6c9d9e38 100644 --- a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts @@ -75,6 +75,7 @@ export class SonarrComponent implements OnInit { languageProfile: [x.languageProfile, [Validators.required, validateProfile]], languageProfileAnime: [x.languageProfileAnime], scanForAvailability: [x.scanForAvailability], + sendUserTags: [x.sendUserTags] }); if (x.qualityProfile) {