Added XML comments
This commit is contained in:
@@ -6,9 +6,6 @@ MinimumVisualStudioVersion = 10.0.40219.1
|
|||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Plugin.AnilistSync", "Jellyfin.Plugin.AnilistSync\Jellyfin.Plugin.AnilistSync.csproj", "{2C1895EC-A7E1-48B6-A2E0-3E847D1FB748}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Plugin.AnilistSync", "Jellyfin.Plugin.AnilistSync\Jellyfin.Plugin.AnilistSync.csproj", "{2C1895EC-A7E1-48B6-A2E0-3E847D1FB748}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{785517C0-5B12-4228-9D64-2495680C537A}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{785517C0-5B12-4228-9D64-2495680C537A}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
.editorconfig = .editorconfig
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
@@ -12,15 +12,31 @@ using Jellyfin.Plugin.AnilistSync.API.Exceptions;
|
|||||||
|
|
||||||
namespace Jellyfin.Plugin.AnilistSync.API
|
namespace Jellyfin.Plugin.AnilistSync.API
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Anilist API.
|
||||||
|
/// </summary>
|
||||||
public class AnilistApi
|
public class AnilistApi
|
||||||
{
|
{
|
||||||
|
// Interfaces //
|
||||||
private readonly ILogger<AnilistApi> _logger;
|
private readonly ILogger<AnilistApi> _logger;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||||
|
|
||||||
|
// Base URLs //
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base url for OAUth uses.
|
||||||
|
/// </summary>
|
||||||
public const string BaseOauthUrl = @"https://anilist.co/api/v2";
|
public const string BaseOauthUrl = @"https://anilist.co/api/v2";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base url for GraphQL queries.
|
||||||
|
/// </summary>
|
||||||
public const string GraphQLUrl = @"https://graphql.anilist.co";
|
public const string GraphQLUrl = @"https://graphql.anilist.co";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generic GraphQL query string used for list updates.
|
||||||
|
/// </summary>
|
||||||
public const string QueryString = @"
|
public const string QueryString = @"
|
||||||
mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
||||||
SaveMediaListEntry(id: $id, mediaId: $mediaId, status: $status, progress: $progress ) {
|
SaveMediaListEntry(id: $id, mediaId: $mediaId, status: $status, progress: $progress ) {
|
||||||
@@ -30,10 +46,21 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
progress
|
progress
|
||||||
}
|
}
|
||||||
}";
|
}";
|
||||||
|
/// <summary>
|
||||||
|
/// Anilist client ID.
|
||||||
|
/// </summary>
|
||||||
public const int ClientId = 5659;
|
public const int ClientId = 5659;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Secret.
|
||||||
|
/// </summary>
|
||||||
public const string Secret = @"h7ym2GZ6OjrdJ9sygDP7kDnQWsBdTwp4U8s7pt4X";
|
public const string Secret = @"h7ym2GZ6OjrdJ9sygDP7kDnQWsBdTwp4U8s7pt4X";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="AnilistApi"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Instance of the <see cref="ILogger{AnilistApi}"/> interface.</param>
|
||||||
|
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||||
public AnilistApi(ILogger<AnilistApi> logger, IHttpClientFactory httpClientFactory)
|
public AnilistApi(ILogger<AnilistApi> logger, IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@@ -41,7 +68,11 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
_jsonSerializerOptions = JsonDefaults.GetOptions();
|
_jsonSerializerOptions = JsonDefaults.GetOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get token.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="code">code.</param>
|
||||||
|
/// <returns><see cref="CodeResponse"/></returns>
|
||||||
public async Task<CodeResponse?> GetToken(string? code)
|
public async Task<CodeResponse?> GetToken(string? code)
|
||||||
{
|
{
|
||||||
string uri = @"/oauth/token";
|
string uri = @"/oauth/token";
|
||||||
@@ -58,6 +89,11 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
return await responseMessage.Content.ReadFromJsonAsync<CodeResponse>(_jsonSerializerOptions);
|
return await responseMessage.Content.ReadFromJsonAsync<CodeResponse>(_jsonSerializerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets total episodes of specified Anilist mediaID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anilistId">Anilist ID.</param>
|
||||||
|
/// <returns><see cref="RootObject"/> containing total episodes parameter.</returns>
|
||||||
public async Task<RootObject?> GetEpisodes(int? anilistId)
|
public async Task<RootObject?> GetEpisodes(int? anilistId)
|
||||||
{
|
{
|
||||||
GraphQLBody content = new GraphQLBody {
|
GraphQLBody content = new GraphQLBody {
|
||||||
@@ -73,6 +109,11 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
return await Post(content);
|
return await Post(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name and ID of the currently authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userToken">User token.</param>
|
||||||
|
/// <returns><see cref="RootObject"/>containing <see cref="User"/> object.</returns>
|
||||||
public async Task<RootObject?> GetUser(string? userToken)
|
public async Task<RootObject?> GetUser(string? userToken)
|
||||||
{
|
{
|
||||||
GraphQLBody content = new GraphQLBody {
|
GraphQLBody content = new GraphQLBody {
|
||||||
@@ -82,6 +123,12 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
return await PostWithAuth(userToken, content);
|
return await PostWithAuth(userToken, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets user's list ID of specified Anilist mediaID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userToken">User token.</param>
|
||||||
|
/// <param name="anilistId">Anilist mediaID</param>
|
||||||
|
/// <returns><see cref="RootObject"/> containing <see cref="ListEntry"/></returns>
|
||||||
public async Task<RootObject?> GetListEntry(string? userToken, int? anilistId)
|
public async Task<RootObject?> GetListEntry(string? userToken, int? anilistId)
|
||||||
{
|
{
|
||||||
GraphQLBody content = new GraphQLBody
|
GraphQLBody content = new GraphQLBody
|
||||||
@@ -98,6 +145,13 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
return await PostWithAuth(userToken, content);
|
return await PostWithAuth(userToken, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates status of specified list item
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userToken">User token.</param>
|
||||||
|
/// <param name="listId">List ID</param>
|
||||||
|
/// <param name="status">Status.</param>
|
||||||
|
/// <returns><see cref="RootObject"/> containing updated list item.</returns>
|
||||||
public async Task<RootObject?> PostListStatusUpdate(string? userToken, int? listId, MediaListStatus? status)
|
public async Task<RootObject?> PostListStatusUpdate(string? userToken, int? listId, MediaListStatus? status)
|
||||||
{
|
{
|
||||||
GraphQLBody content = new GraphQLBody
|
GraphQLBody content = new GraphQLBody
|
||||||
@@ -114,6 +168,13 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
return await PostWithAuth(userToken, content);
|
return await PostWithAuth(userToken, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates progress of specified list item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userToken">User token.</param>
|
||||||
|
/// <param name="listId">List ID></param>
|
||||||
|
/// <param name="progress">Progress.</param>
|
||||||
|
/// <returns><see cref="RootObject"/> containing updated list item.</returns>
|
||||||
public async Task<RootObject?> PostListProgressUpdate(string? userToken, int? listId, int? progress)
|
public async Task<RootObject?> PostListProgressUpdate(string? userToken, int? listId, int? progress)
|
||||||
{
|
{
|
||||||
GraphQLBody content = new GraphQLBody
|
GraphQLBody content = new GraphQLBody
|
||||||
@@ -130,6 +191,11 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
return await PostWithAuth(userToken, content);
|
return await PostWithAuth(userToken, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API private GraphQL Post WITHOUT authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="graphQL"><see cref="GraphQLBody"/> containing query and variables</param>
|
||||||
|
/// <returns><see cref="RootObject"/> containing API response.</returns>
|
||||||
public async Task<RootObject?> Post(GraphQLBody graphQL)
|
public async Task<RootObject?> Post(GraphQLBody graphQL)
|
||||||
{
|
{
|
||||||
var responseMessage = await _httpClientFactory.CreateClient(NamedClient.Default).PostAsJsonAsync<GraphQLBody>(GraphQLUrl, graphQL, _jsonSerializerOptions);
|
var responseMessage = await _httpClientFactory.CreateClient(NamedClient.Default).PostAsJsonAsync<GraphQLBody>(GraphQLUrl, graphQL, _jsonSerializerOptions);
|
||||||
@@ -144,6 +210,12 @@ mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API private GraphQL Post WITH authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userToken">User token.</param>
|
||||||
|
/// <param name="graphQL"><see cref="GraphQLBody"/> containing query and variables</param>
|
||||||
|
/// <returns><see cref="RootObject"/> containing API response.</returns>
|
||||||
public async Task<RootObject?> PostWithAuth(string? userToken, GraphQLBody graphQL)
|
public async Task<RootObject?> PostWithAuth(string? userToken, GraphQLBody graphQL)
|
||||||
{
|
{
|
||||||
using var requestMessage = new HttpRequestMessage
|
using var requestMessage = new HttpRequestMessage
|
||||||
|
|||||||
@@ -2,126 +2,249 @@
|
|||||||
|
|
||||||
namespace Jellyfin.Plugin.AnilistSync.API
|
namespace Jellyfin.Plugin.AnilistSync.API
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Media list status enum,
|
||||||
|
/// </summary>
|
||||||
public enum MediaListStatus
|
public enum MediaListStatus
|
||||||
{
|
{
|
||||||
|
/// <summary>Currently watching.</summary>
|
||||||
CURRENT,
|
CURRENT,
|
||||||
|
/// <summary>Planning to watch.</summary>
|
||||||
PLANNING,
|
PLANNING,
|
||||||
|
/// <summary>Completed.</summary>
|
||||||
COMPLETED,
|
COMPLETED,
|
||||||
|
/// <summary>Dropped.</summary>
|
||||||
DROPPED,
|
DROPPED,
|
||||||
|
/// <summary>n hold.</summary>
|
||||||
PAUSED,
|
PAUSED,
|
||||||
|
/// <summary>Rewatching.</summary>
|
||||||
REPEATING
|
REPEATING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Root JSON object.
|
||||||
|
/// </summary>
|
||||||
public class RootObject
|
public class RootObject
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets data.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("data")]
|
[JsonPropertyName("data")]
|
||||||
public Data? Data { get; set; }
|
public Data? Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets errors.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("errors")]
|
[JsonPropertyName("errors")]
|
||||||
public AnilistError[]? Errors { get; set; }
|
public AnilistError[]? Errors { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data JSON Object.
|
||||||
|
/// </summary>
|
||||||
public class Data
|
public class Data
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets user.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("Viewer")]
|
[JsonPropertyName("Viewer")]
|
||||||
public User? User { get; set; }
|
public User? User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets list entry.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("SaveMediaListEntry")]
|
[JsonPropertyName("SaveMediaListEntry")]
|
||||||
public ListEntry? ListEntry { get; set; }
|
public ListEntry? ListEntry { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets media.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("Media")]
|
[JsonPropertyName("Media")]
|
||||||
public Media? Media { get; set; }
|
public Media? Media { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error object.
|
||||||
|
/// </summary>
|
||||||
public class AnilistError
|
public class AnilistError
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets error message.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("message")]
|
[JsonPropertyName("message")]
|
||||||
public string? ErrorMessage { get; set; }
|
public string? ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets error status.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("status")]
|
[JsonPropertyName("status")]
|
||||||
public int? ErrorStatus { get; set; }
|
public int? ErrorStatus { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets error locations.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("locations")]
|
[JsonPropertyName("locations")]
|
||||||
public Location[]? Locations { get; set; }
|
public Location[]? Locations { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locations of error(s) object.
|
||||||
|
/// </summary>
|
||||||
public class Location
|
public class Location
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets line.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("line")]
|
[JsonPropertyName("line")]
|
||||||
public int? Line { get; set; }
|
public int? Line { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets column.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("column")]
|
[JsonPropertyName("column")]
|
||||||
public int? Column { get; set; }
|
public int? Column { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List Entry object.
|
||||||
|
/// </summary>
|
||||||
public class ListEntry
|
public class ListEntry
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets mediaId.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("mediaId")]
|
[JsonPropertyName("mediaId")]
|
||||||
public int? MediaId { get; set; }
|
public int? MediaId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets ID.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("id")]
|
[JsonPropertyName("id")]
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets progress.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("progress")]
|
[JsonPropertyName("progress")]
|
||||||
public int? Progress { get; set; }
|
public int? Progress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets status.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("status")]
|
[JsonPropertyName("status")]
|
||||||
public MediaListStatus? Status { get; set; }
|
public MediaListStatus? Status { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Media object.
|
||||||
|
/// </summary>
|
||||||
public class Media
|
public class Media
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets episodes.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("episodes")]
|
[JsonPropertyName("episodes")]
|
||||||
public int? Episodes { get; set; }
|
public int? Episodes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User object.
|
||||||
|
/// </summary>
|
||||||
public class User
|
public class User
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets user ID.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("id")]
|
[JsonPropertyName("id")]
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets user name.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("name")]
|
[JsonPropertyName("name")]
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GraphQLBody object.
|
||||||
|
/// </summary>
|
||||||
public class GraphQLBody
|
public class GraphQLBody
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets GraphQL query string.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("query")]
|
[JsonPropertyName("query")]
|
||||||
public string? Query { get; set; }
|
public string? Query { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets GraphQL variables.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("variables")]
|
[JsonPropertyName("variables")]
|
||||||
public ListEntry? Variables { get; set; }
|
public ListEntry? Variables { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OAuth response object.
|
||||||
|
/// </summary>
|
||||||
public class OAuth
|
public class OAuth
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets grant type.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("grant_type")]
|
[JsonPropertyName("grant_type")]
|
||||||
public string? GrantType { get; set; }
|
public string? GrantType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets client ID.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("client_id")]
|
[JsonPropertyName("client_id")]
|
||||||
public int? ClientId { get; set; }
|
public int? ClientId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets client secret.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("client_secret")]
|
[JsonPropertyName("client_secret")]
|
||||||
public string? ClientSecret { get; set; }
|
public string? ClientSecret { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets redirect URI.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("redirect_uri")]
|
[JsonPropertyName("redirect_uri")]
|
||||||
public string? RedirectUri { get; set; }
|
public string? RedirectUri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets code.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("code")]
|
[JsonPropertyName("code")]
|
||||||
public string? Code { get; set; }
|
public string? Code { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Code response object.
|
||||||
|
/// </summary>
|
||||||
public class CodeResponse
|
public class CodeResponse
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets token type.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("token_type")]
|
[JsonPropertyName("token_type")]
|
||||||
public string? TokenType { get; set; }
|
public string? TokenType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets expires in.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("expires_in")]
|
[JsonPropertyName("expires_in")]
|
||||||
public int? ExpiresIn { get; set; }
|
public int? ExpiresIn { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets access token.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("access_token")]
|
[JsonPropertyName("access_token")]
|
||||||
public string? AccessToken { get; set; }
|
public string? AccessToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets refresh token.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("refresh_token")]
|
[JsonPropertyName("refresh_token")]
|
||||||
public string? RefreshToken { get; set; }
|
public string? RefreshToken { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
|
|
||||||
namespace Jellyfin.Plugin.AnilistSync.API
|
namespace Jellyfin.Plugin.AnilistSync.API
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Anilist endpoints.
|
||||||
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Authorize(Policy = "DefaultAuthorization")]
|
[Authorize(Policy = "DefaultAuthorization")]
|
||||||
[Route("AnilistSync")]
|
[Route("AnilistSync")]
|
||||||
@@ -12,17 +15,31 @@ namespace Jellyfin.Plugin.AnilistSync.API
|
|||||||
{
|
{
|
||||||
private readonly AnilistApi _anilistApi;
|
private readonly AnilistApi _anilistApi;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instacne fo the <see cref="Endpoints"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anilistApi"></param>
|
||||||
public Endpoints(AnilistApi anilistApi)
|
public Endpoints(AnilistApi anilistApi)
|
||||||
{
|
{
|
||||||
_anilistApi = anilistApi;
|
_anilistApi = anilistApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the OAuth token.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userCode">User code.</param>
|
||||||
|
/// <returns>Code response containing token.</returns>
|
||||||
[HttpGet("oauth/token/{userCode}")]
|
[HttpGet("oauth/token/{userCode}")]
|
||||||
public async Task<ActionResult<CodeResponse?>> GetToken([FromRoute] string userCode)
|
public async Task<ActionResult<CodeResponse?>> GetToken([FromRoute] string userCode)
|
||||||
{
|
{
|
||||||
return await _anilistApi.GetToken(userCode);
|
return await _anilistApi.GetToken(userCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the currently authenticated user
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user ID.</param>
|
||||||
|
/// <returns>Root object containing User object</returns>
|
||||||
[HttpGet("users/settings/{userId}")]
|
[HttpGet("users/settings/{userId}")]
|
||||||
public async Task<ActionResult<RootObject?>> GetUser([FromRoute] Guid userId)
|
public async Task<ActionResult<RootObject?>> GetUser([FromRoute] Guid userId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,20 +18,23 @@ namespace Jellyfin.Plugin.AnilistSync.Configuration
|
|||||||
ScrobblePercentage = 80;
|
ScrobblePercentage = 80;
|
||||||
ScrobbleNowWatchingPercentage = 5;
|
ScrobbleNowWatchingPercentage = 5;
|
||||||
MinLength = 5;
|
MinLength = 5;
|
||||||
UserToken = string.Empty; // Todo: check if token is still valid
|
UserToken = string.Empty;
|
||||||
ScrobbleTimeout = 30;
|
ScrobbleTimeout = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether scrobble movies.
|
/// Gets or sets a value indicating whether to scrobble movies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ScrobbleMovies { get; set; }
|
public bool ScrobbleMovies { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether scrobble shows.
|
/// Gets or sets a value indicating whether to scrobble shows.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ScrobbleShows { get; set; }
|
public bool ScrobbleShows { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to scrobble rewatches.
|
||||||
|
/// </summary>
|
||||||
public bool ScrobbleRewatches { get; set; }
|
public bool ScrobbleRewatches { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -55,7 +58,7 @@ namespace Jellyfin.Plugin.AnilistSync.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets user token.
|
/// Gets or sets user token.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UserToken { get; set; } // Is the user logged in
|
public string UserToken { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets scrobble timeout.
|
/// Gets or sets scrobble timeout.
|
||||||
|
|||||||
@@ -37,9 +37,5 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -11,23 +11,37 @@ using MediaBrowser.Model.Serialization;
|
|||||||
|
|
||||||
namespace Jellyfin.Plugin.AnilistSync
|
namespace Jellyfin.Plugin.AnilistSync
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Anilist Scrobbler.
|
||||||
|
/// </summary>
|
||||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||||
{
|
{
|
||||||
public override string Name => "AnilistSync";
|
/// <summary>
|
||||||
public override Guid Id => Guid.Parse("18c2a8ea-afa0-4a0b-aa94-072b492ab80b");
|
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
||||||
public override string Description => "Jellyfin plugin to scrobble to Anilist";
|
/// </summary>
|
||||||
public Version version = new Version("2.2.0.0");
|
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
||||||
|
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
||||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||||
: base(applicationPaths, xmlSerializer)
|
: base(applicationPaths, xmlSerializer)
|
||||||
{
|
{
|
||||||
Instance = this;
|
Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string Name => "AnilistSync";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override Guid Id => Guid.Parse("18c2a8ea-afa0-4a0b-aa94-072b492ab80b");
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string Description => "Jellyfin plugin to scrobble to Anilist";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current instance of the plugin
|
||||||
|
/// </summary>
|
||||||
public static Plugin? Instance { get; private set; }
|
public static Plugin? Instance { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public IEnumerable<PluginPageInfo> GetPages()
|
public IEnumerable<PluginPageInfo> GetPages()
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
|
|||||||
@@ -15,15 +15,23 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Jellyfin.Plugin.AnilistSync.Services
|
namespace Jellyfin.Plugin.AnilistSync.Services
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Playback progress scrobbler.
|
||||||
|
/// </summary>
|
||||||
public class PlaybackScrobbler : IServerEntryPoint
|
public class PlaybackScrobbler : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private readonly ISessionManager _sessionManager; // Needed to set up de startPlayBack and endPlayBack functions
|
private readonly ISessionManager _sessionManager; // Needed to set up the startPlayBack and endPlayBack functions
|
||||||
private readonly ILogger<PlaybackScrobbler> _logger;
|
private readonly ILogger<PlaybackScrobbler> _logger;
|
||||||
private readonly Dictionary<string, Guid> _lastScrobbled; // Library ID of last scrobbled item
|
private readonly Dictionary<string, Guid> _lastScrobbled; // Library ID of last scrobbled item
|
||||||
private readonly AnilistApi _anilistApi;
|
private readonly AnilistApi _anilistApi;
|
||||||
private DateTime _nextTry;
|
private DateTime _nextTry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PlaybackScrobbler"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
|
||||||
|
/// <param name="logger">Instance of the <see cref="ILogger{PlaybackScrobbler}"/> interface.</param>
|
||||||
|
/// <param name="anilistApi">Instance of the <see cref="AnilistApi"/>.</param>
|
||||||
public PlaybackScrobbler(ISessionManager sessionManager, ILogger<PlaybackScrobbler> logger, AnilistApi anilistApi)
|
public PlaybackScrobbler(ISessionManager sessionManager, ILogger<PlaybackScrobbler> logger, AnilistApi anilistApi)
|
||||||
{
|
{
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
@@ -33,6 +41,7 @@ namespace Jellyfin.Plugin.AnilistSync.Services
|
|||||||
_nextTry = DateTime.UtcNow;
|
_nextTry = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Task RunAsync()
|
public Task RunAsync()
|
||||||
{
|
{
|
||||||
_sessionManager.PlaybackProgress += OnPlaybackProgress;
|
_sessionManager.PlaybackProgress += OnPlaybackProgress;
|
||||||
@@ -40,12 +49,17 @@ namespace Jellyfin.Plugin.AnilistSync.Services
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">Dispoe all resources.</param>
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
@@ -238,11 +252,11 @@ namespace Jellyfin.Plugin.AnilistSync.Services
|
|||||||
if (currentIndex == totalEpisodes)
|
if (currentIndex == totalEpisodes)
|
||||||
{
|
{
|
||||||
status = MediaListStatus.COMPLETED;
|
status = MediaListStatus.COMPLETED;
|
||||||
var statusResponse = await _anilistApi.PostListStatusUpdate(userConfig.UserToken, listEntry?.Id, status);
|
await _anilistApi.PostListStatusUpdate(userConfig.UserToken, listEntry?.Id, status);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var response = await _anilistApi.PostListProgressUpdate(userConfig.UserToken, listEntry?.Id, currentIndex);
|
await _anilistApi.PostListProgressUpdate(userConfig.UserToken, listEntry?.Id, currentIndex);
|
||||||
}
|
}
|
||||||
_logger.LogInformation("Scrobbled episode: ({currentIndex} of {totalEpisodes}) for Anilist ID: ({anilistId})", currentIndex, totalEpisodes, anilistId);
|
_logger.LogInformation("Scrobbled episode: ({currentIndex} of {totalEpisodes}) for Anilist ID: ({anilistId})", currentIndex, totalEpisodes, anilistId);
|
||||||
_logger.LogInformation("Watch status: {status}", status);
|
_logger.LogInformation("Watch status: {status}", status);
|
||||||
|
|||||||
Reference in New Issue
Block a user