add 2 new template and new source
parent
8e452f4271
commit
28135aed66
@ -0,0 +1,225 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
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.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
internal abstract class Manga18Parser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
domain: String,
|
||||
pageSize: Int = 20,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
protected open val listeurl = "list-manga"
|
||||
protected open val tagUrl = "manga-list"
|
||||
protected open val isNsfwSource = false
|
||||
protected open val datePattern = "dd-MM-yyyy"
|
||||
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
|
||||
@JvmField
|
||||
protected val ongoing: Set<String> = hashSetOf(
|
||||
"On Going",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
protected val finished: Set<String> = hashSetOf(
|
||||
"Completed",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/$listeurl/")
|
||||
append(page.toString())
|
||||
append("?search=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagUrl/")
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
}
|
||||
append("/")
|
||||
append(page.toString())
|
||||
append("?")
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/$listeurl/")
|
||||
append(page.toString())
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
append("order_by=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("lastest")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.story_item").map { div ->
|
||||
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
|
||||
coverUrl = div.selectFirst("img")?.src().orEmpty(),
|
||||
title = div.selectFirstOrThrow("div.mg_info").selectFirst("div.mg_name a")?.text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = null,
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listeurl/").parseHtml()
|
||||
return doc.select("div.grid_cate li").mapNotNullToSet { li ->
|
||||
val a = li.selectFirst("a") ?: return@mapNotNullToSet null
|
||||
val href = a.attr("href").substringAfterLast("/")
|
||||
MangaTag(
|
||||
key = href,
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected open val selectdesc = "div.detail_reviewContent"
|
||||
protected open val selectdate = "div.item p"
|
||||
protected open val selectchapter = "div.chapter_box li"
|
||||
protected open val selectState = "div.item:contains(Status) div.info_value"
|
||||
protected open val selectAlt = "div.item:contains(Other name) div.info_value"
|
||||
protected open val selectTag = "div.item:contains(Categories) div.info_value a"
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val body = doc.body().selectFirstOrThrow("div.detail_listInfo")
|
||||
|
||||
val chaptersDeferred = async { getChapters(manga, doc) }
|
||||
|
||||
val desc = doc.select(selectdesc).let {
|
||||
if (it.select("p").text().isNotEmpty()) {
|
||||
it.select("p").joinToString(separator = "\n\n") { p ->
|
||||
p.text().replace("<br>", "\n")
|
||||
}
|
||||
} else {
|
||||
it.text()
|
||||
}
|
||||
}
|
||||
|
||||
val stateDiv = body.selectFirst(selectState)
|
||||
|
||||
val state = stateDiv?.let {
|
||||
when (it.text()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val alt = doc.body().select(selectAlt).text()
|
||||
|
||||
manga.copy(
|
||||
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
|
||||
title = a.text().toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
description = desc,
|
||||
altTitle = alt,
|
||||
state = state,
|
||||
chapters = chaptersDeferred.await(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
protected open suspend fun getChapters(manga: Manga, doc: Document): List<MangaChapter> {
|
||||
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
|
||||
return doc.body().select(selectchapter).mapChapters(reversed = true) { i, li ->
|
||||
val a = li.selectFirst("a")
|
||||
val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing")
|
||||
val dateText = li.selectFirst(selectdate)?.text()
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = a.text(),
|
||||
number = i + 1,
|
||||
url = href,
|
||||
uploadDate = dateFormat.tryParse(dateText),
|
||||
source = source,
|
||||
scanlator = null,
|
||||
branch = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val script = doc.selectFirstOrThrow("script:containsData(slides_p_path)")
|
||||
val urlencoed = script.data().substringAfter('[').substringBefore(",]").replace("\"", "").split(",")
|
||||
return urlencoed.map { url ->
|
||||
val img = context.decodeBase64(url).toString(Charsets.UTF_8)
|
||||
|
||||
MangaPage(
|
||||
id = generateUid(img),
|
||||
url = img,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected fun Element.src(): String? {
|
||||
var result = absUrl("data-src")
|
||||
if (result.isEmpty()) result = absUrl("data-cfsrc")
|
||||
if (result.isEmpty()) result = absUrl("src")
|
||||
return result.ifEmpty { null }
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
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.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.site.madara.Manga18Parser
|
||||
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrlOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.host
|
||||
import org.koitharu.kotatsu.parsers.util.parseFailed
|
||||
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("HENTAI3ZCC", "Hentai3z Cc", "en")
|
||||
internal class Hentai3zCc(context: MangaLoaderContext) :
|
||||
Manga18Parser(context, MangaSource.HENTAI3ZCC, "hentai3z.cc") {
|
||||
|
||||
override val isNsfwSource = true
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/$listeurl/")
|
||||
append(pages.toString())
|
||||
append("?search=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagUrl/")
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
}
|
||||
append("/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/$listeurl/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
append("order_by=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("lastest")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
|
||||
return doc.select("div.story_item").map { div ->
|
||||
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
|
||||
coverUrl = div.selectFirst("img")?.src()
|
||||
?.replace("cover_thumb_2.webp", "cover_250x350.jpg")
|
||||
?.replace("admin.manga18.us", "bk.18porncomic.com")
|
||||
.orEmpty(),
|
||||
title = div.selectFirstOrThrow("div.mg_info").selectFirst("div.mg_name a")?.text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = null,
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
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.Manga18Parser
|
||||
|
||||
|
||||
@MangaSourceParser("MANGA18", "Manga18", "en")
|
||||
internal class Manga18(context: MangaLoaderContext) :
|
||||
Manga18Parser(context, MangaSource.MANGA18, "manga18.club") {
|
||||
|
||||
override val isNsfwSource = true
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
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.Manga18Parser
|
||||
|
||||
|
||||
@MangaSourceParser("PORNCOMIC18", "18 Porn Comic", "en")
|
||||
internal class PornComic18(context: MangaLoaderContext) :
|
||||
Manga18Parser(context, MangaSource.PORNCOMIC18, "18porncomic.com") {
|
||||
|
||||
override val selectTag = "div.item:not(.info_label) div.info_value a"
|
||||
override val isNsfwSource = true
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara.es
|
||||
|
||||
|
||||
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.Manga18Parser
|
||||
|
||||
|
||||
@MangaSourceParser("TUMANHWAS", "Tumanhwas", "es")
|
||||
internal class Tumanhwas(context: MangaLoaderContext) :
|
||||
Manga18Parser(context, MangaSource.TUMANHWAS, "tumanhwas.club") {
|
||||
|
||||
override val isNsfwSource = true
|
||||
override val selectTag = "div.item:contains(Géneros) div.info_value a"
|
||||
override val selectAlt = "div.item:contains(Títulos alternativos) div.info_value"
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mangareader.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.mangareader.MangaReaderParser
|
||||
|
||||
|
||||
@MangaSourceParser("MANHUASCANUS", "Manhua Scan Us", "en")
|
||||
internal class ManhuaScanUs(context: MangaLoaderContext) :
|
||||
MangaReaderParser(context, MangaSource.MANHUASCANUS, "manhuascan.us", pageSize = 30, searchPageSize = 30) {
|
||||
|
||||
override val isNsfwSource: Boolean = true
|
||||
override val datePattern = "dd-MM-yyyy"
|
||||
override val listUrl = "/manga-list"
|
||||
|
||||
}
|
||||
@ -0,0 +1,230 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
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.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
internal abstract class MmrcmsParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
domain: String,
|
||||
pageSize: Int = 20,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
protected open val listeurl = "filterList"
|
||||
protected open val tagUrl = "manga-list"
|
||||
protected open val isNsfwSource = false
|
||||
protected open val datePattern = "dd MMM. yyyy"
|
||||
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
|
||||
@JvmField
|
||||
protected val ongoing: Set<String> = hashSetOf(
|
||||
"On Going",
|
||||
"Ongoing",
|
||||
"En cours",
|
||||
"En curso",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
protected val finished: Set<String> = hashSetOf(
|
||||
"Completed",
|
||||
"Completo",
|
||||
"Complete",
|
||||
"Terminé",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
append("/$listeurl/")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&asc=true&author=&tag=")
|
||||
|
||||
append("&alpha=")
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
append("&cat=")
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
}
|
||||
}
|
||||
|
||||
append("&sortBy=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("views")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.media").map { div ->
|
||||
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
|
||||
coverUrl = div.selectFirst("img")?.src().orEmpty(),
|
||||
title = div.selectFirstOrThrow("div.media-body h5").text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = div.selectFirstOrThrow("span").ownText().toFloatOrNull()?.div(5f) ?: -1f,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = null,
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$tagUrl/").parseHtml()
|
||||
return doc.select("ul.list-category li").mapNotNullToSet { li ->
|
||||
val a = li.selectFirst("a") ?: return@mapNotNullToSet null
|
||||
val href = a.attr("href").substringAfterLast("cat=")
|
||||
MangaTag(
|
||||
key = href,
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected open val selectdesc = "div.well"
|
||||
protected open val selectState = "dt:contains(Statut)"
|
||||
protected open val selectAlt = "dt:contains(Autres noms)"
|
||||
protected open val selectAut = "dt:contains(Auteur(s))"
|
||||
protected open val selectTag = "dt:contains(Catégories)"
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val body = doc.body().selectFirstOrThrow("dl.dl-horizontal")
|
||||
|
||||
val chaptersDeferred = async { getChapters(manga, doc) }
|
||||
|
||||
val desc = doc.select(selectdesc).let {
|
||||
if (it.select("p").text().isNotEmpty()) {
|
||||
it.select("p").joinToString(separator = "\n\n") { p ->
|
||||
p.text().replace("<br>", "\n")
|
||||
}
|
||||
} else {
|
||||
it.text()
|
||||
}
|
||||
}
|
||||
|
||||
val stateDiv = body.selectFirst(selectState)?.nextElementSibling()
|
||||
|
||||
val state = stateDiv?.let {
|
||||
when (it.text()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val alt = doc.body().selectFirst(selectAlt)?.nextElementSibling()?.text()
|
||||
val auth = doc.body().selectFirst(selectAut)?.nextElementSibling()?.text()
|
||||
|
||||
val tags = doc.body().selectFirst(selectTag)?.nextElementSibling()?.select("a") ?: emptySet()
|
||||
|
||||
manga.copy(
|
||||
tags = tags.mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("/"),
|
||||
title = a.text().toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
author = auth,
|
||||
description = desc,
|
||||
altTitle = alt,
|
||||
state = state,
|
||||
chapters = chaptersDeferred.await(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
protected open val selectdate = "div.date-chapter-title-rtl"
|
||||
protected open val selectchapter = "ul.chapters > li:not(.btn)"
|
||||
|
||||
protected open suspend fun getChapters(manga: Manga, doc: Document): List<MangaChapter> {
|
||||
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
|
||||
|
||||
return doc.body().select(selectchapter).mapChapters(reversed = true) { i, li ->
|
||||
val a = li.selectFirst("a")
|
||||
val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing")
|
||||
val dateText = li.selectFirst(selectdate)?.text()
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = li.selectFirstOrThrow("h5").text(),
|
||||
number = i + 1,
|
||||
url = href,
|
||||
uploadDate = dateFormat.tryParse(dateText),
|
||||
source = source,
|
||||
scanlator = null,
|
||||
branch = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected open val selectPage = "div#all img"
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
|
||||
return doc.select(selectPage).map { url ->
|
||||
val img = url.src()?.toRelativeUrl(domain) ?: url.parseFailed("Image src not found")
|
||||
MangaPage(
|
||||
id = generateUid(img),
|
||||
url = img,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected fun Element.src(): String? {
|
||||
var result = absUrl("data-src")
|
||||
if (result.isEmpty()) result = absUrl("data-cfsrc")
|
||||
if (result.isEmpty()) result = absUrl("src")
|
||||
return result.ifEmpty { null }
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.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.MmrcmsParser
|
||||
|
||||
|
||||
@MangaSourceParser("READCOMICSONLINE", "Read Comics Online", "en")
|
||||
internal class ReadComicsOnline(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.READCOMICSONLINE, "readcomicsonline.ru") {
|
||||
|
||||
override val selectState = "dt:contains(Status)"
|
||||
override val selectTag = "dt:contains(Categories)"
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.es
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("MANGADOOR", "Manga Door", "es")
|
||||
internal class MangaDoor(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.MANGADOOR, "mangadoor.com") {
|
||||
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
|
||||
override val selectState = "dt:contains(Estado)"
|
||||
override val selectAlt = "dt:contains(Otros nombres)"
|
||||
override val selectAut = "dt:contains(Autor(es))"
|
||||
override val selectTag = "dt:contains(Categorías)"
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.fr
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("JPMANGAS", "JpMangas", "fr")
|
||||
internal class JpMangas(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.JPMANGAS, "jpmangas.xyz") {
|
||||
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.fr
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("LELSCANVF", "Lel Scan Vf", "fr")
|
||||
internal class LelScanVf(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.LELSCANVF, "lelscanvf.cc") {
|
||||
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.fr
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("MANGAFR", "Manga Fr", "fr")
|
||||
internal class MangaFr(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.MANGAFR, "manga-fr.me") {
|
||||
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.fr
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("MANGA_SCAN", "Manga-Scan", "fr")
|
||||
internal class MangaScan(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.MANGA_SCAN, "manga-scan.co") {
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.fr
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("SCAN_FR_ORG", "Scan-Fr Org", "fr")
|
||||
internal class ScanFrOrg(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.SCAN_FR_ORG, "www.scan-fr.org") {
|
||||
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
override val selectchapter = "ul.chapterszozo li"
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.fr
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("SCANVF", "Scan Vf", "fr")
|
||||
internal class ScanVf(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.SCANVF, "www.scan-vf.net") {
|
||||
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.id
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("KOMIKID", "KomikId", "id")
|
||||
internal class KomikId(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.KOMIKID, "komikid.com") {
|
||||
|
||||
|
||||
override val selectState = "dt:contains(Status)"
|
||||
override val selectAlt = "dt:contains(Other names)"
|
||||
override val selectAut = "dt:contains(Author(s))"
|
||||
override val selectTag = "dt:contains(Categories)"
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.id
|
||||
|
||||
|
||||
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.MmrcmsParser
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("MANGAID", "Mangaid", "id")
|
||||
internal class Mangaid(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.MANGAID, "mangaid.click") {
|
||||
|
||||
|
||||
override val selectState = "dt:contains(Status)"
|
||||
override val selectAlt = "dt:contains(Other names)"
|
||||
override val selectAut = "dt:contains(Author(s))"
|
||||
override val selectTag = "dt:contains(Categories)"
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mmrcms.pt
|
||||
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MmrcmsParser
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
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.toTitleCase
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("ANIMAREGIA", "Animaregia", "pt")
|
||||
internal class Animaregia(context: MangaLoaderContext) :
|
||||
MmrcmsParser(context, MangaSource.ANIMAREGIA, "animaregia.net") {
|
||||
|
||||
override val selectdate = "div.col-md-4"
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val body = doc.body().selectFirstOrThrow("ul.list-group")
|
||||
|
||||
val chaptersDeferred = async { getChapters(manga, doc) }
|
||||
|
||||
val desc = doc.select(selectdesc).let {
|
||||
if (it.select("p").text().isNotEmpty()) {
|
||||
it.select("p").joinToString(separator = "\n\n") { p ->
|
||||
p.text().replace("<br>", "\n")
|
||||
}
|
||||
} else {
|
||||
it.text()
|
||||
}
|
||||
}
|
||||
|
||||
val stateDiv = body.selectFirst("li.list-group-item:contains(Status)")?.lastElementChild()
|
||||
|
||||
val state = stateDiv?.let {
|
||||
when (it.text()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
val auth = doc.body().selectFirst("li.list-group-item:contains(Autor(es)) a")?.text()
|
||||
|
||||
val tags = doc.body().select("li.list-group-item:contains(Autor(es)) a") ?: emptySet()
|
||||
|
||||
manga.copy(
|
||||
tags = tags.mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("/"),
|
||||
title = a.text().toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
author = auth,
|
||||
description = desc,
|
||||
altTitle = null,
|
||||
state = state,
|
||||
chapters = chaptersDeferred.await(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue