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 eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okio.blackholeSink
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.select.Evaluator import org.jsoup.select.Evaluator
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.util.TimeZone import java.util.TimeZone
import kotlin.math.min
open class Kemono( open class Kemono(
override val name: String, override val name: String,
@ -32,6 +37,8 @@ open class Kemono(
) : HttpSource(), ConfigurableSource { ) : HttpSource(), ConfigurableSource {
override val supportsLatest = true override val supportsLatest = true
private val isNewDesign get() = name == "Kemono"
override val client = network.client.newBuilder().rateLimit(2).build() override val client = network.client.newBuilder().rateLimit(2).build()
override fun headersBuilder() = super.headersBuilder() override fun headersBuilder() = super.headersBuilder()
@ -71,15 +78,86 @@ open class Kemono(
override fun latestUpdatesParse(response: Response) = popularMangaParse(response) 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 { 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 baseUrl = this.baseUrl
val response = client.newCall(GET("$baseUrl/api/creators", headers)).execute() val response = client.newCall(GET("$baseUrl/api/creators", headers)).execute()
val result = response.parseAs<List<KemonoCreatorDto>>() val allCreators = block(response.parseAs())
.filter { it.name.contains(query, ignoreCase = true) } val count = allCreators.size
.sortedByDescending { it.updatedDate } 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) } .map { it.toSManga(baseUrl) }
.filterUnsupported() .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.") override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not used.")
@ -140,6 +218,7 @@ open class Kemono(
companion object { companion object {
private const val PAGE_SIZE = 25 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." const val PROMPT = "You can change how many posts to load in the extension preferences."
private const val POST_PAGE_SIZE = 50 private const val POST_PAGE_SIZE = 50

View File

@ -14,6 +14,7 @@ class KemonoCreatorDto(
val name: String, val name: String,
private val service: String, private val service: String,
private val updated: JsonPrimitive, private val updated: JsonPrimitive,
val favorited: Int = -1,
) { ) {
val updatedDate get() = when { val updatedDate get() = when {
updated.isString -> dateFormat.parse(updated.content)?.time ?: 0 updated.isString -> dateFormat.parse(updated.content)?.time ?: 0

View File

@ -6,7 +6,7 @@ import generator.ThemeSourceGenerator
class KemonoGenerator : ThemeSourceGenerator { class KemonoGenerator : ThemeSourceGenerator {
override val themeClass = "Kemono" override val themeClass = "Kemono"
override val themePkg = "kemono" override val themePkg = "kemono"
override val baseVersionCode = 3 override val baseVersionCode = 4
override val sources = listOf( override val sources = listOf(
SingleLang("Kemono", "https://kemono.party", "all", isNsfw = true), SingleLang("Kemono", "https://kemono.party", "all", isNsfw = true),
SingleLang("Coomer", "https://coomer.party", "all", isNsfw = true) SingleLang("Coomer", "https://coomer.party", "all", isNsfw = true)