Move `wpchapterprotector` logic to Madara base class (#17006)

* move wpchapterprotector logic to Madara base class

* selectFirst
This commit is contained in:
AwkwardPeak7 2023-07-06 03:30:02 +05:00 committed by GitHub
parent deb7903b1d
commit 93bc84108c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 55 additions and 418 deletions

View File

@ -1,13 +1,6 @@
package eu.kanade.tachiyomi.extension.es.aiyumanga
import android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.model.Page
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
@ -21,44 +14,4 @@ class AiYuManga : Madara(
override val chapterUrlSuffix = ""
override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Status) > div.summary-content"
override fun pageListParse(document: Document): List<Page> {
val chapterProtector = document.getElementById("chapter-protector-data")?.html()
?: return super.pageListParse(document)
val password = chapterProtector
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = json.parseToJsonElement(
chapterProtector
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/"),
).jsonObject
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
return imgArray.mapIndexed { idx, it ->
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -1,3 +0,0 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}

View File

@ -1,15 +1,8 @@
package eu.kanade.tachiyomi.extension.pt.amuy
import android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Page
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
@ -26,44 +19,4 @@ class Amuy : Madara(
.build()
override val useNewChapterEndpoint = true
override fun pageListParse(document: Document): List<Page> {
val chapterProtector = document.getElementById("chapter-protector-data")?.html()
?: return super.pageListParse(document)
val password = chapterProtector
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = json.parseToJsonElement(
chapterProtector
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/"),
).jsonObject
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
return imgArray.mapIndexed { idx, it ->
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -1,3 +0,0 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}

View File

@ -1,15 +1,8 @@
package eu.kanade.tachiyomi.extension.pt.cerisescans
import android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Page
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
@ -26,44 +19,4 @@ class CeriseScans : Madara(
.build()
override val useNewChapterEndpoint = true
override fun pageListParse(document: Document): List<Page> {
val chapterProtector = document.getElementById("chapter-protector-data")?.html()
?: return super.pageListParse(document)
val password = chapterProtector
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = json.parseToJsonElement(
chapterProtector
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/"),
).jsonObject
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
return imgArray.mapIndexed { idx, it ->
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -1,3 +0,0 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}

View File

@ -1,15 +1,8 @@
package eu.kanade.tachiyomi.extension.all.leviatanscans
import android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
@ -59,44 +52,4 @@ abstract class LeviatanScans(
chapter.url = baseUrl + split.slice(split.indexOf("manga") until split.size).joinToString("/", "/")
return chapter
}
override fun pageListParse(document: Document): List<Page> {
val chapterProtector = document.getElementById("chapter-protector-data")?.html()
?: return super.pageListParse(document)
val password = chapterProtector
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = json.parseToJsonElement(
chapterProtector
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/"),
).jsonObject
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
return imgArray.mapIndexed { idx, it ->
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -1,3 +0,0 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}

View File

@ -1,18 +1,9 @@
package eu.kanade.tachiyomi.extension.es.mangacrab
import android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Page
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaCrab : Madara(
"Manga Crab",
@ -20,53 +11,10 @@ class MangaCrab : Madara(
"es",
SimpleDateFormat("dd/MM/yyyy", Locale("es")),
) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(uaIntercept)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client = super.client.newBuilder()
.rateLimit(1, 2)
.build()
override fun chapterListSelector() = "div.listing-chapters_wrap > ul > li"
override val mangaDetailsSelectorDescription = "div.c-page__content div.contenedor"
override fun pageListParse(document: Document): List<Page> {
val chapterProtector = document.getElementById("chapter-protector-data")?.html()
?: return super.pageListParse(document)
val password = chapterProtector
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = json.parseToJsonElement(
chapterProtector
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/"),
).jsonObject
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
return imgArray.mapIndexed { idx, it ->
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -1,3 +0,0 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}

View File

@ -1,53 +1,7 @@
package eu.kanade.tachiyomi.extension.en.manhuasy
import android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.model.Page
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.jsoup.nodes.Document
class ManhuaSY : Madara("Manhua SY", "https://www.manhuasy.com", "en") {
override val mangaSubString = "manhua"
override fun pageListParse(document: Document): List<Page> {
val chapterProtector = document.getElementById("chapter-protector-data")?.html()
?: return super.pageListParse(document)
val password = chapterProtector
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = json.parseToJsonElement(
chapterProtector
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/"),
).jsonObject
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
return imgArray.mapIndexed { idx, it ->
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -1,3 +0,0 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}

View File

@ -1,14 +1,6 @@
package eu.kanade.tachiyomi.extension.es.manhwalatino
import android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Page
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
@ -26,51 +18,4 @@ class ManhwaLatino : Madara(
override val chapterUrlSelector = "a:eq(1)"
override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Estado del comic) > div.summary-content"
override fun pageListParse(document: Document): List<Page> {
val script = document.selectFirst("div.reading-content script")
?: return super.pageListParse(document)
val scriptData: String = if (script.hasAttr("src")) {
client.newCall(GET(script.attr("src"), headers)).execute().body.string()
} else {
script.data()
}
val password = scriptData
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = json.parseToJsonElement(
scriptData
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/"),
).jsonObject
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
return imgArray.mapIndexed { idx, it ->
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -1,3 +0,0 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}

View File

@ -1,18 +1,11 @@
package eu.kanade.tachiyomi.extension.pt.sinensis
import android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.Locale
@ -50,40 +43,6 @@ class SinensisScan : Madara(
}
}
override fun pageListParse(document: Document): List<Page> {
val chapterProtector = document.selectFirst("script#chapter-protector-data")?.data()
?: return super.pageListParse(document)
val password = chapterProtector
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = chapterProtector
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/")
.let { json.decodeFromString<Map<String, String>>(it) }
val unsaltedCipherText = Base64.decode(chapterData["ct"]!!, Base64.DEFAULT)
val salt = chapterData["s"]!!.decodeHex()
val cipherText = SALTED + salt + unsaltedCipherText
val rawImageArray = CryptoAES.decrypt(Base64.encodeToString(cipherText, Base64.DEFAULT), password)
val imageArrayString = json.parseToJsonElement(rawImageArray).jsonPrimitive.content
val imageArray = json.parseToJsonElement(imageArrayString).jsonArray
return imageArray.mapIndexed { i, jsonElement ->
Page(i, document.location(), jsonElement.jsonPrimitive.content)
}
}
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
private fun String.removeBadPath(expectedFirstPath: String): String {
val fullUrl = if (contains(baseUrl)) this else (baseUrl + this)
val url = fullUrl.toHttpUrl()
@ -94,8 +53,4 @@ class SinensisScan : Madara(
return url.toString()
}
companion object {
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -2,11 +2,13 @@ package eu.kanade.tachiyomi.multisrc.madara
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import android.util.Log
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservable
@ -21,6 +23,7 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.CacheControl
@ -630,7 +633,17 @@ abstract class Madara(
}
}
manga.genre = genres.toList().joinToString(", ") { it.capitalize(Locale.ROOT) }
manga.genre = genres.toList().joinToString(", ") { genre ->
genre.replaceFirstChar {
if (it.isLowerCase()) {
it.titlecase(
Locale.ROOT,
)
} else {
it.toString()
}
}
}
// add alternative name to manga description
document.select(altNameSelector).firstOrNull()?.ownText()?.let {
@ -868,17 +881,37 @@ abstract class Madara(
open val pageListParseSelector = "div.page-break, li.blocks-gallery-item, .reading-content .text-left:not(:has(.blocks-gallery-item)) img"
open val chapterProtectorSelector = "#chapter-protector-data"
override fun pageListParse(document: Document): List<Page> {
countViews(document)
return document.select(pageListParseSelector).mapIndexed { index, element ->
Page(
index,
document.location(),
element.select("img").first()?.let {
it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src")
},
)
val chapterProtector = document.selectFirst(chapterProtectorSelector)
?: return document.select(pageListParseSelector).mapIndexed { index, element ->
val imageUrl = element.selectFirst("img")?.let { imageFromElement(it) }
Page(index, document.location(), imageUrl)
}
val chapterProtectorHtml = chapterProtector.html()
val password = chapterProtectorHtml
.substringAfter("wpmangaprotectornonce='")
.substringBefore("';")
val chapterData = json.parseToJsonElement(
chapterProtectorHtml
.substringAfter("chapter_data='")
.substringBefore("';")
.replace("\\/", "/"),
).jsonObject
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
return imgArray.mapIndexed { idx, it ->
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
@ -1035,6 +1068,15 @@ abstract class Madara(
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
const val TITLE_RANDOM_UA = "Use Random Latest User-Agent"
const val PREF_KEY_RANDOM_UA = "pref_key_random_ua"
@ -1048,6 +1090,8 @@ abstract class Madara(
const val DOESNOT_SUPPORT_STRING = "This extension doesn't support User-Agent options."
const val URL_SEARCH_PREFIX = "slug:"
private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json"
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -10,7 +10,7 @@ class MadaraGenerator : ThemeSourceGenerator {
override val themeClass = "Madara"
override val baseVersionCode: Int = 29
override val baseVersionCode: Int = 30
override val sources = listOf(
MultiLang("Atlantis Scan", "https://atlantisscan.com", listOf("es", "pt-BR"), isNsfw = true),