[ConstellarScans] changed their key format again (#15076)
* a * bump version * shenanigans beget shenanigans * idk * what's a lookup string * make regex less greedy * fighto!!!!! * fix more shenanigans that emerged while this stuff isn't merged * comments * wrong place to trim * be more specific with exceptions * fix conflicting imports * more generic regex * dont need regex comments * fix docs * fix complaints about shadowing
This commit is contained in:
parent
ca789eca79
commit
731a406861
|
@ -7,6 +7,7 @@ import android.graphics.ColorMatrix
|
||||||
import android.graphics.ColorMatrixColorFilter
|
import android.graphics.ColorMatrixColorFilter
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
|
import android.util.Log
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
@ -23,6 +24,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarscans.com", "en") {
|
class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarscans.com", "en") {
|
||||||
|
@ -64,13 +66,13 @@ class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarsca
|
||||||
val data = client.newCall(req).execute().body!!.use {
|
val data = client.newCall(req).execute().body!!.use {
|
||||||
json.parseToJsonElement(it.string()).jsonArray
|
json.parseToJsonElement(it.string()).jsonArray
|
||||||
}.mapNotNull {
|
}.mapNotNull {
|
||||||
it.jsonObject["user-agent"]?.jsonPrimitive?.content?.takeIf {
|
it.jsonObject["user-agent"]?.jsonPrimitive?.content?.takeIf { ua ->
|
||||||
it.startsWith("Mozilla/5.0") &&
|
ua.startsWith("Mozilla/5.0") &&
|
||||||
(
|
(
|
||||||
it.contains("iPhone") &&
|
ua.contains("iPhone") &&
|
||||||
(it.contains("FxiOS") || it.contains("CriOS")) ||
|
(ua.contains("FxiOS") || ua.contains("CriOS")) ||
|
||||||
it.contains("Android") &&
|
ua.contains("Android") &&
|
||||||
(it.contains("EdgA") || it.contains("Chrome") || it.contains("Firefox"))
|
(ua.contains("EdgA") || ua.contains("Chrome") || ua.contains("Firefox"))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,16 +93,28 @@ class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarsca
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
val pageList = super.pageListParse(document)
|
val tsData = TS_DATA_RE.find(document.select("script").html())?.groupValues?.get(1)
|
||||||
return when {
|
?: return super.pageListParse(document)
|
||||||
document.selectFirst("script:containsData(_code)") != null -> descramblePageUrls(
|
val descrambledData = descrambleString(tsData).trim()
|
||||||
pageList
|
|
||||||
)
|
val match = DESCRAMBLING_KEY_RE.find(descrambledData)?.value
|
||||||
document.selectFirst("script:containsData(ts_reader[_)") != null -> decodeDeviceLimitedChapter(
|
if (match != null) {
|
||||||
document
|
Log.d("constellarscans", "device-limited chapter, key: $match")
|
||||||
)
|
return decodeDeviceLimitedChapter(match)
|
||||||
else -> pageList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val imageListJson =
|
||||||
|
JSON_IMAGE_LIST_REGEX.find(descrambledData)?.groupValues?.get(1).orEmpty()
|
||||||
|
val imageList = try {
|
||||||
|
json.parseToJsonElement(imageListJson).jsonArray
|
||||||
|
} catch (_: IllegalArgumentException) {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
val scriptPages = imageList.mapIndexed { i, jsonEl ->
|
||||||
|
Page(i, imageUrl = jsonEl.jsonPrimitive.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptPages
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageRequest(page: Page): Request = super.imageRequest(page).newBuilder()
|
override fun imageRequest(page: Page): Request = super.imageRequest(page).newBuilder()
|
||||||
|
@ -110,21 +124,15 @@ class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarsca
|
||||||
.header("Sec-Fetch-Site", "same-origin")
|
.header("Sec-Fetch-Site", "same-origin")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun descramblePageUrls(pages: List<Page>): List<Page> {
|
private fun descrambleString(input: String): String =
|
||||||
return pages.map {
|
input.replace(NOT_DIGIT_RE, "")
|
||||||
val lastSegment = it.imageUrl!!.split('/').last()
|
|
||||||
val filename = lastSegment
|
|
||||||
.replace(NOT_DIGIT_RE, "")
|
|
||||||
.chunked(2)
|
.chunked(2)
|
||||||
.joinToString("") { LOOKUP_STRING[it.toInt()].toString() }
|
.joinToString("") { (it.toInt() + 32).toChar().toString() }
|
||||||
Page(it.index, imageUrl = it.imageUrl!!.replace(lastSegment, filename))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decodeDeviceLimitedChapter(document: Document): List<Page> {
|
private fun decodeDeviceLimitedChapter(fullKey: String): List<Page> {
|
||||||
val script = document.selectFirst("script:containsData(ts_reader[_)").data()
|
if (!DESCRAMBLING_KEY_RE.matches(fullKey)) {
|
||||||
val fullKey = DESCRAMBLING_KEY_RE.find(script)?.groupValues?.get(1)
|
throw IllegalArgumentException("Did not receive suitable decryption key. Try opening the chapter again.")
|
||||||
?: throw Exception("Did not receive suitable decryption key. Try opening the chapter again.")
|
}
|
||||||
|
|
||||||
val shiftBy = fullKey.substring(32..33).toInt(16)
|
val shiftBy = fullKey.substring(32..33).toInt(16)
|
||||||
val key = fullKey.substring(0..31) + fullKey.substring(34)
|
val key = fullKey.substring(0..31) + fullKey.substring(34)
|
||||||
|
@ -204,19 +212,21 @@ class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarsca
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DESCRAMBLE = "descramble"
|
const val DESCRAMBLE = "descramble"
|
||||||
|
|
||||||
const val UA_DB_URL =
|
const val UA_DB_URL =
|
||||||
"https://cdn.jsdelivr.net/gh/mimmi20/browscap-helper@30a83c095688f40b9eaca0165a479c661e5a7fbe/tests/0002999.json"
|
"https://cdn.jsdelivr.net/gh/mimmi20/browscap-helper@30a83c095688f40b9eaca0165a479c661e5a7fbe/tests/0002999.json"
|
||||||
|
|
||||||
const val LOOKUP_STRING =
|
|
||||||
" !\"#${'$'}%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}"
|
|
||||||
const val LOOKUP_STRING_ALNUM =
|
const val LOOKUP_STRING_ALNUM =
|
||||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
val NOT_DIGIT_RE = Regex("""\D""")
|
val NOT_DIGIT_RE = Regex("""\D""")
|
||||||
|
|
||||||
|
// We know that `ts_reader.run` accepts a JSON object, which contains `{"`, or in Constellar's
|
||||||
|
// encoding scheme, 91 02.
|
||||||
|
val TS_DATA_RE = Regex(
|
||||||
|
"""['"]([\da-z]*?9[a-z]*?1[a-z]*?0[a-z]*?2[\da-z]+?)['"]""",
|
||||||
|
RegexOption.IGNORE_CASE
|
||||||
|
)
|
||||||
|
|
||||||
// The decoding algorithm looks for a hex number in 32..33, so we write our regex accordingly
|
// The decoding algorithm looks for a hex number in 32..33, so we write our regex accordingly
|
||||||
val DESCRAMBLING_KEY_RE =
|
val DESCRAMBLING_KEY_RE =
|
||||||
Regex("""'([\da-z]{32}[\da-f]{2}[\da-z]+)'""", RegexOption.IGNORE_CASE)
|
Regex("""[\da-z]{32}[\da-f]{2}[\da-z]+""", RegexOption.IGNORE_CASE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ class MangaThemesiaGenerator : ThemeSourceGenerator {
|
||||||
SingleLang("Azure Scans", "https://azuremanga.com", "en", overrideVersionCode = 1),
|
SingleLang("Azure Scans", "https://azuremanga.com", "en", overrideVersionCode = 1),
|
||||||
SingleLang("Boosei", "https://boosei.net", "id", overrideVersionCode = 2),
|
SingleLang("Boosei", "https://boosei.net", "id", overrideVersionCode = 2),
|
||||||
SingleLang("Clayrer", "https://clayrer.net", "es"),
|
SingleLang("Clayrer", "https://clayrer.net", "es"),
|
||||||
SingleLang("Constellar Scans", "https://constellarscans.com", "en", isNsfw = true, overrideVersionCode = 5),
|
SingleLang("Constellar Scans", "https://constellarscans.com", "en", isNsfw = true, overrideVersionCode = 6),
|
||||||
SingleLang("Cosmic Scans", "https://cosmicscans.com", "en", overrideVersionCode = 1),
|
SingleLang("Cosmic Scans", "https://cosmicscans.com", "en", overrideVersionCode = 1),
|
||||||
SingleLang("Diskus Scan", "https://diskusscan.com", "pt-BR", overrideVersionCode = 7),
|
SingleLang("Diskus Scan", "https://diskusscan.com", "pt-BR", overrideVersionCode = 7),
|
||||||
SingleLang("Dojing.net", "https://dojing.net", "id", isNsfw = true, className = "DojingNet"),
|
SingleLang("Dojing.net", "https://dojing.net", "id", isNsfw = true, className = "DojingNet"),
|
||||||
|
|
Loading…
Reference in New Issue