[JapScan] Fix selectors, make ZJS more resistant (#15025)
* [JapScan] Fix selectors, make ZJS more resistant Co-authored-by: Vetle Ledaal <13540478+vetleledaal@users.noreply.github.com> * bump version Co-authored-by: Vetle Ledaal <13540478+vetleledaal@users.noreply.github.com>
This commit is contained in:
parent
4600453724
commit
2cd0dcd55f
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'Japscan'
|
extName = 'Japscan'
|
||||||
pkgNameSuffix = 'fr.japscan'
|
pkgNameSuffix = 'fr.japscan'
|
||||||
extClass = '.Japscan'
|
extClass = '.Japscan'
|
||||||
extVersionCode = 36
|
extVersionCode = 37
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -87,7 +87,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector(): String? = null
|
override fun popularMangaNextPageSelector(): String? = null
|
||||||
|
|
||||||
override fun popularMangaSelector() = "#top_mangas_week li > span"
|
override fun popularMangaSelector() = "#top_mangas_week li"
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element): SManga {
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
|
@ -117,7 +117,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector(): String? = null
|
override fun latestUpdatesNextPageSelector(): String? = null
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = "#chapters > div > h3.text-truncate"
|
override fun latestUpdatesSelector() = "#chapters h3.text-truncate, #chapters_list h3.text-truncate"
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
|
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
|
|
||||||
|
@ -207,12 +207,13 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
val infoElement = document.select("div#main > .card > .card-body").first()
|
val infoElement = document.selectFirst("#main .card-body")
|
||||||
|
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
manga.thumbnail_url = "$baseUrl/${infoElement.select(".d-flex > div.m-2:eq(0) > img").attr("src")}"
|
manga.thumbnail_url = infoElement.select("img").attr("abs:src")
|
||||||
|
|
||||||
infoElement.select(".d-flex > div.m-2:eq(1) > p.mb-2").forEachIndexed { _, el ->
|
val infoRows = infoElement.select(".row, .d-flex")
|
||||||
|
infoRows.select("p").forEach { el ->
|
||||||
when (el.select("span").text().trim()) {
|
when (el.select("span").text().trim()) {
|
||||||
"Auteur(s):" -> manga.author = el.text().replace("Auteur(s):", "").trim()
|
"Auteur(s):" -> manga.author = el.text().replace("Auteur(s):", "").trim()
|
||||||
"Artiste(s):" -> manga.artist = el.text().replace("Artiste(s):", "").trim()
|
"Artiste(s):" -> manga.artist = el.text().replace("Artiste(s):", "").trim()
|
||||||
|
@ -222,7 +223,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
manga.description = infoElement.select("> p").text().orEmpty()
|
manga.description = infoElement.select("div:contains(Synopsis) + p").text().orEmpty()
|
||||||
|
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
@ -257,6 +258,10 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val decodingStringsRe: Regex = Regex("""'([\dA-Z]{62})'""", RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
private val sortedLookupString: List<Char> = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray().toList()
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
/*
|
/*
|
||||||
JapScan stores chapter metadata in a `#data` element, and in the `data-data` attribute.
|
JapScan stores chapter metadata in a `#data` element, and in the `data-data` attribute.
|
||||||
|
@ -273,16 +278,11 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
|
||||||
Log.d("japscan", "ZJS at $zjsurl")
|
Log.d("japscan", "ZJS at $zjsurl")
|
||||||
val zjs = client.newCall(GET(baseUrl + zjsurl, headers)).execute().body!!.string()
|
val zjs = client.newCall(GET(baseUrl + zjsurl, headers)).execute().body!!.string()
|
||||||
|
|
||||||
val stringLookupTables = zjs
|
val stringLookupTables = decodingStringsRe.findAll(zjs).mapNotNull {
|
||||||
.substringAfter("= [")
|
it.groupValues[1].takeIf {
|
||||||
.substringBefore("];")
|
it.toCharArray().sorted() == sortedLookupString
|
||||||
.split(",")
|
|
||||||
.mapNotNull {
|
|
||||||
it.trim().removeSurrounding("'").takeIf { unquoted ->
|
|
||||||
unquoted.length == 62 &&
|
|
||||||
unquoted.toCharArray().sorted().joinToString("") == "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.toList()
|
||||||
|
|
||||||
if (stringLookupTables.size != 2) {
|
if (stringLookupTables.size != 2) {
|
||||||
throw Exception("Attendait 2 chaînes de recherche dans ZJS, a trouvé ${stringLookupTables.size}")
|
throw Exception("Attendait 2 chaînes de recherche dans ZJS, a trouvé ${stringLookupTables.size}")
|
||||||
|
@ -297,6 +297,12 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
|
||||||
val lookupTable = stringLookupTables[i].zip(stringLookupTables[otherIndice]).toMap()
|
val lookupTable = stringLookupTables[i].zip(stringLookupTables[otherIndice]).toMap()
|
||||||
try {
|
try {
|
||||||
val unscrambledData = scrambledData.map { lookupTable[it] ?: it }.joinToString("")
|
val unscrambledData = scrambledData.map { lookupTable[it] ?: it }.joinToString("")
|
||||||
|
if (!unscrambledData.startsWith("ey")) {
|
||||||
|
// `ey` is the Base64 representation of a curly bracket. Since we're expecting a
|
||||||
|
// JSON object, we're counting this attempt as failed if it doesn't start with a
|
||||||
|
// curly bracket.
|
||||||
|
continue
|
||||||
|
}
|
||||||
val decoded = Base64.decode(unscrambledData, Base64.DEFAULT).toString(Charsets.UTF_8)
|
val decoded = Base64.decode(unscrambledData, Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||||
|
|
||||||
val data = json.parseToJsonElement(decoded).jsonObject
|
val data = json.parseToJsonElement(decoded).jsonObject
|
||||||
|
|
Loading…
Reference in New Issue