Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove custom api key #132

Merged
merged 4 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Jellyfin.Plugin.OpenSubtitles/API/OpenSubtitlesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler;
using Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -38,8 +39,10 @@ public class OpenSubtitlesController : ControllerBase
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult> ValidateLoginInfo([FromBody] LoginInfoInput body)
{
var key = !string.IsNullOrWhiteSpace(body.CustomApiKey) ? body.CustomApiKey : OpenSubtitlesPlugin.ApiKey;
var response = await OpenSubtitlesHandler.OpenSubtitles.LogInAsync(body.Username, body.Password, key, CancellationToken.None).ConfigureAwait(false);
var response = await OpenSubtitlesApi.LogInAsync(
body.Username,
body.Password,
CancellationToken.None).ConfigureAwait(false);

if (!response.Ok)
{
Expand All @@ -59,7 +62,7 @@ public async Task<ActionResult> ValidateLoginInfo([FromBody] LoginInfoInput body

if (response.Data is not null)
{
await OpenSubtitlesHandler.OpenSubtitles.LogOutAsync(response.Data, key, CancellationToken.None).ConfigureAwait(false);
await OpenSubtitlesApi.LogOutAsync(response.Data, CancellationToken.None).ConfigureAwait(false);
}

return Ok(new { Downloads = response.Data?.User?.AllowedDownloads ?? 0 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ public class PluginConfiguration : BasePluginConfiguration
/// </summary>
public string Password { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the custom API Key.
/// </summary>
public string CustomApiKey { get; set; } = string.Empty;

/// <summary>
/// Gets or sets a value indicating whether the credentials are invalid.
/// </summary>
Expand Down
35 changes: 11 additions & 24 deletions Jellyfin.Plugin.OpenSubtitles/OpenSubtitleDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,6 @@ public OpenSubtitleDownloader(ILogger<OpenSubtitleDownloader> logger, IHttpClien
/// </summary>
public static OpenSubtitleDownloader? Instance { get; private set; }

/// <summary>
/// Gets the API key that will be used for requests.
/// </summary>
public string ApiKey
{
get
{
return !string.IsNullOrWhiteSpace(_configuration?.CustomApiKey) ? _configuration.CustomApiKey : OpenSubtitlesPlugin.ApiKey;
}
}

/// <inheritdoc />
public string Name
=> "Open Subtitles";
Expand Down Expand Up @@ -173,7 +162,7 @@ public async Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest

_logger.LogDebug("Search query: {Query}", options);

var searchResponse = await OpenSubtitlesHandler.OpenSubtitles.SearchSubtitlesAsync(options, ApiKey, cancellationToken).ConfigureAwait(false);
var searchResponse = await OpenSubtitlesApi.SearchSubtitlesAsync(options, cancellationToken).ConfigureAwait(false);

if (!searchResponse.Ok)
{
Expand All @@ -183,7 +172,7 @@ public async Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest

bool MediaFilter(ResponseData x) =>
x.Attributes?.FeatureDetails?.FeatureType == (request.ContentType == VideoContentType.Episode ? "Episode" : "Movie")
&& x.Attributes?.Files?.Count > 0 && x.Attributes.Files[0].FileId != null
&& x.Attributes?.Files?.Count > 0 && x.Attributes.Files[0].FileId is not null
&& (request.ContentType == VideoContentType.Episode
? x.Attributes.FeatureDetails.SeasonNumber == request.ParentIndexNumber
&& x.Attributes.FeatureDetails.EpisodeNumber == request.IndexNumber
Expand Down Expand Up @@ -261,8 +250,8 @@ private async Task<SubtitleResponse> GetSubtitlesInternal(string id, Cancellatio
var language = idParts[1];
var fileId = int.Parse(idParts[2], CultureInfo.InvariantCulture);

var info = await OpenSubtitlesHandler.OpenSubtitles
.GetSubtitleLinkAsync(fileId, format, _login, ApiKey, cancellationToken)
var info = await OpenSubtitlesApi
.GetSubtitleLinkAsync(fileId, format, _login, cancellationToken)
.ConfigureAwait(false);

if (info.Data?.ResetTime is not null)
Expand Down Expand Up @@ -321,7 +310,7 @@ private async Task<SubtitleResponse> GetSubtitlesInternal(string id, Cancellatio
throw new HttpRequestException(msg);
}

var res = await OpenSubtitlesHandler.OpenSubtitles.DownloadSubtitleAsync(info.Data.Link, cancellationToken).ConfigureAwait(false);
var res = await OpenSubtitlesApi.DownloadSubtitleAsync(info.Data.Link, cancellationToken).ConfigureAwait(false);

if (res.Code != HttpStatusCode.OK || string.IsNullOrWhiteSpace(res.Body))
{
Expand Down Expand Up @@ -355,20 +344,18 @@ private async Task Login(CancellationToken cancellationToken)
return;
}

var loginResponse = await OpenSubtitlesHandler.OpenSubtitles.LogInAsync(
var loginResponse = await OpenSubtitlesApi.LogInAsync(
_configuration.Username,
_configuration.Password,
ApiKey,
cancellationToken).ConfigureAwait(false);

if (!loginResponse.Ok)
{
// 400 = Using email, 401 = invalid credentials, 403 = invalid api key
// 400 = Using email, 401 = invalid credentials
if ((loginResponse.Code == HttpStatusCode.BadRequest && _configuration.Username.Contains('@', StringComparison.OrdinalIgnoreCase))
|| loginResponse.Code == HttpStatusCode.Unauthorized
|| (loginResponse.Code == HttpStatusCode.Forbidden && ApiKey == _configuration.CustomApiKey))
|| loginResponse.Code == HttpStatusCode.Unauthorized)
{
_logger.LogError("Login failed due to invalid credentials/API key, invalidating them ({Code} - {Body})", loginResponse.Code, loginResponse.Body);
_logger.LogError("Login failed due to invalid credentials, invalidating them ({Code} - {Body})", loginResponse.Code, loginResponse.Body);
_configuration.CredentialsInvalid = true;
OpenSubtitlesPlugin.Instance!.SaveConfiguration(_configuration);
}
Expand All @@ -394,7 +381,7 @@ private async Task UpdateUserInfo(CancellationToken cancellationToken)
return;
}

var infoResponse = await OpenSubtitlesHandler.OpenSubtitles.GetUserInfo(_login, ApiKey, cancellationToken).ConfigureAwait(false);
var infoResponse = await OpenSubtitlesApi.GetUserInfo(_login, cancellationToken).ConfigureAwait(false);
if (infoResponse.Ok)
{
_login.User = infoResponse.Data?.Data;
Expand All @@ -415,7 +402,7 @@ private async Task<string> GetLanguage(string language, CancellationToken cancel

if (_languages is null || _languages.Count == 0)
{
var res = await OpenSubtitlesHandler.OpenSubtitles.GetLanguageList(ApiKey, cancellationToken).ConfigureAwait(false);
var res = await OpenSubtitlesApi.GetLanguageList(cancellationToken).ConfigureAwait(false);

if (!res.Ok || res.Data?.Data is null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,4 @@ public class LoginInfoInput
/// </summary>
[Required]
public string Password { get; set; } = null!;

/// <summary>
/// Gets or sets the custom api key.
/// </summary>
public string CustomApiKey { get; set; } = null!;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,19 @@ namespace Jellyfin.Plugin.OpenSubtitles.OpenSubtitlesHandler;
/// <summary>
/// The open subtitles helper class.
/// </summary>
public static class OpenSubtitles
public static class OpenSubtitlesApi
{
/// <summary>
/// Login.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
/// <param name="apiKey">The api key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The api response.</returns>
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, CancellationToken cancellationToken)
{
var body = new { username, password };
var response = await RequestHandler.SendRequestAsync("/login", HttpMethod.Post, body, null, apiKey, 1, cancellationToken).ConfigureAwait(false);
var response = await RequestHandler.SendRequestAsync("/login", HttpMethod.Post, body, null, 1, cancellationToken).ConfigureAwait(false);

return new ApiResponse<LoginInfo>(response);
}
Expand All @@ -34,16 +33,15 @@ public static async Task<ApiResponse<LoginInfo>> LogInAsync(string username, str
/// Logout.
/// </summary>
/// <param name="user">The user information.</param>
/// <param name="apiKey">The api key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>logout status.</returns>
public static async Task<bool> LogOutAsync(LoginInfo user, string apiKey, CancellationToken cancellationToken)
public static async Task<bool> LogOutAsync(LoginInfo user, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(user.Token);

var headers = new Dictionary<string, string> { { "Authorization", user.Token } };

var response = await RequestHandler.SendRequestAsync("/logout", HttpMethod.Delete, null, headers, apiKey, 1, cancellationToken).ConfigureAwait(false);
var response = await RequestHandler.SendRequestAsync("/logout", HttpMethod.Delete, null, headers, 1, cancellationToken).ConfigureAwait(false);

return new ApiResponse<object>(response).Ok;
}
Expand All @@ -52,16 +50,15 @@ public static async Task<bool> LogOutAsync(LoginInfo user, string apiKey, Cancel
/// Get user info.
/// </summary>
/// <param name="user">The user information.</param>
/// <param name="apiKey">The api key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The encapsulated user info.</returns>
public static async Task<ApiResponse<EncapsulatedUserInfo>> GetUserInfo(LoginInfo user, string apiKey, CancellationToken cancellationToken)
public static async Task<ApiResponse<EncapsulatedUserInfo>> GetUserInfo(LoginInfo user, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(user.Token);

var headers = new Dictionary<string, string> { { "Authorization", user.Token } };

var response = await RequestHandler.SendRequestAsync("/infos/user", HttpMethod.Get, null, headers, apiKey, 1, cancellationToken).ConfigureAwait(false);
var response = await RequestHandler.SendRequestAsync("/infos/user", HttpMethod.Get, null, headers, 1, cancellationToken).ConfigureAwait(false);

return new ApiResponse<EncapsulatedUserInfo>(response);
}
Expand All @@ -72,22 +69,20 @@ public static async Task<ApiResponse<EncapsulatedUserInfo>> GetUserInfo(LoginInf
/// <param name="file">The subtitle file.</param>
/// <param name="format">The subtitle format.</param>
/// <param name="user">The user information.</param>
/// <param name="apiKey">The api key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The subtitle download info.</returns>
public static async Task<ApiResponse<SubtitleDownloadInfo>> GetSubtitleLinkAsync(
int file,
string format,
LoginInfo user,
string apiKey,
CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(user.Token);

var headers = new Dictionary<string, string> { { "Authorization", user.Token } };

var body = new { file_id = file, sub_format = format };
var response = await RequestHandler.SendRequestAsync("/download", HttpMethod.Post, body, headers, apiKey, 1, cancellationToken).ConfigureAwait(false);
var response = await RequestHandler.SendRequestAsync("/download", HttpMethod.Post, body, headers, 1, cancellationToken).ConfigureAwait(false);

return new ApiResponse<SubtitleDownloadInfo>(response, $"file id: {file}");
}
Expand Down Expand Up @@ -118,10 +113,9 @@ public static async Task<HttpResponse> DownloadSubtitleAsync(string url, Cancell
/// Search for subtitle.
/// </summary>
/// <param name="options">The search options.</param>
/// <param name="apiKey">The api key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The list of response data.</returns>
public static async Task<ApiResponse<IReadOnlyList<ResponseData>>> SearchSubtitlesAsync(Dictionary<string, string> options, string apiKey, CancellationToken cancellationToken)
public static async Task<ApiResponse<IReadOnlyList<ResponseData>>> SearchSubtitlesAsync(Dictionary<string, string> options, CancellationToken cancellationToken)
{
var opts = new Dictionary<string, string>();
foreach (var (key, value) in options)
Expand All @@ -144,7 +138,7 @@ public static async Task<ApiResponse<IReadOnlyList<ResponseData>>> SearchSubtitl
}

var url = RequestHandler.AddQueryString("/subtitles", opts);
response = await RequestHandler.SendRequestAsync(url, HttpMethod.Get, null, null, apiKey, 1, cancellationToken).ConfigureAwait(false);
response = await RequestHandler.SendRequestAsync(url, HttpMethod.Get, null, null, 1, cancellationToken).ConfigureAwait(false);

last = new ApiResponse<SearchResult>(response, $"url: {url}", $"page: {current}");

Expand Down Expand Up @@ -175,12 +169,11 @@ public static async Task<ApiResponse<IReadOnlyList<ResponseData>>> SearchSubtitl
/// <summary>
/// Get language list.
/// </summary>
/// <param name="apiKey">The api key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The list of languages.</returns>
public static async Task<ApiResponse<EncapsulatedLanguageList>> GetLanguageList(string apiKey, CancellationToken cancellationToken)
public static async Task<ApiResponse<EncapsulatedLanguageList>> GetLanguageList(CancellationToken cancellationToken)
{
var response = await RequestHandler.SendRequestAsync("/infos/languages", HttpMethod.Get, null, null, apiKey, 1, cancellationToken).ConfigureAwait(false);
var response = await RequestHandler.SendRequestAsync("/infos/languages", HttpMethod.Get, null, null, 1, cancellationToken).ConfigureAwait(false);

return new ApiResponse<EncapsulatedLanguageList>(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public static class RequestHandler
/// <param name="method">The method.</param>
/// <param name="body">The request body.</param>
/// <param name="headers">The headers.</param>
/// <param name="apiKey">The api key.</param>
/// <param name="attempt">The request attempt key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The response.</returns>
Expand All @@ -42,18 +41,12 @@ public static async Task<HttpResponse> SendRequestAsync(
HttpMethod method,
object? body,
Dictionary<string, string>? headers,
string? apiKey,
int attempt,
CancellationToken cancellationToken)
{
headers ??= new Dictionary<string, string>();

if (string.IsNullOrWhiteSpace(apiKey))
{
throw new ArgumentException("Provided API key is blank", nameof(apiKey));
}

headers.TryAdd("Api-Key", apiKey);
headers.TryAdd("Api-Key", OpenSubtitlesPlugin.ApiKey);
if (_hRemaining == 0)
{
await Task.Delay(1000 * _hReset, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -98,14 +91,14 @@ public static async Task<HttpResponse> SendRequestAsync(

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, attempt + 1, cancellationToken).ConfigureAwait(false);
}

if (response.statusCode == HttpStatusCode.BadGateway && attempt <= 3)
{
await Task.Delay(500, cancellationToken).ConfigureAwait(false);

return await SendRequestAsync(endpoint, method, body, headers, apiKey, attempt + 1, cancellationToken).ConfigureAwait(false);
return await SendRequestAsync(endpoint, method, body, headers, attempt + 1, cancellationToken).ConfigureAwait(false);
}

if (!response.headers.TryGetValue("x-reason", out value))
Expand Down
10 changes: 2 additions & 8 deletions Jellyfin.Plugin.OpenSubtitles/Web/opensubtitles.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,8 @@ <h3 class="sectionTitle sectionTitle-cards"><b>In order for this plugin to work
<input is="emby-input" type="password" id="password" label="${LabelPassword}" />
<div class="fieldDescription">You can utilize this plugin by editing a library and modifying the options for subtitle downloads.</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="password" id="apikey" label="(Optional) ${HeaderApiKey}:" />
<div class="fieldDescription">
You can provide an <a is="emby-linkbutton" class="button-link" target="_blank" href="https://www.opensubtitles.com/en/consumers">API Key</a> for doing API requests, if left empty the default API key will be used.
</div>
<div class="fieldDescription">
<a is="emby-linkbutton" class="button-link" target="_blank" href="https://92500a62-df9e-42ed-82a4-e6b3eeb89365.site.hbuptime.com/">OpenSubtitles API Status</a>
</div>
<div class="fieldDescription">
<a is="emby-linkbutton" class="button-link" target="_blank" href="https://92500a62-df9e-42ed-82a4-e6b3eeb89365.site.hbuptime.com/">OpenSubtitles API Status</a>
</div>
<div class="fieldDescription" id="ossresponse"></div>
<div>
Expand Down
7 changes: 2 additions & 5 deletions Jellyfin.Plugin.OpenSubtitles/Web/opensubtitles.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export default function (view, params) {
ApiClient.getPluginConfiguration(OpenSubtitlesConfig.pluginUniqueId).then(function (config) {
page.querySelector('#username').value = config.Username || '';
page.querySelector('#password').value = config.Password || '';
page.querySelector('#apikey').value = config.CustomApiKey || '';
if (config.CredentialsInvalid) {
credentialsWarning.style.display = null;
}
Expand All @@ -28,7 +27,6 @@ export default function (view, params) {
ApiClient.getPluginConfiguration(OpenSubtitlesConfig.pluginUniqueId).then(function (config) {
const username = form.querySelector('#username').value.trim();
const password = form.querySelector('#password').value.trim();
const apiKey = form.querySelector('#apikey').value.trim();

if (!username || !password) {
Dashboard.processErrorResponse({statusText: "Account info is incomplete"});
Expand All @@ -37,8 +35,8 @@ export default function (view, params) {

const el = form.querySelector('#ossresponse');
const saveButton = form.querySelector('#save-button');
const data = JSON.stringify({ Username: username, Password: password, CustomApiKey: apiKey });

const data = JSON.stringify({ Username: username, Password: password });
const url = ApiClient.getUrl('Jellyfin.Plugin.OpenSubtitles/ValidateLoginInfo');

const handler = response => response.json().then(res => {
Expand All @@ -48,7 +46,6 @@ export default function (view, params) {

config.Username = username;
config.Password = password;
config.CustomApiKey = apiKey;
config.CredentialsInvalid = false;

ApiClient.updatePluginConfiguration(OpenSubtitlesConfig.pluginUniqueId, config).then(function (result) {
Expand Down