[CuuTruyen] Fix attempt 1

Koitharu 2 years ago
parent d32d1f5044
commit 955c75a99f
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -1 +1 @@
total: 1110 total: 1111

@ -2,33 +2,32 @@ package org.koitharu.kotatsu.parsers.site.vi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import okhttp3.Headers import okhttp3.*
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.HttpUrl
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.* import org.koitharu.kotatsu.parsers.util.json.*
import java.awt.Color
import java.awt.Graphics2D
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import javax.imageio.ImageIO
@Broken
@MangaSourceParser("CUUTRUYEN", "CuuTruyen", "vi") @MangaSourceParser("CUUTRUYEN", "CuuTruyen", "vi")
internal class CuuTruyenParser(context: MangaLoaderContext) : internal class CuuTruyenParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor { PagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor {
override val configKeyDomain = override val configKeyDomain = ConfigKey.Domain(
ConfigKey.Domain("cuutruyen.net", "nettrom.com", "hetcuutruyen.net", "cuutruyent9sv7.xyz") "cuutruyen.net",
"nettrom.com",
"hetcuutruyen.net",
"cuutruyent9sv7.xyz",
)
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,
@ -172,48 +171,45 @@ internal class CuuTruyenParser(context: MangaLoaderContext) :
return response return response
} }
val body = response.body ?: return response
val contentType = body.contentType()
val bytes = body.bytes()
val pageId = getPageIdFromUrl(request.url) val pageId = getPageIdFromUrl(request.url)
val (originalWidth, originalHeight) = pageSizesMap[pageId] ?: (0 to 0) val (originalWidth, originalHeight) = pageSizesMap[pageId] ?: (0 to 0)
val decryptedResponse = response.map { body ->
val bytes = body.bytes()
val decrypted = decryptDRM(bytes, decryptionKey) val decrypted = decryptDRM(bytes, decryptionKey)
val reconstructed = decrypted?.let { (swapSegments(decrypted, originalWidth, originalHeight) ?: decrypted).toResponseBody(body.contentType())
reconstructImage(it, originalWidth, originalHeight) }
} ?: bytes
val newBody = reconstructed.toResponseBody(contentType) return context.redrawImageResponse(decryptedResponse) {
return response.newBuilder().body(newBody).build() redrawImage(it)
}
} }
private fun getPageIdFromUrl(url: HttpUrl): Long { private fun getPageIdFromUrl(url: HttpUrl): Long {
return url.pathSegments.lastOrNull()?.toLongOrNull() ?: 0L return url.pathSegments.lastOrNull()?.toLongOrNull() ?: 0L
} }
private fun getOriginalWidthFromRequest(request: okhttp3.Request): Int { private fun getOriginalWidthFromRequest(request: Request): Int {
val width = request.url.queryParameter("width")?.toIntOrNull() ?: 0 val width = request.url.queryParameter("width")?.toIntOrNull() ?: 0
return width return width
} }
private fun getOriginalHeightFromRequest(request: okhttp3.Request): Int { private fun getOriginalHeightFromRequest(request: Request): Int {
val height = request.url.queryParameter("height")?.toIntOrNull() ?: 0 val height = request.url.queryParameter("height")?.toIntOrNull() ?: 0
return height return height
} }
private fun decryptDRM(drmData: ByteArray, key: ByteArray): ByteArray? { private fun decryptDRM(drmData: ByteArray, key: ByteArray): ByteArray = runCatchingCancellable {
return try {
drmData.mapIndexed { index, byte -> drmData.mapIndexed { index, byte ->
(byte.toInt() xor key[index % key.size].toInt()).toByte() (byte.toInt() xor key[index % key.size].toInt()).toByte()
}.toByteArray() }.toByteArray()
} catch (e: Exception) { }.getOrDefault(drmData)
null
} private fun redrawImage(source: Bitmap): Bitmap {
return source
} }
private fun reconstructImage(decrypted: ByteArray, originalWidth: Int, originalHeight: Int): ByteArray? { private fun swapSegments(decrypted: ByteArray, originalWidth: Int, originalHeight: Int): ByteArray? {
return try {
val delimiter = "#v".toByteArray() val delimiter = "#v".toByteArray()
val delimiterIndex = decrypted.indexOfFirst { val delimiterIndex = decrypted.indexOfFirst {
decrypted.sliceArray(it until (it + delimiter.size)).contentEquals(delimiter) decrypted.sliceArray(it until (it + delimiter.size)).contentEquals(delimiter)
@ -254,28 +250,7 @@ internal class CuuTruyenParser(context: MangaLoaderContext) :
finalSegmentInfo = finalSegmentInfo.toMutableList().apply { add(0 to remainingHeight) } finalSegmentInfo = finalSegmentInfo.toMutableList().apply { add(0 to remainingHeight) }
} }
} }
return decrypted
val originalImage = ImageIO.read(decrypted.inputStream()) ?: return null
val newImage = BufferedImage(originalWidth, originalHeight, BufferedImage.TYPE_INT_RGB)
val graphics: Graphics2D = newImage.createGraphics()
var sy = 0
for ((dy, segHeight) in finalSegmentInfo) {
if (sy + segHeight > originalHeight) {
break
}
val subImage = originalImage.getSubimage(0, sy, originalWidth, segHeight)
graphics.drawImage(subImage, 0, dy, null)
sy += segHeight
}
graphics.dispose()
val outputStream = ByteArrayOutputStream()
ImageIO.write(newImage, "JPEG", outputStream)
outputStream.toByteArray()
} catch (e: Exception) {
null
}
} }
private fun ByteArray.indexOfFirst(predicate: (Int) -> Boolean): Int { private fun ByteArray.indexOfFirst(predicate: (Int) -> Boolean): Int {

@ -6,6 +6,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call import okhttp3.Call
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody
public suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation -> public suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation ->
val callback = ContinuationCallCallback(this, continuation) val callback = ContinuationCallCallback(this, continuation)
@ -37,3 +38,9 @@ public fun Response.Builder.setHeader(name: String, value: String?): Response.Bu
} else { } else {
header(name, value) header(name, value)
} }
public inline fun Response.map(mapper: (ResponseBody) -> ResponseBody): Response = body?.use { responseBody ->
newBuilder()
.body(mapper(responseBody))
.build()
} ?: this

@ -108,4 +108,4 @@ public fun DateFormat.tryParse(str: String?): Long = if (str.isNullOrEmpty()) {
}.getOrDefault(0L) }.getOrDefault(0L)
} }
private fun Response.requireBody(): ResponseBody = requireNotNull(body) { "Response body is null" } internal fun Response.requireBody(): ResponseBody = requireNotNull(body) { "Response body is null" }

@ -1,17 +1,23 @@
package org.koitharu.kotatsu.parsers package org.koitharu.kotatsu.parsers
import com.koushikdutta.quack.QuackContext import com.koushikdutta.quack.QuackContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import org.koitharu.kotatsu.parsers.bitmap.Bitmap import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.await import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.requireBody
import org.koitharu.kotatsu.test_util.BitmapTestImpl
import java.awt.image.BufferedImage
import java.security.SecureRandom import java.security.SecureRandom
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.imageio.ImageIO
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
@ -46,12 +52,17 @@ internal object MangaLoaderContextMock : MangaLoaderContext() {
override fun getDefaultUserAgent(): String = UserAgents.FIREFOX_MOBILE override fun getDefaultUserAgent(): String = UserAgents.FIREFOX_MOBILE
override fun redrawImageResponse(response: Response, redraw: (image: Bitmap) -> Bitmap): Response { override fun redrawImageResponse(response: Response, redraw: (Bitmap) -> Bitmap): Response {
return response val srcImage = response.requireBody().byteStream().use(ImageIO::read)
checkNotNull(srcImage) { "Cannot decode image" }
val resImage = (redraw(BitmapTestImpl(srcImage)) as BitmapTestImpl)
return response.newBuilder()
.body(resImage.compress("png").toResponseBody("image/png".toMediaTypeOrNull()))
.build()
} }
override fun createBitmap(width: Int, height: Int): Bitmap { override fun createBitmap(width: Int, height: Int): Bitmap {
throw UnsupportedOperationException() return BitmapTestImpl(BufferedImage(width, height, BufferedImage.TYPE_INT_RGB))
} }
suspend fun doRequest(url: String, source: MangaSource?): Response { suspend fun doRequest(url: String, source: MangaSource?): Response {

@ -0,0 +1,35 @@
package org.koitharu.kotatsu.test_util
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.bitmap.Rect
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
class BitmapTestImpl(
val image: BufferedImage,
) : Bitmap {
override val width: Int
get() = image.width
override val height: Int
get() = image.height
override fun drawBitmap(
sourceBitmap: Bitmap,
src: Rect,
dst: Rect,
) {
val graphics = image.createGraphics()
val subImage = (sourceBitmap as BitmapTestImpl).image.getSubimage(
src.left, src.top, src.width, src.height,
)
graphics.drawImage(subImage, dst.left, dst.top, dst.width, dst.height, null)
graphics.dispose()
}
fun compress(format: String): ByteArray = ByteArrayOutputStream().also { stream ->
ImageIO.write(image, format, stream)
}.toByteArray()
}
Loading…
Cancel
Save