[poseidonscans] - Fixes broken search due to site changes. (#10740)

* Fix search and add pagination

* Fix URL decoding in PoseidonScans extension
This commit is contained in:
Aurel 2025-09-29 14:41:56 +02:00 committed by Draff
parent 8c74ea91cd
commit ac069dd2ec
Signed by: Draff
GPG Key ID: E8A89F3211677653
2 changed files with 24 additions and 36 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Poseidon Scans' extName = 'Poseidon Scans'
extClass = '.PoseidonScans' extClass = '.PoseidonScans'
extVersionCode = 46 extVersionCode = 47
isNsfw = false isNsfw = false
} }

View File

@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.parseAs import keiyoushi.utils.parseAs
import keiyoushi.utils.tryParse import keiyoushi.utils.tryParse
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -20,6 +19,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import java.net.URLDecoder
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
@ -481,63 +481,51 @@ class PoseidonScans : HttpSource() {
return GET(page.imageUrl!!, imageHeaders) return GET(page.imageUrl!!, imageHeaders)
} }
/**
* Tachiyomi's `page` parameter is not directly used as /series does not paginate via URL params.
* We fetch all series and filter client-side based on the query.
* The query is passed as `app_query` URL parameter for retrieval in `searchMangaParse`.
*/
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply { val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment("series") addPathSegment("series")
if (query.isNotBlank()) { if (query.isNotBlank()) {
fragment(query) addQueryParameter("search", query)
}
if (page > 1) {
addQueryParameter("page", page.toString())
} }
}.build() }.build()
return GET(url, headers) return GET(url, headers)
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val requestUrl = response.request.url
val searchQuery = requestUrl.fragment?.takeIf { it.isNotBlank() } ?: ""
val pageDataJson = extractNextJsPageData(document) val mangas = document.select("div.grid a.block.group").mapNotNull { element ->
?: return MangasPage(emptyList(), false)
val mangaListJsonArray = pageDataJson["mangas"]?.jsonArray
?: pageDataJson["series"]?.jsonArray
?: pageDataJson["initialData"]?.jsonObject?.get("mangas")?.jsonArray
?: pageDataJson["initialData"]?.jsonObject?.get("series")?.jsonArray
?: return MangasPage(emptyList(), false)
val allMangas = mangaListJsonArray.mapNotNull { mangaElement ->
try { try {
val mangaObject = mangaElement.jsonObject val url = element.attr("href").takeIf { it.isNotBlank() } ?: return@mapNotNull null
val title = mangaObject["title"]?.jsonPrimitive?.content ?: return@mapNotNull null val title = element.selectFirst("h2")?.text()?.takeIf { it.isNotBlank() } ?: return@mapNotNull null
val slug = mangaObject["slug"]?.jsonPrimitive?.content ?: return@mapNotNull null
val cover = mangaObject["coverImage"]?.jsonPrimitive?.content val thumbnailUrlPath = element.selectFirst("img[alt]")
?.attr("srcset")
?.substringBefore(" ")
?.let {
URLDecoder.decode(it, "UTF-8")
.substringAfter("url=")
.substringBefore("&")
}
SManga.create().apply { SManga.create().apply {
this.setUrlWithoutDomain(url)
this.title = title this.title = title
setUrlWithoutDomain("/serie/$slug") this.thumbnail_url = thumbnailUrlPath?.takeIf { it.isNotBlank() }?.toApiCoverUrl()
this.thumbnail_url = cover?.takeIf { it.isNotBlank() }?.toApiCoverUrl()
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("PoseidonScans", "Error parsing manga from HTML element", e)
null null
} }
} }
val filteredMangas = if (searchQuery.isNotBlank()) { val hasNextPage = document.select("nav[aria-label=Pagination] a:contains(Suivant)").isNotEmpty()
allMangas.filter { manga ->
manga.title.contains(searchQuery, ignoreCase = true)
}
} else {
allMangas
}
// /series loads all items at once (client-side 'load more'), so no next page from this specific request. return MangasPage(mangas, hasNextPage)
val hasNextPage = false
return MangasPage(filteredMangas, hasNextPage)
} }
override fun imageUrlParse(response: Response): String { throw UnsupportedOperationException("Not used.") } override fun imageUrlParse(response: Response): String { throw UnsupportedOperationException("Not used.") }