[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