[Hentai18VN] Add source

master
Draken 1 year ago
parent f10e6f021c
commit fcd6e92dba

@ -1,17 +1,21 @@
package org.koitharu.kotatsu.parsers.site.vi package org.koitharu.kotatsu.parsers.site.vi
import androidx.collection.arraySetOf import androidx.collection.arraySetOf
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import org.jsoup.nodes.Document
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.network.UserAgents
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.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("HENTAI18VN", "Hentai18VN", "vi", type = ContentType.HENTAI) @MangaSourceParser("HENTAI18VN", "Hentai18VN", "vi", type = ContentType.HENTAI)
internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.HENTAI18VN, 60) { internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.HENTAI18VN, 30) {
override val configKeyDomain = ConfigKey.Domain("hentai18vn.art") override val configKeyDomain = ConfigKey.Domain("hentai18vn.art")
@ -20,18 +24,220 @@ internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(contex
keys.add(userAgentKey) keys.add(userAgentKey)
} }
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isSearchSupported = true, isSearchSupported = true,
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = fetchAvailableTags())
availableTags = fetchAvailableTags(), override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
SortOrder.POPULARITY,
SortOrder.UPDATED
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
return when {
!filter.query.isNullOrEmpty() -> {
if (page > 1) {
return emptyList()
}
val url = "https://$domain/search/html/1".toHttpUrl()
val body = JSONObject().apply {
put("keyword", filter.query.urlEncoded())
}
val headers = Headers.Builder()
.add("X-Requested-With", "XMLHttpRequest")
.build()
val response = webClient.httpPost(url, body, headers).parseHtml()
parseMangaSearch(response)
}
!filter.tags.isNullOrEmpty() -> {
val tag = filter.tags.first()
val url = buildString {
append("https://")
append(domain)
append("/the-loai/")
append(tag.key)
if (page > 1) {
append("?page=")
append(page)
}
}
val response = webClient.httpGet(url).parseHtml()
parseMangaList(response)
}
else -> {
val url = buildString {
append("https://")
append(domain)
append("/")
append(
when (order) {
SortOrder.NEWEST -> "danh-sach/truyen-hentai-moi"
SortOrder.POPULARITY -> "danh-sach/truyen-hentai-hot"
SortOrder.UPDATED -> "danh-sach/truyen-hentai-hoan-thanh"
else -> "danh-sach/truyen-hentai-hay"
}
)
if (page > 1) {
append("?page=")
append(page)
}
}
val response = webClient.httpGet(url).parseHtml()
parseMangaList(response)
}
}
}
private fun parseMangaSearch(doc: Document): List<Manga> {
return doc.select("ul li a.item").map { a ->
val href = a.attr("href")
val img = a.selectFirst("img")!!
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(domain),
title = img.attr("alt"),
altTitle = null,
author = null,
tags = emptySet(),
rating = RATING_UNKNOWN,
state = null,
coverUrl = img.attr("src"),
isNsfw = isNsfwSource,
source = source
)
}
}
private fun parseMangaList(doc: Document): List<Manga> {
return doc.select("div.visual").map { div ->
val a = div.selectFirst("div.main_text h3.title a")!!
val img = div.selectFirst("div.hentai-cover img")!!
val mangaUrl = a.attr("href")
Manga(
id = generateUid(mangaUrl),
publicUrl = mangaUrl,
url = mangaUrl.removePrefix("https://$domain"),
title = a.text(),
altTitle = null,
author = null,
description = null,
tags = emptySet(),
rating = RATING_UNKNOWN,
state = null,
coverUrl = img.attr("data-original").takeIf { it.isNotEmpty() } ?: img.attr("src"),
isNsfw = isNsfwSource,
source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val tags = doc.select("div.hentai-info .line-content a.item-tag")
.mapNotNull { a ->
MangaTag(
title = a.text(),
key = a.attr("href").substringAfterLast("/"),
source = source
)
}.toSet()
val chapters = doc.select("ul#chapter-list li.citem").map { li ->
val a = li.selectFirst("a")!!
val number = a.text().substringAfter("Chap ").toFloatOrNull() ?: 0f
MangaChapter(
id = generateUid(a.attr("href")),
name = a.text(),
number = number,
url = a.attr("href").removePrefix("https://$domain"),
uploadDate = parseChapterDate(li.selectFirst(".time")?.text()),
source = source,
scanlator = null,
branch = null,
volume = 0
)
}
val altTitle = doc.selectFirst("h2.alternative")?.text()
val author = doc.selectFirst("div.hentai-info .line:contains(Tác giả) .line-content")?.text()
val state = when(doc.selectFirst("div.hentai-info .line:contains(Tình trạng) .line-content")?.text()) {
"Đang cập nhật" -> MangaState.ONGOING
"Hoàn thành" -> MangaState.FINISHED
else -> null
}
return manga.copy(
tags = tags,
author = author?.takeUnless { it == "Đang cập nhật" },
altTitle = altTitle,
state = state,
chapters = chapters,
description = doc.select("div.about").text()
)
}
private fun parseChapterDate(date: String?): Long {
if (date == null) return 0
return try {
val now = SimpleDateFormat("dd/MM/yyyy", Locale.US)
now.parse(date)?.time ?: 0L
} catch (e: Exception) {
0L
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
return doc.select("div.chapter-content div.item-photo img").mapNotNull { img ->
val url = img.requireSrc()
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source
)
}
}
private suspend fun fetchAvailableTags(): Set<MangaTag> {
val tags = arraySetOf<MangaTag>()
val firstPage = webClient.httpGet("https://$domain/tim-the-loai").parseHtml()
val lastPage = firstPage.selectFirst("a[aria-label=Last]")
?.attr("href")
?.substringAfter("page=")
?.toIntOrNull() ?: 1
for (page in 1..lastPage) {
val doc = if (page == 1) {
firstPage
} else {
webClient.httpGet("https://$domain/tim-the-loai?page=$page").parseHtml()
}
doc.select("ul.list-tags li").forEach { li ->
val a = li.selectFirst("a") ?: return@forEach
val title = a.selectFirst("h3.tag-name")?.text()?.trim() ?: return@forEach
val url = a.attr("href")
tags.add(
MangaTag(
title = title,
key = url.substringAfterLast("/"),
source = source
)
)
}
}
return tags
}
} }
Loading…
Cancel
Save