|
|
|
@ -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 {
|
|
|
|
|