Kemono: support new design and paginate result (#14014)
This commit is contained in:
parent
c8ed15f451
commit
da1348c813
|
@ -15,15 +15,20 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
|||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okio.blackholeSink
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Evaluator
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
import java.util.TimeZone
|
||||
import kotlin.math.min
|
||||
|
||||
open class Kemono(
|
||||
override val name: String,
|
||||
|
@ -32,6 +37,8 @@ open class Kemono(
|
|||
) : HttpSource(), ConfigurableSource {
|
||||
override val supportsLatest = true
|
||||
|
||||
private val isNewDesign get() = name == "Kemono"
|
||||
|
||||
override val client = network.client.newBuilder().rateLimit(2).build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
|
@ -71,15 +78,86 @@ open class Kemono(
|
|||
|
||||
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
if (!isNewDesign) return super.fetchPopularManga(page)
|
||||
return Observable.fromCallable {
|
||||
fetchNewDesignListing(page, "/artists", compareByDescending { it.favorited })
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
if (!isNewDesign) return super.fetchLatestUpdates(page)
|
||||
return Observable.fromCallable {
|
||||
fetchNewDesignListing(page, "/artists/updated", compareByDescending { it.updatedDate })
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchNewDesignListing(
|
||||
page: Int,
|
||||
path: String,
|
||||
comparator: Comparator<KemonoCreatorDto>,
|
||||
): MangasPage {
|
||||
val baseUrl = baseUrl
|
||||
return if (page == 1) {
|
||||
val document = client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
|
||||
val cardList = document.selectFirst(Evaluator.Class("card-list__items"))
|
||||
val creators = cardList.children().map {
|
||||
SManga.create().apply {
|
||||
url = it.attr("href")
|
||||
title = it.selectFirst(Evaluator.Class("user-card__name")).ownText()
|
||||
author = it.selectFirst(Evaluator.Class("user-card__service")).ownText()
|
||||
thumbnail_url = baseUrl + it.selectFirst(Evaluator.Tag("img")).attr("src")
|
||||
description = PROMPT
|
||||
initialized = true
|
||||
}
|
||||
}.filterUnsupported()
|
||||
MangasPage(creators, true).also { cacheCreators() }
|
||||
} else {
|
||||
fetchCreatorsPage(page) { it.apply { sortWith(comparator) } }
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.fromCallable {
|
||||
if (query.isBlank()) throw Exception("Query is empty")
|
||||
fetchCreatorsPage(page) { all ->
|
||||
val result = all.filterTo(ArrayList()) { it.name.contains(query, ignoreCase = true) }
|
||||
if (result.isEmpty()) return@fetchCreatorsPage emptyList()
|
||||
if (result[0].favorited != -1) {
|
||||
result.sortByDescending { it.favorited }
|
||||
} else {
|
||||
result.sortByDescending { it.updatedDate }
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchCreatorsPage(
|
||||
page: Int,
|
||||
block: (ArrayList<KemonoCreatorDto>) -> List<KemonoCreatorDto>,
|
||||
): MangasPage {
|
||||
val baseUrl = this.baseUrl
|
||||
val response = client.newCall(GET("$baseUrl/api/creators", headers)).execute()
|
||||
val result = response.parseAs<List<KemonoCreatorDto>>()
|
||||
.filter { it.name.contains(query, ignoreCase = true) }
|
||||
.sortedByDescending { it.updatedDate }
|
||||
val allCreators = block(response.parseAs())
|
||||
val count = allCreators.size
|
||||
val fromIndex = (page - 1) * NEW_PAGE_SIZE
|
||||
val toIndex = min(count, fromIndex + NEW_PAGE_SIZE)
|
||||
val creators = allCreators.subList(fromIndex, toIndex)
|
||||
.map { it.toSManga(baseUrl) }
|
||||
.filterUnsupported()
|
||||
MangasPage(result, false)
|
||||
return MangasPage(creators, toIndex < count)
|
||||
}
|
||||
|
||||
private fun cacheCreators() {
|
||||
val callback = object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) =
|
||||
response.body!!.source().run {
|
||||
readAll(blackholeSink())
|
||||
close()
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) = Unit
|
||||
}
|
||||
client.newCall(GET("$baseUrl/api/creators", headers)).enqueue(callback)
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not used.")
|
||||
|
@ -140,6 +218,7 @@ open class Kemono(
|
|||
|
||||
companion object {
|
||||
private const val PAGE_SIZE = 25
|
||||
private const val NEW_PAGE_SIZE = 50
|
||||
const val PROMPT = "You can change how many posts to load in the extension preferences."
|
||||
|
||||
private const val POST_PAGE_SIZE = 50
|
||||
|
|
|
@ -14,6 +14,7 @@ class KemonoCreatorDto(
|
|||
val name: String,
|
||||
private val service: String,
|
||||
private val updated: JsonPrimitive,
|
||||
val favorited: Int = -1,
|
||||
) {
|
||||
val updatedDate get() = when {
|
||||
updated.isString -> dateFormat.parse(updated.content)?.time ?: 0
|
||||
|
|
|
@ -6,7 +6,7 @@ import generator.ThemeSourceGenerator
|
|||
class KemonoGenerator : ThemeSourceGenerator {
|
||||
override val themeClass = "Kemono"
|
||||
override val themePkg = "kemono"
|
||||
override val baseVersionCode = 3
|
||||
override val baseVersionCode = 4
|
||||
override val sources = listOf(
|
||||
SingleLang("Kemono", "https://kemono.party", "all", isNsfw = true),
|
||||
SingleLang("Coomer", "https://coomer.party", "all", isNsfw = true)
|
||||
|
|
Loading…
Reference in New Issue