New Template GattsuParser
Add Baozimh, MuitoHentai, TopReadManhwa, HentaiSeason, HentaiTokyo, MundoHentaiOficial, UniversoHentaipull/428/head
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=*®ion=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=*®ion=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…
Reference in New Issue