DragonTea: Fix page list (#17366)

This commit is contained in:
beerpsi 2023-08-04 00:35:15 +07:00 committed by GitHub
parent 11eb5915ca
commit 59f296a02d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 31 additions and 108 deletions

View File

@ -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<Page> {
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"
}
}

View File

@ -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),