Dâm Cô Nương: Remove redundant escape character (#2160)

master
Draken 8 months ago committed by GitHub
parent b3e7d8e8d5
commit f64ce0f4a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -187,7 +187,7 @@ internal class DamCoNuong(context: MangaLoaderContext) :
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
doc.selectFirst("script:containsData(window.encryptionConfig)")?.data()?.let { scriptContent ->
val fallbackUrlsRegex = Regex(""""fallbackUrls"\s*:\s*(\[.*?\])""")
val fallbackUrlsRegex = Regex(""""fallbackUrls"\s*:\s*(\[.*?])""")
val arrayString = fallbackUrlsRegex.find(scriptContent)?.groupValues?.get(1) ?: return@let
val urlRegex = Regex("""(https?:\\?/\\?[^"]+\.(?:jpg|jpeg|png|webp|gif))""")
val scriptImages = urlRegex.findAll(arrayString).map {

@ -238,4 +238,8 @@ internal class KuroNeko(context: MangaLoaderContext) : PagedMangaParser(context,
)
calendar.timeInMillis
}.getOrDefault(0L)
companion object {
const val PATH = "AxsAEQdJWk4YDUkHDgcVEwxaBQoHShIXHwYbD1seHAwHOwAKCAYFFw==\n"
}
}

@ -1,9 +1,14 @@
package org.koitharu.kotatsu.parsers.site.vi
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response
import org.json.JSONArray
import org.koitharu.kotatsu.parsers.Broken
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
import org.koitharu.kotatsu.parsers.network.UserAgents
@ -13,7 +18,6 @@ import org.koitharu.kotatsu.parsers.util.json.*
import java.text.SimpleDateFormat
import java.util.*
@Broken("Request from site owner: Open webview to read")
@MangaSourceParser("MIMIHENTAI", "MimiHentai", "vi", type = ContentType.HENTAI)
internal class MimiHentai(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.MIMIHENTAI, 18) {
@ -40,14 +44,14 @@ internal class MimiHentai(context: MangaLoaderContext) :
}
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.ALPHABETICAL,
SortOrder.POPULARITY,
SortOrder.POPULARITY_TODAY,
SortOrder.POPULARITY_WEEK,
SortOrder.POPULARITY_MONTH,
SortOrder.RATING,
)
SortOrder.UPDATED,
SortOrder.ALPHABETICAL,
SortOrder.POPULARITY,
SortOrder.POPULARITY_TODAY,
SortOrder.POPULARITY_WEEK,
SortOrder.POPULARITY_MONTH,
SortOrder.RATING,
)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
@ -65,31 +69,31 @@ internal class MimiHentai(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = fetchTags())
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append("$domain/$apiSuffix")
val url = buildString {
append("https://")
append("$domain/$apiSuffix")
if (!filter.query.isNullOrEmpty() ||
if (!filter.query.isNullOrEmpty() ||
!filter.author.isNullOrEmpty() ||
filter.tags.isNotEmpty()
) {
append("/advance-search?page=")
append(page)
append("&max=18") // page size, avoid rate limit
append("/advance-search?page=")
append(page)
append("&max=18") // page size, avoid rate limit
if (!filter.query.isNullOrEmpty()) {
append("&name=")
append(filter.query.urlEncoded())
}
if (!filter.author.isNullOrEmpty()) {
append("&author=")
append(filter.author.urlEncoded())
if (!filter.author.isNullOrEmpty()) {
append("&author=")
append(filter.author.urlEncoded())
}
if (filter.tags.isNotEmpty()) {
append("&genre=")
append(filter.tags.joinToString(",") { it.key })
if (filter.tags.isNotEmpty()) {
append("&genre=")
append(filter.tags.joinToString(",") { it.key })
}
if (filter.tagsExclude.isNotEmpty()) {
@ -97,43 +101,43 @@ internal class MimiHentai(context: MangaLoaderContext) :
append(filter.tagsExclude.joinToString(",") { it.key })
}
append("&sort=")
append(
when (order) {
SortOrder.UPDATED -> "updated_at"
SortOrder.ALPHABETICAL -> "title"
SortOrder.POPULARITY -> "follows"
SortOrder.POPULARITY_TODAY,
SortOrder.POPULARITY_WEEK,
append("&sort=")
append(
when (order) {
SortOrder.UPDATED -> "updated_at"
SortOrder.ALPHABETICAL -> "title"
SortOrder.POPULARITY -> "follows"
SortOrder.POPULARITY_TODAY,
SortOrder.POPULARITY_WEEK,
SortOrder.POPULARITY_MONTH -> "views"
SortOrder.RATING -> "likes"
else -> ""
}
SortOrder.RATING -> "likes"
else -> ""
}
)
}
else {
append(
when (order) {
SortOrder.UPDATED -> "/tatcatruyen?page=$page&sort=updated_at"
SortOrder.ALPHABETICAL -> "/tatcatruyen?page=$page&sort=title"
SortOrder.POPULARITY -> "/tatcatruyen?page=$page&sort=follows"
SortOrder.POPULARITY_TODAY -> "/tatcatruyen?page=$page&sort=views"
SortOrder.POPULARITY_WEEK -> "/top-manga?page=$page&timeType=1&limit=18"
}
else {
append(
when (order) {
SortOrder.UPDATED -> "/tatcatruyen?page=$page&sort=updated_at"
SortOrder.ALPHABETICAL -> "/tatcatruyen?page=$page&sort=title"
SortOrder.POPULARITY -> "/tatcatruyen?page=$page&sort=follows"
SortOrder.POPULARITY_TODAY -> "/tatcatruyen?page=$page&sort=views"
SortOrder.POPULARITY_WEEK -> "/top-manga?page=$page&timeType=1&limit=18"
SortOrder.POPULARITY_MONTH -> "/top-manga?page=$page&timeType=2&limit=18"
SortOrder.RATING -> "/tatcatruyen?page=$page&sort=likes"
else -> "/tatcatruyen?page=$page&sort=updated_at" // default
}
)
SortOrder.RATING -> "/tatcatruyen?page=$page&sort=likes"
else -> "/tatcatruyen?page=$page&sort=updated_at" // default
}
)
if (filter.tagsExclude.isNotEmpty()) {
append("&ex=")
append(filter.tagsExclude.joinToString(",") { it.key })
}
}
}
}
}
val raw = webClient.httpGet(url)
val raw = webClient.httpGet(url)
return if (url.contains("/top-manga")) {
val data = raw.parseJsonArray()
parseTopMangaList(data)
@ -195,19 +199,19 @@ internal class MimiHentai(context: MangaLoaderContext) :
val title = jo.getString("title").takeIf { it.isNotEmpty() } ?: "Web chưa đặt tên"
val description = jo.getStringOrNull("description")
val differentNames = mutableSetOf<String>().apply {
jo.optJSONArray("differentNames")?.let { namesArray ->
for (i in 0 until namesArray.length()) {
namesArray.optString(i)?.takeIf { it.isNotEmpty() }?.let { name ->
add(name)
}
}
}
}
val differentNames = mutableSetOf<String>().apply {
jo.optJSONArray("differentNames")?.let { namesArray ->
for (i in 0 until namesArray.length()) {
namesArray.optString(i)?.takeIf { it.isNotEmpty() }?.let { name ->
add(name)
}
}
}
}
val authors = jo.getJSONArray("authors").mapJSON {
it.getString("name")
}.toSet()
val authors = jo.getJSONArray("authors").mapJSON {
it.getString("name")
}.toSet()
val tags = jo.getJSONArray("genres").mapJSON { genre ->
MangaTag(
@ -238,7 +242,7 @@ internal class MimiHentai(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga {
val url = manga.url.toAbsoluteUrl(domain)
val json = webClient.httpGet(url).parseJson()
val id = json.getLong("id")
val id = json.getLong("id")
val description = json.getStringOrNull("description")
val uploaderName = json.getJSONObject("uploader").getString("displayName")
@ -258,7 +262,7 @@ internal class MimiHentai(context: MangaLoaderContext) :
id = generateUid(jo.getLong("id")),
title = jo.getStringOrNull("title"),
number = jo.getFloatOrDefault("order", 0f),
url = "/$apiSuffix/chapter?id=${jo.getLong("id")}",
url = "${jo.getLong("id")}",
uploadDate = dateFormat.parse(jo.getString("createdAt"))?.time ?: 0L,
source = source,
scanlator = uploaderName,
@ -275,13 +279,160 @@ internal class MimiHentai(context: MangaLoaderContext) :
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val json = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseJson()
val url = context.decodeBase64(KuroNeko.PATH)
.decodeXorCipher()
.toString(Charsets.UTF_8) + "/" + chapter.url
val json = webClient.httpGet(url).parseJson()
return json.getJSONArray("pages").mapJSON { jo ->
val imageUrl = jo.getString("imageUrl")
val gt = jo.getStringOrNull("drm")
MangaPage(
id = generateUid(imageUrl),
url = if (gt != null) "$imageUrl#$GT$gt" else imageUrl,
preview = null,
source = source,
)
}
}
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
val fragment = response.request.url.fragment
if (fragment == null || !fragment.contains(GT)) {
return response
}
return context.redrawImageResponse(response) { bitmap ->
val gt = fragment.substringAfter(GT)
runBlocking {
extractMetadata(bitmap, gt)
}
}
}
private fun extractMetadata(bitmap: Bitmap, gt: String): Bitmap {
val metadata = JSONObject().apply {
var sw = 0
var sh = 0
val pos = JSONObject()
val dims = JSONObject()
for (t in gt.split("|")) {
when {
t.startsWith("sw:") -> sw = t.substring(3).toInt()
t.startsWith("sh:") -> sh = t.substring(3).toInt()
t.contains("@") && t.contains(">") -> {
val (left, right) = t.split(">")
val (n, rectStr) = left.split("@")
val (x, y, w, h) = rectStr.split(",").map { it.toInt() }
dims.put(n, JSONObject().apply {
put("x", x)
put("y", y)
put("width", w)
put("height", h)
})
pos.put(n, right)
}
}
}
put("sw", sw)
put("sh", sh)
put("dims", dims)
put("pos", pos)
}
val sw = metadata.optInt("sw")
val sh = metadata.optInt("sh")
if (sw <= 0 || sh <= 0) return bitmap
val fullW = bitmap.width
val fullH = bitmap.height
val working = context.createBitmap(sw, sh).also { k ->
k.drawBitmap(bitmap, Rect(0, 0, sw, sh), Rect(0, 0, sw, sh))
}
val info = json.getJSONObject("info")
val manga = info.getJSONObject("manga")
val keys = arrayOf("00","01","02","10","11","12","20","21","22")
val baseW = sw / 3
val baseH = sh / 3
val rw = sw % 3
val rh = sh % 3
val defaultDims = HashMap<String, IntArray>().apply {
for (k in keys) {
val i = k[0].digitToInt()
val j = k[1].digitToInt()
val w = baseW + if (j == 2) rw else 0
val h = baseH + if (i == 2) rh else 0
put(k, intArrayOf(j * baseW, i * baseH, w, h))
}
}
val dimsJson = metadata.optJSONObject("dims") ?: JSONObject()
val dims = HashMap<String, IntArray>().apply {
for (k in keys) {
val jo = dimsJson.optJSONObject(k)
if (jo != null) {
put(k, intArrayOf(
jo.getInt("x"),
jo.getInt("y"),
jo.getInt("width"),
jo.getInt("height"),
))
} else {
put(k, defaultDims.getValue(k))
}
}
}
val chapterUrl = "https://$domain/g/${manga.getInt("id")}/chapter/${info.getString("title")}-${info.getInt("id")}"
context.requestBrowserAction(this, chapterUrl)
val pos = metadata.optJSONObject("pos") ?: JSONObject()
val inv = HashMap<String, String>().apply {
val it = pos.keys()
while (it.hasNext()) {
val a = it.next()
val b = pos.getString(a)
put(b, a)
}
}
val result = context.createBitmap(fullW, fullH)
for (k in keys) {
val srcKey = inv[k] ?: continue
val s = dims.getValue(k)
val d = dims.getValue(srcKey)
result.drawBitmap(
working,
Rect(s[0], s[1], s[0] + s[2], s[1] + s[3]),
Rect(d[0], d[1], d[0] + d[2], d[1] + d[3]),
)
}
if (sh < fullH) {
result.drawBitmap(
bitmap,
Rect(0, sh, fullW, fullH),
Rect(0, sh, fullW, fullH),
)
}
if (sw < fullW) {
result.drawBitmap(
bitmap,
Rect(sw, 0, fullW, sh),
Rect(sw, 0, fullW, sh),
)
}
return result
}
private fun ByteArray.decodeXorCipher(): ByteArray {
val k = "kotatsuanddokiarethebest"
.toByteArray(Charsets.UTF_8)
return this.mapIndexed { i, b ->
(b.toInt() xor k[i % k.size].toInt()).toByte()
}.toByteArray()
}
private suspend fun fetchTags(): Set<MangaTag> {
@ -295,4 +446,8 @@ internal class MimiHentai(context: MangaLoaderContext) :
)
}
}
companion object {
private const val GT = "gt="
}
}

Loading…
Cancel
Save