Kagane: use site's chapter numbers & cache filters in network cache (#11248)

* Kagane: chapter number from site

* Kagane: fetch and cache filters in network cache
This commit is contained in:
AwkwardPeak7 2025-10-26 12:04:15 +05:00 committed by Draff
parent 2b394c8c38
commit afbbe6991f
Signed by: Draff
GPG Key ID: E8A89F3211677653
4 changed files with 97 additions and 46 deletions

View File

@ -1,8 +1,12 @@
ext {
extName = 'Kagane'
extClass = '.Kagane'
extVersionCode = 7
extVersionCode = 8
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
compileOnly("com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.11")
}

View File

@ -83,12 +83,14 @@ class ChapterDto(
val releaseDate: String?,
@SerialName("pages_count")
val pagesCount: Int,
@SerialName("number_sort")
val number: Float,
) {
fun toSChapter(index: Int): SChapter = SChapter.create().apply {
fun toSChapter(): SChapter = SChapter.create().apply {
url = "$seriesId;$id;$pagesCount"
name = title
date_upload = dateFormat.tryParse(releaseDate)
chapter_number = index.toFloat()
chapter_number = number
}
}

View File

@ -1,9 +1,7 @@
package eu.kanade.tachiyomi.extension.en.kagane
import eu.kanade.tachiyomi.extension.en.kagane.Kagane.Companion.CONTENT_RATINGS
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import keiyoushi.utils.parseAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.add
@ -11,40 +9,19 @@ import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import kotlinx.serialization.json.putJsonObject
private var metadataFetchAttempts: Int = 0
private var metadataFetched = false
private var genresList: List<String> = emptyList()
private var tagsList: List<String> = emptyList()
private var sourcesList: List<String> = emptyList()
fun fetchMetadata(apiUrl: String, client: okhttp3.OkHttpClient) {
if (metadataFetchAttempts < 3 && !metadataFetched) {
try {
client.newCall(GET("$apiUrl/api/v1/metadata"))
.execute().parseAs<MetadataDto>()
.let { metadata ->
genresList = metadata.getGenresList()
tagsList = metadata.getTagsList()
sourcesList = metadata.getSourcesList()
metadataFetched = true
}
} catch (_: Exception) {
} finally {
metadataFetchAttempts++
}
}
}
@Serializable
data class MetadataDto(
val genres: List<MetadataTagDto>,
val tags: List<MetadataTagDto>,
val sources: List<MetadataTagDto>,
) {
fun getGenresList() = genres.map { it.name }
fun getTagsList() = tags.sortedByDescending { it.count }.slice(0..200).map { it.name }
fun getSourcesList() = sources.map { it.name }
fun getGenresList() = genres
.map { FilterData(it.name, it.name) }
fun getTagsList() = tags.sortedByDescending { it.count }
.slice(0..200)
.map { FilterData(it.name, it.name.replaceFirstChar { c -> c.uppercase() }) }
fun getSourcesList() = sources
.map { FilterData(it.name, it.name) }
}
@Serializable
@ -96,7 +73,7 @@ internal class ContentRatingFilter(
)
internal class GenresFilter(
genres: List<FilterData> = genresList.map { FilterData(it, it) },
genres: List<FilterData>,
) : JsonMultiSelectTriFilter(
"Genres",
"genres",
@ -106,7 +83,7 @@ internal class GenresFilter(
)
internal class TagsFilter(
tags: List<FilterData> = tagsList.map { FilterData(it, it.replaceFirstChar { c -> c.uppercase() }) },
tags: List<FilterData>,
) : JsonMultiSelectTriFilter(
"Tags",
"tags",
@ -116,7 +93,7 @@ internal class TagsFilter(
)
internal class SourcesFilter(
sources: List<FilterData> = sourcesList.map { FilterData(it, it) },
sources: List<FilterData>,
) : JsonMultiSelectFilter(
"Sources",
"sources",
@ -127,7 +104,7 @@ internal class SourcesFilter(
internal class ScanlationsFilter() : Filter.CheckBox("Show scanlations", true)
internal class FilterData(
class FilterData(
val id: String,
val name: String,
)

View File

@ -1,10 +1,12 @@
package eu.kanade.tachiyomi.extension.en.kagane
import android.annotation.SuppressLint
import android.app.Application
import android.content.SharedPreferences
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log
import android.view.View
import android.webkit.JavascriptInterface
import android.webkit.PermissionRequest
@ -15,6 +17,7 @@ import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
@ -30,14 +33,18 @@ import keiyoushi.utils.toJsonString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import okhttp3.CacheControl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.brotli.BrotliInterceptor
import okhttp3.internal.closeQuietly
import okio.IOException
import rx.Observable
import uy.kohesive.injekt.Injekt
@ -66,6 +73,11 @@ class Kagane : HttpSource(), ConfigurableSource {
.addInterceptor(ImageInterceptor())
.addInterceptor(::refreshTokenInterceptor)
.rateLimit(2)
// fix disk cache
.apply {
val index = networkInterceptors().indexOfFirst { it is BrotliInterceptor }
if (index >= 0) interceptors().add(networkInterceptors().removeAt(index))
}
.build()
private fun refreshTokenInterceptor(chain: Interceptor.Chain): Response {
@ -202,7 +214,7 @@ class Kagane : HttpSource(), ConfigurableSource {
override fun chapterListParse(response: Response): List<SChapter> {
val dto = response.parseAs<ChapterDto>()
return dto.content.mapIndexed { i, it -> it.toSChapter(i + 1) }.reversed()
return dto.content.map { it -> it.toSChapter() }.reversed()
}
override fun chapterListRequest(manga: SManga): Request {
@ -250,6 +262,8 @@ class Kagane : HttpSource(), ConfigurableSource {
private var cacheUrl = "https://kazana.$domain"
private var accessToken: String = ""
@SuppressLint("SetJavaScriptEnabled")
private fun getChallengeResponse(seriesId: String, chapterId: String): ChallengeDto {
val f = "$seriesId:$chapterId".sha256().sliceArray(0 until 16)
@ -453,21 +467,75 @@ class Kagane : HttpSource(), ConfigurableSource {
// ============================= Filters ==============================
private val scope = CoroutineScope(Dispatchers.IO)
private fun launchIO(block: () -> Unit) = scope.launch { block() }
private val metadataClient = client.newBuilder()
.addNetworkInterceptor { chain ->
chain.proceed(chain.request()).newBuilder()
.header("Cache-Control", "max-age=${24 * 60 * 60}")
.removeHeader("Pragma")
.removeHeader("Expires")
.build()
}.build()
override fun getFilterList(): FilterList {
launchIO { fetchMetadata(apiUrl, client) }
return FilterList(
override fun getFilterList(): FilterList = runBlocking(Dispatchers.IO) {
val filters: MutableList<Filter<*>> = mutableListOf(
SortFilter(),
ContentRatingFilter(
preferences.contentRating.toSet(),
),
GenresFilter(),
TagsFilter(),
SourcesFilter(),
// GenresFilter(),
// TagsFilter(),
// SourcesFilter(),
Filter.Separator(),
ScanlationsFilter(),
)
val response = metadataClient.newCall(
GET("$apiUrl/api/v1/metadata", headers, CacheControl.FORCE_CACHE),
).await()
// the cache only request fails if it was not cached already
if (!response.isSuccessful) {
CoroutineScope(Dispatchers.IO).launch {
metadataClient.newCall(
GET("$apiUrl/api/v1/metadata", headers, CacheControl.FORCE_NETWORK),
).await().closeQuietly()
}
filters.addAll(
index = 0,
listOf(
Filter.Header("Press 'reset' to load more filters"),
Filter.Separator(),
),
)
return@runBlocking FilterList(filters)
}
val metadata = try {
response.parseAs<MetadataDto>()
} catch (e: Throwable) {
Log.e(name, "Unable to parse filters", e)
filters.addAll(
index = 0,
listOf(
Filter.Header("Failed to parse additional filters"),
Filter.Separator(),
),
)
return@runBlocking FilterList(filters)
}
filters.addAll(
index = 2,
listOf(
GenresFilter(metadata.getGenresList()),
TagsFilter(metadata.getTagsList()),
SourcesFilter(metadata.getSourcesList()),
),
)
FilterList(filters)
}
}