[CuuTruyen] Fixes

This commit is contained in:
2024-09-05 12:29:33 +03:00
parent b404b44008
commit f167a8c8f6
2 changed files with 156 additions and 141 deletions

View File

@@ -1,10 +1,12 @@
package org.koitharu.kotatsu.parsers.site.ru.rulib package org.koitharu.kotatsu.parsers.site.ru.rulib
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.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
@Broken
@MangaSourceParser("HENTAILIB", "HentaiLib", "ru", type = ContentType.HENTAI) @MangaSourceParser("HENTAILIB", "HentaiLib", "ru", type = ContentType.HENTAI)
internal class HentaiLibParser(context: MangaLoaderContext) : LibSocialParser( internal class HentaiLibParser(context: MangaLoaderContext) : LibSocialParser(
context = context, context = context,

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.parsers.site.vi package org.koitharu.kotatsu.parsers.site.vi
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
@@ -9,173 +11,184 @@ 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.model.* import org.koitharu.kotatsu.parsers.model.*
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 org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.network.UserAgents
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 java.util.zip.Inflater
@MangaSourceParser("CUUTRUYEN", "CuuTruyen", "vi") @MangaSourceParser("CUUTRUYEN", "CuuTruyen", "vi")
internal class CuuTruyenParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor { internal class CuuTruyenParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor {
override val configKeyDomain = ConfigKey.Domain("cuutruyen.net", "nettrom.com", "hetcuutruyen.net", "cuutruyent9sv7.xyz") override val configKeyDomain =
ConfigKey.Domain("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,
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.NEWEST,
) )
override fun getRequestHeaders(): Headers = Headers.Builder() override fun getRequestHeaders(): Headers = Headers.Builder()
.add("User-Agent", UserAgents.KOTATSU) .add("User-Agent", UserAgents.KOTATSU)
.build() .build()
private val decryptionKey = "3141592653589793" private val decryptionKey = "3141592653589793"
private val itemsPerPage: Int = 20 private val itemsPerPage: Int = 20
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet() override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
private suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
offset: Int, val url = buildString {
query: String?, append("https://")
tags: Set<MangaTag>?, append(domain)
sortOrder: SortOrder, when (filter) {
): List<Manga> { is MangaListFilter.Search -> {
val page = offset / itemsPerPage + 1 append("/api/v2/mangas/search?q=")
val url = buildString { append(filter.query.urlEncoded())
if (!query.isNullOrEmpty()) { append("&page=")
append("$domain/api/v2/mangas/search") append(page.toString())
append("?q=") }
append(query.urlEncoded())
append("&page=")
append(page.toString())
append("&per_page=")
append(itemsPerPage.toString())
} else {
append("$domain/api/v2/mangas")
when (sortOrder) {
SortOrder.UPDATED -> append("/recently_updated")
SortOrder.POPULARITY -> append("/top")
SortOrder.NEWEST -> append("/recently_updated")
else -> append("/recently_updated")
}
append("?page=")
append(page.toString())
}
}
val json = webClient.httpGet(url).parseJson() else -> {
val data = json.optJSONObject("data") ?: throw ParseException("Invalid response", url) val tag = (filter as? MangaListFilter.Advanced)?.tags?.oneOrThrowIfMany()
if (tag != null) {
append("/api/v2/tags/")
append(tag.key)
} else {
append("/api/v2/mangas")
when (filter?.sortOrder) {
SortOrder.UPDATED -> append("/recently_updated")
SortOrder.POPULARITY -> append("/top")
SortOrder.NEWEST -> append("/recently_updated")
else -> append("/recently_updated")
}
}
append("?page=")
append(page.toString())
}
}
return data.getJSONArray("data").mapJSON { jo -> append("&per_page=")
Manga( append(itemsPerPage)
id = generateUid(jo.getLong("id")), }
url = "/api/v2/mangas/${jo.getLong("id")}",
publicUrl = "$domain/manga/${jo.getLong("id")}",
title = jo.getString("name"),
altTitle = null,
coverUrl = jo.getString("cover_url"),
largeCoverUrl = jo.getString("cover_mobile_url"),
author = jo.getStringOrNull("author_name"),
tags = emptySet(),
state = null,
description = null,
isNsfw = false,
source = source,
rating = RATING_UNKNOWN,
)
}
}
override suspend fun getDetails(manga: Manga): Manga { val json = webClient.httpGet(url).parseJson()
val url = domain + manga.url val data = json.getJSONArray("data")
val json = webClient.httpGet(url).parseJson().getJSONObject("data")
?: throw ParseException("Invalid response", url)
return manga.copy( return data.mapJSON { jo ->
description = json.getString("description"), Manga(
chapters = json.getJSONArray("chapters").mapJSON { jo -> id = generateUid(jo.getLong("id")),
MangaChapter( url = "/api/v2/mangas/${jo.getLong("id")}",
id = generateUid(jo.getLong("id")), publicUrl = "https://$domain/manga/${jo.getLong("id")}",
name = jo.getString("name"), title = jo.getString("name"),
number = jo.getInt("number"), altTitle = null,
url = "/api/v2/chapters/${jo.getLong("id")}", coverUrl = jo.getString("cover_url"),
scanlator = jo.optString("group_name"), largeCoverUrl = jo.getString("cover_mobile_url"),
uploadDate = parseChapterDate(jo.getString("created_at")), author = jo.getStringOrNull("author_name"),
branch = null, tags = emptySet(),
source = source, state = null,
) description = null,
}.reversed(), isNsfw = isNsfwSource,
) source = source,
} rating = RATING_UNKNOWN,
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val url = "$domain${chapter.url}" val url = "https://" + domain + manga.url
val json = webClient.httpGet(url).parseJson().getJSONObject("data") val chapters = async {
?: throw ParseException("Invalid response", url) webClient.httpGet("$url/chapters").parseJson().getJSONArray("data")
}
val json = webClient.httpGet(url).parseJson().getJSONObject("data")
val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
return json.getJSONArray("pages").mapJSON { jo -> manga.copy(
val imageUrl = jo.getString("image_url") title = json.getStringOrNull("name") ?: manga.title,
MangaPage( isNsfw = json.getBooleanOrDefault("is_nsfw", manga.isNsfw),
id = generateUid(jo.getLong("id")), author = json.optJSONObject("author")?.getStringOrNull("name")?.substringBefore(','),
url = imageUrl, description = json.getString("full_description"),
preview = null, tags = json.optJSONArray("tags")?.mapJSONToSet { jo ->
source = source, MangaTag(
) title = jo.getString("name").toTitleCase(sourceLocale),
} key = jo.getString("slug"),
} source = source,
)
}.orEmpty(),
chapters = chapters.await().mapJSON { jo ->
val chapterId = jo.getLong("id")
val number = jo.getFloatOrDefault("number", 0f)
MangaChapter(
id = generateUid(chapterId),
name = jo.getStringOrNull("name") ?: number.formatSimple(),
number = number,
volume = 0,
url = "/api/v2/chapters/$chapterId",
scanlator = jo.optString("group_name"),
uploadDate = chapterDateFormat.tryParse(jo.getStringOrNull("created_at")),
branch = null,
source = source,
)
}.reversed(),
)
}
override fun intercept(chain: Interceptor.Chain): Response { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val request = chain.request() val url = "https://$domain${chapter.url}"
val response = chain.proceed(request) val json = webClient.httpGet(url).parseJson().getJSONObject("data")
if (!request.url.host.contains("cuutruyen.net")) { return json.getJSONArray("pages").mapJSON { jo ->
return response val imageUrl = jo.getString("image_url")
} MangaPage(
id = generateUid(jo.getLong("id")),
url = imageUrl,
preview = null,
source = source,
)
}
}
val body = response.body ?: return response override fun intercept(chain: Interceptor.Chain): Response {
val contentType = body.contentType() val request = chain.request()
val bytes = body.bytes() val response = chain.proceed(request)
val decrypted = try { if (!request.url.host.contains(domain, ignoreCase = true)) {
decrypt(bytes) return response
} catch (e: Exception) { }
bytes
}
val decompressed = try { val body = response.body ?: return response
decompress(decrypted) val contentType = body.contentType()
} catch (e: Exception) { val bytes = body.bytes()
decrypted
}
val newBody = decompressed.toResponseBody(contentType) val decrypted = try {
return response.newBuilder().body(newBody).build() decompress(decrypt(bytes))
} } catch (e: Exception) {
bytes
}
val newBody = decrypted.toResponseBody(contentType)
return response.newBuilder().body(newBody).build()
}
private fun decrypt(input: ByteArray): ByteArray { private fun decrypt(input: ByteArray): ByteArray {
val key = decryptionKey.toByteArray() val key = decryptionKey.toByteArray()
return input.mapIndexed { index, byte -> return input.mapIndexed { index, byte ->
(byte.toInt() xor key[index % key.size].toInt()).toByte() (byte.toInt() xor key[index % key.size].toInt()).toByte()
}.toByteArray() }.toByteArray()
} }
private fun decompress(input: ByteArray): ByteArray { private fun decompress(input: ByteArray): ByteArray {
val inflater = Inflater() val inflater = Inflater()
inflater.setInput(input, 0, input.size) inflater.setInput(input, 0, input.size)
val outputStream = ByteArrayOutputStream(input.size) val outputStream = ByteArrayOutputStream(input.size)
val buffer = ByteArray(1024) val buffer = ByteArray(1024)
while (!inflater.finished()) { while (!inflater.finished()) {
val count = inflater.inflate(buffer) val count = inflater.inflate(buffer)
outputStream.write(buffer, 0, count) outputStream.write(buffer, 0, count)
} }
return outputStream.toByteArray() return outputStream.toByteArray()
} }
private fun parseChapterDate(dateString: String): Long {
return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).parse(dateString)?.time ?: 0L
}
} }