From 24d5d8920bfa498e29b98252d570a7da9168a70c Mon Sep 17 00:00:00 2001 From: Hemal Kothapalli <44635763+SuperMario22922@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:17:52 +1100 Subject: [PATCH] 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 --- src/en/sanascans/build.gradle | 2 +- .../extension/en/sanascans/SanaScans.kt | 75 ++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/en/sanascans/build.gradle b/src/en/sanascans/build.gradle index b50fb27e7..2e9a4358f 100644 --- a/src/en/sanascans/build.gradle +++ b/src/en/sanascans/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.SanaScans' themePkg = 'iken' baseUrl = 'https://sanascans.com' - overrideVersionCode = 0 + overrideVersionCode = 1 isNsfw = false } diff --git a/src/en/sanascans/src/eu/kanade/tachiyomi/extension/en/sanascans/SanaScans.kt b/src/en/sanascans/src/eu/kanade/tachiyomi/extension/en/sanascans/SanaScans.kt index 2398e6e55..8ec7b2f11 100644 --- a/src/en/sanascans/src/eu/kanade/tachiyomi/extension/en/sanascans/SanaScans.kt +++ b/src/en/sanascans/src/eu/kanade/tachiyomi/extension/en/sanascans/SanaScans.kt @@ -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 { + val document = response.asJsoup() + + if (document.selectFirst("svg.lucide-lock") != null) { + throw Exception("Unlock chapter in webview") + } + + val pages = document.getNextJson("images") + .parseAs>() + .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, + ) +}