Intermediate commit :-)

This commit is contained in:
pcjones
2024-02-07 04:50:55 +01:00
parent 20bb2c7577
commit 68030ea7ea
11 changed files with 487 additions and 38 deletions

View File

@@ -1,36 +1,135 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Text; using System.Text;
using System.Xml.Linq;
using UmlautAdaptarr.Models;
using UmlautAdaptarr.Services; using UmlautAdaptarr.Services;
using UmlautAdaptarr.Utilities; using UmlautAdaptarr.Utilities;
namespace UmlautAdaptarr.Controllers 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 ProxyService _proxyService = proxyService;
protected readonly TitleMatchingService _titleMatchingService = titleMatchingService;
protected async Task<IActionResult> BaseSearch(string options, string domain, IDictionary<string, string> queryParameters) protected async Task<IActionResult> BaseSearch(string options,
string domain,
IDictionary<string, string> 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<string>();
// 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<IActionResult> PerformSingleSearchRequest(string domain, IDictionary<string, string> queryParameters)
{
var requestUrl = UrlUtilities.BuildUrl(domain, queryParameters); var requestUrl = UrlUtilities.BuildUrl(domain, queryParameters);
var responseMessage = await _proxyService.ProxyRequestAsync(HttpContext, requestUrl); var responseMessage = await _proxyService.ProxyRequestAsync(HttpContext, requestUrl);
var content = await responseMessage.Content.ReadAsStringAsync(); var content = await responseMessage.Content.ReadAsStringAsync();
var encoding = responseMessage.Content.Headers.ContentType?.CharSet != null ? var encoding = responseMessage.Content.Headers.ContentType?.CharSet != null ?
Encoding.GetEncoding(responseMessage.Content.Headers.ContentType.CharSet) : Encoding.GetEncoding(responseMessage.Content.Headers.ContentType.CharSet) :
Encoding.UTF8; 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); return Content(content, contentType, encoding);
} }
private string ProcessContent(string content, List<string> 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<AggregatedSearchResult> AggregateSearchResults(string domain, IDictionary<string, string> queryParameters, List<string> 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] [HttpGet]
public async Task<IActionResult> MovieSearch([FromRoute] string options, [FromRoute] string domain) public async Task<IActionResult> MovieSearch([FromRoute] string options, [FromRoute] string domain)
@@ -66,16 +165,47 @@ namespace UmlautAdaptarr.Controllers
q => q.Key, q => q.Key,
q => string.Join(",", q.Value)); 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); return await BaseSearch(options, domain, queryParameters);

View File

@@ -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<string> _uniqueItems;
public AggregatedSearchResult(string contentType, Encoding contentEncoding)
{
ContentType = contentType;
ContentEncoding = contentEncoding;
_uniqueItems = new HashSet<string>();
// 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
{
}
}
}
}
}

View File

@@ -6,8 +6,16 @@ internal class Program
{ {
private static void Main(string[] args) 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 builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
// Add services to the container. // Add services to the container.
builder.Services.AddHttpClient("HttpClient").ConfigurePrimaryHttpMessageHandler(() => builder.Services.AddHttpClient("HttpClient").ConfigurePrimaryHttpMessageHandler(() =>
{ {
@@ -28,6 +36,7 @@ internal class Program
builder.Services.AddControllers(); builder.Services.AddControllers();
builder.Services.AddScoped<TitleQueryService>(); builder.Services.AddScoped<TitleQueryService>();
builder.Services.AddScoped<ProxyService>(); builder.Services.AddScoped<ProxyService>();
builder.Services.AddScoped<TitleMatchingService>();
var app = builder.Build(); var app = builder.Build();

View File

@@ -3,7 +3,8 @@
"http": { "http": {
"commandName": "Project", "commandName": "Project",
"launchBrowser": true, "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": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },

View File

@@ -4,6 +4,7 @@
{ {
private readonly HttpClient _httpClient = clientFactory.CreateClient("HttpClient") ?? throw new ArgumentNullException(); 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"); private readonly string _userAgent = configuration["Settings:UserAgent"] ?? throw new ArgumentException("UserAgent must be set in appsettings.json");
// TODO: Add cache!
public async Task<HttpResponseMessage> ProxyRequestAsync(HttpContext context, string targetUri) public async Task<HttpResponseMessage> ProxyRequestAsync(HttpContext context, string targetUri)
{ {
@@ -35,6 +36,8 @@
try try
{ {
var responseMessage = _httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted); var responseMessage = _httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);
// TODO: Handle 503 etc
responseMessage.EnsureSuccessStatusCode(); responseMessage.EnsureSuccessStatusCode();
// Modify the response content if necessary // Modify the response content if necessary

View File

@@ -0,0 +1,125 @@
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UmlautAdaptarr.Utilities;
namespace UmlautAdaptarr.Services
{
public partial class TitleMatchingService
{
public List<string> GenerateTitleVariations(string germanTitle)
{
var cleanTitle = germanTitle.RemoveAccentButKeepGermanUmlauts();
// Start with base variations including handling umlauts
var baseVariations = new List<string>
{
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<string>
{
withoutDash,
withSpaceInsteadOfDash,
withoutDash.ReplaceGermanUmlautsWithLatinEquivalents(),
withoutDash.RemoveGermanUmlautDots(),
withSpaceInsteadOfDash.ReplaceGermanUmlautsWithLatinEquivalents(),
withSpaceInsteadOfDash.RemoveGermanUmlautDots()
});
}
return baseVariations.Distinct().ToList();
}
public string RenameTitlesInContent(string content, List<string> 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();
}
}

View File

@@ -15,18 +15,21 @@ namespace UmlautAdaptarr.Services
private readonly ILogger<TitleQueryService> _logger; private readonly ILogger<TitleQueryService> _logger;
private readonly string _sonarrHost; private readonly string _sonarrHost;
private readonly string _sonarrApiKey; private readonly string _sonarrApiKey;
private readonly string _umlautAdaptarrApiHost;
public TitleQueryService(HttpClient httpClient, IMemoryCache memoryCache, ILogger<TitleQueryService> logger) public TitleQueryService(IMemoryCache memoryCache, ILogger<TitleQueryService> logger, IConfiguration configuration, IHttpClientFactory clientFactory)
{ {
_httpClient = httpClient; _httpClient = clientFactory.CreateClient("HttpClient") ?? throw new ArgumentNullException();
_cache = memoryCache; _cache = memoryCache;
_logger = logger; _logger = logger;
_sonarrHost = Environment.GetEnvironmentVariable("SONARR_HOST"); _sonarrHost = configuration.GetValue<string>("SONARR_HOST");
_sonarrApiKey = Environment.GetEnvironmentVariable("SONARR_API_KEY"); _sonarrApiKey = configuration.GetValue<string>("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}"; var cacheKey = $"show_{tvdbId}";
if (_cache.TryGetValue(cacheKey, out (bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle) cachedResult)) if (_cache.TryGetValue(cacheKey, out (bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle) cachedResult))
@@ -48,7 +51,7 @@ namespace UmlautAdaptarr.Services
return (false, null, string.Empty); return (false, null, string.Empty);
} }
var expectedTitle = shows[0].title as string; var expectedTitle = (string)shows[0].title;
if (expectedTitle == null) if (expectedTitle == null)
{ {
_logger.LogError($"Sonarr Title for TVDB ID {tvdbId} is null"); _logger.LogError($"Sonarr Title for TVDB ID {tvdbId} is null");
@@ -57,22 +60,23 @@ namespace UmlautAdaptarr.Services
string? germanTitle = null; string? germanTitle = null;
var hasGermanTitle = false; 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 apiUrl = $"{_umlautAdaptarrApiHost}/tvshow_german.php?tvdbid={tvdbId}";
var tvdbResponse = await _httpClient.GetStringAsync(thetvdbUrl); var apiResponse = await _httpClient.GetStringAsync(apiUrl);
var tvdbData = JsonConvert.DeserializeObject<dynamic>(tvdbResponse); var responseData = JsonConvert.DeserializeObject<dynamic>(apiResponse);
if (tvdbData == null) if (responseData == null)
{ {
_logger.LogError($"Parsing UmlautAdaptarr TitleQuery API response for TVDB ID {tvdbId} resulted in null"); _logger.LogError($"Parsing UmlautAdaptarr TitleQuery API response for TVDB ID {tvdbId} resulted in null");
return (false, null, string.Empty); 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; hasGermanTitle = true;
} }
} }
@@ -87,11 +91,97 @@ namespace UmlautAdaptarr.Services
var result = (hasGermanUmlaut, germanTitle, expectedTitle); var result = (hasGermanUmlaut, germanTitle, expectedTitle);
_cache.Set(cacheKey, result, new MemoryCacheEntryOptions _cache.Set(cacheKey, result, new MemoryCacheEntryOptions
{ {
Size = 1,
SlidingExpiration = hasGermanTitle ? TimeSpan.FromDays(30) : TimeSpan.FromDays(7) SlidingExpiration = hasGermanTitle ? TimeSpan.FromDays(30) : TimeSpan.FromDays(7)
}); });
return result; 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<dynamic>(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<dynamic>(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);
}
}
} }
} }

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization> <InvariantGlobalization>true</InvariantGlobalization>
<UserSecretsId>c5f05dc6-731e-425e-8b8c-a4c09f440adc</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -12,8 +13,4 @@
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
</ItemGroup>
</Project> </Project>

View File

@@ -9,13 +9,28 @@ namespace UmlautAdaptarr.Utilities
{ {
return context.Request.Query[key].FirstOrDefault() ?? string.Empty; 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) public static string RemoveAccentButKeepGermanUmlauts(this string text)
{ {
// TODO: evaluate if this is needed (here) var normalizedString = text.Normalize(NormalizationForm.FormD);
var stringWithoutSz = text.Replace("ß", "ss");
var normalizedString = stringWithoutSz.Normalize(NormalizationForm.FormD);
var stringBuilder = new StringBuilder(); var stringBuilder = new StringBuilder();
foreach (var c in normalizedString) foreach (var c in normalizedString)
@@ -43,6 +58,18 @@ namespace UmlautAdaptarr.Utilities
.Replace("ß", "ss"); .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) public static bool HasGermanUmlauts(this string text)
{ {
if (text == null) return false; if (text == null) return false;

View File

@@ -15,6 +15,6 @@
}, },
"Settings": { "Settings": {
"UserAgent": "UmlautAdaptarr/1.0", "UserAgent": "UmlautAdaptarr/1.0",
"TitleQueryHost": "https://umlautadaptarr.pcjones.de" "UmlautAdaptarrApiHost": "https://umlautadaptarr.pcjones.de/api/v1"
} }
} }

22
test.txt Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:newznab="http://www.newznab.com/DTD/2010/feeds/attributes/">
<channel>
<atom:link href="https://scenenzbs.com/api/?t=tvsearch&amp;cat=5030%2c5040%2c5100&amp;extended=1&amp;apikey=c1e3d46f8df56a904cc35a6e3ccbc401&amp;offset=0&amp;limit=100&amp;tvdbid=323168&amp;tvmazeid=7194&amp;season=1&amp;ep=130" rel="self" type="application/rss+xml" />
<title>SceneNZBs</title>
<description>SceneNZBs Feed</description>
<link>https://scenenzbs.com/</link>
<language>en-gb</language>
<webMaster>support@scenenzbs.com (SceneNZBs)</webMaster>
<category></category>
<image>
<url>https://scenenzbs.com/templates/default/images/banner.jpg</url>
<title>SceneNZBs</title>
<link>https://scenenzbs.com/</link>
<description>Visit SceneNZBs - </description>
</image>
<newznab:apilimits apicurrent="342" apimax="10000" grabcurrent="3" grabmax="2000" apioldesttime="Tue, 06 Feb 2024 01:33:47 +0100" graboldesttime="Tue, 06 Feb 2024 17:54:25 +0100"/>
<newznab:response offset="0" total="0" />
</channel>
</rss>