Support Jellyfin 10.7

This commit is contained in:
crobibero
2021-02-24 10:29:09 -07:00
parent 1458fd7534
commit 84a1d0934e
35 changed files with 699 additions and 751 deletions
+67
View File
@@ -0,0 +1,67 @@
using System;
using System.Threading.Tasks;
using Jellyfin.Plugin.Simkl.API.Objects;
using Jellyfin.Plugin.Simkl.API.Responses;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Plugin.Simkl.API
{
/// <summary>
/// The simkl endpoints.
/// </summary>
[ApiController]
[Authorize(Policy = "DefaultAuthorization")]
[Route("Simkl")]
public class Endpoints : ControllerBase
{
private readonly SimklApi _simklApi;
/// <summary>
/// Initializes a new instance of the <see cref="Endpoints"/> class.
/// </summary>
/// <param name="simklApi">Instance of the <see cref="SimklApi"/>.</param>
public Endpoints(SimklApi simklApi)
{
_simklApi = simklApi;
}
/// <summary>
/// Gets the oauth pin.
/// </summary>
/// <returns>The oauth pin.</returns>
[HttpGet("oauth/pin")]
public async Task<ActionResult<CodeResponse?>> GetPin()
{
return await _simklApi.GetCode();
}
/// <summary>
/// Gets the status for the code.
/// </summary>
/// <param name="userCode">The user auth code.</param>
/// <returns>The code status response.</returns>
[HttpGet("oauth/pin/{userCode}")]
public async Task<ActionResult<CodeStatusResponse?>> GetPinStatus([FromRoute] string userCode)
{
return await _simklApi.GetCodeStatus(userCode);
}
/// <summary>
/// Gets the settings for the user.
/// </summary>
/// <param name="userId">The user id.</param>
/// <returns>The user settings.</returns>
[HttpGet("users/settings/{userId}")]
public async Task<ActionResult<UserSettings?>> GetUserSettings([FromRoute] Guid userId)
{
var userConfiguration = SimklPlugin.Instance?.Configuration.GetByGuid(userId);
if (userConfiguration == null)
{
return NotFound();
}
return await _simklApi.GetUserSettings(userConfiguration.UserToken);
}
}
}
@@ -16,7 +16,8 @@ namespace Jellyfin.Plugin.Simkl.API.Exceptions
/// Initializes a new instance of the <see cref="InvalidTokenException"/> class.
/// </summary>
/// <param name="msg">The message.</param>
public InvalidTokenException(string msg) : base(msg)
public InvalidTokenException(string msg)
: base(msg)
{
}
@@ -25,7 +26,8 @@ namespace Jellyfin.Plugin.Simkl.API.Exceptions
/// </summary>
/// <param name="msg">The message.</param>
/// <param name="inner">The inner exception.</param>
public InvalidTokenException(string msg, Exception inner) : base(msg, inner)
public InvalidTokenException(string msg, Exception inner)
: base(msg, inner)
{
}
}
-14
View File
@@ -1,14 +0,0 @@
using Jellyfin.Plugin.Simkl.API.Responses;
using MediaBrowser.Model.Services;
namespace Jellyfin.Plugin.Simkl.API
{
/// <summary>
/// Get oauth pin.
/// </summary>
[Route("/Simkl/oauth/pin", "GET")]
public class GetPin : IReturn<CodeResponse>
{
// Doesn't receive anything
}
}
-19
View File
@@ -1,19 +0,0 @@
using Jellyfin.Plugin.Simkl.API.Responses;
using MediaBrowser.Model.Services;
#pragma warning disable SA1300
namespace Jellyfin.Plugin.Simkl.API
{
/// <summary>
/// Get pin status.
/// </summary>
[Route("/Simkl/oauth/pin/{user_code}", "GET")]
public class GetPinStatus : IReturn<CodeStatusResponse>
{
/// <summary>
/// Gets or sets user code.
/// </summary>
[ApiMember(Name = "user_code", Description = "pin to be introduced by the user", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string user_code { get; set; }
}
}
@@ -1,22 +0,0 @@
using System;
using Jellyfin.Plugin.Simkl.API.Objects;
using MediaBrowser.Model.Services;
namespace Jellyfin.Plugin.Simkl.API
{
/// <summary>
/// Get user settings.
/// </summary>
[Route("/Simkl/users/settings/{userId}", "GET")]
public class GetUserSettings : IReturn<UserSettings>
{
/// <summary>
/// Gets or sets user id.
/// </summary>
/// <remarks>
/// Note: In the future, when we'll have config for more than one user, we'll use a parameter.
/// </remarks>
[ApiMember(Name = "id", Description = "user id", IsRequired = true, DataType = "Guid", ParameterType = "path", Verb = "GET")]
public Guid UserId { get; set; }
}
}
+9 -3
View File
@@ -1,4 +1,8 @@
namespace Jellyfin.Plugin.Simkl.API.Objects
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
/// <summary>
/// Season.
@@ -8,11 +12,13 @@
/// <summary>
/// Gets or sets the season number.
/// </summary>
public int? number { get; set; }
[JsonPropertyName("number")]
public int? Number { get; set; }
/// <summary>
/// Gets or sets the episodes.
/// </summary>
public ShowEpisode[] episodes { get; set; }
[JsonPropertyName("episodes")]
public IReadOnlyList<ShowEpisode> Episodes { get; set; } = Array.Empty<ShowEpisode>();
}
}
@@ -1,4 +1,6 @@
namespace Jellyfin.Plugin.Simkl.API.Objects
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
/// <summary>
/// Show episode.
@@ -8,7 +10,8 @@
/// <summary>
/// Gets or sets episode number.
/// </summary>
public int? number { get; set; }
[JsonPropertyName("number")]
public int? Number { get; set; }
// TODO: watched_at
}
}
@@ -1,3 +1,4 @@
using System;
using System.Text.Json.Serialization;
#pragma warning disable SA1300
@@ -12,31 +13,30 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// Gets or sets watched at.
/// </summary>
[JsonPropertyName("watched_at")]
public string watched_at { get; set; }
/// <summary>
/// Gets or sets ids.
/// </summary>
public override SimklIds ids { get; set; }
public DateTime? WatchedAt { get; set; }
/// <summary>
/// Gets or sets the title.
/// </summary>
public string title { get; set; }
[JsonPropertyName("title")]
public string? Title { get; set; }
/// <summary>
/// Gets or sets the season.
/// </summary>
public int season { get; set; }
[JsonPropertyName("season")]
public int? Season { get; set; }
/// <summary>
/// Gets or sets the episode.
/// </summary>
public int episode { get; set; }
[JsonPropertyName("episode")]
public int? Episode { get; set; }
/// <summary>
/// Gets or sets multipart.
/// </summary>
public bool? multipart { get; set; }
[JsonPropertyName("multipart")]
public bool? Multipart { get; set; }
}
}
@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
/// <summary>
@@ -8,16 +10,19 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// <summary>
/// Gets or sets the file.
/// </summary>
public string file { get; set; }
[JsonPropertyName("file")]
public string? File { get; set; }
/// <summary>
/// Gets or sets the part.
/// </summary>
public int? part { get; set; }
[JsonPropertyName("part")]
public int? Part { get; set; }
/// <summary>
/// Gets or sets the hash.
/// </summary>
public string hash { get; set; }
[JsonPropertyName("hash")]
public string? Hash { get; set; }
}
}
@@ -1,6 +1,7 @@
#pragma warning disable CA2227
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
@@ -14,24 +15,27 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// </summary>
public SimklHistory()
{
movies = new List<SimklMovie>();
shows = new List<SimklShow>();
episodes = new List<SimklEpisode>();
Movies = new List<SimklMovie>();
Shows = new List<SimklShow>();
Episodes = new List<SimklEpisode>();
}
/// <summary>
/// Gets or sets list of movies.
/// </summary>
public List<SimklMovie> movies { get; set; }
[JsonPropertyName("movies")]
public List<SimklMovie> Movies { get; set; }
/// <summary>
/// Gets or sets the list of shows.
/// </summary>
public List<SimklShow> shows { get; set; }
[JsonPropertyName("shows")]
public List<SimklShow> Shows { get; set; }
/// <summary>
/// Gets or sets the list of episodes.
/// </summary>
public List<SimklEpisode> episodes { get; set; }
[JsonPropertyName("episodes")]
public List<SimklEpisode> Episodes { get; set; }
}
}
+22 -16
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
@@ -17,52 +18,57 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
{
foreach (var (key, value) in providerIds)
{
if (key.Equals(nameof(simkl), StringComparison.OrdinalIgnoreCase))
if (key.Equals(nameof(Simkl), StringComparison.OrdinalIgnoreCase))
{
simkl = Convert.ToInt32(value, CultureInfo.InvariantCulture);
Simkl = Convert.ToInt32(value, CultureInfo.InvariantCulture);
}
else if (key.Equals(nameof(imdb), StringComparison.OrdinalIgnoreCase))
else if (key.Equals(nameof(Imdb), StringComparison.OrdinalIgnoreCase))
{
imdb = value;
Imdb = value;
}
else if (key.Equals(nameof(slug), StringComparison.OrdinalIgnoreCase))
else if (key.Equals(nameof(Slug), StringComparison.OrdinalIgnoreCase))
{
slug = value;
Slug = value;
}
else if (key.Equals(nameof(netflix), StringComparison.OrdinalIgnoreCase))
else if (key.Equals(nameof(Netflix), StringComparison.OrdinalIgnoreCase))
{
netflix = value;
Netflix = value;
}
else if (key.Equals(nameof(tmdb), StringComparison.OrdinalIgnoreCase))
else if (key.Equals(nameof(Tmdb), StringComparison.OrdinalIgnoreCase))
{
tmdb = value;
Tmdb = value;
}
}
}
/// <summary>
/// Gets or sets simkl.
/// Gets or sets the simkl id.
/// </summary>
public int? simkl { get; set; }
[JsonPropertyName("simkl")]
public int? Simkl { get; set; }
/// <summary>
/// Gets or sets the imdb id.
/// </summary>
public string imdb { get; set; }
[JsonPropertyName("imdb")]
public string? Imdb { get; set; }
/// <summary>
/// Gets or sets the slug.
/// </summary>
public string slug { get; set; }
[JsonPropertyName("slug")]
public string? Slug { get; set; }
/// <summary>
/// Gets or sets the netflix id.
/// </summary>
public string netflix { get; set; }
[JsonPropertyName("netflix")]
public string? Netflix { get; set; }
/// <summary>
/// Gets or sets the TMDb id.
/// </summary>
public string tmdb { get; set; }
[JsonPropertyName("tmdb")]
public string? Tmdb { get; set; }
}
}
@@ -1,13 +1,16 @@
namespace Jellyfin.Plugin.Simkl.API.Objects
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
/// <summary>
/// Simkl media object.
/// </summary>
public abstract class SimklMediaObject
public class SimklMediaObject
{
/// <summary>
/// Gets or sets ids.
/// </summary>
public abstract SimklIds ids { get; set; }
[JsonPropertyName("ids")]
public SimklIds? Ids { get; set; }
}
}
+12 -12
View File
@@ -1,5 +1,5 @@
using System;
using System.Globalization;
using System.Text.Json.Serialization;
using MediaBrowser.Model.Dto;
namespace Jellyfin.Plugin.Simkl.API.Objects
@@ -15,28 +15,28 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// <param name="item">The base item dto.</param>
public SimklMovie(BaseItemDto item)
{
title = item.OriginalTitle;
year = item.ProductionYear;
ids = new SimklMovieIds(item.ProviderIds);
watched_at = DateTime.UtcNow.ToString("yyyy-MM-dd HH\\:mm\\:ss", CultureInfo.InvariantCulture);
Title = item.OriginalTitle;
Year = item.ProductionYear;
Ids = new SimklMovieIds(item.ProviderIds);
WatchedAt = DateTime.UtcNow;
}
/// <summary>
/// Gets or sets the movie title.
/// </summary>
public string title { get; set; }
[JsonPropertyName("title")]
public string? Title { get; set; }
/// <summary>
/// Gets or sets the year.
/// </summary>
public int? year { get; set; }
/// <inheritdoc />
public override SimklIds ids { get; set; }
[JsonPropertyName("year")]
public int? Year { get; set; }
/// <summary>
/// Gets watched at.
/// Gets or sets watched at.
/// </summary>
public string watched_at { get; }
[JsonPropertyName("watched_at")]
public DateTime? WatchedAt { get; set; }
}
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
@@ -19,31 +20,37 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// <summary>
/// Gets or sets the tvdb id.
/// </summary>
public int? tvdb { get; set; }
[JsonPropertyName("tvdb")]
public int? Tvdb { get; set; }
/// <summary>
/// Gets or sets the mal id.
/// </summary>
public int? mal { get; set; }
[JsonPropertyName("mal")]
public int? Mal { get; set; }
/// <summary>
/// Gets or sets the anidb id.
/// </summary>
public int? anidb { get; set; }
[JsonPropertyName("anidb")]
public int? Anidb { get; set; }
/// <summary>
/// Gets or sets the hulu id.
/// </summary>
public int? hulu { get; set; }
[JsonPropertyName("hulu")]
public int? Hulu { get; set; }
/// <summary>
/// Gets or sets the crunchyroll id.
/// </summary>
public int? crunchyroll { get; set; }
[JsonPropertyName("crunchyroll")]
public int? Crunchyroll { get; set; }
/// <summary>
/// Gets or sets the movie db id.
/// </summary>
public string moviedb { get; set; }
[JsonPropertyName("moviedb")]
public string? Moviedb { get; set; }
}
}
+16 -16
View File
@@ -1,4 +1,6 @@
using MediaBrowser.Model.Dto;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MediaBrowser.Model.Dto;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
@@ -13,19 +15,19 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// <param name="mediaInfo">The media info.</param>
public SimklShow(BaseItemDto mediaInfo)
{
title = mediaInfo.SeriesName;
ids = new SimklShowIds(mediaInfo.ProviderIds);
year = mediaInfo.ProductionYear;
seasons = new[]
Title = mediaInfo.SeriesName;
Ids = new SimklShowIds(mediaInfo.ProviderIds);
Year = mediaInfo.ProductionYear;
Seasons = new[]
{
new Season
{
number = mediaInfo.ParentIndexNumber,
episodes = new[]
Number = mediaInfo.ParentIndexNumber,
Episodes = new[]
{
new ShowEpisode
{
number = mediaInfo.IndexNumber
Number = mediaInfo.IndexNumber
}
}
}
@@ -35,21 +37,19 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// <summary>
/// Gets or sets title.
/// </summary>
public string title { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
/// <summary>
/// Gets or sets year.
/// </summary>
public int? year { get; set; }
[JsonPropertyName("year")]
public int? Year { get; set; }
/// <summary>
/// Gets or sets seasons.
/// </summary>
public Season[] seasons { get; set; }
/// <summary>
/// Gets or sets ids.
/// </summary>
public override SimklIds ids { get; set; }
[JsonPropertyName("seasons")]
public IReadOnlyList<Season> Seasons { get; set; }
}
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
@@ -19,31 +20,37 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// <summary>
/// Gets or sets tvdb.
/// </summary>
public int? tvdb { get; set; }
[JsonPropertyName("tvdb")]
public int? Tvdb { get; set; }
/// <summary>
/// Gets or sets mal.
/// </summary>
public int? mal { get; set; }
[JsonPropertyName("mal")]
public int? Mal { get; set; }
/// <summary>
/// Gets or sets anidb.
/// </summary>
public int? anidb { get; set; }
[JsonPropertyName("anidb")]
public int? Anidb { get; set; }
/// <summary>
/// Gets or sets hulu.
/// </summary>
public int? hulu { get; set; }
[JsonPropertyName("hulu")]
public int? Hulu { get; set; }
/// <summary>
/// Gets or sets crunchyroll.
/// </summary>
public int? crunchyroll { get; set; }
[JsonPropertyName("crunchyroll")]
public int? Crunchyroll { get; set; }
/// <summary>
/// Gets or sets zap2it.
/// </summary>
public string zap2It { get; set; }
[JsonPropertyName("zap2It")]
public string? Zap2It { get; set; }
}
}
+5 -2
View File
@@ -1,4 +1,6 @@
#pragma warning disable SA1300
using System.Text.Json.Serialization;
#pragma warning disable SA1300
namespace Jellyfin.Plugin.Simkl.API.Objects
{
@@ -10,6 +12,7 @@ namespace Jellyfin.Plugin.Simkl.API.Objects
/// <summary>
/// Gets or sets name.
/// </summary>
public string name { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
}
}
@@ -1,4 +1,6 @@
namespace Jellyfin.Plugin.Simkl.API.Objects
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Simkl.API.Objects
{
/// <summary>
/// User settings.
@@ -8,11 +10,13 @@
/// <summary>
/// Gets or sets user.
/// </summary>
public User user { get; set; }
[JsonPropertyName("user")]
public User? User { get; set; }
/// <summary>
/// Gets or sets error.
/// </summary>
public string error { get; set; }
[JsonPropertyName("error")]
public string? Error { get; set; }
}
}
@@ -11,35 +11,35 @@ namespace Jellyfin.Plugin.Simkl.API.Responses
/// <summary>
/// Gets or sets result.
/// </summary>
public string Result { get; set; }
public string? Result { get; set; }
/// <summary>
/// Gets or sets device code.
/// </summary>
[JsonPropertyName("device_code")]
public string device_code { get; set; }
public string? DeviceCode { get; set; }
/// <summary>
/// Gets or sets user code.
/// </summary>
[JsonPropertyName("user_code")]
public string user_code { get; set; }
public string? UserCode { get; set; }
/// <summary>
/// Gets or sets verification url.
/// </summary>
[JsonPropertyName("verification_url")]
public string verification_url { get; set; }
public string? VerificationUrl { get; set; }
/// <summary>
/// Gets or sets expires in.
/// </summary>
[JsonPropertyName("expires_in")]
public int expires_in { get; set; }
public int? ExpiresIn { get; set; }
/// <summary>
/// Gets or sets interval.
/// </summary>
public int Interval { get; set; }
public int? Interval { get; set; }
}
}
@@ -11,17 +11,17 @@ namespace Jellyfin.Plugin.Simkl.API.Responses
/// <summary>
/// Gets or sets result.
/// </summary>
public string Result { get; set; }
public string? Result { get; set; }
/// <summary>
/// Gets or sets message.
/// </summary>
public string Message { get; set; }
public string? Message { get; set; }
/// <summary>
/// Gets or sets access token.
/// </summary>
[JsonPropertyName("access_token")]
public string access_token { get; set; }
public string? AccessToken { get; set; }
}
}
@@ -10,21 +10,21 @@ namespace Jellyfin.Plugin.Simkl.API.Responses
/// <summary>
/// Gets or sets type.
/// </summary>
public string Type { get; set; }
public string? Type { get; set; }
/// <summary>
/// Gets or sets episode.
/// </summary>
public SimklEpisode Episode { get; set; }
public SimklEpisode? Episode { get; set; }
/// <summary>
/// Gets or sets movie.
/// </summary>
public SimklMovie Movie { get; set; }
public SimklMovie? Movie { get; set; }
/// <summary>
/// Gets or sets show.
/// </summary>
public SimklShow Show { get; set; }
public SimklShow? Show { get; set; }
}
}
@@ -1,4 +1,5 @@
using Jellyfin.Plugin.Simkl.API.Objects;
using System;
using Jellyfin.Plugin.Simkl.API.Objects;
namespace Jellyfin.Plugin.Simkl.API.Responses
{
@@ -10,16 +11,16 @@ namespace Jellyfin.Plugin.Simkl.API.Responses
/// <summary>
/// Gets or sets movies.
/// </summary>
public SimklMovie[] Movies { get; set; }
public SimklMovie[] Movies { get; set; } = Array.Empty<SimklMovie>();
/// <summary>
/// Gets or sets shows.
/// </summary>
public SimklShow[] Shows { get; set; }
public SimklShow[] Shows { get; set; } = Array.Empty<SimklShow>();
/// <summary>
/// Gets or sets episodes.
/// </summary>
public SimklEpisode[] Episodes { get; set; }
public SimklEpisode[] Episodes { get; set; } = Array.Empty<SimklEpisode>();
}
}
@@ -11,12 +11,12 @@ namespace Jellyfin.Plugin.Simkl.API.Responses
/// <summary>
/// Gets or sets added.
/// </summary>
public SyncHistoryResponseCount Added { get; set; }
public SyncHistoryResponseCount Added { get; set; } = new SyncHistoryResponseCount();
/// <summary>
/// Gets or sets not found.
/// </summary>
[JsonPropertyName("not_found")]
public SyncHistoryNotFound not_found { get; set; }
public SyncHistoryNotFound? NotFound { get; set; }
}
}
@@ -1,75 +0,0 @@
using Jellyfin.Plugin.Simkl.API.Objects;
using Jellyfin.Plugin.Simkl.API.Responses;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
#pragma warning disable CA1801
namespace Jellyfin.Plugin.Simkl.API
{
/// <summary>
/// Server endpoints.
/// </summary>
public class ServerEndpoint : IService, IHasResultFactory
{
private readonly SimklApi _api;
private readonly ILogger<ServerEndpoint> _logger;
private readonly IJsonSerializer _json;
/// <summary>
/// Initializes a new instance of the <see cref="ServerEndpoint"/> class.
/// </summary>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
/// <param name="json">Instance of the <see cref="IJsonSerializer"/> interface.</param>
/// <param name="httpClient">Instance of the <see cref="IHttpClient"/> interface.</param>
public ServerEndpoint(ILoggerFactory loggerFactory, IJsonSerializer json, IHttpClient httpClient)
{
_logger = loggerFactory.CreateLogger<ServerEndpoint>();
_json = json;
_api = new SimklApi(json, loggerFactory.CreateLogger<SimklApi>(), httpClient);
}
/// <summary>
/// Gets or sets result factory.
/// </summary>
public IHttpResultFactory ResultFactory { get; set; }
/// <summary>
/// Gets or sets request.
/// </summary>
public IRequest Request { get; set; }
/// <summary>
/// Get pin request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>The code response.</returns>
public CodeResponse Get(GetPin request)
{
return _api.GetCode().GetAwaiter().GetResult();
}
/// <summary>
/// Get pin status.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Code status response.</returns>
public CodeStatusResponse Get(GetPinStatus request)
{
return _api.GetCodeStatus(request.user_code).GetAwaiter().GetResult();
}
/// <summary>
/// Get user settings.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>User settings.</returns>
public UserSettings Get(GetUserSettings request)
{
_logger.LogDebug(_json.SerializeToString(request));
return _api.GetUserSettings(SimklPlugin.Instance.Configuration.GetByGuid(request.UserId).UserToken).GetAwaiter().GetResult();
}
}
}
+99 -84
View File
@@ -1,12 +1,21 @@
using System.IO;
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Plugin.Simkl.API.Exceptions;
using Jellyfin.Plugin.Simkl.API.Objects;
using Jellyfin.Plugin.Simkl.API.Responses;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; // using System.Threading;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Simkl.API
{
@@ -16,9 +25,9 @@ namespace Jellyfin.Plugin.Simkl.API
public class SimklApi
{
/* INTERFACES */
private readonly IJsonSerializer _json;
private readonly ILogger<SimklApi> _logger;
private readonly IHttpClient _httpClient;
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _jsonSerializerOptions;
/* BASIC API THINGS */
@@ -26,44 +35,43 @@ namespace Jellyfin.Plugin.Simkl.API
/// Base url.
/// </summary>
public const string Baseurl = @"https://api.simkl.com";
// public const string BASE_URL = @"http://private-9c39b-simkl.apiary-proxy.com";
/// <summary>
/// Redirect uri.
/// </summary>
public const string RedirectUri = @"https://simkl.com/apps/emby/connected/";
/// public const string RedirectUri = @"https://simkl.com/apps/jellyfin/connected/";
public const string RedirectUri = @"https://jellyfin.org";
/// <summary>
/// Api key.
/// </summary>
public const string Apikey = @"27dd5d6adc24aa1ad9f95ef913244cbaf6df5696036af577ed41670473dc97d0";
public const string Apikey = @"c721b22482097722a84a20ccc579cf9d232be85b9befe7b7805484d0ddbc6781";
/// <summary>
/// Secret.
/// </summary>
public const string Secret = @"d7b9feb9d48bbaa69dbabaca21ba4671acaa89198637e9e136a4d69ec97ab68b";
public const string Secret = @"87893fc73cdbd2e51a7c63975c6f941ac1c6155c0e20ffa76b83202dd10a507e";
/// <summary>
/// Initializes a new instance of the <see cref="SimklApi"/> class.
/// </summary>
/// <param name="json">Instance of the <see cref="IJsonSerializer"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{SimklApi}"/> interface.</param>
/// <param name="httpClient">Instance of the <see cref="IHttpClient"/> interface.</param>
public SimklApi(IJsonSerializer json, ILogger<SimklApi> logger, IHttpClient httpClient)
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
public SimklApi(ILogger<SimklApi> logger, IHttpClientFactory httpClientFactory)
{
_json = json;
_logger = logger;
_httpClient = httpClient;
_httpClientFactory = httpClientFactory;
_jsonSerializerOptions = JsonDefaults.GetOptions();
}
/// <summary>
/// Get code.
/// </summary>
/// <returns>Code response.</returns>
public async Task<CodeResponse> GetCode()
public async Task<CodeResponse?> GetCode()
{
var uri = $"/oauth/pin?client_id={Apikey}&redirect={RedirectUri}";
return _json.DeserializeFromStream<CodeResponse>(await Get(uri).ConfigureAwait(false));
return await Get<CodeResponse>(uri);
}
/// <summary>
@@ -71,10 +79,10 @@ namespace Jellyfin.Plugin.Simkl.API
/// </summary>
/// <param name="userCode">User code.</param>
/// <returns>Code status.</returns>
public async Task<CodeStatusResponse> GetCodeStatus(string userCode)
public async Task<CodeStatusResponse?> GetCodeStatus(string userCode)
{
var uri = $"/oauth/pin/{userCode}?client_id={Apikey}";
return _json.DeserializeFromStream<CodeStatusResponse>(await Get(uri).ConfigureAwait(false));
return await Get<CodeStatusResponse>(uri);
}
/// <summary>
@@ -82,18 +90,18 @@ namespace Jellyfin.Plugin.Simkl.API
/// </summary>
/// <param name="userToken">User token.</param>
/// <returns>User settings.</returns>
public async Task<UserSettings> GetUserSettings(string userToken)
public async Task<UserSettings?> GetUserSettings(string userToken)
{
try
{
return _json.DeserializeFromStream<UserSettings>(await Post("/users/settings/", userToken).ConfigureAwait(false));
return await Post<UserSettings, object>("/users/settings/", userToken);
}
catch (MediaBrowser.Model.Net.HttpException e) when (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
catch (HttpRequestException e) when (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
// Wontfix: Custom status codes
// "You don't get to pick your response code" - Luke (System Architect of Emby)
// https://emby.media/community/index.php?/topic/61889-wiki-issue-resultfactorythrowerror/
return new UserSettings { error = "user_token_failed" };
return new UserSettings { Error = "user_token_failed" };
}
}
@@ -103,12 +111,12 @@ namespace Jellyfin.Plugin.Simkl.API
/// <param name="item">Item.</param>
/// <param name="userToken">User token.</param>
/// <returns>Status.</returns>
public async Task<(bool success, BaseItemDto item)> MarkAsWatched(BaseItemDto item, string userToken)
public async Task<(bool Success, BaseItemDto Item)> MarkAsWatched(BaseItemDto item, string userToken)
{
var history = CreateHistoryFromItem(item);
var r = await SyncHistoryAsync(history, userToken).ConfigureAwait(false);
_logger.LogDebug("Response: " + _json.SerializeToString(r));
if (history.movies.Count == r.Added.Movies && history.shows.Count == r.Added.Shows)
var r = await SyncHistoryAsync(history, userToken);
_logger.LogDebug("Response: {@Response}", r);
if (r != null && history.Movies.Count == r.Added.Movies && history.Shows.Count == r.Added.Shows)
{
return (true, item);
}
@@ -117,19 +125,19 @@ namespace Jellyfin.Plugin.Simkl.API
// let's try scrobbling from full path
try
{
(history, item) = await GetHistoryFromFileName(item).ConfigureAwait(false);
(history, item) = await GetHistoryFromFileName(item);
}
catch (InvalidDataException)
{
// Let's try again but this time using only the FILE name
_logger.LogDebug("Couldn't scrobble using full path, trying using only filename");
(history, item) = await GetHistoryFromFileName(item, false).ConfigureAwait(false);
(history, item) = await GetHistoryFromFileName(item, false);
}
r = await SyncHistoryAsync(history, userToken).ConfigureAwait(false);
_logger.LogDebug("Response: " + _json.SerializeToString(r));
return (history.movies.Count == r.Added.Movies && history.shows.Count == r.Added.Shows, item);
r = await SyncHistoryAsync(history, userToken);
return r == null
? (false, item)
: (history.Movies.Count == r.Added.Movies && history.Shows.Count == r.Added.Shows, item);
}
/// <summary>
@@ -137,14 +145,11 @@ namespace Jellyfin.Plugin.Simkl.API
/// </summary>
/// <param name="filename">Filename.</param>
/// <returns>Search file response.</returns>
private async Task<SearchFileResponse> GetFromFile(string filename)
private async Task<SearchFileResponse?> GetFromFile(string filename)
{
var f = new SimklFile { file = filename };
_logger.LogInformation("Posting: " + _json.SerializeToString(f));
using var r = new StreamReader(await Post("/search/file/", null, f).ConfigureAwait(false));
var t = await r.ReadToEndAsync().ConfigureAwait(false);
_logger.LogDebug("Response: " + t);
return _json.DeserializeFromString<SearchFileResponse>(t);
var f = new SimklFile { File = filename };
_logger.LogInformation("Posting: {@File}", f);
return await Post<SearchFileResponse, SimklFile>("/search/file/", null, f);
}
/// <summary>
@@ -156,68 +161,69 @@ namespace Jellyfin.Plugin.Simkl.API
private async Task<(SimklHistory history, BaseItemDto item)> GetHistoryFromFileName(BaseItemDto item, bool fullpath = true)
{
var fname = fullpath ? item.Path : Path.GetFileName(item.Path);
var mo = await GetFromFile(fname).ConfigureAwait(false);
var mo = await GetFromFile(fname);
if (mo == null)
{
throw new InvalidDataException("Search file response is null");
}
var history = new SimklHistory();
if (item.IsMovie == true || item.Type == "Movie")
if (mo.Movie != null &&
(item.IsMovie == true || string.Equals(item.Type, nameof(Movie), StringComparison.OrdinalIgnoreCase)))
{
if (mo.Type != "movie")
if (!string.Equals(mo.Type, "movie", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidDataException("type != movie (" + mo.Type + ")");
}
item.Name = mo.Movie.title;
item.ProductionYear = mo.Movie.year;
history.movies.Add(mo.Movie);
item.Name = mo.Movie.Title;
item.ProductionYear = mo.Movie.Year;
history.Movies.Add(mo.Movie);
}
else if (item.IsSeries == true || item.Type == "Episode")
else if (mo.Episode != null
&& mo.Show != null
&& (item.IsSeries == true || string.Equals(item.Type, nameof(Episode), StringComparison.OrdinalIgnoreCase)))
{
if (mo.Type != "episode")
if (!string.Equals(mo.Type, "episode", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidDataException("type != episode (" + mo.Type + ")");
}
item.Name = mo.Episode.title;
item.SeriesName = mo.Show.title;
item.IndexNumber = mo.Episode.episode;
item.ParentIndexNumber = mo.Episode.season;
item.ProductionYear = mo.Show.year;
history.episodes.Add(mo.Episode);
item.Name = mo.Episode.Title;
item.SeriesName = mo.Show.Title;
item.IndexNumber = mo.Episode.Episode;
item.ParentIndexNumber = mo.Episode.Season;
item.ProductionYear = mo.Show.Year;
history.Episodes.Add(mo.Episode);
}
return (history, item);
}
private static HttpRequestOptions GetOptions(string userToken = null)
private static HttpRequestMessage GetOptions(string? userToken = null)
{
var options = new HttpRequestOptions
{
RequestContentType = "application/json",
LogErrorResponseBody = true,
EnableDefaultUserAgent = true
};
options.RequestHeaders.Add("simkl-api-key", Apikey);
// options.RequestHeaders.Add("Content-Type", "application/json");
var requestMessage = new HttpRequestMessage();
requestMessage.Headers.TryAddWithoutValidation("simkl-api-key", Apikey);
if (!string.IsNullOrEmpty(userToken))
{
options.RequestHeaders.Add("Authorization", "Bearer " + userToken);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", userToken);
}
return options;
return requestMessage;
}
private static SimklHistory CreateHistoryFromItem(BaseItemDto item)
{
var history = new SimklHistory();
if (item.IsMovie == true || item.Type == "Movie")
if (item.IsMovie == true || string.Equals(item.Type, nameof(Movie), StringComparison.OrdinalIgnoreCase))
{
history.movies.Add(new SimklMovie(item));
history.Movies.Add(new SimklMovie(item));
}
else if (item.IsSeries == true || item.Type == "Episode")
else if (item.IsSeries == true || string.Equals(item.Type, nameof(Episode), StringComparison.OrdinalIgnoreCase))
{
// TODO: TV Shows scrobbling (WIP)
history.shows.Add(new SimklShow(item));
history.Shows.Add(new SimklShow(item));
}
return history;
@@ -229,17 +235,17 @@ namespace Jellyfin.Plugin.Simkl.API
/// <param name="history">History object.</param>
/// <param name="userToken">User token.</param>
/// <returns>The sync history response.</returns>
private async Task<SyncHistoryResponse> SyncHistoryAsync(SimklHistory history, string userToken)
private async Task<SyncHistoryResponse?> SyncHistoryAsync(SimklHistory history, string userToken)
{
try
{
_logger.LogInformation("Syncing History: " + _json.SerializeToString(history));
return _json.DeserializeFromStream<SyncHistoryResponse>(await Post("/sync/history", userToken, history).ConfigureAwait(false));
_logger.LogInformation("Syncing History");
return await Post<SyncHistoryResponse, SimklHistory>("/sync/history", userToken, history);
}
catch (MediaBrowser.Model.Net.HttpException e) when (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
catch (HttpRequestException e) when (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_logger.LogError("Invalid user token " + userToken + ", deleting");
SimklPlugin.Instance.Configuration.DeleteUserToken(userToken);
_logger.LogError(e, "Invalid user token {UserToken}, deleting", userToken);
SimklPlugin.Instance?.Configuration.DeleteUserToken(userToken);
throw new InvalidTokenException("Invalid user token " + userToken);
}
}
@@ -250,13 +256,15 @@ namespace Jellyfin.Plugin.Simkl.API
/// <param name="url">Relative url.</param>
/// <param name="userToken">Authentication token.</param>
/// <returns>HTTP(s) Stream to be used.</returns>
private async Task<Stream> Get(string url, string userToken = null)
private async Task<T?> Get<T>(string url, string? userToken = null)
{
// Todo: If string is not null neither empty
var options = GetOptions(userToken);
options.Url = Baseurl + url;
return await _httpClient.Get(options).ConfigureAwait(false);
using var options = GetOptions(userToken);
options.RequestUri = new Uri(Baseurl + url);
options.Method = HttpMethod.Get;
var responseMessage = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(options);
return await responseMessage.Content.ReadFromJsonAsync<T>(_jsonSerializerOptions);
}
/// <summary>
@@ -265,16 +273,23 @@ namespace Jellyfin.Plugin.Simkl.API
/// <param name="url">Relative post url.</param>
/// <param name="userToken">Authentication token.</param>
/// <param name="data">Object to serialize.</param>
private async Task<Stream> Post(string url, string userToken = null, object data = null)
private async Task<T1?> Post<T1, T2>(string url, string? userToken = null, T2? data = null)
where T2 : class
{
var options = GetOptions(userToken);
options.Url = Baseurl + url;
using var options = GetOptions(userToken);
options.RequestUri = new Uri(Baseurl + url);
options.Method = HttpMethod.Post;
if (data != null)
{
options.RequestContent = _json.SerializeToString(data);
options.Content = new StringContent(
JsonSerializer.Serialize(data, _jsonSerializerOptions),
Encoding.UTF8,
MediaTypeNames.Application.Json);
}
return (await _httpClient.Post(options).ConfigureAwait(false)).Content;
var responseMessage = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(options);
return await responseMessage.Content.ReadFromJsonAsync<T1>(_jsonSerializerOptions);
}
}
}