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 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 android.util.Base64
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES 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.Page
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -29,7 +20,6 @@ class DragonTea : Madara(
dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US), dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US),
) { ) {
override val client: OkHttpClient = super.client.newBuilder() override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(::begonepeconIntercept)
.rateLimit(1) .rateLimit(1)
.build() .build()
@ -45,109 +35,46 @@ class DragonTea : Madara(
} }
} }
private val begonepeconSelector: String = "div.begonepecon" private val pageIndexRegex = Regex("""image-(\d+)[a-z]+""", RegexOption.IGNORE_CASE)
private val peconholderSelector: String = "div.peconholder"
override fun pageListParse(document: Document): List<Page> { 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 val idKey = "8" + ((dataId + 1306) * 3 - pageCount).toString()
.select(begonepeconSelector) elements.forEach {
.firstOrNull() != null val decryptedId = decryptAesJson(it.attr("id"), idKey).jsonPrimitive.content
it.attr("id", decryptedId)
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)
}
} }
return document.select("div.page-break, li.blocks-gallery-item, $begonepeconSelector") val orderedElements = elements.sortedBy {
.mapIndexed { index, element -> pageIndexRegex.find(it.attr("id"))?.groupValues?.get(1)?.toInt() ?: 0
val imageUrl = if (element.select(peconholderSelector).firstOrNull() == null) { }
element.select("img").first()?.let { it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src") } val dtaKey = "13" + orderedElements.joinToString("") { it.attr("id").takeLast(1) } + (((dataId + 88) * 2) - pageCount - 4).toString()
} else {
element.select("img").joinToString("|") { it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src") } + BEGONEPECON_SUFFIX val srcKey = (dataId + 20).toString() + orderedElements.joinToString("") {
} decryptAesJson(it.attr("dta"), dtaKey).jsonPrimitive.content.takeLast(2)
Page(index, document.location(), imageUrl) } + (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 { private fun decryptAesJson(ciphertext: String, key: String): JsonElement {
if (!chain.request().url.toString().endsWith(BEGONEPECON_SUFFIX)) { val cipherData = json.parseToJsonElement(ciphertext).jsonObject
return chain.proceed(chain.request())
}
val imageUrls = chain.request().url.toString() val unsaltedCiphertext = Base64.decode(cipherData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
.removeSuffix(BEGONEPECON_SUFFIX) val salt = cipherData["s"]!!.jsonPrimitive.content.decodeHex()
.split("%7C") val saltedCiphertext = SALTED + salt + unsaltedCiphertext
var width = 0 return json.parseToJsonElement(CryptoAES.decrypt(Base64.encodeToString(saltedCiphertext, Base64.DEFAULT), key))
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()
} }
fun String.decodeHex(): ByteArray { private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" } check(length % 2 == 0) { "Must have an even length" }
return chunked(2) return chunked(2)
@ -156,10 +83,6 @@ class DragonTea : Madara(
} }
companion object { 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 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("DokkoManga", "https://dokkomanga.com", "es", overrideVersionCode = 1),
SingleLang("Doodmanga", "https://www.doodmanga.com", "th"), SingleLang("Doodmanga", "https://www.doodmanga.com", "th"),
SingleLang("DoujinHentai", "https://doujinhentai.net", "es", isNsfw = true, overrideVersionCode = 1), 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("DragonTranslation.net", "https://dragontranslation.net", "es", isNsfw = true, className = "DragonTranslationNet"),
SingleLang("Drake Scans", "https://drakescans.com", "en", overrideVersionCode = 3), SingleLang("Drake Scans", "https://drakescans.com", "en", overrideVersionCode = 3),
SingleLang("Dream Manga", "https://www.swarmmanga.com", "en", overrideVersionCode = 3), SingleLang("Dream Manga", "https://www.swarmmanga.com", "en", overrideVersionCode = 3),