Projektdateien hinzufügen.
This commit is contained in:
25
UmlautAdaptarr.sln
Normal file
25
UmlautAdaptarr.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.8.34408.163
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UmlautAdaptarr", "UmlautAdaptarr\UmlautAdaptarr.csproj", "{5561BAF7-C3DD-4E27-B8DA-25559CB098B6}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{5561BAF7-C3DD-4E27-B8DA-25559CB098B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5561BAF7-C3DD-4E27-B8DA-25559CB098B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5561BAF7-C3DD-4E27-B8DA-25559CB098B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5561BAF7-C3DD-4E27-B8DA-25559CB098B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {7BE3DEF8-5290-4252-A74D-1E3BEAF58990}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
34
UmlautAdaptarr/Controllers/CapsController.cs
Normal file
34
UmlautAdaptarr/Controllers/CapsController.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using UmlautAdaptarr.Services;
|
||||||
|
using UmlautAdaptarr.Utilities;
|
||||||
|
|
||||||
|
namespace UmlautAdaptarr.Controllers
|
||||||
|
{
|
||||||
|
public class CapsController(ProxyService proxyService) : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ProxyService _proxyService = proxyService;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Caps([FromRoute] string options, [FromRoute] string domain, [FromQuery] string? apikey)
|
||||||
|
{
|
||||||
|
if (!UrlUtilities.IsValidDomain(domain))
|
||||||
|
{
|
||||||
|
return NotFound($"{domain} is not a valid URL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestUrl = UrlUtilities.BuildUrl(domain, "caps", apikey);
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
return Content(content, contentType, encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
UmlautAdaptarr/Controllers/SearchController.cs
Normal file
93
UmlautAdaptarr/Controllers/SearchController.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Text;
|
||||||
|
using UmlautAdaptarr.Services;
|
||||||
|
using UmlautAdaptarr.Utilities;
|
||||||
|
|
||||||
|
namespace UmlautAdaptarr.Controllers
|
||||||
|
{
|
||||||
|
public abstract class SearchControllerBase(ProxyService proxyService) : ControllerBase
|
||||||
|
{
|
||||||
|
protected readonly ProxyService _proxyService = proxyService;
|
||||||
|
|
||||||
|
protected async Task<IActionResult> BaseSearch(string options, string domain, IDictionary<string, string> queryParameters)
|
||||||
|
{
|
||||||
|
if (!UrlUtilities.IsValidDomain(domain))
|
||||||
|
{
|
||||||
|
return NotFound($"{domain} is not a valid URL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
return Content(content, contentType, encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchController(ProxyService proxyService, TitleQueryService titleQueryService) : SearchControllerBase(proxyService)
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> MovieSearch([FromRoute] string options, [FromRoute] string domain)
|
||||||
|
{
|
||||||
|
var queryParameters = HttpContext.Request.Query.ToDictionary(
|
||||||
|
q => q.Key,
|
||||||
|
q => string.Join(",", q.Value));
|
||||||
|
return await BaseSearch(options, domain, queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GenericSearch([FromRoute] string options, [FromRoute] string domain)
|
||||||
|
{
|
||||||
|
var queryParameters = HttpContext.Request.Query.ToDictionary(
|
||||||
|
q => q.Key,
|
||||||
|
q => string.Join(",", q.Value));
|
||||||
|
return await BaseSearch(options, domain, queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> BookSearch([FromRoute] string options, [FromRoute] string domain)
|
||||||
|
{
|
||||||
|
var queryParameters = HttpContext.Request.Query.ToDictionary(
|
||||||
|
q => q.Key,
|
||||||
|
q => string.Join(",", q.Value));
|
||||||
|
return await BaseSearch(options, domain, queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> TVSearch([FromRoute] string options, [FromRoute] string domain)
|
||||||
|
{
|
||||||
|
var queryParameters = HttpContext.Request.Query.ToDictionary(
|
||||||
|
q => q.Key,
|
||||||
|
q => string.Join(",", q.Value));
|
||||||
|
|
||||||
|
|
||||||
|
if (queryParameters.TryGetValue("tvdbid", out string tvdbId))
|
||||||
|
{
|
||||||
|
var (HasGermanUmlaut, GermanTitle, ExpectedTitle) = await titleQueryService.QueryShow(tvdbId);
|
||||||
|
|
||||||
|
if (GermanTitle == null && ExpectedTitle == null)
|
||||||
|
{
|
||||||
|
return NotFound($"Show with TVDB ID {tvdbId} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return await BaseSearch(options, domain, queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> MusicSearch([FromRoute] string options, [FromRoute] string domain)
|
||||||
|
{
|
||||||
|
var queryParameters = HttpContext.Request.Query.ToDictionary(
|
||||||
|
q => q.Key,
|
||||||
|
q => string.Join(",", q.Value));
|
||||||
|
return await BaseSearch(options, domain, queryParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
UmlautAdaptarr/Program.cs
Normal file
70
UmlautAdaptarr/Program.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using System.Net;
|
||||||
|
using UmlautAdaptarr.Routing;
|
||||||
|
using UmlautAdaptarr.Services;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
private static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
builder.Services.AddHttpClient("HttpClient").ConfigurePrimaryHttpMessageHandler(() =>
|
||||||
|
{
|
||||||
|
var handler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli
|
||||||
|
};
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddMemoryCache(options =>
|
||||||
|
{
|
||||||
|
options.SizeLimit = 500;
|
||||||
|
});
|
||||||
|
builder.Services.AddSingleton<ILogger, Logger<TitleQueryService>>();
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddScoped<TitleQueryService>();
|
||||||
|
builder.Services.AddScoped<ProxyService>();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllerRoute(name: "caps",
|
||||||
|
pattern: "{options}/{*domain}",
|
||||||
|
defaults: new { controller = "Caps", action = "Caps" },
|
||||||
|
constraints: new { t = new TRouteConstraint("caps") });
|
||||||
|
|
||||||
|
app.MapControllerRoute(name: "movie-search",
|
||||||
|
pattern: "{options}/{*domain}",
|
||||||
|
defaults: new { controller = "Search", action = "MovieSearch" },
|
||||||
|
constraints: new { t = new TRouteConstraint("movie") });
|
||||||
|
|
||||||
|
app.MapControllerRoute(name: "tv-search",
|
||||||
|
pattern: "{options}/{*domain}",
|
||||||
|
defaults: new { controller = "Search", action = "TVSearch" },
|
||||||
|
constraints: new { t = new TRouteConstraint("tvsearch") });
|
||||||
|
|
||||||
|
app.MapControllerRoute(name: "music-search",
|
||||||
|
pattern: "{options}/{*domain}",
|
||||||
|
defaults: new { controller = "Search", action = "MusicSearch" },
|
||||||
|
constraints: new { t = new TRouteConstraint("music") });
|
||||||
|
|
||||||
|
app.MapControllerRoute(name: "book-search",
|
||||||
|
pattern: "{options}/{*domain}",
|
||||||
|
defaults: new { controller = "Search", action = "BookSearch" },
|
||||||
|
constraints: new { t = new TRouteConstraint("book") });
|
||||||
|
|
||||||
|
app.MapControllerRoute(name: "generic-search",
|
||||||
|
pattern: "{options}/{*domain}",
|
||||||
|
defaults: new { controller = "Search", action = "GenericSearch" },
|
||||||
|
constraints: new { t = new TRouteConstraint("search") });
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
41
UmlautAdaptarr/Properties/launchSettings.json
Normal file
41
UmlautAdaptarr/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "optionsTODO/example.com/api?t=movie&apikey=132&imdbid=123&limit=100",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5182"
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7068;http://localhost:5182"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:49485",
|
||||||
|
"sslPort": 44380
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
UmlautAdaptarr/Routing/TRouteConstraint.cs
Normal file
23
UmlautAdaptarr/Routing/TRouteConstraint.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace UmlautAdaptarr.Routing
|
||||||
|
{
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
|
public class TRouteConstraint : IRouteConstraint
|
||||||
|
{
|
||||||
|
private readonly string _methodName;
|
||||||
|
|
||||||
|
public TRouteConstraint(string methodName)
|
||||||
|
{
|
||||||
|
_methodName = methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
|
||||||
|
{
|
||||||
|
var t = httpContext.Request.Query["t"].ToString();
|
||||||
|
|
||||||
|
return t == _methodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
59
UmlautAdaptarr/Services/ProxyService.cs
Normal file
59
UmlautAdaptarr/Services/ProxyService.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
namespace UmlautAdaptarr.Services
|
||||||
|
{
|
||||||
|
public class ProxyService(IHttpClientFactory clientFactory, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
|
||||||
|
public async Task<HttpResponseMessage> ProxyRequestAsync(HttpContext context, string targetUri)
|
||||||
|
{
|
||||||
|
var requestMessage = new HttpRequestMessage();
|
||||||
|
var requestMethod = context.Request.Method;
|
||||||
|
|
||||||
|
if (!HttpMethods.IsGet(requestMethod))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Only GET requests are supported", nameof(requestMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the request headers
|
||||||
|
foreach (var header in context.Request.Headers)
|
||||||
|
{
|
||||||
|
if (header.Key == "User-Agent" && _userAgent.Length != 0)
|
||||||
|
{
|
||||||
|
requestMessage.Headers.TryAddWithoutValidation(header.Key, $"{header.Value} + {_userAgent}");
|
||||||
|
}
|
||||||
|
else if (!header.Key.Equals("Host", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestMessage.RequestUri = new Uri(targetUri);
|
||||||
|
requestMessage.Method = HttpMethod.Get;
|
||||||
|
|
||||||
|
//var responseMessage = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var responseMessage = _httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);
|
||||||
|
responseMessage.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
// Modify the response content if necessary
|
||||||
|
/*var content = await responseMessage.Content.ReadAsStringAsync();
|
||||||
|
content = ReplaceCharacters(content);
|
||||||
|
responseMessage.Content = new StringContent(content);*/
|
||||||
|
|
||||||
|
return responseMessage;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex.ToString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReplaceCharacters(string input)
|
||||||
|
{
|
||||||
|
return input.Replace("Ä", "AE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
UmlautAdaptarr/Services/TitleQueryService.cs
Normal file
97
UmlautAdaptarr/Services/TitleQueryService.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using UmlautAdaptarr.Utilities;
|
||||||
|
|
||||||
|
namespace UmlautAdaptarr.Services
|
||||||
|
{
|
||||||
|
public class TitleQueryService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
|
private readonly ILogger<TitleQueryService> _logger;
|
||||||
|
private readonly string _sonarrHost;
|
||||||
|
private readonly string _sonarrApiKey;
|
||||||
|
|
||||||
|
public TitleQueryService(HttpClient httpClient, IMemoryCache memoryCache, ILogger<TitleQueryService> logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_cache = memoryCache;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
_sonarrHost = Environment.GetEnvironmentVariable("SONARR_HOST");
|
||||||
|
_sonarrApiKey = Environment.GetEnvironmentVariable("SONARR_API_KEY");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle)> QueryShow(string tvdbId)
|
||||||
|
{
|
||||||
|
var cacheKey = $"show_{tvdbId}";
|
||||||
|
if (_cache.TryGetValue(cacheKey, out (bool hasGermanUmlaut, string? GermanTitle, string ExpectedTitle) cachedResult))
|
||||||
|
{
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = shows[0].title as string;
|
||||||
|
if (expectedTitle == null)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Sonarr Title for TVDB ID {tvdbId} is null");
|
||||||
|
return (false, null, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
string? germanTitle = null;
|
||||||
|
var hasGermanTitle = false;
|
||||||
|
|
||||||
|
if ((string)shows[0].originalLanguage.name != "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);
|
||||||
|
|
||||||
|
if (tvdbData == null)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Parsing UmlautAdaptarr TitleQuery API response for TVDB ID {tvdbId} resulted in null");
|
||||||
|
return (false, null, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tvdbData.status == "success")
|
||||||
|
{
|
||||||
|
germanTitle = tvdbData.germanTitle;
|
||||||
|
hasGermanTitle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
germanTitle = expectedTitle;
|
||||||
|
hasGermanTitle = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasGermanUmlaut = germanTitle?.HasGermanUmlauts() ?? false;
|
||||||
|
|
||||||
|
var result = (hasGermanUmlaut, germanTitle, expectedTitle);
|
||||||
|
_cache.Set(cacheKey, result, new MemoryCacheEntryOptions
|
||||||
|
{
|
||||||
|
SlidingExpiration = hasGermanTitle ? TimeSpan.FromDays(30) : TimeSpan.FromDays(7)
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
19
UmlautAdaptarr/UmlautAdaptarr.csproj
Normal file
19
UmlautAdaptarr/UmlautAdaptarr.csproj
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Models\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
53
UmlautAdaptarr/Utilities/Extensions.cs
Normal file
53
UmlautAdaptarr/Utilities/Extensions.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace UmlautAdaptarr.Utilities
|
||||||
|
{
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static string GetQuery(this HttpContext context, string key)
|
||||||
|
{
|
||||||
|
return context.Request.Query[key].FirstOrDefault() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var c in normalizedString)
|
||||||
|
{
|
||||||
|
var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
|
||||||
|
|
||||||
|
if (unicodeCategory != UnicodeCategory.NonSpacingMark || c == '\u0308')
|
||||||
|
{
|
||||||
|
stringBuilder.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReplaceGermanUmlautsWithLatinEquivalents(this string text)
|
||||||
|
{
|
||||||
|
return text
|
||||||
|
.Replace("Ö", "Oe")
|
||||||
|
.Replace("Ä", "Ae")
|
||||||
|
.Replace("Ü", "Ue")
|
||||||
|
.Replace("ö", "oe")
|
||||||
|
.Replace("ä", "ae")
|
||||||
|
.Replace("ü", "ue")
|
||||||
|
.Replace("ß", "ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasGermanUmlauts(this string text)
|
||||||
|
{
|
||||||
|
if (text == null) return false;
|
||||||
|
var umlauts = new[] { 'ö', 'ä', 'ü', 'Ä', 'Ü', 'Ö', 'ß' };
|
||||||
|
return umlauts.Any(text.Contains);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
UmlautAdaptarr/Utilities/UrlUtilities.cs
Normal file
44
UmlautAdaptarr/Utilities/UrlUtilities.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Web;
|
||||||
|
|
||||||
|
namespace UmlautAdaptarr.Utilities
|
||||||
|
{
|
||||||
|
public partial class UrlUtilities
|
||||||
|
{
|
||||||
|
[GeneratedRegex(@"^(?!http:\/\/)([a-zA-Z0-9]+(\.[a-zA-Z0-9]+)+.*)$")]
|
||||||
|
private static partial Regex UrlMatchingRegex();
|
||||||
|
public static bool IsValidDomain(string domain)
|
||||||
|
{
|
||||||
|
// RegEx für eine einfache URL-Validierung ohne http:// und ohne abschließenden Schrägstrich
|
||||||
|
// Erlaubt optionale Subdomains, Domainnamen und TLDs, aber keine Pfade oder Protokolle
|
||||||
|
var regex = UrlMatchingRegex();
|
||||||
|
return regex.IsMatch(domain) && !domain.EndsWith("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildUrl(string domain, IDictionary<string, string> queryParameters)
|
||||||
|
{
|
||||||
|
var uriBuilder = new UriBuilder("https", domain);
|
||||||
|
|
||||||
|
var query = HttpUtility.ParseQueryString(string.Empty);
|
||||||
|
foreach (var param in queryParameters)
|
||||||
|
{
|
||||||
|
query[param.Key] = param.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uriBuilder.Query = query.ToString();
|
||||||
|
return uriBuilder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildUrl(string domain, string tParameter, string? apiKey = null)
|
||||||
|
{
|
||||||
|
var queryParameters = new Dictionary<string, string>() { { "t", tParameter } };
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(apiKey))
|
||||||
|
{
|
||||||
|
queryParameters["apiKey"] = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BuildUrl(domain, queryParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
UmlautAdaptarr/appsettings.Development.json
Normal file
8
UmlautAdaptarr/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
UmlautAdaptarr/appsettings.json
Normal file
20
UmlautAdaptarr/appsettings.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://localhost:5005"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Settings": {
|
||||||
|
"UserAgent": "UmlautAdaptarr/1.0",
|
||||||
|
"TitleQueryHost": "https://umlautadaptarr.pcjones.de"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user