mirror of
https://github.com/Kareadita/Kavita.git
synced 2026-01-14 16:10:22 -05:00
Co-authored-by: Amelia <77553571+Fesaa@users.noreply.github.com> Co-authored-by: Weblate (bot) <hosted@weblate.org> Co-authored-by: Adam Havránek <adamhavra@seznam.cz> Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com> Co-authored-by: Dark77 <Dark77@pobox.sk> Co-authored-by: Frozehunter <frozehunter@me.com> Co-authored-by: Havokdan <havokdan@yahoo.com.br> Co-authored-by: Igor Dobrača <igor.dobraca@gmail.com> Co-authored-by: Karl B <karl.owl@proton.me> Co-authored-by: Morhain Olivier <sesram@users.noreply.hosted.weblate.org> Co-authored-by: daydreamrabbit <devrabbit90@gmail.com> Co-authored-by: karigane <169052233+karigane-cha@users.noreply.github.com> Co-authored-by: oxygen44k <iiccpp@outlook.com> Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com> Co-authored-by: 無情天 <kofzhanganguo@126.com> Co-authored-by: 안세훈 <on9686@gmail.com>
182 lines
6.9 KiB
C#
182 lines
6.9 KiB
C#
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using API.Constants;
|
|
using API.Data;
|
|
using API.DTOs.Reader;
|
|
using API.Entities.Enums;
|
|
using API.Middleware;
|
|
using API.Services;
|
|
using Kavita.Common;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using VersOne.Epub;
|
|
|
|
namespace API.Controllers;
|
|
|
|
#nullable enable
|
|
|
|
public class BookController : BaseApiController
|
|
{
|
|
private readonly IBookService _bookService;
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
private readonly ICacheService _cacheService;
|
|
private readonly ILocalizationService _localizationService;
|
|
|
|
public BookController(IBookService bookService,
|
|
IUnitOfWork unitOfWork, ICacheService cacheService,
|
|
ILocalizationService localizationService)
|
|
{
|
|
_bookService = bookService;
|
|
_unitOfWork = unitOfWork;
|
|
_cacheService = cacheService;
|
|
_localizationService = localizationService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves information for the PDF and Epub reader. This will cache the file.
|
|
/// </summary>
|
|
/// <remarks>This only applies to Epub or PDF files</remarks>
|
|
/// <param name="chapterId"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("{chapterId}/book-info")]
|
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = ["chapterId"])]
|
|
public async Task<ActionResult<BookInfoDto>> GetBookInfo(int chapterId)
|
|
{
|
|
var dto = await _unitOfWork.ChapterRepository.GetChapterInfoDtoAsync(chapterId);
|
|
if (dto == null) return BadRequest(await _localizationService.Translate(UserId, "chapter-doesnt-exist"));
|
|
var bookTitle = string.Empty;
|
|
|
|
|
|
switch (dto.SeriesFormat)
|
|
{
|
|
case MangaFormat.Epub:
|
|
{
|
|
var mangaFile = (await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId))[0];
|
|
await _cacheService.Ensure(chapterId);
|
|
var file = _cacheService.GetCachedFile(chapterId, mangaFile.FilePath);
|
|
using var book = await EpubReader.OpenBookAsync(file, BookService.LenientBookReaderOptions);
|
|
bookTitle = book.Title;
|
|
|
|
break;
|
|
}
|
|
case MangaFormat.Pdf:
|
|
{
|
|
var mangaFile = (await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId))[0];
|
|
await _cacheService.Ensure(chapterId);
|
|
var file = _cacheService.GetCachedFile(chapterId, mangaFile.FilePath);
|
|
if (string.IsNullOrEmpty(bookTitle))
|
|
{
|
|
// Override with filename
|
|
bookTitle = Path.GetFileNameWithoutExtension(file);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MangaFormat.Image:
|
|
case MangaFormat.Archive:
|
|
case MangaFormat.Unknown:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
var info = new BookInfoDto()
|
|
{
|
|
ChapterNumber = dto.ChapterNumber,
|
|
VolumeNumber = dto.VolumeNumber,
|
|
VolumeId = dto.VolumeId,
|
|
BookTitle = bookTitle,
|
|
SeriesName = dto.SeriesName,
|
|
SeriesFormat = dto.SeriesFormat,
|
|
SeriesId = dto.SeriesId,
|
|
LibraryId = dto.LibraryId,
|
|
IsSpecial = dto.IsSpecial,
|
|
Pages = dto.Pages,
|
|
};
|
|
|
|
|
|
return Ok(info);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is an entry point to fetch resources from within an epub chapter/book.
|
|
/// </summary>
|
|
/// <param name="chapterId"></param>
|
|
/// <param name="file"></param>
|
|
/// <returns></returns>
|
|
[AllowAnonymous]
|
|
[SkipDeviceTracking]
|
|
[HttpGet("{chapterId}/book-resources")]
|
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.FiveMinute, VaryByQueryKeys = ["chapterId", "file"])]
|
|
public async Task<ActionResult> GetBookPageResources(int chapterId, [FromQuery] string file)
|
|
{
|
|
if (chapterId <= 0) return BadRequest(await _localizationService.Get("en", "chapter-doesnt-exist"));
|
|
|
|
var chapter = await _cacheService.Ensure(chapterId);
|
|
if (chapter == null) return BadRequest(await _localizationService.Get("en", "chapter-doesnt-exist"));
|
|
|
|
var cachedFilePath = Path.Join(_cacheService.GetCachePath(chapterId), Path.GetFileName(chapter.Files.ElementAt(0).FilePath));
|
|
var result = await _bookService.GetResourceAsync(cachedFilePath, file);
|
|
|
|
if (!result.IsSuccess) return BadRequest(await _localizationService.Get("en", result.ErrorMessage));
|
|
|
|
return File(result.Content, result.ContentType, $"{chapterId}-{file}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will return a list of mappings from ID -> page num. ID will be the xhtml key and page num will be the reading order
|
|
/// this is used to rewrite anchors in the book text so that we always load properly in our reader.
|
|
/// </summary>
|
|
/// <remarks>This is essentially building the table of contents</remarks>
|
|
/// <param name="chapterId"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("{chapterId}/chapters")]
|
|
public async Task<ActionResult<ICollection<BookChapterItem>>> GetBookChapters(int chapterId)
|
|
{
|
|
if (chapterId <= 0) return BadRequest(await _localizationService.Translate(UserId, "chapter-doesnt-exist"));
|
|
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
|
|
if (chapter == null) return BadRequest(await _localizationService.Translate(UserId, "chapter-doesnt-exist"));
|
|
|
|
try
|
|
{
|
|
return Ok(await _bookService.GenerateTableOfContents(chapter));
|
|
}
|
|
catch (KavitaException ex)
|
|
{
|
|
return BadRequest(ex.Message);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// This returns a single page within the epub book. All html will be rewritten to be scoped within our reader,
|
|
/// all css is scoped, etc.
|
|
/// </summary>
|
|
/// <param name="chapterId"></param>
|
|
/// <param name="page"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("{chapterId}/book-page")]
|
|
public async Task<ActionResult<string>> GetBookPage(int chapterId, [FromQuery] int page)
|
|
{
|
|
var chapter = await _cacheService.Ensure(chapterId);
|
|
if (chapter == null) return BadRequest(await _localizationService.Translate(UserId, "chapter-doesnt-exist"));
|
|
var path = _cacheService.GetCachedFile(chapter);
|
|
|
|
var baseUrl = "//" + Request.Host + Request.PathBase + "/api/";
|
|
|
|
try
|
|
{
|
|
var ptocBookmarks =
|
|
await _unitOfWork.UserTableOfContentRepository.GetPersonalToCForPage(UserId, chapterId, page);
|
|
var annotations = await _unitOfWork.UserRepository.GetAnnotationsByPage(UserId, chapter.Id, page);
|
|
|
|
return Ok(await _bookService.GetBookPage(page, chapterId, path, baseUrl, ptocBookmarks, annotations));
|
|
}
|
|
catch (KavitaException ex)
|
|
{
|
|
return BadRequest(await _localizationService.Translate(UserId, ex.Message));
|
|
}
|
|
}
|
|
}
|