Kemono: fix cache and post list, update domain (#10134)
* Kemono: fix cache and post list, update domain * update
This commit is contained in:
parent
e61892ced7
commit
9ea67f22dd
@ -2,4 +2,8 @@ plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 22
|
||||
baseVersionCode = 23
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.11")
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.multisrc.kemono
|
||||
|
||||
import android.app.Application
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
@ -15,14 +16,19 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import keiyoushi.utils.getPreferences
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import keiyoushi.utils.parseAs
|
||||
import okhttp3.Cache
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.brotli.BrotliInterceptor
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.lang.Thread.sleep
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.min
|
||||
|
||||
open class Kemono(
|
||||
@ -32,13 +38,35 @@ open class Kemono(
|
||||
) : HttpSource(), ConfigurableSource {
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder().rateLimit(1).build()
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(1)
|
||||
.addInterceptor { chain ->
|
||||
val request = chain.request()
|
||||
if (request.url.pathSegments.first() == "api") {
|
||||
chain.proceed(request.newBuilder().header("Accept", "text/css").build())
|
||||
} else {
|
||||
chain.proceed(request)
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
val index = networkInterceptors().indexOfFirst { it is BrotliInterceptor }
|
||||
if (index >= 0) interceptors().add(networkInterceptors().removeAt(index))
|
||||
}
|
||||
.cache(
|
||||
Cache(
|
||||
directory = File(Injekt.get<Application>().externalCacheDir, "network_cache_${name.lowercase()}"),
|
||||
maxSize = 50L * 1024 * 1024, // 50 MiB
|
||||
),
|
||||
)
|
||||
.build()
|
||||
|
||||
private val creatorsClient = client.newBuilder()
|
||||
.readTimeout(5, TimeUnit.MINUTES)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val preferences = getPreferences()
|
||||
|
||||
private val apiPath = "api/v1"
|
||||
@ -47,8 +75,6 @@ open class Kemono(
|
||||
|
||||
private val imgCdnUrl = baseUrl.replace("//", "//img.")
|
||||
|
||||
private var mangasCache: List<KemonoCreatorDto> = emptyList()
|
||||
|
||||
private fun String.formatAvatarUrl(): String = removePrefix("https://").replaceBefore('/', imgCdnUrl)
|
||||
|
||||
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
|
||||
@ -85,6 +111,7 @@ open class Kemono(
|
||||
is SortFilter -> {
|
||||
sort = filter.getValue() to if (filter.state!!.ascending) "asc" else "desc"
|
||||
}
|
||||
|
||||
is TypeFilter -> {
|
||||
filter.state.filter { state -> state.isIncluded() }.forEach { tri ->
|
||||
typeIncluded.add(tri.value)
|
||||
@ -94,44 +121,60 @@ open class Kemono(
|
||||
typeExcluded.add(tri.value)
|
||||
}
|
||||
}
|
||||
is FavouritesFilter -> {
|
||||
|
||||
is FavoritesFilter -> {
|
||||
fav = when (filter.state[0].state) {
|
||||
0 -> null
|
||||
1 -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
var mangas = mangasCache
|
||||
if (page == 1 || mangasCache.isEmpty()) {
|
||||
var favourites: List<KemonoFavouritesDto> = emptyList()
|
||||
if (fav != null) {
|
||||
val favores = client.newCall(GET("$baseUrl/$apiPath/account/favorites", headers)).execute()
|
||||
val mangas = run {
|
||||
val favorites = if (fav != null) {
|
||||
val response = client.newCall(GET("$baseUrl/$apiPath/account/favorites", headers)).execute()
|
||||
|
||||
if (favores.code == 401) throw Exception("You are not Logged In")
|
||||
favourites = favores.parseAs<List<KemonoFavouritesDto>>().filterNot { it.service.lowercase() == "discord" }
|
||||
if (response.isSuccessful) {
|
||||
response.parseAs<List<KemonoFavoritesDto>>().filterNot { it.service.lowercase() == "discord" }
|
||||
} else {
|
||||
response.close()
|
||||
val message = if (response.code == 401) "You are not logged in" else "HTTP error ${response.code}"
|
||||
throw Exception("Failed to fetch favorites: $message")
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val response = client.newCall(GET("$baseUrl/$apiPath/creators", headers)).execute()
|
||||
val request = GET(
|
||||
"$baseUrl/$apiPath/creators",
|
||||
headers,
|
||||
CacheControl.Builder().maxStale(30, TimeUnit.MINUTES).build(),
|
||||
)
|
||||
val response = creatorsClient.newCall(request).execute()
|
||||
if (!response.isSuccessful) {
|
||||
response.close()
|
||||
throw Exception("HTTP error ${response.code}")
|
||||
}
|
||||
val allCreators = response.parseAs<List<KemonoCreatorDto>>().filterNot { it.service.lowercase() == "discord" }
|
||||
mangas = allCreators.filter {
|
||||
allCreators.filter {
|
||||
val includeType = typeIncluded.isEmpty() || typeIncluded.contains(it.service.serviceName().lowercase())
|
||||
val excludeType = typeExcluded.isNotEmpty() && typeExcluded.contains(it.service.serviceName().lowercase())
|
||||
|
||||
val regularSearch = it.name.contains(title, true)
|
||||
|
||||
val isFavourited = when (fav) {
|
||||
true -> favourites.any { f -> f.id == it.id.also { _ -> it.fav = f.faved_seq } }
|
||||
false -> favourites.none { f -> f.id == it.id }
|
||||
val isFavorited = when (fav) {
|
||||
true -> favorites.any { f -> f.id == it.id.also { _ -> it.fav = f.faved_seq } }
|
||||
false -> favorites.none { f -> f.id == it.id }
|
||||
else -> true
|
||||
}
|
||||
|
||||
includeType && !excludeType && isFavourited &&
|
||||
includeType && !excludeType && isFavorited &&
|
||||
regularSearch
|
||||
}.also { mangasCache = it }
|
||||
}
|
||||
}
|
||||
|
||||
val sorted = when (sort.first) {
|
||||
@ -142,6 +185,7 @@ open class Kemono(
|
||||
mangas.sortedBy { it.favorited }
|
||||
}
|
||||
}
|
||||
|
||||
"tit" -> {
|
||||
if (sort.second == "desc") {
|
||||
mangas.sortedByDescending { it.name }
|
||||
@ -149,6 +193,7 @@ open class Kemono(
|
||||
mangas.sortedBy { it.name }
|
||||
}
|
||||
}
|
||||
|
||||
"new" -> {
|
||||
if (sort.second == "desc") {
|
||||
mangas.sortedByDescending { it.id }
|
||||
@ -156,14 +201,16 @@ open class Kemono(
|
||||
mangas.sortedBy { it.id }
|
||||
}
|
||||
}
|
||||
|
||||
"fav" -> {
|
||||
if (fav != true) throw Exception("Please check 'Favourites Only' Filter")
|
||||
if (fav != true) throw Exception("Please check 'Favorites Only' Filter")
|
||||
if (sort.second == "desc") {
|
||||
mangas.sortedByDescending { it.fav }
|
||||
} else {
|
||||
mangas.sortedBy { it.fav }
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (sort.second == "desc") {
|
||||
mangas.sortedByDescending { it.updatedDate }
|
||||
@ -203,7 +250,7 @@ open class Kemono(
|
||||
var hasNextPage = true
|
||||
val result = ArrayList<SChapter>()
|
||||
while (offset < prefMaxPost && hasNextPage) {
|
||||
val request = GET("$baseUrl/$apiPath${manga.url}?o=$offset", headers)
|
||||
val request = GET("$baseUrl/$apiPath${manga.url}/posts?o=$offset", headers)
|
||||
val page: List<KemonoPostDto> = retry(request).parseAs()
|
||||
page.forEach { post -> if (post.images.isNotEmpty()) result.add(post.toSChapter()) }
|
||||
offset += PAGE_POST_LIMIT
|
||||
@ -252,10 +299,6 @@ open class Kemono(
|
||||
|
||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T = use {
|
||||
json.decodeFromStream(it.body.byteStream())
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = POST_PAGES_PREF
|
||||
@ -284,7 +327,7 @@ open class Kemono(
|
||||
getSortsList,
|
||||
),
|
||||
TypeFilter("Types", getTypes),
|
||||
FavouritesFilter(),
|
||||
FavoritesFilter(),
|
||||
)
|
||||
|
||||
open val getTypes: List<String> = emptyList()
|
||||
@ -295,7 +338,7 @@ open class Kemono(
|
||||
Pair("Date Updated", "lat"),
|
||||
Pair("Alphabetical Order", "tit"),
|
||||
Pair("Service", "serv"),
|
||||
Pair("Date Favourited", "fav"),
|
||||
Pair("Date Favorited", "fav"),
|
||||
)
|
||||
|
||||
internal open class TypeFilter(name: String, vals: List<String>) :
|
||||
@ -304,17 +347,19 @@ open class Kemono(
|
||||
vals.map { TriFilter(it, it.lowercase()) },
|
||||
)
|
||||
|
||||
internal class FavouritesFilter() :
|
||||
internal class FavoritesFilter() :
|
||||
Filter.Group<TriFilter>(
|
||||
"Favourites",
|
||||
listOf(TriFilter("Favourites Only", "fav")),
|
||||
"Favorites",
|
||||
listOf(TriFilter("Favorites Only", "fav")),
|
||||
)
|
||||
|
||||
internal open class TriFilter(name: String, val value: String) : Filter.TriState(name)
|
||||
|
||||
internal open class SortFilter(name: String, selection: Selection, private val vals: List<Pair<String, String>>) :
|
||||
Filter.Sort(name, vals.map { it.first }.toTypedArray(), selection) {
|
||||
fun getValue() = vals[state!!.index].second
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PAGE_POST_LIMIT = 50
|
||||
private const val PAGE_CREATORS_LIMIT = 50
|
||||
|
@ -10,7 +10,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@Serializable
|
||||
class KemonoFavouritesDto(
|
||||
class KemonoFavoritesDto(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val service: String,
|
||||
|
@ -2,7 +2,7 @@ ext {
|
||||
extName = 'Coomer'
|
||||
extClass = '.Coomer'
|
||||
themePkg = 'kemono'
|
||||
baseUrl = 'https://coomer.su'
|
||||
baseUrl = 'https://coomer.st'
|
||||
overrideVersionCode = 0
|
||||
isNsfw = true
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.extension.all.coomer
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.kemono.Kemono
|
||||
|
||||
class Coomer : Kemono("Coomer", "https://coomer.su", "all") {
|
||||
class Coomer : Kemono("Coomer", "https://coomer.st", "all") {
|
||||
override val getTypes = listOf(
|
||||
"OnlyFans",
|
||||
"Fansly",
|
||||
|
Loading…
x
Reference in New Issue
Block a user