Intermediate commit :-)
This commit is contained in:
@@ -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<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 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<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]
|
||||
public async Task<IActionResult> 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);
|
||||
|
||||
45
UmlautAdaptarr/Models/AggregatedSearchResult.cs
Normal file
45
UmlautAdaptarr/Models/AggregatedSearchResult.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<TitleQueryService>();
|
||||
builder.Services.AddScoped<ProxyService>();
|
||||
builder.Services.AddScoped<TitleMatchingService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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<HttpResponseMessage> 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
|
||||
|
||||
125
UmlautAdaptarr/Services/TitleMatchingService.cs
Normal file
125
UmlautAdaptarr/Services/TitleMatchingService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,21 @@ namespace UmlautAdaptarr.Services
|
||||
private readonly ILogger<TitleQueryService> _logger;
|
||||
private readonly string _sonarrHost;
|
||||
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;
|
||||
_logger = logger;
|
||||
|
||||
_sonarrHost = Environment.GetEnvironmentVariable("SONARR_HOST");
|
||||
_sonarrApiKey = Environment.GetEnvironmentVariable("SONARR_API_KEY");
|
||||
_sonarrHost = configuration.GetValue<string>("SONARR_HOST");
|
||||
_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}";
|
||||
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<dynamic>(tvdbResponse);
|
||||
var apiUrl = $"{_umlautAdaptarrApiHost}/tvshow_german.php?tvdbid={tvdbId}";
|
||||
var apiResponse = await _httpClient.GetStringAsync(apiUrl);
|
||||
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");
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<UserSecretsId>c5f05dc6-731e-425e-8b8c-a4c09f440adc</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -12,8 +13,4 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Models\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
},
|
||||
"Settings": {
|
||||
"UserAgent": "UmlautAdaptarr/1.0",
|
||||
"TitleQueryHost": "https://umlautadaptarr.pcjones.de"
|
||||
"UmlautAdaptarrApiHost": "https://umlautadaptarr.pcjones.de/api/v1"
|
||||
}
|
||||
}
|
||||
|
||||
22
test.txt
Normal file
22
test.txt
Normal 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&cat=5030%2c5040%2c5100&extended=1&apikey=c1e3d46f8df56a904cc35a6e3ccbc401&offset=0&limit=100&tvdbid=323168&tvmazeid=7194&season=1&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>
|
||||
Reference in New Issue
Block a user