New Template GattsuParser

Add Baozimh, MuitoHentai, TopReadManhwa, HentaiSeason, HentaiTokyo, MundoHentaiOficial, UniversoHentai
pull/428/head
devi 2 years ago
parent d8c32047d0
commit a5219ceb6c

@ -0,0 +1,150 @@
package org.koitharu.kotatsu.parsers.site.gattsu
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
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.util.*
internal abstract class GattsuParser(
context: MangaLoaderContext,
source: MangaSource,
domain: String,
pageSize: Int = 20,
) : PagedMangaParser(context, source, pageSize) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain(domain)
override val isMultipleTagsSupported = false
protected open val tagPrefix = "tag"
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
when (filter) {
is MangaListFilter.Search -> {
append("/page/")
append(page.toString())
append("/?s=")
append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
filter.tags.oneOrThrowIfMany()?.let {
append("/$tagPrefix/")
append(it.key)
}
append("/page/")
append(page.toString())
}
null -> {
append("/page/")
append(page.toString())
}
}
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
protected open fun parseMangaList(doc: Document): List<Manga> {
return doc.select("div.lista ul li, div.videos div.video").mapNotNull { li ->
val a = li.selectFirstOrThrow("a")
val href = a.attrAsAbsoluteUrl("href")
if (!href.contains(domain)) {
//Some sources include ads in manga lists
return@mapNotNull null
}
Manga(
id = generateUid(href),
url = href,
publicUrl = href,
title = li.selectLastOrThrow(".thumb-titulo, .video-titulo").text(),
coverUrl = li.selectFirst("img")?.src().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
description = null,
state = null,
author = null,
isNsfw = isNsfwSource,
source = source,
)
}
}
protected open val tagUrl = "generos"
override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$tagUrl/").parseHtml()
return doc.selectLastOrThrow(".meio-conteudo p, div.lista-tags ul").parseTags()
}
protected open fun Element.parseTags() = select("a").mapToSet {
val key = it.attr("href").removeSuffix("/").substringAfterLast("/")
val name = it.selectFirst(".tag-titulo")?.text() ?: key
MangaTag(
key = key,
title = name,
source = source,
)
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val urlChapter = doc.selectFirstOrThrow("ul.post-fotos li a, ul.paginaPostBotoes a").attrAsAbsoluteUrl("href")
return manga.copy(
description = doc.selectFirst("div.post-texto")?.html(),
tags = doc.selectFirst(".post-itens li:contains(Tags), .paginaPostInfo li:contains(Categorias)")
?.parseTags().orEmpty(),
author = doc.selectFirst(".post-itens li:contains(Autor) a, .paginaPostInfo li:contains(Artista) a")
?.text(),
chapters = listOf(
MangaChapter(
id = manga.id,
name = manga.title,
number = 1,
url = urlChapter,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
),
),
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
val totalPages =
doc.selectLastOrThrow("div.galeria-paginacao span").text().substringAfterLast("- ").substringBeforeLast(')')
.toInt()
val rawUrl = chapter.url.substringBeforeLast("=")
return (1..totalPages).map {
val url = "$rawUrl=$it"
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
override suspend fun getPageUrl(page: MangaPage): String {
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
return doc.selectFirstOrThrow("div.galeria-foto img").src() ?: doc.parseFailed("Image src not found")
}
}

@ -0,0 +1,11 @@
package org.koitharu.kotatsu.parsers.site.gattsu.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
@MangaSourceParser("HENTAISEASON", "HentaiSeason", type = ContentType.HENTAI)
internal class HentaiSeason(context: MangaLoaderContext) :
GattsuParser(context, MangaSource.HENTAISEASON, "hentaiseason.com")

@ -0,0 +1,13 @@
package org.koitharu.kotatsu.parsers.site.gattsu.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
@MangaSourceParser("HENTAITOKYO", "HentaiTokyo", type = ContentType.HENTAI)
internal class HentaiTokyo(context: MangaLoaderContext) :
GattsuParser(context, MangaSource.HENTAITOKYO, "hentaitokyo.net") {
override val tagUrl = "tags"
}

@ -0,0 +1,48 @@
package org.koitharu.kotatsu.parsers.site.gattsu.pt
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.selectLastOrThrow
import org.koitharu.kotatsu.parsers.util.src
@MangaSourceParser("MUNDOHENTAIOFICIAL", "MundoHentaiOficial", type = ContentType.HENTAI)
internal class MundoHentaiOficial(context: MangaLoaderContext) :
GattsuParser(context, MangaSource.MUNDOHENTAIOFICIAL, "mundohentaioficial.com") {
override val tagUrl = "tags"
override fun parseMangaList(doc: Document): List<Manga> {
return doc.select("div.lista ul li, div.videos div.video").mapNotNull { li ->
val a = li.selectLastOrThrow("a")
val href = a.attrAsAbsoluteUrl("href")
if (!href.contains(domain)) {
//Some sources include ads in manga lists
return@mapNotNull null
}
Manga(
id = generateUid(href),
url = href,
publicUrl = href,
title = li.selectLastOrThrow(".thumb-titulo, .video-titulo").text(),
coverUrl = li.selectFirst("img")?.src().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
description = null,
state = null,
author = null,
isNsfw = isNsfwSource,
source = source,
)
}
}
}

@ -0,0 +1,50 @@
package org.koitharu.kotatsu.parsers.site.gattsu.pt
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
import org.koitharu.kotatsu.parsers.util.*
@MangaSourceParser("UNIVERSOHENTAI", "UniversoHentai", type = ContentType.HENTAI)
internal class UniversoHentai(context: MangaLoaderContext) :
GattsuParser(context, MangaSource.UNIVERSOHENTAI, "universohentai.com") {
override val tagPrefix = "category"
override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/tags/").parseHtml()
return doc.requireElementById("menu-topo").parseTags()
}
override fun Element.parseTags() = select("a").mapNotNullToSet {
if (!it.attr("href").contains("/category/")) return@mapNotNullToSet null
val key = it.attr("href").removeSuffix("/").substringAfterLast("/")
MangaTag(
key = key,
title = it.text(),
source = source,
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val images = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml().requireElementById("galeria")
.select(".galeria-foto img")
return images.map { img ->
val urlImages = img.src() ?: img.parseFailed("Image src not found")
MangaPage(
id = generateUid(urlImages),
url = urlImages,
preview = null,
source = source,
)
}
}
override suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
}

@ -0,0 +1,12 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("TOPREADMANHWA", "TopReadManhwa", "en")
internal class TopReadManhwa(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.TOPREADMANHWA, "topreadmanhwa.com") {
override val datePattern = "MM/dd/yyyy"
}

@ -0,0 +1,126 @@
package org.koitharu.kotatsu.parsers.site.pt
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.util.*
@MangaSourceParser("MUITOHENTAI", "MuitoHentai", "pt", ContentType.HENTAI)
class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MUITOHENTAI, 24) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
override val configKeyDomain = ConfigKey.Domain("www.muitohentai.com")
override val isMultipleTagsSupported = false
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
when (filter) {
is MangaListFilter.Search -> {
if (page > 1) return emptyList()
append("/buscar-manga/?q=")
append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
append("/mangas")
filter.tags.oneOrThrowIfMany()?.let {
append("/genero/")
append(it.key)
}
append('/')
append(page.toString())
append('/')
}
null -> {
append("/mangas/")
append(page.toString())
append('/')
}
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.requireElementById("archive-content").select("article").map { div ->
val a = div.selectFirstOrThrow("a")
val href = a.attrAsAbsoluteUrl("href")
Manga(
id = generateUid(href),
url = href,
publicUrl = href,
title = div.selectLastOrThrow("h3").text(),
coverUrl = div.selectFirst("img")?.src().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
description = null,
state = null,
author = null,
isNsfw = isNsfwSource,
source = source,
)
}
}
override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/generos-dos-mangas/").parseHtml()
return doc.select("div.content a.profileSideBar").mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix("/").substringAfterLast("/"),
title = a.text(),
source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
return manga.copy(
description = doc.selectFirstOrThrow(".backgroundpost:contains(Sinopse)").html(),
tags = doc.select("a.genero_btn").mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix("/").substringAfterLast("/"),
title = a.text(),
source = source,
)
},
chapters = doc.select(".backgroundpost h3 a").mapChapters(reversed = true) { i, a ->
val href = a.attrAsAbsoluteUrl("href")
MangaChapter(
id = generateUid(href),
name = a.text(),
number = i + 1,
url = href,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
)
},
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
val data = doc.selectFirstOrThrow("script:containsData(var arr = [)").data()
val images = data.substringAfter("[").substringBefore("];").replace("\"", "").split(",")
return images.map { img ->
MangaPage(
id = generateUid(img),
url = img,
preview = null,
source = source,
)
}
}
}

@ -0,0 +1,195 @@
package org.koitharu.kotatsu.parsers.site.zh
import androidx.collection.ArrayMap
import org.json.JSONArray
import org.jsoup.nodes.Document
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 org.koitharu.kotatsu.parsers.util.json.mapJSON
import java.util.*
@MangaSourceParser("BAOZIMH", "Baozimh", "zh")
internal class Baozimh(context: MangaLoaderContext) :
PagedMangaParser(context, MangaSource.BAOZIMH, pageSize = 36) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
override val configKeyDomain = ConfigKey.Domain("www.baozimh.com")
override val isMultipleTagsSupported = false
private val tagsMap = SuspendLazy(::parseTags)
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
when (filter) {
is MangaListFilter.Search -> {
if (page > 1) return emptyList()
val url = buildString {
append("https://")
append(domain)
append("/search?q=")
append(filter.query.urlEncoded())
}
return parseMangaListSearch(webClient.httpGet(url).parseHtml())
}
is MangaListFilter.Advanced -> {
val url = buildString {
append("https://")
append(domain)
append("/api/bzmhq/amp_comic_list?filter=*&region=all")
if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let {
append("&type=")
append(it.key)
}
} else {
append("&type=all")
}
if (filter.states.isNotEmpty()) {
filter.states.oneOrThrowIfMany()?.let {
append("&state=")
append(
when (it) {
MangaState.ONGOING -> "serial"
MangaState.FINISHED -> "pub"
else -> "all"
},
)
}
} else {
append("&state=all")
}
append("&limit=36&page=")
append(page.toString())
}
return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("items"))
}
null -> {
val url = buildString {
append("https://")
append(domain)
append("/api/bzmhq/amp_comic_list?filter=*&region=all&type=all&state=all&limit=36&page=")
append(page.toString())
}
return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("items"))
}
}
}
private fun parseMangaList(json: JSONArray): List<Manga> {
return json.mapJSON { j ->
val href = "https://$domain/comic/" + j.getString("comic_id")
Manga(
id = generateUid(href),
url = href,
publicUrl = href,
coverUrl = "https://static-tw${domain.removePrefix("www")}/cover/" + j.getString("topic_img"),
title = j.getString("name"),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = j.getString("author"),
state = null,
source = source,
isNsfw = isNsfwSource,
)
}
}
private fun parseMangaListSearch(doc: Document): List<Manga> {
return doc.select("div.comics-card").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga(
id = generateUid(href),
url = href,
publicUrl = href,
coverUrl = div.selectFirst("amp-img")?.src().orEmpty(),
title = div.selectFirstOrThrow(".comics-card__title h3").text(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = isNsfwSource,
)
}
}
override suspend fun getAvailableTags(): Set<MangaTag> {
return tagsMap.get().values.toSet()
}
private suspend fun parseTags(): Map<String, MangaTag> {
val tagElements = webClient.httpGet("https://$domain/classify").parseHtml()
.select("div.nav")[3].select("a.item:not(.active)")
val tagMap = ArrayMap<String, MangaTag>(tagElements.size)
for (el in tagElements) {
val name = el.text()
if (name.isEmpty()) continue
tagMap[name] = MangaTag(
key = el.attr("href").substringAfter("type=").substringBefore("&"),
title = name,
source = source,
)
}
return tagMap
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val state = doc.selectFirstOrThrow(".tag-list span.tag").text()
val tagMap = tagsMap.get()
val selectTag = doc.select(".tag-list span.tag").drop(1)
val tags = selectTag.mapNotNullToSet { tagMap[it.text()] }
return manga.copy(
description = doc.selectFirst(".comics-detail__desc")?.text().orEmpty(),
state = when (state) {
"連載中" -> MangaState.ONGOING
"已完結" -> MangaState.FINISHED
else -> null
},
tags = tags,
chapters = doc.requireElementById("chapter-items").select("div.comics-chapters a")
.mapChapters(reversed = true) { i, a ->
val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain)
MangaChapter(
id = generateUid(url),
name = a.selectFirstOrThrow("span").text(),
number = i + 1,
url = url,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
)
},
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml().requireElementById("__nuxt")
return doc.select("button.pure-button").map { btn ->
val urlPage = btn.attr("on").substringAfter(": '").substringBefore("?t=")
MangaPage(
id = generateUid(urlPage),
url = urlPage,
preview = null,
source = source,
)
}
}
}
Loading…
Cancel
Save