[Nhentai World] Fix chapters list

dragonx943 1 year ago
parent a82e41aabb
commit dc7c859344

@ -8,13 +8,14 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.exception.ParseException
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 org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@Broken // TODO @Broken // TODO: Fix tags
@MangaSourceParser("NHENTAIWORLD", "Nhentai World", "vi", ContentType.HENTAI) @MangaSourceParser("NHENTAIWORLD", "Nhentai World", "vi", ContentType.HENTAI)
internal class NhentaiWorld(context: MangaLoaderContext) : internal class NhentaiWorld(context: MangaLoaderContext) :
LegacyPagedMangaParser(context, MangaParserSource.NHENTAIWORLD, 24) { LegacyPagedMangaParser(context, MangaParserSource.NHENTAIWORLD, 24) {
@ -105,90 +106,145 @@ internal class NhentaiWorld(context: MangaLoaderContext) :
} }
} }
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val root = doc.selectFirst("div.flex-1.bg-neutral-900") ?: return manga val root = doc.selectFirst("div.flex-1.bg-neutral-900") ?: return manga
val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT).apply { val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT).apply {
timeZone = TimeZone.getTimeZone("GMT+7") timeZone = TimeZone.getTimeZone("GMT+7")
} }
val tags = root.select("div.flex.flex-wrap.gap-2 button").mapNotNullToSet { button -> val tags = root.select("div.flex.flex-wrap.gap-2 button").mapNotNullToSet { button ->
val tagName = button.text().toTitleCase(sourceLocale) val tagName = button.text().toTitleCase(sourceLocale)
val tagUrl = button.parent()?.attrOrNull("href")?.substringAfterLast('/') val tagUrl = button.parent()?.attrOrNull("href")?.substringAfterLast('/')
if (tagUrl != null) { if (tagUrl != null) {
MangaTag(title = tagName, key = tagUrl, source = source) MangaTag(title = tagName, key = tagUrl, source = source)
} else { } else {
null null
} }
}
val state = when {
root.selectFirst("a[href*='status=completed']") != null -> MangaState.FINISHED
root.selectFirst("a[href*='status=progress']") != null -> MangaState.ONGOING
else -> null
}
val description = root.selectFirst("div#introduction-wrap p.font-light")?.html()?.nullIfEmpty()
val altTitles = description?.split("\n")?.mapNotNullToSet { line ->
when {
line.startsWith("Tên tiếng anh:", ignoreCase = true) ->
line.substringAfter(':').substringBefore("Tên gốc:").trim()
line.startsWith("Tên gốc:", ignoreCase = true) ->
line.substringAfter(':').trim().substringBefore(' ')
else -> null
}
}
val scriptTag = doc.select("script").firstOrNull { script ->
val data = script.data()
data.contains("data") && data.contains("chapterListEn")
}?.data()
val chapters = parseChapterList(scriptTag, manga, chapterDateFormat)
return manga.copy(
tags = tags,
state = state,
description = description,
altTitles = altTitles.orEmpty(),
chapters = chapters.reversed(),
)
}
private suspend fun parseChapterList(scriptTag: String?, manga: Manga, chapterDateFormat: SimpleDateFormat): List<MangaChapter> {
val idManga = manga.url.substringAfter("detail/").toIntOrNull() ?: return emptyList()
val chapters = ArrayList<MangaChapter>()
if (scriptTag.isNullOrEmpty()) return chapters
val cleanedScript = scriptTag.replace("\\", "")
val cutScript = "null,{\"data\""
val needScript = cleanedScript.indexOf(cutScript)
if (needScript == -1) return chapters
val finalScript = cleanedScript.substring(needScript)
val vnPrefix = "null,{\"data\":"
val vnStart = finalScript.indexOf(vnPrefix)
if (vnStart == -1) return chapters
val beforeEn = ",\"chapterListEn\""
val vnEnd = finalScript.indexOf(beforeEn, vnStart)
if (vnEnd == -1) return chapters
val vnChapterStr = finalScript.substring(vnStart + vnPrefix.length, vnEnd)
val vnArray = try {
JSONArray(vnChapterStr)
} catch (e: Exception) {
JSONArray()
} }
val state = when { for (i in 0 until vnArray.length()) {
root.selectFirst("a[href*='status=completed']") != null -> MangaState.FINISHED val chapter = vnArray.getJSONObject(i)
root.selectFirst("a[href*='status=progress']") != null -> MangaState.ONGOING val name = chapter.optString("name", null) ?: continue
else -> null val uploadDateStr = chapter.optString("createdAt", null)
val uploadDate = chapterDateFormat.tryParse(uploadDateStr)
val href = "${idManga}/${name}?lang=VI"
chapters.add(
MangaChapter(
id = generateUid(href),
title = if (name.toFloatOrNull() != null) "Chapter $name" else name,
number = name.toFloatOrNull() ?: (i + 1).toFloat(),
url = "/read/${href}",
scanlator = null,
uploadDate = uploadDate,
branch = "Tiếng Việt",
source = source,
volume = 0
)
)
} }
val description = root.selectFirst("div#introduction-wrap p.font-light")?.html()?.nullIfEmpty() // Copy + Paste from VI
val enPrefix = ",\"chapterListEn\":"
val altTitles = description?.split("\n")?.mapNotNullToSet { line -> val enStart = finalScript.indexOf(enPrefix)
when { if (enStart == -1) return chapters
line.startsWith("Tên tiếng anh:", ignoreCase = true) -> val beforeId = ",\"id\""
line.substringAfter(':').substringBefore("Tên gốc:").trim() val enEnd = finalScript.indexOf(beforeId, enStart)
if (enEnd == -1) return chapters
line.startsWith("Tên gốc:", ignoreCase = true) -> val enChapterStr = finalScript.substring(enStart + enPrefix.length, enEnd)
line.substringAfter(':').trim().substringBefore(' ')
val enArray = try {
else -> null JSONArray(enChapterStr)
} } catch (e: Exception) {
JSONArray()
} }
val scriptTag = doc.select("script").firstOrNull { it.data().contains("\"data\":") }?.data() for (i in 0 until enArray.length()) {
val chapter = enArray.getJSONObject(i)
val chapters = ArrayList<MangaChapter>() val name = chapter.optString("name", null) ?: continue
if (!scriptTag.isNullOrEmpty()) { val uploadDateStr = chapter.optString("createdAt", null)
val jsonData = JSONObject(scriptTag) val uploadDate = chapterDateFormat.tryParse(uploadDateStr)
val href = "${idManga}/${name}?lang=EN"
val viChaptersArray: JSONArray = jsonData.optJSONArray("data") ?: JSONArray() chapters.add(
val enChaptersArray: JSONArray = jsonData.optJSONArray("chapterListEn") ?: JSONArray() MangaChapter(
id = generateUid(href),
listOf( title = if (name.toFloatOrNull() != null) "Chapter $name" else name,
Pair("Tiếng Việt", viChaptersArray), number = name.toFloatOrNull() ?: (i + 1).toFloat(),
Pair("English", enChaptersArray), url = "/read/${href}",
).flatMapTo(chapters) { (branch: String, chaptersArray: JSONArray) -> scanlator = null,
List(chaptersArray.length()) { i -> uploadDate = uploadDate,
val chapterObj = chaptersArray.getJSONObject(i) branch = "English",
val chapterName = chapterObj.getStringOrNull("name") source = source,
val uploadDateStr = chapterObj.getStringOrNull("createdAt") volume = 0
val uploadDate = chapterDateFormat.tryParse(uploadDateStr) )
)
if (!chapterName.isNullOrEmpty()) {
MangaChapter(
id = generateUid("${manga.url}/$chapterName?lang=${if (branch == "Tiếng Việt") "VI" else "EN"}"),
title = chapterName,
number = chapterName.toFloatOrNull() ?: (i + 1).toFloat(),
url = "/read/${manga.id}/$chapterName?lang=${if (branch == "Tiếng Việt") "VI" else "EN"}",
scanlator = null,
uploadDate = uploadDate,
branch = branch,
source = source,
volume = 0,
)
} else null
}.filterNotNull()
}
} }
return manga.copy( return chapters
tags = tags,
state = state,
description = description,
altTitles = altTitles.orEmpty(),
chapters = chapters,
)
} }
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
return doc.select("img.m-auto.read-image.w-auto.h-auto.md\\:min-h-\\[800px\\].min-h-\\[300px\\]") return doc.select("img.m-auto.read-image.w-auto.h-auto.md\\:min-h-\\[800px\\].min-h-\\[300px\\]")
@ -203,7 +259,7 @@ internal class NhentaiWorld(context: MangaLoaderContext) :
} }
} }
private suspend fun fetchTags(): Set<MangaTag> { private suspend fun fetchTags(): Set<MangaTag> { // TODO
val doc = webClient.httpGet( val doc = webClient.httpGet(
urlBuilder() urlBuilder()
.addPathSegment("genre") .addPathSegment("genre")
@ -221,5 +277,4 @@ internal class NhentaiWorld(context: MangaLoaderContext) :
} }
} }
} }
} }
Loading…
Cancel
Save