[NHentai] New manga source #8
parent
0ed35a4b21
commit
c67b255ba9
@ -0,0 +1,189 @@
|
||||
package org.koitharu.kotatsu.parsers.site
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaParser
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("NHENTAI", "N-Hentai")
|
||||
class NHentaiParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.NHENTAI) {
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain
|
||||
get() = ConfigKey.Domain("nhentai.net", null)
|
||||
|
||||
override val sortOrders: Set<SortOrder>
|
||||
get() = EnumSet.of(SortOrder.NEWEST, SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (query.isNullOrEmpty() && tags != null && tags.size > 1) {
|
||||
return getList(offset, buildQuery(tags), emptySet(), sortOrder)
|
||||
}
|
||||
val domain = getDomain()
|
||||
val page = (offset / 25) + 1
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("/search/?q=")
|
||||
append(query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
} else {
|
||||
append('/')
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
val tag = tags.single()
|
||||
append("tag/")
|
||||
append(tag.key)
|
||||
append('/')
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("popular")
|
||||
}
|
||||
append("?page=")
|
||||
append(page)
|
||||
} else {
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("?sort=popular&page=")
|
||||
} else {
|
||||
append("?page=")
|
||||
}
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
val root = context.httpGet(url).parseHtml().body().getElementById("content")
|
||||
?.selectLast("div.index-container") ?: parseFailed("Root not found")
|
||||
val regexBrackets = Regex("\\[[^]]+]|\\([^)]+\\)")
|
||||
val regexSpaces = Regex("\\s+")
|
||||
return root.select(".gallery").map { div ->
|
||||
val a = div.selectFirstOrThrow("a.cover")
|
||||
val href = a.attrAsRelativeUrl("href")
|
||||
val img = div.selectFirstOrThrow("img")
|
||||
val title = div.selectFirstOrThrow(".caption").text()
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = title.replace(regexBrackets, "")
|
||||
.replace(regexSpaces, " ")
|
||||
.trim(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = img.attrAsAbsoluteUrlOrNull("data-src")
|
||||
?: img.attrAsAbsoluteUrl("src"),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
largeCoverUrl = null,
|
||||
description = null,
|
||||
chapters = listOf(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val root = context.httpGet(
|
||||
url = manga.url.toAbsoluteUrl(getDomain())
|
||||
).parseHtml().body().requireElementById("bigcontainer")
|
||||
val img = root.requireElementById("cover").selectFirstOrThrow("img")
|
||||
val tagContainers = root.requireElementById("tags").select(".tag-container")
|
||||
val dateFormat = SimpleDateFormat(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSSSS'+00:00'",
|
||||
Locale.ROOT,
|
||||
)
|
||||
return manga.copy(
|
||||
tags = tagContainers.find { x -> x.ownText() == "Tags:" }?.parseTags() ?: manga.tags,
|
||||
author = tagContainers.find { x -> x.ownText() == "Artists:" }
|
||||
?.selectFirst("span.name")?.text()?.toCamelCase(),
|
||||
largeCoverUrl = img.attrAsAbsoluteUrlOrNull("data-src")
|
||||
?: img.attrAsAbsoluteUrl("src"),
|
||||
description = null,
|
||||
chapters = listOf(
|
||||
MangaChapter(
|
||||
id = manga.id,
|
||||
name = manga.title,
|
||||
number = 1,
|
||||
url = manga.url,
|
||||
scanlator = null,
|
||||
uploadDate = dateFormat.tryParse(
|
||||
tagContainers.find { x -> x.ownText() == "Uploaded:" }
|
||||
?.selectFirst("time")
|
||||
?.attr("datetime")
|
||||
),
|
||||
branch = null,
|
||||
source = source,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val url = chapter.url.toAbsoluteUrl(getDomain())
|
||||
val root = context.httpGet(url).parseHtml().requireElementById("thumbnail-container")
|
||||
return root.select(".thumb-container").map { div ->
|
||||
val a = div.selectFirstOrThrow("a")
|
||||
val img = div.selectFirstOrThrow("img")
|
||||
val href = a.attrAsRelativeUrl("href")
|
||||
MangaPage(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
referer = url,
|
||||
preview = img.attrAsAbsoluteUrlOrNull("data-src")
|
||||
?: img.attrAsAbsoluteUrl("src"),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPageUrl(page: MangaPage): String {
|
||||
val root = context.httpGet(page.url.toAbsoluteUrl(getDomain())).parseHtml().body()
|
||||
.requireElementById("image-container")
|
||||
return root.selectFirstOrThrow("img").attrAsAbsoluteUrl("src")
|
||||
}
|
||||
|
||||
override suspend fun getTags(): Set<MangaTag> {
|
||||
return coroutineScope {
|
||||
// parse first 3 pages of tags
|
||||
(1..3).map { page ->
|
||||
async { getTags(page) }
|
||||
}
|
||||
}.awaitAll().flattenTo(ArraySet(360))
|
||||
}
|
||||
|
||||
private suspend fun getTags(page: Int): Set<MangaTag> {
|
||||
val root = context.httpGet("https://${getDomain()}/tags/popular?page=$page").parseHtml().body()
|
||||
.getElementById("tag-container")
|
||||
return root?.parseTags().orEmpty()
|
||||
}
|
||||
|
||||
private fun Element.parseTags() = select("a.tag").mapToSet { a ->
|
||||
val href = a.attr("href").removeSuffix('/')
|
||||
MangaTag(
|
||||
title = a.selectFirstOrThrow(".name").text().toTitleCase(),
|
||||
key = href.substringAfterLast('/'),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildQuery(tags: Collection<MangaTag>) = tags.joinToString(separator = " ") { tag ->
|
||||
"tag:\"${tag.key}\""
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue