DragonTea: Fix page list (#17366)
This commit is contained in:
parent
11eb5915ca
commit
59f296a02d
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue