diff --git a/Jellyfin.Plugin.Tvdb/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.Tvdb/Configuration/PluginConfiguration.cs index 9e8a445..d112aa0 100644 --- a/Jellyfin.Plugin.Tvdb/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.Tvdb/Configuration/PluginConfiguration.cs @@ -8,13 +8,16 @@ namespace Jellyfin.Plugin.Tvdb.Configuration public class PluginConfiguration : BasePluginConfiguration { /// - /// Gets or sets the tvdb api key for project. + /// Gets the tvdb api key for project. /// - public string ProjectApiKey { get; set; } = string.Empty; // Jellyin Project API Key + public const string ProjectApiKey = ""; /// /// Gets or sets the tvdb api key for user. /// - public string ApiKey { get; set; } = string.Empty; // User API PIN + /// + /// This is the subscriber's pin. + /// + public string ApiKey { get; set; } = string.Empty; } } diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs index e6a07e6..4c3a039 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs @@ -471,7 +471,7 @@ private static void MapSeriesToResult(MetadataResult result, SeriesExten series.Overview = tvdbSeries.Translations.OverviewTranslations.FirstOrDefault(x => string.Equals(x.Language, TvdbUtils.NormalizeLanguageToTvdb(info.MetadataLanguage), StringComparison.OrdinalIgnoreCase))?.Overview ?? tvdbSeries.Overview; series.OriginalTitle = tvdbSeries.Name; result.ResultLanguage = info.MetadataLanguage; - series.AirDays = TvdbUtils.GetAirDays(tvdbSeries.AirsDays); + series.AirDays = TvdbUtils.GetAirDays(tvdbSeries.AirsDays).ToArray(); series.AirTime = tvdbSeries.AirsTime; // series.CommunityRating = (float?)tvdbSeries.SiteRating; // Attempts to default to USA if not found diff --git a/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs b/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs index 757ae7c..1a87780 100644 --- a/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs +++ b/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs @@ -1,451 +1,443 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; -using System.Linq; +using System.Net; using System.Net.Http; -using System.Reflection; -using System.Runtime.CompilerServices; +using System.Net.Http.Headers; +using System.Text; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Net; +using Jellyfin.Plugin.Tvdb.Configuration; +using MediaBrowser.Common; using MediaBrowser.Controller.Providers; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Tvdb.Sdk; -namespace Jellyfin.Plugin.Tvdb +namespace Jellyfin.Plugin.Tvdb; + +/// +/// Tvdb client manager. +/// +public class TvdbClientManager { + private const string TvdbHttpClient = "TvdbHttpClient"; + private static readonly SemaphoreSlim _tokenUpdateLock = new SemaphoreSlim(1, 1); + + private readonly IHttpClientFactory _httpClientFactory; + private readonly IServiceProvider _serviceProvider; + private readonly SdkClientSettings _sdkClientSettings; + + private DateTime _tokenUpdatedAt; + /// - /// Tvdb client manager. + /// Initializes a new instance of the class. /// - public class TvdbClientManager + /// Instance of the interface. + public TvdbClientManager(IApplicationHost applicationHost) { - private readonly IMemoryCache _cache; - private readonly ServiceProvider _serviceProvider; - - private static SemaphoreSlim _tokenUpdateLock = new SemaphoreSlim(1, 1); - private DateTime _tokenUpdatedAt; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - public TvdbClientManager(IMemoryCache memoryCache, IHttpClientFactory httpClientFactory) - { - _cache = memoryCache; - _tokenUpdatedAt = DateTime.MinValue; - _serviceProvider = ConfigureService(); - } + _serviceProvider = ConfigureService(applicationHost); + _httpClientFactory = _serviceProvider.GetRequiredService(); + _sdkClientSettings = _serviceProvider.GetRequiredService(); - private static string? ProjectApiKey => TvdbPlugin.Instance?.Configuration.ProjectApiKey; + _tokenUpdatedAt = DateTime.MinValue; + } - private static string? ApiKey => TvdbPlugin.Instance?.Configuration.ApiKey; + private static string? UserPin => TvdbPlugin.Instance?.Configuration.ApiKey; - /// - /// Logs in or refresh login to the tvdb api when needed. - /// - private async Task LoginAsync() + /// + /// Logs in or refresh login to the tvdb api when needed. + /// + private async Task LoginAsync() + { + var loginClient = _serviceProvider.GetRequiredService(); + if (string.IsNullOrEmpty(UserPin)) { - var loginClient = _serviceProvider.GetRequiredService(); - var sdkClientSettings = _serviceProvider.GetRequiredService(); + throw new InvalidOperationException("Subscriber PIN not set"); + } - // First time authenticating if the token was never updated or if it's empty in the client - if (_tokenUpdatedAt == DateTime.MinValue || string.IsNullOrEmpty(sdkClientSettings.AccessToken)) + // Ensure we have a recent token. + if (IsTokenInvalid()) + { + await _tokenUpdateLock.WaitAsync().ConfigureAwait(false); + try { - await _tokenUpdateLock.WaitAsync().ConfigureAwait(false); - try + if (IsTokenInvalid()) { - if (string.IsNullOrEmpty(sdkClientSettings.AccessToken)) + var loginResponse = await loginClient.LoginAsync(new Body { - var loginResponse = await loginClient.LoginAsync(new Body - { - Apikey = ProjectApiKey, - Pin = ApiKey - }).ConfigureAwait(false); - _tokenUpdatedAt = DateTime.UtcNow; - sdkClientSettings.AccessToken = loginResponse.Data.Token; - } - } - finally - { - _tokenUpdateLock.Release(); + Apikey = PluginConfiguration.ProjectApiKey, + Pin = UserPin + }).ConfigureAwait(false); + + _tokenUpdatedAt = DateTime.UtcNow; + _sdkClientSettings.AccessToken = loginResponse.Data.Token; } } - - // Refresh if necessary - if (_tokenUpdatedAt < DateTime.UtcNow.Subtract(TimeSpan.FromDays(25))) + finally { - try - { - await _tokenUpdateLock.WaitAsync().ConfigureAwait(false); - if (_tokenUpdatedAt < DateTime.UtcNow.Subtract(TimeSpan.FromDays(25))) - { - var loginResponse = await loginClient.LoginAsync(new Body - { - Apikey = ProjectApiKey, - Pin = ApiKey - }).ConfigureAwait(false); - _tokenUpdatedAt = DateTime.UtcNow; - sdkClientSettings.AccessToken = loginResponse.Data.Token; - } - } - finally - { - _tokenUpdateLock.Release(); - } + _tokenUpdateLock.Release(); } } - /// - /// Get series by name. - /// - /// Series name. - /// Metadata language. - /// Cancellation token. - /// The series search result. - public async Task> GetSeriesByNameAsync( - string name, - string language, - CancellationToken cancellationToken) - { - var searchClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var searchResult = await searchClient.GetSearchResultsAsync(query: name, type: "series", limit: 5, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return searchResult.Data; - } + return; - /// - /// Get series by id. - /// - /// The series tvdb id. - /// Metadata language. - /// Cancellation token. - /// The series response. - public async Task GetSeriesByIdAsync( - int tvdbId, - string language, - CancellationToken cancellationToken) - { - var seriesClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var seriesResult = await seriesClient.GetSeriesBaseAsync(id: tvdbId, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return seriesResult.Data; - } + bool IsTokenInvalid() => + _tokenUpdatedAt == DateTime.MinValue + || string.IsNullOrEmpty(_sdkClientSettings.AccessToken) + || _tokenUpdatedAt < DateTime.UtcNow.Subtract(TimeSpan.FromDays(25)); + } - /// - /// Get series by id. - /// - /// The series tvdb id. - /// Metadata language. - /// Cancellation token. - /// episodes or translations. - /// Payload size. True for smaller payload. - /// The series response. - public async Task GetSeriesExtendedByIdAsync( - int tvdbId, - string language, - CancellationToken cancellationToken, - Meta4? meta = null, - bool? small = null) - { - var seriesClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var seriesResult = await seriesClient.GetSeriesExtendedAsync(id: tvdbId, meta: meta, @short: small, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return seriesResult.Data; - } + /// + /// Get series by name. + /// + /// Series name. + /// Metadata language. + /// Cancellation token. + /// The series search result. + public async Task> GetSeriesByNameAsync( + string name, + string language, + CancellationToken cancellationToken) + { + var searchClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var searchResult = await searchClient.GetSearchResultsAsync(query: name, type: "series", limit: 5, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return searchResult.Data; + } - /// - /// Get all episodes of series. - /// - /// The series tvdb id. - /// Metadata language. - /// Season type: default, dvd, absolute etc. - /// Cancellation token. - /// All episodes of series. - public async Task GetSeriesEpisodesAsync( - int tvdbId, - string language, - string seasonType, - CancellationToken cancellationToken) - { - var seriesClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var seriesResult = await seriesClient.GetSeriesEpisodesAsync(id: tvdbId, season_type: seasonType, cancellationToken: cancellationToken, page: 0) - .ConfigureAwait(false); - return seriesResult.Data; - } + /// + /// Get series by id. + /// + /// The series tvdb id. + /// Metadata language. + /// Cancellation token. + /// The series response. + public async Task GetSeriesByIdAsync( + int tvdbId, + string language, + CancellationToken cancellationToken) + { + var seriesClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var seriesResult = await seriesClient.GetSeriesBaseAsync(id: tvdbId, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return seriesResult.Data; + } - /// - /// Get Season record. - /// - /// The season tvdb id. - /// Metadata language. - /// Cancellation token. - /// The episode record. - public async Task GetSeasonByIdAsync( - int seasonTvdbId, - string language, - CancellationToken cancellationToken) - { - var seasonClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var seasonResult = await seasonClient.GetSeasonExtendedAsync(id: seasonTvdbId, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return seasonResult.Data; - } + /// + /// Get series by id. + /// + /// The series tvdb id. + /// Metadata language. + /// Cancellation token. + /// episodes or translations. + /// Payload size. True for smaller payload. + /// The series response. + public async Task GetSeriesExtendedByIdAsync( + int tvdbId, + string language, + CancellationToken cancellationToken, + Meta4? meta = null, + bool? small = null) + { + var seriesClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var seriesResult = await seriesClient.GetSeriesExtendedAsync(id: tvdbId, meta: meta, @short: small, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return seriesResult.Data; + } - /// - /// Get episode record. - /// - /// The episode tvdb id. - /// Metadata language. - /// Cancellation token. - /// The episode record. - public async Task GetEpisodesAsync( - int episodeTvdbId, - string language, - CancellationToken cancellationToken) - { - var episodeClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var episodeResult = await episodeClient.GetEpisodeExtendedAsync(id: episodeTvdbId, meta: Meta.Translations, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return episodeResult.Data; - } + /// + /// Get all episodes of series. + /// + /// The series tvdb id. + /// Metadata language. + /// Season type: default, dvd, absolute etc. + /// Cancellation token. + /// All episodes of series. + public async Task GetSeriesEpisodesAsync( + int tvdbId, + string language, + string seasonType, + CancellationToken cancellationToken) + { + var seriesClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var seriesResult = await seriesClient.GetSeriesEpisodesAsync(id: tvdbId, season_type: seasonType, cancellationToken: cancellationToken, page: 0) + .ConfigureAwait(false); + return seriesResult.Data; + } - /// - /// Get series by remoteId. - /// - /// The remote id. Supported RemoteIds are: IMDB, TMDB, Zap2It, TV Maze and EIDR. - /// Metadata language. - /// Cancellation token. - /// The series search result. - public async Task> GetSeriesByRemoteIdAsync( - string remoteId, - string language, - CancellationToken cancellationToken) - { - var searchClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var searchResult = await searchClient.GetSearchResultsByRemoteIdAsync(remoteId: remoteId, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return searchResult.Data; - } + /// + /// Get Season record. + /// + /// The season tvdb id. + /// Metadata language. + /// Cancellation token. + /// The episode record. + public async Task GetSeasonByIdAsync( + int seasonTvdbId, + string language, + CancellationToken cancellationToken) + { + var seasonClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var seasonResult = await seasonClient.GetSeasonExtendedAsync(id: seasonTvdbId, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return seasonResult.Data; + } - /// - /// Get actors by tvdb id. - /// - /// People Tvdb id. - /// Metadata language. - /// Cancellation token. - /// The actors attached to the id. - public async Task GetActorAsync( - int tvdbId, - string language, - CancellationToken cancellationToken) - { - var peopleClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var peopleResult = await peopleClient.GetPeopleBaseAsync(id: tvdbId, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return peopleResult.Data; - } + /// + /// Get episode record. + /// + /// The episode tvdb id. + /// Metadata language. + /// Cancellation token. + /// The episode record. + public async Task GetEpisodesAsync( + int episodeTvdbId, + string language, + CancellationToken cancellationToken) + { + var episodeClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var episodeResult = await episodeClient.GetEpisodeExtendedAsync(id: episodeTvdbId, meta: Meta.Translations, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return episodeResult.Data; + } - /// - /// Get image by image tvdb id. - /// - /// Tvdb id. - /// Metadata language. - /// Cancellation token. - /// The images attached to the id. - public async Task GetImageAsync( - int imageTvdbId, - string language, - CancellationToken cancellationToken) - { - var artworkClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var artworkResult = await artworkClient.GetArtworkExtendedAsync(id: imageTvdbId, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return artworkResult.Data; - } + /// + /// Get series by remoteId. + /// + /// The remote id. Supported RemoteIds are: IMDB, TMDB, Zap2It, TV Maze and EIDR. + /// Metadata language. + /// Cancellation token. + /// The series search result. + public async Task> GetSeriesByRemoteIdAsync( + string remoteId, + string language, + CancellationToken cancellationToken) + { + var searchClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var searchResult = await searchClient.GetSearchResultsByRemoteIdAsync(remoteId: remoteId, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return searchResult.Data; + } - /// - /// Get image by series tvdb id. - /// - /// Tvdb id. - /// Metadata language. - /// Cancellation token. - /// The images attached to the id. - public async Task GetSeriesImagesAsync( - int tvdbId, - string language, - CancellationToken cancellationToken) - { - var seriesClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var seriesResult = await seriesClient.GetSeriesArtworksAsync(id: tvdbId, cancellationToken: cancellationToken) - .ConfigureAwait(false); - return seriesResult.Data; - } + /// + /// Get actors by tvdb id. + /// + /// People Tvdb id. + /// Metadata language. + /// Cancellation token. + /// The actors attached to the id. + public async Task GetActorAsync( + int tvdbId, + string language, + CancellationToken cancellationToken) + { + var peopleClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var peopleResult = await peopleClient.GetPeopleBaseAsync(id: tvdbId, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return peopleResult.Data; + } - /// - /// Get all tvdb languages. - /// - /// Cancellation token. - /// All tvdb languages. - public async Task> GetLanguagesAsync(CancellationToken cancellationToken) - { - var languagesClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var languagesResult = await languagesClient.GetAllLanguagesAsync(cancellationToken: cancellationToken) - .ConfigureAwait(false); - return languagesResult.Data; - } + /// + /// Get image by image tvdb id. + /// + /// Tvdb id. + /// Metadata language. + /// Cancellation token. + /// The images attached to the id. + public async Task GetImageAsync( + int imageTvdbId, + string language, + CancellationToken cancellationToken) + { + var artworkClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var artworkResult = await artworkClient.GetArtworkExtendedAsync(id: imageTvdbId, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return artworkResult.Data; + } - /// - /// Gets all tvdb artwork types. - /// - /// Cancellation Token. - /// All tvdb artwork types. - public async Task> GetArtworkTypeAsync(CancellationToken cancellationToken) - { - var artwork_TypesClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - var artwork_TypesResult = await artwork_TypesClient.GetAllArtworkTypesAsync(cancellationToken: cancellationToken) - .ConfigureAwait(false); - return artwork_TypesResult.Data; - } + /// + /// Get image by series tvdb id. + /// + /// Tvdb id. + /// Metadata language. + /// Cancellation token. + /// The images attached to the id. + public async Task GetSeriesImagesAsync( + int tvdbId, + string language, + CancellationToken cancellationToken) + { + var seriesClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var seriesResult = await seriesClient.GetSeriesArtworksAsync(id: tvdbId, cancellationToken: cancellationToken) + .ConfigureAwait(false); + return seriesResult.Data; + } - /// - /// Get an episode's tvdb id. - /// - /// Episode search info. - /// Metadata language. - /// Cancellation token. - /// The tvdb id. - public async Task GetEpisodeTvdbId( - EpisodeInfo searchInfo, - string language, - CancellationToken cancellationToken) + /// + /// Get all tvdb languages. + /// + /// Cancellation token. + /// All tvdb languages. + public async Task> GetLanguagesAsync(CancellationToken cancellationToken) + { + var languagesClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var languagesResult = await languagesClient.GetAllLanguagesAsync(cancellationToken: cancellationToken) + .ConfigureAwait(false); + return languagesResult.Data; + } + + /// + /// Gets all tvdb artwork types. + /// + /// Cancellation Token. + /// All tvdb artwork types. + public async Task> GetArtworkTypeAsync(CancellationToken cancellationToken) + { + var artworkTypesClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var artworkTypesResult = await artworkTypesClient.GetAllArtworkTypesAsync(cancellationToken: cancellationToken) + .ConfigureAwait(false); + return artworkTypesResult.Data; + } + + /// + /// Get an episode's tvdb id. + /// + /// Episode search info. + /// Metadata language. + /// Cancellation token. + /// The tvdb id. + public async Task GetEpisodeTvdbId( + EpisodeInfo searchInfo, + string language, + CancellationToken cancellationToken) + { + var seriesClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + searchInfo.SeriesProviderIds.TryGetValue(TvdbPlugin.ProviderId, out var seriesTvdbId); + int? episodeNumber = null; + int? seasonNumber = null; + string? airDate = null; + bool special = false; + // Prefer SxE over premiere date as it is more robust + if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue) { - var seriesClient = _serviceProvider.GetRequiredService(); - await LoginAsync().ConfigureAwait(false); - searchInfo.SeriesProviderIds.TryGetValue(TvdbPlugin.ProviderId, out var seriesTvdbId); - int? episodeNumber = null; - int? seasonNumber = null; - string? airDate = null; - bool special = false; - // Prefer SxE over premiere date as it is more robust - if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue) - { - switch (searchInfo.SeriesDisplayOrder) - { - case "dvd": - episodeNumber = searchInfo.IndexNumber.Value; - seasonNumber = searchInfo.ParentIndexNumber.Value; - break; - case "absolute": - if (searchInfo.ParentIndexNumber.Value == 0) // check if special - { - special = true; - seasonNumber = 0; - } - else - { - seasonNumber = 1; // absolute order is always season 1 - } - - episodeNumber = searchInfo.IndexNumber.Value; - break; - default: - // aired order - episodeNumber = searchInfo.IndexNumber.Value; - seasonNumber = searchInfo.ParentIndexNumber.Value; - break; - } - } - else if (searchInfo.PremiereDate.HasValue) + switch (searchInfo.SeriesDisplayOrder) { - // tvdb expects yyyy-mm-dd format - airDate = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); - } + case "dvd": + episodeNumber = searchInfo.IndexNumber.Value; + seasonNumber = searchInfo.ParentIndexNumber.Value; + break; + case "absolute": + if (searchInfo.ParentIndexNumber.Value == 0) // check if special + { + special = true; + seasonNumber = 0; + } + else + { + seasonNumber = 1; // absolute order is always season 1 + } - Response56 seriesResponse; - if (!special) - { - switch (searchInfo.SeriesDisplayOrder) - { - case "dvd": - case "absolute": - seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: searchInfo.SeriesDisplayOrder, season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); - break; - default: - seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: "default", season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); - break; - } + episodeNumber = searchInfo.IndexNumber.Value; + break; + default: + // aired order + episodeNumber = searchInfo.IndexNumber.Value; + seasonNumber = searchInfo.ParentIndexNumber.Value; + break; } - else // when special use default order - { - seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: "default", season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); - } - - Data2 seriesData = seriesResponse.Data; + } + else if (searchInfo.PremiereDate.HasValue) + { + // tvdb expects yyyy-mm-dd format + airDate = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + } - if (seriesData == null || seriesData.Episodes == null || seriesData.Episodes.Count == 0) - { - return null; - } - else + Response56 seriesResponse; + if (!special) + { + switch (searchInfo.SeriesDisplayOrder) { - return seriesData.Episodes[0].Id.ToString(CultureInfo.InvariantCulture); + case "dvd": + case "absolute": + seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: searchInfo.SeriesDisplayOrder, season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); + break; + default: + seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: "default", season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); + break; } } - - private static ServiceProvider ConfigureService() + else // when special use default order { - var services = new ServiceCollection(); - static HttpMessageHandler DefaultHttpClientHandlerDelegate(IServiceProvider service) - => new SocketsHttpHandler - { - AutomaticDecompression = System.Net.DecompressionMethods.All, - RequestHeaderEncodingSelector = (_, _) => System.Text.Encoding.UTF8 - }; - services.AddSingleton(); - - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); - - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); - - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); + seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: "default", season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); + } - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); + Data2 seriesData = seriesResponse.Data; - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); + if (seriesData == null || seriesData.Episodes == null || seriesData.Episodes.Count == 0) + { + return null; + } + else + { + return seriesData.Episodes[0].Id.ToString(CultureInfo.InvariantCulture); + } + } - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); + /// + /// Create an independent ServiceProvider because registering HttpClients directly into Jellyfin + /// causes issues upstream. + /// + /// Instance of the . + /// The service provider. + private ServiceProvider ConfigureService(IApplicationHost applicationHost) + { + var productHeader = ProductInfoHeaderValue.Parse(applicationHost.ApplicationUserAgent); - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); + var assembly = typeof(TvdbPlugin).Assembly.GetName(); + var pluginHeader = new ProductInfoHeaderValue( + assembly.Name!.Replace(' ', '-').Replace('.', '-'), + assembly.Version!.ToString(3)); - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); + var contactHeader = new ProductInfoHeaderValue($"({applicationHost.ApplicationUserAgentAddress})"); - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler(DefaultHttpClientHandlerDelegate); + var services = new ServiceCollection(); - return services.BuildServiceProvider(); - } + services.AddSingleton(); + services.AddHttpClient(TvdbHttpClient, c => + { + c.DefaultRequestHeaders.UserAgent.Add(productHeader); + c.DefaultRequestHeaders.UserAgent.Add(pluginHeader); + c.DefaultRequestHeaders.UserAgent.Add(contactHeader); + }) + .ConfigurePrimaryHttpMessageHandler(_ => new SocketsHttpHandler + { + AutomaticDecompression = DecompressionMethods.All, + RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8 + }); + + services.AddTransient(_ => new LoginClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new SearchClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new SeriesClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new SeasonsClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new EpisodesClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new PeopleClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new ArtworkClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new Artwork_TypesClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new LanguagesClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + + return services.BuildServiceProvider(); } } diff --git a/Jellyfin.Plugin.Tvdb/TvdbUtils.cs b/Jellyfin.Plugin.Tvdb/TvdbUtils.cs index 92524ec..94400c9 100644 --- a/Jellyfin.Plugin.Tvdb/TvdbUtils.cs +++ b/Jellyfin.Plugin.Tvdb/TvdbUtils.cs @@ -1,5 +1,5 @@ -using System; -using System.Linq; +using System; +using System.Collections.Generic; using MediaBrowser.Model.Entities; using Tvdb.Sdk; @@ -15,11 +15,6 @@ public static class TvdbUtils /// public const string TvdbBaseUrl = "https://www.thetvdb.com/"; - /// - /// Base url for banners. - /// - public const string BannerUrl = TvdbBaseUrl + "banners/"; - /// /// Get image type from key type. /// @@ -40,7 +35,7 @@ public static ImageType GetImageTypeFromKeyType(string? keyType) } } - throw new ArgumentException($"Null keytype"); + throw new ArgumentException("Null keytype"); } /// @@ -104,21 +99,42 @@ public static ImageType GetImageTypeFromKeyType(string? keyType) /// /// SeriesAirDays. /// List{DayOfWeek}. - public static DayOfWeek[] GetAirDays(SeriesAirsDays seriesAirsDays) + public static IEnumerable GetAirDays(SeriesAirsDays seriesAirsDays) { - // Convert to DayOfWeek? array and remove nulls - var airdays = new[] + if (seriesAirsDays.Sunday) + { + yield return DayOfWeek.Sunday; + } + + if (seriesAirsDays.Monday) + { + yield return DayOfWeek.Monday; + } + + if (seriesAirsDays.Tuesday) + { + yield return DayOfWeek.Tuesday; + } + + if (seriesAirsDays.Wednesday) + { + yield return DayOfWeek.Wednesday; + } + + if (seriesAirsDays.Thursday) { - seriesAirsDays.Monday ? DayOfWeek.Monday : (DayOfWeek?)null, - seriesAirsDays.Tuesday ? DayOfWeek.Tuesday : (DayOfWeek?)null, - seriesAirsDays.Wednesday ? DayOfWeek.Wednesday : (DayOfWeek?)null, - seriesAirsDays.Thursday ? DayOfWeek.Thursday : (DayOfWeek?)null, - seriesAirsDays.Friday ? DayOfWeek.Friday : (DayOfWeek?)null, - seriesAirsDays.Saturday ? DayOfWeek.Saturday : (DayOfWeek?)null, - seriesAirsDays.Sunday ? DayOfWeek.Sunday : (DayOfWeek?)null - }.Where(i => i.HasValue).ToArray(); - // Convert to DayOfWeek array. Nulls are converted to 0 but all should be removed by now. - return Array.ConvertAll(airdays, i => i ?? 0); + yield return DayOfWeek.Thursday; + } + + if (seriesAirsDays.Friday) + { + yield return DayOfWeek.Friday; + } + + if (seriesAirsDays.Saturday) + { + yield return DayOfWeek.Saturday; + } } } }