(Kavita+) External Series Detail (#2309)

This commit is contained in:
Joe Milazzo 2023-10-11 19:31:40 -05:00 committed by GitHub
parent bd62e00ec5
commit 6067c9233c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 2354 additions and 726 deletions

View file

@ -22,4 +22,5 @@ public static class EasyCacheProfiles
public const string KavitaPlusReviews = "kavita+reviews";
public const string KavitaPlusRecommendations = "kavita+recommendations";
public const string KavitaPlusRatings = "kavita+ratings";
public const string KavitaPlusExternalSeries = "kavita+externalSeries";
}

View file

@ -1022,7 +1022,8 @@ public class OpdsController : BaseApiController
/// <param name="pageNumber"></param>
/// <returns></returns>
[HttpGet("{apiKey}/image")]
public async Task<ActionResult> GetPageStreamedImage(string apiKey, [FromQuery] int libraryId, [FromQuery] int seriesId, [FromQuery] int volumeId,[FromQuery] int chapterId, [FromQuery] int pageNumber)
public async Task<ActionResult> GetPageStreamedImage(string apiKey, [FromQuery] int libraryId, [FromQuery] int seriesId,
[FromQuery] int volumeId,[FromQuery] int chapterId, [FromQuery] int pageNumber)
{
var userId = await GetUser(apiKey);
if (pageNumber < 0) return BadRequest(await _localizationService.Translate(userId, "greater-0", "Page"));

View file

@ -10,6 +10,7 @@ using API.DTOs.Dashboard;
using API.DTOs.Filtering;
using API.DTOs.Filtering.v2;
using API.DTOs.Metadata;
using API.DTOs.Recommendation;
using API.DTOs.SeriesDetail;
using API.Entities;
using API.Entities.Enums;
@ -35,14 +36,17 @@ public class SeriesController : BaseApiController
private readonly ISeriesService _seriesService;
private readonly ILicenseService _licenseService;
private readonly ILocalizationService _localizationService;
private readonly IExternalMetadataService _externalMetadataService;
private readonly IEasyCachingProvider _ratingCacheProvider;
private readonly IEasyCachingProvider _reviewCacheProvider;
private readonly IEasyCachingProvider _recommendationCacheProvider;
private readonly IEasyCachingProvider _externalSeriesCacheProvider;
public const string CacheKey = "recommendation_";
public SeriesController(ILogger<SeriesController> logger, ITaskScheduler taskScheduler, IUnitOfWork unitOfWork,
ISeriesService seriesService, ILicenseService licenseService,
IEasyCachingProviderFactory cachingProviderFactory, ILocalizationService localizationService)
IEasyCachingProviderFactory cachingProviderFactory, ILocalizationService localizationService, IExternalMetadataService externalMetadataService)
{
_logger = logger;
_taskScheduler = taskScheduler;
@ -50,10 +54,12 @@ public class SeriesController : BaseApiController
_seriesService = seriesService;
_licenseService = licenseService;
_localizationService = localizationService;
_externalMetadataService = externalMetadataService;
_ratingCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRatings);
_reviewCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusReviews);
_recommendationCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRecommendations);
_externalSeriesCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusExternalSeries);
}
/// <summary>
@ -576,6 +582,32 @@ public class SeriesController : BaseApiController
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-relationship"));
}
[Authorize(Policy = "RequireAdminRole")]
[HttpGet("external-series-detail")]
public async Task<ActionResult<ExternalSeriesDto>> GetExternalSeriesInfo(int? aniListId, long? malId)
{
if (!await _licenseService.HasActiveLicense())
{
return BadRequest();
}
var cacheKey = $"{CacheKey}-{aniListId ?? 0}-{malId ?? 0}";
var results = await _externalSeriesCacheProvider.GetAsync<ExternalSeriesDto>(cacheKey);
if (results.HasValue)
{
return Ok(results.Value);
}
try
{
var ret = await _externalMetadataService.GetExternalSeriesDetail(aniListId, malId);
await _externalSeriesCacheProvider.SetAsync(cacheKey, ret, TimeSpan.FromMinutes(15));
return Ok(ret);
}
catch (Exception ex)
{
return BadRequest("Unable to load External Series details");
}
}
}

View file

@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace API.DTOs.Recommendation;
public class ExternalSeriesDetailDto
{
public string Name { get; set; }
public int? AniListId { get; set; }
public long? MALId { get; set; }
public IList<string> Synonyms { get; set; }
public PlusMediaFormat PlusMediaFormat { get; set; }
public string? SiteUrl { get; set; }
public string? CoverUrl { get; set; }
public IList<string> Genres { get; set; }
public IList<SeriesStaffDto> Staff { get; set; }
public IList<MetadataTagDto> Tags { get; set; }
public string? Summary { get; set; }
public int? VolumeCount { get; set; }
public int? ChapterCount { get; set; }
}

View file

@ -7,4 +7,6 @@ public class ExternalSeriesDto
public required string CoverUrl { get; set; }
public required string Url { get; set; }
public string? Summary { get; set; }
public int? AniListId { get; set; }
public long? MalId { get; set; }
}

View file

@ -0,0 +1,11 @@
namespace API.DTOs.Recommendation;
public class MetadataTagDto
{
public string Name { get; set; }
public string Description { get; private set; }
public int? Rank { get; private set; }
public bool IsGeneralSpoiler { get; private set; }
public bool IsMediaSpoiler { get; private set; }
public bool IsAdult { get; private set; }
}

View file

@ -0,0 +1,15 @@
using System.ComponentModel;
namespace API.DTOs.Recommendation;
public enum PlusMediaFormat
{
[Description("Manga")]
Manga = 1,
[Description("Comic")]
Comic = 2,
[Description("LightNovel")]
LightNovel = 3,
[Description("Book")]
Book = 4
}

View file

@ -0,0 +1,11 @@
namespace API.DTOs.Recommendation;
public class SeriesStaffDto
{
public required string Name { get; set; }
public required string Url { get; set; }
public required string Role { get; set; }
public string? ImageUrl { get; set; }
public string? Gender { get; set; }
public string? Description { get; set; }
}

View file

@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace API.Extensions;
@ -73,6 +72,7 @@ public static class ApplicationServiceExtensions
services.AddScoped<ILicenseService, LicenseService>();
services.AddScoped<IReviewService, ReviewService>();
services.AddScoped<IRatingService, RatingService>();
services.AddScoped<IExternalMetadataService, ExternalMetadataService>();
services.AddSqLite();
services.AddSignalR(opt => opt.EnableDetailedErrors = true);
@ -89,6 +89,7 @@ public static class ApplicationServiceExtensions
options.UseInMemory(EasyCacheProfiles.KavitaPlusReviews);
options.UseInMemory(EasyCacheProfiles.KavitaPlusRecommendations);
options.UseInMemory(EasyCacheProfiles.KavitaPlusRatings);
options.UseInMemory(EasyCacheProfiles.KavitaPlusExternalSeries);
});
services.AddMemoryCache(options =>

View file

@ -550,8 +550,12 @@ public class BookService : IBookService
}
}
// Check if there is a SortTitle
// If this is a single book and not a collection, set publication status to Completed
if (string.IsNullOrEmpty(info.Volume) && Parser.ParseVolume(filePath).Equals(Parser.DefaultVolume))
{
info.Number = "1";
info.Count = 1;
}
// Include regular Writer as well, for cases where there is no special tag
info.Writer = string.Join(",",

View file

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using API.Data;
using API.DTOs.Recommendation;
using API.Entities.Enums;
using API.Helpers.Builders;
using Flurl.Http;
using Kavita.Common;
using Kavita.Common.EnvironmentInfo;
using Kavita.Common.Helpers;
using Microsoft.Extensions.Logging;
namespace API.Services.Plus;
public interface IExternalMetadataService
{
Task<ExternalSeriesDetailDto> GetExternalSeriesDetail(int? aniListId, long? malId);
}
public class ExternalMetadataService : IExternalMetadataService
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<ExternalMetadataService> _logger;
public ExternalMetadataService(IUnitOfWork unitOfWork, ILogger<ExternalMetadataService> logger)
{
_unitOfWork = unitOfWork;
_logger = logger;
FlurlHttp.ConfigureClient(Configuration.KavitaPlusApiUrl, cli =>
cli.Settings.HttpClientFactory = new UntrustedCertClientFactory());
}
public async Task<ExternalSeriesDetailDto?> GetExternalSeriesDetail(int? aniListId, long? malId)
{
if (!aniListId.HasValue && !malId.HasValue)
{
throw new KavitaException("Unable to find valid information from url for External Load");
}
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
return await GetSeriesDetail(license, aniListId, malId);
}
private async Task<ExternalSeriesDetailDto?> GetSeriesDetail(string license, int? anilistId, long? malId)
{
try
{
return await (Configuration.KavitaPlusApiUrl + "/api/metadata/series/detail")
.WithHeader("Accept", "application/json")
.WithHeader("User-Agent", "Kavita")
.WithHeader("x-license-key", license)
.WithHeader("x-installId", HashUtil.ServerToken())
.WithHeader("x-kavita-version", BuildInfo.Version)
.WithHeader("Content-Type", "application/json")
.WithTimeout(TimeSpan.FromSeconds(Configuration.DefaultTimeOutSecs))
.PostJsonAsync(new
{
AnilistId = anilistId,
MalId = malId,
})
.ReceiveJson<ExternalSeriesDetailDto>();
}
catch (Exception e)
{
_logger.LogError(e, "An error happened during the request to Kavita+ API");
}
return null;
}
}

View file

@ -10,9 +10,7 @@ using API.DTOs.Scrobbling;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Helpers;
using API.Helpers.Builders;
using API.Services.Tasks.Scanner.Parser;
using Flurl.Http;
using Kavita.Common;
using Kavita.Common.EnvironmentInfo;
@ -111,7 +109,9 @@ public class RecommendationService : IRecommendationService
Name = string.IsNullOrEmpty(rec.Name) ? rec.RecommendationNames.First() : rec.Name,
Url = rec.SiteUrl,
CoverUrl = rec.CoverUrl,
Summary = rec.Summary
Summary = rec.Summary,
AniListId = rec.AniListId,
MalId = rec.MalId
});
}