Fix sana scans search and downloading (#11024)

* Sana Scans: fix search filtering and chapter page order

- normalize search queries in Sana Scans so catalog results filter properly
- sort chapter images using the site-provided order field in the shared Iken multisrc

* Sana Scans: additional changes to meet checklist

* implement changes from reviewer

* fix iken base version

* fix errors in autometed testing
This commit is contained in:
Hemal Kothapalli 2025-10-14 00:17:52 +11:00 committed by Draff
parent 4007461062
commit 24d5d8920b
Signed by: Draff
GPG Key ID: E8A89F3211677653
2 changed files with 75 additions and 2 deletions

View File

@ -3,7 +3,7 @@ ext {
extClass = '.SanaScans'
themePkg = 'iken'
baseUrl = 'https://sanascans.com'
overrideVersionCode = 0
overrideVersionCode = 1
isNsfw = false
}

View File

@ -1,10 +1,83 @@
package eu.kanade.tachiyomi.extension.en.sanascans
import eu.kanade.tachiyomi.multisrc.iken.Iken
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.parseAs
import kotlinx.serialization.Serializable
import okhttp3.Response
import java.text.Normalizer
import java.util.Locale
class SanaScans : Iken(
"Sana Scans",
"en",
"https://sanascans.com",
"https://api.sanascans.com",
)
) {
override fun searchMangaParse(response: Response): MangasPage {
val result = super.searchMangaParse(response)
val normalizedQuery = response.request.url.queryParameter("searchTerm").normalizeForSearch()
if (normalizedQuery.isEmpty()) {
return result
}
val queryTokens = normalizedQuery.split(' ').filter { it.isNotEmpty() }
val filtered = result.mangas.filter { manga ->
val searchableFields = listOf(
manga.title.normalizeForSearch(),
manga.description.normalizeForSearch(),
)
queryTokens.all { token ->
searchableFields.any { field -> field.contains(token) }
}
}
return MangasPage(filtered, result.hasNextPage)
}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
if (document.selectFirst("svg.lucide-lock") != null) {
throw Exception("Unlock chapter in webview")
}
val pages = document.getNextJson("images")
.parseAs<List<SanaPageDto>>()
.sortedBy { it.order ?: Int.MAX_VALUE }
return pages.mapIndexed { idx, p ->
Page(idx, imageUrl = p.url)
}
}
private fun String?.normalizeForSearch(): String {
if (this.isNullOrBlank()) return ""
val base = Normalizer.normalize(this, Normalizer.Form.NFKD)
.lowercase(Locale.ROOT)
.replace(diacriticsRegex, "")
val collapsed = nonAlphanumericRegex.replace(base, " ").trim()
return multiSpaceRegex.replace(collapsed, " ").trim()
}
companion object {
private val diacriticsRegex = Regex("\\p{M}+")
private val nonAlphanumericRegex = Regex("[^a-z0-9]+")
private val multiSpaceRegex = Regex("\\s+")
}
@Serializable
private data class SanaPageDto(
val url: String,
val order: Int? = null,
)
}