This commit is contained in:
cheahkhing 2026-03-08 15:20:44 +00:00 committed by GitHub
commit dd00398e47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 86 additions and 45 deletions

View File

@ -87,7 +87,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
}
else
{
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
return await Plugin.Instance.HttpClient.GetAsync(Utils.ResolveUrl(Plugin.Instance.Configuration.TubeArchivistUrl, url), cancellationToken).ConfigureAwait(false);
}
}
}

View File

@ -100,7 +100,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
}
else
{
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
return await Plugin.Instance.HttpClient.GetAsync(Utils.ResolveUrl(Plugin.Instance.Configuration.TubeArchivistUrl, url), cancellationToken).ConfigureAwait(false);
}
}
}

View File

@ -105,7 +105,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
}
else
{
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
return await Plugin.Instance.HttpClient.GetAsync(Utils.ResolveUrl(Plugin.Instance.Configuration.TubeArchivistUrl, url), cancellationToken).ConfigureAwait(false);
}
}
}

View File

@ -98,7 +98,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
}
else
{
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
return await Plugin.Instance.HttpClient.GetAsync(Utils.ResolveUrl(Plugin.Instance.Configuration.TubeArchivistUrl, url), cancellationToken).ConfigureAwait(false);
}
}
}

View File

@ -17,6 +17,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
/// </summary>
public class TubeArchivistApi
{
private const int MaxRedirectCount = 10;
private ILogger _logger;
private HttpClient client;
private static TubeArchivistApi _taApiInstance = null!;
@ -60,6 +61,42 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
return _taApiInstance;
}
private static bool IsRedirectStatusCode(HttpStatusCode statusCode)
{
return statusCode == HttpStatusCode.Moved
|| statusCode == HttpStatusCode.Redirect
|| statusCode == HttpStatusCode.RedirectMethod
|| statusCode == HttpStatusCode.TemporaryRedirect
|| statusCode == HttpStatusCode.PermanentRedirect;
}
private static Uri ResolveTubeArchivistUri(string? url)
{
return Utils.ResolveUrl(Plugin.Instance!.Configuration.TubeArchivistUrl, url);
}
private async Task<HttpResponseMessage> GetAsync(string url)
{
var resolvedUrl = ResolveTubeArchivistUri(url);
var response = await client.GetAsync(resolvedUrl).ConfigureAwait(true);
var redirectCount = 0;
while (IsRedirectStatusCode(response.StatusCode) && response.Headers.Location != null && redirectCount < MaxRedirectCount)
{
resolvedUrl = ResolveTubeArchivistUri(response.Headers.Location.OriginalString);
_logger.LogDebug("Received redirect to: {ResolvedUrl}", resolvedUrl);
response = await client.GetAsync(resolvedUrl).ConfigureAwait(true);
redirectCount++;
}
if (redirectCount >= MaxRedirectCount && IsRedirectStatusCode(response.StatusCode) && response.Headers.Location != null)
{
_logger.LogWarning("Stopped following TubeArchivist redirect chain after {RedirectCount} redirects. Last location: {RedirectLocation}", redirectCount, response.Headers.Location);
}
return response;
}
/// <summary>
/// Retrieves the given channel information from TubeArchivist.
/// </summary>
@ -70,14 +107,9 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
Channel? channel = null;
var channelsEndpoint = "/api/channel/";
var url = new Uri(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + channelsEndpoint + channelId));
var response = await client.GetAsync(url).ConfigureAwait(true);
while (response.StatusCode == HttpStatusCode.Moved)
{
url = response.Headers.Location;
_logger.LogDebug("{Message}", "Received redirect to: " + url);
response = await client.GetAsync(url).ConfigureAwait(true);
}
var requestPath = channelsEndpoint + channelId;
var url = ResolveTubeArchivistUri(requestPath);
var response = await GetAsync(requestPath).ConfigureAwait(true);
_logger.LogDebug("{Message}", url + ": " + response.StatusCode);
@ -100,14 +132,9 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
Video? video = null;
var videosEndpoint = "/api/video/";
var url = new Uri(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + videosEndpoint + videoId));
var response = await client.GetAsync(url).ConfigureAwait(true);
while (response.StatusCode == HttpStatusCode.Moved)
{
url = response.Headers.Location;
_logger.LogDebug("{Message}", "Received redirect to: " + url);
response = await client.GetAsync(url).ConfigureAwait(true);
}
var requestPath = videosEndpoint + videoId;
var url = ResolveTubeArchivistUri(requestPath);
var response = await GetAsync(requestPath).ConfigureAwait(true);
_logger.LogDebug("{Message}", url + ": " + response.StatusCode);
@ -130,15 +157,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
PingResponse? pong = null;
var pingEndpoint = "/api/ping/";
var url = new Uri(Utils.SanitizeUrl(Plugin.Instance!.Configuration.TubeArchivistUrl + pingEndpoint));
var response = await client.GetAsync(url).ConfigureAwait(true);
while (response.StatusCode == HttpStatusCode.Moved)
{
url = response.Headers.Location;
_logger.LogDebug("{Message}", "Received redirect to: " + url);
response = await client.GetAsync(url).ConfigureAwait(true);
}
var url = ResolveTubeArchivistUri(pingEndpoint);
var response = await GetAsync(pingEndpoint).ConfigureAwait(true);
_logger.LogDebug("{Message}", url + ": " + response.StatusCode);
@ -213,14 +233,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
ResponseContainer<ISet<Playlist>?>? playlists = null;
var playlistsEndpoint = "/api/playlist/";
var url = new Uri(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + playlistsEndpoint));
var response = await client.GetAsync(url).ConfigureAwait(true);
while (response.StatusCode == HttpStatusCode.Moved)
{
url = response.Headers.Location;
_logger.LogInformation("{Message}", "Received redirect to: " + url);
response = await client.GetAsync(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + url)).ConfigureAwait(true);
}
var url = ResolveTubeArchivistUri(playlistsEndpoint);
var response = await GetAsync(playlistsEndpoint).ConfigureAwait(true);
_logger.LogInformation("{Message}", url + ": " + response.StatusCode);
@ -236,14 +250,9 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
while (playlists.Paginate.CurrentPage < lastPage)
{
var nextPage = playlists.Paginate.CurrentPage + 1;
var pagedUrl = new Uri(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + playlistsEndpoint + "?page=" + nextPage));
response = await client.GetAsync(pagedUrl).ConfigureAwait(true);
while (response.StatusCode == HttpStatusCode.Moved)
{
url = response.Headers.Location;
_logger.LogInformation("{Message}", "Received redirect to: " + url);
response = await client.GetAsync(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + url)).ConfigureAwait(true);
}
var nextPagePath = playlistsEndpoint + "?page=" + nextPage;
var pagedUrl = ResolveTubeArchivistUri(nextPagePath);
response = await GetAsync(nextPagePath).ConfigureAwait(true);
_logger.LogInformation("{Message}", pagedUrl + ": " + response.StatusCode);

View File

@ -77,6 +77,38 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
return cleanedUrl;
}
/// <summary>
/// Resolves a URL against the configured TubeArchivist base URL.
/// </summary>
/// <param name="baseUrl">Configured TubeArchivist base URL.</param>
/// <param name="url">The relative or absolute URL to resolve. If null or whitespace, the base URL is returned.</param>
/// <returns>
/// A resolved absolute <see cref="Uri"/>.
/// Absolute URLs pointing to a different origin keep only their path, query and fragment,
/// so requests stay pinned to the configured TubeArchivist host.
/// </returns>
public static Uri ResolveUrl(string baseUrl, string? url)
{
var sanitizedBaseUrl = SanitizeUrl(baseUrl);
var baseUri = new Uri(sanitizedBaseUrl, UriKind.Absolute);
if (string.IsNullOrWhiteSpace(url))
{
return baseUri;
}
if (Uri.TryCreate(url, UriKind.Absolute, out Uri? absoluteUri))
{
if (Uri.Compare(baseUri, absoluteUri, UriComponents.SchemeAndServer, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0)
{
return absoluteUri;
}
return new Uri(baseUri, absoluteUri.PathAndQuery + absoluteUri.Fragment);
}
return new Uri(baseUri, url);
}
/// <summary>
/// Formats episodes and series descriptions replacing newlines with br tags.
/// </summary>