Merge pull request #355 from KotatsuApp/GalleryAdultsParser

Add GalleryAdultsParser
Koitharu 3 years ago committed by GitHub
commit face1d5b26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,187 +0,0 @@
package org.koitharu.kotatsu.parsers.site.all
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.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser
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", type = ContentType.HENTAI)
class NHentaiParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.NHENTAI, pageSize = 25) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("nhentai.net")
override val sortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.NEWEST, SortOrder.POPULARITY)
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (query.isNullOrEmpty() && tags != null && tags.size > 1) {
return getListPage(page, buildQuery(tags), emptySet(), sortOrder)
}
val domain = domain
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 = webClient.httpGet(url).parseHtml().body().requireElementById("content")
.selectLastOrThrow("div.index-container")
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 = webClient.httpGet(
url = manga.url.toAbsoluteUrl(domain),
).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(domain)
val root = webClient.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,
preview = img.attrAsAbsoluteUrlOrNull("data-src")
?: img.attrAsAbsoluteUrl("src"),
source = source,
)
}
}
override suspend fun getPageUrl(page: MangaPage): String {
val root = webClient.httpGet(page.url.toAbsoluteUrl(domain)).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 = webClient.httpGet("https://${domain}/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}\""
}
}

@ -1,23 +1,27 @@
package org.koitharu.kotatsu.parsers.site.all package org.koitharu.kotatsu.parsers.site.galleryadults
import androidx.collection.ArraySet import androidx.collection.ArraySet
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
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.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@MangaSourceParser("HENTAIFOX", "HentaiFox", type = ContentType.HENTAI) internal abstract class GalleryAdultsParser(
internal class HentaiFox(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.HENTAIFOX, 20) { context: MangaLoaderContext,
source: MangaSource,
domain: String,
pageSize: Int = 20,
) : PagedMangaParser(context, source, pageSize) {
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED) override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("hentaifox.com") override val configKeyDomain = ConfigKey.Domain(domain)
override suspend fun getListPage( override suspend fun getListPage(
page: Int, page: Int,
@ -26,49 +30,48 @@ internal class HentaiFox(context: MangaLoaderContext) : PagedMangaParser(context
sortOrder: SortOrder, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val tag = tags.oneOrThrowIfMany() val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
if (!tags.isNullOrEmpty()) { if (!tags.isNullOrEmpty()) {
append("/tag/") append("/tag/")
append(tag?.key.orEmpty()) append(tag?.key.orEmpty())
if (page > 1) { append("/?")
append("/pag/")
append(page)
append("/")
}
} else if (!query.isNullOrEmpty()) { } else if (!query.isNullOrEmpty()) {
append("/search/?q=") append("/search/?q=")
append(query.urlEncoded()) append(query.urlEncoded())
if (page > 1) { append("&")
append("&page=")
append(page)
}
} else { } else {
if (page > 2) { append("/?")
append("/pag/")
append(page)
append("/")
} else if (page > 1) {
append("/page/")
append(page)
append("/")
} }
append("page=")
append(page)
} }
return parseMangaList(webClient.httpGet(url).parseHtml())
} }
val doc = webClient.httpGet(url).parseHtml()
return doc.select(".lc_galleries .thumb").map { div -> protected open val selectGallery = ".preview_item"
val href = div.selectFirstOrThrow(".inner_thumb a").attrAsRelativeUrl("href") protected open val selectGalleryLink = ".inner_thumb a"
protected open val selectGalleryImg = ".inner_thumb img"
protected open val selectGalleryTitle = "h2"
protected open fun parseMangaList(doc: Document): List<Manga> {
val regexBrackets = Regex("\\[[^]]+]|\\([^)]+\\)")
val regexSpaces = Regex("\\s+")
return doc.select(selectGallery).map { div ->
val href = div.selectFirstOrThrow(selectGalleryLink).attrAsRelativeUrl("href")
Manga( Manga(
id = generateUid(href), id = generateUid(href),
title = div.select("h2.g_title").text(), title = div.select(selectGalleryTitle).text().replace(regexBrackets, "")
.replace(regexSpaces, " ")
.trim(),
altTitle = null, altTitle = null,
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfwSource, isNsfw = isNsfwSource,
coverUrl = div.selectFirstOrThrow("img").src().orEmpty(), coverUrl = div.selectFirstOrThrow(selectGalleryImg).src().orEmpty(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, author = null,
@ -87,37 +90,38 @@ internal class HentaiFox(context: MangaLoaderContext) : PagedMangaParser(context
}.awaitAll().flattenTo(ArraySet(360)) }.awaitAll().flattenTo(ArraySet(360))
} }
protected open val pathTagUrl = "/tags/popular/pag/"
protected open val selectTags = ".tags_page ul.tags li"
private suspend fun getTags(page: Int): Set<MangaTag> { private suspend fun getTags(page: Int): Set<MangaTag> {
val url = "https://$domain/tags/popular/pag/$page/" val url = "https://$domain$pathTagUrl$page"
val root = webClient.httpGet(url).parseHtml() val root = webClient.httpGet(url).parseHtml().selectFirstOrThrow(selectTags)
return root.parseTags() return root.parseTags()
} }
private fun Element.parseTags() = select(".list_tags a.tag_btn").mapToSet { protected open fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet {
val key = it.attr("href").removeSuffix('/').substringAfterLast('/') val key = it.attr("href").removeSuffix('/').substringAfterLast('/')
val name = it.selectFirst(".item_name")?.text() ?: it.text()
MangaTag( MangaTag(
key = key, key = key,
title = it.selectFirstOrThrow("h3").text(), title = name,
source = source, source = source,
) )
} }
protected open val selectTag = "div.tags:contains(Tags:) .tag_list"
protected open val selectAuthor = "ul.artists a.tag_btn"
protected open val urlReplaceBefore = "/g/"
protected open val urlReplaceAfter = "/gallery/"
protected open val selectLanguageChapter = "div.tags:contains(Languages:) .tag_list a span.tag"
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 urlChapters = manga.url.replace("/gallery/", "/g/") + "1/" val urlChapters = manga.url.replace(urlReplaceBefore, urlReplaceAfter) + "1/"
val tag = doc.selectFirstOrThrow(selectTag)
return manga.copy( return manga.copy(
altTitle = null, tags = tag.parseTags(),
tags = doc.select("ul.tags a.tag_btn ").mapNotNullToSet { author = doc.selectFirst(selectAuthor)?.html()?.substringBefore("<span"),
val key = it.attr("href").removeSuffix('/').substringAfterLast('/')
MangaTag(
key = key,
title = it.html().substringBefore("<span"),
source = source,
)
},
author = doc.selectFirst("ul.artists a.tag_btn")?.html()?.substringBefore("<span"),
description = null,
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(
id = manga.id, id = manga.id,
@ -126,7 +130,7 @@ internal class HentaiFox(context: MangaLoaderContext) : PagedMangaParser(context
url = urlChapters, url = urlChapters,
scanlator = null, scanlator = null,
uploadDate = 0, uploadDate = 0,
branch = doc.selectFirstOrThrow("ul.languages a.tag_btn").html().substringBefore("<span"), branch = doc.selectFirst(selectLanguageChapter)?.html()?.substringBefore("<"),
source = source, source = source,
), ),
), ),
@ -134,31 +138,14 @@ internal class HentaiFox(context: MangaLoaderContext) : PagedMangaParser(context
} }
override suspend fun getRelatedManga(seed: Manga): List<Manga> { override suspend fun getRelatedManga(seed: Manga): List<Manga> {
val doc = webClient.httpGet(seed.url.toAbsoluteUrl(domain)).parseHtml() return parseMangaList(webClient.httpGet(seed.url.toAbsoluteUrl(domain)).parseHtml())
val root = doc.body().selectFirstOrThrow(".related_galleries")
return root.select("div.thumb").mapNotNull { div ->
val a = div.selectFirst(".inner_thumb a") ?: return@mapNotNull null
val href = a.attrAsRelativeUrl("href")
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(a.host ?: domain),
altTitle = null,
title = div.selectFirstOrThrow("h2.g_title").text(),
author = null,
coverUrl = div.selectFirst("img")?.src().orEmpty(),
tags = emptySet(),
rating = RATING_UNKNOWN,
state = null,
isNsfw = isNsfwSource,
source = source,
)
}
} }
protected open val selectTotalPage = ".total_pages"
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()
val totalPages = doc.selectFirstOrThrow(".total_pages").text().toInt() val totalPages = doc.selectFirstOrThrow(selectTotalPage).text().toInt()
val rawUrl = chapter.url.replace("/1/", "/") val rawUrl = chapter.url.replace("/1/", "/")
return (1..totalPages).map { return (1..totalPages).map {
val url = "$rawUrl$it/" val url = "$rawUrl$it/"
@ -171,9 +158,11 @@ internal class HentaiFox(context: MangaLoaderContext) : PagedMangaParser(context
} }
} }
protected open val idImg = "gimg"
override suspend fun getPageUrl(page: MangaPage): String { override suspend fun getPageUrl(page: MangaPage): String {
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
val root = doc.body() val root = doc.body()
return root.requireElementById("gimg").attrAsAbsoluteUrl("data-src") return root.requireElementById(idImg).src() ?: root.parseFailed("Image src not found")
} }
} }

@ -0,0 +1,30 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.removeSuffix
@MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI)
internal class AsmHentai(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.ASMHENTAI, "asmhentai.com") {
override val selectGalleryLink = ".image a"
override val selectGalleryImg = ".image img"
override val pathTagUrl = "/tags/?page="
override val selectAuthor = "div.tags:contains(Artists:) .tag_list a span.tag"
override val selectTotalPage = ".tp"
override val idImg = "fimg"
override fun Element.parseTags() = select("a").mapToSet {
val key = it.attr("href").removeSuffix('/').substringAfterLast('/')
val name = it.selectFirst(".tag")?.html()?.substringBefore("<") ?: it.html().substringBefore("<")
MangaTag(
key = key,
title = name,
source = source,
)
}
}

@ -0,0 +1,75 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import org.koitharu.kotatsu.parsers.util.urlEncoded
@MangaSourceParser("HENTAIERA", "HentaiEra", type = ContentType.HENTAI)
internal class HentaiEra(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.HENTAIERA, "hentaiera.com", 25) {
override val selectGallery = ".thumb"
override val pathTagUrl = "/tags/popular?page="
override val selectTags = ".tags_section"
override val selectTag = ".galleries_info li:contains(Tags) div.info_tags"
override val selectAuthor = ".galleries_info li:contains(Artists) span.item_name"
override val urlReplaceBefore = "/gallery/"
override val urlReplaceAfter = "/view/"
override val selectLanguageChapter = ".galleries_info li:contains(Languages) div.info_tags .item_name"
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://")
append(domain)
if (!tags.isNullOrEmpty()) {
append("/tag/")
append(tag?.key.orEmpty())
append("/?")
} else if (!query.isNullOrEmpty()) {
append("/search/?key=")
append(query.urlEncoded())
append("&")
} else {
append("/?")
}
append("page=")
append(page)
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val urlChapters = manga.url.replace(urlReplaceBefore, urlReplaceAfter) + "1/"
val tag = doc.selectFirstOrThrow(selectTag)
return manga.copy(
tags = tag.parseTags(),
author = doc.selectFirst(selectAuthor)?.text(),
chapters = listOf(
MangaChapter(
id = manga.id,
name = manga.title,
number = 1,
url = urlChapters,
scanlator = null,
uploadDate = 0,
branch = doc.selectFirst(selectLanguageChapter)?.text(),
source = source,
),
),
)
}
}

@ -0,0 +1,71 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
@MangaSourceParser("HENTAIFOX", "HentaiFox", type = ContentType.HENTAI)
internal class HentaiFox(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.HENTAIFOX, "hentaifox.com") {
override val selectGallery = ".lc_galleries .thumb, .related_galleries .thumb"
override val selectTags = ".list_tags"
override val selectTag = "ul.tags"
override val urlReplaceBefore = "/gallery/"
override val urlReplaceAfter = "/g/"
override val selectLanguageChapter = "ul.languages a.tag_btn"
override val selectTotalPage = ".total_pages"
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://")
append(domain)
if (!tags.isNullOrEmpty()) {
append("/tag/")
append(tag?.key.orEmpty())
if (page > 1) {
append("/pag/")
append(page)
append("/")
}
} else if (!query.isNullOrEmpty()) {
append("/search/?q=")
append(query.urlEncoded())
if (page > 1) {
append("&page=")
append(page)
}
} else {
if (page > 2) {
append("/pag/")
append(page)
append("/")
} else if (page > 1) {
append("/page/")
append(page)
append("/")
}
}
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
override fun Element.parseTags() = select("a").mapToSet {
val key = it.attr("href").removeSuffix('/').substringAfterLast('/')
val name = it.html().substringBefore("<")
MangaTag(
key = key,
title = name,
source = source,
)
}
}

@ -0,0 +1,19 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
@MangaSourceParser("HENTAIROX", "HentaiRox", type = ContentType.HENTAI)
internal class HentaiRox(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.HENTAIROX, "hentairox.com") {
override val selectGallery = ".thumb"
override val pathTagUrl = "/tags/popular?page="
override val selectTags = ".gtags"
override val selectTag = "li:contains(Tags:)"
override val selectAuthor = "li:contains(Artists:) span.item_name"
override val urlReplaceBefore = "/gallery/"
override val urlReplaceAfter = "/view/"
override val selectLanguageChapter = "li:contains(Languages:) .item_name"
}

@ -0,0 +1,79 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
@MangaSourceParser("NHENTAI", "NHentai", type = ContentType.HENTAI)
internal class NHentaiParser(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.NHENTAI, "nhentai.net", 25) {
override val selectGallery = "div.index-container:not(.index-popular) .gallery, #related-container .gallery"
override val selectGalleryLink = "a"
override val selectGalleryImg = "img"
override val selectGalleryTitle = ".caption"
override val pathTagUrl = "/tags/popular?page="
override val selectTags = "#tag-container a"
override val selectTag = ".tag-container:contains(Tags:) span.tags"
override val selectAuthor = "#tags div.tag-container:contains(Artists:) span.name"
override val urlReplaceBefore = "/g/"
override val urlReplaceAfter = "/g/"
override val selectLanguageChapter =
".tag-container:contains(Languages:) span.tags a:not(.tag-17249) span.name" // tag-17249 = translated
override val selectTotalPage = ".num-pages"
override val idImg = "image-container"
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (query.isNullOrEmpty() && tags != null && tags.size > 1) {
return getListPage(page, buildQuery(tags), emptySet(), sortOrder)
}
val url = buildString {
append("https://")
append(domain)
if (!tags.isNullOrEmpty()) {
val tag = tags.single()
append("/tag/")
append(tag.key)
append("/?")
} else if (!query.isNullOrEmpty()) {
append("/search/?q=")
append(query.urlEncoded())
append("&")
} else {
append("/?")
}
append("page=")
append(page)
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
override suspend fun getPageUrl(page: MangaPage): String {
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
val root = doc.body()
return root.requireElementById(idImg).selectFirstOrThrow("img").src() ?: root.parseFailed("Image src not found")
}
override fun Element.parseTags() = select("a").mapToSet {
val key = it.attr("href").removeSuffix('/').substringAfterLast('/')
val name = it.selectFirst(".name")?.text() ?: it.text()
MangaTag(
key = key,
title = name,
source = source,
)
}
private fun buildQuery(tags: Collection<MangaTag>) = tags.joinToString(separator = " ") { tag ->
"tag:\"${tag.key}\""
}
}
Loading…
Cancel
Save