diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt index 89b8f3df..ed507e0c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt @@ -79,6 +79,7 @@ object MangaAPI { patch("{mangaId}/chapter/{chapterIndex}/meta", MangaController.chapterMeta) get("{mangaId}/chapter/{chapterIndex}/page/{index}", MangaController.pageRetrieve) + get("{mangaId}/chapter/{chapterIndex}/page/{index}/image", MangaController.pageImage) } path("chapter") { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt index f41a7b59..4db17a45 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt @@ -503,6 +503,46 @@ object MangaController { }, ) + /** Get page image binary with Content-Length for progress tracking */ + val pageImage = + handler( + pathParam("mangaId"), + pathParam("chapterIndex"), + pathParam("index"), + queryParam("format"), + documentWith = { + withOperation { + summary("Get a chapter page image") + description("Returns the raw image binary with Content-Length header for progress tracking.") + } + }, + behaviorOf = { ctx, mangaId, chapterIndex, index, format -> + ctx.getAttribute(Attribute.TachideskUser).requireUser() + + ctx.future { + future { + Page.getPageImageServe( + mangaId = mangaId, + chapterIndex = chapterIndex, + index = index, + format = format, + ) + }.thenApply { (stream, mimeType) -> + val bytes = stream.readBytes() + ctx.header("content-type", mimeType) + ctx.header("content-length", bytes.size.toString()) + val httpCacheSeconds = 1.days.inWholeSeconds + ctx.header("cache-control", "max-age=$httpCacheSeconds") + ctx.result(bytes) + } + } + }, + withResults = { + image(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) + }, + ) + val downloadChapter = handler( pathParam("chapterId"),