Merge pull request #222 from davvarrr/master

add new template and source
Koitharu 3 years ago committed by GitHub
commit c945434d71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -48,7 +48,7 @@ class NicovideoSeigaParser(context: MangaLoaderContext) :
!query.isNullOrEmpty() -> return if (offset == 0) getSearchList(query, page) else emptyList() !query.isNullOrEmpty() -> return if (offset == 0) getSearchList(query, page) else emptyList()
tags.isNullOrEmpty() -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}" tags.isNullOrEmpty() -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}"
tags.size == 1 -> "https://$domain/manga/list?category=${tags.first().key}&page=$page" + tags.size == 1 -> "https://$domain/manga/list?category=${tags.first().key}&page=$page" +
"&sort=${getSortKey(sortOrder)}" "&sort=${getSortKey(sortOrder)}"
tags.size > 1 -> throw IllegalArgumentException("This source supports only 1 category") tags.size > 1 -> throw IllegalArgumentException("This source supports only 1 category")
else -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}" else -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}"

@ -438,31 +438,31 @@ internal abstract class MadaraParser(
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
d.endsWith(" ago") || d.endsWith(" atrás") || // Handle translated 'ago' in Portuguese. d.endsWith(" ago") || d.endsWith(" atrás") || // Handle translated 'ago' in Portuguese.
d.startsWith("") || // other translated 'ago' in Portuguese. d.startsWith("") || // other translated 'ago' in Portuguese.
d.endsWith(" hace") || // other translated 'ago' in Spanish d.endsWith(" hace") || // other translated 'ago' in Spanish
d.endsWith(" назад") || // other translated 'ago' in Russian d.endsWith(" назад") || // other translated 'ago' in Russian
d.endsWith(" önce") || // Handle translated 'ago' in Turkish. d.endsWith(" önce") || // Handle translated 'ago' in Turkish.
d.endsWith(" trước") || // Handle translated 'ago' in Viêt Nam. d.endsWith(" trước") || // Handle translated 'ago' in Viêt Nam.
d.startsWith("il y a") || // Handle translated 'ago' in French. d.startsWith("il y a") || // Handle translated 'ago' in French.
//If there is no ago but just a motion of time //If there is no ago but just a motion of time
// short Hours // short Hours
d.endsWith(" h") || d.endsWith(" h") ||
// short Day // short Day
d.endsWith(" d") || d.endsWith(" d") ||
// Day in Portuguese // Day in Portuguese
d.endsWith(" días") || d.endsWith(" día") || d.endsWith(" días") || d.endsWith(" día") ||
// Day in French // Day in French
d.endsWith(" jour") || d.endsWith(" jours") || d.endsWith(" jour") || d.endsWith(" jours") ||
// Hours in Portuguese // Hours in Portuguese
d.endsWith(" horas") || d.endsWith(" hora") || d.endsWith(" horas") || d.endsWith(" hora") ||
// Hours in french // Hours in french
d.endsWith(" heure") || d.endsWith(" heures") || d.endsWith(" heure") || d.endsWith(" heures") ||
// Minutes in English // Minutes in English
d.endsWith(" mins") || d.endsWith(" mins") ||
// Minutes in Portuguese // Minutes in Portuguese
d.endsWith(" minutos") || d.endsWith(" minuto") || d.endsWith(" minutos") || d.endsWith(" minuto") ||
//Minutes in French //Minutes in French
d.endsWith(" minute") || d.endsWith(" minutes") -> parseRelativeDate(date) d.endsWith(" minute") || d.endsWith(" minutes") -> parseRelativeDate(date)
// Handle 'yesterday' and 'today', using midnight // Handle 'yesterday' and 'today', using midnight
d.startsWith("year") -> Calendar.getInstance().apply { d.startsWith("year") -> Calendar.getInstance().apply {
@ -559,10 +559,10 @@ internal abstract class MadaraParser(
private fun createRequestTemplate() = private fun createRequestTemplate() =
("action=madara_load_more&page=1&template=madara-core%2Fcontent%2Fcontent-search&vars%5Bs%5D=&vars%5B" + ("action=madara_load_more&page=1&template=madara-core%2Fcontent%2Fcontent-search&vars%5Bs%5D=&vars%5B" +
"orderby%5D=meta_value_num&vars%5Bpaged%5D=1&vars%5Btemplate%5D=search&vars%5Bmeta_query" + "orderby%5D=meta_value_num&vars%5Bpaged%5D=1&vars%5Btemplate%5D=search&vars%5Bmeta_query" +
"%5D%5B0%5D%5Brelation%5D=AND&vars%5Bmeta_query%5D%5Brelation%5D=OR&vars%5Bpost_type" + "%5D%5B0%5D%5Brelation%5D=AND&vars%5Bmeta_query%5D%5Brelation%5D=OR&vars%5Bpost_type" +
"%5D=wp-manga&vars%5Bpost_status%5D=publish&vars%5Bmeta_key%5D=_latest_update&vars%5Border" + "%5D=wp-manga&vars%5Bpost_status%5D=publish&vars%5Bmeta_key%5D=_latest_update&vars%5Border" +
"%5D=desc&vars%5Bmanga_archives_item_layout%5D=default").split( "%5D=desc&vars%5Bmanga_archives_item_layout%5D=default").split(
'&', '&',
).map { ).map {
val pos = it.indexOf('=') val pos = it.indexOf('=')

@ -0,0 +1,216 @@
package org.koitharu.kotatsu.parsers.site.manga18
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 datePattern = "dd-MM-yyyy"
init {
paginator.firstPage = 1
searchPaginator.firstPage = 1
}
@JvmField
protected val ongoing: Set<String> = setOf(
"On Going",
)
@JvmField
protected val finished: Set<String> = setOf(
"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.selectFirstOrThrow("a").attrAsRelativeUrl("href")
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").removeSuffix('/').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.selectFirstOrThrow(selectdesc).html()
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.selectFirstOrThrow("a")
val href = a.attrAsRelativeUrl("href")
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,94 @@
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.ContentType
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.manga18.Manga18Parser
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
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.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", ContentType.HENTAI)
internal class Hentai3zCc(context: MangaLoaderContext) :
Manga18Parser(context, MangaSource.HENTAI3ZCC, "hentai3z.cc") {
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.selectFirstOrThrow("a").attrAsRelativeUrl("href")
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,13 @@
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.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser
@MangaSourceParser("MANGA18", "Manga18", "en", ContentType.HENTAI)
internal class Manga18(context: MangaLoaderContext) :
Manga18Parser(context, MangaSource.MANGA18, "manga18.club")

@ -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.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser
@MangaSourceParser("PORNCOMIC18", "18 Porn Comic", "en", ContentType.HENTAI)
internal class PornComic18(context: MangaLoaderContext) :
Manga18Parser(context, MangaSource.PORNCOMIC18, "18porncomic.com") {
override val selectTag = "div.item:not(.info_label) div.info_value a"
}

@ -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.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser
@MangaSourceParser("TUMANHWAS", "Tumanhwas", "es", ContentType.HENTAI)
internal class Tumanhwas(context: MangaLoaderContext) :
Manga18Parser(context, MangaSource.TUMANHWAS, "tumanhwas.club") {
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,15 @@
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("LYNXSCANS", "LynxScans", "en")
internal class LynxScans(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.LYNXSCANS, "lynxscans.com", pageSize = 25, searchPageSize = 10) {
override val listUrl = "/comics"
}

@ -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.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("MANHUASCANUS", "Manhua Scan Us", "en", ContentType.HENTAI)
internal class ManhuaScanUs(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANHUASCANUS, "manhuascan.us", pageSize = 30, searchPageSize = 30) {
override val datePattern = "dd-MM-yyyy"
override val listUrl = "/manga-list"
}

@ -0,0 +1,260 @@
package org.koitharu.kotatsu.parsers.site.mmrcms
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,
SortOrder.UPDATED,
)
protected open val listeurl = "filterList"
protected open val tagUrl = "manga-list"
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é",
)
protected open val imgUpdated = "/cover/cover_250x350.jpg"
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val url = if (sortOrder == SortOrder.UPDATED) {
//the Updated page doesn't really exist, we just use the home page to weight the latest chapters, so it doesn't include tag and page management.
buildString {
append("https://")
append(domain)
if (page == 2) {
append("/STOP")
}
}
} else {
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()
if (sortOrder == SortOrder.UPDATED) {
return doc.select("div.manga-item").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val deeplink = href.substringAfterLast("/")
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = "https://$domain/uploads/manga/$deeplink$imgUpdated",
title = div.selectFirstOrThrow("a").text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = isNsfwSource,
)
}
} else {
return doc.select("div.media").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
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) ?: RATING_UNKNOWN,
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.selectFirstOrThrow(selectdesc).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").removeSuffix('/').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.selectFirstOrThrow("a")
val href = a.attrAsRelativeUrl("href")
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.mmrcms.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.mmrcms.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.mmrcms.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.mmrcms.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,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.mmrcms.MmrcmsParser
import java.util.Locale
@MangaSourceParser("MANGAFR", "Manga Fr", "fr")
internal class MangaFr(context: MangaLoaderContext) :
MmrcmsParser(context, MangaSource.MANGAFR, "manga-fr.me") {
override val imgUpdated = ".jpg"
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.mmrcms.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 imgUpdated = ".jpg"
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.mmrcms.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.mmrcms.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.mmrcms.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.mmrcms.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,69 @@
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.model.SortOrder
import org.koitharu.kotatsu.parsers.site.mmrcms.MmrcmsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
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
//temporary
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
)
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).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").removeSuffix('/').substringAfterLast('/'),
title = a.text().toTitleCase(),
source = source,
)
},
author = auth,
description = desc,
altTitle = null,
state = state,
chapters = chaptersDeferred.await(),
)
}
}
Loading…
Cancel
Save