From 9d384d92f14b41fb8289306cea572dfb0f069a78 Mon Sep 17 00:00:00 2001 From: Jendrik Weise Date: Sat, 22 Jul 2023 05:27:31 +0200 Subject: [PATCH] New: Allow updating Jellyfin/Emby library by name Fixes #5826 --- .../MediaBrowser/MediaBrowserException.cs | 23 +++++ .../MediaBrowser/MediaBrowserItems.cs | 25 +++++ .../MediaBrowser/MediaBrowserProxy.cs | 91 ++++++++++++++++++- .../MediaBrowser/MediaBrowserService.cs | 17 +++- .../MediaBrowser/MediaBrowserSettings.cs | 3 + 5 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserException.cs create mode 100644 src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserItems.cs diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserException.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserException.cs new file mode 100644 index 000000000..c4d6bab5e --- /dev/null +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserException.cs @@ -0,0 +1,23 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Notifications.Emby +{ + public class MediaBrowserException : NzbDroneException + { + public MediaBrowserException(string message) + : base(message) + { + } + + public MediaBrowserException(string message, params object[] args) + : base(message, args) + { + } + + public MediaBrowserException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserItems.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserItems.cs new file mode 100644 index 000000000..fad5f89cb --- /dev/null +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserItems.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Notifications.Emby +{ + public class MediaBrowserItems + { + public List Items { get; set; } + } + + public class MediaBrowserItem + { + public string Name { get; set; } + public string Path { get; set; } + + public MediaBrowserProviderIds ProviderIds { get; set; } + } + + public class MediaBrowserProviderIds + { + public string Imdb { get; set; } + public int Tvdb { get; set; } + public int TvMaze { get; set; } + public int TvRage { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs index ed190cae7..d21ea2820 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs @@ -1,6 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; using NLog; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.Emby { @@ -31,6 +35,71 @@ namespace NzbDrone.Core.Notifications.Emby ProcessRequest(request, settings); } + public List GetPaths(MediaBrowserSettings settings, Series series) + { + var path = "/Items"; + var url = GetUrl(settings); + + // NameStartsWith uses the sort title, which is not the series title + var request = new HttpRequestBuilder(url) + .Resource(path) + .AddQueryParam("recursive", "true") + .AddQueryParam("includeItemTypes", "Series") + .AddQueryParam("fields", "Path,ProviderIds") + .AddQueryParam("years", series.Year) + .Build(); + + try + { + var paths = ProcessGetRequest(request, settings).Items.GroupBy(item => + { + var accumulator = 0; + + if (item is { ProviderIds.Tvdb: int tvdbid } && tvdbid != 0 && tvdbid == series.TvdbId) + { + accumulator |= 1 << 4; + } + + if (item is { ProviderIds.Imdb: string imdbid } && imdbid == series.ImdbId) + { + accumulator |= 1 << 3; + } + + if (item is { ProviderIds.TvMaze: int tvmazeid } && tvmazeid != 0 && tvmazeid == series.TvMazeId) + { + accumulator |= 1 << 2; + } + + if (item is { ProviderIds.TvRage: int tvrageid } && tvrageid != 0 && tvrageid == series.TvRageId) + { + accumulator |= 1 << 1; + } + + if (item is { Name: var name } && name == series.Title) + { + accumulator |= 1 << 0; + } + + _logger.Trace($"{item.Path} {accumulator} {item.ProviderIds.TvRage} {series.TvRageId}"); + + return -accumulator; + }, item => item.Path).OrderBy(group => group.Key).First(); + + if (paths.Key == 0) + { + throw new MediaBrowserException("Could not find series by name"); + } + + _logger.Trace("Found series by name: {0}", string.Join(" ", paths)); + + return paths.ToList(); + } + catch (InvalidOperationException ex) + { + throw new MediaBrowserException("Could not find series by name", ex); + } + } + public void Update(MediaBrowserSettings settings, string seriesPath, string updateType) { var path = "/Library/Media/Updated"; @@ -52,6 +121,19 @@ namespace NzbDrone.Core.Notifications.Emby ProcessRequest(request, settings); } + private T ProcessGetRequest(HttpRequest request, MediaBrowserSettings settings) + where T : new() + { + request.Headers.Add("X-MediaBrowser-Token", settings.ApiKey); + + var response = _httpClient.Get(request); + _logger.Trace("Response: {0}", response.Content); + + CheckForError(response); + + return response.Resource; + } + private string ProcessRequest(HttpRequest request, MediaBrowserSettings settings) { request.Headers.Add("X-MediaBrowser-Token", settings.ApiKey); @@ -64,10 +146,15 @@ namespace NzbDrone.Core.Notifications.Emby return response.Content; } - private HttpRequest BuildRequest(string path, MediaBrowserSettings settings) + private string GetUrl(MediaBrowserSettings settings) { var scheme = settings.UseSsl ? "https" : "http"; - var url = $@"{scheme}://{settings.Address}/mediabrowser"; + return $@"{scheme}://{settings.Address}"; + } + + private HttpRequest BuildRequest(string path, MediaBrowserSettings settings) + { + var url = GetUrl(settings); return new HttpRequestBuilder(url).Resource(path).Build(); } diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs index 70174fe34..d30ff88c6 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Net; using FluentValidation.Results; using NLog; @@ -32,7 +33,21 @@ namespace NzbDrone.Core.Notifications.Emby public void Update(MediaBrowserSettings settings, Series series, string updateType) { - _proxy.Update(settings, series.Path, updateType); + List paths; + + if (settings.UpdateLibraryByName) + { + paths = _proxy.GetPaths(settings, series); + } + else + { + paths = new List { series.Path }; + } + + foreach (var path in paths) + { + _proxy.Update(settings, path, updateType); + } } public ValidationFailure Test(MediaBrowserSettings settings) diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs index 4f6a6ad9d..ec3ea943d 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs @@ -43,6 +43,9 @@ namespace NzbDrone.Core.Notifications.Emby [FieldDefinition(5, Label = "Update Library", HelpText = "Update Library on Import, Rename, or Delete?", Type = FieldType.Checkbox)] public bool UpdateLibrary { get; set; } + [FieldDefinition(5, Label = "Update Library By Name", HelpText = "Update Library by name rather than path(Requires 'UpdateLibrary')", Type = FieldType.Checkbox, Advanced = true)] + public bool UpdateLibraryByName { get; set; } + [JsonIgnore] public string Address => $"{Host.ToUrlHost()}:{Port}";