diff --git a/src/Jellyfin.Plugin.SeerrFin/Configuration/PluginConfiguration.cs b/src/Jellyfin.Plugin.SeerrFin/Configuration/PluginConfiguration.cs index b1fe31d..f934054 100644 --- a/src/Jellyfin.Plugin.SeerrFin/Configuration/PluginConfiguration.cs +++ b/src/Jellyfin.Plugin.SeerrFin/Configuration/PluginConfiguration.cs @@ -19,6 +19,8 @@ public class PluginConfiguration : BasePluginConfiguration public string? SonarrApiKey { get; set; } = string.Empty; + public List ServarrInstances { get; set; } = new(); + public string? JellyseerrPreferredLanguages { get; set; } = "en"; public string? TmdbApiKey { get; set; } = string.Empty; diff --git a/src/Jellyfin.Plugin.SeerrFin/Configuration/ServarrConfigHelper.cs b/src/Jellyfin.Plugin.SeerrFin/Configuration/ServarrConfigHelper.cs new file mode 100644 index 0000000..63726de --- /dev/null +++ b/src/Jellyfin.Plugin.SeerrFin/Configuration/ServarrConfigHelper.cs @@ -0,0 +1,202 @@ +namespace Jellyfin.Plugin.SeerrFin.Configuration; + +public static class ServarrConfigHelper +{ + private static readonly string[] KnownKinds = new[] { "radarr", "sonarr" }; + + public static IReadOnlyList Resolve(PluginConfiguration config) + { + config.ServarrInstances ??= new List(); + + List instances = config.ServarrInstances + .Where(instance => instance != null) + .Select(NormalizeInstance) + .Where(instance => !IsEffectivelyEmpty(instance)) + .ToList(); + + if (instances.Count == 0) + { + instances = CreateLegacyInstances(config); + } + + NormalizeNames(instances); + NormalizeDefaults(instances); + + config.ServarrInstances = instances; + SyncLegacyFields(config, instances); + return instances; + } + + public static IReadOnlyList GetConfiguredInstances(PluginConfiguration config, string kind) => + Resolve(config) + .Where(instance => string.Equals(instance.Kind, NormalizeKind(kind), StringComparison.OrdinalIgnoreCase) && IsConfigured(instance)) + .ToList(); + + public static string? GetPreferredUrl(PluginConfiguration config, string kind) + { + ServarrInstanceConfig? instance = GetPreferredInstance(config, kind); + return instance?.Url?.Trim(); + } + + public static ServarrInstanceConfig? GetPreferredInstance(PluginConfiguration config, string kind) + { + string normalizedKind = NormalizeKind(kind); + IReadOnlyList instances = Resolve(config) + .Where(instance => string.Equals(instance.Kind, normalizedKind, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (instances.Count == 0) + { + return null; + } + + ServarrInstanceConfig? configuredDefault = instances.FirstOrDefault(instance => instance.IsDefault && IsConfigured(instance)); + if (configuredDefault != null) + { + return configuredDefault; + } + + ServarrInstanceConfig? configured = instances.FirstOrDefault(IsConfigured); + if (configured != null) + { + return configured; + } + + return instances[0]; + } + + public static bool IsConfigured(ServarrInstanceConfig instance) => + !string.IsNullOrWhiteSpace(instance.Url) && !string.IsNullOrWhiteSpace(instance.ApiKey); + + public static string NormalizeKind(string? kind) => + string.Equals(kind, "sonarr", StringComparison.OrdinalIgnoreCase) ? "sonarr" : "radarr"; + + public static string GetKindLabel(string kind) => + string.Equals(NormalizeKind(kind), "sonarr", StringComparison.OrdinalIgnoreCase) ? "Sonarr" : "Radarr"; + + private static ServarrInstanceConfig NormalizeInstance(ServarrInstanceConfig instance) + { + string kind = NormalizeKind(instance.Kind); + return new ServarrInstanceConfig + { + Kind = kind, + Name = string.IsNullOrWhiteSpace(instance.Name) ? string.Empty : instance.Name.Trim(), + Url = string.IsNullOrWhiteSpace(instance.Url) ? string.Empty : instance.Url.Trim(), + ApiKey = string.IsNullOrWhiteSpace(instance.ApiKey) ? string.Empty : instance.ApiKey.Trim(), + IsDefault = instance.IsDefault, + Is4k = string.Equals(kind, "radarr", StringComparison.OrdinalIgnoreCase) && instance.Is4k + }; + } + + private static List CreateLegacyInstances(PluginConfiguration config) + { + List instances = new(); + + if (!string.IsNullOrWhiteSpace(config.RadarrUrl) || !string.IsNullOrWhiteSpace(config.RadarrApiKey)) + { + instances.Add(new ServarrInstanceConfig + { + Kind = "radarr", + Name = string.Empty, + Url = config.RadarrUrl?.Trim() ?? string.Empty, + ApiKey = config.RadarrApiKey?.Trim() ?? string.Empty, + IsDefault = true, + Is4k = false + }); + } + + if (!string.IsNullOrWhiteSpace(config.SonarrUrl) || !string.IsNullOrWhiteSpace(config.SonarrApiKey)) + { + instances.Add(new ServarrInstanceConfig + { + Kind = "sonarr", + Name = string.Empty, + Url = config.SonarrUrl?.Trim() ?? string.Empty, + ApiKey = config.SonarrApiKey?.Trim() ?? string.Empty, + IsDefault = true, + Is4k = false + }); + } + + return instances; + } + + private static void NormalizeNames(List instances) + { + Dictionary counts = new(StringComparer.OrdinalIgnoreCase); + + foreach (ServarrInstanceConfig instance in instances) + { + string kind = NormalizeKind(instance.Kind); + counts.TryGetValue(kind, out int current); + current++; + counts[kind] = current; + + if (string.IsNullOrWhiteSpace(instance.Name)) + { + instance.Name = $"{GetKindLabel(kind)} {current}"; + } + else + { + instance.Name = instance.Name.Trim(); + } + } + } + + private static void NormalizeDefaults(List instances) + { + foreach (string kind in KnownKinds) + { + List byKind = instances + .Where(instance => string.Equals(instance.Kind, kind, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (byKind.Count == 0) + { + continue; + } + + ServarrInstanceConfig? selected = byKind.FirstOrDefault(instance => instance.IsDefault && IsConfigured(instance)) + ?? byKind.FirstOrDefault(instance => instance.IsDefault) + ?? byKind.FirstOrDefault(IsConfigured) + ?? byKind[0]; + + bool selectedSeen = false; + foreach (ServarrInstanceConfig instance in byKind) + { + if (!selectedSeen && ReferenceEquals(instance, selected)) + { + instance.IsDefault = true; + selectedSeen = true; + } + else + { + instance.IsDefault = false; + } + } + } + } + + private static void SyncLegacyFields(PluginConfiguration config, IReadOnlyList instances) + { + ServarrInstanceConfig? radarr = GetPreferredLegacySource(instances, "radarr"); + ServarrInstanceConfig? sonarr = GetPreferredLegacySource(instances, "sonarr"); + + config.RadarrUrl = radarr?.Url?.Trim() ?? string.Empty; + config.RadarrApiKey = radarr?.ApiKey?.Trim() ?? string.Empty; + config.SonarrUrl = sonarr?.Url?.Trim() ?? string.Empty; + config.SonarrApiKey = sonarr?.ApiKey?.Trim() ?? string.Empty; + } + + private static ServarrInstanceConfig? GetPreferredLegacySource(IReadOnlyList instances, string kind) + { + string normalizedKind = NormalizeKind(kind); + return instances.FirstOrDefault(instance => string.Equals(instance.Kind, normalizedKind, StringComparison.OrdinalIgnoreCase) && instance.IsDefault && IsConfigured(instance)) + ?? instances.FirstOrDefault(instance => string.Equals(instance.Kind, normalizedKind, StringComparison.OrdinalIgnoreCase) && IsConfigured(instance)) + ?? instances.FirstOrDefault(instance => string.Equals(instance.Kind, normalizedKind, StringComparison.OrdinalIgnoreCase) && instance.IsDefault) + ?? instances.FirstOrDefault(instance => string.Equals(instance.Kind, normalizedKind, StringComparison.OrdinalIgnoreCase)); + } + + private static bool IsEffectivelyEmpty(ServarrInstanceConfig instance) => + string.IsNullOrWhiteSpace(instance.Url) && string.IsNullOrWhiteSpace(instance.ApiKey); +} diff --git a/src/Jellyfin.Plugin.SeerrFin/Configuration/ServarrInstanceConfig.cs b/src/Jellyfin.Plugin.SeerrFin/Configuration/ServarrInstanceConfig.cs new file mode 100644 index 0000000..cc5b47a --- /dev/null +++ b/src/Jellyfin.Plugin.SeerrFin/Configuration/ServarrInstanceConfig.cs @@ -0,0 +1,16 @@ +namespace Jellyfin.Plugin.SeerrFin.Configuration; + +public sealed class ServarrInstanceConfig +{ + public string Kind { get; set; } = "radarr"; + + public string? Name { get; set; } = string.Empty; + + public string? Url { get; set; } = string.Empty; + + public string? ApiKey { get; set; } = string.Empty; + + public bool IsDefault { get; set; } + + public bool Is4k { get; set; } +} diff --git a/src/Jellyfin.Plugin.SeerrFin/Configuration/config.html b/src/Jellyfin.Plugin.SeerrFin/Configuration/config.html index 8ad1683..e46c19f 100644 --- a/src/Jellyfin.Plugin.SeerrFin/Configuration/config.html +++ b/src/Jellyfin.Plugin.SeerrFin/Configuration/config.html @@ -65,6 +65,66 @@ padding: 1em; } + .bst-servarr-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1em; + margin-bottom: 1em; + } + + .bst-servarr-instance-list { + display: flex; + flex-direction: column; + gap: 1em; + } + + .bst-servarr-instance-card { + display: flex; + flex-direction: column; + gap: 1em; + } + + .bst-servarr-instance-top { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1em; + } + + .bst-servarr-instance-title-wrap { + min-width: 0; + } + + .bst-servarr-instance-kind { + font-size: 1.05em; + font-weight: 500; + line-height: 1.35; + color: #fff; + margin-bottom: 0.25em; + } + + .bst-servarr-instance-actions { + display: flex; + align-items: center; + gap: 0.65em; + flex-shrink: 0; + } + + .bst-modal--compact { + width: min(100%, 24em); + } + + .bst-modal-actions--stack { + flex-direction: column; + align-items: stretch; + } + + .bst-modal-btn--choice { + justify-content: center; + text-align: center; + } + .bst-section-header { display: flex; justify-content: space-between; @@ -955,46 +1015,11 @@
Radarr/Sonarr configuration for download progress on Requests page
-
-
-
- -
- -
URL used by the server to reach Radarr (or you use to access it)
-
-
-
-
-
- -
- -
Key for Radarr which you can get from Radarr → Settings → General → Security → API Key
-
-
-
-
-
-
-
- -
- -
URL used by the server to reach Sonarr (or you use to access it)
-
-
-
-
-
- -
- -
Key for Sonarr which you can get from Sonarr → Settings → General → Security → API Key
-
-
-
+
+
Add one or more Radarr or Sonarr instances. The plugin uses them for Requests progress and open links.
+
+
@@ -1731,6 +1756,74 @@

+ + + +