From 286ccd2f53b97b8dd2c27cf19bfc09c43f1b77b0 Mon Sep 17 00:00:00 2001 From: hatozuki-programmer Date: Sun, 3 Aug 2025 10:12:47 -0400 Subject: [PATCH] GigaViewer: Add paginated chapter list parse support (#9911) * Support for paginated readable products API * Simplify date handling * Chapter status labels * Fix type * Handle null display_open_at value * Additional chapter status label * Mark GigaViewer paginated sources * Implement requested changes from feedback * Remove unused fields * Use tryParse for date handling * Remove label constants * Remove extra whitespace --- lib-multisrc/gigaviewer/build.gradle.kts | 2 +- .../multisrc/gigaviewer/GigaViewer.kt | 76 +++++++++++++++---- .../multisrc/gigaviewer/GigaViewerDto.kt | 36 +++++++++ .../extension/ja/comicdays/ComicDays.kt | 1 + .../extension/ja/comicgardo/ComicGardo.kt | 1 + .../extension/ja/comiplex/Comiplex.kt | 1 + .../extension/ja/kuragebunch/KurageBunch.kt | 1 + .../tachiyomi/extension/ja/magcomi/MagComi.kt | 1 + .../ja/shonenjumpplus/ShonenJumpPlus.kt | 1 + .../ja/sundaywebevery/SundayWebEvery.kt | 1 + .../ja/tonarinoyoungjump/TonariNoYoungJump.kt | 1 + 11 files changed, 107 insertions(+), 15 deletions(-) diff --git a/lib-multisrc/gigaviewer/build.gradle.kts b/lib-multisrc/gigaviewer/build.gradle.kts index ede652be5..f0ad544d5 100644 --- a/lib-multisrc/gigaviewer/build.gradle.kts +++ b/lib-multisrc/gigaviewer/build.gradle.kts @@ -2,4 +2,4 @@ plugins { id("lib-multisrc") } -baseVersionCode = 6 +baseVersionCode = 7 diff --git a/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt b/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt index 2ec54da00..8f266576d 100644 --- a/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt +++ b/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt @@ -14,6 +14,8 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import keiyoushi.utils.parseAs +import keiyoushi.utils.tryParse import kotlinx.serialization.SerializationException import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json @@ -34,7 +36,6 @@ import rx.Observable import uy.kohesive.injekt.injectLazy import java.io.ByteArrayOutputStream import java.io.InputStream -import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale import kotlin.math.floor @@ -44,6 +45,7 @@ abstract class GigaViewer( override val baseUrl: String, override val lang: String, private val cdnUrl: String = "", + private val isPaginated: Boolean = false, ) : ParsedHttpSource() { override val supportsLatest = true @@ -134,7 +136,7 @@ abstract class GigaViewer( .attr("data-src") } - override fun chapterListParse(response: Response): List { + protected fun chapterListParseSinglePage(response: Response): List { val document = response.asJsoup() val aggregateId = document.selectFirst("script.js-valve")!!.attr("data-giga_series") @@ -180,6 +182,61 @@ abstract class GigaViewer( return chapters } + protected fun paginatedChaptersRequest(referer: String, aggregateId: String, offset: Int): Response { + val headers = headers.newBuilder() + .set("Referer", referer) + .build() + + val apiUrl = baseUrl.toHttpUrl().newBuilder() + .addPathSegment("api") + .addPathSegment("viewer") + .addPathSegment("pagination_readable_products") + .addQueryParameter("type", "episode") + .addQueryParameter("aggregate_id", aggregateId) + .addQueryParameter("sort_order", "desc") + .addQueryParameter("offset", offset.toString()) + .build() + .toString() + + val request = GET(apiUrl, headers) + return client.newCall(request).execute() + } + + protected fun chapterListParsePaginated(response: Response): List { + val document = response.asJsoup() + val referer = response.request.url.toString() + val aggregateId = document.selectFirst("script.js-valve")!!.attr("data-giga_series") + + val chapters = mutableListOf() + + var offset = 0 + + // repeat until the offset is too large to return any chapters, resulting in an empty list + while (true) { + // make request + val result = paginatedChaptersRequest(referer, aggregateId, offset) + val resultData = result.parseAs>() + if (resultData.isEmpty()) { + break + } + resultData.mapTo(chapters) { element -> + element.toSChapter(chapterListMode, publisher) + } + // increase offset + offset += resultData.size + } + + return chapters + } + + override fun chapterListParse(response: Response): List { + return if (isPaginated) { + chapterListParsePaginated(response) + } else { + chapterListParseSinglePage(response) + } + } + override fun chapterListSelector() = "li.episode" protected open val chapterListMode = CHAPTER_LIST_PAID @@ -195,9 +252,7 @@ abstract class GigaViewer( } else if (chapterListMode == CHAPTER_LIST_LOCKED && element.hasClass("private")) { name = LOCK + name } - date_upload = info.selectFirst("span.series-episode-list-date") - ?.text().orEmpty() - .toDate() + date_upload = DATE_PARSER_SIMPLE.tryParse(info.selectFirst("span.series-episode-list-date")?.text().orEmpty()) scanlator = publisher setUrlWithoutDomain(if (info.tagName() == "a") info.attr("href") else mangaUrl) } @@ -314,14 +369,7 @@ abstract class GigaViewer( } } - private fun String.toDate(): Long { - return runCatching { DATE_PARSER.parse(this)?.time } - .getOrNull() ?: 0L - } - companion object { - private val DATE_PARSER by lazy { SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH) } - private const val DIVIDE_NUM = 4 private const val MULTIPLE = 8 private val jpegMediaType = "image/jpeg".toMediaType() @@ -329,7 +377,7 @@ abstract class GigaViewer( const val CHAPTER_LIST_PAID = 0 const val CHAPTER_LIST_LOCKED = 1 - private const val YEN_BANKNOTE = "💴 " - private const val LOCK = "🔒 " + const val YEN_BANKNOTE = "💴 " + const val LOCK = "🔒 " } } diff --git a/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerDto.kt b/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerDto.kt index 3a210597f..6b3481941 100644 --- a/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerDto.kt +++ b/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerDto.kt @@ -1,6 +1,14 @@ package eu.kanade.tachiyomi.multisrc.gigaviewer +import eu.kanade.tachiyomi.multisrc.gigaviewer.GigaViewer.Companion.CHAPTER_LIST_LOCKED +import eu.kanade.tachiyomi.multisrc.gigaviewer.GigaViewer.Companion.CHAPTER_LIST_PAID +import eu.kanade.tachiyomi.multisrc.gigaviewer.GigaViewer.Companion.LOCK +import eu.kanade.tachiyomi.multisrc.gigaviewer.GigaViewer.Companion.YEN_BANKNOTE +import eu.kanade.tachiyomi.source.model.SChapter +import keiyoushi.utils.tryParse import kotlinx.serialization.Serializable +import java.text.SimpleDateFormat +import java.util.Locale @Serializable data class GigaViewerEpisodeDto( @@ -24,3 +32,31 @@ data class GigaViewerPage( val type: String = "", val width: Int = 0, ) + +@Serializable +class GigaViewerPaginationReadableProduct( + private val display_open_at: String?, + private val readable_product_id: String = "", + private val status: GigaViewerPaginationReadableProductStatus?, + private val title: String = "", +) { + fun toSChapter(chapterListMode: Int, publisher: String) = SChapter.create().apply { + name = title + if (chapterListMode == CHAPTER_LIST_PAID && status?.label != "is_free") { + name = YEN_BANKNOTE + name + } else if (chapterListMode == CHAPTER_LIST_LOCKED && status?.label == "unpublished") { + name = LOCK + name + } + date_upload = DATE_PARSER_COMPLEX.tryParse(display_open_at) + scanlator = publisher + url = "/episode/$readable_product_id" + } +} + +@Serializable +class GigaViewerPaginationReadableProductStatus( + val label: String?, // is_free, is_rentable, is_purchasable, unpublished +) + +val DATE_PARSER_SIMPLE = SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH) +val DATE_PARSER_COMPLEX = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH) diff --git a/src/ja/comicdays/src/eu/kanade/tachiyomi/extension/ja/comicdays/ComicDays.kt b/src/ja/comicdays/src/eu/kanade/tachiyomi/extension/ja/comicdays/ComicDays.kt index c67d5adae..ff0fd5858 100644 --- a/src/ja/comicdays/src/eu/kanade/tachiyomi/extension/ja/comicdays/ComicDays.kt +++ b/src/ja/comicdays/src/eu/kanade/tachiyomi/extension/ja/comicdays/ComicDays.kt @@ -10,6 +10,7 @@ class ComicDays : GigaViewer( "https://comic-days.com", "ja", "https://cdn-img.comic-days.com/public/page", + isPaginated = true, ) { override val client: OkHttpClient = super.client.newBuilder() diff --git a/src/ja/comicgardo/src/eu/kanade/tachiyomi/extension/ja/comicgardo/ComicGardo.kt b/src/ja/comicgardo/src/eu/kanade/tachiyomi/extension/ja/comicgardo/ComicGardo.kt index c1819ffcf..2412bb954 100644 --- a/src/ja/comicgardo/src/eu/kanade/tachiyomi/extension/ja/comicgardo/ComicGardo.kt +++ b/src/ja/comicgardo/src/eu/kanade/tachiyomi/extension/ja/comicgardo/ComicGardo.kt @@ -10,6 +10,7 @@ class ComicGardo : GigaViewer( "https://comic-gardo.com", "ja", "https://cdn-img.comic-gardo.com/public/page", + isPaginated = true, ) { override val supportsLatest: Boolean = false diff --git a/src/ja/comiplex/src/eu/kanade/tachiyomi/extension/ja/comiplex/Comiplex.kt b/src/ja/comiplex/src/eu/kanade/tachiyomi/extension/ja/comiplex/Comiplex.kt index c19846f9d..15230be45 100644 --- a/src/ja/comiplex/src/eu/kanade/tachiyomi/extension/ja/comiplex/Comiplex.kt +++ b/src/ja/comiplex/src/eu/kanade/tachiyomi/extension/ja/comiplex/Comiplex.kt @@ -12,6 +12,7 @@ class Comiplex : GigaViewer( "https://viewer.heros-web.com", "ja", "https://cdn-img.viewer.heros-web.com/public/page", + isPaginated = true, ) { override val supportsLatest: Boolean = false diff --git a/src/ja/kuragebunch/src/eu/kanade/tachiyomi/extension/ja/kuragebunch/KurageBunch.kt b/src/ja/kuragebunch/src/eu/kanade/tachiyomi/extension/ja/kuragebunch/KurageBunch.kt index 6e8a56ac2..bfe36791a 100644 --- a/src/ja/kuragebunch/src/eu/kanade/tachiyomi/extension/ja/kuragebunch/KurageBunch.kt +++ b/src/ja/kuragebunch/src/eu/kanade/tachiyomi/extension/ja/kuragebunch/KurageBunch.kt @@ -12,6 +12,7 @@ class KurageBunch : GigaViewer( "https://kuragebunch.com", "ja", "https://cdn-img.kuragebunch.com", + isPaginated = true, ) { override val supportsLatest: Boolean = false diff --git a/src/ja/magcomi/src/eu/kanade/tachiyomi/extension/ja/magcomi/MagComi.kt b/src/ja/magcomi/src/eu/kanade/tachiyomi/extension/ja/magcomi/MagComi.kt index e941ea5bd..b77c88287 100644 --- a/src/ja/magcomi/src/eu/kanade/tachiyomi/extension/ja/magcomi/MagComi.kt +++ b/src/ja/magcomi/src/eu/kanade/tachiyomi/extension/ja/magcomi/MagComi.kt @@ -10,6 +10,7 @@ class MagComi : GigaViewer( "https://magcomi.com", "ja", "https://cdn-img.magcomi.com/public/page", + isPaginated = true, ) { override val supportsLatest: Boolean = false diff --git a/src/ja/shonenjumpplus/src/eu/kanade/tachiyomi/extension/ja/shonenjumpplus/ShonenJumpPlus.kt b/src/ja/shonenjumpplus/src/eu/kanade/tachiyomi/extension/ja/shonenjumpplus/ShonenJumpPlus.kt index c34222abb..4a322a727 100644 --- a/src/ja/shonenjumpplus/src/eu/kanade/tachiyomi/extension/ja/shonenjumpplus/ShonenJumpPlus.kt +++ b/src/ja/shonenjumpplus/src/eu/kanade/tachiyomi/extension/ja/shonenjumpplus/ShonenJumpPlus.kt @@ -8,6 +8,7 @@ class ShonenJumpPlus : GigaViewer( "https://shonenjumpplus.com", "ja", "https://cdn-ak-img.shonenjumpplus.com", + isPaginated = true, ) { override val client: OkHttpClient = super.client.newBuilder() diff --git a/src/ja/sundaywebevery/src/eu/kanade/tachiyomi/extension/ja/sundaywebevery/SundayWebEvery.kt b/src/ja/sundaywebevery/src/eu/kanade/tachiyomi/extension/ja/sundaywebevery/SundayWebEvery.kt index a8d5fea94..e5123e0fc 100644 --- a/src/ja/sundaywebevery/src/eu/kanade/tachiyomi/extension/ja/sundaywebevery/SundayWebEvery.kt +++ b/src/ja/sundaywebevery/src/eu/kanade/tachiyomi/extension/ja/sundaywebevery/SundayWebEvery.kt @@ -10,6 +10,7 @@ class SundayWebEvery : GigaViewer( "https://www.sunday-webry.com", "ja", "https://cdn-img.www.sunday-webry.com/public/page", + isPaginated = true, ) { override val client: OkHttpClient = super.client.newBuilder() diff --git a/src/ja/tonarinoyoungjump/src/eu/kanade/tachiyomi/extension/ja/tonarinoyoungjump/TonariNoYoungJump.kt b/src/ja/tonarinoyoungjump/src/eu/kanade/tachiyomi/extension/ja/tonarinoyoungjump/TonariNoYoungJump.kt index f482e30b2..29c61a0e5 100644 --- a/src/ja/tonarinoyoungjump/src/eu/kanade/tachiyomi/extension/ja/tonarinoyoungjump/TonariNoYoungJump.kt +++ b/src/ja/tonarinoyoungjump/src/eu/kanade/tachiyomi/extension/ja/tonarinoyoungjump/TonariNoYoungJump.kt @@ -10,6 +10,7 @@ class TonariNoYoungJump : GigaViewer( "https://tonarinoyj.jp", "ja", "https://cdn-img.tonarinoyj.jp/public/page", + isPaginated = true, ) { override val supportsLatest: Boolean = false