mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-22 10:22:47 +01:00
parent
1156546834
commit
89bc635ebe
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Kumanga'
|
||||
extClass = '.Kumanga'
|
||||
extVersionCode = 8
|
||||
extVersionCode = 9
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.es.kumanga
|
||||
import android.util.Base64
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
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
|
||||
@ -11,12 +12,8 @@ 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 kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
@ -36,78 +33,71 @@ class Kumanga : HttpSource() {
|
||||
|
||||
override val baseUrl = "https://www.kumanga.com"
|
||||
|
||||
private val apiUrl = "https://www.kumanga.com/backend/ajax/searchengine_master.php"
|
||||
|
||||
override val lang = "es"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
.newBuilder()
|
||||
.followRedirects(true)
|
||||
.addInterceptor { chain ->
|
||||
val originalRequest = chain.request()
|
||||
if (originalRequest.url.toString().endsWith("token=")) {
|
||||
getKumangaToken()
|
||||
val url = originalRequest.url.toString() + kumangaToken
|
||||
val newRequest = originalRequest.newBuilder().url(url).build()
|
||||
chain.proceed(newRequest)
|
||||
} else {
|
||||
chain.proceed(originalRequest)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
private var kumangaToken = ""
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||
.add("Referer", baseUrl)
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
private var kumangaToken = ""
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
.newBuilder()
|
||||
.rateLimit(2)
|
||||
.addInterceptor { chain ->
|
||||
val request = chain.request()
|
||||
if (!request.url.toString().startsWith(apiUrl)) return@addInterceptor chain.proceed(request)
|
||||
if (kumangaToken.isBlank()) getKumangaToken()
|
||||
var newRequest = addTokenToRequest(request)
|
||||
val response = chain.proceed(newRequest)
|
||||
if (response.code == 400) {
|
||||
response.close()
|
||||
getKumangaToken()
|
||||
newRequest = addTokenToRequest(request)
|
||||
chain.proceed(newRequest)
|
||||
} else {
|
||||
response
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
private fun encodeAndReverse(dtValue: String): String {
|
||||
return Base64.encodeToString(dtValue.toByteArray(), Base64.DEFAULT).reversed().trim()
|
||||
private fun addTokenToRequest(request: Request): Request {
|
||||
return request.newBuilder()
|
||||
.url(request.url.newBuilder().removeAllQueryParameters("token").addQueryParameter("token", kumangaToken).build())
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun decodeBase64(encodedString: String): String {
|
||||
return Base64.decode(encodedString, Base64.DEFAULT).toString(charset("UTF-8"))
|
||||
}
|
||||
|
||||
private fun getKumangaToken(): String {
|
||||
private fun getKumangaToken() {
|
||||
val body = client.newCall(GET("$baseUrl/mangalist?&page=1", headers)).execute().asJsoup()
|
||||
val dt = body.select("#searchinput").attr("dt").toString()
|
||||
val kumangaTokenKey = encodeAndReverse(encodeAndReverse(dt))
|
||||
.replace("=", "k")
|
||||
.lowercase(Locale.ROOT)
|
||||
kumangaToken = body.select("div.input-group [type=hidden]").attr(kumangaTokenKey)
|
||||
return kumangaToken
|
||||
}
|
||||
|
||||
private fun getMangaCover(mangaId: String) = "$baseUrl/kumathumb.php?src=$mangaId"
|
||||
|
||||
private fun getMangaUrl(mangaId: String, mangaSlug: String, page: Int) = "/manga/$mangaId/p/$page/$mangaSlug#cl"
|
||||
|
||||
private fun parseMangaFromJson(jsonObj: JsonObject) = SManga.create().apply {
|
||||
title = jsonObj["name"]!!.jsonPrimitive.content
|
||||
description = jsonObj["description"]!!.jsonPrimitive.content.replace("\\", "")
|
||||
url = getMangaUrl(jsonObj["id"]!!.jsonPrimitive.content, jsonObj["slug"]!!.jsonPrimitive.content, 1)
|
||||
thumbnail_url = getMangaCover(jsonObj["id"]!!.jsonPrimitive.content)
|
||||
genre = jsonObj["categories"]!!.jsonArray
|
||||
.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
getKumangaToken() // Get new token every request (prevent http 400)
|
||||
return POST("$baseUrl/backend/ajax/searchengine.php?page=$page&perPage=10&keywords=&retrieveCategories=true&retrieveAuthors=false&contentType=manga&token=$kumangaToken", headers)
|
||||
val url = apiUrl.toHttpUrl().newBuilder()
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("perPage", CONTENT_PER_PAGE.toString())
|
||||
.addQueryParameter("retrieveCategories", "true")
|
||||
.addQueryParameter("retrieveAuthors", "true")
|
||||
.addQueryParameter("contentType", "manga")
|
||||
.build()
|
||||
|
||||
return POST(url.toString(), headers)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val jsonResult = json.parseToJsonElement(response.body.string()).jsonObject
|
||||
|
||||
val mangaList = jsonResult["contents"]!!.jsonArray
|
||||
.map { jsonEl -> parseMangaFromJson(jsonEl.jsonObject) }
|
||||
|
||||
val hasNextPage = jsonResult["retrievedCount"]!!.jsonPrimitive.int == 10
|
||||
|
||||
return MangasPage(mangaList, hasNextPage)
|
||||
val jsonResult = json.decodeFromString<ComicsPayloadDto>(response.body.string())
|
||||
val mangas = jsonResult.contents.map { it.toSManga(baseUrl) }
|
||||
val hasNextPage = jsonResult.retrievedCount == CONTENT_PER_PAGE
|
||||
return MangasPage(mangas, hasNextPage)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||
@ -115,31 +105,25 @@ class Kumanga : HttpSource() {
|
||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
|
||||
val body = response.asJsoup()
|
||||
thumbnail_url = body.selectFirst("div.km-img-gral-2 img")?.attr("abs:src")
|
||||
body.select("div#tab2").let {
|
||||
val document = response.asJsoup()
|
||||
thumbnail_url = document.selectFirst("div.km-img-gral-2 img")?.attr("abs:src")
|
||||
document.select("div#tab1").let {
|
||||
description = it.select("p").text()
|
||||
}
|
||||
document.select("div#tab2").let {
|
||||
status = parseStatus(it.select("span").text().orEmpty())
|
||||
author = it.select("p:nth-child(3) > a").text()
|
||||
artist = it.select("p:nth-child(4) > a").text()
|
||||
author = it.select("p:contains(Autor) > a").text()
|
||||
artist = it.select("p:contains(Artista) > a").text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("Activo") -> SManga.ONGOING
|
||||
status.contains("Finalizado") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault())
|
||||
.parse(date)?.time ?: 0L
|
||||
|
||||
private fun chapterSelector() = "div#accordion .title"
|
||||
private fun chapterSelector() = "div[id^=accordion] .title"
|
||||
|
||||
private fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
element.select("a:has(i)").let {
|
||||
setUrlWithoutDomain(it.attr("abs:href").replace("/c/", "/leer/"))
|
||||
name = it.text()
|
||||
date_upload = parseChapterDate(it.attr("title"))
|
||||
date_upload = parseDate(it.attr("title"))
|
||||
}
|
||||
scanlator = element.select("span.pull-right.greenSpan").text()
|
||||
}
|
||||
@ -157,7 +141,6 @@ class Kumanga : HttpSource() {
|
||||
val numberOfPages = (numberChapters / 10.toDouble() + 0.4).roundToInt()
|
||||
document.select(chapterSelector()).map { add(chapterFromElement(it)) }
|
||||
var page = 2
|
||||
|
||||
while (page <= numberOfPages) {
|
||||
document = client.newCall(GET(baseUrl + getMangaUrl(mangaId, mangaSlug, page))).execute().asJsoup()
|
||||
document.select(chapterSelector()).map { add(chapterFromElement(it)) }
|
||||
@ -185,13 +168,12 @@ class Kumanga : HttpSource() {
|
||||
?.substringAfter("var pUrl=")
|
||||
?.substringBefore(";")
|
||||
?.let { decodeBase64(decodeBase64(it).reversed().dropLast(10).drop(10)) }
|
||||
?: throw Exception("imagesJsonListStr null")
|
||||
?: throw Exception("No se pudo obtener la lista de imágenes")
|
||||
|
||||
val jsonResult = json.parseToJsonElement(imagesJsonRaw).jsonArray
|
||||
val jsonResult = json.decodeFromString<List<ImageDto>>(imagesJsonRaw)
|
||||
|
||||
return jsonResult.mapIndexed { i, jsonEl ->
|
||||
val jsonObj = jsonEl.jsonObject
|
||||
val imagePath = jsonObj["imgURL"]!!.jsonPrimitive.content.replace("\\", "")
|
||||
return jsonResult.mapIndexed { i, item ->
|
||||
val imagePath = item.imgURL.replace("\\", "")
|
||||
val docUrl = document.location()
|
||||
val baseUrl = URL(docUrl).protocol + "://" + URL(docUrl).host // For some reason baseUri returns the full url
|
||||
Page(i, baseUrl, "$baseUrl/$imagePath")
|
||||
@ -209,8 +191,13 @@ class Kumanga : HttpSource() {
|
||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
getKumangaToken()
|
||||
val url = "$baseUrl/backend/ajax/searchengine.php?page=$page&perPage=10&keywords=$query&retrieveCategories=true&retrieveAuthors=false&contentType=manga&token=$kumangaToken".toHttpUrl().newBuilder()
|
||||
val url = apiUrl.toHttpUrl().newBuilder()
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("perPage", CONTENT_PER_PAGE.toString())
|
||||
.addQueryParameter("retrieveCategories", "true")
|
||||
.addQueryParameter("retrieveAuthors", "true")
|
||||
.addQueryParameter("contentType", "manga")
|
||||
.addQueryParameter("keywords", query)
|
||||
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
@ -319,4 +306,33 @@ class Kumanga : HttpSource() {
|
||||
Genre("Yaoi", "44"),
|
||||
Genre("Yuri", "45"),
|
||||
)
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("Activo") -> SManga.ONGOING
|
||||
status.contains("Finalizado") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
private fun parseDate(date: String): Long {
|
||||
return try {
|
||||
DATE_FORMAT.parse(date)?.time ?: 0
|
||||
} catch (_: Exception) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMangaUrl(mangaId: String, mangaSlug: String, page: Int) = "/manga/$mangaId/p/$page/$mangaSlug"
|
||||
|
||||
private fun encodeAndReverse(dtValue: String): String {
|
||||
return Base64.encodeToString(dtValue.toByteArray(), Base64.DEFAULT).reversed().trim()
|
||||
}
|
||||
|
||||
private fun decodeBase64(encodedString: String): String {
|
||||
return Base64.decode(encodedString, Base64.DEFAULT).toString(charset("UTF-8"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DATE_FORMAT = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.ROOT)
|
||||
private const val CONTENT_PER_PAGE = 24
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package eu.kanade.tachiyomi.extension.es.kumanga
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class ComicsPayloadDto(
|
||||
val contents: List<ComicDto>,
|
||||
val retrievedCount: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class ComicDto(
|
||||
private val id: Int,
|
||||
private val name: String,
|
||||
private val slug: String,
|
||||
) {
|
||||
fun toSManga(baseUrl: String) = SManga.create().apply {
|
||||
title = name
|
||||
url = createMangaUrl(id.toString(), slug)
|
||||
thumbnail_url = guessMangaCover(id.toString(), baseUrl)
|
||||
}
|
||||
|
||||
private fun guessMangaCover(mangaId: String, baseUrl: String) = "$baseUrl/kumathumb.php?src=$mangaId"
|
||||
private fun createMangaUrl(mangaId: String, mangaSlug: String) = "/manga/$mangaId/p/1/$mangaSlug"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class ImageDto(
|
||||
val imgURL: String,
|
||||
)
|
Loading…
Reference in New Issue
Block a user