add sources and fix

pull/235/head
devi 3 years ago
parent 407ef5b655
commit b74c7841dd

@ -0,0 +1,151 @@
package org.koitharu.kotatsu.parsers.site.en
import okhttp3.Headers
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.network.UserAgents
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en")
internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.COMICEXTRA, 25) {
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
override val configKeyDomain = ConfigKey.Domain("comicextra.net")
override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
.build()
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://$domain/")
if (!tags.isNullOrEmpty()) {
append(tag?.key.orEmpty())
if (page > 1) {
append(page)
}
} else if (!query.isNullOrEmpty()) {
append("comic-search?key=")
append(query.urlEncoded())
if (page > 1) {
append("&page=")
append(page)
}
} else {
when (sortOrder) {
SortOrder.POPULARITY -> append("popular-comic/")
SortOrder.UPDATED -> append("new-comic/")
SortOrder.NEWEST -> append("recent-comic/")
else -> append("new-comic/")
}
if (page > 1) {
append(page)
}
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.movie-list-index div.cartoon-box").map { div ->
val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href")
Manga(
id = generateUid(href),
title = div.selectFirstOrThrow("h3").text(),
altTitle = null,
url = href,
publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN,
isNsfw = false,
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"),
tags = emptySet(),
state = when (div.selectFirstOrThrow(".detail:contains(Stasus: )").text()) {
"Stasus: Ongoing" -> MangaState.ONGOING
"Stasus: Completed" -> MangaState.FINISHED
else -> null
},
author = null,
source = source,
)
}
}
override suspend fun getTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/popular-comic").parseHtml()
return doc.select("li.tag-item a").mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").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(
altTitle = doc.selectFirstOrThrow("dt.movie-dt:contains(Alternate name:) + dd").text(),
state = when (doc.selectFirstOrThrow("dt.movie-dt:contains(Status:) + dd a").text()) {
"Ongoing" -> MangaState.ONGOING
"Completed" -> MangaState.FINISHED
else -> null
},
tags = doc.select("dt.movie-dt:contains(Genres:) + dd a").mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").substringAfterLast("/"),
title = a.text(),
source = source,
)
},
author = doc.select("dt.movie-dt:contains(Author:) + dd").text(),
description = doc.getElementById("film-content")?.text(),
chapters = doc.requireElementById("list").select("tr")
.mapChapters(reversed = true) { i, tr ->
val a = tr.selectFirstOrThrow("a")
val url = a.attrAsRelativeUrl("href") + "/full"
val name = a.text()
val dateText = tr.select("td").last()?.text()
val dateFormat = SimpleDateFormat("MM/dd/yy", sourceLocale)
MangaChapter(
id = generateUid(url),
name = name,
number = i,
url = url,
scanlator = null,
uploadDate = dateFormat.tryParse(dateText),
branch = null,
source = source,
)
},
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return doc.select(".chapter-container img.chapter_img").map { img ->
val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
}

@ -0,0 +1,161 @@
package org.koitharu.kotatsu.parsers.site.en
import okhttp3.Headers
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.network.UserAgents
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("COMICASTLE", "Comicastle", "en")
internal class Comicastle(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.COMICASTLE, 42) {
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("comicastle.org")
override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
.build()
init {
paginator.firstPage = 0
searchPaginator.firstPage = 0
}
override suspend fun getFavicons(): Favicons {
return Favicons(
listOf(
Favicon("https://$domain/assets/static/app-assets/images/logo/logo-primary.png", 54, null),
),
domain,
)
}
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val doc = if (!query.isNullOrEmpty()) {
val url = buildString {
append("https://$domain/library/search/result/")
append(page + 1)
}
val postdata = "submit=Submit&search=" + query.urlEncoded()
webClient.httpPost(url, postdata).parseHtml()
} else if (!tags.isNullOrEmpty()) {
val url = buildString {
append("https://$domain/library/search/genre/")
append(page + 1)
}
val postdata = "submit=Submit&search=" + tag?.key.orEmpty()
webClient.httpPost(url, postdata).parseHtml()
} else {
val url = buildString {
append("https://$domain")
append("/library/")
when (sortOrder) {
SortOrder.POPULARITY -> append("popular/desc/")
SortOrder.UPDATED -> append("postdate/desc")
else -> append("postdate/desc")
}
append("/index/")
append(page * pageSize)
}
webClient.httpGet(url).parseHtml()
}
return doc.select("div.card-body div.match-height div.col-6")
.map { div ->
val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href")
Manga(
id = generateUid(href),
title = div.selectFirstOrThrow("p").text(),
altTitle = null,
url = href,
publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN,
isNsfw = false,
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("data-src"),
tags = emptySet(),
state = null,
author = null,
source = source,
)
}
}
override suspend fun getTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/library/").parseHtml()
return doc.requireElementById("sidebar").selectFirstOrThrow(".card-body").select("button")
.mapNotNullToSet { button ->
MangaTag(
key = button.attr("value"),
title = button.text(),
source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
return manga.copy(
altTitle = null,
state = when (root.selectFirstOrThrow(".card-body p span.mr-1 strong").text()) {
"Ongoing" -> MangaState.ONGOING
"Completed" -> MangaState.FINISHED
else -> null
},
tags = root.select("p:contains(Genre) ~ div form").mapNotNullToSet { form ->
MangaTag(
key = form.selectFirstOrThrow("input").attr("value"),
title = form.selectFirstOrThrow("button").text(),
source = source,
)
},
author = root.select("thead:contains(Writer) + tbody button").text(),
description = root.getElementById("comic-desc")?.text(),
chapters = root.select("div.card-body > .table-responsive tr a")
.mapChapters { i, a ->
val url = a.attrAsRelativeUrl("href")
val name = a.text()
MangaChapter(
id = generateUid(url),
name = name,
number = i,
url = url,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
)
},
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return doc.selectFirstOrThrow(".card-content .form-control.pr-3").select("option").map { option ->
val url = option.attr("alt")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
}

@ -101,97 +101,21 @@ internal class JapScanParser(context: MangaLoaderContext) : PagedMangaParser(con
)
}
private fun extractQuotedContent(input: String): List<String> {
val regex = Regex("'(.*?)'")
return regex.findAll(input).map { it.groupValues[1] }.toList()
}
private fun listJSToKey(jsList: MutableList<String>, offsettab: Int, listKey: List<String>): MutableList<String> {
for (i in 0 until jsList.size) {
if (jsList[i].contains("0x")) {
var decoupeHexa = jsList[i].split("('")[1]
decoupeHexa = decoupeHexa.split("')")[0]
var indexkey = Integer.decode(decoupeHexa) - offsettab - 1
if (indexkey < 0) {
indexkey = listKey.size - 1
}
jsList[i] = listKey[indexkey]
}
}
return jsList
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(chapterUrl).parseHtml()
val scriptUrl = doc.getElementsByTag("script").firstNotNullOf { script ->
script.attrAsAbsoluteUrlOrNull("src")?.takeIf { it.contains("/zjs/") }
}
val embeddedData = doc.requireElementById("data").attr("data-data")
val script = webClient.httpGet(scriptUrl).parseRaw()
var tabKey = "'" + script.split("=['")[1]
tabKey = tabKey.split("];")[0]
val listKey = tabKey.split("','").toMutableList()
var decoupeOffset = script.split("-0x")[1]
decoupeOffset = "0x" + decoupeOffset.split(";")[0]
val offsettab = Integer.decode(decoupeOffset)
var decoupeFuncOrder = script.split("while(!![])")[1]
decoupeFuncOrder = decoupeFuncOrder.split("if")[0]
val listKeyOrder = extractQuotedContent(decoupeFuncOrder).toMutableList()
if (listKeyOrder.size < 3) {
throw Exception("L'ordre des clés n'a pas pu être déterminé")
}
var goodorder = false
for (i in 0 until listKey.size) {
for (z in 0 until listKeyOrder.size) {
if (listKey[Integer.decode(listKeyOrder[z]) - offsettab - 1].contains("[0-9]".toRegex())) {
goodorder = true
} else {
goodorder = false
break
}
}
if (goodorder) {
break
}
val firstElement = listKey.removeAt(0)
listKey.add(firstElement)
}
if (!goodorder) {
throw Exception("L'ordre des clés n'a pas pu être déterminé")
}
val zjscalc = script.split("/[A-Z0-9]/gi,")[1]
val calc1 = zjscalc.split(",")[0]
var calc1tab = calc1.split("+").toMutableList()
calc1tab = listJSToKey(calc1tab, offsettab, listKey)
val calc2 = zjscalc.split(",")[1]
var calc2tab = calc2.split("+").toMutableList()
calc2tab = listJSToKey(calc2tab, offsettab, listKey)
var key1 = calc1tab.joinToString("")
var key2 = calc2tab.joinToString("")
val embeddedData = doc.requireElementById("data").attr("data-data")
key1 = key1.filterNot { c -> c == '\'' || c == ' ' }
key2 = key2.filterNot { c -> c == '\'' || c == ' ' }
val jsonkey =
webClient.httpGet("https://gist.githubusercontent.com/zormy111/f6abdc7f9385e95203e2b6a64af15ea3/raw/")
.parseJsonArray()
val keyTables = listOf(
key1.reversed(),
key2.reversed(),
jsonkey[0].toString(),
jsonkey[1].toString(),
)
var error: Exception? = null
repeat(2) { i ->
val key = keyTables[i].zip(keyTables[1 - i]).toMap()

@ -155,16 +155,20 @@ internal abstract class MadaraParser(
!tags.isNullOrEmpty() -> {
append("/$tagPrefix")
append(tag?.key.orEmpty())
append("/page/")
append(pages.toString())
if (page > 1) {
append("/page/")
append(pages.toString())
}
append("?")
}
else -> {
append("/$listUrl")
append("/page/")
append(pages.toString())
if (page > 1) {
append("page/")
append(pages)
}
append("?")
}
}
@ -448,6 +452,7 @@ internal abstract class MadaraParser(
d.endsWith(" назад") || // other translated 'ago' in Russian
d.endsWith(" önce") || // Handle translated 'ago' in Turkish.
d.endsWith(" trước") || // Handle translated 'ago' in Viêt Nam.
d.endsWith("مضت") || // Handle translated 'ago' in Arabic
d.startsWith("il y a") || // Handle translated 'ago' in French.
//If there is no ago but just a motion of time
// short Hours
@ -516,7 +521,17 @@ internal abstract class MadaraParser(
"день",
).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h").anyWordIn(date) -> cal.apply {
WordSet(
"jam",
"saat",
"heure",
"hora",
"horas",
"hour",
"hours",
"h",
"ساعات",
).anyWordIn(date) -> cal.apply {
add(
Calendar.HOUR,
-number,
@ -533,6 +548,7 @@ internal abstract class MadaraParser(
"mins",
"phút",
"минут",
"دقيقة",
).anyWordIn(date) -> cal.apply {
add(
Calendar.MINUTE,
@ -540,7 +556,7 @@ internal abstract class MadaraParser(
)
}.timeInMillis
WordSet("detik", "segundo", "second").anyWordIn(date) -> cal.apply {
WordSet("detik", "segundo", "second", "ثوان").anyWordIn(date) -> cal.apply {
add(
Calendar.SECOND,
-number,

@ -0,0 +1,16 @@
package org.koitharu.kotatsu.parsers.site.madara.all
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.madara.MadaraParser
import java.util.Locale
@MangaSourceParser("MANGACRAZY", "Manga Crazy", "", ContentType.HENTAI)
internal class MangaCrazy(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGACRAZY, "mangacrazy.net") {
override val sourceLocale: Locale = Locale.ENGLISH
}

@ -0,0 +1,16 @@
package org.koitharu.kotatsu.parsers.site.madara.all
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.madara.MadaraParser
import java.util.Locale
@MangaSourceParser("MANGATOP", "Manga Top", "", ContentType.HENTAI)
internal class MangaTop(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGATOP, "mangatop.site") {
override val datePattern = "d MMMM yyyy"
override val sourceLocale: Locale = Locale.ENGLISH
override val stylepage = ""
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.madara.ar
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("MANGAROSE", "Manga Rose", "ar")
internal class MangaRose(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAROSE, "mangarose.net", pageSize = 20)

@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGASTARZ", "Manga Starz", "ar")
internal class MangaStarz(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGASTARZ, "mangastarz.com", pageSize = 10) {
MadaraParser(context, MangaSource.MANGASTARZ, "mangalike.org", pageSize = 10) {
override val datePattern = "d MMMM، yyyy"
}

@ -0,0 +1,10 @@
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("ARCHERSCANS", "ArcherScans", "en")
internal class ArcherScans(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ARCHERSCANS, "www.archerscans.com", 10)

@ -0,0 +1,10 @@
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("ASURASCANS_US", "AsuraScans Us", "en")
internal class AsuraScansUs(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ASURASCANS_US, "asurascans.us")

@ -0,0 +1,10 @@
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("BANANA_MANGA", "Banana Manga", "en")
internal class BananaManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.BANANA_MANGA, "bananamanga.net", 20)

@ -0,0 +1,143 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.EnumSet
@MangaSourceParser("COFFEE_MANGA_TOP", "Coffee Manga Top", "en")
internal class CoffeeMangaTop(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.COFFEE_MANGA_TOP, "coffeemanga.top") {
override val tagPrefix = "mangas/"
override val listUrl = "latest-manga/"
override val datePattern = "MMMM d, HH:mm"
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,
SortOrder.UPDATED,
)
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://")
append(domain)
val pages = page + 1
when {
!query.isNullOrEmpty() -> {
append("/?search=")
append(query.urlEncoded())
append("&page=")
append(pages.toString())
append("&post_type=wp-manga")
}
!tags.isNullOrEmpty() -> {
append("/mangas/")
append(tag?.key.orEmpty())
append("?orderby=2&page=")
append(pages.toString())
}
else -> {
if (sortOrder == SortOrder.POPULARITY) {
append("/popular-manga")
}
if (sortOrder == SortOrder.UPDATED) {
append("/latest-manga")
}
append("?page=")
append(pages.toString())
}
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.row.c-tabs-item__content").ifEmpty {
doc.select("div.page-item-detail.manga")
}.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src().orEmpty(),
title = (summary?.selectFirst("h3") ?: summary?.selectFirst("h4"))?.text().orEmpty(),
altTitle = null,
rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f,
tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
title = a.text().ifEmpty { return@mapNotNullToSet null }.toTitleCase(),
source = source,
)
}.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()?.trim()
?.lowercase()) {
"Ongoing" -> MangaState.ONGOING
"Completed " -> MangaState.FINISHED
else -> null
},
source = source,
isNsfw = isNsfwSource,
)
}
}
override suspend fun loadChapters(mangaUrl: String, document: Document): List<MangaChapter> {
val mangaId = document.select("div[id^=manga-chapters-holder]").attr("data-id")
val doc = webClient.httpGet("https://$domain/ajax-list-chapter?mangaID=$mangaId").parseHtml()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return doc.select(selectChapter).mapChapters(reversed = true) { i, li ->
val a = li.selectFirst("a")
val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing")
val link = href + stylepage
MangaChapter(
id = generateUid(href),
url = link,
name = a.ownText(),
number = i + 1,
branch = null,
uploadDate = parseChapterDate(
dateFormat,
li.selectFirst(selectDate)?.text(),
),
scanlator = null,
source = source,
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val urlarray = doc.select("p#arraydata").text().split(",").toTypedArray()
return urlarray.map { url ->
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
}

@ -0,0 +1,10 @@
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("COMICSCANS", "Comic Scans", "en")
internal class ComicScans(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.COMICSCANS, "www.comicscans.org")

@ -0,0 +1,10 @@
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("ELITEMANGA", "Elite Manga", "en")
internal class EliteManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ELITEMANGA, "www.elitemanga.org")

@ -0,0 +1,10 @@
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("FACTMANGA", "FactManga", "en")
internal class FactManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.FACTMANGA, "factmanga.com")

@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("TODAYMIC", "Todaymic", "en")
internal class Todaymic(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.TODAYMIC, "todaymic.com")
@MangaSourceParser("GLMANGA", "Gl Manga", "en")
internal class GlManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.GLMANGA, "glmanga.com")

@ -65,10 +65,9 @@ internal class IsekaiScan(context: MangaLoaderContext) :
}
}
}
val doc = webClient.httpGet(url).parseHtml()
if (url != "ursdvsdvl") {
throw Exception(doc.toString())
}
return doc.select("div.row.c-tabs-item__content").ifEmpty {
doc.select("div.page-item-detail.manga")
}.map { div ->

@ -0,0 +1,10 @@
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("LUFFYMANGA", "Luffy Manga", "en")
internal class LuffyManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.LUFFYMANGA, "luffymanga.com", 10)

@ -0,0 +1,10 @@
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("MANGA1001", "Manga 1001", "en")
internal class Manga1001(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA1001, "manga-1001.com", 10)

@ -0,0 +1,11 @@
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.madara.MadaraParser
@MangaSourceParser("MANGA18H", "Manga18h", "en", ContentType.HENTAI)
internal class Manga18h(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA18H, "manga18h.com", 20)

@ -0,0 +1,11 @@
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.madara.MadaraParser
@MangaSourceParser("MANGA18X", "Manga18x", "en", ContentType.HENTAI)
internal class Manga18x(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA18X, "manga18x.net", 24)

@ -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.madara.MadaraParser
@MangaSourceParser("MANGABEE", "MangaBee", "en", ContentType.HENTAI)
internal class MangaBee(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGABEE, "mangabee.net") {
override val datePattern = "MM/dd/yyyy"
}

@ -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.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGACLASH_TV", "Mangaclash Tv (unoriginal)", "en")
internal class MangaClashTv(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGACLASH_TV, "mangaclash.tv", pageSize = 10) {
override val datePattern = "MM/dd/yyyy"
override val postreq = true
}

@ -0,0 +1,10 @@
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("MANGAKITSU", "MangaKitsu", "en")
internal class MangaKitsu(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAKITSU, "mangakitsu.com", 20)

@ -0,0 +1,10 @@
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("MANGA_NERDS", "Manga Nerds", "en")
internal class MangaNerds(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA_NERDS, "manganerds.com", 20)

@ -0,0 +1,10 @@
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("MANGA_ONLINE", "Manga Online", "en")
internal class MangaOnline(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA_ONLINE, "mangaonline.team", 18)

@ -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("MANGAOWL_IO", "MangaOwl Io (unoriginal)", "en")
internal class MangaOwlIo(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAOWL_IO, "mangaowl.io") {
override val listUrl = "mangaowl-all/"
}

@ -0,0 +1,11 @@
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.madara.MadaraParser
@MangaSourceParser("MANGAOWL_US", "MangaOwl Us (unoriginal)", "en", ContentType.HENTAI)
internal class MangaOwlUs(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAOWL_US, "mangaowl.us")

@ -0,0 +1,145 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.EnumSet
@MangaSourceParser("MANGAPURE", "Manga Pure", "en")
internal class MangaPure(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAPURE, "mangapure.net") {
override val tagPrefix = "mangas/"
override val listUrl = "latest-manga/"
override val datePattern = "MMMM d, HH:mm"
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,
SortOrder.UPDATED,
)
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://")
append(domain)
val pages = page + 1
when {
!query.isNullOrEmpty() -> {
append("/?search=")
append(query.urlEncoded())
append("&page=")
append(pages.toString())
append("&post_type=wp-manga")
}
!tags.isNullOrEmpty() -> {
append("/mangas/")
append(tag?.key.orEmpty())
append("?orderby=2&page=")
append(pages.toString())
}
else -> {
if (sortOrder == SortOrder.POPULARITY) {
append("/popular-manga")
}
if (sortOrder == SortOrder.UPDATED) {
append("/latest-manga")
}
append("?page=")
append(pages.toString())
}
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.row.c-tabs-item__content").ifEmpty {
doc.select("div.page-item-detail.manga")
}.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src().orEmpty(),
title = (summary?.selectFirst("h3") ?: summary?.selectFirst("h4"))?.text().orEmpty(),
altTitle = null,
rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f,
tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
title = a.text().ifEmpty { return@mapNotNullToSet null }.toTitleCase(),
source = source,
)
}.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()?.trim()
?.lowercase()) {
"Ongoing" -> MangaState.ONGOING
"Completed " -> MangaState.FINISHED
else -> null
},
source = source,
isNsfw = isNsfwSource,
)
}
}
override suspend fun loadChapters(mangaUrl: String, document: Document): List<MangaChapter> {
val mangaId = document.select("div[id^=manga-chapters-holder]").attr("data-id")
val doc = webClient.httpGet("https://$domain/ajax-list-chapter?mangaID=$mangaId").parseHtml()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return doc.select(selectChapter).mapChapters(reversed = true) { i, li ->
val a = li.selectFirst("a")
val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing")
val link = href + stylepage
MangaChapter(
id = generateUid(href),
url = link,
name = a.ownText(),
number = i + 1,
branch = null,
uploadDate = parseChapterDate(
dateFormat,
li.selectFirst(selectDate)?.text(),
),
scanlator = null,
source = source,
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val urlarray = doc.select("p#arraydata").text().split(",").toTypedArray()
return urlarray.map { url ->
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
}

@ -0,0 +1,10 @@
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("MANGA_QUEEN", "Manga Queen", "en")
internal class MangaQueen(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA_QUEEN, "mangaqueen.com", 16)

@ -0,0 +1,10 @@
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("MANGA_QUEEN_ONLINE", "Manga Queen Online", "en")
internal class MangaQueenOnline(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA_QUEEN_ONLINE, "mangaqueen.online", 10)

@ -0,0 +1,10 @@
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("MANGAROCKTEAM", "Manga Rock (unoriginal)", "en")
internal class MangaRockTeam(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAROCKTEAM, "mangarock.team", 18)

@ -0,0 +1,10 @@
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("MANGARUBY", "Manga Ruby", "en")
internal class MangaRuby(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGARUBY, "mangaruby.com", 10)

@ -0,0 +1,11 @@
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.madara.MadaraParser
@MangaSourceParser("MANGARYU", "Manga Ryu", "en", ContentType.HENTAI)
internal class MangaRyu(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGARYU, "mangaryu.com", 10)

@ -0,0 +1,10 @@
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("MANGATX_GG", "MangaTx Gg", "en")
internal class MangaTxGg(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGATX_GG, "mangatx.gg")

@ -0,0 +1,10 @@
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("MANGATYRANT", "MangaTyrant", "en")
internal class MangaTyrant(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGATYRANT, "mangatyrant.com")

@ -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("MANGAKAKALOT_IO", "Mangakakalot Io", "en")
internal class MangakakalotIo(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAKAKALOT_IO, "mangakakalot.io", 20) {
override val postreq = true
}

@ -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("MANGANELO", "Manganelo", "en")
internal class Manganelo(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGANELO, "manganelo.biz", 10) {
override val postreq = true
}

@ -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("MANGANELO_WEBSITE", "Manganelo Website", "en")
internal class ManganeloWebsite(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGANELO_WEBSITE, "manganelo.website", 20) {
override val postreq = true
}

@ -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.madara.MadaraParser
@MangaSourceParser("MANGAOWL_ONE", "MangaOwl One (unoriginal)", "en", ContentType.HENTAI)
internal class MangaowlOne(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAOWL_ONE, "mangaowl.one") {
override val postreq = true
}

@ -0,0 +1,10 @@
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("MANHUADEX", "Manhua Dex", "en")
internal class ManhuaDex(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHUADEX, "manhuadex.com")

@ -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("MANHUAMANHWA", "Manhua Manhwa", "en")
internal class ManhuaManhwa(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHUAMANHWA, "manhuamanhwa.com") {
override val datePattern = "MM/dd/yyyy"
}

@ -0,0 +1,10 @@
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("MANHUAMANHWAONLINE", "Manhua Manhwa Online", "en")
internal class ManhuaManhwaOnline(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHUAMANHWAONLINE, "manhuamanhwa.online", 10)

@ -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.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANHUAZONGHE", "Manhua Zonghe", "en")
internal class ManhuaZonghe(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHUAZONGHE, "manhuazonghe.com") {
override val tagPrefix = "genre/"
override val listUrl = "manhua/"
}

@ -0,0 +1,10 @@
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("MANHWA2READ", "Manhwa2Read", "en")
internal class Manhwa2Read(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHWA2READ, "manhwa2read.com")

@ -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.madara.MadaraParser
@MangaSourceParser("MANHWAHENTAITO", "ManhwaHentai To", "en", ContentType.HENTAI)
internal class ManhwaHentaiTo(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHWAHENTAITO, "manhwahentai.to", 10) {
override val tagPrefix = "pornhwa-genre/"
override val listUrl = "pornhwa/"
override val datePattern = "d MMMM yyyy"
}

@ -0,0 +1,10 @@
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("MANHWAMANHUA", "Manhwa Manhua", "en")
internal class ManhwaManhua(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHWAMANHUA, "manhwamanhua.com")

@ -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("MANHWANEW", "Manhwa New", "en")
internal class ManhwaNew(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHWANEW, "manhwanew.com") {
override val datePattern = "MM/dd/yyyy"
}

@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGAKIO", "Manga Kio", "en")
internal class MangaKio(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAKIO, "mangakio.me", 10)
@MangaSourceParser("MANHWAS", "Free Manhwa", "en")
internal class Manhwas(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHWAS, "manhwas.com")

@ -0,0 +1,14 @@
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("ONLYMANHWA", "OnlyManhwa", "en")
internal class OnlyManhwa(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ONLYMANHWA, "onlymanhwa.org") {
override val listUrl = "manhwa/"
override val datePattern = "d 'de' MMMM 'de' yyyy"
}

@ -0,0 +1,11 @@
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.madara.MadaraParser
@MangaSourceParser("PAWMANGA", "PawManga", "en", ContentType.HENTAI)
internal class PawManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.PAWMANGA, "pawmanga.com", 10)

@ -0,0 +1,11 @@
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.madara.MadaraParser
@MangaSourceParser("PONYMANGA", "PonyManga", "en", ContentType.HENTAI)
internal class PonyManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.PONYMANGA, "ponymanga.com", 10)

@ -0,0 +1,10 @@
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("STMANHWA", "1st Manhwa", "en")
internal class StManhwa(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.STMANHWA, "1stmanhwa.com")

@ -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("STKISSMANGA_COM", "1st Kiss-Manga (unoriginal)", "en")
internal class StkissMangaCom(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.STKISSMANGA_COM, "1stkiss-manga.com", 10)

@ -0,0 +1,14 @@
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("STKISSMANGA_TV", "1stKissManga Tv", "en")
internal class StkissMangaTv(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.STKISSMANGA_TV, "1stkissmanga.tv", 20) {
override val postreq = true
}

@ -0,0 +1,14 @@
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.madara.MadaraParser
@MangaSourceParser("TOONIZY", "Toonizy", "en", ContentType.HENTAI)
internal class Toonizy(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.TOONIZY, "toonizy.com", 24) {
override val datePattern = "MMM d, yy"
override val listUrl = "webtoon/"
}

@ -0,0 +1,10 @@
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("WEBDEXSCANS", "WebDex Scans", "en")
internal class WebDexScans(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.WEBDEXSCANS, "webdexscans.com")

@ -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.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("WEBTOONCITY", "Webtoon City", "en")
internal class WebtoonCity(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.WEBTOONCITY, "webtooncity.com", 20) {
override val listUrl = "webtoon/"
override val tagPrefix = "webtoon-genre/"
}

@ -0,0 +1,11 @@
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("ZINMANGA_TOP", "ZinManga Top (unoriginal)", "en")
internal class ZinMangaTop(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ZINMANGA_TOP, "zinmanga.top", 20)

@ -0,0 +1,16 @@
package org.koitharu.kotatsu.parsers.site.madara.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.MadaraParser
@MangaSourceParser("KOMIKGUE", "Komikgue", "id")
internal class Komikgue(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.KOMIKGUE, "komikgue.pro", 10) {
override val tagPrefix = "komik-genre/"
override val listUrl = "komik/"
override val datePattern = "MMMM dd, yyyy"
override val withoutAjax = true
}

@ -1,48 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.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.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
@MangaSourceParser("LIMASCANS", "Lima Scans", "pt", ContentType.HENTAI)
internal class LimaScans(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.LIMASCANS, "limascans.xyz/v2", 10) {
override val postreq = true
override val datePattern: String = "dd 'de' MMMMM 'de' yyyy"
override suspend fun loadChapters(mangaUrl: String, document: Document): List<MangaChapter> {
val mangaId = document.select("div#manga-chapters-holder").attr("data-id")
val url = "https://$domain/wp-admin/admin-ajax.php"
val postdata = "action=manga_get_chapters&manga=$mangaId"
val doc = webClient.httpPost(url, postdata).parseHtml()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return doc.select(selectChapter).mapChapters(reversed = true) { i, li ->
val a = li.selectFirstOrThrow("a")
val href = a.attrAsRelativeUrl("href")
val link = href + stylepage
val dateText = li.selectFirst("a.c-new-tag")?.attr("title") ?: li.selectFirst(selectDate)?.text()
val name = a.selectFirst("p")?.text() ?: a.ownText()
MangaChapter(
id = generateUid(href),
url = link.replace("/v2", ""),
name = name,
number = i + 1,
branch = null,
uploadDate = parseChapterDate(dateFormat, dateText),
scanlator = null,
source = source,
)
}
}
}

@ -1,15 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.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.madara.MadaraParser
@MangaSourceParser("THESUGARSCAN", "The Sugar Scan", "pt", ContentType.HENTAI)
internal class TheSugarScan(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.THESUGARSCAN, "thesugarscan.com", pageSize = 15) {
override val datePattern: String = "dd/MM/yyyy"
}

@ -0,0 +1,14 @@
package org.koitharu.kotatsu.parsers.site.madara.th
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("KINGS_MANGA", "Kings Manga", "th")
internal class KingsManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.KINGS_MANGA, "www.kings-manga.co") {
override val postreq = true
override val datePattern = "d MMMM yyyy"
}

@ -0,0 +1,14 @@
package org.koitharu.kotatsu.parsers.site.madara.th
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("NEKOPOST", "Neko Post", "th")
internal class NekoPost(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.NEKOPOST, "www.nekopost.co") {
override val postreq = true
override val datePattern = "d MMMM yyyy"
}

@ -0,0 +1,12 @@
package org.koitharu.kotatsu.parsers.site.madara.tr
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("MONOMANGA", "Mono Manga", "tr")
internal class MonoManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MONOMANGA, "monomanga.com") {
override val datePattern = "d MMM yyyy"
}

@ -38,7 +38,7 @@ internal class Mangakakalot(context: MangaLoaderContext) :
append("?page=")
append(page.toString())
}else {
} else {
append("$listUrl/")
when (sortOrder) {
SortOrder.POPULARITY -> append("?type=topview")
@ -49,7 +49,7 @@ internal class Mangakakalot(context: MangaLoaderContext) :
if (!tags.isNullOrEmpty()) {
append("&category=")
append(tag?.key.orEmpty())
}else{
} else {
append("&category=all")
}
append("&state=all&page=")
@ -89,11 +89,9 @@ internal class Mangakakalot(context: MangaLoaderContext) :
val a = li.selectFirstOrThrow("a")
val href = a.attrAsRelativeUrl("href")
val dateText = li.select(selectDate).last()?.text() ?: "0"
val dateFormat = if(dateText.contains("-"))
{
val dateFormat = if (dateText.contains("-")) {
SimpleDateFormat("MMM-dd-yy", sourceLocale)
}else
{
} else {
SimpleDateFormat(datePattern, sourceLocale)
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.mangareader.ar
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("POTATOMANGA", "Potato Manga", "ar")
internal class PotatoManga(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.POTATOMANGA, "potatomanga.xyz", pageSize = 30, searchPageSize = 10)

@ -0,0 +1,10 @@
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("OPSCANS", "OpScans", "en")
internal class OpScans(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.OPSCANS, "opscans.com", pageSize = 20, searchPageSize = 10)

@ -1,15 +1,94 @@
package org.koitharu.kotatsu.parsers.site.mangareader.en
import org.jsoup.nodes.Document
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.MangaChapter
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.mangareader.MangaReaderParser
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
import org.koitharu.kotatsu.parsers.util.parseHtml
import java.util.EnumSet
@MangaSourceParser("REALMSCANS", "RealmScans", "en")
internal class RealmScans(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.REALMSCANS, "realmscans.xyz", pageSize = 30, searchPageSize = 50) {
MangaReaderParser(context, MangaSource.REALMSCANS, "realmscans.to", pageSize = 52, searchPageSize = 50) {
override val listUrl = "/series"
override val datePattern = "dd MMM yyyy"
override val sortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.NEWEST)
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (!query.isNullOrEmpty()) {
return emptyList() // to do
}
if (!tags.isNullOrEmpty()) {
if (page > 1) {
return emptyList()
}
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://")
append(domain)
append("genre/")
append(tag?.key.orEmpty())
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
if (page > 1) {
return emptyList()
}
val url = buildString {
append("https://")
append(domain)
append(listUrl)
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
override suspend fun parseInfo(docs: Document, manga: Manga, chapters: List<MangaChapter>): Manga {
val tagMap = getOrCreateTagMap()
val selectTag = docs.select(".wd-full .mgen > a")
val tags = selectTag.mapNotNullToSet { tagMap[it.text()] }
val mangaState = docs.selectFirst(".bs-status")?.let {
when (it.text()) {
"ongoing" -> MangaState.ONGOING
"Completed" -> MangaState.FINISHED
else -> null
}
}
val author = docs.selectFirst(".tsinfo div:contains(Author)")?.lastElementChild()?.text()
val nsfw = docs.selectFirst(".restrictcontainer") != null
|| docs.selectFirst(".info-right .alr") != null
|| docs.selectFirst(".postbody .alr") != null
// Description in markdown renders it unattractive and unclear on the synopsis
// val desc = docs.selectFirstOrThrow("script:containsData(var description)").data().substringAfter("var description = \"").substringBefore("\";")
return manga.copy(
description = null,
state = mangaState,
author = author,
isNsfw = manga.isNsfw || nsfw,
tags = tags,
chapters = chapters,
)
}
}

@ -0,0 +1,14 @@
package org.koitharu.kotatsu.parsers.site.mangareader.id
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
import java.util.Locale
@MangaSourceParser("MANGADOP", "Mangadop", "id", ContentType.HENTAI)
internal class Mangadop(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANGADOP, "mangadop.net", pageSize = 20, searchPageSize = 20) {
override val sourceLocale: Locale = Locale.ENGLISH
}

@ -0,0 +1,16 @@
package org.koitharu.kotatsu.parsers.site.mangareader.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.mangareader.MangaReaderParser
import java.util.Locale
@MangaSourceParser("NOROMAX", "Noromax", "id")
internal class Noromax(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.NOROMAX, "noromax.my.id", pageSize = 20, searchPageSize = 10) {
override val listUrl = "/Komik"
override val sourceLocale: Locale = Locale.ENGLISH
}

@ -7,7 +7,13 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("SKANLACJEFENIKSY", "SkanlacjeFeniksy", "pl")
internal class SkanlacjeFeniksy(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.SKANLACJEFENIKSY, "skanlacje-feniksy.pl", pageSize = 10, searchPageSize = 10) {
MangaReaderParser(
context,
MangaSource.SKANLACJEFENIKSY,
"skanlacje-feniksy.pl",
pageSize = 10,
searchPageSize = 10,
) {
override val datePattern = "d MMMM, yyyy"
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.mangareader.pt
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("ARKHAMSCAN", "ArkhamScan", "pt")
internal class ArkhamScan(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.ARKHAMSCAN, "arkhamscan.com", pageSize = 20, searchPageSize = 10)

@ -0,0 +1,440 @@
package org.koitharu.kotatsu.parsers.site.otakusanctuary
import kotlinx.coroutines.coroutineScope
import okhttp3.HttpUrl.Companion.toHttpUrl
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.DateFormat
import java.text.SimpleDateFormat
import java.util.*
internal abstract class OtakuSanctuaryParser(
context: MangaLoaderContext,
source: MangaSource,
domain: String,
pageSize: Int = 32,
) : PagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.NEWEST,
)
protected open val listeurl = "Manga/Newest"
protected open val datePattern = "dd/MM/yyyy"
protected open val lang = ""
@JvmField
protected val ongoing: Set<String> = setOf(
"Ongoing",
)
@JvmField
protected val finished: Set<String> = setOf(
"Completed",
"Done",
)
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val doc = if (!tags.isNullOrEmpty()) {
val url = buildString {
append("https://$domain/Genre/MangaGenrePartial?id=")
append(tag?.key.orEmpty())
append("&lang=$lang")
append("&offset=")
append(page * pageSize)
append("&pagesize=$pageSize")
}
webClient.httpGet(url).parseHtml()
} else if (!query.isNullOrEmpty()) {
if (page > 1) {
return emptyList()
}
val url = buildString {
append("https://$domain/Home/Search?search=")
append(query.urlEncoded())
}
webClient.httpGet(url).parseHtml()
} else {
val url = "https://$domain/$listeurl"
val payload = HashMap<String, String>()
payload["Lang"] = lang
payload["Page"] = page.toString()
payload["Type"] = "Include"
when (sortOrder) {
SortOrder.NEWEST -> payload["Dir"] = "CreatedDate"
SortOrder.UPDATED -> payload["Dir"] = "NewPostedDate"
else -> payload["Dir"] = "NewPostedDate"
}
webClient.httpPost(url, payload).parseHtml()
}
val mangas = if (!query.isNullOrEmpty()) {
doc.requireElementById("collection-manga").select("div.picture-card")
} else {
doc.select("div.picture-card")
}
return mangas.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirstOrThrow("img").src().orEmpty(),
title = div.selectFirstOrThrow("h4").text().orEmpty(),
altTitle = null,
rating = div.selectFirst(".rating")?.ownText()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = isNsfwSource,
)
}
}
protected open val selectBodyTag = "div#genre-table a"
override suspend fun getTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/Home/LoadingGenresMenu").parseHtml()
return doc.select(selectBodyTag).mapNotNullToSet { a ->
val href = a.attr("href").substringAfterLast("/").substringBefore("?")
MangaTag(
key = href,
title = a.text(),
source = source,
)
}
}
protected open val selectDesc = "div.summary"
protected open val selectState = ".table-info tr:contains(Tình Trạng) td"
protected open val selectAlt = ".table-info tr:contains(Other Name) + tr"
protected open val selectAut = ".table-info tr a.capitalize"
protected open val selectTag = ".genres a"
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val desc = doc.selectFirstOrThrow(selectDesc).html()
val stateDiv = doc.selectFirst(selectState)
val state = stateDiv?.let {
when (it.text()) {
in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED
else -> null
}
}
val alt = doc.body().selectFirst(selectAlt)?.text()?.replace("Other names", "")
val auth = doc.body().selectFirst(selectAut)?.text()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
manga.copy(
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
val href = a.attr("href").substringAfterLast("/").substringBefore("?")
MangaTag(
key = href,
title = a.text(),
source = source,
)
},
description = desc,
altTitle = alt,
author = auth,
state = state,
chapters = doc.body().requireElementById("chapter").select("tr.chapter")
.mapChapters(reversed = true) { i, tr ->
val dateText = tr.select("td")[3].text()
val a = tr.selectFirstOrThrow("td.read-chapter a")
val url = a.attrAsRelativeUrl("href")
val name = a.text()
MangaChapter(
id = generateUid(url),
name = name,
number = i,
url = url,
scanlator = null,
uploadDate = parseChapterDate(
dateFormat,
dateText,
),
branch = null,
source = source,
)
},
)
}
protected open val selectPage = "div#rendering .image-wraper img"
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
if (doc.select(selectPage).attr("src").isNullOrEmpty()) {
val chapterId = doc.select("#inpit-c").attr("data-chapter-id")
val url = "https://$domain/Manga/UpdateView"
val postdata = "chapId=$chapterId"
val json = webClient.httpPost(url, postdata).parseRaw()
val urls = json.replace("\\u0022", "").substringAfter("{\"view\":\"[").substringBefore("]\",\"isSuccess")
.split(",")
return urls.map {
val urlImage = processUrl(it)
MangaPage(
id = generateUid(urlImage),
url = urlImage,
preview = null,
source = source,
)
}
} else {
return doc.select(selectPage).map { img ->
val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
}
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
// Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it
val d = date?.lowercase() ?: return 0
return when {
d.endsWith(" ago") || d.endsWith(" atrás") || d.startsWith("cách đây ") -> parseRelativeDate(date)
else -> dateFormat.tryParse(date)
}
}
// Parses dates in this form:
// 21 hours ago
private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
return when {
WordSet(
"day",
"days",
"d",
"ngày",
).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet(
"tiếng",
"hour",
"hours",
).anyWordIn(date) -> cal.apply {
add(
Calendar.HOUR,
-number,
)
}.timeInMillis
WordSet(
"min",
"minute",
"minutes",
"phút",
).anyWordIn(date) -> cal.apply {
add(
Calendar.MINUTE,
-number,
)
}.timeInMillis
WordSet("second", "giây").anyWordIn(date) -> cal.apply {
add(
Calendar.SECOND,
-number,
)
}.timeInMillis
WordSet("month", "months").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}
fun processUrl(url: String, vi: String = ""): String {
var url = url.replace("_h_", "http")
.replace("_e_", "/extendContent/Manga")
.replace("_r_", "/extendContent/MangaRaw")
if (url.startsWith("//")) {
url = "https:$url"
}
if (url.contains("drive.google.com")) {
return url
}
url = when (url.slice(0..4)) {
"[GDP]" -> url.replace("[GDP]", "https://drive.google.com/uc?export=view&id=")
"[GDT]" -> if (lang == "us") {
url.replace("image2.otakuscan.net", "image3.shopotaku.net")
.replace("image2.otakusan.net", "image3.shopotaku.net")
} else {
url
}
"[IS1]" -> {
val url = url.replace("[IS1]", "https://imagepi.otakuscan.net/")
if (url.contains("vi") && url.contains("otakusan.net_")) {
url
} else {
url.toHttpUrl().newBuilder().apply {
addQueryParameter("vi", vi)
}.build().toString()
}
}
"[IS3]" -> url.replace("[IS3]", "https://image3.otakusan.net/")
"[IO3]" -> url.replace("[IO3]", "http://image3.shopotaku.net/")
else -> url
}
if (url.contains("/Content/Workshop") || url.contains("otakusan") || url.contains("myrockmanga")) {
return url
}
if (url.contains("file-bato-orig.anyacg.co")) {
url = url.replace("file-bato-orig.anyacg.co", "file-bato-orig.bato.to")
}
if (url.contains("file-comic")) {
if (url.contains("file-comic-1")) {
url = url.replace("file-comic-1.anyacg.co", "z-img-01.mangapark.net")
}
if (url.contains("file-comic-2")) {
url = url.replace("file-comic-2.anyacg.co", "z-img-02.mangapark.net")
}
if (url.contains("file-comic-3")) {
url = url.replace("file-comic-3.anyacg.co", "z-img-03.mangapark.net")
}
if (url.contains("file-comic-4")) {
url = url.replace("file-comic-4.anyacg.co", "z-img-04.mangapark.net")
}
if (url.contains("file-comic-5")) {
url = url.replace("file-comic-5.anyacg.co", "z-img-05.mangapark.net")
}
if (url.contains("file-comic-6")) {
url = url.replace("file-comic-6.anyacg.co", "z-img-06.mangapark.net")
}
if (url.contains("file-comic-9")) {
url = url.replace("file-comic-9.anyacg.co", "z-img-09.mangapark.net")
}
if (url.contains("file-comic-10")) {
url = url.replace("file-comic-10.anyacg.co", "z-img-10.mangapark.net")
}
if (url.contains("file-comic-99")) {
url = url.replace("file-comic-99.anyacg.co/uploads", "file-bato-0001.bato.to")
}
}
if (url.contains("cdn.nettruyen.com")) {
url = url.replace(
"cdn.nettruyen.com/Data/Images/",
"truyen.cloud/data/images/",
)
}
if (url.contains("url=")) {
url = url.substringAfter("url=")
}
if (url.contains("blogspot") || url.contains("fshare")) {
url = url.replace("http:", "https:")
}
if (url.contains("blogspot") && !url.contains("http")) {
url = "https://$url"
}
if (url.contains("app/manga/uploads/") && !url.contains("http")) {
url = "https://lhscan.net$url"
}
url = url.replace("//cdn.adtrue.com/rtb/async.js", "")
if (url.contains(".webp")) {
url = "https://otakusan.net/api/Value/ImageSyncing?ip=34512351".toHttpUrl().newBuilder()
.apply {
addQueryParameter("url", url)
}.build().toString()
} else if (
(
url.contains("merakiscans") ||
url.contains("mangazuki") ||
url.contains("ninjascans") ||
url.contains("anyacg.co") ||
url.contains("mangakatana") ||
url.contains("zeroscans") ||
url.contains("mangapark") ||
url.contains("mangadex") ||
url.contains("uptruyen") ||
url.contains("hocvientruyentranh") ||
url.contains("ntruyen.info") ||
url.contains("chancanvas") ||
url.contains("bato.to")
) &&
(
!url.contains("googleusercontent") &&
!url.contains("otakusan") &&
!url.contains("otakuscan") &&
!url.contains("shopotaku")
)
) {
url =
"https://images2-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&gadget=a&no_expand=1&resize_h=0&rewriteMime=image%2F*".toHttpUrl()
.newBuilder().apply {
addQueryParameter("url", url)
}.build().toString()
} else if (url.contains("imageinstant.com")) {
url = "https://images.weserv.nl/".toHttpUrl().newBuilder().apply {
addQueryParameter("url", url)
}.build().toString()
} else if (!url.contains("otakusan.net")) {
url = "https://otakusan.net/api/Value/ImageSyncing?ip=34512351".toHttpUrl().newBuilder()
.apply {
addQueryParameter("url", url)
}.build().toString()
}
return if (url.contains("vi=") && !url.contains("otakusan.net_")) {
url
} else {
url.toHttpUrl().newBuilder().apply {
addQueryParameter("vi", vi)
}.build().toString()
}
}
}

@ -0,0 +1,14 @@
package org.koitharu.kotatsu.parsers.site.otakusanctuary.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.otakusanctuary.OtakuSanctuaryParser
@MangaSourceParser("MYROCKMANGA_EN", "MyrockManga En", "en")
internal class MyrockMangaEn(context: MangaLoaderContext) :
OtakuSanctuaryParser(context, MangaSource.MYROCKMANGA_EN, "myrockmanga.com") {
override val lang = "us"
}

@ -0,0 +1,15 @@
package org.koitharu.kotatsu.parsers.site.otakusanctuary.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.otakusanctuary.OtakuSanctuaryParser
@MangaSourceParser("OTAKUSAN_EN", "Otakusan En", "en")
internal class OtakusanEn(context: MangaLoaderContext) :
OtakuSanctuaryParser(context, MangaSource.OTAKUSAN_EN, "otakusan.net") {
override val lang = "us"
override val selectState = ".table-info tr:contains(Status) td"
}

@ -0,0 +1,15 @@
package org.koitharu.kotatsu.parsers.site.otakusanctuary.vi
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.otakusanctuary.OtakuSanctuaryParser
@MangaSourceParser("MYROCKMANGA_VI", "MyrockManga Vi", "en")
internal class MyrockMangaVi(context: MangaLoaderContext) :
OtakuSanctuaryParser(context, MangaSource.MYROCKMANGA_VI, "myrockmanga.com") {
override val lang = "vn"
}

@ -0,0 +1,16 @@
package org.koitharu.kotatsu.parsers.site.otakusanctuary.vi
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.otakusanctuary.OtakuSanctuaryParser
@MangaSourceParser("OTAKUSAN_VI", "Otakusan Vi", "vi")
internal class OtakusanVi(context: MangaLoaderContext) :
OtakuSanctuaryParser(context, MangaSource.OTAKUSAN_VI, "otakusan.net") {
override val selectState = ".table-info tr:contains(Status) td"
override val lang = "vn"
}

@ -0,0 +1,145 @@
package org.koitharu.kotatsu.parsers.site.pt
import okhttp3.Headers
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.network.UserAgents
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("BRMANGAS", "BrMangas", "pt")
internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.BRMANGAS, 25) {
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("www.brmangas.net")
override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
.build()
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://$domain/")
if (!tags.isNullOrEmpty()) {
append("category/")
append(tag?.key.orEmpty())
if (page > 1) {
append("/page/$page/")
}
} else if (!query.isNullOrEmpty()) {
if (page > 1) {
append("/page/$page/")
}
append("/?s=")
append(query.urlEncoded())
} else {
when (sortOrder) {
SortOrder.POPULARITY -> append("/")
SortOrder.UPDATED -> append("manga/")
else -> append("manga/")
}
if (page > 1) {
append("page/$page/")
}
}
}
val doc = webClient.httpGet(url).parseHtml()
val item = if (sortOrder == SortOrder.POPULARITY) {
doc.select("div.listagem")[1].select("div.item") // To remove the 6 mangas updated on the home page
} else {
doc.select("div.listagem div.item")
}
return item.map { div ->
val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href")
Manga(
id = generateUid(href),
title = div.selectFirstOrThrow("h2").text(),
altTitle = null,
url = href,
publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN,
isNsfw = false,
coverUrl = div.selectFirstOrThrow("img").src().orEmpty(),
tags = emptySet(),
state = null,
author = null,
source = source,
)
}
}
override suspend fun getTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/lista-de-generos-de-manga/").parseHtml()
return doc.select(".genres_page a").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(
altTitle = null,
state = null,
tags = doc.select("div.serie-infos li:contains(Categorias:) a").mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix("/").substringAfterLast("/"),
title = a.text(),
source = source,
)
},
author = doc.select("div.serie-infos li:contains(Autor:)").text().replace("Autor:", ""),
description = doc.select(".serie-texto p").text(),
isNsfw = doc.select("div.serie-infos li:contains(Categorias:)").text().contains("Hentai"),
chapters = doc.select(".capitulos li a")
.mapChapters(reversed = true) { i, a ->
val url = a.attrAsRelativeUrl("href")
val name = a.text()
MangaChapter(
id = generateUid(url),
name = name,
number = i,
url = url,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
)
},
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val scriptData =
doc.selectFirstOrThrow("script:containsData(imageArray)").data().substringAfter("[").substringBefore("]")
.split(",")
return scriptData.map { data ->
val url = data.replace("\\\"", "").replace("\\/", "/")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
}

@ -0,0 +1,203 @@
package org.koitharu.kotatsu.parsers.site.sinmh
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
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 SinmhParser(
context: MangaLoaderContext,
source: MangaSource,
domain: String,
pageSize: Int = 36,
) : PagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
)
protected open val searchUrl = "search/"
protected open val listUrl = "list/"
init {
paginator.firstPage = 1
searchPaginator.firstPage = 1
}
@JvmField
protected val ongoing: Set<String> = hashSetOf(
"连载中",
)
@JvmField
protected val finished: Set<String> = hashSetOf(
"已完结",
)
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://")
append(domain)
when {
!query.isNullOrEmpty() -> {
append("/$searchUrl?keywords=")
append(query.urlEncoded())
append("&page=")
append(page)
}
!tags.isNullOrEmpty() -> {
append("/$listUrl")
append(tag?.key.orEmpty())
append("/$page/")
}
else -> {
append("/$listUrl")
when (sortOrder) {
SortOrder.POPULARITY -> append("click/")
SortOrder.UPDATED -> append("update/")
else -> append("")
}
append("?page=")
append(page)
}
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.select("#contList > li, li.list-comic").map { div ->
val href = div.selectFirstOrThrow("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.selectFirst("p > a, h3 > a")?.text().orEmpty(),
altTitle = null,
rating = div.selectFirst("span.total_votes")?.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/$listUrl").parseHtml()
return doc.select(".filter-item:contains(按剧情) li a:not(.active)").mapNotNullToSet { a ->
val href = a.attr("href").removeSuffix("/").substringAfterLast("/")
MangaTag(
key = href,
title = a.text(),
source = source,
)
}
}
protected open val selectDesc = "div#intro-all p"
protected open val selectGenre = "ul.detail-list li:contains(漫画类型) a"
protected open val selectState = "ul.detail-list li:contains(漫画状态) 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()
val chapters = getChapters(manga, doc)
val desc = body.selectFirst(selectDesc)?.html()
val state = body.selectFirst(selectState)?.let {
when (it.text()) {
in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED
else -> null
}
}
manga.copy(
tags = doc.body().select(selectGenre).mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
title = a.text().toTitleCase(),
source = source,
)
},
description = desc,
state = state,
chapters = chapters,
)
}
protected open val selectChapter = "ul#chapter-list-1 li"
protected open suspend fun getChapters(manga: Manga, doc: Document): List<MangaChapter> {
return doc.body().select(selectChapter).mapChapters { i, li ->
val href = li.selectFirstOrThrow("a").attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing")
val name = li.selectFirstOrThrow("a").text()
MangaChapter(
id = generateUid(href),
name = name,
number = i + 1,
url = href,
uploadDate = 0,
source = source,
scanlator = null,
branch = null,
)
}
}
protected open val selectTestScript = "script:containsData(chapterImages = )"
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val host = webClient.httpGet("/js/config.js".toAbsoluteUrl(domain)).parseRaw().substringAfter("domain\":[\"")
.substringBefore("\"]}")
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
val docs = webClient.httpGet(chapterUrl).parseHtml()
val script = docs.selectFirstOrThrow(selectTestScript).html()
val images =
script.substringAfter("chapterImages = [").substringBefore("];var chapterPath").replace("\"", "").split(",")
val path = script.substringAfter("chapterPath = \"").substringBefore("\";var ")
val pages = ArrayList<MangaPage>()
images.map {
val imageUrl = when {
it.startsWith("https:\\/\\/") -> it.replace("\\", "")
it.startsWith("/") -> "$host$it"
else -> "$host/$path$it"
}
pages.add(
MangaPage(
id = generateUid(imageUrl),
url = imageUrl,
preview = null,
source = source,
),
)
}
return pages
}
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.sinmh.zh
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.sinmh.SinmhParser
@MangaSourceParser("GUFENGMH", "Gufengmh", "zh")
internal class Gufengmh(context: MangaLoaderContext) :
SinmhParser(context, MangaSource.GUFENGMH, "www.gufengmh.com")

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.sinmh.zh
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.sinmh.SinmhParser
@MangaSourceParser("IMITUI", "Imitui", "zh")
internal class Imitui(context: MangaLoaderContext) :
SinmhParser(context, MangaSource.IMITUI, "www.imitui.com")

@ -155,7 +155,7 @@ fun Element.attrOrNull(vararg names: String): String? {
}
@JvmOverloads
fun Element.src(names: Array<String> = arrayOf("data-src", "data-cfsrc", "data-original", "data-cdn", "data-sizes", "data-lazy-src", "data-srcset", "src")): String? {
fun Element.src(names: Array<String> = arrayOf("data-src", "data-cfsrc", "data-original", "data-cdn", "data-sizes", "data-lazy-src", "data-srcset", "original-src", "src")): String? {
for (name in names) {
val value = attrAsAbsoluteUrlOrNull(name)
if (value != null) {

Loading…
Cancel
Save