mirror of
https://github.com/jellyfin/jellyfin-plugin-opensubtitles.git
synced 2024-11-27 00:10:50 +00:00
Merge pull request #87 from MBR-0001/apollo
This commit is contained in:
commit
ee797cbc81
@ -199,12 +199,10 @@ namespace Jellyfin.Plugin.OpenSubtitles
|
|||||||
Comment = i.Attributes?.Comments,
|
Comment = i.Attributes?.Comments,
|
||||||
CommunityRating = i.Attributes?.Ratings,
|
CommunityRating = i.Attributes?.Ratings,
|
||||||
DownloadCount = i.Attributes?.DownloadCount,
|
DownloadCount = i.Attributes?.DownloadCount,
|
||||||
Format = i.Attributes?.Format ?? "srt",
|
Format = "srt",
|
||||||
ProviderName = Name,
|
ProviderName = Name,
|
||||||
ThreeLetterISOLanguageName = request.Language,
|
ThreeLetterISOLanguageName = request.Language,
|
||||||
|
Id = $"srt-{request.Language}-{i.Attributes?.Files[0].FileId}",
|
||||||
// new API (currently) does not return the format
|
|
||||||
Id = $"{i.Attributes?.Format ?? "srt"}-{request.Language}-{i.Attributes?.Files[0].FileId}",
|
|
||||||
Name = i.Attributes?.Release,
|
Name = i.Attributes?.Release,
|
||||||
DateCreated = i.Attributes?.UploadDate,
|
DateCreated = i.Attributes?.UploadDate,
|
||||||
IsHashMatch = i.Attributes?.MovieHashMatch
|
IsHashMatch = i.Attributes?.MovieHashMatch
|
||||||
@ -314,7 +312,7 @@ namespace Jellyfin.Plugin.OpenSubtitles
|
|||||||
|
|
||||||
var res = await OpenSubtitlesHandler.OpenSubtitles.DownloadSubtitleAsync(info.Data.Link, cancellationToken).ConfigureAwait(false);
|
var res = await OpenSubtitlesHandler.OpenSubtitles.DownloadSubtitleAsync(info.Data.Link, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!res.Ok || string.IsNullOrWhiteSpace(res.Data))
|
if (res.Code != HttpStatusCode.OK || string.IsNullOrWhiteSpace(res.Body))
|
||||||
{
|
{
|
||||||
var msg = string.Format(
|
var msg = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
@ -325,7 +323,7 @@ namespace Jellyfin.Plugin.OpenSubtitles
|
|||||||
throw new HttpRequestException(msg);
|
throw new HttpRequestException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SubtitleResponse { Format = format, Language = language, Stream = new MemoryStream(Encoding.UTF8.GetBytes(res.Data)) };
|
return new SubtitleResponse { Format = format, Language = language, Stream = new MemoryStream(Encoding.UTF8.GetBytes(res.Body)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Login(CancellationToken cancellationToken)
|
private async Task Login(CancellationToken cancellationToken)
|
||||||
|
@ -42,12 +42,6 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler.Models
|
|||||||
Body = response.Reason;
|
Body = response.Reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof(T) == typeof(string))
|
|
||||||
{
|
|
||||||
Data = (T)(object)Body;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Ok)
|
if (!Ok)
|
||||||
{
|
{
|
||||||
// don't bother parsing json if HTTP status code is bad
|
// don't bother parsing json if HTTP status code is bad
|
||||||
|
@ -15,12 +15,6 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler.Models.Responses
|
|||||||
[JsonPropertyName("download_count")]
|
[JsonPropertyName("download_count")]
|
||||||
public int DownloadCount { get; set; }
|
public int DownloadCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the subtitle format.
|
|
||||||
/// </summary>
|
|
||||||
[JsonPropertyName("format")]
|
|
||||||
public string? Format { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the subtitle rating.
|
/// Gets or sets the subtitle rating.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -20,12 +20,6 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler.Models.Responses
|
|||||||
[JsonPropertyName("remaining")]
|
[JsonPropertyName("remaining")]
|
||||||
public int Remaining { get; set; }
|
public int Remaining { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the message.
|
|
||||||
/// </summary>
|
|
||||||
[JsonPropertyName("message")]
|
|
||||||
public string? Message { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the reset time.
|
/// Gets or sets the reset time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -26,7 +26,7 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
public static async Task<ApiResponse<LoginInfo>> LogInAsync(string username, string password, string apiKey, CancellationToken cancellationToken)
|
public static async Task<ApiResponse<LoginInfo>> LogInAsync(string username, string password, string apiKey, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var body = new { username, password };
|
var body = new { username, password };
|
||||||
var response = await RequestHandler.SendRequestAsync("/login", HttpMethod.Post, body, null, apiKey, cancellationToken).ConfigureAwait(false);
|
var response = await RequestHandler.SendRequestAsync("/login", HttpMethod.Post, body, null, apiKey, 1, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new ApiResponse<LoginInfo>(response);
|
return new ApiResponse<LoginInfo>(response);
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
|
|
||||||
var headers = new Dictionary<string, string> { { "Authorization", user.Token } };
|
var headers = new Dictionary<string, string> { { "Authorization", user.Token } };
|
||||||
|
|
||||||
var response = await RequestHandler.SendRequestAsync("/logout", HttpMethod.Delete, null, headers, apiKey, cancellationToken).ConfigureAwait(false);
|
var response = await RequestHandler.SendRequestAsync("/logout", HttpMethod.Delete, null, headers, apiKey, 1, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new ApiResponse<object>(response).Ok;
|
return new ApiResponse<object>(response).Ok;
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
|
|
||||||
var headers = new Dictionary<string, string> { { "Authorization", user.Token } };
|
var headers = new Dictionary<string, string> { { "Authorization", user.Token } };
|
||||||
|
|
||||||
var response = await RequestHandler.SendRequestAsync("/infos/user", HttpMethod.Get, null, headers, apiKey, cancellationToken).ConfigureAwait(false);
|
var response = await RequestHandler.SendRequestAsync("/infos/user", HttpMethod.Get, null, headers, apiKey, 1, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new ApiResponse<EncapsulatedUserInfo>(response);
|
return new ApiResponse<EncapsulatedUserInfo>(response);
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
var headers = new Dictionary<string, string> { { "Authorization", user.Token } };
|
var headers = new Dictionary<string, string> { { "Authorization", user.Token } };
|
||||||
|
|
||||||
var body = new { file_id = file };
|
var body = new { file_id = file };
|
||||||
var response = await RequestHandler.SendRequestAsync("/download", HttpMethod.Post, body, headers, apiKey, cancellationToken).ConfigureAwait(false);
|
var response = await RequestHandler.SendRequestAsync("/download", HttpMethod.Post, body, headers, apiKey, 1, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new ApiResponse<SubtitleDownloadInfo>(response, $"file id: {file}");
|
return new ApiResponse<SubtitleDownloadInfo>(response, $"file id: {file}");
|
||||||
}
|
}
|
||||||
@ -101,12 +101,21 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url">the subtitle url.</param>
|
/// <param name="url">the subtitle url.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The subtitle string.</returns>
|
/// <returns>The Http response.</returns>
|
||||||
public static async Task<ApiResponse<string>> DownloadSubtitleAsync(string url, CancellationToken cancellationToken)
|
public static async Task<HttpResponse> DownloadSubtitleAsync(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await RequestHandler.SendRequestAsync(url, HttpMethod.Get, null, null, null, cancellationToken).ConfigureAwait(false);
|
var response = await OpenSubtitlesRequestHelper.Instance!.SendRequestAsync(
|
||||||
|
url,
|
||||||
|
HttpMethod.Get,
|
||||||
|
null,
|
||||||
|
new Dictionary<string, string>(),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new ApiResponse<string>(response);
|
return new HttpResponse
|
||||||
|
{
|
||||||
|
Body = response.body,
|
||||||
|
Code = response.statusCode
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -141,7 +150,7 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
opts.Add(key.ToLower(CultureInfo.InvariantCulture), value.ToLower(CultureInfo.InvariantCulture));
|
opts.Add(key.ToLower(CultureInfo.InvariantCulture), value.ToLower(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
response = await RequestHandler.SendRequestAsync($"/subtitles?{opts}", HttpMethod.Get, null, null, apiKey, cancellationToken).ConfigureAwait(false);
|
response = await RequestHandler.SendRequestAsync($"/subtitles?{opts}", HttpMethod.Get, null, null, apiKey, 1, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
last = new ApiResponse<SearchResult>(response, $"query: {opts}", $"page: {current}");
|
last = new ApiResponse<SearchResult>(response, $"query: {opts}", $"page: {current}");
|
||||||
|
|
||||||
@ -177,7 +186,7 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
/// <returns>The list of languages.</returns>
|
/// <returns>The list of languages.</returns>
|
||||||
public static async Task<ApiResponse<EncapsulatedLanguageList>> GetLanguageList(string apiKey, CancellationToken cancellationToken)
|
public static async Task<ApiResponse<EncapsulatedLanguageList>> GetLanguageList(string apiKey, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await RequestHandler.SendRequestAsync("/infos/languages", HttpMethod.Get, null, null, apiKey, cancellationToken).ConfigureAwait(false);
|
var response = await RequestHandler.SendRequestAsync("/infos/languages", HttpMethod.Get, null, null, apiKey, 1, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new ApiResponse<EncapsulatedLanguageList>(response);
|
return new ApiResponse<EncapsulatedLanguageList>(response);
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,12 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<(string, Dictionary<string, string>, HttpStatusCode)> SendRequestAsync(string url, HttpMethod method, object? body, Dictionary<string, string> headers, CancellationToken cancellationToken)
|
internal async Task<(string body, Dictionary<string, string> headers, HttpStatusCode statusCode)> SendRequestAsync(
|
||||||
|
string url,
|
||||||
|
HttpMethod method,
|
||||||
|
object? body,
|
||||||
|
Dictionary<string, string> headers,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var client = _clientFactory.CreateClient("Default");
|
var client = _clientFactory.CreateClient("Default");
|
||||||
|
|
||||||
|
@ -30,96 +30,96 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler
|
|||||||
/// <param name="body">The request body.</param>
|
/// <param name="body">The request body.</param>
|
||||||
/// <param name="headers">The headers.</param>
|
/// <param name="headers">The headers.</param>
|
||||||
/// <param name="apiKey">The api key.</param>
|
/// <param name="apiKey">The api key.</param>
|
||||||
|
/// <param name="attempt">The request attempt key.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The response.</returns>
|
/// <returns>The response.</returns>
|
||||||
/// <exception cref="ArgumentException">API Key is empty.</exception>
|
/// <exception cref="ArgumentException">API Key is empty.</exception>
|
||||||
public static async Task<HttpResponse> SendRequestAsync(string endpoint, HttpMethod method, object? body, Dictionary<string, string>? headers, string? apiKey, CancellationToken cancellationToken)
|
public static async Task<HttpResponse> SendRequestAsync(
|
||||||
|
string endpoint,
|
||||||
|
HttpMethod method,
|
||||||
|
object? body,
|
||||||
|
Dictionary<string, string>? headers,
|
||||||
|
string? apiKey,
|
||||||
|
int attempt,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var url = endpoint.StartsWith('/') ? BaseApiUrl + endpoint : endpoint;
|
|
||||||
var isFullUrl = url.StartsWith(BaseApiUrl, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
headers ??= new Dictionary<string, string>();
|
headers ??= new Dictionary<string, string>();
|
||||||
|
|
||||||
if (isFullUrl)
|
if (string.IsNullOrWhiteSpace(apiKey))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(apiKey))
|
throw new ArgumentException("Provided API key is blank", nameof(apiKey));
|
||||||
{
|
}
|
||||||
throw new ArgumentException("Provided API key is blank", nameof(apiKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!headers.ContainsKey("Api-Key"))
|
if (!headers.ContainsKey("Api-Key"))
|
||||||
{
|
{
|
||||||
headers.Add("Api-Key", apiKey);
|
headers.Add("Api-Key", apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_hRemaining == 0)
|
if (_hRemaining == 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000 * _hReset, cancellationToken).ConfigureAwait(false);
|
||||||
|
_hRemaining = -1;
|
||||||
|
_hReset = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_requestCount == 40)
|
||||||
|
{
|
||||||
|
var diff = DateTime.UtcNow.Subtract(_windowStart).TotalSeconds;
|
||||||
|
if (diff <= 10)
|
||||||
{
|
{
|
||||||
await Task.Delay(1000 * _hReset, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(1000 * (int)Math.Ceiling(10 - diff), cancellationToken).ConfigureAwait(false);
|
||||||
_hRemaining = -1;
|
_hRemaining = -1;
|
||||||
_hReset = -1;
|
_hReset = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_requestCount == 40)
|
|
||||||
{
|
|
||||||
var diff = DateTime.UtcNow.Subtract(_windowStart).TotalSeconds;
|
|
||||||
if (diff <= 10)
|
|
||||||
{
|
|
||||||
await Task.Delay(1000 * (int)Math.Ceiling(10 - diff), cancellationToken).ConfigureAwait(false);
|
|
||||||
_hRemaining = -1;
|
|
||||||
_hReset = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DateTime.UtcNow.Subtract(_windowStart).TotalSeconds >= 10)
|
|
||||||
{
|
|
||||||
_windowStart = DateTime.UtcNow;
|
|
||||||
_requestCount = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (response, responseHeaders, httpStatusCode) = await OpenSubtitlesRequestHelper.Instance!.SendRequestAsync(url, method, body, headers, cancellationToken).ConfigureAwait(false);
|
if (DateTime.UtcNow.Subtract(_windowStart).TotalSeconds >= 10)
|
||||||
|
|
||||||
if (!isFullUrl)
|
|
||||||
{
|
{
|
||||||
return new HttpResponse
|
_windowStart = DateTime.UtcNow;
|
||||||
{
|
_requestCount = 0;
|
||||||
Body = response,
|
|
||||||
Code = httpStatusCode
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var response = await OpenSubtitlesRequestHelper.Instance!.SendRequestAsync(BaseApiUrl + endpoint, method, body, headers, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
_requestCount++;
|
_requestCount++;
|
||||||
|
|
||||||
if (responseHeaders.TryGetValue("x-ratelimit-remaining-second", out var value))
|
if (response.headers.TryGetValue("x-ratelimit-remaining-second", out var value))
|
||||||
{
|
{
|
||||||
_ = int.TryParse(value, out _hRemaining);
|
_ = int.TryParse(value, out _hRemaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseHeaders.TryGetValue("ratelimit-reset", out value))
|
if (response.headers.TryGetValue("ratelimit-reset", out value))
|
||||||
{
|
{
|
||||||
_ = int.TryParse(value, out _hReset);
|
_ = int.TryParse(value, out _hReset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpStatusCode != HttpStatusCode.TooManyRequests)
|
if (response.statusCode == HttpStatusCode.TooManyRequests && attempt <= 4)
|
||||||
{
|
{
|
||||||
if (!responseHeaders.TryGetValue("x-reason", out value))
|
var time = _hReset == -1 ? 5 : _hReset;
|
||||||
{
|
|
||||||
value = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HttpResponse
|
await Task.Delay(time * 1000, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
Body = response,
|
return await SendRequestAsync(endpoint, method, body, headers, apiKey, attempt + 1, cancellationToken).ConfigureAwait(false);
|
||||||
Code = httpStatusCode,
|
|
||||||
Reason = value
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var time = _hReset == -1 ? 5 : _hReset;
|
if (response.statusCode == HttpStatusCode.BadGateway && attempt <= 3)
|
||||||
|
{
|
||||||
|
await Task.Delay(500, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await Task.Delay(time * 1000, cancellationToken).ConfigureAwait(false);
|
return await SendRequestAsync(endpoint, method, body, headers, apiKey, attempt + 1, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
return await SendRequestAsync(endpoint, method, body, headers, apiKey, cancellationToken).ConfigureAwait(false);
|
if (!response.headers.TryGetValue("x-reason", out value))
|
||||||
|
{
|
||||||
|
value = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HttpResponse
|
||||||
|
{
|
||||||
|
Body = response.body,
|
||||||
|
Code = response.statusCode,
|
||||||
|
Reason = value
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
|
||||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
This is a plugin allows you to download subtitles from [Open Subtitles](https://opensubtitles.org) for your media.
|
This is a plugin allows you to download subtitles from [Open Subtitles](https://opensubtitles.com) for your media.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user