Refactored some apis to the new KavitaPlusApiService.

This commit is contained in:
Joseph Milazzo 2025-06-20 15:09:48 -05:00
parent 09d9edd828
commit 224e839dad
6 changed files with 75 additions and 40 deletions

View file

@ -6,7 +6,7 @@ namespace API.DTOs.KavitaPlus.ExternalMetadata;
/// <summary> /// <summary>
/// Used for matching and fetching metadata on a series /// Used for matching and fetching metadata on a series
/// </summary> /// </summary>
internal sealed record ExternalMetadataIdsDto public sealed record ExternalMetadataIdsDto
{ {
public long? MalId { get; set; } public long? MalId { get; set; }
public int? AniListId { get; set; } public int? AniListId { get; set; }

View file

@ -7,7 +7,7 @@ namespace API.DTOs.KavitaPlus.ExternalMetadata;
/// <summary> /// <summary>
/// Represents a request to match some series from Kavita to an external id which K+ uses. /// Represents a request to match some series from Kavita to an external id which K+ uses.
/// </summary> /// </summary>
internal sealed record MatchSeriesRequestDto public sealed record MatchSeriesRequestDto
{ {
public required string SeriesName { get; set; } public required string SeriesName { get; set; }
public ICollection<string> AlternativeNames { get; set; } = []; public ICollection<string> AlternativeNames { get; set; } = [];

View file

@ -6,7 +6,7 @@ using API.DTOs.SeriesDetail;
namespace API.DTOs.KavitaPlus.ExternalMetadata; namespace API.DTOs.KavitaPlus.ExternalMetadata;
internal sealed record SeriesDetailPlusApiDto public sealed record SeriesDetailPlusApiDto
{ {
public IEnumerable<MediaRecommendationDto> Recommendations { get; set; } public IEnumerable<MediaRecommendationDto> Recommendations { get; set; }
public IEnumerable<UserReviewDto> Reviews { get; set; } public IEnumerable<UserReviewDto> Reviews { get; set; }

View file

@ -67,6 +67,7 @@ public class ExternalMetadataService : IExternalMetadataService
private readonly IScrobblingService _scrobblingService; private readonly IScrobblingService _scrobblingService;
private readonly IEventHub _eventHub; private readonly IEventHub _eventHub;
private readonly ICoverDbService _coverDbService; private readonly ICoverDbService _coverDbService;
private readonly IKavitaPlusApiService _kavitaPlusApiService;
private readonly TimeSpan _externalSeriesMetadataCache = TimeSpan.FromDays(30); private readonly TimeSpan _externalSeriesMetadataCache = TimeSpan.FromDays(30);
public static readonly HashSet<LibraryType> NonEligibleLibraryTypes = public static readonly HashSet<LibraryType> NonEligibleLibraryTypes =
[LibraryType.Comic, LibraryType.Book, LibraryType.Image]; [LibraryType.Comic, LibraryType.Book, LibraryType.Image];
@ -82,7 +83,8 @@ public class ExternalMetadataService : IExternalMetadataService
private static bool IsRomanCharacters(string input) => Regex.IsMatch(input, @"^[\p{IsBasicLatin}\p{IsLatin-1Supplement}]+$"); private static bool IsRomanCharacters(string input) => Regex.IsMatch(input, @"^[\p{IsBasicLatin}\p{IsLatin-1Supplement}]+$");
public ExternalMetadataService(IUnitOfWork unitOfWork, ILogger<ExternalMetadataService> logger, IMapper mapper, public ExternalMetadataService(IUnitOfWork unitOfWork, ILogger<ExternalMetadataService> logger, IMapper mapper,
ILicenseService licenseService, IScrobblingService scrobblingService, IEventHub eventHub, ICoverDbService coverDbService) ILicenseService licenseService, IScrobblingService scrobblingService, IEventHub eventHub, ICoverDbService coverDbService,
IKavitaPlusApiService kavitaPlusApiService)
{ {
_unitOfWork = unitOfWork; _unitOfWork = unitOfWork;
_logger = logger; _logger = logger;
@ -91,6 +93,7 @@ public class ExternalMetadataService : IExternalMetadataService
_scrobblingService = scrobblingService; _scrobblingService = scrobblingService;
_eventHub = eventHub; _eventHub = eventHub;
_coverDbService = coverDbService; _coverDbService = coverDbService;
_kavitaPlusApiService = kavitaPlusApiService;
FlurlConfiguration.ConfigureClientForUrl(Configuration.KavitaPlusApiUrl); FlurlConfiguration.ConfigureClientForUrl(Configuration.KavitaPlusApiUrl);
} }
@ -179,9 +182,7 @@ public class ExternalMetadataService : IExternalMetadataService
_logger.LogDebug("Fetching Kavita+ for MAL Stacks for user {UserName}", user.MalUserName); _logger.LogDebug("Fetching Kavita+ for MAL Stacks for user {UserName}", user.MalUserName);
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value; var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var result = await ($"{Configuration.KavitaPlusApiUrl}/api/metadata/v2/stacks?username={user.MalUserName}") var result = await _kavitaPlusApiService.GetMalStacks(user.MalUserName, license);
.WithKavitaPlusHeaders(license)
.GetJsonAsync<IList<MalStackDto>>();
if (result == null) if (result == null)
{ {
@ -207,7 +208,7 @@ public class ExternalMetadataService : IExternalMetadataService
/// <returns></returns> /// <returns></returns>
public async Task<IList<ExternalSeriesMatchDto>> MatchSeries(MatchSeriesDto dto) public async Task<IList<ExternalSeriesMatchDto>> MatchSeries(MatchSeriesDto dto)
{ {
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(dto.SeriesId, var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(dto.SeriesId,
SeriesIncludes.Metadata | SeriesIncludes.ExternalMetadata | SeriesIncludes.Library); SeriesIncludes.Metadata | SeriesIncludes.ExternalMetadata | SeriesIncludes.Library);
if (series == null) return []; if (series == null) return [];
@ -239,14 +240,9 @@ public class ExternalMetadataService : IExternalMetadataService
MalId = potentialMalId ?? ScrobblingService.GetMalId(series) MalId = potentialMalId ?? ScrobblingService.GetMalId(series)
}; };
var token = (await _unitOfWork.UserRepository.GetDefaultAdminUser()).AniListAccessToken;
try try
{ {
var results = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/match-series") var results = await _kavitaPlusApiService.MatchSeries(matchRequest);
.WithKavitaPlusHeaders(license, token)
.PostJsonAsync(matchRequest)
.ReceiveJson<IList<ExternalSeriesMatchDto>>();
// Some summaries can contain multiple <br/>s, we need to ensure it's only 1 // Some summaries can contain multiple <br/>s, we need to ensure it's only 1
foreach (var result in results) foreach (var result in results)
@ -287,9 +283,7 @@ public class ExternalMetadataService : IExternalMetadataService
} }
// This is for the Series drawer. We can get this extra information during the initial SeriesDetail call so it's all coming from the DB // This is for the Series drawer. We can get this extra information during the initial SeriesDetail call so it's all coming from the DB
var details = await GetSeriesDetail(aniListId, malId, seriesId);
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var details = await GetSeriesDetail(license, aniListId, malId, seriesId);
return details; return details;
@ -442,16 +436,12 @@ public class ExternalMetadataService : IExternalMetadataService
try try
{ {
_logger.LogDebug("Fetching Kavita+ Series Detail data for {SeriesName}", string.IsNullOrEmpty(data.SeriesName) ? data.AniListId : data.SeriesName); _logger.LogDebug("Fetching Kavita+ Series Detail data for {SeriesName}", string.IsNullOrEmpty(data.SeriesName) ? data.AniListId : data.SeriesName);
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var token = (await _unitOfWork.UserRepository.GetDefaultAdminUser()).AniListAccessToken;
SeriesDetailPlusApiDto? result = null; SeriesDetailPlusApiDto? result = null;
try try
{ {
result = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-detail") // This returns an AniListSeries and Match returns ExternalSeriesDto
.WithKavitaPlusHeaders(license, token) result = await _kavitaPlusApiService .GetSeriesDetail(data);
.PostJsonAsync(data)
.ReceiveJson<SeriesDetailPlusApiDto>(); // This returns an AniListSeries and Match returns ExternalSeriesDto
} }
catch (FlurlHttpException ex) catch (FlurlHttpException ex)
{ {
@ -466,11 +456,7 @@ public class ExternalMetadataService : IExternalMetadataService
_logger.LogDebug("Hit rate limit, will retry in 3 seconds"); _logger.LogDebug("Hit rate limit, will retry in 3 seconds");
await Task.Delay(3000); await Task.Delay(3000);
result = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-detail") result = await _kavitaPlusApiService.GetSeriesDetail(data);
.WithKavitaPlusHeaders(license, token)
.PostJsonAsync(data)
.ReceiveJson<
SeriesDetailPlusApiDto>();
} }
else if (errorMessage.Contains("Unknown Series")) else if (errorMessage.Contains("Unknown Series"))
{ {
@ -1777,7 +1763,7 @@ public class ExternalMetadataService : IExternalMetadataService
/// <param name="malId"></param> /// <param name="malId"></param>
/// <param name="seriesId"></param> /// <param name="seriesId"></param>
/// <returns></returns> /// <returns></returns>
private async Task<ExternalSeriesDetailDto?> GetSeriesDetail(string license, int? aniListId, long? malId, int? seriesId) private async Task<ExternalSeriesDetailDto?> GetSeriesDetail(int? aniListId, long? malId, int? seriesId)
{ {
var payload = new ExternalMetadataIdsDto() var payload = new ExternalMetadataIdsDto()
{ {
@ -1809,11 +1795,7 @@ public class ExternalMetadataService : IExternalMetadataService
} }
try try
{ {
var token = (await _unitOfWork.UserRepository.GetDefaultAdminUser()).AniListAccessToken; var ret = await _kavitaPlusApiService.GetSeriesDetailById(payload);
var ret = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-by-ids")
.WithKavitaPlusHeaders(license, token)
.PostJsonAsync(payload)
.ReceiveJson<ExternalSeriesDetailDto>();
ret.Summary = StringHelper.RemoveSourceInDescription(StringHelper.SquashBreaklines(ret.Summary)); ret.Summary = StringHelper.RemoveSourceInDescription(StringHelper.SquashBreaklines(ret.Summary));

View file

@ -1,6 +1,13 @@
#nullable enable #nullable enable
using System.Collections.Generic;
using System.Threading.Tasks; 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.DTOs.Scrobbling;
using API.Entities.Enums;
using API.Extensions; using API.Extensions;
using Flurl.Http; using Flurl.Http;
using Kavita.Common; using Kavita.Common;
@ -17,9 +24,13 @@ public interface IKavitaPlusApiService
Task<bool> HasTokenExpired(string license, string token, ScrobbleProvider provider); Task<bool> HasTokenExpired(string license, string token, ScrobbleProvider provider);
Task<int> GetRateLimit(string license, string token); Task<int> GetRateLimit(string license, string token);
Task<ScrobbleResponseDto> PostScrobbleUpdate(ScrobbleDto data, string license); Task<ScrobbleResponseDto> PostScrobbleUpdate(ScrobbleDto data, string license);
Task<IList<MalStackDto>> GetMalStacks(string malUsername, string license);
Task<IList<ExternalSeriesMatchDto>> MatchSeries(MatchSeriesRequestDto request);
Task<SeriesDetailPlusApiDto> GetSeriesDetail(PlusSeriesRequestDto request);
Task<ExternalSeriesDetailDto> GetSeriesDetailById(ExternalMetadataIdsDto request);
} }
public class KavitaPlusApiService(ILogger<KavitaPlusApiService> logger): IKavitaPlusApiService public class KavitaPlusApiService(ILogger<KavitaPlusApiService> logger, IUnitOfWork unitOfWork): IKavitaPlusApiService
{ {
private const string ScrobblingPath = "/api/scrobbling/"; private const string ScrobblingPath = "/api/scrobbling/";
@ -42,6 +53,46 @@ public class KavitaPlusApiService(ILogger<KavitaPlusApiService> logger): IKavita
return await PostAndReceive<ScrobbleResponseDto>(ScrobblingPath + "update", data, license); return await PostAndReceive<ScrobbleResponseDto>(ScrobblingPath + "update", data, license);
} }
public async Task<IList<MalStackDto>> GetMalStacks(string malUsername, string license)
{
return await $"{Configuration.KavitaPlusApiUrl}/api/metadata/v2/stacks?username={malUsername}"
.WithKavitaPlusHeaders(license)
.GetJsonAsync<IList<MalStackDto>>();
}
public async Task<IList<ExternalSeriesMatchDto>> 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<IList<ExternalSeriesMatchDto>>();
}
public async Task<SeriesDetailPlusApiDto> 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<SeriesDetailPlusApiDto>();
}
public async Task<ExternalSeriesDetailDto> 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<ExternalSeriesDetailDto>();
}
/// <summary> /// <summary>
/// Send a GET request to K+ /// Send a GET request to K+
/// </summary> /// </summary>

View file

@ -74,12 +74,14 @@ export class ManageMatchedMetadataComponent implements OnInit {
} }
this.messageHub.messages$.subscribe(message => { this.messageHub.messages$.subscribe(message => {
if (message.event !== EVENTS.ScanSeries) return; if (message.event == EVENTS.ScanSeries) {
const evt = message.payload as ScanSeriesEvent;
const evt = message.payload as ScanSeriesEvent; if (this.data.filter(d => d.series.id === evt.seriesId).length > 0) {
if (this.data.filter(d => d.series.id === evt.seriesId).length > 0) { this.loadData();
this.loadData(); }
} }
}); });
this.filterGroup.valueChanges.pipe( this.filterGroup.valueChanges.pipe(