add sources and fix
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
@ -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")
|
||||
@ -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"
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
@ -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
|
||||
|
||||
}
|
||||
@ -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")
|
||||
Loading…
Reference in New Issue