[Madara] Decrypt image list for Amuy and Cerise Scans ()

* madata: add chapter image list decryption

* add final newline

* Exit early if there's no chapter protection

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

* Change approach to individual sources

multisrc doesn't like external dependencies

* unbump baseversioncode

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
beerpsi 2023-01-23 10:39:45 +07:00 committed by GitHub
parent 589f46de0b
commit ca789eca79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 3 deletions
multisrc
overrides/madara
src/main/java/eu/kanade/tachiyomi/multisrc/madara

View File

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

View File

@ -1,8 +1,15 @@
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
@ -19,4 +26,44 @@ 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

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

View File

@ -1,8 +1,15 @@
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
@ -19,4 +26,44 @@ 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

@ -358,6 +358,7 @@ abstract class Madara(
if (list.isNotEmpty()) { list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) } } if (list.isNotEmpty()) { list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) } }
} }
} }
else -> {}
} }
} }
return GET(url.toString(), headers) return GET(url.toString(), headers)
@ -488,6 +489,7 @@ abstract class Madara(
taxQueryIdx++ taxQueryIdx++
} }
} }
else -> {}
} }
} }
} }
@ -816,7 +818,7 @@ abstract class Madara(
} }
open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE) open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE)
public fun String.notUpdating(): Boolean { fun String.notUpdating(): Boolean {
return this.contains(updatingRegex).not() return this.contains(updatingRegex).not()
} }

View File

@ -35,7 +35,7 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("Aleatória Scan", "https://aleatoriascan.xyz", "pt-BR", className = "AleatoriaScan"), SingleLang("Aleatória Scan", "https://aleatoriascan.xyz", "pt-BR", className = "AleatoriaScan"),
SingleLang("AllPornComic", "https://allporncomic.com", "en", isNsfw = true), SingleLang("AllPornComic", "https://allporncomic.com", "en", isNsfw = true),
SingleLang("Aln Scans", "https://alnscans.com", "en"), 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("Anikiga", "https://anikiga.com", "tr"),
SingleLang("Anisa Manga", "https://anisamanga.com", "tr"), SingleLang("Anisa Manga", "https://anisamanga.com", "tr"),
SingleLang("Ansh Scans", "https://anshscans.org", "en"), 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("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("Cat300", "https://cat300.com", "th", isNsfw = true, className = "Cat300", overrideVersionCode = 1),
SingleLang("CatOnHeadTranslations", "https://catonhead.com", "en", overrideVersionCode = 2), 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("Chibi Manga", "https://www.cmreader.info", "en", overrideVersionCode = 1),
SingleLang("Clover Manga", "https://clover-manga.com", "tr", overrideVersionCode = 2), SingleLang("Clover Manga", "https://clover-manga.com", "tr", overrideVersionCode = 2),
SingleLang("Coffee Manga", "https://coffeemanga.io", "en", overrideVersionCode = 1), SingleLang("Coffee Manga", "https://coffeemanga.io", "en", overrideVersionCode = 1),