Merge pull request #1093 from dragonx943/het-cuu-truyen

Fix decryptor + Add image reconstructor ?
master
Koitharu 2 years ago committed by GitHub
commit 091c5247d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -5,6 +5,7 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import okhttp3.HttpUrl
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
@ -14,10 +15,13 @@ 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.io.ByteArrayOutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.zip.Inflater import javax.imageio.ImageIO
@MangaSourceParser("CUUTRUYEN", "CuuTruyen", "vi") @MangaSourceParser("CUUTRUYEN", "CuuTruyen", "vi")
internal class CuuTruyenParser(context: MangaLoaderContext) : internal class CuuTruyenParser(context: MangaLoaderContext) :
@ -43,7 +47,7 @@ internal class CuuTruyenParser(context: MangaLoaderContext) :
.add("User-Agent", UserAgents.KOTATSU) .add("User-Agent", UserAgents.KOTATSU)
.build() .build()
private val decryptionKey = "3141592653589793" private val decryptionKey = "3141592653589793".toByteArray()
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString { val url = buildString {
@ -141,14 +145,18 @@ internal class CuuTruyenParser(context: MangaLoaderContext) :
) )
} }
private val pageSizesMap = mutableMapOf<Long, Pair<Int, Int>>()
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val url = "https://$domain${chapter.url}" val url = "https://$domain${chapter.url}"
val json = webClient.httpGet(url).parseJson().getJSONObject("data") val json = webClient.httpGet(url).parseJson().getJSONObject("data")
return json.getJSONArray("pages").mapJSON { jo -> return json.getJSONArray("pages").mapJSON { jo ->
val imageUrl = jo.getString("image_url") val imageUrl = jo.getString("image_url")
val id = jo.getLong("id")
pageSizesMap[id] = jo.getInt("width") to jo.getInt("height")
MangaPage( MangaPage(
id = generateUid(jo.getLong("id")), id = generateUid(id),
url = imageUrl, url = imageUrl,
preview = null, preview = null,
source = source, source = source,
@ -168,31 +176,112 @@ internal class CuuTruyenParser(context: MangaLoaderContext) :
val contentType = body.contentType() val contentType = body.contentType()
val bytes = body.bytes() val bytes = body.bytes()
val decrypted = try { val pageId = getPageIdFromUrl(request.url)
decompress(decrypt(bytes)) val (originalWidth, originalHeight) = pageSizesMap[pageId] ?: (0 to 0)
} catch (e: Exception) {
bytes val decrypted = decryptDRM(bytes, decryptionKey)
} val reconstructed = decrypted?.let {
val newBody = decrypted.toResponseBody(contentType) reconstructImage(it, originalWidth, originalHeight)
} ?: bytes
val newBody = reconstructed.toResponseBody(contentType)
return response.newBuilder().body(newBody).build() return response.newBuilder().body(newBody).build()
} }
private fun decrypt(input: ByteArray): ByteArray { private fun getPageIdFromUrl(url: HttpUrl): Long {
val key = decryptionKey.toByteArray() return url.pathSegments.lastOrNull()?.toLongOrNull() ?: 0L
return input.mapIndexed { index, byte -> }
private fun getOriginalWidthFromRequest(request: okhttp3.Request): Int {
val width = request.url.queryParameter("width")?.toIntOrNull() ?: 0
return width
}
private fun getOriginalHeightFromRequest(request: okhttp3.Request): Int {
val height = request.url.queryParameter("height")?.toIntOrNull() ?: 0
return height
}
private fun decryptDRM(drmData: ByteArray, key: ByteArray): ByteArray? {
return try {
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) {
null
}
}
private fun reconstructImage(decrypted: ByteArray, originalWidth: Int, originalHeight: Int): ByteArray? {
return try {
val delimiter = "#v".toByteArray()
val delimiterIndex = decrypted.indexOfFirst {
decrypted.sliceArray(it until (it + delimiter.size)).contentEquals(delimiter)
}
if (delimiterIndex == -1) {
return null
}
val segmentsInfoStart = delimiterIndex + delimiter.size
val segmentsData = decrypted.sliceArray(segmentsInfoStart until decrypted.size)
val segments = String(segmentsData).split("|").filter { it.contains("-") }
if (segments.isEmpty()) {
return null
}
val segmentInfo = segments.mapNotNull { seg ->
try {
val (dyStr, heightStr) = seg.split("-")
val dy = if (dyStr.startsWith("dy")) dyStr.substring(2).trim() else dyStr.trim()
val dyInt = dy.toInt()
val height = heightStr.trim().toInt()
dyInt to height
} catch (e: Exception) {
null
}
}
if (segmentInfo.isEmpty()) {
return null
}
var finalSegmentInfo = segmentInfo
val totalHeight = finalSegmentInfo.sumOf { it.second }
if (totalHeight != originalHeight) {
val remainingHeight = originalHeight - totalHeight
if (remainingHeight > 0) {
finalSegmentInfo = finalSegmentInfo.toMutableList().apply { add(0 to remainingHeight) }
}
}
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 decompress(input: ByteArray): ByteArray { private fun ByteArray.indexOfFirst(predicate: (Int) -> Boolean): Int {
val inflater = Inflater() for (i in indices) {
inflater.setInput(input, 0, input.size) if (predicate(i)) return i
val outputStream = ByteArrayOutputStream(input.size)
val buffer = ByteArray(1024)
while (!inflater.finished()) {
val count = inflater.inflate(buffer)
outputStream.write(buffer, 0, count)
} }
return outputStream.toByteArray() return -1
} }
} }

@ -21,7 +21,7 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context,
SortOrder.POPULARITY, SortOrder.POPULARITY,
) )
override val configKeyDomain = ConfigKey.Domain("lxmanga.life") override val configKeyDomain = ConfigKey.Domain("lxmanga.click")
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)

@ -4,6 +4,7 @@ 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.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -28,6 +29,8 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context,
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
) )
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)

@ -10,6 +10,6 @@ import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser
internal class NetTruyen(context: MangaLoaderContext) : internal class NetTruyen(context: MangaLoaderContext) :
WpComicsParser(context, MangaParserSource.NETTRUYEN, "www.nettruyenupp.com", 44) { WpComicsParser(context, MangaParserSource.NETTRUYEN, "www.nettruyenupp.com", 44) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain( override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain(
"www.nettruyenupp.com", "nettruyenww.com", "nettruyenx.com", "nettruyenupp.com", "nettruyenww.com", "nettruyenx.com",
) )
} }

@ -9,9 +9,7 @@ import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet import java.util.EnumSet
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("NETTRUYENLL", "NetTruyenLL", "vi") @MangaSourceParser("NETTRUYENLL", "NetTruyenLL", "vi")
internal class NetTruyenLL(context: MangaLoaderContext) : internal class NetTruyenLL(context: MangaLoaderContext) :
WpComicsParser(context, MangaParserSource.NETTRUYENLL, "nettruyenll.com", 20) { WpComicsParser(context, MangaParserSource.NETTRUYENLL, "nettruyenll.com", 20) {

@ -8,10 +8,8 @@ import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.Broken
import java.util.EnumSet import java.util.EnumSet
@Broken
@MangaSourceParser("NETTRUYENSSR", "NetTruyenSSR", "vi") @MangaSourceParser("NETTRUYENSSR", "NetTruyenSSR", "vi")
internal class NetTruyenSSR(context: MangaLoaderContext) : internal class NetTruyenSSR(context: MangaLoaderContext) :
WpComicsParser(context, MangaParserSource.NETTRUYENSSR, "nettruyenssr.com", 20) { WpComicsParser(context, MangaParserSource.NETTRUYENSSR, "nettruyenssr.com", 20) {

Loading…
Cancel
Save