Kemono: add mirror setting and Coomer new design (#17311)
This commit is contained in:
parent
bbcae007e8
commit
66cd665b92
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue