using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
using Jellyfin.Plugin.AnilistSync.API.Exceptions;
namespace Jellyfin.Plugin.AnilistSync.API
{
///
/// Anilist API.
///
public class AnilistApi
{
// Interfaces //
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _jsonSerializerOptions;
// Base URLs //
///
/// Base url for OAUth uses.
///
public const string BaseOauthUrl = @"https://anilist.co/api/v2";
///
/// Base url for GraphQL queries.
///
public const string GraphQLUrl = @"https://graphql.anilist.co";
///
/// Generic GraphQL query string used for list updates.
///
public const string QueryString = @"
mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int,) {
SaveMediaListEntry(id: $id, mediaId: $mediaId, status: $status, progress: $progress ) {
id,
mediaId,
status,
progress
}
}";
///
/// Anilist client ID.
///
public const int ClientId = 5659;
///
/// Secret.
///
public const string Secret = @"h7ym2GZ6OjrdJ9sygDP7kDnQWsBdTwp4U8s7pt4X";
///
/// Initializes a new instance of class.
///
/// Instance of the interface.
/// Instance of the interface.
public AnilistApi(ILogger logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_jsonSerializerOptions = JsonDefaults.GetOptions();
}
///
/// Get token.
///
/// code.
///
public async Task GetToken(string? code)
{
string uri = @"/oauth/token";
string payload = JsonSerializer.Serialize(new OAuth
{
GrantType = "authorization_code",
ClientId = ClientId,
ClientSecret = Secret,
RedirectUri = $"{ BaseOauthUrl }/oauth/pin",
Code = code
});
using HttpContent content = new StringContent(payload, Encoding.UTF8, "application/json");
var responseMessage = await _httpClientFactory.CreateClient(NamedClient.Default).PostAsync(BaseOauthUrl + uri, content);
return await responseMessage.Content.ReadFromJsonAsync(_jsonSerializerOptions);
}
///
/// Gets total episodes of specified Anilist mediaID.
///
/// Anilist ID.
/// containing total episodes parameter.
public async Task GetEpisodes(int? anilistId)
{
GraphQLBody content = new GraphQLBody {
Query = @"query ($mediaId: Int) { Media (id: $mediaId) { episodes }}",
Variables = new ListEntry
{
MediaId = anilistId,
Id = null,
Progress = null,
Status = null
}
};
return await Post(content);
}
///
/// Gets the name and ID of the currently authenticated user.
///
/// User token.
/// containing object.
public async Task GetUser(string? userToken)
{
GraphQLBody content = new GraphQLBody {
Query = @"query { Viewer { id, name }}",
Variables = null
};
return await PostWithAuth(userToken, content);
}
///
/// Gets user's list ID of specified Anilist mediaID
///
/// User token.
/// Anilist mediaID
/// containing
public async Task GetListEntry(string? userToken, int? anilistId)
{
GraphQLBody content = new GraphQLBody
{
Query = QueryString,
Variables = new ListEntry
{
MediaId = anilistId,
Id = null,
Progress = null,
Status = null
}
};
return await PostWithAuth(userToken, content);
}
///
/// Updates status of specified list item
///
/// User token.
/// List ID
/// Status.
/// containing updated list item.
public async Task PostListStatusUpdate(string? userToken, int? listId, MediaListStatus? status)
{
GraphQLBody content = new GraphQLBody
{
Query = QueryString,
Variables = new ListEntry
{
MediaId = null,
Id = listId,
Progress = null,
Status = status
}
};
return await PostWithAuth(userToken, content);
}
///
/// Updates progress of specified list item.
///
/// User token.
/// List ID>
/// Progress.
/// containing updated list item.
public async Task PostListProgressUpdate(string? userToken, int? listId, int? progress)
{
GraphQLBody content = new GraphQLBody
{
Query = QueryString,
Variables = new ListEntry
{
MediaId = null,
Id = listId,
Progress = progress,
Status = null
}
};
return await PostWithAuth(userToken, content);
}
///
/// API private GraphQL Post WITHOUT authentication.
///
/// containing query and variables
/// containing API response.
public async Task Post(GraphQLBody graphQL)
{
var responseMessage = await _httpClientFactory.CreateClient(NamedClient.Default).PostAsJsonAsync(GraphQLUrl, graphQL, _jsonSerializerOptions);
var root = await responseMessage.Content.ReadFromJsonAsync(_jsonSerializerOptions);
if (root?.Errors != null)
{
foreach (AnilistError error in root.Errors)
{
throw new AnilistAPIException(error.ErrorMessage, error.ErrorStatus, error.Locations);
}
}
return root;
}
///
/// API private GraphQL Post WITH authentication.
///
/// User token.
/// containing query and variables
/// containing API response.
public async Task PostWithAuth(string? userToken, GraphQLBody graphQL)
{
using var requestMessage = new HttpRequestMessage
{
RequestUri = new Uri(GraphQLUrl),
Method = HttpMethod.Post
};
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", userToken);
string content = JsonSerializer.Serialize(graphQL, _jsonSerializerOptions);
requestMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
var responseMessage = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(requestMessage);
var root = await responseMessage.Content.ReadFromJsonAsync(_jsonSerializerOptions);
if (root?.Errors != null)
{
foreach (AnilistError error in root.Errors)
{
throw new AnilistAPIException(error.ErrorMessage, error.ErrorStatus, error.Locations);
}
}
return root;
}
}
}