Quantum Scans/Toon: Redesign (#10368)

* Quantum Toon Redesign

* separate dto

* fix genre and robust page parsing

* rsc: 1

* try block
This commit is contained in:
manti 2025-09-05 16:21:47 +02:00 committed by Draff
parent eb51001d0a
commit a999e665de
Signed by: Draff
GPG Key ID: E8A89F3211677653
3 changed files with 127 additions and 11 deletions

View File

@ -1,9 +1,9 @@
ext {
extName = 'Quantum Scans'
extName = 'Quantum Toon'
extClass = '.QuantumScans'
themePkg = 'heancms'
baseUrl = 'https://quantumscans.org'
overrideVersionCode = 9
themePkg = 'iken'
baseUrl = 'https://quantumtoon.com'
overrideVersionCode = 29
isNsfw = false
}

View File

@ -0,0 +1,9 @@
package eu.kanade.tachiyomi.extension.en.quantumscans
import kotlinx.serialization.Serializable
@Serializable
class GenreDto(val id: Int, val name: String)
@Serializable
class PageDto(val url: String, val order: Int)

View File

@ -1,20 +1,127 @@
package eu.kanade.tachiyomi.extension.en.quantumscans
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms
import eu.kanade.tachiyomi.multisrc.iken.GenreFilter
import eu.kanade.tachiyomi.multisrc.iken.Iken
import eu.kanade.tachiyomi.multisrc.iken.SelectFilter
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import keiyoushi.utils.parseAs
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import java.util.concurrent.TimeUnit
class QuantumScans : HeanCms(
"Quantum Scans",
"https://quantumscans.org",
// Moved from HeanCms to Iken
class QuantumScans : Iken(
"Quantum Toon",
"en",
"https://quantumtoon.com",
"https://vapi.quantumtoon.com",
) {
// Moved from Keyoapp to HeanCms
override val versionId = 3
override val versionId = 4
override val client = super.client.newBuilder()
.rateLimit(3, 1, TimeUnit.SECONDS)
.build()
override val useNewChapterEndpoint = true
override fun popularMangaRequest(page: Int): Request {
val url = "$apiUrl/api/query".toHttpUrl().newBuilder().apply {
addQueryParameter("page", page.toString())
addQueryParameter("perPage", "18")
addQueryParameter("orderBy", "totalViews")
}.build()
return GET(url, headers)
}
override fun popularMangaParse(response: Response): MangasPage {
return searchMangaParse(response)
}
override fun latestUpdatesRequest(page: Int): Request {
val url = "$apiUrl/api/query".toHttpUrl().newBuilder().apply { // 'query' instead of 'posts'
addQueryParameter("page", page.toString())
addQueryParameter("perPage", "18")
addQueryParameter("orderBy", "updatedAt")
}.build()
return GET(url, headers)
}
override fun pageListRequest(chapter: SChapter): Request {
return GET(baseUrl + chapter.url, headersBuilder().add("rsc", "1").build())
}
override fun pageListParse(response: Response): List<Page> {
return response.body.string().lines()
.mapNotNull { line ->
val jsonStartIndex = line.indexOf('{').takeIf { it != -1 } ?: return@mapNotNull null
val jsonString = line.substring(jsonStartIndex)
try {
jsonString.parseAs<PageDto>().takeIf { it.url.isNotEmpty() }
} catch (e: Exception) {
null
}
}
.sortedBy { it.order }
.mapIndexed { i, p -> Page(i, imageUrl = p.url) }
}
private var genresList: List<Pair<String, String>> = emptyList()
private var fetchGenresAttempts = 0
private fun fetchGenres() {
try {
val response = client.newCall(GET("$apiUrl/api/genres", headers)).execute()
genresList = response.parseAs<List<GenreDto>>()
.map { Pair(it.name, it.id.toString()) }
} catch (e: Throwable) {} finally {
fetchGenresAttempts++
}
}
override fun getFilterList(): FilterList {
if (genresList.isEmpty() && fetchGenresAttempts < 3) {
Observable.fromCallable { fetchGenres() }
.subscribeOn(rx.schedulers.Schedulers.io())
.subscribe()
}
val filters = mutableListOf<Filter<*>>(
SortFilter(),
StatusFilter(),
)
if (genresList.isNotEmpty()) {
filters.add(GenreFilter(genresList))
} else {
filters.add(Filter.Header("Press 'Reset' to attempt to load genres"))
}
return FilterList(filters)
}
private class SortFilter : SelectFilter(
"Sort",
"orderBy",
listOf(
Pair("Popularity", "totalViews"),
Pair("Latest", "updatedAt"),
),
)
private class StatusFilter : SelectFilter(
"Status",
"seriesStatus",
listOf(
Pair("All", ""),
Pair("Ongoing", "ONGOING"),
Pair("Hiatus", "HIATUS"),
Pair("Completed", "COMPLETED"),
Pair("Dropped", "DROPPED"),
),
)
}