parent
8914fc42dd
commit
28d71dd743
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Koinobori Scan'
|
extName = 'Koinobori Scan'
|
||||||
extClass = '.KoinoboriScan'
|
extClass = '.KoinoboriScan'
|
||||||
extVersionCode = 37
|
extVersionCode = 38
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
@ -18,12 +19,12 @@ import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class KoinoboriScan : HttpSource() {
|
class KoinoboriScan : HttpSource() {
|
||||||
|
|
||||||
// Site change theme from Madara to custom
|
override val versionId = 3
|
||||||
override val versionId = 2
|
|
||||||
|
|
||||||
override val name = "Koinobori Scan"
|
override val name = "Koinobori Scan"
|
||||||
|
|
||||||
|
@ -37,7 +38,9 @@ class KoinoboriScan : HttpSource() {
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
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()
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
.rateLimit(2, 1)
|
.rateLimit(2, 1)
|
||||||
|
@ -47,21 +50,23 @@ class KoinoboriScan : HttpSource() {
|
||||||
.set("Referer", "$baseUrl/")
|
.set("Referer", "$baseUrl/")
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request =
|
override fun popularMangaRequest(page: Int): Request =
|
||||||
GET("$apiBaseUrl/topSeries", headers)
|
GET("$apiBaseUrl/api/topSeries", headers)
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val mangas = json.decodeFromString<List<SeriesDto>>(response.body.string())
|
val result = json.decodeFromString<TopSeriesDto>(response.body.string())
|
||||||
.map { it.toSManga(apiBaseUrl) }
|
val mangas = (result.mensualRes + result.weekRes + result.dayRes)
|
||||||
|
.distinctBy { it.slug }
|
||||||
|
.map { it.toSManga() }
|
||||||
|
|
||||||
return MangasPage(mangas, false)
|
return MangasPage(mangas, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request =
|
override fun latestUpdatesRequest(page: Int): Request =
|
||||||
GET("$apiBaseUrl/lastupdates", headers)
|
GET("$apiBaseUrl/api/lastupdates", headers)
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
val mangas = json.decodeFromString<List<SeriesDto>>(response.body.string())
|
val mangas = json.decodeFromString<List<SeriesDto>>(response.body.string())
|
||||||
.map { it.toSManga(apiBaseUrl) }
|
.map { it.toSManga() }
|
||||||
|
|
||||||
return MangasPage(mangas, false)
|
return MangasPage(mangas, false)
|
||||||
}
|
}
|
||||||
|
@ -83,7 +88,7 @@ class KoinoboriScan : HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
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 {
|
private fun searchMangaParse(response: Response, page: Int, query: String): MangasPage {
|
||||||
val result = json.decodeFromString<List<SeriesDto>>(response.body.string())
|
val result = json.decodeFromString<List<SeriesDto>>(response.body.string())
|
||||||
|
@ -99,7 +104,7 @@ class KoinoboriScan : HttpSource() {
|
||||||
val mangas = filteredSeries.subList(
|
val mangas = filteredSeries.subList(
|
||||||
(page - 1) * SERIES_PER_PAGE,
|
(page - 1) * SERIES_PER_PAGE,
|
||||||
min(page * SERIES_PER_PAGE, filteredSeries.size),
|
min(page * SERIES_PER_PAGE, filteredSeries.size),
|
||||||
).map { it.toSManga(apiBaseUrl) }
|
).map { it.toSManga() }
|
||||||
|
|
||||||
val hasNextPage = filteredSeries.size > page * SERIES_PER_PAGE
|
val hasNextPage = filteredSeries.size > page * SERIES_PER_PAGE
|
||||||
|
|
||||||
|
@ -110,27 +115,38 @@ class KoinoboriScan : HttpSource() {
|
||||||
Filter.Header("Presione 'Filtrar' para mostrar toda la biblioteca"),
|
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 =
|
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 {
|
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 =
|
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
|
||||||
GET("$apiBaseUrl/api/project/${manga.url}", headers)
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
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 ->
|
return result.seasons.flatMap { season ->
|
||||||
season.chapters.map { chapter ->
|
season.chapters.map { chapter ->
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
url = chapter.id.toString()
|
url = "$seriesSlug/${chapter.slug}"
|
||||||
name = "Capítulo ${chapter.name}: ${chapter.title}"
|
name = chapter.name
|
||||||
|
if (!chapter.title.isNullOrBlank()) {
|
||||||
|
name += ": ${chapter.title}"
|
||||||
|
}
|
||||||
date_upload = try {
|
date_upload = try {
|
||||||
dateFormat.parse(chapter.date)?.time ?: 0
|
dateFormat.parse(chapter.date)?.time ?: 0
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -138,25 +154,29 @@ class KoinoboriScan : HttpSource() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.reversed()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request =
|
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> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
val result = json.decodeFromString<PagesPayloadDto>(response.body.string())
|
val document = response.asJsoup()
|
||||||
val key = result.key
|
return document.select("body > div.w-full > div > img").mapIndexed { i, img ->
|
||||||
val chapterId = result.chapter.id
|
Page(i, imageUrl = img.attr("abs:src"))
|
||||||
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 searchMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
private fun String.unescape(): String {
|
||||||
|
return UNESCAPE_REGEX.replace(this, "$1")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SERIES_PER_PAGE = 24
|
const val SERIES_PER_PAGE = 24
|
||||||
|
val UNESCAPE_REGEX = """\\(.)""".toRegex()
|
||||||
|
val MANGA_DETAILS_REGEX = """self\.__next_f\.push\(.*info\\":(\{.*Chapter.*\}).*\\"userIsFollowed""".toRegex()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,33 +4,40 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class TopSeriesDto(
|
||||||
|
val mensualRes: List<SeriesDto>,
|
||||||
|
val weekRes: List<SeriesDto>,
|
||||||
|
val dayRes: List<SeriesDto>,
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SeriesDto(
|
class SeriesDto(
|
||||||
@SerialName("ID") private val id: Int,
|
@SerialName("series_slug") val slug: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
private val description: String,
|
private val description: String?,
|
||||||
private val thumbnail: String,
|
private val thumbnail: String?,
|
||||||
private val status: String?,
|
private val status: String?,
|
||||||
private val author: String?,
|
private val author: String?,
|
||||||
private val tags: List<SeriesTagsDto>? = emptyList(),
|
private val tags: List<SeriesTagsDto>? = emptyList(),
|
||||||
) {
|
) {
|
||||||
fun toSManga(cdnUrl: String) = SManga.create().apply {
|
fun toSManga() = SManga.create().apply {
|
||||||
title = this@SeriesDto.title
|
title = this@SeriesDto.title
|
||||||
thumbnail_url = cdnUrl + thumbnail
|
thumbnail_url = thumbnail
|
||||||
url = id.toString()
|
url = slug
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toSMangaDetails(cdnUrl: String) = SManga.create().apply {
|
fun toSMangaDetails() = SManga.create().apply {
|
||||||
title = this@SeriesDto.title.trim()
|
title = this@SeriesDto.title.trim()
|
||||||
author = this@SeriesDto.author?.trim()
|
author = this@SeriesDto.author?.trim()
|
||||||
status = parseStatus(this@SeriesDto.status)
|
status = parseStatus(this@SeriesDto.status)
|
||||||
thumbnail_url = cdnUrl + thumbnail
|
thumbnail_url = thumbnail
|
||||||
genre = tags?.joinToString { it.name.trim() }
|
genre = tags?.joinToString { it.name.trim() }
|
||||||
description = this@SeriesDto.description.trim()
|
description = this@SeriesDto.description?.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseStatus(status: String?) = when (status?.trim()) {
|
private fun parseStatus(status: String?) = when (status?.trim()) {
|
||||||
"En emisión", "En curso" -> SManga.ONGOING
|
"Ongoing" -> SManga.ONGOING
|
||||||
"Completado" -> SManga.COMPLETED
|
"Completado" -> SManga.COMPLETED
|
||||||
"Abandonado" -> SManga.CANCELLED
|
"Abandonado" -> SManga.CANCELLED
|
||||||
"Pausado" -> SManga.ON_HIATUS
|
"Pausado" -> SManga.ON_HIATUS
|
||||||
|
@ -45,30 +52,19 @@ class SeriesTagsDto(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ChaptersPayloadDto(
|
class ChaptersPayloadDto(
|
||||||
val seasons: List<SeasonDto>,
|
@SerialName("series_slug") val seriesSlug: String,
|
||||||
|
@SerialName("Season") val seasons: List<SeasonDto>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SeasonDto(
|
class SeasonDto(
|
||||||
val chapters: List<ChapterDto>,
|
@SerialName("Chapter") val chapters: List<ChapterDto>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ChapterDto(
|
class ChapterDto(
|
||||||
@SerialName("ID") val id: Int,
|
@SerialName("chapter_slug") val slug: String,
|
||||||
@SerialName("chapter_name") val name: String,
|
@SerialName("chapter_name") val name: String,
|
||||||
@SerialName("chapter_title") val title: String,
|
@SerialName("chapter_title") val title: String?,
|
||||||
@SerialName("CreatedAt") val date: String,
|
@SerialName("created_at") val date: String,
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class PagesPayloadDto(
|
|
||||||
val chapter: ChapterImagesDto,
|
|
||||||
val key: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class ChapterImagesDto(
|
|
||||||
@SerialName("ID") val id: Int,
|
|
||||||
val images: List<String>,
|
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue