Global refactoring: migrate getList to new filter
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package org.koitharu.kotatsu.parsers
|
||||
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.Headers
|
||||
@@ -32,15 +31,27 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
*
|
||||
* For better performance use [EnumSet] for more than one item.
|
||||
*/
|
||||
@Deprecated("")
|
||||
open val availableStates: Set<MangaState>
|
||||
get() = emptySet()
|
||||
|
||||
open val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = isMultipleTagsSupported,
|
||||
isTagsExclusionSupported = isTagsExclusionSupported,
|
||||
isSearchSupported = isSearchSupported,
|
||||
isSearchWithFiltersSupported = searchSupportedWithMultipleFilters,
|
||||
isYearSupported = isSearchYearSupported,
|
||||
isYearRangeSupported = isSearchYearRangeSupported,
|
||||
isSourceLocaleSupported = isSearchOriginalLanguages,
|
||||
)
|
||||
|
||||
/**
|
||||
* Supported [ContentRating] variants for filtering. May be empty.
|
||||
*
|
||||
* For better performance use [EnumSet] for more than one item.
|
||||
*/
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val availableContentRating: Set<ContentRating>
|
||||
get() = emptySet()
|
||||
|
||||
@@ -49,6 +60,7 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
*
|
||||
* For better performance use [EnumSet] for more than one item.
|
||||
*/
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val availableContentTypes: Set<ContentType>
|
||||
get() = emptySet()
|
||||
|
||||
@@ -57,44 +69,50 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
*
|
||||
* For better performance use [EnumSet] for more than one item.
|
||||
*/
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val availableDemographics: Set<Demographic>
|
||||
get() = emptySet()
|
||||
|
||||
/**
|
||||
* Whether parser supports filtering by more than one tag
|
||||
*/
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val isMultipleTagsSupported: Boolean = true
|
||||
|
||||
/**
|
||||
* Whether parser supports tagsExclude field in filter
|
||||
*/
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val isTagsExclusionSupported: Boolean = false
|
||||
|
||||
/**
|
||||
* Whether parser supports searching by string query using [MangaListFilter.Search]
|
||||
*/
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val isSearchSupported: Boolean = true
|
||||
|
||||
/**
|
||||
* Whether parser supports searching by string query using [MangaListFilter.Advanced]
|
||||
*/
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val searchSupportedWithMultipleFilters: Boolean = false
|
||||
|
||||
/**
|
||||
* Whether parser supports searching by year
|
||||
*/
|
||||
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val isSearchYearSupported: Boolean = false
|
||||
|
||||
/**
|
||||
* Whether parser supports searching by year range
|
||||
*/
|
||||
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val isSearchYearRangeSupported: Boolean = false
|
||||
|
||||
/**
|
||||
* Whether parser supports searching Original Languages
|
||||
*/
|
||||
@Deprecated("Use getListFilterCapabilities instead")
|
||||
open val isSearchOriginalLanguages: Boolean = false
|
||||
|
||||
|
||||
@@ -129,7 +147,6 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
/**
|
||||
* Used as fallback if value of `sortOrder` passed to [getList] is null
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
|
||||
open val defaultSortOrder: SortOrder
|
||||
get() {
|
||||
val supported = availableSortOrders
|
||||
@@ -144,141 +161,10 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
*
|
||||
* @param offset starting from 0 and used for pagination.
|
||||
* Note than passed value may not be divisible by internal page size, so you should adjust it manually.
|
||||
* @param query search query, may be null or empty if no search needed
|
||||
* @param tags genres for filtering, values from [getAvailableTags] and [Manga.tags]. May be null or empty
|
||||
* @param sortOrder one of [availableSortOrders] or null for default value
|
||||
* @param order one of [availableSortOrders] or null for default value
|
||||
* @param filter is a set of filter rules
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@InternalParsersApi
|
||||
@Deprecated(
|
||||
"Use getList with filter instead",
|
||||
replaceWith = ReplaceWith("getList(offset, filter)"),
|
||||
)
|
||||
open suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
tagsExclude: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> = throw NotImplementedError("Please implement getList(offset, filter) instead")
|
||||
|
||||
/**
|
||||
* Parse list of manga with search by text query
|
||||
*
|
||||
* @param offset starting from 0 and used for pagination.
|
||||
* @param query search query
|
||||
*/
|
||||
@Deprecated(
|
||||
"Use getList with filter instead",
|
||||
ReplaceWith(
|
||||
"getList(offset, SortOrder.RELEVANCE, MangaListFilterV2(query = query))",
|
||||
"org.koitharu.kotatsu.parsers.model.MangaListFilter",
|
||||
),
|
||||
)
|
||||
open suspend fun getList(offset: Int, query: String): List<Manga> {
|
||||
return getList(offset, MangaListFilter.Search(query))
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse list of manga by specified criteria
|
||||
*
|
||||
* @param offset starting from 0 and used for pagination.
|
||||
* Note than passed value may not be divisible by internal page size, so you should adjust it manually.
|
||||
* @param tags genres for filtering, values from [getAvailableTags] and [Manga.tags]. May be null or empty
|
||||
* @param sortOrder one of [availableSortOrders] or null for default value
|
||||
*/
|
||||
@Deprecated(
|
||||
"Use getList with filter instead",
|
||||
ReplaceWith(
|
||||
"getList(offset, sortOrder, MangaListFilterV2(tags = tags))",
|
||||
"org.koitharu.kotatsu.parsers.model.MangaListFilter",
|
||||
),
|
||||
)
|
||||
open suspend fun getList(
|
||||
offset: Int,
|
||||
tags: Set<MangaTag>?,
|
||||
tagsExclude: Set<MangaTag>?,
|
||||
sortOrder: SortOrder?,
|
||||
): List<Manga> {
|
||||
return getList(
|
||||
offset,
|
||||
MangaListFilter.Advanced(
|
||||
sortOrder = sortOrder ?: defaultSortOrder,
|
||||
tags = tags.orEmpty(),
|
||||
tagsExclude = tagsExclude.orEmpty(),
|
||||
locale = null,
|
||||
localeMangas = null,
|
||||
states = emptySet(),
|
||||
contentRating = emptySet(),
|
||||
query = null,
|
||||
year = null,
|
||||
yearFrom = null,
|
||||
yearTo = null,
|
||||
types = emptySet(),
|
||||
demographics = emptySet(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Use getList with filter instead",
|
||||
ReplaceWith(
|
||||
"getList(offset, filter.sortOrder, filter)",
|
||||
"org.koitharu.kotatsu.parsers.model.MangaListFilter",
|
||||
),
|
||||
)
|
||||
open suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
return when (filter) {
|
||||
is MangaListFilter.Advanced -> getList(
|
||||
offset = offset,
|
||||
query = null,
|
||||
tags = filter.tags,
|
||||
tagsExclude = filter.tagsExclude,
|
||||
sortOrder = filter.sortOrder,
|
||||
)
|
||||
|
||||
is MangaListFilter.Search -> getList(
|
||||
offset = offset,
|
||||
query = filter.query,
|
||||
tags = null,
|
||||
tagsExclude = null,
|
||||
sortOrder = defaultSortOrder,
|
||||
)
|
||||
|
||||
null -> getList(
|
||||
offset = offset,
|
||||
query = null,
|
||||
tags = null,
|
||||
tagsExclude = null,
|
||||
sortOrder = defaultSortOrder,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2) = getList(
|
||||
offset = offset,
|
||||
filter = when {
|
||||
filter.query.isNullOrEmpty() -> MangaListFilter.Advanced(
|
||||
sortOrder = order,
|
||||
tags = filter.tags,
|
||||
tagsExclude = filter.tagsExclude,
|
||||
locale = filter.locale,
|
||||
localeMangas = filter.sourceLocale,
|
||||
states = filter.states,
|
||||
contentRating = filter.contentRating,
|
||||
query = filter.query,
|
||||
year = filter.year,
|
||||
yearFrom = filter.yearFrom,
|
||||
yearTo = filter.yearTo,
|
||||
types = filter.types,
|
||||
demographics = filter.demographics,
|
||||
)
|
||||
|
||||
else -> MangaListFilter.Search(
|
||||
query = filter.query,
|
||||
)
|
||||
},
|
||||
)
|
||||
abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga>
|
||||
|
||||
/**
|
||||
* Parse details for [Manga]: chapters list, description, large cover, etc.
|
||||
@@ -301,32 +187,26 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
/**
|
||||
* Fetch available tags (genres) for source
|
||||
*/
|
||||
abstract suspend fun getAvailableTags(): Set<MangaTag>
|
||||
@Deprecated("Use getListFilterDatasets instead")
|
||||
open suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
open suspend fun getListFilterCapabilities(): MangaListFilterCapabilities = coroutineScope {
|
||||
open suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope {
|
||||
val tagsDeferred = async { getAvailableTags() }
|
||||
val localesDeferred = async { getAvailableLocales() }
|
||||
MangaListFilterCapabilities(
|
||||
availableSortOrders = availableSortOrders,
|
||||
MangaListFilterOptions(
|
||||
availableTags = tagsDeferred.await(),
|
||||
availableStates = availableStates,
|
||||
availableContentRating = availableContentRating,
|
||||
availableContentTypes = availableContentTypes,
|
||||
availableDemographics = availableDemographics,
|
||||
availableLocales = localesDeferred.await(),
|
||||
isMultipleTagsSupported = isMultipleTagsSupported,
|
||||
isTagsExclusionSupported = isTagsExclusionSupported,
|
||||
isSearchSupported = isSearchSupported,
|
||||
searchSupportedWithMultipleFilters = searchSupportedWithMultipleFilters,
|
||||
isSearchYearSupported = isSearchYearSupported,
|
||||
isSearchYearRangeSupported = isSearchYearRangeSupported,
|
||||
isSearchOriginalLanguages = isSearchOriginalLanguages,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch available locales for multilingual sources
|
||||
*/
|
||||
@Deprecated("Use getListFilterDatasets instead")
|
||||
open suspend fun getAvailableLocales(): Set<Locale> = emptySet()
|
||||
|
||||
@Deprecated(
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilterV2
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.Paginator
|
||||
|
||||
@InternalParsersApi
|
||||
@@ -18,72 +21,29 @@ abstract class PagedMangaParser(
|
||||
@JvmField
|
||||
protected val searchPaginator = Paginator(searchPageSize)
|
||||
|
||||
final override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
return getList(
|
||||
paginator = if (filter is MangaListFilter.Search) {
|
||||
searchPaginator
|
||||
} else {
|
||||
paginator = if (filter.query.isNullOrEmpty()) {
|
||||
paginator
|
||||
} else {
|
||||
searchPaginator
|
||||
},
|
||||
offset = offset,
|
||||
order = order,
|
||||
filter = filter,
|
||||
)
|
||||
}
|
||||
|
||||
@InternalParsersApi
|
||||
@Deprecated("You should use getListPage for PagedMangaParser", level = DeprecationLevel.HIDDEN)
|
||||
final override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
tagsExclude: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> = throw UnsupportedOperationException("You should use getListPage for PagedMangaParser")
|
||||
|
||||
@Deprecated("")
|
||||
open suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
tagsExclude: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> = throw NotImplementedError("Please implement getListPage(page, filter) instead")
|
||||
|
||||
open suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
return when (filter) {
|
||||
is MangaListFilter.Advanced -> getListPage(
|
||||
page = page,
|
||||
query = null,
|
||||
tags = filter.tags,
|
||||
tagsExclude = filter.tagsExclude,
|
||||
sortOrder = filter.sortOrder,
|
||||
)
|
||||
|
||||
is MangaListFilter.Search -> getListPage(
|
||||
page = page,
|
||||
query = filter.query,
|
||||
tags = null,
|
||||
tagsExclude = null,
|
||||
sortOrder = defaultSortOrder,
|
||||
)
|
||||
|
||||
null -> getListPage(
|
||||
page = page,
|
||||
query = null,
|
||||
tags = null,
|
||||
tagsExclude = null,
|
||||
sortOrder = defaultSortOrder,
|
||||
)
|
||||
}
|
||||
}
|
||||
abstract suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga>
|
||||
|
||||
private suspend fun getList(
|
||||
paginator: Paginator,
|
||||
offset: Int,
|
||||
filter: MangaListFilter?,
|
||||
order: SortOrder,
|
||||
filter: MangaListFilterV2,
|
||||
): List<Manga> {
|
||||
val page = paginator.getPage(offset)
|
||||
val list = getListPage(page, filter)
|
||||
val list = getListPage(page, order, filter)
|
||||
paginator.onListReceived(offset, page, list.size)
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.koitharu.kotatsu.parsers
|
||||
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilterV2
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
|
||||
@InternalParsersApi
|
||||
abstract class SinglePageMangaParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaParserSource,
|
||||
) : MangaParser(context, source) {
|
||||
|
||||
final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
if (offset > 0) {
|
||||
return emptyList()
|
||||
}
|
||||
return getList(order, filter)
|
||||
}
|
||||
|
||||
abstract suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga>
|
||||
}
|
||||
@@ -1,21 +1,13 @@
|
||||
package org.koitharu.kotatsu.parsers.model
|
||||
|
||||
import org.koitharu.kotatsu.parsers.InternalParsersApi
|
||||
import java.util.*
|
||||
|
||||
data class MangaListFilterCapabilities @InternalParsersApi constructor(
|
||||
val availableSortOrders: Set<SortOrder>,
|
||||
val availableTags: Set<MangaTag>,
|
||||
val availableStates: Set<MangaState>,
|
||||
val availableContentRating: Set<ContentRating>,
|
||||
val availableContentTypes: Set<ContentType>,
|
||||
val availableDemographics: Set<Demographic>,
|
||||
val availableLocales: Set<Locale>,
|
||||
val isMultipleTagsSupported: Boolean,
|
||||
val isTagsExclusionSupported: Boolean,
|
||||
val isSearchSupported: Boolean,
|
||||
val searchSupportedWithMultipleFilters: Boolean,
|
||||
val isSearchYearSupported: Boolean,
|
||||
val isSearchYearRangeSupported: Boolean,
|
||||
val isSearchOriginalLanguages: Boolean,
|
||||
val isSearchWithFiltersSupported: Boolean,
|
||||
val isYearSupported: Boolean = false,
|
||||
val isYearRangeSupported: Boolean = false,
|
||||
val isSourceLocaleSupported: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.koitharu.kotatsu.parsers.model
|
||||
|
||||
import org.koitharu.kotatsu.parsers.InternalParsersApi
|
||||
import java.util.*
|
||||
|
||||
data class MangaListFilterOptions @InternalParsersApi constructor(
|
||||
val availableTags: Set<MangaTag>,
|
||||
val availableStates: Set<MangaState> = emptySet(),
|
||||
val availableContentRating: Set<ContentRating> = emptySet(),
|
||||
val availableContentTypes: Set<ContentType> = emptySet(),
|
||||
val availableDemographics: Set<Demographic> = emptySet(),
|
||||
val availableLocales: Set<Locale> = emptySet(),
|
||||
)
|
||||
@@ -60,11 +60,39 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
SortOrder.POPULARITY_HOUR,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override val isTagsExclusionSupported: Boolean = true
|
||||
|
||||
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.SAFE)
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
availableContentRating = EnumSet.of(ContentRating.SAFE),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = setOf(
|
||||
Locale.CHINESE, Locale.ENGLISH, Locale.US, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.JAPANESE,
|
||||
Locale("af"), Locale("ar"), Locale("az"), Locale("eu"), Locale("be"),
|
||||
Locale("bn"), Locale("bs"), Locale("bg"), Locale("my"), Locale("km"),
|
||||
Locale("ceb"), Locale("zh_hk"), Locale("zh_tw"), Locale("hr"), Locale("cs"),
|
||||
Locale("da"), Locale("nl"), Locale("eo"), Locale("et"), Locale("fil"),
|
||||
Locale("fi"), Locale("ka"), Locale("el"), Locale("ht"), Locale("he"),
|
||||
Locale("hi"), Locale("hu"), Locale("id"), Locale("kk"), Locale("ko"),
|
||||
Locale("lv"), Locale("ms"), Locale("ml"), Locale("mo"), Locale("mn"),
|
||||
Locale("ne"), Locale("no"), Locale("fa"), Locale("pl"), Locale("pt"),
|
||||
Locale("pt_br"), Locale("pt_pt"), Locale("ro"), Locale("ru"), Locale("sr"),
|
||||
Locale("si"), Locale("sk"), Locale("es"), Locale("es_419"), Locale("ta"),
|
||||
Locale("te"), Locale("th"), Locale("ti"), Locale("tr"), Locale("uk"),
|
||||
Locale("vi"), Locale("zu"),
|
||||
),
|
||||
)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(
|
||||
"bato.to",
|
||||
@@ -94,21 +122,20 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
"zbato.org",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
return search(page, filter.query)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
append("/browse?sort=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> append("update.za")
|
||||
SortOrder.POPULARITY -> append("views_a.za")
|
||||
SortOrder.NEWEST -> append("create.za")
|
||||
@@ -171,17 +198,6 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
|
||||
return parseList(url, page)
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/browse?sort=update.za")
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
return parseList(url, page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +268,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
throw ParseException("Cannot find images list", fullUrl)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val scripts = webClient.httpGet(
|
||||
"https://${domain}/browse",
|
||||
).parseHtml().selectOrThrow("script")
|
||||
@@ -273,22 +289,6 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
throw ParseException("Cannot find gernes list", scripts[0].baseUri())
|
||||
}
|
||||
|
||||
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
|
||||
Locale.CHINESE, Locale.ENGLISH, Locale.US, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.JAPANESE,
|
||||
Locale("af"), Locale("ar"), Locale("az"), Locale("eu"), Locale("be"),
|
||||
Locale("bn"), Locale("bs"), Locale("bg"), Locale("my"), Locale("km"),
|
||||
Locale("ceb"), Locale("zh_hk"), Locale("zh_tw"), Locale("hr"), Locale("cs"),
|
||||
Locale("da"), Locale("nl"), Locale("eo"), Locale("et"), Locale("fil"),
|
||||
Locale("fi"), Locale("ka"), Locale("el"), Locale("ht"), Locale("he"),
|
||||
Locale("hi"), Locale("hu"), Locale("id"), Locale("kk"), Locale("ko"),
|
||||
Locale("lv"), Locale("ms"), Locale("ml"), Locale("mo"), Locale("mn"),
|
||||
Locale("ne"), Locale("no"), Locale("fa"), Locale("pl"), Locale("pt"),
|
||||
Locale("pt_br"), Locale("pt_pt"), Locale("ro"), Locale("ru"), Locale("sr"),
|
||||
Locale("si"), Locale("sk"), Locale("es"), Locale("es_419"), Locale("ta"),
|
||||
Locale("te"), Locale("th"), Locale("ti"), Locale("tr"), Locale("uk"),
|
||||
Locale("vi"), Locale("zu"),
|
||||
)
|
||||
|
||||
private suspend fun search(page: Int, query: String): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
|
||||
@@ -27,10 +27,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val isTagsExclusionSupported = true
|
||||
|
||||
override val isSearchYearRangeSupported = true
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.UPDATED,
|
||||
@@ -38,21 +34,34 @@ internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
|
||||
override val availableContentTypes: Set<ContentType> = EnumSet.of(
|
||||
ContentType.MANGA,
|
||||
ContentType.MANHWA,
|
||||
ContentType.MANHUA,
|
||||
ContentType.OTHER,
|
||||
)
|
||||
|
||||
override val availableDemographics: Set<Demographic> = EnumSet.allOf(Demographic::class.java)
|
||||
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
|
||||
|
||||
private val tagsArray = SuspendLazy(::loadTags)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = true,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = EnumSet.of(
|
||||
ContentType.MANGA,
|
||||
ContentType.MANHWA,
|
||||
ContentType.MANHUA,
|
||||
ContentType.OTHER,
|
||||
),
|
||||
availableDemographics = EnumSet.allOf(Demographic::class.java),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val domain = domain
|
||||
val url = urlBuilder()
|
||||
.host("api.$domain")
|
||||
@@ -62,12 +71,12 @@ internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
.addQueryParameter("tachiyomi", "true")
|
||||
.addQueryParameter("limit", pageSize.toString())
|
||||
.addQueryParameter("page", page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
url.addQueryParameter("q", filter.query)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.tags.forEach {
|
||||
url.addQueryParameter("genres", it.key)
|
||||
@@ -79,7 +88,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
|
||||
url.addQueryParameter(
|
||||
"sort",
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> "view"
|
||||
SortOrder.UPDATED -> "uploaded"
|
||||
SortOrder.NEWEST -> "created_at"
|
||||
@@ -101,11 +110,11 @@ internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
filter.yearFrom?.let {
|
||||
if (filter.yearFrom != 0) {
|
||||
url.addQueryParameter("from", filter.yearFrom.toString())
|
||||
}
|
||||
|
||||
filter.yearTo?.let {
|
||||
if (filter.yearTo != 0) {
|
||||
url.addQueryParameter("to", filter.yearTo.toString())
|
||||
}
|
||||
|
||||
@@ -135,10 +144,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
url.addQueryParameter("sort", "uploaded")
|
||||
}
|
||||
}
|
||||
val ja = webClient.httpGet(url.build()).parseJsonArray()
|
||||
val tagsMap = tagsArray.get()
|
||||
@@ -208,7 +213,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val sparseArray = tagsArray.get()
|
||||
val set = ArraySet<MangaTag>(sparseArray.size())
|
||||
for (i in 0 until sparseArray.size()) {
|
||||
|
||||
@@ -33,7 +33,6 @@ internal class ExHentaiParser(
|
||||
) : PagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider, Interceptor {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = setOf(SortOrder.NEWEST)
|
||||
override val isTagsExclusionSupported: Boolean = true
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain
|
||||
get() = ConfigKey.Domain(
|
||||
@@ -51,6 +50,17 @@ internal class ExHentaiParser(
|
||||
private val suspiciousContentKey = ConfigKey.ShowSuspiciousContent(false)
|
||||
private val tagsMap = SuspendLazy(::fetchTags)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override val isAuthorized: Boolean
|
||||
get() {
|
||||
val authorized = isAuthorized(DOMAIN_UNAUTHORIZED)
|
||||
@@ -75,7 +85,32 @@ internal class ExHentaiParser(
|
||||
searchPaginator.firstPage = 0
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = tagsMap.get().values.toSet(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = setOf(
|
||||
Locale.JAPANESE,
|
||||
Locale.ENGLISH,
|
||||
Locale.CHINESE,
|
||||
Locale("nl"),
|
||||
Locale.FRENCH,
|
||||
Locale.GERMAN,
|
||||
Locale("hu"),
|
||||
Locale.ITALIAN,
|
||||
Locale("kr"),
|
||||
Locale("pl"),
|
||||
Locale("pt"),
|
||||
Locale("ru"),
|
||||
Locale("es"),
|
||||
Locale("th"),
|
||||
Locale("vi"),
|
||||
),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val next = nextPages.get(page, 0L)
|
||||
|
||||
if (page > 0 && next == 0L) {
|
||||
@@ -90,15 +125,15 @@ internal class ExHentaiParser(
|
||||
append(domain)
|
||||
append("/?next=")
|
||||
append(next)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
search += filter.query.urlEncoded()
|
||||
append("&f_search=")
|
||||
append(search.trim().replace(' ', '+'))
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.toSearchQuery()?.let { sq ->
|
||||
append("&f_search=")
|
||||
@@ -121,8 +156,6 @@ internal class ExHentaiParser(
|
||||
append(fCats)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
// by unknown reason cookie "sl=dm_2" is ignored, so, we should request it again
|
||||
if (updateDm) {
|
||||
@@ -141,7 +174,7 @@ internal class ExHentaiParser(
|
||||
body.parseFailed("Cannot find root")
|
||||
} else {
|
||||
updateDm = true
|
||||
return getListPage(page, filter)
|
||||
return getListPage(page, order, filter)
|
||||
}
|
||||
updateDm = false
|
||||
nextPages[page + 1] = getNextTimestamp(body)
|
||||
@@ -267,10 +300,6 @@ internal class ExHentaiParser(
|
||||
"unusual pupils,urination,vore,vtuber,widow,wings,witch,wolf girl,x-ray,yuri,zombie,sole male,males only,yaoi," +
|
||||
"tomgirl,tall man,oni,shotacon,prostate massage,policeman,males only,huge penis,fox boy,feminization,dog boy,dickgirl on male,big penis"
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return tagsMap.get().values.toSet()
|
||||
}
|
||||
|
||||
private suspend fun fetchTags(): Map<String, MangaTag> {
|
||||
val tagMap = ArrayMap<String, MangaTag>()
|
||||
val tagElements = tags.split(",")
|
||||
@@ -297,24 +326,6 @@ internal class ExHentaiParser(
|
||||
return tagMap
|
||||
}
|
||||
|
||||
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
|
||||
Locale.JAPANESE,
|
||||
Locale.ENGLISH,
|
||||
Locale.CHINESE,
|
||||
Locale("nl"),
|
||||
Locale.FRENCH,
|
||||
Locale.GERMAN,
|
||||
Locale("hu"),
|
||||
Locale.ITALIAN,
|
||||
Locale("kr"),
|
||||
Locale("pl"),
|
||||
Locale("pt"),
|
||||
Locale("ru"),
|
||||
Locale("es"),
|
||||
Locale("th"),
|
||||
Locale("vi"),
|
||||
)
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val response = chain.proceed(chain.request())
|
||||
if (response.headersContentLength() <= 256) {
|
||||
@@ -420,7 +431,7 @@ internal class ExHentaiParser(
|
||||
?.toLongOrNull() ?: 1
|
||||
}
|
||||
|
||||
private fun MangaListFilter.Advanced.toSearchQuery(): String? {
|
||||
private fun MangaListFilterV2.toSearchQuery(): String? {
|
||||
val joiner = StringUtil.StringJoiner(" ")
|
||||
for (tag in tags) {
|
||||
if (tag.key.isNumeric()) {
|
||||
|
||||
@@ -109,13 +109,10 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa
|
||||
|
||||
private var cachedSearchIds: List<Int> = emptyList()
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
filter: MangaListFilter?,
|
||||
): List<Manga> = when (filter) {
|
||||
is MangaListFilter.Advanced -> {
|
||||
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> = when {
|
||||
filter.query.isNullOrEmpty() -> {
|
||||
if (filter.tags.isEmpty()) {
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> {
|
||||
getGalleryIDsFromNozomi(
|
||||
"popular",
|
||||
@@ -134,7 +131,7 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa
|
||||
cachedSearchIds =
|
||||
hitomiSearch(
|
||||
filter.tags.joinToString(" ") { it.key },
|
||||
filter.sortOrder == SortOrder.POPULARITY,
|
||||
order == SortOrder.POPULARITY,
|
||||
filter.locale.getSiteLang(),
|
||||
).toList()
|
||||
}
|
||||
@@ -142,14 +139,12 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
else -> {
|
||||
if (offset == 0) {
|
||||
cachedSearchIds = hitomiSearch(filter.query, filter.sortOrder == SortOrder.POPULARITY).toList()
|
||||
cachedSearchIds = hitomiSearch(filter.query, order == SortOrder.POPULARITY).toList()
|
||||
}
|
||||
cachedSearchIds.subList(offset, min(offset + 25, cachedSearchIds.size))
|
||||
}
|
||||
|
||||
else -> getGalleryIDsFromNozomi(null, "popular", "all", offset.nextOffsetRange())
|
||||
}.toMangaList()
|
||||
|
||||
private fun Int.nextOffsetRange(): LongRange {
|
||||
@@ -478,14 +473,14 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa
|
||||
title = doc.selectFirstOrThrow("h1").text(),
|
||||
url = id.toString(),
|
||||
coverUrl =
|
||||
"https:" +
|
||||
doc.selectFirstOrThrow("picture > source")
|
||||
.attr("data-srcset")
|
||||
.substringBefore(" "),
|
||||
"https:" +
|
||||
doc.selectFirstOrThrow("picture > source")
|
||||
.attr("data-srcset")
|
||||
.substringBefore(" "),
|
||||
publicUrl =
|
||||
doc.selectFirstOrThrow("h1 > a")
|
||||
.attrAsRelativeUrl("href")
|
||||
.toAbsoluteUrl(domain),
|
||||
doc.selectFirstOrThrow("h1 > a")
|
||||
.attrAsRelativeUrl("href")
|
||||
.toAbsoluteUrl(domain),
|
||||
author = null,
|
||||
tags = emptySet(),
|
||||
isNsfw = true,
|
||||
@@ -508,37 +503,37 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa
|
||||
return manga.copy(
|
||||
title = json.getString("title"),
|
||||
largeCoverUrl =
|
||||
json.getJSONArray("files").getJSONObject(0).let {
|
||||
val hash = it.getString("hash")
|
||||
val commonId = commonImageId()
|
||||
val imageId = imageIdFromHash(hash)
|
||||
val subDomain = 'a' + subdomainOffset(imageId)
|
||||
json.getJSONArray("files").getJSONObject(0).let {
|
||||
val hash = it.getString("hash")
|
||||
val commonId = commonImageId()
|
||||
val imageId = imageIdFromHash(hash)
|
||||
val subDomain = 'a' + subdomainOffset(imageId)
|
||||
|
||||
"https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
|
||||
},
|
||||
"https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
|
||||
},
|
||||
author =
|
||||
json.optJSONArray("artists")
|
||||
?.mapJSON { it.getString("artist").toCamelCase() }
|
||||
?.joinToString(),
|
||||
json.optJSONArray("artists")
|
||||
?.mapJSON { it.getString("artist").toCamelCase() }
|
||||
?.joinToString(),
|
||||
publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain),
|
||||
tags =
|
||||
buildSet {
|
||||
json.optJSONArray("characters")
|
||||
?.mapToTags("character")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("tags")
|
||||
?.mapToTags("tag")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("artists")
|
||||
?.mapToTags("artist")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("parodys")
|
||||
?.mapToTags("parody")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("groups")
|
||||
?.mapToTags("group")
|
||||
?.let(::addAll)
|
||||
},
|
||||
buildSet {
|
||||
json.optJSONArray("characters")
|
||||
?.mapToTags("character")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("tags")
|
||||
?.mapToTags("tag")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("artists")
|
||||
?.mapToTags("artist")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("parodys")
|
||||
?.mapToTags("parody")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("groups")
|
||||
?.mapToTags("group")
|
||||
?.let(::addAll)
|
||||
},
|
||||
chapters = listOf(
|
||||
MangaChapter(
|
||||
id = generateUid(manga.url),
|
||||
@@ -562,15 +557,15 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa
|
||||
mapJSON {
|
||||
MangaTag(
|
||||
title =
|
||||
it.getString(key).toCamelCase().let { title ->
|
||||
if (it.getStringOrNull("female")?.toIntOrNull() == 1) {
|
||||
"$title ♀"
|
||||
} else if (it.getStringOrNull("male")?.toIntOrNull() == 1) {
|
||||
"$title ♂"
|
||||
} else {
|
||||
title
|
||||
}
|
||||
},
|
||||
it.getString(key).toCamelCase().let { title ->
|
||||
if (it.getStringOrNull("female")?.toIntOrNull() == 1) {
|
||||
"$title ♀"
|
||||
} else if (it.getStringOrNull("male")?.toIntOrNull() == 1) {
|
||||
"$title ♂"
|
||||
} else {
|
||||
title
|
||||
}
|
||||
},
|
||||
key = it.getString("url").tagUrlToTag(),
|
||||
source = source,
|
||||
).let(tags::add)
|
||||
|
||||
@@ -27,19 +27,23 @@ internal class ImHentai(context: MangaLoaderContext) :
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
order: SortOrder,
|
||||
filter: MangaListFilterV2,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/search/?page=")
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&key=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&key=")
|
||||
@@ -53,17 +57,13 @@ internal class ImHentai(context: MangaLoaderContext) :
|
||||
}
|
||||
append(lang)
|
||||
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> append("<=1&pp=0")
|
||||
SortOrder.POPULARITY -> append("<=0&pp=1")
|
||||
SortOrder.RATING -> append("<=0&pp=0")
|
||||
else -> append("<=1&pp=0")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("<=1&pp=0")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -135,117 +135,80 @@ internal abstract class LineWebtoonsParser(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val manga =
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
makeRequest("/lineWebtoon/webtoon/searchChallenge?query=${filter.query.urlEncoded()}&startIndex=${offset + 1}&pageSize=20")
|
||||
.getJSONObject("challengeSearch")
|
||||
.getJSONArray("titleList")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val manga = when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
makeRequest("/lineWebtoon/webtoon/searchChallenge?query=${filter.query.urlEncoded()}&startIndex=${offset + 1}&pageSize=20")
|
||||
.getJSONObject("challengeSearch")
|
||||
.getJSONArray("titleList")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = isNsfwSource,
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = null,
|
||||
tags = emptySet(),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = null,
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val genre = filter.tags.oneOrThrowIfMany()?.key ?: "ALL"
|
||||
|
||||
val sortOrderStr = when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> "UPDATE"
|
||||
SortOrder.POPULARITY -> "READ_COUNT"
|
||||
SortOrder.RATING -> "LIKEIT"
|
||||
else -> throw IllegalArgumentException("Unsupported sort order: ${filter.sortOrder}")
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = isNsfwSource,
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = null,
|
||||
tags = emptySet(),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = null,
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
val result =
|
||||
makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=$genre&sortOrder=$sortOrderStr&startIndex=${offset + 1}&pageSize=20")
|
||||
|
||||
val genres = result.getJSONObject("genreList")
|
||||
.getJSONArray("challengeGenres")
|
||||
.mapJSON { jo -> parseTag(jo) }
|
||||
.associateBy { tag -> tag.key }
|
||||
|
||||
result
|
||||
.getJSONObject("titleList")
|
||||
.getJSONArray("titles")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
|
||||
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource),
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
|
||||
tags = setOfNotNull(genres[jo.getString("representGenre")]),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = jo.getString("synopsis"),
|
||||
// I don't think the API provides this info
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
|
||||
val result =
|
||||
makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=ALL&sortOrder=UPDATE&startIndex=${offset + 1}&pageSize=20")
|
||||
|
||||
val genres = result.getJSONObject("genreList")
|
||||
.getJSONArray("challengeGenres")
|
||||
.mapJSON { jo -> parseTag(jo) }
|
||||
.associateBy { tag -> tag.key }
|
||||
|
||||
result
|
||||
.getJSONObject("titleList")
|
||||
.getJSONArray("titles")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
|
||||
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource),
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
|
||||
tags = setOfNotNull(genres[jo.getString("representGenre")]),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = jo.getString("synopsis"),
|
||||
// I don't think the API provides this info
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
val genre = filter.tags.oneOrThrowIfMany()?.key ?: "ALL"
|
||||
|
||||
val sortOrderStr = when (order) {
|
||||
SortOrder.UPDATED -> "UPDATE"
|
||||
SortOrder.POPULARITY -> "READ_COUNT"
|
||||
SortOrder.RATING -> "LIKEIT"
|
||||
else -> throw IllegalArgumentException("Unsupported sort order: $order")
|
||||
}
|
||||
|
||||
val result =
|
||||
makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=$genre&sortOrder=$sortOrderStr&startIndex=${offset + 1}&pageSize=20")
|
||||
|
||||
val genres = result.getJSONObject("genreList")
|
||||
.getJSONArray("challengeGenres")
|
||||
.mapJSON { jo -> parseTag(jo) }
|
||||
.associateBy { tag -> tag.key }
|
||||
|
||||
result
|
||||
.getJSONObject("titleList")
|
||||
.getJSONArray("titles")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
|
||||
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource),
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
|
||||
tags = setOfNotNull(genres[jo.getString("representGenre")]),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = jo.getString("synopsis"),
|
||||
// I don't think the API provides this info
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return manga
|
||||
}
|
||||
|
||||
@@ -33,19 +33,36 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = true,
|
||||
isYearSupported = true,
|
||||
isYearRangeSupported = true,
|
||||
isSourceLocaleSupported = true,
|
||||
)
|
||||
|
||||
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.allOf(SortOrder::class.java)
|
||||
|
||||
override val availableContentRating: Set<ContentRating> = EnumSet.allOf(ContentRating::class.java)
|
||||
|
||||
override val availableDemographics: Set<Demographic> = EnumSet.allOf(Demographic::class.java)
|
||||
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
|
||||
|
||||
override val isTagsExclusionSupported: Boolean = true
|
||||
override val searchSupportedWithMultipleFilters: Boolean = true
|
||||
override val isSearchYearSupported: Boolean = true
|
||||
override val isSearchOriginalLanguages: Boolean = true
|
||||
override suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope {
|
||||
val localesDeferred = async { fetchAvailableLocales() }
|
||||
val tagsDeferred = async { fetchAvailableTags() }
|
||||
MangaListFilterOptions(
|
||||
availableTags = tagsDeferred.await(),
|
||||
availableStates = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.PAUSED,
|
||||
MangaState.ABANDONED,
|
||||
),
|
||||
availableContentRating = EnumSet.allOf(ContentRating::class.java),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = EnumSet.allOf(Demographic::class.java),
|
||||
availableLocales = localesDeferred.await(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val domain = domain
|
||||
@@ -231,7 +248,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val tags = webClient.httpGet("https://api.${domain}/manga/tag").parseJson()
|
||||
.getJSONArray("data")
|
||||
return tags.mapJSONToSet { jo ->
|
||||
@@ -245,7 +262,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableLocales(): Set<Locale> {
|
||||
private suspend fun fetchAvailableLocales(): Set<Locale> {
|
||||
val head = webClient.httpGet("https://$domain/").parseHtml().head()
|
||||
return head.getElementsByAttributeValue("property", "og:locale:alternate")
|
||||
.mapNotNullToSet { meta ->
|
||||
|
||||
@@ -60,8 +60,6 @@ internal abstract class MangaFireParser(
|
||||
?: body.parseFailed("Cannot find username")
|
||||
}
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
private val tags = SoftSuspendLazy {
|
||||
webClient.httpGet("https://$domain/filter").parseHtml()
|
||||
.select(".genres > li").map {
|
||||
@@ -73,36 +71,48 @@ internal abstract class MangaFireParser(
|
||||
}.associateBy { it.title }
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return tags.get().values.toSet()
|
||||
}
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override val isTagsExclusionSupported = true
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = tags.get().values.toSet(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = "https://$domain/filter".toHttpUrl().newBuilder().apply {
|
||||
addQueryParameter("page", page.toString())
|
||||
addQueryParameter("language[]", siteLang)
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
addQueryParameter("keyword", filter.query)
|
||||
filter.sortOrder?.let {
|
||||
addQueryParameter(
|
||||
name = "sort",
|
||||
value = when (it) {
|
||||
SortOrder.UPDATED -> "recently_updated"
|
||||
SortOrder.POPULARITY -> "most_viewed"
|
||||
SortOrder.RATING -> "scores"
|
||||
SortOrder.NEWEST -> "release_date"
|
||||
SortOrder.ALPHABETICAL -> "title_az"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
addQueryParameter(
|
||||
name = "sort",
|
||||
value = when (order) {
|
||||
SortOrder.UPDATED -> "recently_updated"
|
||||
SortOrder.POPULARITY -> "most_viewed"
|
||||
SortOrder.RATING -> "scores"
|
||||
SortOrder.NEWEST -> "release_date"
|
||||
SortOrder.ALPHABETICAL -> "title_az"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
filter.tagsExclude.forEach { tag ->
|
||||
addQueryParameter("genre[]", "-${tag.key}")
|
||||
}
|
||||
@@ -126,7 +136,7 @@ internal abstract class MangaFireParser(
|
||||
}
|
||||
addQueryParameter(
|
||||
name = "sort",
|
||||
value = when (filter.sortOrder) {
|
||||
value = when (order) {
|
||||
SortOrder.UPDATED -> "recently_updated"
|
||||
SortOrder.POPULARITY -> "most_viewed"
|
||||
SortOrder.RATING -> "scores"
|
||||
@@ -136,8 +146,6 @@ internal abstract class MangaFireParser(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}.build()
|
||||
|
||||
|
||||
@@ -19,14 +19,49 @@ internal class MangaPark(context: MangaLoaderContext) :
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.SAFE)
|
||||
|
||||
override val isTagsExclusionSupported: Boolean = true
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangapark.net")
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = tagsMap.get().values.toSet(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
availableContentRating = EnumSet.of(ContentRating.SAFE),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = setOf(
|
||||
Locale("af"), Locale("sq"), Locale("am"), Locale("ar"), Locale("hy"),
|
||||
Locale("az"), Locale("be"), Locale("bn"), Locale("zh_hk"), Locale("zh_tw"),
|
||||
Locale.CHINESE, Locale("ceb"), Locale("ca"), Locale("km"), Locale("my"),
|
||||
Locale("bg"), Locale("bs"), Locale("hr"), Locale("cs"), Locale("da"),
|
||||
Locale("nl"), Locale.ENGLISH, Locale("et"), Locale("fo"), Locale("fil"),
|
||||
Locale("fi"), Locale("he"), Locale("ha"), Locale("jv"), Locale("lb"),
|
||||
Locale("mn"), Locale("ro"), Locale("si"), Locale("ta"), Locale("uz"),
|
||||
Locale("ur"), Locale("tg"), Locale("sd"), Locale("pt_br"), Locale("mo"),
|
||||
Locale("lt"), Locale.JAPANESE, Locale.ITALIAN, Locale("ht"), Locale("lv"),
|
||||
Locale("mr"), Locale("pt"), Locale("sn"), Locale("sv"), Locale("uk"),
|
||||
Locale("tk"), Locale("sw"), Locale("st"), Locale("pl"), Locale("mi"),
|
||||
Locale("lo"), Locale("ga"), Locale("gu"), Locale("gn"), Locale("id"),
|
||||
Locale("ky"), Locale("mt"), Locale("fa"), Locale("sh"), Locale("es_419"),
|
||||
Locale("tr"), Locale("to"), Locale("vi"), Locale("es"), Locale("sr"),
|
||||
Locale("ps"), Locale("ml"), Locale("ku"), Locale("ig"), Locale("el"),
|
||||
Locale.GERMAN, Locale("is"), Locale.KOREAN, Locale("ms"), Locale("ny"), Locale("sm"),
|
||||
Locale("so"), Locale("ti"), Locale("zu"), Locale("yo"), Locale("th"),
|
||||
Locale("sl"), Locale("ru"), Locale("no"), Locale("mg"), Locale("kk"),
|
||||
Locale("hu"), Locale("ka"), Locale.FRENCH, Locale("hi"), Locale("kn"),
|
||||
Locale("mk"), Locale("ne"), Locale("rm"), Locale("sk"), Locale("te"),
|
||||
),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
@@ -38,19 +73,19 @@ internal class MangaPark(context: MangaLoaderContext) :
|
||||
context.cookieJar.insertCookies(domain, "nsfw", "2")
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/search?page=")
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&word=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("&genres=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -88,7 +123,7 @@ internal class MangaPark(context: MangaLoaderContext) :
|
||||
|
||||
append("&sortby=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> "views_d000"
|
||||
SortOrder.UPDATED -> "field_update"
|
||||
SortOrder.NEWEST -> "field_create"
|
||||
@@ -104,8 +139,6 @@ internal class MangaPark(context: MangaLoaderContext) :
|
||||
append(it.language)
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&sortby=field_update")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,10 +162,6 @@ internal class MangaPark(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return tagsMap.get().values.toSet()
|
||||
}
|
||||
|
||||
private suspend fun parseTags(): Map<String, MangaTag> {
|
||||
val tagElements = webClient.httpGet("https://$domain/search").parseHtml()
|
||||
.select("div.flex-col:contains(Genres) div.whitespace-nowrap")
|
||||
@@ -149,29 +178,6 @@ internal class MangaPark(context: MangaLoaderContext) :
|
||||
return tagMap
|
||||
}
|
||||
|
||||
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
|
||||
Locale("af"), Locale("sq"), Locale("am"), Locale("ar"), Locale("hy"),
|
||||
Locale("az"), Locale("be"), Locale("bn"), Locale("zh_hk"), Locale("zh_tw"),
|
||||
Locale.CHINESE, Locale("ceb"), Locale("ca"), Locale("km"), Locale("my"),
|
||||
Locale("bg"), Locale("bs"), Locale("hr"), Locale("cs"), Locale("da"),
|
||||
Locale("nl"), Locale.ENGLISH, Locale("et"), Locale("fo"), Locale("fil"),
|
||||
Locale("fi"), Locale("he"), Locale("ha"), Locale("jv"), Locale("lb"),
|
||||
Locale("mn"), Locale("ro"), Locale("si"), Locale("ta"), Locale("uz"),
|
||||
Locale("ur"), Locale("tg"), Locale("sd"), Locale("pt_br"), Locale("mo"),
|
||||
Locale("lt"), Locale.JAPANESE, Locale.ITALIAN, Locale("ht"), Locale("lv"),
|
||||
Locale("mr"), Locale("pt"), Locale("sn"), Locale("sv"), Locale("uk"),
|
||||
Locale("tk"), Locale("sw"), Locale("st"), Locale("pl"), Locale("mi"),
|
||||
Locale("lo"), Locale("ga"), Locale("gu"), Locale("gn"), Locale("id"),
|
||||
Locale("ky"), Locale("mt"), Locale("fa"), Locale("sh"), Locale("es_419"),
|
||||
Locale("tr"), Locale("to"), Locale("vi"), Locale("es"), Locale("sr"),
|
||||
Locale("ps"), Locale("ml"), Locale("ku"), Locale("ig"), Locale("el"),
|
||||
Locale.GERMAN, Locale("is"), Locale.KOREAN, Locale("ms"), Locale("ny"), Locale("sm"),
|
||||
Locale("so"), Locale("ti"), Locale("zu"), Locale("yo"), Locale("th"),
|
||||
Locale("sl"), Locale("ru"), Locale("no"), Locale("mg"), Locale("kk"),
|
||||
Locale("hu"), Locale("ka"), Locale.FRENCH, Locale("hi"), Locale("kn"),
|
||||
Locale("mk"), Locale("ne"), Locale("rm"), Locale("sk"), Locale("te"),
|
||||
)
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val tagMap = tagsMap.get()
|
||||
|
||||
@@ -9,8 +9,8 @@ import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaParser
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
@@ -24,7 +24,7 @@ internal abstract class MangaPlusParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaParserSource,
|
||||
private val sourceLang: String,
|
||||
) : MangaParser(context, source), Interceptor {
|
||||
) : SinglePageMangaParser(context, source), Interceptor {
|
||||
|
||||
private val apiUrl = "https://jumpg-webapi.tokyo-cdn.com/api"
|
||||
override val configKeyDomain = ConfigKey.Domain("mangaplus.shueisha.co.jp")
|
||||
@@ -45,22 +45,17 @@ internal abstract class MangaPlusParser(
|
||||
// no tags or tag search available
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (offset > 0) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return when (filter) {
|
||||
is MangaListFilter.Advanced -> {
|
||||
when (filter.sortOrder) {
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
return when {
|
||||
filter.query.isNullOrEmpty() -> {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> getPopularList()
|
||||
SortOrder.UPDATED -> getLatestList()
|
||||
else -> getAllTitleList()
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Search -> getAllTitleList(filter.query)
|
||||
else -> getAllTitleList()
|
||||
else -> getAllTitleList(filter.query)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,6 @@ class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
val tags = SoftSuspendLazy {
|
||||
val document = webClient.httpGet("https://$domain/filter").parseHtml()
|
||||
|
||||
@@ -69,27 +67,41 @@ class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}.associateBy { it.title }
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return tags.get().values.toSet()
|
||||
}
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override val isTagsExclusionSupported = false
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = tags.get().values.toSet(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = "https://$domain".toHttpUrl().newBuilder().apply {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
addPathSegment("search")
|
||||
addQueryParameter("keyword", filter.query)
|
||||
addQueryParameter("page", page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
addPathSegment("filter")
|
||||
addQueryParameter("page", page.toString())
|
||||
addQueryParameter(
|
||||
name = "sort",
|
||||
value = when (filter.sortOrder) {
|
||||
value = when (order) {
|
||||
SortOrder.POPULARITY -> "most-viewed"
|
||||
SortOrder.RATING -> "score"
|
||||
SortOrder.UPDATED -> "latest-updated"
|
||||
@@ -111,11 +123,6 @@ class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
null -> {
|
||||
addPathSegment("filter")
|
||||
addQueryParameter("page", page.toString())
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
|
||||
|
||||
@@ -40,12 +40,28 @@ internal abstract class NineMangaParser(
|
||||
SortOrder.POPULARITY,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
)
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override val isTagsExclusionSupported: Boolean = true
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = getOrCreateTagMap().values.toSet(),
|
||||
availableStates = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
@@ -57,12 +73,12 @@ internal abstract class NineMangaParser(
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search/?name_sel=&wd=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
@@ -70,7 +86,7 @@ internal abstract class NineMangaParser(
|
||||
append(".html")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty() || filter.states.isNotEmpty()) {
|
||||
append("/search/?category_id=")
|
||||
@@ -94,12 +110,6 @@ internal abstract class NineMangaParser(
|
||||
append(page.toString())
|
||||
append(".html")
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/category/index_")
|
||||
append(page)
|
||||
append(".html")
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -184,10 +194,6 @@ internal abstract class NineMangaParser(
|
||||
private var tagCache: ArrayMap<String, MangaTag>? = null
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return getOrCreateTagMap().values.toSet()
|
||||
}
|
||||
|
||||
private suspend fun getOrCreateTagMap(): Map<String, MangaTag> = mutex.withLock {
|
||||
tagCache?.let { return@withLock it }
|
||||
val tagMap = ArrayMap<String, MangaTag>()
|
||||
|
||||
@@ -34,13 +34,29 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getAvailableLocales() = setOf(
|
||||
Locale.ENGLISH,
|
||||
Locale.CHINESE,
|
||||
Locale.JAPANESE,
|
||||
Locale("es"),
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = setOf(
|
||||
Locale.ENGLISH,
|
||||
Locale.CHINESE,
|
||||
Locale.JAPANESE,
|
||||
Locale("es"),
|
||||
),
|
||||
)
|
||||
|
||||
private fun Locale?.getSiteLang(): String {
|
||||
@@ -75,7 +91,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
return cdn?.toHttpUrlOrNull()?.host ?: "edge.fast4speed.rsvp"
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val query = """
|
||||
queryTags(
|
||||
search: {format:"tagchapter",sortBy:Popular}
|
||||
@@ -102,23 +118,15 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
return when (filter) {
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isEmpty() && filter.sortOrder == SortOrder.POPULARITY) {
|
||||
getPopularList(page, filter.locale)
|
||||
} else {
|
||||
getSearchList(page, null, filter.locale, filter.tags, filter.sortOrder)
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
getSearchList(page, filter.query, null, null, filter.sortOrder)
|
||||
}
|
||||
|
||||
else -> {
|
||||
getPopularList(page, null)
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
return if (filter.query.isNullOrEmpty()) {
|
||||
if (filter.tags.isEmpty() && order == SortOrder.POPULARITY) {
|
||||
getPopularList(page, filter.locale)
|
||||
} else {
|
||||
getSearchList(page, null, filter.locale, filter.tags, order)
|
||||
}
|
||||
} else {
|
||||
getSearchList(page, filter.query, null, null, order)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ internal abstract class WebtoonsParser(
|
||||
source: MangaParserSource,
|
||||
) : MangaParser(context, source) {
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
private val signer by lazy {
|
||||
WebtoonsUrlSigner("gUtPzJFZch4ZyAGviiyH94P99lQ3pFdRTwpJWDlSGFfwgpr6ses5ALOxWHOIT7R1")
|
||||
}
|
||||
@@ -49,8 +47,20 @@ internal abstract class WebtoonsParser(
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
)
|
||||
|
||||
override val userAgentKey = ConfigKey.UserAgent("nApps (Android 12;; linewebtoon; 3.1.0)")
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = getAllGenreList().values.toSet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
@@ -182,10 +192,9 @@ internal abstract class WebtoonsParser(
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val webtoons = when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val webtoons = when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
makeRequest("/lineWebtoon/webtoon/searchWebtoon?query=${filter.query.urlEncoded()}").getJSONObject("webtoonSearch")
|
||||
.getJSONArray("titleList").mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
@@ -210,7 +219,7 @@ internal abstract class WebtoonsParser(
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
val genre = filter.tags.oneOrThrowIfMany()?.key ?: "ALL"
|
||||
|
||||
val genres = getAllGenreList()
|
||||
@@ -220,17 +229,14 @@ internal abstract class WebtoonsParser(
|
||||
result = result.filter { it.manga.tags.contains(genres[genre]) }
|
||||
}
|
||||
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> result.sortedByDescending { it.date }
|
||||
SortOrder.POPULARITY -> result.sortedByDescending { it.readCount }
|
||||
SortOrder.RATING -> result.sortedByDescending { it.manga.rating }
|
||||
//SortOrder.LIKE -> result.sortedBy { it.likeitCount }
|
||||
else -> throw IllegalArgumentException("Unsupported sort order: ${filter.sortOrder}")
|
||||
else -> throw IllegalArgumentException("Unsupported sort order: $order")
|
||||
}
|
||||
}
|
||||
|
||||
else -> getAllTitleList()
|
||||
|
||||
}
|
||||
return webtoons.map { it.manga }.subList(offset, (offset + 20).coerceAtMost(webtoons.size))
|
||||
}
|
||||
@@ -257,10 +263,6 @@ internal abstract class WebtoonsParser(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return getAllGenreList().values.toSet()
|
||||
}
|
||||
|
||||
private suspend fun makeRequest(url: String): JSONObject {
|
||||
val resp = webClient.httpGet(finalizeUrl(url))
|
||||
val message: JSONObject? = resp.parseJson().optJSONObject("message")
|
||||
|
||||
@@ -25,8 +25,6 @@ internal abstract class AnimeBootstrapParser(
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
@@ -37,13 +35,32 @@ internal abstract class AnimeBootstrapParser(
|
||||
protected open val listUrl = "/manga"
|
||||
protected open val datePattern = "dd MMM. yyyy"
|
||||
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
@@ -52,13 +69,13 @@ internal abstract class AnimeBootstrapParser(
|
||||
append(page.toString())
|
||||
append("&type=all")
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&categorie=")
|
||||
@@ -66,7 +83,7 @@ internal abstract class AnimeBootstrapParser(
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("view")
|
||||
SortOrder.UPDATED -> append("updated")
|
||||
SortOrder.ALPHABETICAL -> append("default")
|
||||
@@ -75,8 +92,6 @@ internal abstract class AnimeBootstrapParser(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("&sort=updated")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -100,7 +115,7 @@ internal abstract class AnimeBootstrapParser(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
protected open suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain$listUrl").parseHtml()
|
||||
return doc.select("div.product__page__filter div:contains(Genre:) option ").mapNotNullToSet { option ->
|
||||
val key = option.attr("value") ?: return@mapNotNullToSet null
|
||||
|
||||
@@ -17,7 +17,6 @@ import java.util.*
|
||||
internal class PapScan(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaParserSource.PAPSCAN, "papscan.com") {
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
override val isMultipleTagsSupported = false
|
||||
override val listUrl = "/liste-manga"
|
||||
override val selectState = "div.anime__details__widget li:contains(En cours)"
|
||||
override val selectTag = "div.anime__details__widget li:contains(Genre) a"
|
||||
@@ -29,20 +28,20 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
SortOrder.ALPHABETICAL_DESC,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/filterList")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&alpha=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&cat=")
|
||||
@@ -50,7 +49,7 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("&sortBy=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("name&asc=false")
|
||||
SortOrder.ALPHABETICAL -> append("name&asc=true")
|
||||
@@ -58,8 +57,6 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("&sortBy=updated")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -82,7 +79,7 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
override suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain$listUrl").parseHtml()
|
||||
return doc.select("a.category ").mapNotNullToSet { a ->
|
||||
val key = a.attr("href").substringAfterLast('=')
|
||||
|
||||
@@ -19,19 +19,36 @@ import java.util.*
|
||||
internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.ADULT)
|
||||
override val configKeyDomain = ConfigKey.Domain("flixscans.net")
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
availableContentRating = EnumSet.of(ContentRating.ADULT),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val json = when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val json = when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -41,7 +58,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
webClient.httpPost(url, body).parseJson().getJSONArray("data")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
@@ -90,11 +107,6 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = "https://api.$domain/api/v1/webtoon/pages/latest/romance?page=$page"
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
}
|
||||
return json.mapJSON { j ->
|
||||
val href = "https://$domain/series/${j.getString("prefix")}-${j.getString("id")}-${j.getString("slug")}"
|
||||
@@ -122,7 +134,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/search/advance").parseHtml()
|
||||
val json = JSONArray(doc.requireElementById("__NUXT_DATA__").data())
|
||||
val tagsList = json.getJSONArray(3).toString().replace("[", "").replace("]", "").split(",")
|
||||
|
||||
@@ -25,19 +25,19 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
append("&query=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
@@ -46,7 +46,7 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
append("?page=")
|
||||
append(page)
|
||||
} else {
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
} else {
|
||||
@@ -56,11 +56,6 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -17,8 +17,6 @@ import java.util.*
|
||||
internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("teamoney.site")
|
||||
|
||||
@@ -27,16 +25,33 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
@@ -45,7 +60,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
append("/series?genre=")
|
||||
@@ -56,7 +71,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}
|
||||
append("&")
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("/series")
|
||||
SortOrder.UPDATED -> append("/")
|
||||
else -> append("/")
|
||||
@@ -70,7 +85,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY || filter.tags.isNotEmpty()) {
|
||||
if (order == SortOrder.POPULARITY || filter.tags.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("status=")
|
||||
append(
|
||||
@@ -84,8 +99,6 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?page=$page")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -115,7 +128,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/series").parseHtml()
|
||||
return doc.requireElementById("select_genre").select("option").mapNotNullToSet {
|
||||
MangaTag(
|
||||
|
||||
@@ -34,31 +34,26 @@ internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context,
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
filter: MangaListFilter?,
|
||||
): List<Manga> {
|
||||
val filters =
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
return if (offset == 0) {
|
||||
search(filter.query)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val filters = when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
return if (offset == 0) {
|
||||
search(filter.query)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
filter.tags.takeUnless { it.isEmpty() }?.joinToString(
|
||||
separator = ",",
|
||||
prefix = "genres: [",
|
||||
postfix = "]",
|
||||
) { "\"${it.key}\"" }.orEmpty()
|
||||
}
|
||||
|
||||
null -> ""
|
||||
}
|
||||
|
||||
else -> {
|
||||
filter.tags.takeUnless { it.isEmpty() }?.joinToString(
|
||||
separator = ",",
|
||||
prefix = "genres: [",
|
||||
postfix = "]",
|
||||
) { "\"${it.key}\"" }.orEmpty()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val array = apiCall(
|
||||
"""
|
||||
getMediaList(offset: $offset, limit: 20, mediaType: manga, filters: {$filters}) {
|
||||
|
||||
@@ -27,26 +27,42 @@ internal abstract class CupFoxParser(
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search/")
|
||||
append(filter.query.urlEncoded())
|
||||
append('/')
|
||||
append(page)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append("/category/")
|
||||
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("order/hits/")
|
||||
SortOrder.UPDATED -> append("order/addtime/")
|
||||
else -> append("order/addtime/")
|
||||
@@ -73,11 +89,6 @@ internal abstract class CupFoxParser(
|
||||
append("page/")
|
||||
append(page)
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/category/order/addtime/page/")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
@@ -193,7 +204,8 @@ internal abstract class CupFoxParser(
|
||||
}
|
||||
|
||||
protected open val selectAvailableTags = "div.swiper-wrapper a[href*=tags], ul.stui-screen__list li a[href*=tags]"
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
|
||||
protected open suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/category/").parseHtml()
|
||||
return doc.select(selectAvailableTags)
|
||||
.mapNotNullToSet { a ->
|
||||
|
||||
@@ -25,31 +25,47 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("asuracomic.net")
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = getOrCreateTagMap().values.toSet(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val isMultipleTagsSupported = true
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/series?page=")
|
||||
append(page)
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&name=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&genres=")
|
||||
@@ -70,7 +86,7 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("&types=-1&order=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.RATING -> append("rating")
|
||||
SortOrder.UPDATED -> append("update")
|
||||
SortOrder.NEWEST -> append("latest")
|
||||
@@ -79,8 +95,6 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
|
||||
else -> append("update")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&genres=&status=-1&order=update&types=-1")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -113,10 +127,6 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
|
||||
private var tagCache: ArrayMap<String, MangaTag>? = null
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return getOrCreateTagMap().values.toSet()
|
||||
}
|
||||
|
||||
private suspend fun getOrCreateTagMap(): Map<String, MangaTag> = mutex.withLock {
|
||||
tagCache?.let { return@withLock it }
|
||||
val tagMap = ArrayMap<String, MangaTag>()
|
||||
|
||||
@@ -22,14 +22,32 @@ internal class BeeToon(context: MangaLoaderContext) :
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -37,7 +55,7 @@ internal class BeeToon(context: MangaLoaderContext) :
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
@@ -47,7 +65,7 @@ internal class BeeToon(context: MangaLoaderContext) :
|
||||
append(page)
|
||||
append("/")
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> append("/latest-update/")
|
||||
SortOrder.POPULARITY -> append("/popular-manga/")
|
||||
else -> append("/latest-update/")
|
||||
@@ -57,8 +75,6 @@ internal class BeeToon(context: MangaLoaderContext) :
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/latest-update/page-$page/")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -85,7 +101,7 @@ internal class BeeToon(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/").parseHtml()
|
||||
return doc.requireElementById("menu-item-3").select("ul.sub-menu li a").mapNotNullToSet {
|
||||
MangaTag(
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import org.koitharu.kotatsu.parsers.InternalParsersApi
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaParser
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("CLONEMANGA", "CloneManga", "en")
|
||||
internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.CLONEMANGA) {
|
||||
internal class CloneMangaParser(context: MangaLoaderContext) :
|
||||
SinglePageMangaParser(context, MangaParserSource.CLONEMANGA) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = Collections.singleton(
|
||||
SortOrder.POPULARITY,
|
||||
@@ -23,27 +23,11 @@ internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(conte
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
@InternalParsersApi
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val link = when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (offset > 0) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
"https://$domain/viewer_landing.php"
|
||||
}
|
||||
|
||||
null -> "https://$domain/viewer_landing.php"
|
||||
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(link).parseHtml()
|
||||
val doc = webClient.httpGet("https://$domain/viewer_landing.php").parseHtml()
|
||||
val mangas = doc.getElementsByClass("comicPreviewContainer")
|
||||
return mangas.mapNotNull { item ->
|
||||
val background = item.selectFirstOrThrow(".comicPreview").styleValueOrNull("background")
|
||||
|
||||
@@ -30,14 +30,13 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("search?keyword=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
@@ -46,7 +45,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.isNotEmpty() && filter.states.isEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
@@ -65,7 +64,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
} else if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) {
|
||||
throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED)
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("popular-comic")
|
||||
SortOrder.UPDATED -> append("new-comic")
|
||||
SortOrder.NEWEST -> append("recent-comic")
|
||||
@@ -78,15 +77,6 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("popular-comic")
|
||||
if (page > 1) {
|
||||
append("/")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -32,9 +32,9 @@ internal class DynastyScans(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
@@ -47,7 +47,7 @@ internal class DynastyScans(context: MangaLoaderContext) :
|
||||
return parseMangaListQuery(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
@@ -68,16 +68,6 @@ internal class DynastyScans(context: MangaLoaderContext) :
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/series?view=cover&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,14 +31,13 @@ internal class FlixScansOrg(context: MangaLoaderContext) :
|
||||
|
||||
override val isSearchSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val json = when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val json = when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
@@ -75,11 +74,6 @@ internal class FlixScansOrg(context: MangaLoaderContext) :
|
||||
}
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = "https://api.$domain/api/v1/search/advance?=&serie_type=webtoon&page=$page"
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
}
|
||||
return json.mapJSON { j ->
|
||||
val href = "https://$domain/series/${j.getString("prefix")}-${j.getString("id")}-${j.getString("slug")}"
|
||||
|
||||
@@ -26,13 +26,12 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -40,13 +39,13 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("/browse-comics/?results=")
|
||||
append(page)
|
||||
|
||||
append("&filter=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("Updated")
|
||||
SortOrder.NEWEST -> append("New")
|
||||
@@ -60,12 +59,6 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/browse-comics/?results=")
|
||||
append(page)
|
||||
append("&filter=Updated")
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -31,26 +31,25 @@ internal class MangaKawaiiEn(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?query=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&search_type=manga&page=")
|
||||
append(page)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.sortOrder == SortOrder.UPDATED && filter.tags.isNotEmpty()) {
|
||||
if (order == SortOrder.UPDATED && filter.tags.isNotEmpty()) {
|
||||
throw IllegalArgumentException("Filter part tag is not available with sort not updated")
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
|
||||
if (order == SortOrder.ALPHABETICAL) {
|
||||
append("/manga-list")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/category/")
|
||||
@@ -62,12 +61,6 @@ internal class MangaKawaiiEn(context: MangaLoaderContext) :
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,19 +35,19 @@ internal class MangaTownParser(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?name=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append("/directory/")
|
||||
append("0-")
|
||||
|
||||
@@ -79,7 +79,7 @@ internal class MangaTownParser(context: MangaLoaderContext) :
|
||||
append(".htm")
|
||||
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> ""
|
||||
SortOrder.UPDATED -> "?last_chapter_time.za"
|
||||
SortOrder.ALPHABETICAL -> "?name.az"
|
||||
@@ -88,8 +88,6 @@ internal class MangaTownParser(context: MangaLoaderContext) :
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
null -> append("/directory/$page.htm?last_chapter_time.za")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -25,32 +25,53 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangaowl.to")
|
||||
|
||||
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
order: SortOrder,
|
||||
filter: MangaListFilterV2,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/10-search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("/10-comics")
|
||||
append("?page=")
|
||||
@@ -74,7 +95,7 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
|
||||
append("&ordering=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> "view_count"
|
||||
SortOrder.UPDATED -> "-modified_at"
|
||||
SortOrder.NEWEST -> "created_at"
|
||||
@@ -83,11 +104,6 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/10-comics?ordering=-modified_at&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -110,7 +126,7 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/10-genres").parseHtml()
|
||||
return doc.select("div.genres-container span.genre-item a").mapNotNullToSet { a ->
|
||||
val key = a.attr("href").removeSuffix('/').substringAfterLast('/').substringBefore("-")
|
||||
|
||||
@@ -48,21 +48,20 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/tim-kiem?page=")
|
||||
append(page.toString())
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("&accept_genres=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -80,7 +79,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
|
||||
append("&sort=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.ALPHABETICAL_DESC -> "za"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
@@ -103,8 +102,6 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&sort=update")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,21 +28,24 @@ class ManhwasMen(context: MangaLoaderContext) :
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
order: SortOrder,
|
||||
filter: MangaListFilterV2,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/manga-list")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&genero=")
|
||||
@@ -60,8 +63,6 @@ class ManhwasMen(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaParser
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
@@ -10,7 +11,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("PO2SCANS", "Po2Scans", "en")
|
||||
internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.PO2SCANS) {
|
||||
internal class Po2Scans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.PO2SCANS) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
override val configKeyDomain = ConfigKey.Domain("po2scans.com")
|
||||
@@ -20,23 +21,14 @@ internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, Mang
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (offset > 0) {
|
||||
return emptyList()
|
||||
}
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/series")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {}
|
||||
|
||||
null -> {}
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
append("?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -29,19 +29,19 @@ internal class Pururin(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append("/browse")
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
@@ -54,7 +54,7 @@ internal class Pururin(context: MangaLoaderContext) :
|
||||
append(page)
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("most-viewed")
|
||||
SortOrder.RATING -> append("highest-rated")
|
||||
@@ -62,11 +62,6 @@ internal class Pururin(context: MangaLoaderContext) :
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/browse?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -36,20 +36,19 @@ class VyManga(context: MangaLoaderContext) :
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
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)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isEmpty()) {
|
||||
|
||||
@@ -84,7 +83,7 @@ class VyManga(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("viewed&sort_type=desc")
|
||||
SortOrder.POPULARITY_ASC -> append("viewed&sort_type=asc")
|
||||
SortOrder.RATING -> append("scored&sort_type=desc")
|
||||
@@ -99,11 +98,6 @@ class VyManga(context: MangaLoaderContext) :
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/search?search_po=0&q=&author_po=0&author=&completed=2&sort=updated_at&sort_type=desc&page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.parsers.site.es
|
||||
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.jsoup.nodes.Document
|
||||
import org.koitharu.kotatsu.parsers.ErrorMessages
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
@@ -27,32 +26,36 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val isSearchSupported = false
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = false,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = emptySet(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.sortOrder == SortOrder.NEWEST) {
|
||||
append("/comics?page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/comics?page=")
|
||||
append(page.toString())
|
||||
if (order == SortOrder.NEWEST) {
|
||||
append("/comics?page=")
|
||||
append(page)
|
||||
} else {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,8 +82,6 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
@@ -40,22 +40,22 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/library")
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("?title=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append("?order_item=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> "likes_count&order_dir=desc"
|
||||
SortOrder.POPULARITY_ASC -> "likes_count&order_dir=asc"
|
||||
SortOrder.UPDATED -> "release_date&order_dir=desc"
|
||||
@@ -88,10 +88,6 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("?order_item=release_date&order_dir=desc&filter_by=title")
|
||||
}
|
||||
}
|
||||
append("&_pg=1&page=")
|
||||
append(page.toString())
|
||||
|
||||
@@ -35,18 +35,21 @@ internal abstract class FmreaderParser(
|
||||
SortOrder.ALPHABETICAL_DESC,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.ABANDONED,
|
||||
)
|
||||
|
||||
override val isTagsExclusionSupported = true
|
||||
|
||||
protected open val listUrl = "/manga-list.html"
|
||||
protected open val datePattern = "MMMM d, yyyy"
|
||||
protected open val tagPrefix = "manga-list-genre-"
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
@@ -72,20 +75,33 @@ internal abstract class FmreaderParser(
|
||||
"drop",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.ABANDONED,
|
||||
),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&name=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("&genre=")
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
@@ -95,7 +111,7 @@ internal abstract class FmreaderParser(
|
||||
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views&sort_type=DESC")
|
||||
SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC")
|
||||
SortOrder.UPDATED -> append("last_update&sort_type=DESC")
|
||||
@@ -118,8 +134,6 @@ internal abstract class FmreaderParser(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("&sort=last_update")
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
@@ -150,7 +164,7 @@ internal abstract class FmreaderParser(
|
||||
|
||||
protected open val selectBodyTag = "ul.filter-type li a"
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
protected open suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
|
||||
return doc.select(selectBodyTag).mapNotNullToSet { a ->
|
||||
val href = a.attr("href").substringAfter(tagPrefix).substringBeforeLast(".html")
|
||||
|
||||
@@ -22,20 +22,20 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
|
||||
override val selectPage = "div#chapter-content img"
|
||||
override val selectBodyTag = "div.advanced-wrapper .genre_label"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/tim-kiem?page=")
|
||||
append(page.toString())
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("&accept_genres=")
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
@@ -45,7 +45,7 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
|
||||
|
||||
append("&sort=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.ALPHABETICAL_DESC -> "za"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
@@ -68,8 +68,6 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&sort=update")
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -17,12 +17,12 @@ internal class OlimpoScans(context: MangaLoaderContext) :
|
||||
override val isMultipleTagsSupported = false
|
||||
override val isTagsExclusionSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
@@ -30,7 +30,7 @@ internal class OlimpoScans(context: MangaLoaderContext) :
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/lista-de-comics-genero-")
|
||||
@@ -42,7 +42,7 @@ internal class OlimpoScans(context: MangaLoaderContext) :
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views&sort_type=DESC")
|
||||
SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC")
|
||||
SortOrder.UPDATED -> append("last_update&sort_type=DESC")
|
||||
@@ -65,13 +65,6 @@ internal class OlimpoScans(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&sort=last_update")
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -32,68 +32,69 @@ internal abstract class FoolSlideParser(
|
||||
protected open val pagination = true // false if the manga list has no pages
|
||||
protected open val datePattern = "yyyy.MM.dd"
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val doc =
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = emptySet(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
append(searchUrl)
|
||||
}
|
||||
|
||||
webClient.httpPost(url, "search=${filter.query.urlEncoded()}").parseHtml()
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val doc = when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
append(listUrl)
|
||||
// For some sites that don't have enough manga and page 2 links to page 1
|
||||
if (!pagination) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
} else {
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
append(searchUrl)
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
append(listUrl)
|
||||
if (!pagination) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
} else {
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
|
||||
}
|
||||
webClient.httpPost(url, "search=${filter.query.urlEncoded()}").parseHtml()
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
append(listUrl)
|
||||
// For some sites that don't have enough manga and page 2 links to page 1
|
||||
if (!pagination) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
} else {
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return doc.select("div.list div.group").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
@@ -114,8 +115,6 @@ internal abstract class FoolSlideParser(
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
protected open val selectInfo = "div.info"
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
|
||||
@@ -47,19 +47,19 @@ internal class BentomangaParser(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 0
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = urlBuilder()
|
||||
.host(domain)
|
||||
.addPathSegment("manga_list")
|
||||
.addQueryParameter("limit", page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
url.addQueryParameter("search", filter.query)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> url.addQueryParameter("order_by", "update")
|
||||
.addQueryParameter("order", "desc")
|
||||
|
||||
@@ -104,8 +104,6 @@ internal class BentomangaParser(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> url.addQueryParameter("order_by", "update")
|
||||
}
|
||||
val root = webClient.httpGet(url.build()).parseHtml().requireElementById("mangas_content")
|
||||
return root.select(".manga[data-manga]").map { div ->
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.koitharu.kotatsu.parsers.ErrorMessages
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
@@ -16,7 +17,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr")
|
||||
internal class FuryoSociety(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.FURYOSOCIETY, 0) {
|
||||
SinglePageMangaParser(context, MangaParserSource.FURYOSOCIETY) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED)
|
||||
|
||||
@@ -40,27 +41,20 @@ internal class FuryoSociety(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
|
||||
else -> {
|
||||
if (order == SortOrder.ALPHABETICAL) {
|
||||
append("/mangas")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,12 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val end = page * pageSize
|
||||
val start = end - (pageSize - 1)
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -43,7 +43,7 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
|
||||
return parseMangaListQuery(webClient.httpGet(url).parseJson())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
@@ -68,18 +68,6 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJson())
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/misc/comic/search/query?status=&order=&genreNames=&type=&start=")
|
||||
append(start)
|
||||
append("&end=")
|
||||
append(end)
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJson())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,52 +25,37 @@ internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context,
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val doc =
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val q = filter.query.urlEncoded().replace("%20", "+")
|
||||
val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q"
|
||||
webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/manga/")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val doc = when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val q = filter.query.urlEncoded().replace("%20", "+")
|
||||
val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q"
|
||||
webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml()
|
||||
}
|
||||
|
||||
else -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/manga/")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
}
|
||||
}
|
||||
|
||||
return doc.select("div.sect__content.grid-items div.item-poster").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
|
||||
@@ -23,15 +23,30 @@ internal class LugnicaScans(context: MangaLoaderContext) :
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
|
||||
|
||||
override val isSearchSupported = false
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")
|
||||
|
||||
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = false,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = emptySet(),
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
@@ -53,15 +68,15 @@ internal class LugnicaScans(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
|
||||
if (order == SortOrder.ALPHABETICAL) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -92,16 +107,6 @@ internal class LugnicaScans(context: MangaLoaderContext) :
|
||||
return parseMangaList(webClient.httpGet(url).parseJsonArray())
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/get/homegrid/")
|
||||
append(page)
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJsonArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +225,4 @@ internal class LugnicaScans(context: MangaLoaderContext) :
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
}
|
||||
|
||||
@@ -30,26 +30,25 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?query=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&search_type=manga&page=")
|
||||
append(page)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.sortOrder == SortOrder.UPDATED && filter.tags.isNotEmpty()) {
|
||||
if (order == SortOrder.UPDATED && filter.tags.isNotEmpty()) {
|
||||
throw IllegalArgumentException("Filtrer part tag n'est pas disponible avec le tri pas mis à jour")
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
|
||||
if (order == SortOrder.ALPHABETICAL) {
|
||||
append("/manga-list")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/category/")
|
||||
@@ -61,12 +60,6 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,12 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val postData = buildString {
|
||||
append("page=")
|
||||
append(page)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -88,9 +87,9 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.sortOrder == SortOrder.UPDATED) {
|
||||
if (order == SortOrder.UPDATED) {
|
||||
|
||||
if (filter.tags.isNotEmpty() or filter.states.isNotEmpty()) {
|
||||
throw IllegalArgumentException("Le filtrage par « tri par : mis à jour » avec les genres ou les statuts n'est pas pris en charge par cette source.")
|
||||
@@ -140,7 +139,7 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
}
|
||||
|
||||
append("&sort_by=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.RATING -> append("score&sort_dir=desc")
|
||||
SortOrder.NEWEST -> append("updated_at&sort_dir=desc")
|
||||
SortOrder.ALPHABETICAL -> append("name&sort_dir=asc")
|
||||
@@ -149,8 +148,6 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&sort_by=updated_at&sort_dir=desc")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.json.JSONArray
|
||||
import org.jsoup.nodes.Document
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
@@ -14,7 +14,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr")
|
||||
internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.SCANS_MANGAS_ME, 0) {
|
||||
SinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.ALPHABETICAL,
|
||||
@@ -27,28 +27,43 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
|
||||
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&post_type=manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/genres/")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
@@ -56,7 +71,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
}
|
||||
} else {
|
||||
append("/tous-nos-mangas/?order=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("popular")
|
||||
SortOrder.UPDATED -> append("update")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
@@ -65,8 +80,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/tous-nos-mangas/?order=update")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +104,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/tous-nos-mangas/").parseHtml()
|
||||
return doc.select("ul.genre li").mapNotNullToSet { li ->
|
||||
val key = li.selectFirstOrThrow("a").attr("href").removeSuffix('/').substringAfterLast('/')
|
||||
|
||||
@@ -20,30 +20,48 @@ internal class ScantradUnion(context: MangaLoaderContext) :
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("scantrad-union.com")
|
||||
|
||||
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/tag/")
|
||||
@@ -53,25 +71,19 @@ internal class ScantradUnion(context: MangaLoaderContext) :
|
||||
append("/")
|
||||
}
|
||||
} else {
|
||||
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
|
||||
if (order == SortOrder.ALPHABETICAL) {
|
||||
append("/manga/page/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.UPDATED && page > 1) {
|
||||
if (order == SortOrder.UPDATED && page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/manga/page/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -183,7 +195,7 @@ internal class ScantradUnion(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/").parseHtml()
|
||||
val body = doc.body()
|
||||
val list = body.select(".asp_gochosen")[1].select("option").orEmpty()
|
||||
|
||||
@@ -73,21 +73,21 @@ internal abstract class FuzzyDoodleParser(
|
||||
protected open val pausedValue = "haitus"
|
||||
protected open val abandonedValue = "dropped"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/manga?page=")
|
||||
append(page)
|
||||
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&title=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append("&type=")
|
||||
|
||||
append("&status=")
|
||||
@@ -110,8 +110,6 @@ internal abstract class FuzzyDoodleParser(
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,20 +30,57 @@ internal abstract class GalleryAdultsParser(
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = setOf(
|
||||
Locale.ENGLISH,
|
||||
Locale.FRENCH,
|
||||
Locale.JAPANESE,
|
||||
Locale.CHINESE,
|
||||
Locale("es"),
|
||||
Locale("ru"),
|
||||
Locale("ko"),
|
||||
Locale.GERMAN,
|
||||
Locale("id"),
|
||||
Locale.ITALIAN,
|
||||
Locale("pt"),
|
||||
Locale("tr"),
|
||||
Locale("th"),
|
||||
Locale("vi"),
|
||||
),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
order: SortOrder,
|
||||
filter: MangaListFilterV2,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search/?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
val lang = filter.locale
|
||||
if (tag != null && lang != null) {
|
||||
@@ -61,8 +98,6 @@ internal abstract class GalleryAdultsParser(
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?")
|
||||
}
|
||||
append("page=")
|
||||
append(page)
|
||||
@@ -102,7 +137,7 @@ internal abstract class GalleryAdultsParser(
|
||||
|
||||
//Tags are deliberately reduced because there are too many and this slows down the application.
|
||||
//only the most popular ones are taken.
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
return coroutineScope {
|
||||
(1..3).map { page ->
|
||||
async { getTags(page) }
|
||||
@@ -110,23 +145,6 @@ internal abstract class GalleryAdultsParser(
|
||||
}.awaitAll().flattenTo(ArraySet(360))
|
||||
}
|
||||
|
||||
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
|
||||
Locale.ENGLISH,
|
||||
Locale.FRENCH,
|
||||
Locale.JAPANESE,
|
||||
Locale.CHINESE,
|
||||
Locale("es"),
|
||||
Locale("ru"),
|
||||
Locale("ko"),
|
||||
Locale.GERMAN,
|
||||
Locale("id"),
|
||||
Locale.ITALIAN,
|
||||
Locale("pt"),
|
||||
Locale("tr"),
|
||||
Locale("th"),
|
||||
Locale("vi"),
|
||||
)
|
||||
|
||||
protected open val pathTagUrl = "/tags/popular/?page="
|
||||
protected open val selectTags = ".tags_page ul.tags li"
|
||||
|
||||
|
||||
@@ -37,24 +37,24 @@ internal class Hentai3(context: MangaLoaderContext) :
|
||||
Locale("pt"),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
|
||||
append("/search?q=")
|
||||
append(buildQuery(filter.tags, filter.locale))
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
append("&page=")
|
||||
@@ -64,7 +64,7 @@ internal class Hentai3(context: MangaLoaderContext) :
|
||||
append(filter.locale.toLanguagePath())
|
||||
append("/")
|
||||
append(page.toString())
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("?sort=popular")
|
||||
}
|
||||
} else if (filter.tags.isNotEmpty()) {
|
||||
@@ -74,7 +74,7 @@ internal class Hentai3(context: MangaLoaderContext) :
|
||||
}
|
||||
append("/")
|
||||
append(page.toString())
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("?sort=popular")
|
||||
}
|
||||
} else {
|
||||
@@ -82,11 +82,6 @@ internal class Hentai3(context: MangaLoaderContext) :
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -24,18 +24,18 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search/?s_key=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
if (filter.locale != null) {
|
||||
throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED)
|
||||
@@ -43,7 +43,7 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("/popular")
|
||||
}
|
||||
append("/?")
|
||||
@@ -56,8 +56,6 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?")
|
||||
}
|
||||
append("page=")
|
||||
append(page)
|
||||
|
||||
@@ -18,17 +18,21 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override val isMultipleTagsSupported = true
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = super.filterCapabilities.copy(
|
||||
isMultipleTagsSupported = true,
|
||||
)
|
||||
|
||||
|
||||
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
|
||||
Locale.ENGLISH,
|
||||
Locale.FRENCH,
|
||||
Locale.JAPANESE,
|
||||
Locale("es"),
|
||||
Locale("ru"),
|
||||
Locale("ko"),
|
||||
Locale.GERMAN,
|
||||
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
|
||||
availableLocales = setOf(
|
||||
Locale.ENGLISH,
|
||||
Locale.FRENCH,
|
||||
Locale.JAPANESE,
|
||||
Locale("es"),
|
||||
Locale("ru"),
|
||||
Locale("ko"),
|
||||
Locale.GERMAN,
|
||||
),
|
||||
)
|
||||
|
||||
override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet {
|
||||
@@ -41,22 +45,22 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search/?key=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
|
||||
append("/search/?key=")
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append(
|
||||
buildQuery(filter.tags, filter.locale)
|
||||
.replace("<=1&dl=0&pp=0&tr=0", "<=0&dl=0&pp=1&tr=0"),
|
||||
@@ -72,7 +76,7 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
||||
}
|
||||
append("/")
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
append("?")
|
||||
@@ -81,7 +85,7 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
||||
append(filter.locale.toLanguagePath())
|
||||
append("/")
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
append("?")
|
||||
@@ -89,8 +93,6 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?")
|
||||
}
|
||||
append("page=")
|
||||
append(page.toString())
|
||||
|
||||
@@ -46,22 +46,22 @@ internal class HentaiForce(context: MangaLoaderContext) :
|
||||
return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found")
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
|
||||
append("/search?q=")
|
||||
append(buildQuery(filter.tags, filter.locale))
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
append("&page=")
|
||||
@@ -72,7 +72,7 @@ internal class HentaiForce(context: MangaLoaderContext) :
|
||||
}
|
||||
append("/")
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
append("?")
|
||||
@@ -81,7 +81,7 @@ internal class HentaiForce(context: MangaLoaderContext) :
|
||||
append(filter.locale.toLanguagePath())
|
||||
append("/")
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
append("?")
|
||||
@@ -89,8 +89,6 @@ internal class HentaiForce(context: MangaLoaderContext) :
|
||||
append("/page/")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/page/")
|
||||
}
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ internal class HentaiFox(context: MangaLoaderContext) :
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search/?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
@@ -36,7 +36,7 @@ internal class HentaiFox(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
|
||||
append("/search/?q=")
|
||||
append(buildQuery(filter.tags, filter.locale))
|
||||
@@ -45,7 +45,7 @@ internal class HentaiFox(context: MangaLoaderContext) :
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
} else if (filter.tags.isNotEmpty()) {
|
||||
@@ -54,7 +54,7 @@ internal class HentaiFox(context: MangaLoaderContext) :
|
||||
append(it.key)
|
||||
}
|
||||
append("/")
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ internal class HentaiFox(context: MangaLoaderContext) :
|
||||
append("/language/")
|
||||
append(filter.locale.toLanguagePath())
|
||||
append("/")
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
if (order == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
|
||||
@@ -88,18 +88,6 @@ internal class HentaiFox(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
if (page > 2) {
|
||||
append("/pag/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
} else if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,22 +26,28 @@ internal class NHentaiParser(context: MangaLoaderContext) :
|
||||
".tag-container:contains(Languages:) span.tags a:not(.tag-17249) span.name" // tag-17249 = translated
|
||||
override val idImg = "image-container"
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.POPULARITY_TODAY, SortOrder.POPULARITY_WEEK)
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.POPULARITY_TODAY, SortOrder.POPULARITY_WEEK)
|
||||
|
||||
override val isMultipleTagsSupported = true
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = super.filterCapabilities.copy(
|
||||
isMultipleTagsSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
|
||||
availableLocales = setOf(Locale.ENGLISH, Locale.JAPANESE, Locale.CHINESE),
|
||||
)
|
||||
|
||||
override fun getRequestHeaders(): Headers = super.getRequestHeaders().newBuilder()
|
||||
.set("User-Agent", config[userAgentKey])
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
// Check if the query is all numbers
|
||||
val numericQuery = filter.query.trim()
|
||||
if (numericQuery.matches("\\d+".toRegex())) {
|
||||
@@ -56,13 +62,13 @@ internal class NHentaiParser(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append("/search/?q=pages:>0 ")
|
||||
// for Search with query
|
||||
// append(filter.query.urlEncoded())
|
||||
// append(' ')
|
||||
append(buildQuery(filter.tags, filter.locale).urlEncoded())
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("&sort=popular")
|
||||
SortOrder.POPULARITY_TODAY -> append("&sort=popular-today")
|
||||
SortOrder.POPULARITY_WEEK -> append("&sort=popular-week")
|
||||
@@ -70,8 +76,6 @@ internal class NHentaiParser(context: MangaLoaderContext) :
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/search/?q=pages:>0 ")
|
||||
}
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
@@ -122,12 +126,6 @@ internal class NHentaiParser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
|
||||
Locale.ENGLISH,
|
||||
Locale.JAPANESE,
|
||||
Locale.CHINESE,
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
|
||||
@@ -29,21 +29,20 @@ internal abstract class GattsuParser(
|
||||
|
||||
protected open val tagPrefix = "tag"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/$tagPrefix/")
|
||||
@@ -54,11 +53,6 @@ internal abstract class GattsuParser(
|
||||
append(page.toString())
|
||||
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.guya
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
@@ -12,11 +12,30 @@ internal abstract class GuyaParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 0,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
) : SinglePageMangaParser(context, source) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = emptySet(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
@@ -24,25 +43,17 @@ internal abstract class GuyaParser(
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) return emptyList()
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/get_all_series/")
|
||||
}
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
return parseMangaList(webClient.httpGet(url).parseJson(), filter.query)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {}
|
||||
|
||||
null -> {}
|
||||
return if (!filter.query.isNullOrEmpty()) {
|
||||
parseMangaList(webClient.httpGet(url).parseJson(), filter.query)
|
||||
} else {
|
||||
parseMangaList(webClient.httpGet(url).parseJson(), "")
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseJson(), "")
|
||||
}
|
||||
|
||||
protected open fun parseMangaList(json: JSONObject, query: String): List<Manga> {
|
||||
@@ -79,8 +90,6 @@ internal abstract class GuyaParser(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val json = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseJson().getJSONObject("chapters")
|
||||
val slug = manga.url.removeSuffix('/').substringAfterLast('/')
|
||||
|
||||
@@ -39,27 +39,43 @@ internal abstract class HeanCms(
|
||||
SortOrder.POPULARITY_ASC,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
|
||||
|
||||
|
||||
protected open val pathManga = "series"
|
||||
protected open val apiPath
|
||||
get() = getDomain("api")
|
||||
|
||||
protected open val paramsUpdated = "latest"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(apiPath)
|
||||
append("/query?query_string=")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
@@ -75,7 +91,7 @@ internal abstract class HeanCms(
|
||||
}
|
||||
|
||||
append("&orderBy=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("total_views&order=desc")
|
||||
SortOrder.POPULARITY_ASC -> append("total_views&order=asc")
|
||||
SortOrder.UPDATED -> append("$paramsUpdated&order=desc")
|
||||
@@ -94,8 +110,6 @@ internal abstract class HeanCms(
|
||||
append("]".urlEncoded())
|
||||
|
||||
}
|
||||
|
||||
null -> append("&status=All&orderBy=$paramsUpdated&order=desc&series_type=Comic&perPage=20")
|
||||
}
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
@@ -184,7 +198,7 @@ internal abstract class HeanCms(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/comics").parseHtml()
|
||||
val regex = Regex("\"tags\\\\.*?(\\[.+?])")
|
||||
val tags = doc.select("script").firstNotNullOf { script ->
|
||||
|
||||
@@ -41,20 +41,13 @@ internal abstract class HeanCmsAlt(
|
||||
protected open val selectManga = "div.grid.grid-cols-2 div:not([class]):contains(M)"
|
||||
protected open val selectMangaTitle = "h5"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
}
|
||||
|
||||
null -> {}
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
|
||||
}
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
|
||||
@@ -31,17 +31,35 @@ internal abstract class HotComicsParser(
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
protected open val mangasUrl = "/genres"
|
||||
|
||||
protected open val onePage = false
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = false,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun getRequestHeaders(): Headers = Headers.Builder()
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
if (onePage && page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -49,16 +67,16 @@ internal abstract class HotComicsParser(
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?keyword=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append(mangasUrl)
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append('/')
|
||||
@@ -70,11 +88,6 @@ internal abstract class HotComicsParser(
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/genres?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
val tagMap = getOrCreateTagMap()
|
||||
@@ -174,7 +187,7 @@ internal abstract class HotComicsParser(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val map = getOrCreateTagMap()
|
||||
val tagSet = ArraySet<MangaTag>(map.size)
|
||||
for (entry in map) {
|
||||
|
||||
@@ -33,22 +33,22 @@ class DoujinDesuParser(context: MangaLoaderContext) :
|
||||
.add("Referer", "https://$domain/")
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = urlBuilder().apply {
|
||||
addPathSegment("manga")
|
||||
addPathSegment("page")
|
||||
addPathSegment("$page/")
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
addQueryParameter("title", filter.query)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
addQueryParameter("title", "")
|
||||
addQueryParameter(
|
||||
"order",
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
@@ -72,8 +72,6 @@ class DoujinDesuParser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> addQueryParameter("order", "update")
|
||||
}
|
||||
}.build()
|
||||
|
||||
|
||||
@@ -24,20 +24,19 @@ internal class HentaiCrot(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/category/")
|
||||
@@ -49,12 +48,6 @@ internal class HentaiCrot(context: MangaLoaderContext) :
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,20 +24,19 @@ internal class PixHentai(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/genre/")
|
||||
@@ -49,12 +48,6 @@ internal class PixHentai(context: MangaLoaderContext) :
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,25 +28,45 @@ internal abstract class IkenParser(
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
|
||||
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.UPCOMING)
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override val isMultipleTagsSupported = true
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.ABANDONED,
|
||||
MangaState.UPCOMING,
|
||||
),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/query?page=")
|
||||
append(page)
|
||||
append("&perPage=18&searchTerm=")
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&genreIds=")
|
||||
@@ -66,8 +86,6 @@ internal abstract class IkenParser(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJson())
|
||||
@@ -145,7 +163,7 @@ internal abstract class IkenParser(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
protected open suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/series").parseHtml()
|
||||
return doc.selectLastOrThrow("select").select("option[value]").mapNotNullToSet {
|
||||
val key = it.attr("value") ?: return@mapNotNullToSet null
|
||||
|
||||
@@ -45,32 +45,28 @@ class NicovideoSeigaParser(context: MangaLoaderContext) :
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp")
|
||||
|
||||
@InternalParsersApi
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val page = (offset / 20f).toIntUp().inc()
|
||||
val domain = getDomain("seiga")
|
||||
val url =
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
return if (offset == 0) getSearchList(filter.query, page) else emptyList()
|
||||
}
|
||||
val url = when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
return if (offset == 0) getSearchList(filter.query, page) else emptyList()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany().let {
|
||||
"https://$domain/manga/list?category=${it?.key}&page=$page&sort=${getSortKey(filter.sortOrder)}"
|
||||
}
|
||||
|
||||
} else {
|
||||
"https://$domain/manga/list?page=$page&sort=${getSortKey(filter.sortOrder)}"
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany().let {
|
||||
"https://$domain/manga/list?category=${it?.key}&page=$page&sort=${getSortKey(order)}"
|
||||
}
|
||||
|
||||
} else {
|
||||
"https://$domain/manga/list?page=$page&sort=${getSortKey(order)}"
|
||||
}
|
||||
|
||||
null -> "https://$domain/manga/list?page=$page"
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
val comicList = doc.body().select("#comic_list > ul > li") ?: doc.parseFailed("Container not found")
|
||||
|
||||
@@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
@@ -17,8 +17,7 @@ internal abstract class KeyoappParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 24,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
) : SinglePageMangaParser(context, source) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
|
||||
@@ -58,29 +57,20 @@ internal abstract class KeyoappParser(
|
||||
"dropped",
|
||||
)
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
var query = ""
|
||||
var tag = ""
|
||||
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val url = urlBuilder().apply {
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
addPathSegment("series")
|
||||
query = filter.query
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
@@ -88,15 +78,13 @@ internal abstract class KeyoappParser(
|
||||
}
|
||||
}
|
||||
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> addPathSegment("latest")
|
||||
SortOrder.NEWEST -> addPathSegment("series")
|
||||
else -> addPathSegment("latest")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> addPathSegment("latest")
|
||||
}
|
||||
}.build()
|
||||
|
||||
|
||||
@@ -37,25 +37,25 @@ internal abstract class LikeMangaParser(
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/?act=search")
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&f")
|
||||
append("[keyword]".urlEncoded())
|
||||
append("=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append("&f")
|
||||
append("[sortby]".urlEncoded())
|
||||
append("=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("hot")
|
||||
SortOrder.UPDATED -> append("lastest-chap")
|
||||
SortOrder.NEWEST -> append("lastest-manga")
|
||||
@@ -85,12 +85,6 @@ internal abstract class LikeMangaParser(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("&f")
|
||||
append("[sortby]".urlEncoded())
|
||||
append("=lastest-chap")
|
||||
}
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
|
||||
@@ -27,6 +27,26 @@ internal abstract class MadaraParser(
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.allOf(MangaState::class.java),
|
||||
availableContentRating = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
@@ -54,8 +74,6 @@ internal abstract class MadaraParser(
|
||||
}
|
||||
}
|
||||
|
||||
override val isMultipleTagsSupported = true
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.UPDATED_ASC,
|
||||
@@ -69,12 +87,6 @@ internal abstract class MadaraParser(
|
||||
SortOrder.RATING_ASC,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT)
|
||||
|
||||
override val isTagsExclusionSupported = true
|
||||
|
||||
protected open val tagPrefix = "manga-genre/"
|
||||
protected open val datePattern = "MMMM d, yyyy"
|
||||
protected open val stylePage = "?style=list"
|
||||
@@ -196,7 +208,7 @@ internal abstract class MadaraParser(
|
||||
// can be changed to retrieve tags see getTags
|
||||
protected open val listUrl = "manga/"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
if (withoutAjax) {
|
||||
val pages = page + 1
|
||||
|
||||
@@ -204,9 +216,9 @@ internal abstract class MadaraParser(
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
@@ -216,7 +228,7 @@ internal abstract class MadaraParser(
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
@@ -278,7 +290,7 @@ internal abstract class MadaraParser(
|
||||
|
||||
|
||||
append("&m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -288,10 +300,6 @@ internal abstract class MadaraParser(
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/?s=&post_type=wp-manga&m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
@@ -301,13 +309,13 @@ internal abstract class MadaraParser(
|
||||
|
||||
payload["page"] = page.toString()
|
||||
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
payload["vars[s]"] = filter.query.urlEncoded()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
|
||||
// Support query
|
||||
@@ -362,7 +370,7 @@ internal abstract class MadaraParser(
|
||||
payload["vars[tax_query][relation]"] = "AND"
|
||||
}
|
||||
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> {
|
||||
payload["vars[meta_key]"] = "_wp_manga_views"
|
||||
payload["vars[orderby]"] = "meta_value_num"
|
||||
@@ -453,10 +461,6 @@ internal abstract class MadaraParser(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
payload["vars[meta_key]"] = "_latest_update"
|
||||
}
|
||||
}
|
||||
|
||||
return parseMangaList(
|
||||
@@ -510,7 +514,7 @@ internal abstract class MadaraParser(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
protected open suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
|
||||
val body = doc.body()
|
||||
val root1 = body.selectFirst("header")?.selectFirst("ul.second-menu")
|
||||
|
||||
@@ -29,20 +29,19 @@ internal class Manga18Fx(context: MangaLoaderContext) :
|
||||
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -59,13 +58,6 @@ internal class Manga18Fx(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -32,20 +32,20 @@ internal class Manhwa18Cc(context: MangaLoaderContext) :
|
||||
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -60,7 +60,7 @@ internal class Manhwa18Cc(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("?orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("trending")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
@@ -68,10 +68,6 @@ internal class Manhwa18Cc(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("?s&post_type=wp-manga&m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -25,22 +25,27 @@ internal class AdultWebtoon(context: MangaLoaderContext) :
|
||||
override val listUrl = "adult-webtoon/"
|
||||
override val postReq = true
|
||||
override val withoutAjax = true
|
||||
override val isTagsExclusionSupported = false
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING)
|
||||
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = super.filterCapabilities.copy(
|
||||
isTagsExclusionSupported = false,
|
||||
)
|
||||
|
||||
override val availableContentRating: Set<ContentRating> = emptySet()
|
||||
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val pages = page + 1
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
@@ -50,7 +55,7 @@ internal class AdultWebtoon(context: MangaLoaderContext) :
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
@@ -71,7 +76,7 @@ internal class AdultWebtoon(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("?m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -80,16 +85,6 @@ internal class AdultWebtoon(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append('/')
|
||||
append(listUrl)
|
||||
if (pages > 1) {
|
||||
append("page/")
|
||||
append(pages)
|
||||
append("/?m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -26,12 +26,12 @@ internal class Hentai4Free(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
@@ -39,7 +39,7 @@ internal class Hentai4Free(context: MangaLoaderContext) :
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -85,7 +85,7 @@ internal class Hentai4Free(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -96,14 +96,6 @@ internal class Hentai4Free(context: MangaLoaderContext) :
|
||||
|
||||
|
||||
}
|
||||
|
||||
null -> {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
append("/?m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,14 +29,14 @@ internal class HentaiManga(context: MangaLoaderContext) :
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val availableContentRating: Set<ContentRating> = emptySet()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val pages = page + 1
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
@@ -46,7 +46,7 @@ internal class HentaiManga(context: MangaLoaderContext) :
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
@@ -67,7 +67,7 @@ internal class HentaiManga(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("?m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -76,16 +76,6 @@ internal class HentaiManga(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append('/')
|
||||
append(listUrl)
|
||||
if (pages > 1) {
|
||||
append("page/")
|
||||
append(pages)
|
||||
append("/?m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -23,20 +23,27 @@ internal class HentaiWebtoon(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.HENTAIWEBTOON, "hentaiwebtoon.com") {
|
||||
override val postReq = true
|
||||
override val withoutAjax = true
|
||||
override val isTagsExclusionSupported = false
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING)
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val availableContentRating: Set<ContentRating> = emptySet()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = super.filterCapabilities.copy(
|
||||
isMultipleTagsSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
|
||||
availableContentRating = emptySet(),
|
||||
availableStates = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val pages = page + 1
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
@@ -46,7 +53,7 @@ internal class HentaiWebtoon(context: MangaLoaderContext) :
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
@@ -67,7 +74,7 @@ internal class HentaiWebtoon(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("?m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -76,16 +83,6 @@ internal class HentaiWebtoon(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append('/')
|
||||
append(listUrl)
|
||||
if (pages > 1) {
|
||||
append("page/")
|
||||
append(pages)
|
||||
append("/?m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -28,13 +28,12 @@ internal class IsekaiScan(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
@@ -42,14 +41,14 @@ internal class IsekaiScan(context: MangaLoaderContext) :
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("2")
|
||||
SortOrder.UPDATED -> append("3")
|
||||
else -> append("3")
|
||||
@@ -57,7 +56,7 @@ internal class IsekaiScan(context: MangaLoaderContext) :
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("/popular-manga")
|
||||
SortOrder.UPDATED -> append("/latest-manga")
|
||||
else -> append("/latest-manga")
|
||||
@@ -66,11 +65,6 @@ internal class IsekaiScan(context: MangaLoaderContext) :
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/latest-manga?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -23,15 +23,14 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
@@ -41,7 +40,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -84,7 +83,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -93,10 +92,6 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/?s&post_type=wp-manga&m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -32,20 +32,19 @@ internal class MangaDass(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -62,7 +61,7 @@ internal class MangaDass(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -71,13 +70,6 @@ internal class MangaDass(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
append("/")
|
||||
append(page.toString())
|
||||
append("?orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -25,19 +25,19 @@ internal class MangaDna(context: MangaLoaderContext) :
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val availableContentRating: Set<ContentRating> = emptySet()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -52,7 +52,7 @@ internal class MangaDna(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("?orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("trending")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
@@ -60,13 +60,6 @@ internal class MangaDna(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("?orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,12 @@ internal class MangaPure(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
@@ -45,14 +45,14 @@ internal class MangaPure(context: MangaLoaderContext) :
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("2")
|
||||
SortOrder.UPDATED -> append("3")
|
||||
else -> append("3")
|
||||
@@ -60,7 +60,7 @@ internal class MangaPure(context: MangaLoaderContext) :
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("/popular-manga")
|
||||
SortOrder.UPDATED -> append("/latest-manga")
|
||||
else -> append("/latest-manga")
|
||||
@@ -69,11 +69,6 @@ internal class MangaPure(context: MangaLoaderContext) :
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/latest-manga?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,19 +33,19 @@ internal class Manhwaz(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -62,7 +62,7 @@ internal class Manhwaz(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new")
|
||||
@@ -70,13 +70,6 @@ internal class Manhwaz(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -32,14 +32,14 @@ internal class ManyToon(context: MangaLoaderContext) :
|
||||
|
||||
override val availableContentRating: Set<ContentRating> = emptySet()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val pages = page + 1
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
@@ -49,7 +49,7 @@ internal class ManyToon(context: MangaLoaderContext) :
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
@@ -70,7 +70,7 @@ internal class ManyToon(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
append("?m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -79,16 +79,6 @@ internal class ManyToon(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append('/')
|
||||
append(listUrl)
|
||||
if (pages > 1) {
|
||||
append("page/")
|
||||
append(pages)
|
||||
append("/?m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -20,19 +20,19 @@ internal class DragonTranslationParser(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/mangas?buscar=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("/mangas?page=")
|
||||
append(page.toString())
|
||||
@@ -43,11 +43,6 @@ internal class DragonTranslationParser(context: MangaLoaderContext) :
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/mangas?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ internal class TmoManga(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/$listUrl")
|
||||
append("?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
@@ -41,7 +41,7 @@ internal class TmoManga(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -59,14 +59,6 @@ internal class TmoManga(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,31 +15,38 @@ internal class ManhwaHub(context: MangaLoaderContext) :
|
||||
override val datePattern = "MMMM d, yyyy"
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
override val withoutAjax = true
|
||||
override val isTagsExclusionSupported = false
|
||||
override val listUrl = "genre/manhwa"
|
||||
override val selectTestAsync = "ul.box-list-chapter"
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val availableContentRating: Set<ContentRating> = emptySet()
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = super.filterCapabilities.copy(
|
||||
isTagsExclusionSupported = false,
|
||||
)
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -55,11 +62,6 @@ internal class ManhwaHub(context: MangaLoaderContext) :
|
||||
|
||||
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -98,7 +100,7 @@ internal class ManhwaHub(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
override suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain").parseHtml()
|
||||
return doc.select("div.genres li").mapNotNullToSet { li ->
|
||||
val a = li.selectFirst("a") ?: return@mapNotNullToSet null
|
||||
|
||||
@@ -6,9 +6,11 @@ import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
import kotlin.collections.Set
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("SAYTRUYENHAY", "PheTruyen", "vi")
|
||||
@@ -17,31 +19,38 @@ internal class Saytruyenhay(context: MangaLoaderContext) :
|
||||
|
||||
override val tagPrefix = "genre/"
|
||||
override val withoutAjax = true
|
||||
override val isTagsExclusionSupported = false
|
||||
override val listUrl = "public/genre/manga/"
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val availableContentRating: Set<ContentRating> = emptySet()
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.RATING, SortOrder.NEWEST)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = super.filterCapabilities.copy(
|
||||
isTagsExclusionSupported = false,
|
||||
)
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
|
||||
availableStates = emptySet(),
|
||||
availableContentRating = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("/search?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
@@ -54,7 +63,7 @@ internal class Saytruyenhay(context: MangaLoaderContext) :
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.RATING -> append("rating")
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
@@ -62,12 +71,6 @@ internal class Saytruyenhay(context: MangaLoaderContext) :
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ internal abstract class MadthemeParser(
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
protected open val listUrl = "search/"
|
||||
protected open val datePattern = "MMM dd, yyyy"
|
||||
|
||||
@@ -59,23 +57,43 @@ internal abstract class MadthemeParser(
|
||||
"COMPLETED",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isTagsExclusionSupported = false,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = false,
|
||||
isYearSupported = false,
|
||||
isYearRangeSupported = false,
|
||||
isSourceLocaleSupported = false,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
availableTags = fetchAvailableTags(),
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
|
||||
availableContentRating = emptySet(),
|
||||
availableContentTypes = emptySet(),
|
||||
availableDemographics = emptySet(),
|
||||
availableLocales = emptySet(),
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append('/')
|
||||
append(listUrl)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("?sort=updated_at&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("?sort=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty.
|
||||
@@ -104,8 +122,6 @@ internal abstract class MadthemeParser(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("?sort=updated_at")
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
@@ -140,7 +156,7 @@ internal abstract class MadthemeParser(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
|
||||
return doc.select("div.genres .checkbox").mapNotNullToSet { checkbox ->
|
||||
val key = checkbox.selectFirstOrThrow("input").attr("value") ?: return@mapNotNullToSet null
|
||||
|
||||
@@ -17,23 +17,23 @@ internal class ManhuaScan(context: MangaLoaderContext) :
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
override val listUrl = "search"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append('/')
|
||||
append(listUrl)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("?sort=updated_at&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
append("?sort=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
@@ -62,8 +62,6 @@ internal class ManhuaScan(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("?sort=updated_at")
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
|
||||
@@ -54,14 +54,14 @@ internal abstract class Manga18Parser(
|
||||
"Completed",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append('/')
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append(listUrl)
|
||||
append(page.toString())
|
||||
append("?search=")
|
||||
@@ -69,7 +69,7 @@ internal abstract class Manga18Parser(
|
||||
append("&order_by=latest")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(tagUrl)
|
||||
@@ -82,19 +82,13 @@ internal abstract class Manga18Parser(
|
||||
|
||||
append(page.toString())
|
||||
append("?order_by=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("lastest")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append(page.toString())
|
||||
append("?order_by=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -55,20 +55,20 @@ internal abstract class MangaboxParser(
|
||||
"completed",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?s=all")
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append("&keyw=")
|
||||
append(filter.query.replace(" ", "_").urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&g_i=")
|
||||
@@ -100,7 +100,7 @@ internal abstract class MangaboxParser(
|
||||
}
|
||||
|
||||
append("&orby=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
@@ -108,8 +108,6 @@ internal abstract class MangaboxParser(
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
|
||||
@@ -33,22 +33,23 @@ internal class Mangairo(context: MangaLoaderContext) :
|
||||
)
|
||||
override val isTagsExclusionSupported = false
|
||||
override val isMultipleTagsSupported = false
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append(searchUrl)
|
||||
append(filter.query.urlEncoded())
|
||||
append("?page=")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append(listUrl)
|
||||
append("/type-")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
@@ -81,11 +82,6 @@ internal class Mangairo(context: MangaLoaderContext) :
|
||||
|
||||
append("/page-")
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("/type-latest/ctg-all/state-all/page-")
|
||||
}
|
||||
}
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@ internal class Mangakakalot(context: MangaLoaderContext) :
|
||||
override val otherDomain = "chapmanganato.com"
|
||||
override val listUrl = "/manga_list"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
when {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
append(searchUrl)
|
||||
val regex = Regex("[^A-Za-z0-9 ]")
|
||||
val q = regex.replace(filter.query, "")
|
||||
@@ -38,10 +38,10 @@ internal class Mangakakalot(context: MangaLoaderContext) :
|
||||
append("?page=")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
else -> {
|
||||
append(listUrl)
|
||||
append("?type=")
|
||||
when (filter.sortOrder) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
@@ -67,11 +67,6 @@ internal class Mangakakalot(context: MangaLoaderContext) :
|
||||
|
||||
append("&page=")
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("?type=latest&page=")
|
||||
}
|
||||
}
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user