### 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)
```