Japscan: Update url & fix other stuff (#10169)

Update url & other stuff
This commit is contained in:
Secozzi 2025-08-18 18:56:51 +02:00 committed by Draff
parent 88407d64af
commit 262d246f31
Signed by: Draff
GPG Key ID: E8A89F3211677653
2 changed files with 30 additions and 23 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Japscan' extName = 'Japscan'
extClass = '.Japscan' extClass = '.Japscan'
extVersionCode = 49 extVersionCode = 50
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.getPreferencesLazy import keiyoushi.utils.getPreferencesLazy
import keiyoushi.utils.parseAs
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
@ -48,7 +49,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
override val name = "Japscan" override val name = "Japscan"
override val baseUrl = "https://www.japscan.lol" override val baseUrl = "https://www.japscan.si"
override val lang = "fr" override val lang = "fr"
@ -91,7 +92,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
element.select("a").first()!!.let { element.select("a").first()!!.let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
manga.thumbnail_url = "$baseUrl/imgs/${it.attr("href").replace(Regex("/$"),".jpg").replace("manga","mangas")}".lowercase(Locale.ROOT) manga.thumbnail_url = it.selectFirst("img")?.attr("abs:data-src")
} }
return manga return manga
} }
@ -131,7 +132,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
.add("X-Requested-With", "XMLHttpRequest") .add("X-Requested-With", "XMLHttpRequest")
.build() .build()
return POST("$baseUrl/live-search/", searchHeaders, formBody) return POST("$baseUrl/ls/", searchHeaders, formBody)
} }
} }
@ -140,7 +141,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
override fun searchMangaSelector(): String = "div.card div.p-2" override fun searchMangaSelector(): String = "div.card div.p-2"
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
if (response.request.url.pathSegments.first() == "live-search") { if (response.request.url.pathSegments.first() == "ls") {
val jsonResult = json.parseToJsonElement(response.body.string()).jsonArray val jsonResult = json.parseToJsonElement(response.body.string()).jsonArray
val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) } val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) }
@ -178,10 +179,9 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.selectFirst("#main .card-body")!! val infoElement = document.selectFirst("#main .card-body")!!
val manga = SManga.create() val manga = SManga.create()
val path = document.location().replaceFirst("$baseUrl/", "")
manga.thumbnail_url = "$baseUrl/imgs/${path.replace(Regex("/$"),".jpg").replace("manga","mangas")}".lowercase(Locale.ROOT) manga.thumbnail_url = infoElement.selectFirst("img")?.attr("abs:src")
val infoRows = infoElement.select(".row, .d-flex") val infoRows = infoElement.select(".row, .d-flex")
infoRows.select("p").forEach { el -> infoRows.select("p").forEach { el ->
@ -194,7 +194,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
} }
} }
} }
manga.description = infoElement.select("div:contains(Synopsis) + p").text().orEmpty() manga.description = infoElement.selectFirst("#synopsis")?.ownText().orEmpty()
return manga return manga
} }
@ -205,7 +205,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListSelector() = "#chapters_list > div.collapse > div.chapters_list" + override fun chapterListSelector() = "#list_chapters > div.collapse > div.list_chapters" +
if (chapterListPref() == "hide") { ":not(:has(.badge:contains(SPOILER),.badge:contains(RAW),.badge:contains(VUS)))" } else { "" } if (chapterListPref() == "hide") { ":not(:has(.badge:contains(SPOILER),.badge:contains(RAW),.badge:contains(VUS)))" } else { "" }
// JapScan sometimes uploads some "spoiler preview" chapters, containing 2 or 3 untranslated pictures taken from a raw. Sometimes they also upload full RAWs/US versions and replace them with a translation as soon as available. // JapScan sometimes uploads some "spoiler preview" chapters, containing 2 or 3 untranslated pictures taken from a raw. Sometimes they also upload full RAWs/US versions and replace them with a translation as soon as available.
// Those have a span.badge "SPOILER" or "RAW". The additional pseudo selector makes sure to exclude these from the chapter list. // Those have a span.badge "SPOILER" or "RAW". The additional pseudo selector makes sure to exclude these from the chapter list.
@ -231,12 +231,22 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
document.body().prepend( document.body().prepend(
""" """
<script> <script>
const _atob = atob; Object.defineProperty(Object.prototype, 'imagesLink', {
atob = function(arg) { set: function(value) {
let data = _atob(arg) window.$interfaceName.passPayload(JSON.stringify(value));
window.$interfaceName.passPayload(data); Object.defineProperty(this, '_imagesLink', {
return data; value: value,
}; writable: true,
enumerable: false,
configurable: true
});
},
get: function() {
return this._imagesLink;
},
enumerable: false,
configurable: true
});
</script> </script>
""".trimIndent(), """.trimIndent(),
) )
@ -281,11 +291,11 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
throw Exception("Timed out decrypting image links") throw Exception("Timed out decrypting image links")
} }
val baseUrlHost = baseUrl.toHttpUrl().host val baseUrlHost = baseUrl.toHttpUrl().host.substringAfter("www.")
return jsInterface return jsInterface
.images .images
.filterNot { it.toHttpUrl().host == baseUrlHost } // Pages not served through their CDN are probably ads .filter { it.toHttpUrl().host.endsWith(baseUrlHost) } // Pages not served through their CDN are probably ads
.mapIndexed { i, url -> .mapIndexed { i, url ->
Page(i, imageUrl = url) Page(i, imageUrl = url)
} }
@ -323,8 +333,6 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
} }
internal class JsInterface(private val latch: CountDownLatch) { internal class JsInterface(private val latch: CountDownLatch) {
private val json: Json by injectLazy()
var images: List<String> = listOf() var images: List<String> = listOf()
private set private set
@ -332,9 +340,8 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
@Suppress("UNUSED") @Suppress("UNUSED")
fun passPayload(rawData: String) { fun passPayload(rawData: String) {
try { try {
val data = json.parseToJsonElement(rawData).jsonObject images = rawData.parseAs<List<String>>()
.map { "$it?y=1" }
images = data["imagesLink"]!!.jsonArray.map { it.jsonPrimitive.content }
latch.countDown() latch.countDown()
} catch (_: Exception) { } catch (_: Exception) {
return return