Kemono: add mirror setting and Coomer new design (#17311)

This commit is contained in:
stevenyomi 2023-07-30 05:31:38 +08:00 committed by GitHub
parent bbcae007e8
commit 66cd665b92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 49 deletions

View File

@ -20,7 +20,6 @@ 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
@ -32,12 +31,12 @@ import kotlin.math.min
open class Kemono(
override val name: String,
override val baseUrl: String,
private val defaultUrl: String,
override val lang: String = "all",
) : HttpSource(), ConfigurableSource {
override val supportsLatest = true
private val isNewDesign get() = name == "Kemono"
private val mirrorUrls get() = arrayOf(defaultUrl, defaultUrl.removeSuffix(".party") + ".su")
override val client = network.client.newBuilder().rateLimit(2).build()
@ -46,47 +45,33 @@ open class Kemono(
private val json: Json by injectLazy()
private val preferences by lazy {
private val preferences =
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/artists?o=${PAGE_SIZE * (page - 1)}", headers)
override val baseUrl = preferences.getString(BASE_URL_PREF, defaultUrl)!!
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val cardList = document.selectFirst(Evaluator.Class("card-list"))!!
val creators = cardList.select(Evaluator.Tag("article")).map {
val children = it.children()
val avatar = children[0].selectFirst(Evaluator.Tag("img"))!!.attr("src")
val link = children[1].child(0)
val service = children[2].ownText()
SManga.create().apply {
url = link.attr("href")
title = link.ownText()
author = service
thumbnail_url = baseUrl + avatar
description = PROMPT
initialized = true
}
}.filterUnsupported()
return MangasPage(creators, document.hasNextPage())
}
private val imgCdnUrl = when (name) {
"Kemono" -> baseUrl
else -> defaultUrl
}.replace("//", "//img.")
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/artists/updated?o=${PAGE_SIZE * (page - 1)}", headers)
private fun String.formatAvatarUrl(): String = removePrefix("https://").replaceBefore('/', imgCdnUrl)
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
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 })
}
@ -106,7 +91,7 @@ open class Kemono(
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")
thumbnail_url = it.selectFirst(Evaluator.Tag("img"))!!.absUrl("src").formatAvatarUrl()
description = PROMPT
initialized = true
}
@ -135,14 +120,14 @@ open class Kemono(
page: Int,
block: (ArrayList<KemonoCreatorDto>) -> List<KemonoCreatorDto>,
): MangasPage {
val baseUrl = this.baseUrl
val imgCdnUrl = this.imgCdnUrl
val response = client.newCall(GET("$baseUrl/api/creators", headers)).execute()
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) }
.map { it.toSManga(imgCdnUrl) }
.filterUnsupported()
return MangasPage(creators, toIndex < count)
}
@ -160,12 +145,15 @@ open class Kemono(
client.newCall(GET("$baseUrl/api/creators", headers)).enqueue(callback)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not used.")
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Not used.")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga)
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
manga.thumbnail_url = manga.thumbnail_url!!.formatAvatarUrl()
return Observable.just(manga)
}
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException("Not used.")
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.fromCallable {
KemonoPostDto.dateFormat.timeZone = when (manga.author) {
@ -187,7 +175,7 @@ open class Kemono(
result
}
override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Not used.")
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override fun pageListRequest(chapter: SChapter): Request =
GET("$baseUrl/api${chapter.url}", headers)
@ -197,7 +185,7 @@ open class Kemono(
return post[0].images.mapIndexed { i, path -> Page(i, imageUrl = baseUrl + path) }
}
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used.")
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromStream(it.body.byteStream())
@ -214,10 +202,18 @@ open class Kemono(
}.toTypedArray()
setDefaultValue(POST_PAGES_DEFAULT)
}.let { screen.addPreference(it) }
ListPreference(screen.context).apply {
key = BASE_URL_PREF
title = "Mirror URL"
summary = "%s\nRequires app restart to take effect"
entries = mirrorUrls
entryValues = mirrorUrls
setDefaultValue(defaultUrl)
}.let(screen::addPreference)
}
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."
@ -226,11 +222,8 @@ open class Kemono(
private const val POST_PAGES_DEFAULT = "1"
private const val POST_PAGES_MAX = 50
private fun Element.hasNextPage(): Boolean {
val pagination = selectFirst(Evaluator.Class("paginator"))!!
return pagination.selectFirst("a[title=Next page]") != null
}
private fun List<SManga>.filterUnsupported() = filterNot { it.author == "Discord" }
private const val BASE_URL_PREF = "BASE_URL"
}
}

View File

@ -21,11 +21,11 @@ class KemonoCreatorDto(
else -> (updated.double * 1000).toLong()
}
fun toSManga(baseUrl: String) = SManga.create().apply {
fun toSManga(imgCdnUrl: String) = SManga.create().apply {
url = "/$service/user/$id" // should be /server/ for Discord but will be filtered anyway
title = name
author = service.serviceName()
thumbnail_url = "$baseUrl/icons/$service/$id"
thumbnail_url = "$imgCdnUrl/icons/$service/$id"
description = Kemono.PROMPT
initialized = true
}

View File

@ -9,7 +9,7 @@ class KemonoGenerator : ThemeSourceGenerator {
override val themePkg = "kemono"
override val baseVersionCode = 5
override val baseVersionCode = 6
override val sources = listOf(
SingleLang("Kemono", "https://kemono.party", "all", isNsfw = true),