diff --git a/multisrc/overrides/madara/dragontea/src/DragonTea.kt b/multisrc/overrides/madara/dragontea/src/DragonTea.kt index 84a67d9c9..fb784bb81 100644 --- a/multisrc/overrides/madara/dragontea/src/DragonTea.kt +++ b/multisrc/overrides/madara/dragontea/src/DragonTea.kt @@ -1,24 +1,15 @@ package eu.kanade.tachiyomi.extension.en.dragontea -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect 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.JsonElement import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient -import okhttp3.Protocol -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody import org.jsoup.nodes.Document -import java.io.ByteArrayOutputStream import java.text.SimpleDateFormat import java.util.Locale @@ -29,7 +20,6 @@ class DragonTea : Madara( dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US), ) { override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(::begonepeconIntercept) .rateLimit(1) .build() @@ -45,109 +35,46 @@ class DragonTea : Madara( } } - private val begonepeconSelector: String = "div.begonepecon" - - private val peconholderSelector: String = "div.peconholder" + private val pageIndexRegex = Regex("""image-(\d+)[a-z]+""", RegexOption.IGNORE_CASE) override fun pageListParse(document: Document): List { - countViews(document) + val dataId = document.selectFirst(".entry-header.header")?.attr("data-id")?.toInt() + ?: return super.pageListParse(document) + val elements = document.select(".reading-content .page-break img") + val pageCount = elements.size - val hasSplitImages = document - .select(begonepeconSelector) - .firstOrNull() != null - - if (!hasSplitImages) { - return document.select(pageListParseSelector).mapIndexed { index, element -> - val imageUrl = element.selectFirst("img")?.let { - val src = when { - it.hasAttr("data-src") -> it.attr("data-src") - it.hasAttr("data-lazy-src") -> it.attr("data-lazy-src") - it.hasAttr("srcset") -> it.attr("srcset").substringBefore(" ") - else -> it.attr("src") - }.trim() - - if (!src.startsWith("{\"")) { - return@let imageFromElement(it) - } - - val srcData = json.parseToJsonElement(src).jsonObject - - val unsaltedCiphertext = Base64.decode(srcData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT) - val salt = srcData["s"]!!.jsonPrimitive.content.decodeHex() - val ciphertext = SALTED + salt + unsaltedCiphertext - - val plaintext = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), PASSWORD) - json.parseToJsonElement(plaintext).jsonPrimitive.content - } - - Page(index, document.location(), imageUrl) - } + val idKey = "8" + ((dataId + 1306) * 3 - pageCount).toString() + elements.forEach { + val decryptedId = decryptAesJson(it.attr("id"), idKey).jsonPrimitive.content + it.attr("id", decryptedId) } - return document.select("div.page-break, li.blocks-gallery-item, $begonepeconSelector") - .mapIndexed { index, element -> - val imageUrl = if (element.select(peconholderSelector).firstOrNull() == null) { - element.select("img").first()?.let { it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src") } - } else { - element.select("img").joinToString("|") { it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src") } + BEGONEPECON_SUFFIX - } - Page(index, document.location(), imageUrl) - } + val orderedElements = elements.sortedBy { + pageIndexRegex.find(it.attr("id"))?.groupValues?.get(1)?.toInt() ?: 0 + } + val dtaKey = "13" + orderedElements.joinToString("") { it.attr("id").takeLast(1) } + (((dataId + 88) * 2) - pageCount - 4).toString() + + val srcKey = (dataId + 20).toString() + orderedElements.joinToString("") { + decryptAesJson(it.attr("dta"), dtaKey).jsonPrimitive.content.takeLast(2) + } + (pageCount * 2).toString() + + return orderedElements.mapIndexed { i, element -> + val src = decryptAesJson(element.attr("data-src"), srcKey).jsonPrimitive.content + Page(i, document.location(), src) + } } - private fun begonepeconIntercept(chain: Interceptor.Chain): Response { - if (!chain.request().url.toString().endsWith(BEGONEPECON_SUFFIX)) { - return chain.proceed(chain.request()) - } + private fun decryptAesJson(ciphertext: String, key: String): JsonElement { + val cipherData = json.parseToJsonElement(ciphertext).jsonObject - val imageUrls = chain.request().url.toString() - .removeSuffix(BEGONEPECON_SUFFIX) - .split("%7C") + val unsaltedCiphertext = Base64.decode(cipherData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT) + val salt = cipherData["s"]!!.jsonPrimitive.content.decodeHex() + val saltedCiphertext = SALTED + salt + unsaltedCiphertext - var width = 0 - var height = 0 - - val imageBitmaps = imageUrls.map { imageUrl -> - val request = chain.request().newBuilder().url(imageUrl).build() - val response = chain.proceed(request) - - val bitmap = BitmapFactory.decodeStream(response.body.byteStream()) - - width += bitmap.width - height = bitmap.height - - bitmap - } - - val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(result) - - var left = 0 - - imageBitmaps.forEach { bitmap -> - val srcRect = Rect(0, 0, bitmap.width, bitmap.height) - val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height) - - canvas.drawBitmap(bitmap, srcRect, dstRect, null) - - left += bitmap.width - } - - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) - - val responseBody = output.toByteArray().toResponseBody(PNG_MEDIA_TYPE) - - return Response.Builder() - .code(200) - .protocol(Protocol.HTTP_1_1) - .request(chain.request()) - .message("OK") - .body(responseBody) - .build() + return json.parseToJsonElement(CryptoAES.decrypt(Base64.encodeToString(saltedCiphertext, Base64.DEFAULT), key)) } - fun String.decodeHex(): ByteArray { + private fun String.decodeHex(): ByteArray { check(length % 2 == 0) { "Must have an even length" } return chunked(2) @@ -156,10 +83,6 @@ class DragonTea : Madara( } companion object { - private const val BEGONEPECON_SUFFIX = "?begonepecon" - private val PNG_MEDIA_TYPE = "image/png".toMediaType() - private val SALTED = "Salted__".toByteArray(Charsets.UTF_8) - private val PASSWORD = "releasethestormy888" } } 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 8cd38c7c6..ecc2d02f7 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 @@ -83,7 +83,7 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("DokkoManga", "https://dokkomanga.com", "es", overrideVersionCode = 1), SingleLang("Doodmanga", "https://www.doodmanga.com", "th"), SingleLang("DoujinHentai", "https://doujinhentai.net", "es", isNsfw = true, overrideVersionCode = 1), - SingleLang("DragonTea", "https://dragontea.ink", "en", overrideVersionCode = 1), + SingleLang("DragonTea", "https://dragontea.ink", "en", overrideVersionCode = 2), SingleLang("DragonTranslation.net", "https://dragontranslation.net", "es", isNsfw = true, className = "DragonTranslationNet"), SingleLang("Drake Scans", "https://drakescans.com", "en", overrideVersionCode = 3), SingleLang("Dream Manga", "https://www.swarmmanga.com", "en", overrideVersionCode = 3),