#nullable enable
using System.Collections.Generic;
using System.Threading.Tasks;
using API.Data;
using API.DTOs.Collection;
using API.DTOs.KavitaPlus.ExternalMetadata;
using API.DTOs.KavitaPlus.Metadata;
using API.DTOs.Metadata.Matching;
using API.DTOs.Scrobbling;
using API.Entities.Enums;
using API.Extensions;
using Flurl.Http;
using Kavita.Common;
using Microsoft.Extensions.Logging;
namespace API.Services.Plus;
///
/// All Http requests to K+ should be contained in this service, the service will not handle any errors.
/// This is expected from the caller.
///
public interface IKavitaPlusApiService
{
Task HasTokenExpired(string license, string token, ScrobbleProvider provider);
Task GetRateLimit(string license, string token);
Task PostScrobbleUpdate(ScrobbleDto data, string license);
Task> GetMalStacks(string malUsername, string license);
Task> MatchSeries(MatchSeriesRequestDto request);
Task GetSeriesDetail(PlusSeriesRequestDto request);
Task GetSeriesDetailById(ExternalMetadataIdsDto request);
}
public class KavitaPlusApiService(ILogger logger, IUnitOfWork unitOfWork): IKavitaPlusApiService
{
private const string ScrobblingPath = "/api/scrobbling/";
public async Task HasTokenExpired(string license, string token, ScrobbleProvider provider)
{
var res = await Get(ScrobblingPath + "valid-key?provider=" + provider + "&key=" + token, license, token);
var str = await res.GetStringAsync();
return bool.Parse(str);
}
public async Task GetRateLimit(string license, string token)
{
var res = await Get(ScrobblingPath + "rate-limit?accessToken=" + token, license, token);
var str = await res.GetStringAsync();
return int.Parse(str);
}
public async Task PostScrobbleUpdate(ScrobbleDto data, string license)
{
return await PostAndReceive(ScrobblingPath + "update", data, license);
}
public async Task> GetMalStacks(string malUsername, string license)
{
return await $"{Configuration.KavitaPlusApiUrl}/api/metadata/v2/stacks?username={malUsername}"
.WithKavitaPlusHeaders(license)
.GetJsonAsync>();
}
public async Task> MatchSeries(MatchSeriesRequestDto request)
{
var license = (await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var token = (await unitOfWork.UserRepository.GetDefaultAdminUser()).AniListAccessToken;
return await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/match-series")
.WithKavitaPlusHeaders(license, token)
.PostJsonAsync(request)
.ReceiveJson>();
}
public async Task GetSeriesDetail(PlusSeriesRequestDto request)
{
var license = (await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var token = (await unitOfWork.UserRepository.GetDefaultAdminUser()).AniListAccessToken;
return await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-detail")
.WithKavitaPlusHeaders(license, token)
.PostJsonAsync(request)
.ReceiveJson();
}
public async Task GetSeriesDetailById(ExternalMetadataIdsDto request)
{
var license = (await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var token = (await unitOfWork.UserRepository.GetDefaultAdminUser()).AniListAccessToken;
return await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-by-ids")
.WithKavitaPlusHeaders(license, token)
.PostJsonAsync(request)
.ReceiveJson();
}
///
/// Send a GET request to K+
///
/// only path of the uri, the host is added
///
///
///
private static async Task Get(string url, string license, string? aniListToken = null)
{
return await (Configuration.KavitaPlusApiUrl + url)
.WithKavitaPlusHeaders(license, aniListToken)
.GetAsync();
}
///
/// Send a POST request to K+
///
/// only path of the uri, the host is added
///
///
///
/// Return type
///
private static async Task PostAndReceive(string url, object body, string license, string? aniListToken = null)
{
return await (Configuration.KavitaPlusApiUrl + url)
.WithKavitaPlusHeaders(license, aniListToken)
.PostJsonAsync(body)
.ReceiveJson();
}
}