[AsuraComic] Fix close #1077
add ContentTypes , SearchWithFilters [CloneManga] isSearchSupported false [MangaGeko] fix tags, add MultipleTags, TagsExclusion [Manhwa18.net] add isSearchSupported [ManhwasMen] add Exception [Pururin] add SearchWithFilters, MultipleTags , TagsExclusion [VyManga] add SearchWithFilters, MultipleTags, TagsExclusion
This commit is contained in:
@@ -17,32 +17,38 @@ import java.util.*
|
||||
internal class AsuraScansParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.ASURASCANS, pageSize = 30) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.RATING,
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.ALPHABETICAL_DESC,
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("asuracomic.net")
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isSearchSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = getOrCreateTagMap().values.toSet(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.RATING,
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.ALPHABETICAL_DESC,
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = getOrCreateTagMap().values.toSet(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
availableContentTypes = EnumSet.of(
|
||||
ContentType.MANGA,
|
||||
ContentType.MANHWA,
|
||||
ContentType.MANHUA,
|
||||
),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
@@ -50,42 +56,49 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
|
||||
append("/series?page=")
|
||||
append(page)
|
||||
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&name=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
filter.query?.let {
|
||||
append("&name=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&genres=")
|
||||
append(filter.tags.joinToString(separator = ",") { it.key })
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&genres=")
|
||||
append(filter.tags.joinToString(separator = ",") { it.key })
|
||||
}
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "3"
|
||||
MangaState.ABANDONED -> "4"
|
||||
MangaState.PAUSED -> "2"
|
||||
MangaState.UPCOMING -> "6"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "3"
|
||||
MangaState.ABANDONED -> "4"
|
||||
MangaState.PAUSED -> "2"
|
||||
MangaState.UPCOMING -> "6"
|
||||
},
|
||||
)
|
||||
}
|
||||
filter.types.oneOrThrowIfMany()?.let {
|
||||
append("&types=")
|
||||
append(
|
||||
when (it) {
|
||||
ContentType.MANGA -> "3"
|
||||
ContentType.MANHWA -> "1"
|
||||
ContentType.MANHUA -> "2"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&types=-1&order=")
|
||||
when (order) {
|
||||
SortOrder.RATING -> append("rating")
|
||||
SortOrder.UPDATED -> append("update")
|
||||
SortOrder.NEWEST -> append("latest")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("desc")
|
||||
SortOrder.ALPHABETICAL -> append("asc")
|
||||
else -> append("update")
|
||||
}
|
||||
}
|
||||
append("&order=")
|
||||
when (order) {
|
||||
SortOrder.RATING -> append("rating")
|
||||
SortOrder.UPDATED -> append("update")
|
||||
SortOrder.POPULARITY -> append("bookmarks")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("desc")
|
||||
SortOrder.ALPHABETICAL -> append("asc")
|
||||
else -> append("update")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -101,7 +114,7 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
|
||||
rating = a.selectFirst("div.block label.ml-1")?.text()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = when (a.selectLastOrThrow("span.status").text()) {
|
||||
state = when (a.selectLast("span.status")?.text().orEmpty()) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
|
||||
@@ -18,10 +18,7 @@ internal class CloneMangaParser(context: MangaLoaderContext) :
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("manga.clone-army.org")
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
)
|
||||
override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities()
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions()
|
||||
|
||||
@@ -31,9 +28,6 @@ internal class CloneMangaParser(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val doc = webClient.httpGet("https://$domain/viewer_landing.php").parseHtml()
|
||||
val mangas = doc.getElementsByClass("comicPreviewContainer")
|
||||
return mangas.mapNotNull { item ->
|
||||
|
||||
@@ -14,9 +14,6 @@ import java.util.*
|
||||
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en")
|
||||
internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.COMICEXTRA, 25) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("comixextra.com")
|
||||
|
||||
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
|
||||
@@ -26,6 +23,9 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
isSearchSupported = true,
|
||||
)
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
|
||||
@@ -52,6 +52,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty() && filter.states.isEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
@@ -67,7 +68,9 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
)
|
||||
}
|
||||
|
||||
} else if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) {
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) {
|
||||
throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED)
|
||||
} else {
|
||||
when (order) {
|
||||
|
||||
@@ -20,11 +20,18 @@ import java.util.*
|
||||
@MangaSourceParser("DYNASTYSCANS", "DynastyScans", "en")
|
||||
internal class DynastyScans(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.DYNASTYSCANS, 117) {
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("dynasty-scans.com")
|
||||
|
||||
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
@@ -34,11 +41,6 @@ internal class DynastyScans(context: MangaLoaderContext) :
|
||||
availableTags = fetchAvailableTags(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
@@ -67,7 +69,6 @@ internal class DynastyScans(context: MangaLoaderContext) :
|
||||
append("?view=groupings")
|
||||
} else {
|
||||
append("/series?view=cover")
|
||||
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
@@ -78,7 +79,6 @@ internal class DynastyScans(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun parseMangaList(doc: Document): List<Manga> {
|
||||
return doc.select("li.span2")
|
||||
.map { div ->
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.json.JSONArray
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.ErrorMessages
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
@@ -15,6 +16,7 @@ import org.koitharu.kotatsu.parsers.util.json.toJSONList
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("FLIXSCANSORG", "FlixScans.org", "en")
|
||||
internal class FlixScansOrg(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) {
|
||||
|
||||
@@ -22,6 +22,8 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
@@ -51,20 +53,24 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
append("/browse-comics/?results=")
|
||||
append(page)
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&tags_include=")
|
||||
append(filter.tags.joinToString(separator = ",") { it.key })
|
||||
}
|
||||
|
||||
if (filter.tagsExclude.isNotEmpty()) {
|
||||
append("&tags_exclude=")
|
||||
append(filter.tagsExclude.joinToString(separator = ",") { it.key })
|
||||
}
|
||||
|
||||
append("&filter=")
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("Updated")
|
||||
SortOrder.NEWEST -> append("New")
|
||||
// SortOrder.RANDOM -> append("Random")
|
||||
else -> append("Updated")
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&genre=")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +96,7 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/browse-comics/").parseHtml()
|
||||
return doc.select("label.checkbox-inline").mapNotNullToSet { label ->
|
||||
return doc.selectFirstOrThrow("div.genre-select-i").select("label").mapNotNullToSet { label ->
|
||||
MangaTag(
|
||||
key = label.selectFirstOrThrow("input").attr("value"),
|
||||
title = label.text(),
|
||||
|
||||
@@ -15,11 +15,16 @@ import java.util.*
|
||||
internal class MangaKawaiiEn(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.MANGAKAWAII_EN, 50) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
@@ -29,11 +34,6 @@ internal class MangaKawaiiEn(context: MangaLoaderContext) :
|
||||
availableTags = fetchAvailableTags(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override fun getRequestHeaders(): Headers = Headers.Builder()
|
||||
.add("Accept-Language", "en")
|
||||
.build()
|
||||
|
||||
@@ -29,13 +29,12 @@ internal class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
private val tagsMap = SuspendLazy(::parseTags)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
@@ -63,54 +62,57 @@ internal class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
append("/tim-kiem?page=")
|
||||
append(page.toString())
|
||||
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
append("&accept_genres=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append(
|
||||
filter.tags.joinToString(",") { it.key },
|
||||
)
|
||||
}
|
||||
|
||||
append("&reject_genres=")
|
||||
if (filter.tagsExclude.isNotEmpty()) {
|
||||
append(
|
||||
filter.tagsExclude.joinToString(",") { it.key },
|
||||
)
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
append(
|
||||
when (order) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.ALPHABETICAL_DESC -> "za"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.RATING -> "like"
|
||||
else -> null
|
||||
},
|
||||
)
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "3"
|
||||
MangaState.PAUSED -> "2"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
filter.query?.let {
|
||||
append("&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
append("&accept_genres=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append(
|
||||
filter.tags.joinToString(",") { it.key },
|
||||
)
|
||||
}
|
||||
|
||||
append("&reject_genres=")
|
||||
if (filter.tagsExclude.isNotEmpty()) {
|
||||
append(
|
||||
filter.tagsExclude.joinToString(",") { it.key },
|
||||
)
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
append(
|
||||
when (order) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.ALPHABETICAL_DESC -> "za"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.RATING -> "like"
|
||||
else -> "update"
|
||||
},
|
||||
)
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "3"
|
||||
MangaState.PAUSED -> "2"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Support author
|
||||
// filter.author.let{
|
||||
// the
|
||||
// append("&artist=")
|
||||
// append(filter.author)
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
@@ -184,14 +186,8 @@ internal class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
// 7 minutes ago
|
||||
// 5 hours ago
|
||||
// 2 days ago
|
||||
// 2 weeks ago
|
||||
// 4 years ago
|
||||
private fun parseUploadDate(timeStr: String?): Long {
|
||||
timeStr ?: return 0
|
||||
|
||||
val timeWords = timeStr.split(' ')
|
||||
if (timeWords.size != 3) return 0
|
||||
val timeWord = timeWords[1]
|
||||
@@ -226,6 +222,8 @@ internal class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
private val tagsMap = SuspendLazy(::parseTags)
|
||||
|
||||
private suspend fun parseTags(): Map<String, MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/tim-kiem?q=").parseHtml()
|
||||
val list = doc.getElementsByAttribute("data-genre-id")
|
||||
|
||||
@@ -53,6 +53,11 @@ internal class ManhwasMen(context: MangaLoaderContext) :
|
||||
|
||||
else -> {
|
||||
|
||||
|
||||
if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) {
|
||||
throw IllegalArgumentException("The source supports one filter at a time")
|
||||
}
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&genero=")
|
||||
append(it.key)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
||||
@@ -9,6 +10,7 @@ import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("PO2SCANS", "Po2Scans", "en")
|
||||
internal class Po2Scans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.PO2SCANS) {
|
||||
|
||||
|
||||
@@ -17,58 +17,72 @@ import java.util.*
|
||||
internal class Pururin(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING, SortOrder.ALPHABETICAL)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("pururin.to")
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING, SortOrder.ALPHABETICAL)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = true,
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
append("/search?tag_condition=contains&page=")
|
||||
append(page.toString())
|
||||
|
||||
filter.query?.let {
|
||||
append("&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&included_tags=[")
|
||||
filter.tags.joinToString(separator = ",") {
|
||||
append("{\"id\":")
|
||||
append(it.key)
|
||||
append(",\"name\":\"")
|
||||
append(it.title.replace(" ", "+"))
|
||||
append("[Content]\"}")
|
||||
}
|
||||
append("]")
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/browse")
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/tags/content/")
|
||||
append(it.key)
|
||||
append("/")
|
||||
}
|
||||
|
||||
append("?page=")
|
||||
append(page)
|
||||
|
||||
append("&sort=")
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("most-viewed")
|
||||
SortOrder.RATING -> append("highest-rated")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
else -> append("")
|
||||
}
|
||||
if (filter.tagsExclude.isNotEmpty()) {
|
||||
append("&excluded_tags=[")
|
||||
filter.tagsExclude.joinToString(separator = ",") {
|
||||
append("{\"id\":")
|
||||
append(it.key)
|
||||
append(",\"name\":\"")
|
||||
append(it.title.replace(" ", "+"))
|
||||
append("[Content]\"}")
|
||||
}
|
||||
append("]")
|
||||
}
|
||||
|
||||
|
||||
append("&sort=")
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("most-viewed")
|
||||
SortOrder.RATING -> append("highest-rated")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -109,7 +123,7 @@ internal class Pururin(context: MangaLoaderContext) :
|
||||
val href = it.attr("href").substringAfterLast("content/").substringBeforeLast('/')
|
||||
MangaTag(
|
||||
key = href,
|
||||
title = it.text(),
|
||||
title = it.text().substringBefore(" /"),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ internal class VyManga(context: MangaLoaderContext) :
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = true,
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
@@ -46,65 +49,54 @@ internal class VyManga(context: MangaLoaderContext) :
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?search_po=0&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&author_po=0&author=&completed=2&sort=updated_at&sort_type=desc&page=")
|
||||
append(page)
|
||||
}
|
||||
append("/search?search_po=0&q=")
|
||||
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isEmpty()) {
|
||||
|
||||
append("/search?search_po=0&q=&author_po=0&author=&completed=")
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "0"
|
||||
MangaState.FINISHED -> "1"
|
||||
else -> "2"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
append("/genre/")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
append("?status=")
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "0"
|
||||
MangaState.FINISHED -> "1"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("viewed&sort_type=desc")
|
||||
SortOrder.POPULARITY_ASC -> append("viewed&sort_type=asc")
|
||||
SortOrder.RATING -> append("scored&sort_type=desc")
|
||||
SortOrder.RATING_ASC -> append("scored&sort_type=asc")
|
||||
SortOrder.NEWEST -> append("created_at&sort_type=desc")
|
||||
SortOrder.NEWEST_ASC -> append("created_at&sort_type=asc")
|
||||
SortOrder.UPDATED -> append("updated_at&sort_type=desc")
|
||||
SortOrder.UPDATED_ASC -> append("updated_at&sort_type=asc")
|
||||
else -> append("Updated")
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
filter.query?.let {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
append("&author_po=0&author=")
|
||||
|
||||
// filter.author?.let {
|
||||
// append(filter.author.urlEncoded())
|
||||
// }
|
||||
|
||||
append("&completed=")
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "0"
|
||||
MangaState.FINISHED -> "1"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("viewed&sort_type=desc")
|
||||
SortOrder.POPULARITY_ASC -> append("viewed&sort_type=asc")
|
||||
SortOrder.RATING -> append("scored&sort_type=desc")
|
||||
SortOrder.RATING_ASC -> append("scored&sort_type=asc")
|
||||
SortOrder.NEWEST -> append("created_at&sort_type=desc")
|
||||
SortOrder.NEWEST_ASC -> append("created_at&sort_type=asc")
|
||||
SortOrder.UPDATED -> append("updated_at&sort_type=desc")
|
||||
SortOrder.UPDATED_ASC -> append("updated_at&sort_type=asc")
|
||||
else -> append("updated_at&sort_type=desc")
|
||||
}
|
||||
|
||||
filter.tags.forEach { tag ->
|
||||
append("&genre[]=")
|
||||
append(tag.key)
|
||||
}
|
||||
|
||||
filter.tagsExclude.forEach { tagsExclude ->
|
||||
append("&exclude_genre[]=")
|
||||
append(tagsExclude.key)
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select(".comic-item").map { div ->
|
||||
@@ -127,11 +119,11 @@ internal class VyManga(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/").parseHtml()
|
||||
return doc.select("div.dropdown-menu.custom-menu ul li a[href*=genre]").mapNotNullToSet {
|
||||
val doc = webClient.httpGet("https://$domain/search").parseHtml()
|
||||
return doc.select("#advance-search .check-genre .d-flex").mapNotNullToSet {
|
||||
MangaTag(
|
||||
key = it.attr("href").substringAfterLast('/'),
|
||||
title = it.text(),
|
||||
key = it.selectFirstOrThrow(".checkbox-genre").attr("data-value"),
|
||||
title = it.selectFirstOrThrow("label").text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user