diff --git a/multisrc/overrides/madara/amuy/additional.gradle b/multisrc/overrides/madara/amuy/additional.gradle new file mode 100644 index 000000000..04805cd35 --- /dev/null +++ b/multisrc/overrides/madara/amuy/additional.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation(project(':lib-cryptoaes')) +} diff --git a/multisrc/overrides/madara/amuy/src/Amuy.kt b/multisrc/overrides/madara/amuy/src/Amuy.kt index 5f08b9da5..e74c3c6e9 100644 --- a/multisrc/overrides/madara/amuy/src/Amuy.kt +++ b/multisrc/overrides/madara/amuy/src/Amuy.kt @@ -1,8 +1,15 @@ 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 @@ -19,4 +26,44 @@ class Amuy : Madara( .build() override val useNewChapterEndpoint = true + + override fun pageListParse(document: Document): List { + 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) + } } diff --git a/multisrc/overrides/madara/cerisescans/additional.gradle b/multisrc/overrides/madara/cerisescans/additional.gradle new file mode 100644 index 000000000..04805cd35 --- /dev/null +++ b/multisrc/overrides/madara/cerisescans/additional.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation(project(':lib-cryptoaes')) +} diff --git a/multisrc/overrides/madara/cerisescans/src/CeriseScans.kt b/multisrc/overrides/madara/cerisescans/src/CeriseScans.kt index fa5a88c55..b4cb46355 100644 --- a/multisrc/overrides/madara/cerisescans/src/CeriseScans.kt +++ b/multisrc/overrides/madara/cerisescans/src/CeriseScans.kt @@ -1,8 +1,15 @@ 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 @@ -19,4 +26,44 @@ class CeriseScans : Madara( .build() override val useNewChapterEndpoint = true + + override fun pageListParse(document: Document): List { + 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) + } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt index ed0f82047..d8e14c4ee 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt @@ -358,6 +358,7 @@ abstract class Madara( if (list.isNotEmpty()) { list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) } } } } + else -> {} } } return GET(url.toString(), headers) @@ -488,6 +489,7 @@ abstract class Madara( taxQueryIdx++ } } + else -> {} } } } @@ -816,7 +818,7 @@ abstract class Madara( } open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE) - public fun String.notUpdating(): Boolean { + fun String.notUpdating(): Boolean { return this.contains(updatingRegex).not() } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt index 388128511..95f5e3f33 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt @@ -35,7 +35,7 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("Aleatória Scan", "https://aleatoriascan.xyz", "pt-BR", className = "AleatoriaScan"), SingleLang("AllPornComic", "https://allporncomic.com", "en", isNsfw = true), SingleLang("Aln Scans", "https://alnscans.com", "en"), - SingleLang("Amuy", "https://amuyscans.com", "pt-BR", isNsfw = true), + SingleLang("Amuy", "https://amuyscans.com", "pt-BR", isNsfw = true, overrideVersionCode = 1), SingleLang("Anikiga", "https://anikiga.com", "tr"), SingleLang("Anisa Manga", "https://anisamanga.com", "tr"), SingleLang("Ansh Scans", "https://anshscans.org", "en"), @@ -64,7 +64,7 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("CAT-translator", "https://cats-translator.com/manga", "th", className = "CatTranslator", overrideVersionCode = 2), SingleLang("Cat300", "https://cat300.com", "th", isNsfw = true, className = "Cat300", overrideVersionCode = 1), SingleLang("CatOnHeadTranslations", "https://catonhead.com", "en", overrideVersionCode = 2), - SingleLang("Cerise Scans", "https://cerisescan.com", "pt-BR", overrideVersionCode = 3), + SingleLang("Cerise Scans", "https://cerisescan.com", "pt-BR", overrideVersionCode = 4), SingleLang("Chibi Manga", "https://www.cmreader.info", "en", overrideVersionCode = 1), SingleLang("Clover Manga", "https://clover-manga.com", "tr", overrideVersionCode = 2), SingleLang("Coffee Manga", "https://coffeemanga.io", "en", overrideVersionCode = 1),