From c48db39d040bd37b9dce69fdd8b7e6298c7fc513 Mon Sep 17 00:00:00 2001 From: pcjones Date: Tue, 22 Jul 2025 16:33:13 +0200 Subject: [PATCH 1/3] v0.7.3 (#74) * Add newzbay categories; add title lookup API * Update dependencies --- .../Controllers/SearchController.cs | 1 - .../Controllers/TitleLookupController.cs | 32 ++++++++++++++++ UmlautAdaptarr/Options/GlobalOptions.cs | 9 ++++- UmlautAdaptarr/Services/CacheService.cs | 28 +++++++++----- .../Services/TitleMatchingService.cs | 37 ++++++++++++------- UmlautAdaptarr/UmlautAdaptarr.csproj | 6 +-- UmlautAdaptarr/appsettings.json | 3 +- 7 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 UmlautAdaptarr/Controllers/TitleLookupController.cs diff --git a/UmlautAdaptarr/Controllers/SearchController.cs b/UmlautAdaptarr/Controllers/SearchController.cs index 9a98daa..0423e7b 100644 --- a/UmlautAdaptarr/Controllers/SearchController.cs +++ b/UmlautAdaptarr/Controllers/SearchController.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Options; using System.Text; using UmlautAdaptarr.Models; using UmlautAdaptarr.Options; -using UmlautAdaptarr.Providers; using UmlautAdaptarr.Services; using UmlautAdaptarr.Utilities; diff --git a/UmlautAdaptarr/Controllers/TitleLookupController.cs b/UmlautAdaptarr/Controllers/TitleLookupController.cs new file mode 100644 index 0000000..a60ceb9 --- /dev/null +++ b/UmlautAdaptarr/Controllers/TitleLookupController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using UmlautAdaptarr.Options; +using UmlautAdaptarr.Services; + +namespace UmlautAdaptarr.Controllers +{ + [ApiController] + [Route("titlelookup/")] + public class TitleLookupController(CacheService cacheService, IOptions options) : ControllerBase + { + GlobalOptions _options = options.Value; + [HttpGet] + public IActionResult GetOriginalTitle([FromQuery] string changedTitle) + { + if (!_options.EnableChangedTitleCache) + { + return StatusCode(501, "Set SETTINGS__EnableChangedTitleCache to true to use this endpoint."); + } + + if (string.IsNullOrWhiteSpace(changedTitle)) + return BadRequest("changedTitle is required."); + + var originalTitle = cacheService.GetOriginalTitleFromRenamed(changedTitle); + + return originalTitle != null + ? Ok(new { changedTitle, originalTitle }) + : NotFound($"Original title not found for '{changedTitle}'."); + } + } + +} diff --git a/UmlautAdaptarr/Options/GlobalOptions.cs b/UmlautAdaptarr/Options/GlobalOptions.cs index ee731cc..6c9c5e1 100644 --- a/UmlautAdaptarr/Options/GlobalOptions.cs +++ b/UmlautAdaptarr/Options/GlobalOptions.cs @@ -22,10 +22,17 @@ /// /// API key for requests to the UmlautAdaptarr. Optional. + /// public string? ApiKey { get; set; } = null; /// /// Proxy port for the internal UmlautAdaptarr proxy. + /// public int ProxyPort { get; set; } = 5006; - } + + /// + /// Enable or disable the cache for changed titles. + /// + public bool EnableChangedTitleCache { get; set; } = false; + } } \ No newline at end of file diff --git a/UmlautAdaptarr/Services/CacheService.cs b/UmlautAdaptarr/Services/CacheService.cs index 1c3dab0..10833d9 100644 --- a/UmlautAdaptarr/Services/CacheService.cs +++ b/UmlautAdaptarr/Services/CacheService.cs @@ -1,20 +1,19 @@ -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Extensions.Caching.Memory; -using System.Reflection.Metadata.Ecma335; -using System.Text.RegularExpressions; +using Microsoft.Extensions.Caching.Memory; using UmlautAdaptarr.Models; using UmlautAdaptarr.Utilities; namespace UmlautAdaptarr.Services { - public partial class CacheService(IMemoryCache cache) + public partial class CacheService(IMemoryCache cache) { private readonly Dictionary> VariationIndex = []; private readonly Dictionary TitleVariations, string CacheKey)>> BookVariationIndex = []; private readonly Dictionary TitleVariations, string CacheKey)>> AudioVariationIndex = []; private const int VARIATION_LOOKUP_CACHE_LENGTH = 5; + private const string TitleRenamePrefix = "title_rename_"; + private static readonly TimeSpan TitleRenameCacheDuration = TimeSpan.FromHours(12); - public void CacheSearchItem(SearchItem item) + public void CacheSearchItem(SearchItem item) { var prefix = item.MediaType; var cacheKey = $"{prefix}_extid_{item.ExternalId}"; @@ -196,8 +195,19 @@ namespace UmlautAdaptarr.Services return null; } + public void CacheTitleRename(string changedTitle, string originalTitle) + { + if (string.IsNullOrWhiteSpace(changedTitle) || string.IsNullOrWhiteSpace(originalTitle)) + return; - [GeneratedRegex("\\s")] - private static partial Regex WhiteSpaceRegex(); - } + var key = $"{TitleRenamePrefix}{changedTitle.Trim().ToLowerInvariant()}"; + cache.Set(key, originalTitle, TitleRenameCacheDuration); + } + + public string? GetOriginalTitleFromRenamed(string changedTitle) + { + var key = $"{TitleRenamePrefix}{changedTitle.Trim().ToLowerInvariant()}"; + return cache.TryGetValue(key, out string? originalTitle) ? originalTitle : null; + } + } } diff --git a/UmlautAdaptarr/Services/TitleMatchingService.cs b/UmlautAdaptarr/Services/TitleMatchingService.cs index 802c92b..8658374 100644 --- a/UmlautAdaptarr/Services/TitleMatchingService.cs +++ b/UmlautAdaptarr/Services/TitleMatchingService.cs @@ -1,14 +1,18 @@ using Microsoft.Extensions.FileSystemGlobbing.Internal; +using Microsoft.Extensions.Options; using System.Text.RegularExpressions; using System.Xml.Linq; using UmlautAdaptarr.Models; +using UmlautAdaptarr.Options; using UmlautAdaptarr.Utilities; namespace UmlautAdaptarr.Services { - public partial class TitleMatchingService(CacheService cacheService, ILogger logger) + public partial class TitleMatchingService(CacheService cacheService, ILogger logger, IOptions options) { - public string RenameTitlesInContent(string content, SearchItem? searchItem) + public GlobalOptions _options { get; } = options.Value; + + public string RenameTitlesInContent(string content, SearchItem? searchItem) { var xDoc = XDocument.Parse(content); @@ -46,10 +50,10 @@ namespace UmlautAdaptarr.Services switch (mediaType) { case "tv": - FindAndReplaceForMoviesAndTV(logger, searchItem, titleElement, originalTitle, cleanTitleSeperatedBySpace!); + FindAndReplaceForMoviesAndTV(searchItem, titleElement, originalTitle, cleanTitleSeperatedBySpace!); break; case "movie": - FindAndReplaceForMoviesAndTV(logger, searchItem, titleElement, originalTitle, cleanTitleSeperatedBySpace!); + FindAndReplaceForMoviesAndTV(searchItem, titleElement, originalTitle, cleanTitleSeperatedBySpace!); break; case "audio": FindAndReplaceForBooksAndAudio(searchItem, titleElement, originalTitle!); @@ -94,7 +98,11 @@ namespace UmlautAdaptarr.Services // Update the title element titleElement.Value = updatedTitle; - logger.LogInformation($"TitleMatchingService - Title changed: '{originalTitle}' to '{updatedTitle}'"); + if (_options.EnableChangedTitleCache) + { + cacheService.CacheTitleRename(updatedTitle, originalTitle); + } + logger.LogInformation($"TitleMatchingService - Title changed: '{originalTitle}' to '{updatedTitle}'"); } else { @@ -161,7 +169,7 @@ namespace UmlautAdaptarr.Services } // This method replaces the first variation that starts at the beginning of the release title - private static void FindAndReplaceForMoviesAndTV(ILogger logger, SearchItem searchItem, XElement? titleElement, string originalTitle, string normalizedOriginalTitle) + private void FindAndReplaceForMoviesAndTV(SearchItem searchItem, XElement? titleElement, string originalTitle, string normalizedOriginalTitle) { var titleMatchVariations = searchItem.TitleMatchVariations; var expectedTitle = searchItem.ExpectedTitle; @@ -218,8 +226,11 @@ namespace UmlautAdaptarr.Services // Update the title element's value with the new title //titleElement.Value = newTitle + $"({originalTitle.Substring(0, variationLength)})"; titleElement.Value = newTitle; - - logger.LogInformation($"TitleMatchingService - Title changed: '{originalTitle}' to '{newTitle}'"); + if (_options.EnableChangedTitleCache) + { + cacheService.CacheTitleRename(newTitle, originalTitle); + } + logger.LogInformation($"TitleMatchingService - Title changed: '{originalTitle}' to '{newTitle}'"); break; } } @@ -298,23 +309,23 @@ namespace UmlautAdaptarr.Services return null; } - if (category == "7000" || category.StartsWith("EBook", StringComparison.OrdinalIgnoreCase) || category.StartsWith("Book", StringComparison.OrdinalIgnoreCase)) + if (category == "7000" || category.StartsWith("EBook", StringComparison.OrdinalIgnoreCase) || category.StartsWith("Book", StringComparison.OrdinalIgnoreCase) ||category.StartsWith("Bücher", StringComparison.OrdinalIgnoreCase)) { return "book"; } - else if (category == "2000" || category.StartsWith("Movies", StringComparison.OrdinalIgnoreCase)) + else if (category == "2000" || category.StartsWith("Movies", StringComparison.OrdinalIgnoreCase) || category.StartsWith("Filme", StringComparison.OrdinalIgnoreCase)) { return "movies"; } - else if (category == "5000" || category.StartsWith("TV", StringComparison.OrdinalIgnoreCase)) + else if (category == "5000" || category.StartsWith("TV", StringComparison.OrdinalIgnoreCase) || category.StartsWith("Serien", StringComparison.OrdinalIgnoreCase)) { return "tv"; } - else if (category == "3030" || category.Contains("Audiobook", StringComparison.OrdinalIgnoreCase)) + else if (category == "3030" || category.Contains("Audiobook", StringComparison.OrdinalIgnoreCase) || category.Contains("Hörbuch", StringComparison.OrdinalIgnoreCase)) { return "book"; } - else if (category == "3000" || category.StartsWith("Audio", StringComparison.OrdinalIgnoreCase)) + else if (category == "3000" || category.StartsWith("Audio", StringComparison.OrdinalIgnoreCase) || category.StartsWith("Musik", StringComparison.OrdinalIgnoreCase)) { return "audio"; } diff --git a/UmlautAdaptarr/UmlautAdaptarr.csproj b/UmlautAdaptarr/UmlautAdaptarr.csproj index a60f613..d7e0a3d 100644 --- a/UmlautAdaptarr/UmlautAdaptarr.csproj +++ b/UmlautAdaptarr/UmlautAdaptarr.csproj @@ -9,10 +9,10 @@ - + - - + + diff --git a/UmlautAdaptarr/appsettings.json b/UmlautAdaptarr/appsettings.json index 92436d1..ab538b4 100644 --- a/UmlautAdaptarr/appsettings.json +++ b/UmlautAdaptarr/appsettings.json @@ -23,7 +23,8 @@ "UmlautAdaptarrApiHost": "https://umlautadaptarr.pcjones.de/api/v1", "IndexerRequestsCacheDurationInMinutes": 12, "ApiKey": null, - "ProxyPort": 5006 + "ProxyPort": 5006, + "EnableChangedTitleCache": false // Set to true if you are using crowdnfo.net post processing script }, "Sonarr": [ { From b828b64a22a168e830fc82a3762ab57d87c3106f Mon Sep 17 00:00:00 2001 From: pcjones Date: Thu, 24 Jul 2025 16:54:20 +0200 Subject: [PATCH 2/3] Update run_on_seedbox.sh --- run_on_seedbox.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/run_on_seedbox.sh b/run_on_seedbox.sh index b1618e2..d87a67b 100644 --- a/run_on_seedbox.sh +++ b/run_on_seedbox.sh @@ -43,6 +43,7 @@ export LIDARR__APIKEY=APIKEY # Advanced Options #export IpLeakTest__Enabled=false #export SETTINGS__IndexerRequestsCacheDurationInMinutes=12 +export ASPNETCORE_CONTENTROOT="./publish" export SETTINGS__ApiKey="apikey" # Change to something unique! Then in Prowlarr, in the proxy settings set any username and use this ApiKey as password. export SETTINGS__ProxyPort=1234 # Port for Proxy export Kestrel__Endpoints__Http__Url="http://[::]:1235" # Port for UmlautAdaptarr API From feae0ca309abeb0a2734566b249a70fc5b83ff88 Mon Sep 17 00:00:00 2001 From: pcjones Date: Sun, 16 Nov 2025 19:35:42 +0100 Subject: [PATCH 3/3] Fix releases with ":" not working in title lookup API --- UmlautAdaptarr/Controllers/SearchController.cs | 5 ++--- UmlautAdaptarr/Controllers/TitleLookupController.cs | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/UmlautAdaptarr/Controllers/SearchController.cs b/UmlautAdaptarr/Controllers/SearchController.cs index 0423e7b..3209c65 100644 --- a/UmlautAdaptarr/Controllers/SearchController.cs +++ b/UmlautAdaptarr/Controllers/SearchController.cs @@ -13,7 +13,7 @@ namespace UmlautAdaptarr.Controllers // TODO evaluate if this should be set to true by default private readonly bool TODO_FORCE_TEXT_SEARCH_ORIGINAL_TITLE = true; private readonly bool TODO_FORCE_TEXT_SEARCH_GERMAN_TITLE = false; - protected async Task BaseSearch(string apiKey, + protected async Task BaseSearch(string apiKey, string domain, IDictionary queryParameters, SearchItem? searchItem = null) @@ -30,8 +30,7 @@ namespace UmlautAdaptarr.Controllers return NotFound($"{domain} is not a valid URL."); } - ContentResult? initialSearchResult = await PerformSingleSearchRequest(domain, queryParameters) as ContentResult; - if (initialSearchResult == null) + if (await PerformSingleSearchRequest(domain, queryParameters) is not ContentResult initialSearchResult) { return null; } diff --git a/UmlautAdaptarr/Controllers/TitleLookupController.cs b/UmlautAdaptarr/Controllers/TitleLookupController.cs index a60ceb9..a392c6c 100644 --- a/UmlautAdaptarr/Controllers/TitleLookupController.cs +++ b/UmlautAdaptarr/Controllers/TitleLookupController.cs @@ -21,7 +21,9 @@ namespace UmlautAdaptarr.Controllers if (string.IsNullOrWhiteSpace(changedTitle)) return BadRequest("changedTitle is required."); - var originalTitle = cacheService.GetOriginalTitleFromRenamed(changedTitle); + var cleanChangedTitle = changedTitle.Replace(":", "-"); + + var originalTitle = cacheService.GetOriginalTitleFromRenamed(cleanChangedTitle); return originalTitle != null ? Ok(new { changedTitle, originalTitle })