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 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.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.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -21,44 +14,4 @@ class AiYuManga : Madara(
override val chapterUrlSuffix = "" override val chapterUrlSuffix = ""
override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Status) > div.summary-content" 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 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.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit 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 okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -26,44 +19,4 @@ class Amuy : Madara(
.build() .build()
override val useNewChapterEndpoint = true 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 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.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit 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 okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -26,44 +19,4 @@ class CeriseScans : Madara(
.build() .build()
override val useNewChapterEndpoint = true 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 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.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga 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 org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -59,44 +52,4 @@ abstract class LeviatanScans(
chapter.url = baseUrl + split.slice(split.indexOf("manga") until split.size).joinToString("/", "/") chapter.url = baseUrl + split.slice(split.indexOf("manga") until split.size).joinToString("/", "/")
return chapter 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 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.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit 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.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaCrab : Madara( class MangaCrab : Madara(
"Manga Crab", "Manga Crab",
@ -20,53 +11,10 @@ class MangaCrab : Madara(
"es", "es",
SimpleDateFormat("dd/MM/yyyy", Locale("es")), SimpleDateFormat("dd/MM/yyyy", Locale("es")),
) { ) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client = super.client.newBuilder()
.addInterceptor(uaIntercept)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.rateLimit(1, 2) .rateLimit(1, 2)
.build() .build()
override fun chapterListSelector() = "div.listing-chapters_wrap > ul > li" override fun chapterListSelector() = "div.listing-chapters_wrap > ul > li"
override val mangaDetailsSelectorDescription = "div.c-page__content div.contenedor" 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 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.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") { class ManhuaSY : Madara("Manhua SY", "https://www.manhuasy.com", "en") {
override val mangaSubString = "manhua" 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 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.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.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -26,51 +18,4 @@ class ManhwaLatino : Madara(
override val chapterUrlSelector = "a:eq(1)" override val chapterUrlSelector = "a:eq(1)"
override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Estado del comic) > div.summary-content" 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 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.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit 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.SChapter
import eu.kanade.tachiyomi.source.model.SManga 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.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale 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 { private fun String.removeBadPath(expectedFirstPath: String): String {
val fullUrl = if (contains(baseUrl)) this else (baseUrl + this) val fullUrl = if (contains(baseUrl)) this else (baseUrl + this)
val url = fullUrl.toHttpUrl() val url = fullUrl.toHttpUrl()
@ -94,8 +53,4 @@ class SinensisScan : Madara(
return url.toString() 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.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Base64
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservable import eu.kanade.tachiyomi.network.asObservable
@ -21,6 +23,7 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
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.CacheControl 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 // add alternative name to manga description
document.select(altNameSelector).firstOrNull()?.ownText()?.let { 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 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> { override fun pageListParse(document: Document): List<Page> {
countViews(document) countViews(document)
return document.select(pageListParseSelector).mapIndexed { index, element -> val chapterProtector = document.selectFirst(chapterProtectorSelector)
Page( ?: return document.select(pageListParseSelector).mapIndexed { index, element ->
index, val imageUrl = element.selectFirst("img")?.let { imageFromElement(it) }
document.location(), Page(index, document.location(), imageUrl)
element.select("img").first()?.let { }
it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src") 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 { companion object {
const val TITLE_RANDOM_UA = "Use Random Latest User-Agent" const val TITLE_RANDOM_UA = "Use Random Latest User-Agent"
const val PREF_KEY_RANDOM_UA = "pref_key_random_ua" 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 DOESNOT_SUPPORT_STRING = "This extension doesn't support User-Agent options."
const val URL_SEARCH_PREFIX = "slug:" const val URL_SEARCH_PREFIX = "slug:"
private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json" 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 themeClass = "Madara"
override val baseVersionCode: Int = 29 override val baseVersionCode: Int = 30
override val sources = listOf( override val sources = listOf(
MultiLang("Atlantis Scan", "https://atlantisscan.com", listOf("es", "pt-BR"), isNsfw = true), MultiLang("Atlantis Scan", "https://atlantisscan.com", listOf("es", "pt-BR"), isNsfw = true),