diff --git a/UmlautAdaptarr/Controllers/SearchController.cs b/UmlautAdaptarr/Controllers/SearchController.cs index e592e70..8776601 100644 --- a/UmlautAdaptarr/Controllers/SearchController.cs +++ b/UmlautAdaptarr/Controllers/SearchController.cs @@ -1,36 +1,135 @@ using Microsoft.AspNetCore.Mvc; using System.Text; +using System.Xml.Linq; +using UmlautAdaptarr.Models; using UmlautAdaptarr.Services; using UmlautAdaptarr.Utilities; namespace UmlautAdaptarr.Controllers { - public abstract class SearchControllerBase(ProxyService proxyService) : ControllerBase + public abstract class SearchControllerBase(ProxyService proxyService, TitleMatchingService titleMatchingService) : ControllerBase { protected readonly ProxyService _proxyService = proxyService; + protected readonly TitleMatchingService _titleMatchingService = titleMatchingService; - protected async Task BaseSearch(string options, string domain, IDictionary queryParameters) + protected async Task BaseSearch(string options, + string domain, + IDictionary queryParameters, + string? germanTitle = null, + string? expectedTitle = null, + bool hasGermanUmlaut = false) { - if (!UrlUtilities.IsValidDomain(domain)) + try { - return NotFound($"{domain} is not a valid URL."); + if (!UrlUtilities.IsValidDomain(domain)) + { + return NotFound($"{domain} is not a valid URL."); + } + + // Generate title variations for renaming process + var germanTitleVariations = !string.IsNullOrEmpty(germanTitle) ? _titleMatchingService.GenerateTitleVariations(germanTitle) : new List(); + + // Check if "q" parameter exists for multiple search request handling + if (hasGermanUmlaut && !string.IsNullOrEmpty(germanTitle) && !string.IsNullOrEmpty(expectedTitle) && queryParameters.ContainsKey("q")) + { + // Add original search query to title variations + var q = queryParameters["q"]; + if (!germanTitleVariations.Contains(q)) + { + germanTitleVariations.Add(queryParameters["q"]!); + } + + // Handle multiple search requests based on German title variations + var aggregatedResult = await AggregateSearchResults(domain, queryParameters, germanTitleVariations, expectedTitle); + // Rename titles in the aggregated content + var processedContent = ProcessContent(aggregatedResult.Content, germanTitleVariations, expectedTitle); + return Content(processedContent, aggregatedResult.ContentType, aggregatedResult.ContentEncoding); + } + else + { + var singleSearchResult = await PerformSingleSearchRequest(domain, queryParameters); + // Rename titles in the single search content + var contentResult = singleSearchResult as ContentResult; + if (contentResult != null) + { + var processedContent = ProcessContent(contentResult.Content ?? "", germanTitleVariations, expectedTitle); + return Content(processedContent, contentResult.ContentType!, Encoding.UTF8); + } + return singleSearchResult; + } } + catch (Exception ex) + { + // TODO error logging + Console.WriteLine(ex.ToString()); + return null; + } + } + + private async Task PerformSingleSearchRequest(string domain, IDictionary queryParameters) + { var requestUrl = UrlUtilities.BuildUrl(domain, queryParameters); - var responseMessage = await _proxyService.ProxyRequestAsync(HttpContext, requestUrl); - var content = await responseMessage.Content.ReadAsStringAsync(); + var encoding = responseMessage.Content.Headers.ContentType?.CharSet != null ? Encoding.GetEncoding(responseMessage.Content.Headers.ContentType.CharSet) : Encoding.UTF8; - var contentType = responseMessage.Content.Headers.ContentType?.MediaType ?? "application/xml"; - + string contentType = responseMessage.Content.Headers.ContentType?.MediaType ?? "application/xml"; + return Content(content, contentType, encoding); } + + + private string ProcessContent(string content, List germanTitleVariations, string? expectedTitle) + { + // Check if German title and expected title are provided for renaming + if (!string.IsNullOrEmpty(expectedTitle) && germanTitleVariations.Count != 0) + { + // Process and rename titles in the content + content = _titleMatchingService.RenameTitlesInContent(content, germanTitleVariations, expectedTitle); + } + return content; + } + + public async Task AggregateSearchResults(string domain, IDictionary queryParameters, List germanTitleVariations, string expectedTitle) + { + string defaultContentType = "application/xml"; + Encoding defaultEncoding = Encoding.UTF8; + bool encodingSet = false; + + var aggregatedResult = new AggregatedSearchResult(defaultContentType, defaultEncoding); + + foreach (var titleVariation in germanTitleVariations.Distinct()) + { + queryParameters["q"] = titleVariation; // Replace the "q" parameter for each variation + var requestUrl = UrlUtilities.BuildUrl(domain, queryParameters); + var responseMessage = await _proxyService.ProxyRequestAsync(HttpContext, requestUrl); + var content = await responseMessage.Content.ReadAsStringAsync(); + + // Only update encoding from the first response + if (!encodingSet && responseMessage.Content.Headers.ContentType?.CharSet != null) + { + aggregatedResult.ContentEncoding = Encoding.GetEncoding(responseMessage.Content.Headers.ContentType.CharSet); ; + aggregatedResult.ContentType = responseMessage.Content.Headers.ContentType?.MediaType ?? defaultContentType; + encodingSet = true; + } + + // Process and rename titles in the content + content = _titleMatchingService.RenameTitlesInContent(content, germanTitleVariations, expectedTitle); + + // Aggregate the items into a single document + aggregatedResult.AggregateItems(content); + } + + return aggregatedResult; + } } - public class SearchController(ProxyService proxyService, TitleQueryService titleQueryService) : SearchControllerBase(proxyService) + public class SearchController(ProxyService proxyService, + TitleMatchingService titleMatchingService, + TitleQueryService titleQueryService) : SearchControllerBase(proxyService, titleMatchingService) { [HttpGet] public async Task MovieSearch([FromRoute] string options, [FromRoute] string domain) @@ -66,16 +165,47 @@ namespace UmlautAdaptarr.Controllers q => q.Key, q => string.Join(",", q.Value)); + string? searchKey = null; + string? searchValue = null; - if (queryParameters.TryGetValue("tvdbid", out string tvdbId)) + if (queryParameters.TryGetValue("tvdbid", out string? tvdbId)) { - var (HasGermanUmlaut, GermanTitle, ExpectedTitle) = await titleQueryService.QueryShow(tvdbId); + searchKey = "tvdbid"; + searchValue = tvdbId; + } + else if (queryParameters.TryGetValue("q", out string? title)) + { + searchKey = "q"; + searchValue = title; + } - if (GermanTitle == null && ExpectedTitle == null) + // Perform the search if a valid search key was identified + if (searchKey != null && searchValue != null) + { + var (hasGermanUmlaut, germanTitle, expectedTitle) = searchKey == "tvdbid" + ? await titleQueryService.QueryGermanShowTitleByTVDBId(searchValue) + : await titleQueryService.QueryGermanShowTitleByTitle(searchValue); + + if (!string.IsNullOrEmpty(germanTitle) && !string.IsNullOrEmpty(expectedTitle)) { - return NotFound($"Show with TVDB ID {tvdbId} not found."); - } + var initialSearchResult = await BaseSearch(options, domain, queryParameters, germanTitle, expectedTitle, hasGermanUmlaut); + // Additional search with german title because the automatic tvdbid association often fails at the indexer too if there are umlauts + if (hasGermanUmlaut && searchKey == "tvdbid") + { + // Remove identifiers for subsequent searches + queryParameters.Remove("tvdbid"); + queryParameters.Remove("tvmazeid"); + queryParameters.Remove("imdbid"); + + // Aggregate the initial search result with additional results + var germanTitleVariations = _titleMatchingService.GenerateTitleVariations(germanTitle); + var aggregatedResult = await AggregateSearchResults(domain, queryParameters, germanTitleVariations, expectedTitle); + aggregatedResult.AggregateItems((initialSearchResult as ContentResult)?.Content ?? ""); + return Content(aggregatedResult.Content, aggregatedResult.ContentType, aggregatedResult.ContentEncoding); + } + return initialSearchResult; + } } return await BaseSearch(options, domain, queryParameters); diff --git a/UmlautAdaptarr/Models/AggregatedSearchResult.cs b/UmlautAdaptarr/Models/AggregatedSearchResult.cs new file mode 100644 index 0000000..0484236 --- /dev/null +++ b/UmlautAdaptarr/Models/AggregatedSearchResult.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Text; +using System.Xml.Linq; + +namespace UmlautAdaptarr.Models +{ + public class AggregatedSearchResult + { + public XDocument ContentDocument { get; private set; } + public string ContentType { get; set; } + public Encoding ContentEncoding { get; set; } + private HashSet _uniqueItems; + + public AggregatedSearchResult(string contentType, Encoding contentEncoding) + { + ContentType = contentType; + ContentEncoding = contentEncoding; + _uniqueItems = new HashSet(); + + // Initialize ContentDocument with a basic RSS structure + ContentDocument = new XDocument(new XElement("rss", new XElement("channel"))); + } + + public string Content => ContentDocument.ToString(); + + public void AggregateItems(string content) + { + var xDoc = XDocument.Parse(content); + var items = xDoc.Descendants("item"); + + foreach (var item in items) + { + var itemAsString = item.ToString(); + if (_uniqueItems.Add(itemAsString)) + { + ContentDocument.Root.Element("channel").Add(item); + } + else + { + + } + } + } + } +} diff --git a/UmlautAdaptarr/Program.cs b/UmlautAdaptarr/Program.cs index ca52875..2e5e465 100644 --- a/UmlautAdaptarr/Program.cs +++ b/UmlautAdaptarr/Program.cs @@ -6,8 +6,16 @@ internal class Program { private static void Main(string[] args) { + // TODO: + // add option to sort by nzb age + + // TODO + // add delay between requests + var builder = WebApplication.CreateBuilder(args); + var configuration = builder.Configuration; + // Add services to the container. builder.Services.AddHttpClient("HttpClient").ConfigurePrimaryHttpMessageHandler(() => { @@ -28,6 +36,7 @@ internal class Program builder.Services.AddControllers(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); var app = builder.Build(); diff --git a/UmlautAdaptarr/Properties/launchSettings.json b/UmlautAdaptarr/Properties/launchSettings.json index 054c6b0..54ed8e4 100644 --- a/UmlautAdaptarr/Properties/launchSettings.json +++ b/UmlautAdaptarr/Properties/launchSettings.json @@ -3,7 +3,8 @@ "http": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "optionsTODO/example.com/api?t=movie&apikey=132&imdbid=123&limit=100", + "_launchUrl": "optionsTODO/example.com/api?t=movie&apikey=132&imdbid=123&limit=100", + "launchUrl": "/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/UmlautAdaptarr/Services/ProxyService.cs b/UmlautAdaptarr/Services/ProxyService.cs index 3fe6606..cd56ed5 100644 --- a/UmlautAdaptarr/Services/ProxyService.cs +++ b/UmlautAdaptarr/Services/ProxyService.cs @@ -4,6 +4,7 @@ { private readonly HttpClient _httpClient = clientFactory.CreateClient("HttpClient") ?? throw new ArgumentNullException(); private readonly string _userAgent = configuration["Settings:UserAgent"] ?? throw new ArgumentException("UserAgent must be set in appsettings.json"); + // TODO: Add cache! public async Task ProxyRequestAsync(HttpContext context, string targetUri) { @@ -35,6 +36,8 @@ try { var responseMessage = _httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted); + + // TODO: Handle 503 etc responseMessage.EnsureSuccessStatusCode(); // Modify the response content if necessary diff --git a/UmlautAdaptarr/Services/TitleMatchingService.cs b/UmlautAdaptarr/Services/TitleMatchingService.cs new file mode 100644 index 0000000..5f00967 --- /dev/null +++ b/UmlautAdaptarr/Services/TitleMatchingService.cs @@ -0,0 +1,125 @@ +using System.Text.RegularExpressions; +using System.Xml.Linq; +using UmlautAdaptarr.Utilities; + +namespace UmlautAdaptarr.Services +{ + public partial class TitleMatchingService + { + public List GenerateTitleVariations(string germanTitle) + { + var cleanTitle = germanTitle.RemoveAccentButKeepGermanUmlauts(); + + // Start with base variations including handling umlauts + var baseVariations = new List + { + cleanTitle, // No change + cleanTitle.ReplaceGermanUmlautsWithLatinEquivalents(), + cleanTitle.RemoveGermanUmlautDots() + }; + + // Additional variations to accommodate titles with "-" + if (cleanTitle.Contains('-')) + { + var withoutDash = cleanTitle.Replace("-", ""); + var withSpaceInsteadOfDash = cleanTitle.Replace("-", " "); + + // Add variations of the title without dash and with space instead of dash + baseVariations.AddRange(new List + { + withoutDash, + withSpaceInsteadOfDash, + withoutDash.ReplaceGermanUmlautsWithLatinEquivalents(), + withoutDash.RemoveGermanUmlautDots(), + withSpaceInsteadOfDash.ReplaceGermanUmlautsWithLatinEquivalents(), + withSpaceInsteadOfDash.RemoveGermanUmlautDots() + }); + } + + return baseVariations.Distinct().ToList(); + } + + + public string RenameTitlesInContent(string content, List germanTitleVariations, string expectedTitle) + { + var xDoc = XDocument.Parse(content); + + foreach (var item in xDoc.Descendants("item")) + { + var titleElement = item.Element("title"); + if (titleElement != null) + { + var originalTitle = titleElement.Value; + var normalizedOriginalTitle = NormalizeTitle(originalTitle); + + // Attempt to find a variation that matches the start of the original title + foreach (var variation in germanTitleVariations) + { + // Variation is already normalized at creation + var pattern = "^" + Regex.Escape(variation).Replace("\\ ", "[._ ]"); + + // Check if the originalTitle starts with the variation (ignoring case and separators) + if (Regex.IsMatch(normalizedOriginalTitle, pattern, RegexOptions.IgnoreCase)) + { + // Find the first separator used in the original title for consistent replacement + var separator = FindFirstSeparator(originalTitle); + // Reconstruct the expected title using the original separator + var newTitlePrefix = expectedTitle.Replace(" ", separator.ToString()); + + // Extract the suffix from the original title starting right after the matched variation length + var variationLength = variation.Length; + var suffix = originalTitle[Math.Min(variationLength, originalTitle.Length)..]; + + // Clean up any leading separators from the suffix + suffix = Regex.Replace(suffix, "^[._ ]+", ""); + + // TODO EVALUTE! definitely make this optional - this adds GERMAN to the title is the title is german to make sure it's recognized as german + // can lead to problems with shows such as "dark" that have international dubs + /* + // Check if "german" is not in the original title, ignoring case + if (!Regex.IsMatch(originalTitle, "german", RegexOptions.IgnoreCase)) + { + // Insert "GERMAN" after the newTitlePrefix + newTitlePrefix += separator + "GERMAN"; + } + */ + + // Construct the new title with the original suffix + var newTitle = newTitlePrefix + (string.IsNullOrEmpty(suffix) ? "" : separator + suffix); + + // Update the title element's value with the new title + titleElement.Value = newTitle + $"({originalTitle.Substring(0, variationLength)})"; + break; // Break after the first successful match and modification + } + } + } + } + + return xDoc.ToString(); + } + + + private static string NormalizeTitle(string title) + { + title = title.RemoveAccentButKeepGermanUmlauts(); + // Replace all known separators with a consistent one for normalization + return WordSeperationCharRegex().Replace(title, " ".ToString()); + } + + private static char FindFirstSeparator(string title) + { + var match = WordSeperationCharRegex().Match(title); + return match.Success ? match.Value.First() : ' '; // Default to space if no separator found + } + + private static string ReconstructTitleWithSeparator(string title, char separator) + { + // Replace spaces with the original separator found in the title + return title.Replace(' ', separator); + } + + + [GeneratedRegex("[._ ]")] + private static partial Regex WordSeperationCharRegex(); + } +} diff --git a/UmlautAdaptarr/Services/TitleQueryService.cs b/UmlautAdaptarr/Services/TitleQueryService.cs index f1101ef..5aa934b 100644 --- a/UmlautAdaptarr/Services/TitleQueryService.cs +++ b/UmlautAdaptarr/Services/TitleQueryService.cs @@ -15,18 +15,21 @@ namespace UmlautAdaptarr.Services private readonly ILogger _logger; private readonly string _sonarrHost; private readonly string _sonarrApiKey; + private readonly string _umlautAdaptarrApiHost; - public TitleQueryService(HttpClient httpClient, IMemoryCache memoryCache, ILogger logger) + public TitleQueryService(IMemoryCache memoryCache, ILogger logger, IConfiguration configuration, IHttpClientFactory clientFactory) { - _httpClient = httpClient; + _httpClient = clientFactory.CreateClient("HttpClient") ?? throw new ArgumentNullException(); _cache = memoryCache; _logger = logger; - _sonarrHost = Environment.GetEnvironmentVariable("SONARR_HOST"); - _sonarrApiKey = Environment.GetEnvironmentVariable("SONARR_API_KEY"); + _sonarrHost = configuration.GetValue("SONARR_HOST"); + _sonarrApiKey = configuration.GetValue("SONARR_API_KEY"); + _umlautAdaptarrApiHost = configuration["Settings:UmlautAdaptarrApiHost"] ?? throw new ArgumentException("UmlautAdaptarrApiHost must be set in appsettings.json"); + } - public async Task<(bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle)> QueryShow(string tvdbId) + public async Task<(bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle)> QueryGermanShowTitleByTVDBId(string tvdbId) { var cacheKey = $"show_{tvdbId}"; if (_cache.TryGetValue(cacheKey, out (bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle) cachedResult)) @@ -48,7 +51,7 @@ namespace UmlautAdaptarr.Services return (false, null, string.Empty); } - var expectedTitle = shows[0].title as string; + var expectedTitle = (string)shows[0].title; if (expectedTitle == null) { _logger.LogError($"Sonarr Title for TVDB ID {tvdbId} is null"); @@ -57,22 +60,23 @@ namespace UmlautAdaptarr.Services string? germanTitle = null; var hasGermanTitle = false; + var originalLanguage = (string)shows[0].originalLanguage.name; - if ((string)shows[0].originalLanguage.name != "German") + if (originalLanguage != "German") { - var thetvdbUrl = $"https://umlautadaptarr.pcjones.de/get_german_title.php?tvdbid={tvdbId}"; - var tvdbResponse = await _httpClient.GetStringAsync(thetvdbUrl); - var tvdbData = JsonConvert.DeserializeObject(tvdbResponse); + var apiUrl = $"{_umlautAdaptarrApiHost}/tvshow_german.php?tvdbid={tvdbId}"; + var apiResponse = await _httpClient.GetStringAsync(apiUrl); + var responseData = JsonConvert.DeserializeObject(apiResponse); - if (tvdbData == null) + if (responseData == null) { _logger.LogError($"Parsing UmlautAdaptarr TitleQuery API response for TVDB ID {tvdbId} resulted in null"); return (false, null, string.Empty); } - if (tvdbData.status == "success") + if (responseData.status == "success" && !string.IsNullOrEmpty((string)responseData.germanTitle)) { - germanTitle = tvdbData.germanTitle; + germanTitle = responseData.germanTitle; hasGermanTitle = true; } } @@ -87,11 +91,97 @@ namespace UmlautAdaptarr.Services var result = (hasGermanUmlaut, germanTitle, expectedTitle); _cache.Set(cacheKey, result, new MemoryCacheEntryOptions { + Size = 1, SlidingExpiration = hasGermanTitle ? TimeSpan.FromDays(30) : TimeSpan.FromDays(7) }); return result; } + public async Task<(bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle)> QueryGermanShowTitleByTitle(string title) + { + // TVDB doesn't use ß + var tvdbCleanTitle = title.Replace("ß", "ss"); + + var cacheKey = $"show_{tvdbCleanTitle}"; + if (_cache.TryGetValue(cacheKey, out (bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle) cachedResult)) + { + return cachedResult; + } + + var apiUrl = $"{_umlautAdaptarrApiHost}/tvshow_german.php?title={tvdbCleanTitle}"; + var apiResponse = await _httpClient.GetStringAsync(apiUrl); + var responseData = JsonConvert.DeserializeObject(apiResponse); + + if (responseData == null) + { + _logger.LogError($"Parsing UmlautAdaptarr TitleQuery API response for title {title} resulted in null"); + return (false, null, string.Empty); + } + + if (responseData.status == "success" && !string.IsNullOrEmpty((string)responseData.germanTitle)) + { + var tvdbId = (string)responseData.tvdbId; + if (tvdbId == null) + { + _logger.LogError($"Parsing UmlautAdaptarr TitleQuery API response tvdbId {responseData} resulted in null"); + return (false, null, string.Empty); + } + + var sonarrUrl = $"{_sonarrHost}/api/v3/series?tvdbId={tvdbId}&includeSeasonImages=false&apikey={_sonarrApiKey}"; + var response = await _httpClient.GetStringAsync(sonarrUrl); + var shows = JsonConvert.DeserializeObject(response); + + if (shows == null) + { + _logger.LogError($"Parsing Sonarr API response for TVDB ID {tvdbId} resulted in null"); + return (false, null, string.Empty); + } + else if (shows.Count == 0) + { + _logger.LogWarning($"No results found for TVDB ID {tvdbId}"); + return (false, null, string.Empty); + } + + var expectedTitle = (string)shows[0].title; + if (expectedTitle == null) + { + _logger.LogError($"Sonarr Title for TVDB ID {tvdbId} is null"); + return (false, null, string.Empty); + } + + string germanTitle ; + bool hasGermanTitle; + var originalLanguage = (string)shows[0].originalLanguage.name; + + if (originalLanguage != "German") + { + germanTitle = responseData.germanTitle; + hasGermanTitle = true; + } + else + { + germanTitle = expectedTitle; + hasGermanTitle = true; + } + + var hasGermanUmlaut = germanTitle?.HasGermanUmlauts() ?? false; + + var result = (hasGermanUmlaut, germanTitle, expectedTitle); + _cache.Set(cacheKey, result, new MemoryCacheEntryOptions + { + Size = 1, + SlidingExpiration = hasGermanTitle ? TimeSpan.FromDays(30) : TimeSpan.FromDays(7) + }); + + return result; + } + else + { + _logger.LogWarning($"UmlautAdaptarr TitleQuery { apiUrl } didn't succeed."); + return (false, null, string.Empty); + } + } + } } diff --git a/UmlautAdaptarr/UmlautAdaptarr.csproj b/UmlautAdaptarr/UmlautAdaptarr.csproj index c81b7ff..b1c8592 100644 --- a/UmlautAdaptarr/UmlautAdaptarr.csproj +++ b/UmlautAdaptarr/UmlautAdaptarr.csproj @@ -1,10 +1,11 @@ - + net8.0 enable enable true + c5f05dc6-731e-425e-8b8c-a4c09f440adc @@ -12,8 +13,4 @@ - - - - diff --git a/UmlautAdaptarr/Utilities/Extensions.cs b/UmlautAdaptarr/Utilities/Extensions.cs index 31e5f85..9c6d05b 100644 --- a/UmlautAdaptarr/Utilities/Extensions.cs +++ b/UmlautAdaptarr/Utilities/Extensions.cs @@ -9,13 +9,28 @@ namespace UmlautAdaptarr.Utilities { return context.Request.Query[key].FirstOrDefault() ?? string.Empty; } + public static string RemoveAccent(this string text) + { + var normalizedString = text.Normalize(NormalizationForm.FormD); + var stringBuilder = new StringBuilder(); + + foreach (var c in normalizedString) + { + var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); + + if (unicodeCategory != UnicodeCategory.NonSpacingMark) + { + stringBuilder.Append(c); + } + } + + return stringBuilder.ToString().Normalize(NormalizationForm.FormC); + } + public static string RemoveAccentButKeepGermanUmlauts(this string text) { - // TODO: evaluate if this is needed (here) - var stringWithoutSz = text.Replace("ß", "ss"); - - var normalizedString = stringWithoutSz.Normalize(NormalizationForm.FormD); + var normalizedString = text.Normalize(NormalizationForm.FormD); var stringBuilder = new StringBuilder(); foreach (var c in normalizedString) @@ -43,6 +58,18 @@ namespace UmlautAdaptarr.Utilities .Replace("ß", "ss"); } + public static string RemoveGermanUmlautDots(this string text) + { + return text + .Replace("ö", "o") + .Replace("ü", "u") + .Replace("ä", "a") + .Replace("Ö", "O") + .Replace("Ü", "U") + .Replace("Ä", "A") + .Replace("ß", "ss"); + } + public static bool HasGermanUmlauts(this string text) { if (text == null) return false; diff --git a/UmlautAdaptarr/appsettings.json b/UmlautAdaptarr/appsettings.json index 7783dbc..a75a43f 100644 --- a/UmlautAdaptarr/appsettings.json +++ b/UmlautAdaptarr/appsettings.json @@ -15,6 +15,6 @@ }, "Settings": { "UserAgent": "UmlautAdaptarr/1.0", - "TitleQueryHost": "https://umlautadaptarr.pcjones.de" + "UmlautAdaptarrApiHost": "https://umlautadaptarr.pcjones.de/api/v1" } } diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..7dde09f --- /dev/null +++ b/test.txt @@ -0,0 +1,22 @@ + + + + +SceneNZBs +SceneNZBs Feed +https://scenenzbs.com/ +en-gb +support@scenenzbs.com (SceneNZBs) + + + https://scenenzbs.com/templates/default/images/banner.jpg + SceneNZBs + https://scenenzbs.com/ + Visit SceneNZBs - + + + + + + + \ No newline at end of file