Kemono: support new design and paginate result (#14014)

This commit is contained in:
stevenyomi 2022-10-28 21:53:15 +08:00 committed by GitHub
parent c8ed15f451
commit da1348c813
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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)