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