KoinoboriScan: Apply API changes (#5535)

* update api

* remove comment
This commit is contained in:
bapeey 2024-10-15 02:02:55 -05:00 committed by Draff
parent 8914fc42dd
commit 28d71dd743
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
3 changed files with 70 additions and 54 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Koinobori Scan'
extClass = '.KoinoboriScan'
extVersionCode = 37
extVersionCode = 38
isNsfw = true
}

View File

@ -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<List<SeriesDto>>(response.body.string())
.map { it.toSManga(apiBaseUrl) }
val result = json.decodeFromString<TopSeriesDto>(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<List<SeriesDto>>(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<List<SeriesDto>>(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<SeriesDto>(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<SeriesDto>(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<SChapter> {
val result = json.decodeFromString<ChaptersPayloadDto>(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<ChaptersPayloadDto>(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<Page> {
val result = json.decodeFromString<PagesPayloadDto>(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()
}
}

View File

@ -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<SeriesDto>,
val weekRes: List<SeriesDto>,
val dayRes: List<SeriesDto>,
)
@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<SeriesTagsDto>? = 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<SeasonDto>,
@SerialName("series_slug") val seriesSlug: String,
@SerialName("Season") val seasons: List<SeasonDto>,
)
@Serializable
class SeasonDto(
val chapters: List<ChapterDto>,
@SerialName("Chapter") val chapters: List<ChapterDto>,
)
@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<String>,
@SerialName("chapter_title") val title: String?,
@SerialName("created_at") val date: String,
)