### StartAsync Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/HttpProxyService.md Starts the TCP listener on the configured proxy port and begins handling incoming requests asynchronously. ```APIDOC ## StartAsync ### Description Starts the TCP listener on the configured proxy port and begins handling incoming requests asynchronously. ### Method StartAsync ### Signature public Task StartAsync(CancellationToken cancellationToken) ### Parameters - **cancellationToken** (CancellationToken) - Required - Cancellation token to stop the operation. ### Returns Task.CompletedTask ### Remarks From source code: `_listener = new TcpListener(IPAddress.Any, _options.ProxyPort); _listener.Start(); Task.Run(() => HandleRequests(cancellationToken), cancellationToken);` – the listener listens on all interfaces on the configured ProxyPort. ``` -------------------------------- ### StartAsync method signature Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/HttpProxyService.md Public method that starts the TCP listener on all interfaces using the configured ProxyPort. Returns Task.CompletedTask. ```csharp public Task StartAsync(CancellationToken cancellationToken) ``` -------------------------------- ### Basic aggregation example Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/AggregatedSearchResult.md Shows how to create an AggregatedSearchResult, aggregate two XML responses, and print the resulting content. Duplicate items are ignored. ```csharp using System.Text; using UmlautAdaptarr.Models; var aggregated = new AggregatedSearchResult("application/xml", Encoding.UTF8); aggregated.AggregateItems(""" A.S01E01 B.S01E01 """); aggregated.AggregateItems(""" B.S01E01 C.S01E01 """); Console.WriteLine(aggregated.Content); // Enthält A, B, C – B nur einmal ``` -------------------------------- ### Create an audio SearchItem Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchItem.md Example of creating a SearchItem for an audio album with expectedAuthor and no German title. ```csharp var album = new SearchItem( arrId: 3, externalId: "die-ärzte-best-of-die-ärzte", title: "best of die Ärzte", expectedTitle: "best of die Ärzte", germanTitle: null, mediaType: "audio", aliases: null, expectedAuthor: "Die Ärzte"); ``` -------------------------------- ### Read variations for renaming Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchItem.md Example of iterating over TitleMatchVariations and printing each variation, with a comment showing example output. ```csharp foreach (var variation in item.TitleMatchVariations) { Console.WriteLine(variation); } // z. B. "Game of Thrones - Das Lied von Eis und Feuer", // "Game.of.Thrones.Das.Lied.von.Eis.und.Feuer", ... (Trennzeichen normalisiert) ``` -------------------------------- ### Create a TV SearchItem Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchItem.md Example of creating a SearchItem for a TV show with German title and aliases, and printing HasUmlaut and TitleMatchVariations length. ```csharp using UmlautAdaptarr.Models; var item = new SearchItem( arrId: 12, externalId: "385925", title: "Game of Thrones", expectedTitle: "Game of Thrones", germanTitle: "Game of Thrones - Das Lied von Eis und Feuer", mediaType: "tv", aliases: new[] { "Game of Thrones - Das Lied von Eis und Feuer" }); Console.WriteLine(item.HasUmlaut); // false Console.WriteLine(item.TitleMatchVariations.Length); // mehrere Varianten inkl. Umlaut-Äquivalente ``` -------------------------------- ### Title-based TV search with error handling Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchItemLookupService.md Example of using GetOrFetchSearchItemByTitle for TV, with null check and exception handling. ```csharp try { var show = await lookup.GetOrFetchSearchItemByTitle("tv", "Game of Thrones"); if (show == null) { Console.WriteLine("Kein Suchobjekt gefunden – Titel wird nicht umbenannt."); } } catch (Exception ex) { Console.WriteLine($"Fehler bei der Auflösung: {ex.Message}"); } ``` -------------------------------- ### GET /{apiKey}/{indexerDomain}?t=caps&apikey={indexerApiKey} Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Capabilities-Route der Newznab/Torznab-kompatiblen Schnittstelle. Diese Route liefert die Fähigkeiten des Indexers. Die Authentifizierung ist hier praktisch deaktiviert, da die Bedingung im Quellcode nie wahr wird. ```APIDOC ## [GET] /{apiKey}/{indexerDomain}?t=caps&apikey={indexerApiKey} ### Description Capabilities-Route der Newznab/Torznab-kompatiblen Schnittstelle. Diese Route liefert die Fähigkeiten des Indexers. Die Authentifizierung ist hier praktisch deaktiviert, da die Bedingung im Quellcode nie wahr wird. ### Method GET ### Endpoint `/{apiKey}/{indexerDomain}?t=caps&apikey={indexerApiKey}` ### Parameters #### Path Parameters - **apiKey** (string) - Required - Der UmlautAdaptarr-eigene API-Key (`Settings__ApiKey`). Bei nicht gesetztem Key wird `_` verwendet. - **indexerDomain** (string) - Required - Domain des echten Indexers (z. B. `scenenzbs.com`); optional mit Pfad/Port gemäß `UrlUtilities.IsValidDomain`. #### Query Parameters - **t** (string) - Required - Muss `caps` sein. - **apikey** (string) - Required - Der API-Key des echten Indexers, wird an den echten Indexer weitergegeben. ### Request Example ``` http://localhost:5005/_/scenenzbs.com?t=caps&apikey= ``` ### Response #### Success Response (200) - **body** (string) - Die Antwort des echten Indexers. #### Response Example ``` ... ``` ### Error Handling - **404 Not Found** - Wenn die Domain nicht gültig ist: `if (!domain.StartsWith("localhost") && !UrlUtilities.IsValidDomain(domain))`. ``` -------------------------------- ### GET /{apiKey}/{indexerDomain}?t={type}&&apikey={indexerApiKey} Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Newznab/Torznab-kompatible Such- und Capabilities-Routen. Diese Endpunkte werden verwendet, um Suchanfragen an einen echten Indexer weiterzuleiten und Capabilities abzurufen. Die Authentifizierung erfolgt über den API-Key in der Route. ```APIDOC ## [GET] /{apiKey}/{indexerDomain}?t={type}&&apikey={indexerApiKey} ### Description Newznab/Torznab-kompatible Such- und Capabilities-Routen. Diese Endpunkte werden verwendet, um Suchanfragen an einen echten Indexer weiterzuleiten und Capabilities abzurufen. Die Authentifizierung erfolgt über den API-Key in der Route. ### Method GET ### Endpoint `/{apiKey}/{indexerDomain}?t={type}&&apikey={indexerApiKey}` ### Parameters #### Path Parameters - **apiKey** (string) - Required - Der UmlautAdaptarr-eigene API-Key (`Settings__ApiKey`). Bei nicht gesetztem Key wird `_` verwendet. Wird gegen `GlobalOptions.ApiKey` geprüft. - **indexerDomain** (string) - Required - Domain des echten Indexers (z. B. `scenenzbs.com`); optional mit Pfad/Port gemäß `UrlUtilities.IsValidDomain`. #### Query Parameters - **t** (string) - Required - Der Typ der Anfrage (z. B. `search`). - **parameter** (string) - Optional - Weitere Parameter, die an den echten Indexer weitergegeben werden. - **apikey** (string) - Required - Der API-Key des echten Indexers, wird an den echten Indexer weitergegeben. ### Request Example ``` http://localhost:5005/_/scenenzbs.com?t=search&apikey= ``` ### Response #### Success Response (200) - **body** (string) - Die Antwort des echten Indexers. #### Response Example ``` ... ``` ### Error Handling - **401 Unauthorized** - Wenn der Routen-`apiKey` nicht mit `GlobalOptions.ApiKey` übereinstimmt und `ApiKey` nicht leer ist. Body: `"Unauthorized: Invalid or missing API key."` - **404 Not Found** - Wenn die Domain nicht gültig ist (für CapsController: `if (!domain.StartsWith("localhost") && !UrlUtilities.IsValidDomain(domain))`). ``` -------------------------------- ### Basic forwarding from a controller Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ProxyRequestService.md Example of using ProxyRequestService from a controller to forward a request. Builds a URL with UrlUtilities.BuildUrl, calls ProxyRequestAsync, and returns the response content as XML. ```csharp using Microsoft.AspNetCore.Http; using UmlautAdaptarr.Services; public class MyProxy(ProxyRequestService proxy) { public async Task Forward(HttpContext context, string indexerDomain) { var url = UmlautAdaptarr.Utilities.UrlUtilities.BuildUrl(indexerDomain, "tvsearch", "myKey"); var response = await proxy.ProxyRequestAsync(context, url); var content = await response.Content.ReadAsStringAsync(); return new ContentResult { Content = content, ContentType = "application/xml", StatusCode = (int)response.StatusCode }; } } ``` -------------------------------- ### Error handling for non-GET requests Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ProxyRequestService.md Shows that a POST request is rejected with an ArgumentException. The exception message is "Only GET requests are supported". ```csharp try { // Eine POST-Anfrage wird abgelehnt: var response = await proxy.ProxyRequestAsync(httpContext, "https://example.com?t=search"); } catch (ArgumentException ex) { Console.WriteLine(ex.Message); // "Only GET requests are supported" } ``` -------------------------------- ### GET /{apiKey}/{*domain}?t=caps&apikey={apikey} Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/CapsController.md Handles the Newznab t=caps request. Validates the domain, proxies the request to the upstream indexer, and returns the indexer's response (typically XML) unchanged. ```APIDOC ## [GET] /{apiKey}/{*domain}?t=caps&apikey={apikey} ### Description Handles the Newznab `t=caps` request. Validates the domain, proxies the request to the upstream indexer via `ProxyRequestService`, and returns the indexer's response (typically XML) unchanged. ### Method GET ### Endpoint `/{apiKey}/{*domain}?t=caps&apikey={apikey}` ### Parameters #### Path Parameters - **apiKey** (string) - Required - API key from the route segment `{apiKey}`. - **domain** (string) - Required - Indexer domain from the catch-all segment `{*domain}`. #### Query Parameters - **apikey** (string?) - Optional - API key as a query parameter (for forwarding to the indexer). ### Request Example ``` GET http://localhost:5005/_/scenenzbs.com?t=caps&apikey=myIndexerKey ``` ### Response #### Success Response (200) Returns the upstream indexer's response as a `ContentResult` (XML/Text). #### Response Example ```xml ``` #### Error Responses - **404 Not Found** - If the domain is not valid (not starting with `localhost` and failing `UrlUtilities.IsValidDomain`). Response body: `"{domain} is not a valid URL."` - **401 Unauthorized** - Intended for invalid/missing API key, but the condition in the source is always false (dead code), so this is never returned. ### Notes - The authentication check `if (!string.IsNullOrEmpty(apikey) && !apiKey.Equals(apiKey))` compares the route parameter with itself, always evaluating to `true`, so the `Unauthorized` response is never triggered. - Local hosts (domains starting with `localhost`) are allowed without domain validation. - The request is forwarded to `https://?t=caps[&apikey=...]` via `ProxyRequestService.ProxyRequestAsync`. - The response encoding is determined from the `Content-Type` charset (fallback UTF-8), media type fallback `application/xml`. - No explicit exceptions are thrown; errors are handled in `ProxyRequestService` and returned as HTTP 500. ``` -------------------------------- ### BuildUrl Überladung 1 Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/UrlUtilities-and-Helper.md Erstellt eine URL mit https://-Schema und Query-Parametern aus einem Dictionary. Verwendet UriBuilder und HttpUtility.ParseQueryString. Beispiel: BuildUrl("example.com", {["t"]="search"}) → https://example.com/?t=search. ```csharp public static string BuildUrl(string domain, IDictionary queryParameters) ``` -------------------------------- ### Fetch single item by TVDB ID Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ArrClientBase-and-Clients.md Gets the first Sonarr instance from the factory and fetches an item by its TVDB ID (example: 385925). Prints the title and German title if found, otherwise indicates the series is not present in Sonarr. ```csharp var sonarr = factory.SonarrInstances.First(); var item = await sonarr.FetchItemByExternalIdAsync("385925"); if (item != null) { Console.WriteLine($"{item.Title} | deutsch: {item.GermanTitle}"); } else { Console.WriteLine("Serie nicht in Sonarr vorhanden."); } ``` -------------------------------- ### Method Guard: Only GET requests are supported Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ProxyRequestService.md Throws ArgumentException if the request method is not GET. The method name is passed as paramName. ```csharp if (!HttpMethods.IsGet(context.Request.Method)) throw new ArgumentException("Only GET requests are supported", context.Request.Method); ``` -------------------------------- ### Startsequenz und Middleware in Program.cs Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/Program.md Konfiguriert die Middleware-Pipeline: zeigt das ASCII-Logo, führt optional einen IP-Leak-Test aus (IpLeakTest:Enabled), initialisiert den statischen Logger und aktiviert HTTPS-Redirect sowie Authorization. ```csharp var app = builder.Build(); Helper.ShowLogo(); if (app.Configuration.GetValue("IpLeakTest:Enabled")) { await Helper.ShowInformation(); } GlobalStaticLogger.Initialize(app.Services.GetService()!); app.UseHttpsRedirection(); app.UseAuthorization(); ``` -------------------------------- ### ReadarrClient Options property Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ArrClientBase-and-Clients.md Gets the ReadarrInstanceOptions for the client instance. ```csharp public ReadarrInstanceOptions Options { get; init; } ``` -------------------------------- ### ShowLogo method Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/UrlUtilities-and-Helper.md Displays the ASCII 'UmlautAdaptarr' logo on the console. Called in Program.MainAsync before startup. ```csharp public static void ShowLogo() ``` -------------------------------- ### TV-Suche mit TVDB-ID (HTTP GET) Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchController.md Typischer Aufruf von außen über HTTP GET, um eine TV-Suche mit einer TVDB-ID durchzuführen. Der Endpunkt ist `/_/tv-vault` mit den Parametern `t=tvsearch`, `tvdbid`, `cat`, `apikey`, `limit` und `offset`. ```http GET http://localhost:5005/_/tv-vault?t=tvsearch&tvdbid=385925&cat=5000&apikey=xxx&limit=100&offset=0 ``` -------------------------------- ### DI- und Service-Registrierung in MainAsync Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/Program.md Konfiguriert den WebApplication-Builder, Serilog, HttpClient mit Decompression, MemoryCache, Controller und alle Dienste. Registriert Hosted Services und HttpProxyService als IHostedService. ```csharp var builder = WebApplication.CreateBuilder(args); var configuration = builder.Configuration; ConfigureLogger(configuration); builder.Services.AddSerilog(); builder.Services.AddHttpClient("HttpClient").ConfigurePrimaryHttpMessageHandler(() => { var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli }; return handler; }); builder.Services.AddMemoryCache(options => { // TODO cache size limit? option? //options.SizeLimit = 20000; }); builder.Services.AllowResolvingKeyedServicesAsDictionary(); builder.Services.AddControllers(); builder.AddTitleLookupService(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); await builder.AddSonarrSupport(); await builder.AddLidarrSupport(); await builder.AddReadarrSupport(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); ``` -------------------------------- ### Create HttpRequestMessage Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ProxyRequestService.md Creates a new GET request message with the target URI. ```csharp new HttpRequestMessage with RequestUri = new Uri(targetUri), Method = HttpMethod.Get. ``` -------------------------------- ### Static logger usage Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/UrlUtilities-and-Helper.md Shows how to initialize the static logger and use it to log a message from static code. ```csharp GlobalStaticLogger.Initialize(loggerFactory); GlobalStaticLogger.Logger.LogInformation("Meldung aus statischem Code"); ``` -------------------------------- ### GET /_/tv-vault Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchController.md Performs a TV search using a TVDB ID. This is the typical external call to the search controller. ```APIDOC ## [GET] /_/tv-vault ### Description Performs a TV search using a TVDB ID. This is the typical external call to the search controller. ### Method GET ### Endpoint /_/tv-vault ### Parameters #### Query Parameters - **t** (string) - Required - Search type, must be `tvsearch`. - **tvdbid** (integer) - Required - The TVDB ID of the show to search for. - **cat** (integer) - Required - Category ID (e.g., 5000 for TV). - **apikey** (string) - Required - API key for authentication. - **limit** (integer) - Optional - Maximum number of results to return. - **offset** (integer) - Optional - Number of results to skip for pagination. ### Request Example ``` GET http://localhost:5005/_/tv-vault?t=tvsearch&tvdbid=385925&cat=5000&apikey=xxx&limit=100&offset=0 ``` ### Response #### Success Response (200) Returns the search results in RSS format. #### Response Example ``` (Not provided in source) ``` ``` -------------------------------- ### appsettings.json array configuration for multiple Sonarr instances Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/configuration.md Shows how to configure multiple Sonarr instances in appsettings.json using an array. Each object requires Enabled, Name, Host, and ApiKey. The Enabled flag must be true for the instance to be registered. ```json "Sonarr": [ { "Enabled": false, "Name": "Sonarr", "Host": "...", "ApiKey": "..." }, { "Enabled": false, "Name": "Sonarr 4k", "Host": "...", "ApiKey": "..." } ] ``` -------------------------------- ### Constructor Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ArrApplicationFactory.md Initializes the ArrApplicationFactory with a dictionary of keyed *arr instances and a logger. It sorts the instances by type and throws an exception if no instances are present. ```APIDOC ## Constructor ### Description Initializes the ArrApplicationFactory with all registered *arr instances and a logger. The instances are sorted by concrete client type (Sonarr, Lidarr, Readarr). If no instances are present, an exception is thrown. ### Signature ```csharp public ArrApplicationFactory(IDictionary arrApplications, ILogger logger) ``` ### Parameters - **arrApplications** (IDictionary) - Required - All keyed registered *arr instances (key = instance name) - **logger** (ILogger) - Required - Logger ### Exceptions - **Exception** - Thrown when no *arr instances are successfully initialized. Message: "No ArrApplication could be successfully initialized. This could be due to a faulty configuration" ``` -------------------------------- ### ConfigureLogger: Serilog-Konfiguration mit Filtern Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/Program.md Konfiguriert Serilog mit Konfiguration aus appsettings.json, Konsolenausgabe mit Zeitstempel. In RELEASE-Builds werden ASP.NET-Core-Quellen ausgeblendet. Immer ausgeblendet werden System.Net.Http.HttpClient und Microsoft.Extensions.Http.DefaultHttpClientFactory, um API-Keys nicht zu loggen. ApiKeyMaskingEnricher ist auskommentiert (TODO). ```csharp private static void ConfigureLogger(ConfigurationManager configuration) { Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .WriteTo.Console(outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") #if RELEASE .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.Mvc")) .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.Routing")) .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.Diagnostics")) .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.Hosting")) #endif // TODO workaround to not log api keys .Filter.ByExcluding(Matching.FromSource("System.Net.Http.HttpClient")) .Filter.ByExcluding(Matching.FromSource("Microsoft.Extensions.Http.DefaultHttpClientFactory")) //.Enrich.With(new ApiKeyMaskingEnricher("appsettings.json")) // TODO - Not working currently .CreateLogger(); } ``` -------------------------------- ### URL building and redaction with UrlUtilities Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/UrlUtilities-and-Helper.md Shows how to build a URL with BuildUrl, validate a domain with IsValidDomain, and redact the API key with RedactApiKey. The output comments show expected results. ```csharp using UmlautAdaptarr.Utilities; var url = UrlUtilities.BuildUrl("example-indexer.com", "tvsearch", "secret"); // "https://example-indexer.com/?t=tvsearch&apikey=secret" bool ok = UrlUtilities.IsValidDomain("sub.example-indexer.com"); // true bool bad = UrlUtilities.IsValidDomain("http://example.com"); // false Console.WriteLine(UrlUtilities.RedactApiKey(url)); // "https://example-indexer.com/?t=tvsearch&apikey=[REDACTED]" ``` -------------------------------- ### GET {apiKey}/{domain}?t=book Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Searches for books by forwarding the request to the indexer. Identical to the movie search: no search object lookup, pure forwarding. ```APIDOC ## GET {apiKey}/{domain}?t=book ### Description Searches for books by forwarding the request to the indexer. Identical to the movie search: no search object lookup, pure forwarding. ### Method GET ### Endpoint {apiKey}/{domain}?t=book ### Parameters #### Path Parameters - **apiKey** (string) - Required - API-Key - **domain** (string) - Required - Indexer-Domain #### Query Parameters - **q** (string) - Optional - Search query, forwarded unchanged - **imdbid** (string) - Optional - IMDb ID, forwarded unchanged - **tmdbid** (string) - Optional - TMDb ID, forwarded unchanged ### Response #### Success Response (200) Response as described for TV search: 200/401/404/null. ``` -------------------------------- ### BuildUrl Überladung 2 Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/UrlUtilities-and-Helper.md Erstellt eine URL mit t-Parameter und optionalem apikey-Query-Parameter. Der t-Parameter wird mit dem angegebenen Wert gesetzt, apikey nur wenn nicht null. ```csharp public static string BuildUrl(string domain, string tParameter, string? apiKey = null) ``` -------------------------------- ### GET {apiKey}/{domain}?t=music Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Searches for music by forwarding the request to the indexer. Identical to the movie search: no search object lookup, pure forwarding. ```APIDOC ## GET {apiKey}/{domain}?t=music ### Description Searches for music by forwarding the request to the indexer. Identical to the movie search: no search object lookup, pure forwarding. ### Method GET ### Endpoint {apiKey}/{domain}?t=music ### Parameters #### Path Parameters - **apiKey** (string) - Required - API-Key - **domain** (string) - Required - Indexer-Domain #### Query Parameters - **q** (string) - Optional - Search query, forwarded unchanged - **imdbid** (string) - Optional - IMDb ID, forwarded unchanged - **tmdbid** (string) - Optional - TMDb ID, forwarded unchanged ### Response #### Success Response (200) Response as described for TV search: 200/401/404/null. ``` -------------------------------- ### Constructor: TitleMatchingService(CacheService, ILogger, IOptions) Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/TitleMatchingService.md Initializes a new instance of the TitleMatchingService with the required dependencies for caching, logging, and configuration. ```APIDOC ## Constructor: TitleMatchingService(CacheService, ILogger, IOptions) ### Description Initializes a new instance of the TitleMatchingService with the required dependencies for caching, logging, and configuration. ### Method Constructor ### Signature ```csharp public partial class TitleMatchingService(CacheService cacheService, ILogger logger, IOptions options) ``` ### Parameters - **cacheService** (CacheService) - Required - For the variation cache and the renaming cache. - **logger** (ILogger) - Required - Logger. - **options** (IOptions) - Required - Global options (especially EnableChangedTitleCache). ### Remarks Exposes a public property `_options` of type `GlobalOptions` that holds the resolved options value. ``` -------------------------------- ### IArrApplication Interface Definition Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ArrClientBase-and-Clients.md Definiert die vertraglichen Methoden für alle *arr-Provider: FetchAllItemsAsync, FetchItemByExternalIdAsync und FetchItemByTitleAsync. Jede Methode gibt einen Task zurück, der SearchItem-Objekte oder null liefert. ```csharp public interface IArrApplication { Task> FetchAllItemsAsync(); Task FetchItemByExternalIdAsync(string externalId); Task FetchItemByTitleAsync(string title); } ``` -------------------------------- ### GET {apiKey}/{domain}?t=search Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Performs a generic search with optional category filtering. Resolves a search item based on category IDs before forwarding the request. ```APIDOC ## GET {apiKey}/{domain}?t=search ### Description Performs a generic search with optional category filtering. Resolves a search item based on category IDs before forwarding the request. ### Method GET ### Endpoint {apiKey}/{domain}?t=search ### Parameters #### Path Parameters - **apiKey** (string) - Required - API-Key - **domain** (string) - Required - Indexer-Domain #### Query Parameters - **q** (string) - Optional - Title, required if category matching is used - **cat** (string) - Optional - Comma-separated category IDs - **apikey** (string) - Optional - Indexer API key ### Response #### Success Response (200) Response as described for TV search: 200/401/404/null. ``` -------------------------------- ### ShowInformation method Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/UrlUtilities-and-Helper.md Performs an IP leak test by calling https://ipinfo.io/json with a 10-second timeout, deserializes into IpInfo, and prints IP, hostname, city, region, country, and provider (Org). On errors, shows a red error message. Only runs if IpLeakTest__Enabled is set. ```csharp public static async Task ShowInformation() ``` -------------------------------- ### TV search by TVDB ID Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchItemLookupService.md Example of using GetOrFetchSearchItemByExternalId with media type "tv" and a TVDB ID, printing the title and expected title if found. ```csharp using UmlautAdaptarr.Services; var item = await lookup.GetOrFetchSearchItemByExternalId("tv", "385925"); if (item != null) { Console.WriteLine($"{item.Title} → erwartet: {item.ExpectedTitle}"); } ``` -------------------------------- ### TRouteConstraint class implementation Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/Validator-Routing-DI.md Implements IRouteConstraint. The constructor takes a methodName string that is compared against the 't' query parameter in the Match method. Returns true when they match exactly. ```csharp 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; } } ``` -------------------------------- ### GET titlelookup/?changedTitle= Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Retrieves the original title for a given changed title. Requires the changedTitle query parameter and returns the original title if found in the cache. ```APIDOC ## GET titlelookup/?changedTitle= ### Description Retrieves the original title for a given changed title. Requires the changedTitle query parameter and returns the original title if found in the cache. ### Method GET ### Endpoint titlelookup/?changedTitle= ### Parameters #### Query Parameters - **changedTitle** (string) - Required - The renamed title ### Response #### Success Response (200) - **changedTitle** (string) - The changed title - **originalTitle** (string) - The original title #### Response Example { "changedTitle": "...", "originalTitle": "..." } #### Error Responses - **400 Bad Request** - changedTitle is empty or whitespace. Body: "changedTitle is required." - **404 Not Found** - No cache hit. Body: "Original title not found for '{changedTitle}'." - **501 Not Implemented** - EnableChangedTitleCache is false. Body: "Set SETTINGS__EnableChangedTitleCache to true to use this endpoint." ``` -------------------------------- ### BookSearch HTTP action Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchController.md HTTP GET action for book search. Takes apiKey and domain from route. Calls BaseSearch without a searchItem, so no title resolution is performed. ```csharp [HttpGet] public async Task BookSearch([FromRoute] string apiKey, [FromRoute] string domain) ``` -------------------------------- ### Routenregistrierung für Newznab-Endpunkte in Program.cs Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/Program.md Registriert alle Newznab-Routen mit dem Muster {apiKey}/{*domain} und einem TRouteConstraint auf den Query-Parameter t. Jede Route wird einem Controller-Action zugeordnet, z.B. caps, movie-search, tv-search, music-search, book-search und generic-search. ```csharp app.MapControllerRoute("caps", "{apiKey}/{*domain}", new { controller = "Caps", action = "Caps" }, new { t = new TRouteConstraint("caps") }); app.MapControllerRoute("movie-search", "{apiKey}/{*domain}", new { controller = "Search", action = "MovieSearch" }, new { t = new TRouteConstraint("movie") }); app.MapControllerRoute("tv-search", "{apiKey}/{*domain}", new { controller = "Search", action = "TVSearch" }, new { t = new TRouteConstraint("tvsearch") }); app.MapControllerRoute("music-search", "{apiKey}/{*domain}", new { controller = "Search", action = "MusicSearch" }, new { t = new TRouteConstraint("music") }); app.MapControllerRoute("book-search", "{apiKey}/{*domain}", new { controller = "Search", action = "BookSearch" }, new { t = new TRouteConstraint("book") }); app.MapControllerRoute("generic-search", "{apiKey}/{*domain}", new { controller = "Search", action = "GenericSearch" }, new { t = new TRouteConstraint("search") }); app.Run(); ``` -------------------------------- ### MusicSearch HTTP action Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchController.md HTTP GET action for music search. Takes apiKey and domain from route. Calls BaseSearch without a searchItem, so no title resolution is performed. ```csharp [HttpGet] public async Task MusicSearch([FromRoute] string apiKey, [FromRoute] string domain) ``` -------------------------------- ### MovieSearch HTTP action Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchController.md HTTP GET action for movie search. Takes apiKey and domain from route. Calls BaseSearch without a searchItem, so no title resolution is performed. ```csharp [HttpGet] public async Task MovieSearch([FromRoute] string apiKey, [FromRoute] string domain) ``` -------------------------------- ### Konstruktor-Nebeneffekte: HttpClient und UserAgent Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ProxyRequestService.md Erstellt den benannten HttpClient "HttpClient" und wirft ArgumentNullException, falls die Factory null ist. Liest die UserAgent-Option und wirft ArgumentException, falls sie nicht in appsettings.json gesetzt ist. ```csharp _httpClient = clientFactory.CreateClient("HttpClient") ?? throw new ArgumentNullException(nameof(clientFactory)); _userAgent = _options.UserAgent ?? throw new ArgumentException("UserAgent must be set in appsettings.json"); ``` -------------------------------- ### Book and audio search by search ID Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchItemLookupService.md Example showing how to use GetOrFetchSearchItemByExternalId for book and audio media types, using the Readarr and Lidarr title forms as external IDs. ```csharp // Für Bücher wird die Readarr-Suchform des Titels als externalId verwendet: var bookId = title.GetReadarrTitleForExternalId(); // z. B. aus der GenericSearch-Logik var book = await lookup.GetOrFetchSearchItemByExternalId("book", bookId); // Für Audio analog mit der Lidarr-Suchform: var audioId = title.GetLidarrTitleForExternalId(); var album = await lookup.GetOrFetchSearchItemByExternalId("audio", audioId); ``` -------------------------------- ### Constructor initializes document structure Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/AggregatedSearchResult.md Initializes the ContentDocument with a basic RSS structure containing an empty channel element. ```csharp ContentDocument = new XDocument(new XElement("rss", new XElement("channel"))); ``` -------------------------------- ### GET {apiKey}/{domain}?t=movie Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Searches for movies by forwarding the request to the indexer. No search item lookup is performed; title renaming is handled cache-based per item in TitleMatchingService. ```APIDOC ## GET {apiKey}/{domain}?t=movie ### Description Searches for movies by forwarding the request to the indexer. No search item lookup is performed; title renaming is handled cache-based per item in TitleMatchingService. ### Method GET ### Endpoint {apiKey}/{domain}?t=movie ### Parameters #### Path Parameters - **apiKey** (string) - Required - API-Key - **domain** (string) - Required - Indexer-Domain #### Query Parameters - **q** (string) - Optional - Search query, forwarded unchanged - **imdbid** (string) - Optional - IMDb ID, forwarded unchanged - **tmdbid** (string) - Optional - TMDb ID, forwarded unchanged ### Response #### Success Response (200) Response as described for TV search: 200/401/404/null. ``` -------------------------------- ### C#-Client-Code zur Fehlerbehandlung Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/TitleLookupController.md Zeigt, wie ein HttpClient den Endpunkt aufruft und auf 404, 501 und Erfolg reagiert. Bei 404 wird 'Kein Originaltitel gefunden.' ausgegeben, bei 501 'EnableChangedTitleCache ist deaktiviert.'. ```csharp using var http = new HttpClient(); var response = await http.GetAsync( "http://localhost:5005/titlelookup/?changedTitle=Some%20Unknown%20Title"); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { // Titel wurde nie umbenannt oder ist aus dem Cache gefallen Console.WriteLine("Kein Originaltitel gefunden."); } else if (response.StatusCode == System.Net.HttpStatusCode.NotImplemented) { Console.WriteLine("EnableChangedTitleCache ist deaktiviert."); } else if (response.IsSuccessStatusCode) { var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body); } ``` -------------------------------- ### GET {apiKey}/{domain}?t=tvsearch Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Performs a TV search on the indexer, either by title or by TVDB ID. The request is proxied to the indexer with additional Newznab parameters, and the response is returned as RSS/XML. ```APIDOC ## GET {apiKey}/{domain}?t=tvsearch ### Description Performs a TV search on the indexer, either by title or by TVDB ID. The request is proxied to the indexer with additional Newznab parameters, and the response is returned as RSS/XML. ### Method GET ### Endpoint {apiKey}/{domain}?t=tvsearch ### Parameters #### Path Parameters - **apiKey** (string) - Required - UmlautAdaptarr-API-Key - **domain** (string) - Required - Indexer-Domain #### Query Parameters - **q** (string) - Optional (if no tvdbid) - Titel für Textsuche - **tvdbid** (string) - Optional (alternative to q) - TVDB-ID für ID-Suche - **apikey** (string?) - Optional - Indexer-API-Key (wird weitergegeben) - **cat** (string) - Optional - Newznab category parameter - **limit** (string) - Optional - Newznab limit parameter - **offset** (string) - Optional - Newznab offset parameter - **extended** (string) - Optional - Newznab extended parameter ### Request Example GET /{apiKey}/{domain}?t=tvsearch&q=Breaking+Bad&apikey=optionalIndexerApiKey&cat=5000&limit=100 ### Response #### Success Response (200) - **body** (string) - RSS/XML-Antwort (ggf. aggregiert aus mehreren Titel-Varianten-Suchen) #### Response Example Indexer search results #### Error Response (401) - **body** (string) - "Unauthorized: Invalid or missing API key." #### Error Response (404) - **body** (string) - "{domain} is not a valid URL." ``` -------------------------------- ### GET {apiKey}/{domain}?t=caps Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Retrieves the capabilities of the indexer by proxying the request to the indexer's caps endpoint. This endpoint is used to fetch the indexer's supported features and categories. ```APIDOC ## GET {apiKey}/{domain}?t=caps ### Description Retrieves the capabilities of the indexer by proxying the request to the indexer's caps endpoint. This endpoint is used to fetch the indexer's supported features and categories. ### Method GET ### Endpoint {apiKey}/{domain}?t=caps ### Parameters #### Path Parameters - **apiKey** (string) - Required - UmlautAdaptarr-API-Key - **domain** (string) - Required - Indexer-Domain #### Query Parameters - **apikey** (string?) - Optional - Indexer-API-Key (wird weitergegeben) ### Request Example GET /{apiKey}/{domain}?t=caps&apikey=optionalIndexerApiKey ### Response #### Success Response (200) - **body** (string) - Antwort des Indexers (XML/Text) #### Response Example Indexer response content #### Error Response (404) - **body** (string) - "{domain} is not a valid URL." ``` -------------------------------- ### Environment variables for multiple Sonarr instances (indexed) Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/configuration.md Shows how to set multiple Sonarr instances via environment variables using zero-based indexing (SONARR__0__*, SONARR__1__*). Each instance requires NAME, ENABLED, HOST, and APIKEY variables. ```plaintext SONARR__0__NAME=NAME 1 SONARR__0__ENABLED=true SONARR__0__HOST=http://localhost:8989 SONARR__0__APIKEY=APIKEY SONARR__1__NAME=NAME 2 SONARR__1__ENABLED=false SONARR__1__HOST=http://localhost:8989 SONARR__1__APIKEY=APIKEY ``` -------------------------------- ### GET /titlelookup/ Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/endpoints.md Optionaler Titel-Lookup-Endpunkt. Dieser Endpunkt wird in der Dokumentation erwähnt, aber es werden keine weiteren Details zu Parametern oder Antworten bereitgestellt. ```APIDOC ## [GET] /titlelookup/ ### Description Optionaler Titel-Lookup-Endpunkt. Dieser Endpunkt wird in der Dokumentation erwähnt, aber es werden keine weiteren Details zu Parametern oder Antworten bereitgestellt. ### Method GET ### Endpoint `/titlelookup/` ### Parameters Keine Parameter dokumentiert. ### Request Example Kein Beispiel verfügbar. ### Response Keine Antwortdetails dokumentiert. ``` -------------------------------- ### Abgeleitete Instanzoptions-Klassen Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/types.md Leere abgeleitete Klassen für Lidarr, Readarr und Sonarr, die von GlobalInstanceOptions erben. Sie werden in den Client-Konstruktoren über IOptionsMonitor verwendet. ```csharp public class LidarrInstanceOptions : GlobalInstanceOptions { } public class ReadarrInstanceOptions : GlobalInstanceOptions { } public class SonarrInstanceOptions : GlobalInstanceOptions { } ``` -------------------------------- ### Resolve instance by name with error handling Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ArrApplicationFactory.md Resolve a specific instance by name, fetch items, and handle the ArgumentException thrown for unknown names with the documented message. ```csharp try { var instance = factory.GetArrInstanceByName("Sonarr 4k"); var items = await instance.FetchAllItemsAsync(); } catch (ArgumentException ex) { Console.WriteLine(ex.Message); // "No ArrService with the name Sonarr 4k could be found" } ``` -------------------------------- ### GET titlelookup/ Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/TitleLookupController.md Resolves the original title from a given changed title. Requires the EnableChangedTitleCache option to be enabled. Returns the original title if found, otherwise returns appropriate error statuses. ```APIDOC ## GET titlelookup/ ### Description Resolves the original title from a given changed title. Requires the EnableChangedTitleCache option to be enabled. Returns the original title if found, otherwise returns appropriate error statuses. ### Method GET ### Endpoint titlelookup/ ### Parameters #### Query Parameters - **changedTitle** (string) - Required - The renamed title whose original title is sought. ### Request Example GET /titlelookup/?changedTitle=Game%20of%20Thrones-S01E01 ### Response #### Success Response (200) - **changedTitle** (string) - The provided changed title. - **originalTitle** (string) - The resolved original title. #### Response Example { "changedTitle": "Game of Thrones-S01E01", "originalTitle": "Game of Thrones - Das Lied von Eis und Feuer" } #### Error Responses - **501 Not Implemented** - Returned when EnableChangedTitleCache is false. Body: "Set SETTINGS__EnableChangedTitleCache to true to use this endpoint." - **400 Bad Request** - Returned when changedTitle is empty or whitespace. Body: "changedTitle is required." - **404 Not Found** - Returned when original title not found. Body: "Original title not found for '{changedTitle}'." ``` -------------------------------- ### Error handling for upstream errors (no throw, status 500) Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/ProxyRequestService.md Shows that upstream errors do not throw; instead the response has a non-success status code. The example checks IsSuccessStatusCode and reads the error body. ```csharp var response = await proxy.ProxyRequestAsync(context, targetUri); if (!response.IsSuccessStatusCode) { var errorBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Upstream-Fehler: {errorBody}"); } ``` -------------------------------- ### IsValidDomain Methode Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/UrlUtilities-and-Helper.md Prüft, ob eine Domain dem Regex ^(?!http:\/\/)([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+(:\d+)?(/.*)?)$ entspricht. Erlaubt optionale Subdomains, Domain, TLD, optionalen Port und Pfad, aber kein http://-Präfix. ```csharp public static bool IsValidDomain(string domain) ``` -------------------------------- ### Private TODO-Konstanten im Basis-Controller Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/SearchController.md Zwei private Konstanten, die derzeit mit TODO-Prefix versehen sind und immer true bzw. false sind. Sie steuern die zusätzliche Textsuche. ```csharp private readonly bool TODO_FORCE_TEXT_SEARCH_ORIGINAL_TITLE = true; private readonly bool TODO_FORCE_TEXT_SEARCH_GERMAN_TITLE = false; ``` -------------------------------- ### Konstanten für Cache-Länge und Titel-Umbenennung Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/CacheService.md Definiert Konstanten für die Cache-Länge der Variationssuche, den Präfix für Titel-Umbenennungen und die Ablaufzeit von 12 Stunden für Titel-Umbenennungs-Zuordnungen. ```csharp private const int VARIATION_LOOKUP_CACHE_LENGTH = 5; private const string TitleRenamePrefix = "title_rename_"; private static readonly TimeSpan TitleRenameCacheDuration = TimeSpan.FromHours(12); ``` -------------------------------- ### Primary Constructor des TitleMatchingService Source: https://github.com/pcjones/umlautadaptarr/blob/master/_autodocs/api-reference/TitleMatchingService.md Definiert den primären Konstruktor mit Abhängigkeiten für CacheService, Logger und GlobalOptions. Erforderlich für die Initialisierung des Dienstes. ```csharp public partial class TitleMatchingService(CacheService cacheService, ILogger logger, IOptions options) ```