Koinobori Scan: Update to custom theme (#5097)

* first attempt

* termux my beloved
This commit is contained in:
bapeey 2024-09-17 01:05:58 -05:00 committed by Draff
parent 66eb921df6
commit 5aa0cd2afa
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
3 changed files with 228 additions and 11 deletions

View File

@ -1,9 +1,7 @@
ext {
extName = 'Koinobori Scan'
extClass = '.KoinoboriScan'
themePkg = 'madara'
baseUrl = 'https://koinoboriscan.com'
overrideVersionCode = 0
extVersionCode = 37
isNsfw = true
}

View File

@ -1,17 +1,162 @@
package eu.kanade.tachiyomi.extension.es.koinoboriscan
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
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 kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
import kotlin.math.min
class KoinoboriScan : Madara(
"Koinobori Scan",
"https://koinoboriscan.com",
"es",
SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
) {
override val client = super.client.newBuilder()
class KoinoboriScan : HttpSource() {
// Site change theme from Madara to custom
override val versionId = 2
override val name = "Koinobori Scan"
override val lang = "es"
override val baseUrl = "https://visorkoi.com"
private val apiBaseUrl = "https://api.visorkoi.com"
override val supportsLatest = true
private val json: Json by injectLazy()
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale("es"))
override val client = network.cloudflareClient.newBuilder()
.rateLimit(2, 1)
.build()
override fun headersBuilder() = super.headersBuilder()
.set("Referer", "$baseUrl/")
override fun popularMangaRequest(page: Int): Request =
GET("$apiBaseUrl/topSeries", headers)
override fun popularMangaParse(response: Response): MangasPage {
val mangas = json.decodeFromString<List<SeriesDto>>(response.body.string())
.map { it.toSManga(apiBaseUrl) }
return MangasPage(mangas, false)
}
override fun latestUpdatesRequest(page: Int): Request =
GET("$apiBaseUrl/lastupdates", headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val mangas = json.decodeFromString<List<SeriesDto>>(response.body.string())
.map { it.toSManga(apiBaseUrl) }
return MangasPage(mangas, false)
}
private val seriesList = mutableListOf<SeriesDto>()
override fun fetchSearchManga(
page: Int,
query: String,
filters: FilterList,
): Observable<MangasPage> {
return if (seriesList.isEmpty()) {
client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess()
.map { searchMangaParse(it, page, query) }
} else {
Observable.just(parseSeriesList(page, query))
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
GET("$apiBaseUrl/all", headers)
private fun searchMangaParse(response: Response, page: Int, query: String): MangasPage {
val result = json.decodeFromString<List<SeriesDto>>(response.body.string())
seriesList.addAll(result)
return parseSeriesList(page, query)
}
private fun parseSeriesList(page: Int, query: String): MangasPage {
val filteredSeries = seriesList.filter {
it.title.contains(query, ignoreCase = true)
}
val mangas = filteredSeries.subList(
(page - 1) * SERIES_PER_PAGE,
min(page * SERIES_PER_PAGE, filteredSeries.size),
).map { it.toSManga(apiBaseUrl) }
val hasNextPage = filteredSeries.size > page * SERIES_PER_PAGE
return MangasPage(mangas, hasNextPage)
}
override fun getFilterList() = FilterList(
Filter.Header("Presione 'Filtrar' para mostrar toda la biblioteca"),
)
override fun getMangaUrl(manga: SManga) = "$baseUrl/?tipo=serie&identificador=${manga.url}"
override fun mangaDetailsRequest(manga: SManga): Request =
GET("$apiBaseUrl/api/project/${manga.url}", headers)
override fun mangaDetailsParse(response: Response): SManga {
return json.decodeFromString<SeriesDto>(response.body.string()).toSMangaDetails(apiBaseUrl)
}
override fun getChapterUrl(chapter: SChapter) = "$baseUrl/?tipo=capitulo&identificador=${chapter.url}"
override fun chapterListRequest(manga: SManga): Request =
GET("$apiBaseUrl/api/project/${manga.url}", headers)
override fun chapterListParse(response: Response): List<SChapter> {
val result = json.decodeFromString<ChaptersPayloadDto>(response.body.string())
return result.seasons.flatMap { season ->
season.chapters.map { chapter ->
SChapter.create().apply {
url = chapter.id.toString()
name = "Capítulo ${chapter.name}: ${chapter.title}"
date_upload = try {
dateFormat.parse(chapter.date)?.time ?: 0
} catch (e: Exception) {
0
}
}
}
}.reversed()
}
override fun pageListRequest(chapter: SChapter): Request =
GET("$apiBaseUrl/api/chapter/${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")
}
}
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
companion object {
const val SERIES_PER_PAGE = 24
}
}

View File

@ -0,0 +1,74 @@
package eu.kanade.tachiyomi.extension.es.koinoboriscan
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class SeriesDto(
@SerialName("ID") private val id: Int,
val title: 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 {
title = this@SeriesDto.title
thumbnail_url = cdnUrl + thumbnail
url = id.toString()
}
fun toSMangaDetails(cdnUrl: String) = SManga.create().apply {
title = this@SeriesDto.title.trim()
author = this@SeriesDto.author?.trim()
status = parseStatus(this@SeriesDto.status)
thumbnail_url = cdnUrl + thumbnail
genre = tags?.joinToString { it.name.trim() }
description = this@SeriesDto.description.trim()
}
private fun parseStatus(status: String?) = when (status?.trim()) {
"En emisión", "En curso" -> SManga.ONGOING
"Completado" -> SManga.COMPLETED
"Abandonado" -> SManga.CANCELLED
"Pausado" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
}
@Serializable
class SeriesTagsDto(
val name: String,
)
@Serializable
class ChaptersPayloadDto(
val seasons: List<SeasonDto>,
)
@Serializable
class SeasonDto(
val chapters: List<ChapterDto>,
)
@Serializable
class ChapterDto(
@SerialName("ID") val id: Int,
@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>,
)