From 28d71dd7431dbfc81f5498c7645317753ba434e6 Mon Sep 17 00:00:00 2001 From: bapeey <90949336+bapeey@users.noreply.github.com> Date: Tue, 15 Oct 2024 02:02:55 -0500 Subject: [PATCH] KoinoboriScan: Apply API changes (#5535) * update api * remove comment --- src/es/koinoboriscan/build.gradle | 2 +- .../es/koinoboriscan/KoinoboriScan.kt | 72 ++++++++++++------- .../es/koinoboriscan/KoinoboriScanDto.kt | 50 ++++++------- 3 files changed, 70 insertions(+), 54 deletions(-) diff --git a/src/es/koinoboriscan/build.gradle b/src/es/koinoboriscan/build.gradle index 1dffceaed..02d085ac1 100644 --- a/src/es/koinoboriscan/build.gradle +++ b/src/es/koinoboriscan/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Koinobori Scan' extClass = '.KoinoboriScan' - extVersionCode = 37 + extVersionCode = 38 isNsfw = true } diff --git a/src/es/koinoboriscan/src/eu/kanade/tachiyomi/extension/es/koinoboriscan/KoinoboriScan.kt b/src/es/koinoboriscan/src/eu/kanade/tachiyomi/extension/es/koinoboriscan/KoinoboriScan.kt index 850d34830..43b4d01ea 100644 --- a/src/es/koinoboriscan/src/eu/kanade/tachiyomi/extension/es/koinoboriscan/KoinoboriScan.kt +++ b/src/es/koinoboriscan/src/eu/kanade/tachiyomi/extension/es/koinoboriscan/KoinoboriScan.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import okhttp3.Request @@ -18,12 +19,12 @@ import rx.Observable import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale +import java.util.TimeZone import kotlin.math.min class KoinoboriScan : HttpSource() { - // Site change theme from Madara to custom - override val versionId = 2 + override val versionId = 3 override val name = "Koinobori Scan" @@ -37,7 +38,9 @@ class KoinoboriScan : HttpSource() { private val json: Json by injectLazy() - private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale("es")) + private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale("es")).apply { + timeZone = TimeZone.getTimeZone("UTC") + } override val client = network.cloudflareClient.newBuilder() .rateLimit(2, 1) @@ -47,21 +50,23 @@ class KoinoboriScan : HttpSource() { .set("Referer", "$baseUrl/") override fun popularMangaRequest(page: Int): Request = - GET("$apiBaseUrl/topSeries", headers) + GET("$apiBaseUrl/api/topSeries", headers) override fun popularMangaParse(response: Response): MangasPage { - val mangas = json.decodeFromString>(response.body.string()) - .map { it.toSManga(apiBaseUrl) } + val result = json.decodeFromString(response.body.string()) + val mangas = (result.mensualRes + result.weekRes + result.dayRes) + .distinctBy { it.slug } + .map { it.toSManga() } return MangasPage(mangas, false) } override fun latestUpdatesRequest(page: Int): Request = - GET("$apiBaseUrl/lastupdates", headers) + GET("$apiBaseUrl/api/lastupdates", headers) override fun latestUpdatesParse(response: Response): MangasPage { val mangas = json.decodeFromString>(response.body.string()) - .map { it.toSManga(apiBaseUrl) } + .map { it.toSManga() } return MangasPage(mangas, false) } @@ -83,7 +88,7 @@ class KoinoboriScan : HttpSource() { } override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - GET("$apiBaseUrl/all", headers) + GET("$apiBaseUrl/api/allComics", headers) private fun searchMangaParse(response: Response, page: Int, query: String): MangasPage { val result = json.decodeFromString>(response.body.string()) @@ -99,7 +104,7 @@ class KoinoboriScan : HttpSource() { val mangas = filteredSeries.subList( (page - 1) * SERIES_PER_PAGE, min(page * SERIES_PER_PAGE, filteredSeries.size), - ).map { it.toSManga(apiBaseUrl) } + ).map { it.toSManga() } val hasNextPage = filteredSeries.size > page * SERIES_PER_PAGE @@ -110,27 +115,38 @@ class KoinoboriScan : HttpSource() { Filter.Header("Presione 'Filtrar' para mostrar toda la biblioteca"), ) - override fun getMangaUrl(manga: SManga) = "$baseUrl/?tipo=serie&identificador=${manga.url}" + override fun getMangaUrl(manga: SManga) = "$baseUrl/comic/${manga.url}" override fun mangaDetailsRequest(manga: SManga): Request = - GET("$apiBaseUrl/api/project/${manga.url}", headers) + GET("$baseUrl/comic/${manga.url}", headers) override fun mangaDetailsParse(response: Response): SManga { - return json.decodeFromString(response.body.string()).toSMangaDetails(apiBaseUrl) + val document = response.asJsoup() + val scriptsData = document.select("script").joinToString("\n") { it.data() } + val jsonData = MANGA_DETAILS_REGEX.find(scriptsData)?.groupValues?.get(1) + ?: throw Exception("No se pudo obtener la información de la serie") + return json.decodeFromString(jsonData.unescape()).toSMangaDetails() } - override fun getChapterUrl(chapter: SChapter) = "$baseUrl/?tipo=capitulo&identificador=${chapter.url}" + override fun getChapterUrl(chapter: SChapter) = "$baseUrl/comic/${chapter.url}" - override fun chapterListRequest(manga: SManga): Request = - GET("$apiBaseUrl/api/project/${manga.url}", headers) + override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga) override fun chapterListParse(response: Response): List { - val result = json.decodeFromString(response.body.string()) + val document = response.asJsoup() + val scriptsData = document.select("script").joinToString("\n") { it.data() } + val jsonData = MANGA_DETAILS_REGEX.find(scriptsData)?.groupValues?.get(1) + ?: throw Exception("No se pudo obtener la información de la serie") + val result = json.decodeFromString(jsonData.unescape()) + val seriesSlug = result.seriesSlug return result.seasons.flatMap { season -> season.chapters.map { chapter -> SChapter.create().apply { - url = chapter.id.toString() - name = "Capítulo ${chapter.name}: ${chapter.title}" + url = "$seriesSlug/${chapter.slug}" + name = chapter.name + if (!chapter.title.isNullOrBlank()) { + name += ": ${chapter.title}" + } date_upload = try { dateFormat.parse(chapter.date)?.time ?: 0 } catch (e: Exception) { @@ -138,25 +154,29 @@ class KoinoboriScan : HttpSource() { } } } - }.reversed() + } } override fun pageListRequest(chapter: SChapter): Request = - GET("$apiBaseUrl/api/chapter/${chapter.url}", headers) + GET("$baseUrl/comic/${chapter.url}", headers) override fun pageListParse(response: Response): List { - val result = json.decodeFromString(response.body.string()) - val key = result.key - val chapterId = result.chapter.id - return result.chapter.images.mapIndexed { i, img -> - Page(i, imageUrl = "$apiBaseUrl/api/images/chapter/$chapterId/$img?token=$key") + val document = response.asJsoup() + return document.select("body > div.w-full > div > img").mapIndexed { i, img -> + Page(i, imageUrl = img.attr("abs:src")) } } override fun searchMangaParse(response: Response) = throw UnsupportedOperationException() override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() + private fun String.unescape(): String { + return UNESCAPE_REGEX.replace(this, "$1") + } + companion object { const val SERIES_PER_PAGE = 24 + val UNESCAPE_REGEX = """\\(.)""".toRegex() + val MANGA_DETAILS_REGEX = """self\.__next_f\.push\(.*info\\":(\{.*Chapter.*\}).*\\"userIsFollowed""".toRegex() } } diff --git a/src/es/koinoboriscan/src/eu/kanade/tachiyomi/extension/es/koinoboriscan/KoinoboriScanDto.kt b/src/es/koinoboriscan/src/eu/kanade/tachiyomi/extension/es/koinoboriscan/KoinoboriScanDto.kt index 739fc874b..5ee635183 100644 --- a/src/es/koinoboriscan/src/eu/kanade/tachiyomi/extension/es/koinoboriscan/KoinoboriScanDto.kt +++ b/src/es/koinoboriscan/src/eu/kanade/tachiyomi/extension/es/koinoboriscan/KoinoboriScanDto.kt @@ -4,33 +4,40 @@ import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +@Serializable +class TopSeriesDto( + val mensualRes: List, + val weekRes: List, + val dayRes: List, +) + @Serializable class SeriesDto( - @SerialName("ID") private val id: Int, + @SerialName("series_slug") val slug: String, val title: String, - private val description: String, - private val thumbnail: String, + private val description: String?, + private val thumbnail: String?, private val status: String?, private val author: String?, private val tags: List? = emptyList(), ) { - fun toSManga(cdnUrl: String) = SManga.create().apply { + fun toSManga() = SManga.create().apply { title = this@SeriesDto.title - thumbnail_url = cdnUrl + thumbnail - url = id.toString() + thumbnail_url = thumbnail + url = slug } - fun toSMangaDetails(cdnUrl: String) = SManga.create().apply { + fun toSMangaDetails() = SManga.create().apply { title = this@SeriesDto.title.trim() author = this@SeriesDto.author?.trim() status = parseStatus(this@SeriesDto.status) - thumbnail_url = cdnUrl + thumbnail + thumbnail_url = thumbnail genre = tags?.joinToString { it.name.trim() } - description = this@SeriesDto.description.trim() + description = this@SeriesDto.description?.trim() } private fun parseStatus(status: String?) = when (status?.trim()) { - "En emisión", "En curso" -> SManga.ONGOING + "Ongoing" -> SManga.ONGOING "Completado" -> SManga.COMPLETED "Abandonado" -> SManga.CANCELLED "Pausado" -> SManga.ON_HIATUS @@ -45,30 +52,19 @@ class SeriesTagsDto( @Serializable class ChaptersPayloadDto( - val seasons: List, + @SerialName("series_slug") val seriesSlug: String, + @SerialName("Season") val seasons: List, ) @Serializable class SeasonDto( - val chapters: List, + @SerialName("Chapter") val chapters: List, ) @Serializable class ChapterDto( - @SerialName("ID") val id: Int, + @SerialName("chapter_slug") val slug: String, @SerialName("chapter_name") val name: String, - @SerialName("chapter_title") val title: String, - @SerialName("CreatedAt") val date: String, -) - -@Serializable -class PagesPayloadDto( - val chapter: ChapterImagesDto, - val key: String, -) - -@Serializable -class ChapterImagesDto( - @SerialName("ID") val id: Int, - val images: List, + @SerialName("chapter_title") val title: String?, + @SerialName("created_at") val date: String, )