From c0ea9cadd7b907ecaa5bb0490b19f79417ec102b Mon Sep 17 00:00:00 2001
From: palaks-1
Date: Tue, 18 Feb 2025 23:18:05 +0200
Subject: [PATCH 1/4] New MangaSearchQuery implementation proposal
---
.../koitharu/kotatsu/parsers/MangaParser.kt | 41 ++-
.../kotatsu/parsers/PagedMangaParser.kt | 47 ++++
.../kotatsu/parsers/SinglePageMangaParser.kt | 20 ++
.../koitharu/kotatsu/parsers/model/Manga.kt | 90 +++++++
.../kotatsu/parsers/model/MangaListFilter.kt | 40 +++
.../model/MangaListFilterCapabilities.kt | 1 +
.../parsers/model/search/MangaSearchQuery.kt | 130 +++++++++
.../search/MangaSearchQueryCapabilities.kt | 105 ++++++++
.../parsers/model/search/QueryCriteria.kt | 63 +++++
.../parsers/model/search/SearchCapability.kt | 10 +
.../parsers/model/search/SearchableField.kt | 23 ++
.../parsers/site/all/MangaDexParser.kt | 255 +++++++++---------
.../parsers/site/mangabox/MangaboxParser.kt | 148 ++++++----
.../parsers/site/mangabox/en/Mangairo.kt | 87 ++++++
.../parsers/site/mangabox/en/Mangakakalot.kt | 117 +++++---
.../site/mangabox/en/MangakakalotTv.kt | 111 +++++---
.../parsers/site/mangabox/en/Manganato.kt | 1 +
.../parsers/util/SearchQueryConverter.kt | 154 +++++++++++
.../kotatsu/parsers/MangaParserTest.kt | 71 +++--
.../model/search/MangaSearchQueryTest.kt | 72 +++++
.../ListFilterToSearchQueryConverterTest.kt | 77 ++++++
.../SearchQueryToListFilterConverterTest.kt | 90 +++++++
22 files changed, 1472 insertions(+), 281 deletions(-)
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
create mode 100644 src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt
create mode 100644 src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt
create mode 100644 src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
index cc3a920c1..3621d202e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
@@ -6,13 +6,10 @@ import okhttp3.HttpUrl
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.*
import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
import org.koitharu.kotatsu.parsers.network.WebClient
-import org.koitharu.kotatsu.parsers.util.FaviconParser
-import org.koitharu.kotatsu.parsers.util.LinkResolver
-import org.koitharu.kotatsu.parsers.util.RelatedMangaFinder
-import org.koitharu.kotatsu.parsers.util.domain
-import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
+import org.koitharu.kotatsu.parsers.util.*
import java.util.*
public abstract class MangaParser @InternalParsersApi constructor(
@@ -27,8 +24,12 @@ public abstract class MangaParser @InternalParsersApi constructor(
*/
public abstract val availableSortOrders: Set
+ @Deprecated("Please check searchQueryCapabilities")
public abstract val filterCapabilities: MangaListFilterCapabilities
+ public open val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities.from(filterCapabilities)
+
public val config: MangaSourceConfig by lazy { context.getConfig(source) }
public open val sourceLocale: Locale
@@ -62,6 +63,32 @@ public abstract class MangaParser @InternalParsersApi constructor(
@JvmField
protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
+ /**
+ * Search list of manga by specified searchQuery
+ *
+ * @param searchQuery searchQuery
+ */
+ public suspend fun searchManga(searchQuery: MangaSearchQuery, validateQuery: Boolean = true): List {
+ if (validateQuery) {
+ searchQueryCapabilities.validate(searchQuery)
+ }
+
+ return validatedMangaSearch(searchQuery)
+ }
+
+ /**
+ * Search list of manga by specified searchQuery
+ *
+ * @param searchQuery searchQuery
+ */
+ protected open suspend fun validatedMangaSearch(searchQuery: MangaSearchQuery): List {
+ return getList(
+ searchQuery.offset ?: 0,
+ searchQuery.order ?: defaultSortOrder,
+ convertToMangaListFilter(searchQuery),
+ )
+ }
+
/**
* Parse list of manga by specified criteria
*
@@ -69,7 +96,10 @@ public abstract class MangaParser @InternalParsersApi constructor(
* Note than passed value may not be divisible by internal page size, so you should adjust it manually.
* @param order one of [availableSortOrders] or [defaultSortOrder] for default value
* @param filter is a set of filter rules
+ *
+ * @deprecated New [searchManga] should be preferred.
*/
+ @Deprecated("New searchManga method should be preferred")
public abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List
/**
@@ -113,4 +143,5 @@ public abstract class MangaParser @InternalParsersApi constructor(
* @see [Manga.publicUrl]
*/
internal open suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
index 02492f43c..231792072 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
@@ -5,7 +5,10 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.SortOrder
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.SearchableField
import org.koitharu.kotatsu.parsers.util.Paginator
+import org.koitharu.kotatsu.parsers.util.convertToMangaListFilter
@InternalParsersApi
public abstract class PagedMangaParser(
@@ -21,6 +24,33 @@ public abstract class PagedMangaParser(
@JvmField
protected val searchPaginator: Paginator = Paginator(searchPageSize)
+ final override suspend fun validatedMangaSearch(searchQuery: MangaSearchQuery): List {
+ var containTitleNameCriteria = false
+ searchQuery.criteria.forEach {
+ if (it.field == SearchableField.TITLE_NAME) {
+ containTitleNameCriteria = true
+ }
+ }
+
+ return searchManga(
+ paginator = if (containTitleNameCriteria) {
+ paginator
+ } else {
+ searchPaginator
+ },
+ searchQuery = searchQuery,
+ )
+ }
+
+ public open suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ return getList(
+ searchQuery.offset ?: 0,
+ searchQuery.order ?: defaultSortOrder,
+ convertToMangaListFilter(searchQuery),
+ )
+ }
+
+ @Deprecated("New searchManga method should be preferred")
final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
return getList(
paginator = if (filter.query.isNullOrEmpty()) {
@@ -34,6 +64,7 @@ public abstract class PagedMangaParser(
)
}
+ @Deprecated("New searchManga method should be preferred")
public abstract suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List
private suspend fun getList(
@@ -47,4 +78,20 @@ public abstract class PagedMangaParser(
paginator.onListReceived(offset, page, list.size)
return list
}
+
+ private suspend fun searchManga(
+ paginator: Paginator,
+ searchQuery: MangaSearchQuery,
+ ): List {
+ val offset: Int = searchQuery.offset ?: 0
+ val page = paginator.getPage(offset)
+ val list = searchPageManga(
+ MangaSearchQuery.builder()
+ .copy(searchQuery)
+ .offset(page)
+ .build(),
+ )
+ paginator.onListReceived(offset, page, list.size)
+ return list
+ }
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
index 6e2425af5..a4e32f5e7 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
@@ -4,6 +4,8 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.SortOrder
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.util.convertToMangaListFilter
@InternalParsersApi
public abstract class SinglePageMangaParser(
@@ -11,6 +13,23 @@ public abstract class SinglePageMangaParser(
source: MangaParserSource,
) : MangaParser(context, source) {
+
+ final override suspend fun validatedMangaSearch(searchQuery: MangaSearchQuery): List {
+ if (searchQuery.offset != null && searchQuery.offset > 0) {
+ return emptyList()
+ }
+ return searchSinglePageManga(searchQuery)
+ }
+
+ public open suspend fun searchSinglePageManga(searchQuery: MangaSearchQuery): List {
+ return getList(
+ searchQuery.offset ?: 0,
+ searchQuery.order ?: defaultSortOrder,
+ convertToMangaListFilter(searchQuery),
+ )
+ }
+
+ @Deprecated("New searchManga method should be preferred")
final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
if (offset > 0) {
return emptyList()
@@ -18,5 +37,6 @@ public abstract class SinglePageMangaParser(
return getList(order, filter)
}
+ @Deprecated("New searchManga method should be preferred")
public abstract suspend fun getList(order: SortOrder, filter: MangaListFilter): List
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt
index d7a9b8ddb..53326d8bb 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt
@@ -52,7 +52,12 @@ public data class Manga private constructor(
/**
* Author of the manga, may be null
*/
+ @Deprecated("Please use authors")
@JvmField public val author: String?,
+ /**
+ * Authors of the manga
+ */
+ @JvmField public val authors: Set,
/**
* Large cover url (absolute), null if is no large cover
* @see coverUrl
@@ -189,6 +194,7 @@ public data class Manga private constructor(
source = source,
)
+ @Deprecated("")
public operator fun invoke(
/**
* Unique identifier for manga
@@ -271,5 +277,89 @@ public data class Manga private constructor(
chapters = chapters,
source = source,
)
+
+ public operator fun invoke(
+ /**
+ * Unique identifier for manga
+ */
+ id: Long,
+ /**
+ * Manga title, human-readable
+ */
+ title: String,
+ /**
+ * Alternative title (for example on other language), may be null
+ */
+ altTitle: String?,
+ /**
+ * Relative url to manga (**without** a domain) or any other uri.
+ * Used principally in parsers
+ */
+ url: String,
+ /**
+ * Absolute url to manga, must be ready to open in browser
+ */
+ publicUrl: String,
+ /**
+ * Normalized manga rating, must be in range of 0..1 or [RATING_UNKNOWN] if rating s unknown
+ * @see hasRating
+ */
+ rating: Float,
+ /**
+ * Indicates that manga may contain sensitive information (18+, NSFW)
+ */
+ contentRating: ContentRating?,
+ /**
+ * Absolute link to the cover
+ * @see largeCoverUrl
+ */
+ coverUrl: String?,
+ /**
+ * Tags (genres) of the manga
+ */
+ tags: Set,
+ /**
+ * Manga status (ongoing, finished) or null if unknown
+ */
+ state: MangaState?,
+ /**
+ * Authors of the manga
+ */
+ authors: Set,
+ /**
+ * Large cover url (absolute), null if is no large cover
+ * @see coverUrl
+ */
+ largeCoverUrl: String? = null,
+ /**
+ * Manga description, may be html or null
+ */
+ description: String? = null,
+ /**
+ * List of chapters
+ */
+ chapters: List? = null,
+ /**
+ * Manga source
+ */
+ source: MangaSource,
+ ): Manga = Manga(
+ id = id,
+ title = title,
+ altTitle = altTitle?.nullIfEmpty(),
+ url = url,
+ publicUrl = publicUrl,
+ rating = rating,
+ contentRating = contentRating,
+ coverUrl = coverUrl?.nullIfEmpty(),
+ tags = tags,
+ state = state,
+ authors = authors,
+ largeCoverUrl = largeCoverUrl?.nullIfEmpty(),
+ description = description?.nullIfEmpty(),
+ chapters = chapters,
+ source = source,
+ author = authors?.firstOrNull()?.title
+ )
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
index e996a5884..febf96eed 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.model
import java.util.*
+@Deprecated("Please check new searchManga method and MangaSearchQuery class")
public data class MangaListFilter(
@JvmField val query: String? = null,
@JvmField val tags: Set = emptySet(),
@@ -42,4 +43,43 @@ public data class MangaListFilter(
@JvmStatic
public val EMPTY: MangaListFilter = MangaListFilter()
}
+
+ public class Builder {
+ private var query: String? = null
+ private val tags: MutableSet = mutableSetOf()
+ private val tagsExclude: MutableSet = mutableSetOf()
+ private var locale: Locale? = null
+ private var originalLocale: Locale? = null
+ private val states: MutableSet = mutableSetOf()
+ private val contentRating: MutableSet = mutableSetOf()
+ private val types: MutableSet = mutableSetOf()
+ private val demographics: MutableSet = mutableSetOf()
+ private var year: Int = YEAR_UNKNOWN
+ private var yearFrom: Int = YEAR_UNKNOWN
+ private var yearTo: Int = YEAR_UNKNOWN
+
+ public fun query(query: String?): Builder = apply { this.query = query }
+ public fun addTag(tag: MangaTag): Builder = apply { tags.add(tag) }
+ public fun addTags(tags: Collection): Builder = apply { this.tags.addAll(tags) }
+ public fun excludeTag(tag: MangaTag): Builder = apply { tagsExclude.add(tag) }
+ public fun excludeTags(tags: Collection): Builder = apply { this.tagsExclude.addAll(tags) }
+ public fun locale(locale: Locale?): Builder = apply { this.locale = locale }
+ public fun originalLocale(locale: Locale?): Builder = apply { this.originalLocale = locale }
+ public fun addState(state: MangaState): Builder = apply { states.add(state) }
+ public fun addStates(states: Collection): Builder = apply { this.states.addAll(states) }
+ public fun addContentRating(rating: ContentRating): Builder = apply { contentRating.add(rating) }
+ public fun addContentRatings(ratings: Collection): Builder = apply { this.contentRating.addAll(ratings) }
+ public fun addType(type: ContentType): Builder = apply { types.add(type) }
+ public fun addTypes(types: Collection): Builder = apply { this.types.addAll(types) }
+ public fun addDemographic(demographic: Demographic): Builder = apply { demographics.add(demographic) }
+ public fun addDemographics(demographics: Collection): Builder = apply { this.demographics.addAll(demographics) }
+ public fun year(year: Int): Builder = apply { this.year = year }
+ public fun yearFrom(year: Int): Builder = apply { this.yearFrom = year }
+ public fun yearTo(year: Int): Builder = apply { this.yearTo = year }
+
+ public fun build(): MangaListFilter = MangaListFilter(
+ query, tags, tagsExclude, locale, originalLocale, states,
+ contentRating, types, demographics, year, yearFrom, yearTo
+ )
+ }
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt
index d0daa06d3..00a479cdb 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.model
import org.koitharu.kotatsu.parsers.InternalParsersApi
+@Deprecated("Please check new MangaSearchQueryCapabilities class")
public data class MangaListFilterCapabilities @InternalParsersApi constructor(
/**
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
new file mode 100644
index 000000000..dde23d109
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
@@ -0,0 +1,130 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import org.koitharu.kotatsu.parsers.model.SortOrder
+
+/**
+ * Represents a search query for filtering and sorting manga search results.
+ * This class is immutable and must be constructed using the [Builder].
+ *
+ * @property criteria The set of search criteria applied to the query.
+ * @property order The sorting order for the results (optional).
+ * @property offset The offset number for paginated search results (optional).
+ */
+public class MangaSearchQuery private constructor(
+ @JvmField public val criteria: Set> = emptySet(),
+ @JvmField public val order: SortOrder? = null,
+ @JvmField public val offset: Int? = null,
+) {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is MangaSearchQuery) return false
+
+ return criteria == other.criteria &&
+ order == other.order &&
+ offset == other.offset
+ }
+
+ override fun hashCode(): Int {
+ var result = criteria.hashCode()
+ result = 31 * result + (order?.hashCode() ?: 0)
+ result = 31 * result + (offset ?: 0)
+ return result
+ }
+
+ public companion object {
+ public fun builder(): Builder = Builder()
+ }
+
+ public class Builder {
+ private var criteria: MutableSet> = mutableSetOf()
+ private var order: SortOrder? = null
+ private var offset: Int? = null
+
+ public fun copy(searchQuery: MangaSearchQuery): Builder = apply {
+ this.criteria = searchQuery.criteria as MutableSet>
+ this.order = searchQuery.order
+ this.offset = searchQuery.offset
+ }
+
+ @Throws(IllegalArgumentException::class)
+ public fun criterion(criterion: QueryCriteria<*>): Builder = apply {
+ validateCriterion(criterion)
+ this.criteria.add(criterion)
+ }
+
+ public fun order(order: SortOrder?): Builder = apply { this.order = order }
+
+ public fun offset(offset: Int?): Builder = apply { this.offset = offset }
+
+ @Throws(IllegalArgumentException::class)
+ public fun build(): MangaSearchQuery {
+ return MangaSearchQuery(deduplicateCriteria(criteria), order, offset)
+ }
+
+ /**
+ * Validates the provided [QueryCriteria] to ensure type correctness.
+ *
+ * @param criterion The search criterion to validate.
+ * @throws IllegalArgumentException If the criterion type does not match the expected type.
+ */
+ private fun validateCriterion(criterion: QueryCriteria<*>) {
+ try {
+ val expectedType = criterion.field.type
+ val actualType: Class<*>? = when (criterion) {
+ is QueryCriteria.Include<*> -> criterion.values.first().javaClass
+ is QueryCriteria.Exclude<*> -> criterion.values.first().javaClass
+ is QueryCriteria.Match<*> -> criterion.value.javaClass
+ is QueryCriteria.Range<*> -> {
+ if (criterion.from.javaClass != criterion.to.javaClass) {
+ throw IllegalArgumentException(
+ "Mismatched types for field '${criterion.field}'. 'from' and 'to' should have same types"
+ )
+ }
+ criterion.from.javaClass
+ }
+ }
+
+ val isCompatibleIntType = (expectedType == Int::class.java && actualType == Integer::class.java) ||
+ (expectedType == Integer::class.java && actualType == Int::class.java)
+
+ if (actualType != null && !expectedType.isAssignableFrom(actualType) && !isCompatibleIntType) {
+ throw IllegalArgumentException(
+ "Invalid type for ${criterion.field}. Expected: ${expectedType.simpleName}, but got: ${actualType.simpleName}"
+ )
+ }
+ } catch (e: NoSuchElementException) {
+ throw IllegalArgumentException(
+ "QueryCriteria values should not be empty"
+ )
+ }
+ }
+
+ private fun deduplicateCriteria(criteria: Set>): Set> {
+ val uniqueCriteria = mutableMapOf>>, QueryCriteria<*>>()
+
+ for (criterion in criteria) {
+ val key = criterion.field to criterion::class.java
+ val existing = uniqueCriteria[key]
+
+ when {
+ existing == null -> uniqueCriteria[key] = criterion
+
+ existing is QueryCriteria.Include<*> && criterion is QueryCriteria.Include<*> -> {
+ uniqueCriteria[key] = QueryCriteria.Include(criterion.field,existing.values union criterion.values)
+ }
+
+ existing is QueryCriteria.Exclude<*> && criterion is QueryCriteria.Exclude<*> -> {
+ uniqueCriteria[key] = QueryCriteria.Exclude(criterion.field,existing.values union criterion.values)
+ }
+
+ else -> throw IllegalArgumentException(
+ "Match and Range have only one criterion per type, but found duplicates for: ${criterion.field} in ${criterion::class.simpleName}"
+ )
+ }
+ }
+
+ return uniqueCriteria.values.toSet()
+ }
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
new file mode 100644
index 000000000..b7152b397
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
@@ -0,0 +1,105 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+
+public data class MangaSearchQueryCapabilities(
+ val capabilities: Set = emptySet(),
+) {
+ public companion object {
+ @InternalParsersApi
+ public fun from(filterCapabilities: MangaListFilterCapabilities): MangaSearchQueryCapabilities {
+ return MangaSearchQueryCapabilities(
+ capabilities = setOfNotNull(
+ filterCapabilities.isMultipleTagsSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TAG, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
+ )
+ },
+ filterCapabilities.isTagsExclusionSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TAG, criteriaTypes = setOf(Exclude::class), multiValue = true, otherCriteria = true
+ )
+ },
+ filterCapabilities.isSearchSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = false
+ )
+ },
+ filterCapabilities.isSearchWithFiltersSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true
+ )
+ },
+ filterCapabilities.isYearSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = PUBLICATION_YEAR, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true
+ )
+ },
+ filterCapabilities.isYearRangeSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = PUBLICATION_YEAR, criteriaTypes = setOf(Range::class), multiValue = false, otherCriteria = true
+ )
+ },
+ filterCapabilities.isOriginalLocaleSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = ORIGINAL_LANGUAGE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
+ )
+ },
+ SearchCapability(
+ field = LANGUAGE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
+ ),
+ SearchCapability(
+ field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
+ ),
+ SearchCapability(
+ field = CONTENT_TYPE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
+ ),
+ SearchCapability(
+ field = CONTENT_RATING, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
+ ),
+ SearchCapability(
+ field = DEMOGRAPHIC, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
+ ),
+ ),
+ )
+ }
+ }
+
+ @InternalParsersApi
+ public fun validate(query: MangaSearchQuery) {
+ val strictFields = capabilities.filter { !it.otherCriteria }.map { it.field }.toSet()
+ val usedStrictFields = query.criteria.map { it.field }.toSet().intersect(strictFields)
+
+ if (usedStrictFields.isNotEmpty() && query.criteria.size > 1) {
+ throw IllegalArgumentException(
+ "Query contains multiple criteria, but at least one field (${usedStrictFields.joinToString()}) does not support multiple criteria."
+ )
+ }
+
+ for (criterion in query.criteria) {
+ val capability = capabilities.find { it.field == criterion.field }
+ ?: throw IllegalArgumentException("Unsupported search field: ${criterion.field}")
+
+ if (criterion::class !in capability.criteriaTypes) {
+ throw IllegalArgumentException(
+ "Unsupported search criterion: ${criterion::class.simpleName} for field ${criterion.field}"
+ )
+ }
+
+ // Ensure single value per criterion if supportMultiValue is false
+ if (!capability.multiValue) {
+ when (criterion) {
+ is Include<*> -> if (criterion.values.size > 1)
+ throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}")
+ is Exclude<*> -> if (criterion.values.size > 1)
+ throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}")
+ is Range<*> -> {} // Range is always valid (from, to)
+ is Match<*> -> {} // Match always has a single value
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt
new file mode 100644
index 000000000..8294844db
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt
@@ -0,0 +1,63 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+/**
+ * Represents a generic search criterion used for filtering manga search results.
+ * Each criterion applies a specific condition to a [SearchableField] and operates on values of type [T].
+ *
+ * @param T The type of value associated with the search criterion.
+ * @property field The field to which this search criterion applies.
+ */
+public sealed class QueryCriteria(
+ @JvmField public val field: SearchableField
+) {
+ public class Include(field: SearchableField, @JvmField public val values: Set) : QueryCriteria(field) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Include<*>) return false
+ return field == other.field && values == other.values
+ }
+
+ override fun hashCode(): Int {
+ return 31 * field.hashCode() + values.hashCode()
+ }
+ }
+
+ public class Exclude(field: SearchableField, @JvmField public val values: Set) : QueryCriteria(field) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Exclude<*>) return false
+ return field == other.field && values == other.values
+ }
+
+ override fun hashCode(): Int {
+ return 31 * field.hashCode() + values.hashCode()
+ }
+ }
+
+ public class Range>(field: SearchableField, @JvmField public val from: T, @JvmField public val to: T) : QueryCriteria(field) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Range<*>) return false
+ return field == other.field && from == other.from && to == other.to
+ }
+
+ override fun hashCode(): Int {
+ var result = field.hashCode()
+ result = 31 * result + from.hashCode()
+ result = 31 * result + to.hashCode()
+ return result
+ }
+ }
+
+ public class Match(field: SearchableField, @JvmField public val value: T) : QueryCriteria(field) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Match<*>) return false
+ return field == other.field && value == other.value
+ }
+
+ override fun hashCode(): Int {
+ return 31 * field.hashCode() + value.hashCode()
+ }
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt
new file mode 100644
index 000000000..097986fab
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt
@@ -0,0 +1,10 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import kotlin.reflect.KClass
+
+public data class SearchCapability (
+ @JvmField public val field: SearchableField,
+ @JvmField public val criteriaTypes: Set>>,
+ @JvmField public val multiValue: Boolean,
+ @JvmField public val otherCriteria: Boolean,
+)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt
new file mode 100644
index 000000000..e906713a8
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt
@@ -0,0 +1,23 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import org.koitharu.kotatsu.parsers.model.*
+import java.util.Locale
+
+/**
+ * Represents the various fields that can be used for searching manga.
+ * Each field is associated with a specific data type that defines its expected values.
+ *
+ * @property type The Java class representing the expected type of values for this field.
+ */
+public enum class SearchableField(public val type: Class<*>) {
+ TITLE_NAME(String::class.java),
+ TAG(MangaTag::class.java),
+ AUTHOR(MangaTag::class.java),
+ LANGUAGE(Locale::class.java),
+ ORIGINAL_LANGUAGE(Locale::class.java),
+ STATE(MangaState::class.java),
+ CONTENT_TYPE(ContentType::class.java),
+ CONTENT_RATING(ContentRating::class.java),
+ DEMOGRAPHIC(Demographic::class.java),
+ PUBLICATION_YEAR(Int::class.java);
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
index 9f57ba588..99b5de223 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
@@ -14,6 +14,9 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.*
import java.text.SimpleDateFormat
@@ -74,6 +77,22 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
isAuthorSearchSupported = true,
)
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ capabilities = setOf(
+ SearchCapability(field = TAG, criteriaTypes = setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true),
+ SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = AUTHOR, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = CONTENT_TYPE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = CONTENT_RATING, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = DEMOGRAPHIC, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = ORIGINAL_LANGUAGE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = LANGUAGE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = PUBLICATION_YEAR, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true),
+ ),
+ )
+
override suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope {
val localesDeferred = async { fetchAvailableLocales() }
val tagsDeferred = async { fetchAvailableTags() }
@@ -97,121 +116,117 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
)
}
- override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
- val domain = domain
- val url = buildString {
- append("https://api.")
- append(domain)
- append("/manga?limit=")
- append(PAGE_SIZE)
- append("&offset=")
- append(offset)
- append("&includes[]=cover_art&includes[]=author&includes[]=artist")
-
- filter.query?.let {
- append("&title=")
- append(filter.query.urlEncoded())
- }
-
- filter.tags.forEach {
- append("&includedTags[]=")
- append(it.key)
- }
+ private fun SearchableField.toParamName(): String = when (this) {
+ TITLE_NAME -> "title"
+ TAG -> "includedTags[]"
+ AUTHOR -> "authors[]"
+ STATE -> "status[]"
+ CONTENT_TYPE -> "contentType[]"
+ CONTENT_RATING -> "contentRating[]"
+ DEMOGRAPHIC -> "publicationDemographic[]"
+ ORIGINAL_LANGUAGE -> "originalLanguage[]"
+ LANGUAGE -> "availableTranslatedLanguage[]"
+ PUBLICATION_YEAR -> "year"
+ else -> ""
+ }
- filter.tagsExclude.forEach {
- append("&excludedTags[]=")
- append(it.key)
- }
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> urlEncoded()
+ is Locale -> if (language == "in") "id" else language
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ MangaState.ABANDONED -> "cancelled"
+ MangaState.PAUSED -> "hiatus"
+ else -> ""
+ }
+ is ContentRating -> when (this) {
+ ContentRating.SAFE -> "safe"
+ // quick fix for double value
+ ContentRating.SUGGESTIVE -> "suggestive&contentRating[]=erotica"
+ ContentRating.ADULT -> "pornographic"
+ else -> ""
+ }
+ is Demographic -> when (this) {
+ Demographic.SHOUNEN -> "shounen"
+ Demographic.SHOUJO -> "shoujo"
+ Demographic.SEINEN -> "seinen"
+ Demographic.JOSEI -> "josei"
+ Demographic.NONE -> "none"
+ else -> ""
+ }
+ is SortOrder -> when (this) {
+ SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
+ SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
+ SortOrder.RATING -> "[rating]=desc"
+ SortOrder.RATING_ASC -> "[rating]=asc"
+ SortOrder.ALPHABETICAL -> "[title]=asc"
+ SortOrder.ALPHABETICAL_DESC -> "[title]=desc"
+ SortOrder.NEWEST -> "[year]=desc"
+ SortOrder.NEWEST_ASC -> "[year]=asc"
+ SortOrder.POPULARITY -> "[followedCount]=desc"
+ SortOrder.POPULARITY_ASC -> "[followedCount]=asc"
+ SortOrder.ADDED -> "[createdAt]=desc"
+ SortOrder.ADDED_ASC -> "[createdAt]=asc"
+ SortOrder.RELEVANCE -> "&order[relevance]=desc"
+ else -> "[latestUploadedChapter]=desc"
+ }
+ else -> this.toString().urlEncoded()
+ }
- if (filter.contentRating.isNotEmpty()) {
- filter.contentRating.forEach {
- when (it) {
- ContentRating.SAFE -> append("&contentRating[]=safe")
- ContentRating.SUGGESTIVE -> append("&contentRating[]=suggestive&contentRating[]=erotica")
- ContentRating.ADULT -> append("&contentRating[]=pornographic")
+ private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
+ val param = paramName ?: field.toParamName()
+ if (param.isNotBlank()) {
+ append("&$param=")
+ append(value.toQueryParam())
+ }
+ }
+ override suspend fun validatedMangaSearch(searchQuery: MangaSearchQuery): List {
+ val url = buildString {
+ append("https://api.$domain/manga?limit=$PAGE_SIZE&offset=${searchQuery.offset ?: 0}")
+ .append("&includes[]=cover_art&includes[]=author&includes[]=artist&includedTagsMode=AND&excludedTagsMode=OR")
+
+ var hasContentRating = false
+
+ searchQuery.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ if (criterion.field == CONTENT_RATING) {
+ hasContentRating = true
+ }
+ criterion.values.forEach { appendCriterion(criterion.field, it) }
+ }
+ is Exclude<*> -> {
+ criterion.values.forEach { appendCriterion(criterion.field, it, "excludedTags[]") }
+ }
+ is Match<*> -> {
+ appendCriterion(criterion.field, criterion.value)
+ }
+ else -> {
+ // Not supported
}
}
- } else {
- append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
}
- if (!filter.author.isNullOrEmpty()) {
- append("&authorOrArtist=").append(getAuthorId(filter.author))
+ // If contentRating is not provided, add default values
+ if (!hasContentRating) {
+ append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
}
append("&order")
- append(
- when (order) {
- SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
- SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
- SortOrder.RATING -> "[rating]=desc"
- SortOrder.RATING_ASC -> "[rating]=asc"
- SortOrder.ALPHABETICAL -> "[title]=asc"
- SortOrder.ALPHABETICAL_DESC -> "[title]=desc"
- SortOrder.NEWEST -> "[year]=desc"
- SortOrder.NEWEST_ASC -> "[year]=asc"
- SortOrder.POPULARITY -> "[followedCount]=desc"
- SortOrder.POPULARITY_ASC -> "[followedCount]=asc"
- SortOrder.ADDED -> "[createdAt]=desc"
- SortOrder.ADDED_ASC -> "[createdAt]=asc"
- SortOrder.RELEVANCE -> "&order[relevance]=desc"
- else -> "[latestUploadedChapter]=desc"
- },
- )
-
- filter.states.forEach {
- append("&status[]=")
- when (it) {
- MangaState.ONGOING -> append("ongoing")
- MangaState.FINISHED -> append("completed")
- MangaState.ABANDONED -> append("cancelled")
- MangaState.PAUSED -> append("hiatus")
- else -> append("")
- }
- }
-
- filter.demographics.forEach {
- append("&publicationDemographic[]=")
- append(
- when (it) {
- Demographic.SHOUNEN -> "shounen"
- Demographic.SHOUJO -> "shoujo"
- Demographic.SEINEN -> "seinen"
- Demographic.JOSEI -> "josei"
- Demographic.NONE -> "none"
- else -> ""
- },
- )
- }
-
- filter.locale?.let {
- append("&availableTranslatedLanguage[]=")
- if (it.language == "in") {
- append("id")
- } else {
- append(it.language)
- }
- }
-
- filter.originalLocale?.let {
- append("&originalLanguage[]=")
- if (it.language == "in") {
- append("id")
- } else {
- append(it.language)
- }
- }
-
- if (filter.year != 0) {
- append("&year=")
- append(filter.year)
- }
+ append((searchQuery.order ?: defaultSortOrder).toQueryParam())
}
+
val json = webClient.httpGet(url).parseJson().getJSONArray("data")
return json.mapJSON { jo -> jo.fetchManga(null) }
}
+ override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
+ return searchManga(convertToMangaSearchQuery(offset, order, filter))
+ }
+
override suspend fun getDetails(manga: Manga): Manga {
val mangaId = manga.url.removePrefix("/")
return getDetails(mangaId)
@@ -282,11 +297,23 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
val attrs = getJSONObject("attributes")
val relations = getJSONArray("relationships").associateByKey("type")
val cover = relations["cover_art"]
+ ?.firstOrNull()
?.getJSONObject("attributes")
?.getString("fileName")
?.let {
"https://uploads.$domain/covers/$id/$it"
}
+ val authors: Set = (relations["author"] ?: relations["artist"])
+ ?.mapNotNull {
+ val key = it.getStringOrNull("id")
+ val title = it.getJSONObject("attributes")?.getStringOrNull("name")
+
+ key?.let { k ->
+ title?.let { t -> MangaTag(key = k, title = t, source = source) }
+ }
+ }
+ ?.toSet() ?: emptySet()
+
return Manga(
id = generateUid(id),
title = requireNotNull(attrs.getJSONObject("title").selectByLocale()) {
@@ -322,9 +349,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
"cancelled" -> MangaState.ABANDONED
else -> null
},
- author = (relations["author"] ?: relations["artist"])
- ?.getJSONObject("attributes")
- ?.getStringOrNull("name"),
+ authors = authors,
chapters = chapters,
source = source,
)
@@ -408,22 +433,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
}
}
- private suspend fun getAuthorId(name: String): String {
- val url = urlBuilder("api")
- .addPathSegment("author")
- .addQueryParameter("name", name)
- .addQueryParameter("limit", "1")
- .build()
- val json = webClient.httpGet(url).parseJson()
- .getJSONArray("data")
- .getJSONObject(0)
- if (json.getJSONObject("attributes").getString("name").equals(name, ignoreCase = true)) {
- return json.getString("id")
- } else {
- throw NotFoundException("Author $name not found", url.toString())
- }
- }
-
private fun mapChapters(list: List): List {
// 2022-01-02T00:27:11+00:00
val dateFormat = SimpleDateFormat(
@@ -443,7 +452,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
val locale = attrs.getStringOrNull("translatedLanguage")?.let { Locale.forLanguageTag(it) }
val lc = locale?.getDisplayName(locale)?.toTitleCase(locale)
val relations = jo.getJSONArray("relationships").associateByKey("type")
- val team = relations["scanlation_group"]?.optJSONObject("attributes")?.getStringOrNull("name")
+ val team = relations["scanlation_group"]?.firstOrNull()?.optJSONObject("attributes")?.getStringOrNull("name")
val branch = (list.indices).firstNotNullOf { i ->
val b = if (i == 0) lc else "$lc ($i)"
if (branchedChapters[b]?.get(volume to number) == null) b else null
@@ -469,12 +478,12 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
return chaptersBuilder.toList()
}
- private fun JSONArray.associateByKey(key: String): Map {
- val destination = LinkedHashMap(length())
+ private fun JSONArray.associateByKey(key: String): Map> {
+ val destination = LinkedHashMap>(length())
repeat(length()) { i ->
val item = getJSONObject(i)
val keyValue = item.getString(key)
- destination[keyValue] = item
+ destination.computeIfAbsent(keyValue) { mutableListOf() }.add(item)
}
return destination
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
index b008e266e..e74d5e604 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
@@ -7,6 +7,12 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchCapability
+import org.koitharu.kotatsu.parsers.model.search.SearchableField
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.util.*
import java.text.DateFormat
import java.text.SimpleDateFormat
@@ -38,6 +44,16 @@ internal abstract class MangaboxParser(
isSearchWithFiltersSupported = true,
)
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ capabilities = setOf(
+ SearchCapability(field = TAG, criteriaTypes = setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true),
+ SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
+ SearchCapability(field = AUTHOR, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = false),
+ ),
+ )
+
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
@@ -59,64 +75,82 @@ internal abstract class MangaboxParser(
)
protected open val listUrl = "/advanced_search"
+ protected open val authorUrl = "/search/author"
protected open val searchUrl = "/search/story/"
protected open val datePattern = "MMM dd,yy"
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- val url = buildString {
- append("https://")
- append(domain)
- append(listUrl)
- append("/?s=all")
-
- filter.query?.let {
- append("&keyw=")
- append(filter.query.replace(" ", "_").urlEncoded())
- }
+ private fun SearchableField.toParamName(): String = when (this) {
+ TITLE_NAME, AUTHOR -> "keyw"
+ TAG -> "g_i"
+ STATE -> "sts"
+ else -> ""
+ }
- if (filter.tags.isNotEmpty()) {
- append("&g_i=")
- filter.tags.forEach {
- append("_")
- append(it.key)
- append("_")
- }
- }
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> replace(" ", "_").urlEncoded()
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ else -> ""
+ }
+ is SortOrder -> when (this) {
+ SortOrder.ALPHABETICAL -> "az"
+ SortOrder.NEWEST -> "newest"
+ SortOrder.POPULARITY -> "topview"
+ else -> ""
+ }
+ else -> this.toString().replace(" ", "_").urlEncoded()
+ }
- if (filter.tagsExclude.isNotEmpty()) {
- append("&g_e=")
- filter.tagsExclude.forEach {
- append("_")
- append(it.key)
- append("_")
- }
- }
+ private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
+ val param = paramName ?: field.toParamName()
+ if (param.isNotBlank()) {
+ append("&$param=")
+ append(value.toQueryParam())
+ }
+ }
- filter.states.oneOrThrowIfMany()?.let {
- append("&sts=")
- append(
- when (it) {
- MangaState.ONGOING -> "ongoing"
- MangaState.FINISHED -> "completed"
- else -> ""
- },
- )
- }
+ private val authorKeyRegex by lazy {
+ Regex("[^/]+\$", RegexOption.IGNORE_CASE)
+ }
- append("&orby=")
- when (order) {
- SortOrder.POPULARITY -> append("topview")
- SortOrder.UPDATED -> append("")
- SortOrder.NEWEST -> append("newest")
- SortOrder.ALPHABETICAL -> append("az")
- else -> append("")
+ override suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ var authorSearchUrl: String? = null
+ val url = buildString {
+ val pageQueryParameter = "page=${searchQuery.offset ?: 0}"
+ append("https://${domain}${listUrl}/?s=all")
+
+ searchQuery.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ if (criterion.field == AUTHOR) {
+ criterion.values.firstOrNull()?.toQueryParam()?.takeIf { it.isNotBlank() }?.let { authorKey ->
+ authorSearchUrl = "https://${domain}${authorUrl}/${authorKey}/?$pageQueryParameter"
+ }
+ }
+
+ criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
+ append("&$param=${criterion.values.joinToString("_") { it.toQueryParam() }}")
+ }
+ }
+ is Exclude<*> -> {
+ append("&g_e=${criterion.values.joinToString("_") { it.toQueryParam() }}")
+ }
+ is Match<*> -> {
+ appendCriterion(criterion.field, criterion.value)
+ }
+ else -> {
+ // Not supported
+ }
+ }
}
- append("&page=")
- append(page.toString())
+ append("&${pageQueryParameter}")
+ append("&orby=${(searchQuery.order ?: defaultSortOrder).toQueryParam()}")
}
- val doc = webClient.httpGet(url).parseHtml()
+ val doc = webClient.httpGet(authorSearchUrl ?: url).parseHtml()
return doc.select("div.content-genres-item, div.list-story-item").ifEmpty {
doc.select("div.search-story-item")
@@ -131,14 +165,18 @@ internal abstract class MangaboxParser(
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
- author = null,
+ authors = emptySet(),
state = null,
source = source,
- isNsfw = isNsfwSource,
+ contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
)
}
}
+ override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+ return searchManga(convertToMangaSearchQuery(page, order, filter))
+ }
+
protected open val selectTagMap = "div.panel-genres-list a:not(.genres-select)"
protected open suspend fun fetchAvailableTags(): Set {
@@ -175,7 +213,14 @@ internal abstract class MangaboxParser(
}
}
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "").nullIfEmpty()
- val aut = doc.body().select(selectAut).eachText().joinToString().nullIfEmpty()
+ val authors = doc.body().select(selectAut).mapToSet {
+ MangaTag(
+ key = it.attribute("href").value.find(authorKeyRegex)?: it.text(),
+ title = it.text(),
+ source = source,
+ )
+ }
+
manga.copy(
tags = doc.body().select(selectTag).mapToSet { a ->
MangaTag(
@@ -186,7 +231,8 @@ internal abstract class MangaboxParser(
},
description = desc,
altTitle = alt,
- author = aut,
+ authors = authors,
+ author = authors.firstOrNull()?.title,
state = state,
chapters = chaptersDeferred.await(),
)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
index fb19b4c4a..895ef4739 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
@@ -6,6 +6,10 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Include
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Match
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@@ -38,6 +42,89 @@ internal class Mangairo(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false,
)
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ capabilities = setOf(
+ SearchCapability(field = TAG, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = false),
+ SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ ),
+ )
+
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> replace(" ", "_").urlEncoded()
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ else -> "all"
+ }
+ is SortOrder -> when (this) {
+ SortOrder.POPULARITY -> "topview"
+ SortOrder.UPDATED -> "latest"
+ SortOrder.NEWEST -> "newest"
+ else -> "latest"
+ }
+ else -> this.toString().urlEncoded()
+ }
+
+ override suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ var titleSearchUrl: String? = null
+ var category = "all"
+ var state = "all"
+
+ val url = buildString {
+ append("https://${domain}${listUrl}")
+ append("/type-${(searchQuery.order ?: defaultSortOrder).toQueryParam()}")
+
+ searchQuery.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ when (criterion.field) {
+ TAG -> category = criterion.values.first().toQueryParam()
+ STATE -> state = criterion.values.first().toQueryParam()
+ else -> Unit
+ }
+ }
+ is Match<*> -> {
+ if (criterion.field == TITLE_NAME) {
+ criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
+ titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
+ "?page=${searchQuery.offset ?: 0}"
+ }
+ }
+ }
+ else -> {
+ // Not supported
+ }
+ }
+ }
+ append("/ctg-$category")
+ append("/state-$state")
+ append("/page-${searchQuery.offset ?: 0}")
+ }
+
+ val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
+
+ return doc.select("div.story-item").map { div ->
+ val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
+ Manga(
+ id = generateUid(href),
+ url = href,
+ publicUrl = href.toAbsoluteUrl(div.host ?: domain),
+ coverUrl = div.selectFirst("img")?.src().orEmpty(),
+ title = (div.selectFirst("h2")?.text() ?: div.selectFirst("h3")?.text()).orEmpty(),
+ altTitle = null,
+ rating = RATING_UNKNOWN,
+ tags = emptySet(),
+ authors = emptySet(),
+ state = null,
+ source = source,
+ contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
+ )
+ }
+ }
+
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
val url = buildString {
append("https://")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
index 8c77b79a7..4ed13a69d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
@@ -5,6 +5,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
@@ -25,57 +28,91 @@ internal class Mangakakalot(context: MangaLoaderContext) :
isMultipleTagsSupported = false,
isSearchWithFiltersSupported = false,
)
+
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ capabilities = setOf(
+ SearchCapability(field = TAG, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = false),
+ SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ ),
+ )
+
override val otherDomain = "chapmanganato.com"
override val listUrl = "/manga_list"
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+ private fun SearchableField.toParamName(): String = when (this) {
+ TAG -> "category"
+ STATE -> "state"
+ else -> ""
+ }
+
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> {
+ sanitizeTitleNameRegex.replace(this, "")
+ .replace(" ", "_")
+ .urlEncoded()
+ }
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ else -> "all"
+ }
+ is SortOrder -> when (this) {
+ SortOrder.POPULARITY -> "topview"
+ SortOrder.UPDATED -> "latest"
+ SortOrder.NEWEST -> "newest"
+ else -> ""
+ }
+ else -> this.toString().replace(" ", "_").urlEncoded()
+ }
+
+ private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
+ val param = paramName ?: field.toParamName()
+ if (param.isNotBlank()) {
+ append("&$param=")
+ append(value.toQueryParam())
+ }
+ }
+
+ private val sanitizeTitleNameRegex by lazy {
+ Regex("[^A-Za-z0-9 ]")
+ }
+
+ override suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ var titleSearchUrl: String? = null
val url = buildString {
- append("https://")
- append(domain)
- when {
-
- !filter.query.isNullOrEmpty() -> {
- append(searchUrl)
- val regex = Regex("[^A-Za-z0-9 ]")
- val q = regex.replace(filter.query, "")
- append(q.replace(" ", "_"))
- append("?page=")
- }
+ val pageQueryParameter = "page=${searchQuery.offset ?: 0}"
+ append("https://$domain/?")
- else -> {
- append(listUrl)
- append("?type=")
- when (order) {
- SortOrder.POPULARITY -> append("topview")
- SortOrder.UPDATED -> append("latest")
- SortOrder.NEWEST -> append("newest")
- else -> append("latest")
+ searchQuery.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
+ append("&$param=${criterion.values.first().toQueryParam()}")
+ }
}
- if (filter.tags.isNotEmpty()) {
- append("&category=")
- filter.tags.oneOrThrowIfMany()?.let {
- append(it.key)
+ is Match<*> -> {
+ if (criterion.field == TITLE_NAME) {
+ criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
+ titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
+ "?$pageQueryParameter"
+ }
}
+ appendCriterion(criterion.field, criterion.value)
}
-
- filter.states.oneOrThrowIfMany()?.let {
- append("&state=")
- append(
- when (it) {
- MangaState.ONGOING -> "ongoing"
- MangaState.FINISHED -> "completed"
- else -> "all"
- },
- )
+ else -> {
+ // Not supported
}
-
- append("&page=")
}
}
- append(page.toString())
+
+ append("&$pageQueryParameter")
+ append("&type=${(searchQuery.order ?: defaultSortOrder).toQueryParam()}")
}
- val doc = webClient.httpGet(url).parseHtml()
+ val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
return doc.select("div.list-truyen-item-wrap").ifEmpty {
doc.select("div.story_item")
@@ -90,10 +127,10 @@ internal class Mangakakalot(context: MangaLoaderContext) :
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
- author = null,
+ authors = emptySet(),
state = null,
source = source,
- isNsfw = isNsfwSource,
+ contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
index 3ec07a94b..418f1e290 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
@@ -6,6 +6,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@@ -29,51 +32,81 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false,
)
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ capabilities = setOf(
+ SearchCapability(field = TAG, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = false),
+ SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ ),
+ )
+
+ private fun SearchableField.toParamName(): String = when (this) {
+ TAG -> "category"
+ STATE -> "state"
+ else -> ""
+ }
+
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> urlEncoded()
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ else -> "all"
+ }
+ is SortOrder -> when (this) {
+ SortOrder.POPULARITY -> "topview"
+ SortOrder.UPDATED -> "latest"
+ SortOrder.NEWEST -> "newest"
+ else -> ""
+ }
+ else -> this.toString().urlEncoded()
+ }
+
+ private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
+ val param = paramName ?: field.toParamName()
+ if (param.isNotBlank()) {
+ append("&$param=")
+ append(value.toQueryParam())
+ }
+ }
+
+ override suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ var titleSearchUrl: String? = null
val url = buildString {
- append("https://")
- append(domain)
- when {
-
- !filter.query.isNullOrEmpty() -> {
- append(searchUrl)
- append(filter.query.urlEncoded())
- append("?page=")
- }
+ val pageQueryParameter = "page=${searchQuery.offset ?: 0}"
+ append("https://$domain/?")
- else -> {
- append(listUrl)
- append("?type=")
- when (order) {
- SortOrder.POPULARITY -> append("topview")
- SortOrder.UPDATED -> append("latest")
- SortOrder.NEWEST -> append("newest")
- else -> append("latest")
+ searchQuery.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
+ append("&$param=${criterion.values.first().toQueryParam()}")
+ }
}
- if (filter.tags.isNotEmpty()) {
- append("&category=")
- filter.tags.oneOrThrowIfMany()?.let {
- append(it.key)
+ is Match<*> -> {
+ if (criterion.field == TITLE_NAME) {
+ criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
+ titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
+ "?$pageQueryParameter"
+ }
}
+ appendCriterion(criterion.field, criterion.value)
}
-
- filter.states.oneOrThrowIfMany()?.let {
- append("&state=")
- append(
- when (it) {
- MangaState.ONGOING -> "Ongoing"
- MangaState.FINISHED -> "Completed"
- else -> "all"
- },
- )
+ else -> {
+ // Not supported
}
-
- append("&page=")
}
}
- append(page.toString())
+
+ append("&$pageQueryParameter")
+ append("&type=${(searchQuery.order ?: defaultSortOrder).toQueryParam()}")
}
- val doc = webClient.httpGet(url).parseHtml()
+
+ val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
+
return doc.select("div.list-truyen-item-wrap").ifEmpty {
doc.select("div.story_item")
}.map { div ->
@@ -83,14 +116,14 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src().orEmpty(),
- title = div.selectFirstOrThrow("h3").text().orEmpty(),
+ title = div.selectFirst("h3")?.text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
- author = null,
+ authors = emptySet(),
state = null,
source = source,
- isNsfw = isNsfwSource,
+ contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt
index a41005c31..e885d9c3d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt
@@ -16,5 +16,6 @@ internal class Manganato(context: MangaLoaderContext) :
)
override val otherDomain = "chapmanganato.to"
+ override val authorUrl = "/author/story"
override val selectPage = ".container-chapter-reader > img"
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
new file mode 100644
index 000000000..74680b6ab
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
@@ -0,0 +1,154 @@
+package org.koitharu.kotatsu.parsers.util
+
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+
+/**
+ * Converts a [MangaListFilter] into a [MangaSearchQuery].
+ *
+ * This function iterates through the filter attributes in [MangaListFilter] and creates corresponding
+ * search criteria in a [MangaSearchQuery.Builder].
+ *
+ * @param filter The [MangaListFilter] to convert.
+ * @return A [MangaSearchQuery] constructed based on the given [filter].
+ */
+@InternalParsersApi
+public fun convertToMangaSearchQuery(offset: Int, sortOrder: SortOrder, filter: MangaListFilter): MangaSearchQuery {
+ return MangaSearchQuery.Builder().apply {
+ offset(offset)
+ order(sortOrder)
+ if (filter.tags.isNotEmpty()) criterion(QueryCriteria.Include(TAG, filter.tags))
+ if (filter.tagsExclude.isNotEmpty()) criterion(QueryCriteria.Exclude(TAG, filter.tagsExclude))
+ if (filter.states.isNotEmpty()) criterion(QueryCriteria.Include(STATE, filter.states))
+ if (filter.types.isNotEmpty()) criterion(QueryCriteria.Include(CONTENT_TYPE, filter.types))
+ if (filter.contentRating.isNotEmpty()) criterion(QueryCriteria.Include(CONTENT_RATING, filter.contentRating))
+ if (filter.demographics.isNotEmpty()) criterion(QueryCriteria.Include(DEMOGRAPHIC, filter.demographics))
+ if (validateYear(filter.yearFrom) || validateYear(filter.yearTo)) {
+ criterion(QueryCriteria.Range(PUBLICATION_YEAR, filter.yearFrom, filter.yearTo))
+ }
+ if (validateYear(filter.year)) {
+ criterion(QueryCriteria.Match(PUBLICATION_YEAR, filter.year))
+ }
+ filter.locale?.takeIf { it != null }?.let {
+ criterion(QueryCriteria.Include(LANGUAGE, setOf(it)))
+ }
+ filter.originalLocale?.takeIf { it != null }?.let {
+ criterion(QueryCriteria.Include(ORIGINAL_LANGUAGE, setOf(it)))
+ }
+ filter.query?.takeIf { it.isNotBlank() }?.let {
+ criterion(QueryCriteria.Match(TITLE_NAME, it))
+ }
+ }.build()
+}
+
+/**
+ * Converts a {@link MangaSearchQuery} into a {@link MangaListFilter}.
+ *
+ * This method iterates through the search criteria defined in the provided {@code searchQuery}
+ * and applies them to a {@link MangaListFilter.Builder}. The criteria are processed based on
+ * their types, such as inclusion, exclusion, equality checks, range filtering, and pattern matching.
+ *
+ *
+ * Supported criteria:
+ *
+ * - {@link QueryCriteria.Include} - Adds tags, states, content types, content ratings, demographics, and languages.
+ * - {@link QueryCriteria.Exclude} - Excludes tags.
+ * - {@link QueryCriteria.Equals} - Sets specific values like publication year.
+ * - {@link QueryCriteria.Between} - Sets a range of values like publication year range.
+ * - {@link QueryCriteria.Match} - Adds a search pattern for the title name.
+ *
+ *
+ *
+ * If an unsupported field is encountered, an {@link UnsupportedOperationException} is thrown.
+ *
+ *
+ * @param searchQuery The {@link MangaSearchQuery} to convert.
+ * @return A {@link MangaListFilter} constructed based on the given {@code searchQuery}.
+ * @throws UnsupportedOperationException If the search criteria contain unsupported fields.
+ */
+@InternalParsersApi
+public fun convertToMangaListFilter(searchQuery: MangaSearchQuery): MangaListFilter {
+ return MangaListFilter.Builder().apply {
+ for (criterion in searchQuery.criteria) {
+ when (criterion) {
+ is QueryCriteria.Include<*> -> handleInclude(this, criterion)
+ is QueryCriteria.Exclude<*> -> handleExclude(this, criterion)
+ is QueryCriteria.Range<*> -> handleBetween(this, criterion)
+ is QueryCriteria.Match<*> -> handleMatch(this, criterion)
+ }
+ }
+ }.build()
+}
+
+private fun handleInclude(builder: MangaListFilter.Builder, criterion: QueryCriteria.Include<*>) {
+ val type = criterion.field.type
+
+ when (criterion.field) {
+ TAG -> builder.addTags(filterValues(criterion, type))
+ STATE -> builder.addStates(filterValues(criterion, type))
+ CONTENT_TYPE -> builder.addTypes(filterValues(criterion, type))
+ CONTENT_RATING -> builder.addContentRatings(filterValues(criterion, type))
+ DEMOGRAPHIC -> builder.addDemographics(filterValues(criterion, type))
+ LANGUAGE -> builder.locale(getFirstValue(criterion, type))
+ ORIGINAL_LANGUAGE -> builder.originalLocale(getFirstValue(criterion, type))
+ else -> throw UnsupportedOperationException("Unsupported field for Include criterion: ${criterion.field}")
+ }
+}
+
+private fun handleExclude(builder: MangaListFilter.Builder, criterion: QueryCriteria.Exclude<*>) {
+ val type = criterion.field.type
+
+ when (criterion.field) {
+ TAG -> builder.excludeTags(filterValues(criterion, type))
+ else -> throw UnsupportedOperationException("Unsupported field for Exclude criterion: ${criterion.field}")
+ }
+}
+
+private fun handleBetween(builder: MangaListFilter.Builder, criterion: QueryCriteria.Range<*>) {
+ val type = criterion.field.type
+
+ when (criterion.field) {
+ PUBLICATION_YEAR -> {
+ builder.yearFrom(getValue(criterion.from, type, YEAR_UNKNOWN))
+ builder.yearTo(getValue(criterion.to, type, YEAR_UNKNOWN))
+ }
+ else -> throw UnsupportedOperationException("Unsupported field for Between criterion: ${criterion.field}")
+ }
+}
+
+private fun handleMatch(builder: MangaListFilter.Builder, criterion: QueryCriteria.Match<*>) {
+ val type = criterion.field.type
+
+ when (criterion.field) {
+ TITLE_NAME -> builder.query(getValue(criterion.value, type, ""))
+ PUBLICATION_YEAR -> builder.year(getValue(criterion.value, type, YEAR_UNKNOWN))
+ else -> throw UnsupportedOperationException("Unsupported field for Match criterion: ${criterion.field}")
+ }
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun filterValues(criterion: QueryCriteria.Include<*>, type: Class<*>): List {
+ return criterion.values.filter { type.isInstance(it) } as List
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun filterValues(criterion: QueryCriteria.Exclude<*>, type: Class<*>): List {
+ return criterion.values.filter { type.isInstance(it) } as List
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun getFirstValue(criterion: QueryCriteria.Include<*>, type: Class<*>): T? {
+ return criterion.values.firstOrNull { type.isInstance(it) } as? T
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun getValue(value: Any?, type: Class<*>, default: T): T {
+ val isCompatibleIntType = (type == Int::class.java && Integer::class.isInstance(value))
+
+ return if (type.isInstance(value) || isCompatibleIntType) value as T else default
+}
+
+private fun validateYear(year: Int) = year != null && year != YEAR_UNKNOWN
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
index 969500600..d667cf90b 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
@@ -6,6 +6,10 @@ import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.params.ParameterizedTest
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Include
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.medianOrNull
import org.koitharu.kotatsu.parsers.util.mimeType
@@ -34,8 +38,8 @@ internal class MangaParserTest {
if (parser is SinglePageMangaParser) {
return@runTest
}
- val page1 = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY)
- val page2 = parser.getList(page1.size, parser.defaultSortOrder, MangaListFilter.EMPTY)
+ val page1 = parser.searchManga(MangaSearchQuery.builder().offset(0).order(parser.defaultSortOrder).build())
+ val page2 = parser.searchManga(MangaSearchQuery.builder().offset(page1.size).order(parser.defaultSortOrder).build())
if (parser is PagedMangaParser) {
assert(parser.pageSize >= page1.size) {
"Page size is ${page1.size} but ${parser.pageSize} expected"
@@ -52,18 +56,25 @@ internal class MangaParserTest {
@ParameterizedTest(name = "{index}|search|{0}")
@MangaSources
- fun search(source: MangaParserSource) = runTest(timeout = timeout) {
+ fun searchByTitleName(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val subject = parser.getList(
- offset = 0,
- order = SortOrder.POPULARITY,
- filter = MangaListFilter.EMPTY,
+ val subject = parser.searchManga(
+ MangaSearchQuery.builder()
+ .offset(0)
+ .order(parser.defaultSortOrder)
+ .build()
).minByOrNull {
it.title.length
} ?: error("No manga found")
+
val query = subject.title
check(query.isNotBlank()) { "Manga title '$query' is blank" }
- val list = parser.getList(0, SortOrder.RELEVANCE, MangaListFilter(query = query))
+ val list = parser.searchManga(
+ MangaSearchQuery.builder()
+ .order(SortOrder.RELEVANCE)
+ .criterion(QueryCriteria.Match(TITLE_NAME, query))
+ .build(),
+ )
assert(list.isNotEmpty()) { "Empty search results by \"$query\"" }
assert(list.singleOrNull { it.url == subject.url && it.id == subject.id } != null) {
"Single subject '${subject.title} (${subject.publicUrl})' not found in search results"
@@ -91,10 +102,12 @@ internal class MangaParserTest {
assert(tags.all { it.source == source })
val tag = tags.last()
- val list = parser.getList(
- offset = 0,
- order = parser.defaultSortOrder,
- filter = MangaListFilter(tags = setOf(tag)),
+ val list = parser.searchManga(
+ MangaSearchQuery.builder()
+ .offset(0)
+ .order(parser.defaultSortOrder)
+ .criterion(Include(TAG, setOf(tag)))
+ .build(),
)
checkMangaList(list, "${tag.title} (${tag.key})")
assert(list.all { it.source == source })
@@ -107,8 +120,14 @@ internal class MangaParserTest {
if (!parser.filterCapabilities.isMultipleTagsSupported) return@runTest
val tags = parser.getFilterOptions().availableTags.shuffled().take(2).toSet()
- val filter = MangaListFilter(tags = tags)
- val list = parser.getList(0, parser.defaultSortOrder, filter)
+ val list = parser.searchManga(
+ MangaSearchQuery.builder()
+ .offset(0)
+ .order(parser.defaultSortOrder)
+ .criterion(Include(TAG, tags))
+ .build(),
+ )
+
checkMangaList(list, "${tags.joinToString { it.title }} (${tags.joinToString { it.key }})")
assert(list.all { it.source == source })
}
@@ -121,12 +140,17 @@ internal class MangaParserTest {
if (locales.isEmpty()) {
return@runTest
}
- val filter = MangaListFilter(
- locale = locales.random(),
- originalLocale = locales.random(),
+ val locale = locales.random()
+ val list = parser.searchManga(
+ MangaSearchQuery.builder()
+ .offset(0)
+ .order(parser.defaultSortOrder)
+ .criterion(Include(LANGUAGE, setOf(locale)))
+ .criterion(Include(LANGUAGE, setOf(locale)))
+ .criterion(Include(ORIGINAL_LANGUAGE, setOf(locales.random())))
+ .build(),
)
- val list = parser.getList(offset = 0, order = parser.defaultSortOrder, filter)
- checkMangaList(list, filter.locale.toString())
+ checkMangaList(list, locale.toString())
assert(list.all { it.source == source })
}
@@ -135,8 +159,9 @@ internal class MangaParserTest {
@MangaSources
fun details(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY)
- val manga = list[3]
+ val list = parser.searchManga(MangaSearchQuery.builder().offset(0).order(parser.defaultSortOrder).build())
+
+ val manga = list[0]
parser.getDetails(manga).apply {
assert(!chapters.isNullOrEmpty()) { "Chapters are null or empty" }
assert(publicUrl.isUrlAbsolute()) { "Manga public url is not absolute: '$publicUrl'" }
@@ -165,7 +190,7 @@ internal class MangaParserTest {
@MangaSources
fun pages(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY)
+ val list = parser.searchManga(MangaSearchQuery.builder().offset(0).order(parser.defaultSortOrder).build())
val manga = list.first()
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null at ${manga.publicUrl}")
val pages = parser.getPages(chapter)
@@ -220,7 +245,7 @@ internal class MangaParserTest {
@MangaSources
fun link(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val manga = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY).first()
+ val manga = parser.searchManga(MangaSearchQuery.builder().offset(0).order(parser.defaultSortOrder).build()).first()
val resolved = context.newLinkResolver(manga.publicUrl).getManga()
Assertions.assertNotNull(resolved)
resolved ?: return@runTest
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt
new file mode 100644
index 000000000..9aaddec61
--- /dev/null
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt
@@ -0,0 +1,72 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import org.junit.jupiter.api.Assertions.assertDoesNotThrow
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Test
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.MangaState
+import org.koitharu.kotatsu.parsers.model.MangaTag
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+import java.util.Locale
+
+class MangaSearchQueryCapabilitiesTest {
+
+ private val capabilities = MangaSearchQueryCapabilities(
+ capabilities = setOf(
+ SearchCapability(TITLE_NAME, setOf(Match::class), multiValue = false, otherCriteria = false),
+ SearchCapability(TAG, setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true),
+ SearchCapability(PUBLICATION_YEAR, setOf(Range::class), multiValue = false, otherCriteria = true),
+ SearchCapability(STATE, setOf(Include::class), multiValue = false, otherCriteria = true),
+ )
+ )
+
+ @Test
+ fun validateValidSingleCriterionQuery() {
+ val query = MangaSearchQuery.builder()
+ .criterion(Match(TITLE_NAME, "title"))
+ .build()
+
+ assertDoesNotThrow { capabilities.validate(query) }
+ }
+
+ @Test
+ fun validateUnsupportedFieldThrowsException() {
+ val query = MangaSearchQuery.builder()
+ .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.ENGLISH)))
+ .build()
+
+ assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
+ }
+
+ @Test
+ fun validateUnsupportedMultiValueThrowsException() {
+ val query = MangaSearchQuery.builder()
+ .criterion(Include(STATE, setOf(MangaState.ONGOING, MangaState.FINISHED)))
+ .build()
+
+ assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
+ }
+
+ @Test
+ fun validateMultipleCriteriaWithOtherCriteriaAllowed() {
+ val query = MangaSearchQuery.builder()
+ .criterion(Include(TAG, setOf(buildTag("tag1"), buildTag("tag2"))))
+ .criterion(Exclude(TAG, setOf(buildTag("tag3"))))
+ .build()
+
+ assertDoesNotThrow { capabilities.validate(query) }
+ }
+
+ @Test
+ fun validateMultipleCriteriaWithStrictCapabilityThrowsException() {
+ val query = MangaSearchQuery.builder()
+ .criterion(Match(TITLE_NAME, "title"))
+ .criterion(Range(PUBLICATION_YEAR, 1990, 2000))
+ .build()
+
+ assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
+ }
+
+ private fun buildTag(name: String) = MangaTag(title = name, key = "${name}Key", source = MangaParserSource.DUMMY)
+}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt
new file mode 100644
index 000000000..d43db48e2
--- /dev/null
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt
@@ -0,0 +1,77 @@
+package org.koitharu.kotatsu.parsers.util
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.ContentType.MANGA
+import org.koitharu.kotatsu.parsers.model.ContentType.MANHUA
+import org.koitharu.kotatsu.parsers.model.Demographic.SEINEN
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+import java.util.*
+
+class ListFilterToSearchQueryConverterTest {
+
+ @Test
+ fun convertToMangaSearchQueryTest() {
+ val tags = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
+ val excludedTags = setOf(buildMangaTag("exclude_tag"))
+ val states = setOf(MangaState.ONGOING)
+ val contentRatings = setOf(ContentRating.SAFE)
+ val contentTypes = setOf(MANGA, MANHUA)
+ val demographics = setOf(SEINEN)
+
+ val filter = MangaListFilter(
+ query = "title_name",
+ tags = tags,
+ tagsExclude = excludedTags,
+ locale = Locale.ENGLISH,
+ originalLocale = Locale.JAPANESE,
+ states = states,
+ contentRating = contentRatings,
+ types = contentTypes,
+ demographics = demographics,
+ year = 2020,
+ yearFrom = 1997,
+ yearTo = 2024
+ )
+
+ val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter)
+
+ val expectedQuery = MangaSearchQuery.builder()
+ .offset(0)
+ .order(SortOrder.NEWEST)
+ .criterion(Match(TITLE_NAME, "title_name"))
+ .criterion(Include(TAG, tags))
+ .criterion(Exclude(TAG, excludedTags))
+ .criterion(Include(LANGUAGE, setOf(Locale.ENGLISH)))
+ .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.JAPANESE)))
+ .criterion(Include(STATE, states))
+ .criterion(Include(CONTENT_RATING, contentRatings))
+ .criterion(Include(CONTENT_TYPE, contentTypes))
+ .criterion(Include(DEMOGRAPHIC, demographics))
+ .criterion(Range(PUBLICATION_YEAR, 1997, 2024))
+ .criterion(Match(PUBLICATION_YEAR, 2020))
+ .build()
+
+ assertEquals(expectedQuery, searchQuery)
+ }
+
+ @Test
+ fun convertToMangaSearchQueryWithEmptyFieldsTest() {
+ val filter = MangaListFilter()
+
+ val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter)
+
+ assertEquals(MangaSearchQuery.builder().offset(0).order(SortOrder.NEWEST).build(), searchQuery)
+ }
+
+ private fun buildMangaTag(name: String): MangaTag {
+ return MangaTag(
+ key = "${name}Key",
+ title = name,
+ source = MangaParserSource.DUMMY
+ )
+ }
+}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt
new file mode 100644
index 000000000..1d7dfa7a5
--- /dev/null
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt
@@ -0,0 +1,90 @@
+package org.koitharu.kotatsu.parsers.util
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.ContentType.*
+import org.koitharu.kotatsu.parsers.model.Demographic.SEINEN
+import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+import java.util.*
+
+class ConvertToMangaListFilterTest {
+
+ @Test
+ fun convertToMangaListFilterTest() {
+ val tags = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
+ val excludedTags = setOf(buildMangaTag("exclude_tag"))
+ val states = setOf(MangaState.ONGOING)
+ val contentRatings = setOf(ContentRating.SAFE)
+ val contentTypes = setOf(MANGA, MANHUA)
+ val demographics = setOf(SEINEN)
+
+ val query = MangaSearchQuery.builder()
+ .criterion(Match(TITLE_NAME, "title_name"))
+ .criterion(Include(TAG, tags))
+ .criterion(Exclude(TAG, excludedTags))
+ .criterion(Include(LANGUAGE, setOf(Locale.ENGLISH)))
+ .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.JAPANESE)))
+ .criterion(Include(STATE, states))
+ .criterion(Include(CONTENT_RATING, contentRatings))
+ .criterion(Include(CONTENT_TYPE, contentTypes))
+ .criterion(Include(DEMOGRAPHIC, demographics))
+ .criterion(Range(PUBLICATION_YEAR, 1997, 2024))
+ .criterion(Match(PUBLICATION_YEAR, 2020))
+ .build()
+
+ val listFilter = convertToMangaListFilter(query)
+
+ assertEquals(listFilter.query, "title_name")
+ assertEquals(listFilter.tags, tags)
+ assertEquals(listFilter.tagsExclude, excludedTags)
+ assertEquals(listFilter.locale, Locale.ENGLISH)
+ assertEquals(listFilter.originalLocale, Locale.JAPANESE)
+ assertEquals(listFilter.states, states)
+ assertEquals(listFilter.contentRating, contentRatings)
+ assertEquals(listFilter.types, contentTypes)
+ assertEquals(listFilter.demographics, demographics)
+ assertEquals(listFilter.year, 2020)
+ assertEquals(listFilter.yearFrom, 1997)
+ assertEquals(listFilter.yearTo, 2024)
+ }
+
+ @Test
+ fun convertToMangaListFilterWithMultipleTagsIncludeTest() {
+ val tags1 = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
+ val tags2 = setOf(buildMangaTag("tag3"), buildMangaTag("tag4"))
+
+ val query = MangaSearchQuery.builder()
+ .criterion(Include(TAG, tags1))
+ .criterion(Include(TAG, tags2))
+ .build()
+
+ val listFilter = convertToMangaListFilter(query)
+
+ assertEquals(listFilter.tags, tags1 union tags2)
+ }
+
+ @Test
+ fun convertToMangaListFilterWithUnsupportedFieldTest() {
+ val query = MangaSearchQuery.builder()
+ .criterion(Include(AUTHOR, setOf(buildMangaTag("author"))))
+ .build()
+
+ val exception = assertThrows {
+ convertToMangaListFilter(query)
+ }
+
+ assert(exception.message!!.contains("Unsupported field for Include criterion: AUTHOR"))
+ }
+
+ private fun buildMangaTag(name: String): MangaTag {
+ return MangaTag(
+ key = "${name}Key",
+ title = name,
+ source = MangaParserSource.DUMMY,
+ )
+ }
+}
From f107e1152859501bc432342d3bc9e86ce475860b Mon Sep 17 00:00:00 2001
From: Koitharu
Date: Mon, 24 Feb 2025 17:14:27 +0200
Subject: [PATCH 2/4] Refactoring
---
.../koitharu/kotatsu/parsers/MangaParser.kt | 26 ++-
.../kotatsu/parsers/PagedMangaParser.kt | 27 ++--
.../kotatsu/parsers/SinglePageMangaParser.kt | 8 +-
.../kotatsu/parsers/model/MangaListFilter.kt | 46 +++---
.../parsers/model/search/MangaSearchQuery.kt | 110 ++++---------
.../search/MangaSearchQueryCapabilities.kt | 80 ++--------
.../parsers/model/search/QueryCriteria.kt | 72 ++++-----
.../parsers/model/search/SearchableField.kt | 2 +-
.../parsers/site/all/HitomiLaParser.kt | 88 +++++------
.../parsers/site/all/MangaDexParser.kt | 103 +++++++++---
.../kotatsu/parsers/site/en/WeebCentral.kt | 42 ++---
.../parsers/site/mangabox/MangaboxParser.kt | 52 ++++--
.../parsers/site/mangabox/en/Mangairo.kt | 39 +++--
.../parsers/site/mangabox/en/Mangakakalot.kt | 34 +++-
.../site/mangabox/en/MangakakalotTv.kt | 33 +++-
.../parsers/util/SearchQueryConverter.kt | 148 ++++++++++++++----
.../kotatsu/parsers/MangaParserTest.kt | 34 ++--
.../model/search/MangaSearchQueryTest.kt | 16 +-
.../ListFilterToSearchQueryConverterTest.kt | 8 +-
.../SearchQueryToListFilterConverterTest.kt | 18 ++-
20 files changed, 554 insertions(+), 432 deletions(-)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
index 3621d202e..c11a99d90 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
@@ -28,7 +28,7 @@ public abstract class MangaParser @InternalParsersApi constructor(
public abstract val filterCapabilities: MangaListFilterCapabilities
public open val searchQueryCapabilities: MangaSearchQueryCapabilities
- get() = MangaSearchQueryCapabilities.from(filterCapabilities)
+ get() = filterCapabilities.toMangaSearchQueryCapabilities()
public val config: MangaSourceConfig by lazy { context.getConfig(source) }
@@ -68,26 +68,24 @@ public abstract class MangaParser @InternalParsersApi constructor(
*
* @param searchQuery searchQuery
*/
- public suspend fun searchManga(searchQuery: MangaSearchQuery, validateQuery: Boolean = true): List {
- if (validateQuery) {
+ public suspend fun queryManga(searchQuery: MangaSearchQuery): List {
+ if (!searchQuery.skipValidation) {
searchQueryCapabilities.validate(searchQuery)
}
- return validatedMangaSearch(searchQuery)
+ return getList(searchQuery)
}
/**
* Search list of manga by specified searchQuery
*
- * @param searchQuery searchQuery
+ * @param query searchQuery
*/
- protected open suspend fun validatedMangaSearch(searchQuery: MangaSearchQuery): List {
- return getList(
- searchQuery.offset ?: 0,
- searchQuery.order ?: defaultSortOrder,
- convertToMangaListFilter(searchQuery),
- )
- }
+ protected open suspend fun getList(query: MangaSearchQuery): List = getList(
+ offset = query.offset,
+ order = query.order ?: defaultSortOrder,
+ filter = convertToMangaListFilter(query),
+ )
/**
* Parse list of manga by specified criteria
@@ -97,9 +95,9 @@ public abstract class MangaParser @InternalParsersApi constructor(
* @param order one of [availableSortOrders] or [defaultSortOrder] for default value
* @param filter is a set of filter rules
*
- * @deprecated New [searchManga] should be preferred.
+ * @deprecated New [getList] should be preferred.
*/
- @Deprecated("New searchManga method should be preferred")
+ @Deprecated("New getList(query: MangaSearchQuery) method should be preferred")
public abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List
/**
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
index 231792072..e80a07cc8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
@@ -24,9 +24,9 @@ public abstract class PagedMangaParser(
@JvmField
protected val searchPaginator: Paginator = Paginator(searchPageSize)
- final override suspend fun validatedMangaSearch(searchQuery: MangaSearchQuery): List {
+ final override suspend fun getList(query: MangaSearchQuery): List {
var containTitleNameCriteria = false
- searchQuery.criteria.forEach {
+ query.criteria.forEach {
if (it.field == SearchableField.TITLE_NAME) {
containTitleNameCriteria = true
}
@@ -38,15 +38,15 @@ public abstract class PagedMangaParser(
} else {
searchPaginator
},
- searchQuery = searchQuery,
+ query = query,
)
}
- public open suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
- return getList(
- searchQuery.offset ?: 0,
- searchQuery.order ?: defaultSortOrder,
- convertToMangaListFilter(searchQuery),
+ public open suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
+ return getListPage(
+ page = page,
+ order = query.order ?: defaultSortOrder,
+ filter = convertToMangaListFilter(query),
)
}
@@ -81,16 +81,11 @@ public abstract class PagedMangaParser(
private suspend fun searchManga(
paginator: Paginator,
- searchQuery: MangaSearchQuery,
+ query: MangaSearchQuery,
): List {
- val offset: Int = searchQuery.offset ?: 0
+ val offset: Int = query.offset
val page = paginator.getPage(offset)
- val list = searchPageManga(
- MangaSearchQuery.builder()
- .copy(searchQuery)
- .offset(page)
- .build(),
- )
+ val list = getListPage(query, page)
paginator.onListReceived(offset, page, list.size)
return list
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
index a4e32f5e7..406f2181c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
@@ -14,16 +14,16 @@ public abstract class SinglePageMangaParser(
) : MangaParser(context, source) {
- final override suspend fun validatedMangaSearch(searchQuery: MangaSearchQuery): List {
- if (searchQuery.offset != null && searchQuery.offset > 0) {
+ final override suspend fun getList(query: MangaSearchQuery): List {
+ if (query.offset > 0) {
return emptyList()
}
- return searchSinglePageManga(searchQuery)
+ return searchSinglePageManga(query)
}
public open suspend fun searchSinglePageManga(searchQuery: MangaSearchQuery): List {
return getList(
- searchQuery.offset ?: 0,
+ searchQuery.offset,
searchQuery.order ?: defaultSortOrder,
convertToMangaListFilter(searchQuery),
)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
index febf96eed..771f22874 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
@@ -44,7 +44,7 @@ public data class MangaListFilter(
public val EMPTY: MangaListFilter = MangaListFilter()
}
- public class Builder {
+ internal class Builder {
private var query: String? = null
private val tags: MutableSet = mutableSetOf()
private val tagsExclude: MutableSet = mutableSetOf()
@@ -58,28 +58,32 @@ public data class MangaListFilter(
private var yearFrom: Int = YEAR_UNKNOWN
private var yearTo: Int = YEAR_UNKNOWN
- public fun query(query: String?): Builder = apply { this.query = query }
- public fun addTag(tag: MangaTag): Builder = apply { tags.add(tag) }
- public fun addTags(tags: Collection): Builder = apply { this.tags.addAll(tags) }
- public fun excludeTag(tag: MangaTag): Builder = apply { tagsExclude.add(tag) }
- public fun excludeTags(tags: Collection): Builder = apply { this.tagsExclude.addAll(tags) }
- public fun locale(locale: Locale?): Builder = apply { this.locale = locale }
- public fun originalLocale(locale: Locale?): Builder = apply { this.originalLocale = locale }
- public fun addState(state: MangaState): Builder = apply { states.add(state) }
- public fun addStates(states: Collection): Builder = apply { this.states.addAll(states) }
- public fun addContentRating(rating: ContentRating): Builder = apply { contentRating.add(rating) }
- public fun addContentRatings(ratings: Collection): Builder = apply { this.contentRating.addAll(ratings) }
- public fun addType(type: ContentType): Builder = apply { types.add(type) }
- public fun addTypes(types: Collection): Builder = apply { this.types.addAll(types) }
- public fun addDemographic(demographic: Demographic): Builder = apply { demographics.add(demographic) }
- public fun addDemographics(demographics: Collection): Builder = apply { this.demographics.addAll(demographics) }
- public fun year(year: Int): Builder = apply { this.year = year }
- public fun yearFrom(year: Int): Builder = apply { this.yearFrom = year }
- public fun yearTo(year: Int): Builder = apply { this.yearTo = year }
+ fun query(query: String?): Builder = apply { this.query = query }
+ fun addTag(tag: MangaTag): Builder = apply { tags.add(tag) }
+ fun addTags(tags: Collection): Builder = apply { this.tags.addAll(tags) }
+ fun excludeTag(tag: MangaTag): Builder = apply { tagsExclude.add(tag) }
+ fun excludeTags(tags: Collection): Builder = apply { this.tagsExclude.addAll(tags) }
+ fun locale(locale: Locale?): Builder = apply { this.locale = locale }
+ fun originalLocale(locale: Locale?): Builder = apply { this.originalLocale = locale }
+ fun addState(state: MangaState): Builder = apply { states.add(state) }
+ fun addStates(states: Collection): Builder = apply { this.states.addAll(states) }
+ fun addContentRating(rating: ContentRating): Builder = apply { contentRating.add(rating) }
+ fun addContentRatings(ratings: Collection): Builder =
+ apply { this.contentRating.addAll(ratings) }
- public fun build(): MangaListFilter = MangaListFilter(
+ fun addType(type: ContentType): Builder = apply { types.add(type) }
+ fun addTypes(types: Collection): Builder = apply { this.types.addAll(types) }
+ fun addDemographic(demographic: Demographic): Builder = apply { demographics.add(demographic) }
+ fun addDemographics(demographics: Collection): Builder =
+ apply { this.demographics.addAll(demographics) }
+
+ fun year(year: Int): Builder = apply { this.year = year }
+ fun yearFrom(year: Int): Builder = apply { this.yearFrom = year }
+ fun yearTo(year: Int): Builder = apply { this.yearTo = year }
+
+ fun build(): MangaListFilter = MangaListFilter(
query, tags, tagsExclude, locale, originalLocale, states,
- contentRating, types, demographics, year, yearFrom, yearTo
+ contentRating, types, demographics, year, yearFrom, yearTo,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
index dde23d109..628b13f39 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.parsers.model.search
+import androidx.collection.ArrayMap
+import androidx.collection.ArraySet
import org.koitharu.kotatsu.parsers.model.SortOrder
/**
@@ -10,98 +12,48 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
* @property order The sorting order for the results (optional).
* @property offset The offset number for paginated search results (optional).
*/
-public class MangaSearchQuery private constructor(
- @JvmField public val criteria: Set> = emptySet(),
- @JvmField public val order: SortOrder? = null,
- @JvmField public val offset: Int? = null,
-) {
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is MangaSearchQuery) return false
- return criteria == other.criteria &&
- order == other.order &&
- offset == other.offset
- }
+@ConsistentCopyVisibility
+public data class MangaSearchQuery private constructor(
+ @JvmField public val criteria: Set>,
+ @JvmField public val order: SortOrder?,
+ @JvmField public val offset: Int,
+ @JvmField public val skipValidation: Boolean,
+) {
- override fun hashCode(): Int {
- var result = criteria.hashCode()
- result = 31 * result + (order?.hashCode() ?: 0)
- result = 31 * result + (offset ?: 0)
- return result
- }
-
- public companion object {
- public fun builder(): Builder = Builder()
- }
+ public fun newBuilder(): Builder = Builder(this)
public class Builder {
- private var criteria: MutableSet> = mutableSetOf()
+
+ private val criteria = ArraySet>()
private var order: SortOrder? = null
- private var offset: Int? = null
+ private var offset: Int = 0
+ private var skipValidation: Boolean = false
- public fun copy(searchQuery: MangaSearchQuery): Builder = apply {
- this.criteria = searchQuery.criteria as MutableSet>
- this.order = searchQuery.order
- this.offset = searchQuery.offset
- }
+ public constructor()
- @Throws(IllegalArgumentException::class)
- public fun criterion(criterion: QueryCriteria<*>): Builder = apply {
- validateCriterion(criterion)
- this.criteria.add(criterion)
+ public constructor(query: MangaSearchQuery) : this() {
+ criteria.addAll(query.criteria)
+ order = query.order
+ offset = query.offset
}
+ public fun criterion(criterion: QueryCriteria<*>): Builder = apply { criteria.add(criterion) }
+
public fun order(order: SortOrder?): Builder = apply { this.order = order }
- public fun offset(offset: Int?): Builder = apply { this.offset = offset }
+ public fun offset(offset: Int): Builder = apply { this.offset = offset }
+
+ public fun skipValidation(skip: Boolean): Builder = apply { this.skipValidation = skip }
@Throws(IllegalArgumentException::class)
public fun build(): MangaSearchQuery {
- return MangaSearchQuery(deduplicateCriteria(criteria), order, offset)
- }
-
- /**
- * Validates the provided [QueryCriteria] to ensure type correctness.
- *
- * @param criterion The search criterion to validate.
- * @throws IllegalArgumentException If the criterion type does not match the expected type.
- */
- private fun validateCriterion(criterion: QueryCriteria<*>) {
- try {
- val expectedType = criterion.field.type
- val actualType: Class<*>? = when (criterion) {
- is QueryCriteria.Include<*> -> criterion.values.first().javaClass
- is QueryCriteria.Exclude<*> -> criterion.values.first().javaClass
- is QueryCriteria.Match<*> -> criterion.value.javaClass
- is QueryCriteria.Range<*> -> {
- if (criterion.from.javaClass != criterion.to.javaClass) {
- throw IllegalArgumentException(
- "Mismatched types for field '${criterion.field}'. 'from' and 'to' should have same types"
- )
- }
- criterion.from.javaClass
- }
- }
-
- val isCompatibleIntType = (expectedType == Int::class.java && actualType == Integer::class.java) ||
- (expectedType == Integer::class.java && actualType == Int::class.java)
-
- if (actualType != null && !expectedType.isAssignableFrom(actualType) && !isCompatibleIntType) {
- throw IllegalArgumentException(
- "Invalid type for ${criterion.field}. Expected: ${expectedType.simpleName}, but got: ${actualType.simpleName}"
- )
- }
- } catch (e: NoSuchElementException) {
- throw IllegalArgumentException(
- "QueryCriteria values should not be empty"
- )
- }
+ return MangaSearchQuery(deduplicateCriteria(criteria), order, offset, skipValidation)
}
private fun deduplicateCriteria(criteria: Set>): Set> {
- val uniqueCriteria = mutableMapOf>>, QueryCriteria<*>>()
+ val uniqueCriteria =
+ ArrayMap>>, QueryCriteria<*>>(criteria.size)
for (criterion in criteria) {
val key = criterion.field to criterion::class.java
@@ -111,15 +63,17 @@ public class MangaSearchQuery private constructor(
existing == null -> uniqueCriteria[key] = criterion
existing is QueryCriteria.Include<*> && criterion is QueryCriteria.Include<*> -> {
- uniqueCriteria[key] = QueryCriteria.Include(criterion.field,existing.values union criterion.values)
+ uniqueCriteria[key] =
+ QueryCriteria.Include(criterion.field, existing.values union criterion.values)
}
existing is QueryCriteria.Exclude<*> && criterion is QueryCriteria.Exclude<*> -> {
- uniqueCriteria[key] = QueryCriteria.Exclude(criterion.field,existing.values union criterion.values)
+ uniqueCriteria[key] =
+ QueryCriteria.Exclude(criterion.field, existing.values union criterion.values)
}
else -> throw IllegalArgumentException(
- "Match and Range have only one criterion per type, but found duplicates for: ${criterion.field} in ${criterion::class.simpleName}"
+ "Match and Range have only one criterion per type, but found duplicates for: ${criterion.field} in ${criterion::class.simpleName}",
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
index b7152b397..8295d18b6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
@@ -1,81 +1,25 @@
package org.koitharu.kotatsu.parsers.model.search
+import androidx.collection.ArraySet
import org.koitharu.kotatsu.parsers.InternalParsersApi
-import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
-import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+import org.koitharu.kotatsu.parsers.util.mapToSet
-public data class MangaSearchQueryCapabilities(
- val capabilities: Set = emptySet(),
+@ExposedCopyVisibility
+public data class MangaSearchQueryCapabilities internal constructor(
+ val capabilities: Set,
) {
- public companion object {
- @InternalParsersApi
- public fun from(filterCapabilities: MangaListFilterCapabilities): MangaSearchQueryCapabilities {
- return MangaSearchQueryCapabilities(
- capabilities = setOfNotNull(
- filterCapabilities.isMultipleTagsSupported.takeIf { it }?.let {
- SearchCapability(
- field = TAG, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
- )
- },
- filterCapabilities.isTagsExclusionSupported.takeIf { it }?.let {
- SearchCapability(
- field = TAG, criteriaTypes = setOf(Exclude::class), multiValue = true, otherCriteria = true
- )
- },
- filterCapabilities.isSearchSupported.takeIf { it }?.let {
- SearchCapability(
- field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = false
- )
- },
- filterCapabilities.isSearchWithFiltersSupported.takeIf { it }?.let {
- SearchCapability(
- field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true
- )
- },
- filterCapabilities.isYearSupported.takeIf { it }?.let {
- SearchCapability(
- field = PUBLICATION_YEAR, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true
- )
- },
- filterCapabilities.isYearRangeSupported.takeIf { it }?.let {
- SearchCapability(
- field = PUBLICATION_YEAR, criteriaTypes = setOf(Range::class), multiValue = false, otherCriteria = true
- )
- },
- filterCapabilities.isOriginalLocaleSupported.takeIf { it }?.let {
- SearchCapability(
- field = ORIGINAL_LANGUAGE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
- )
- },
- SearchCapability(
- field = LANGUAGE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
- ),
- SearchCapability(
- field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
- ),
- SearchCapability(
- field = CONTENT_TYPE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
- ),
- SearchCapability(
- field = CONTENT_RATING, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
- ),
- SearchCapability(
- field = DEMOGRAPHIC, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true
- ),
- ),
- )
- }
- }
+
+ public constructor(vararg capabilities: SearchCapability) : this(ArraySet(capabilities))
@InternalParsersApi
public fun validate(query: MangaSearchQuery) {
- val strictFields = capabilities.filter { !it.otherCriteria }.map { it.field }.toSet()
- val usedStrictFields = query.criteria.map { it.field }.toSet().intersect(strictFields)
+ val strictFields = capabilities.filter { !it.otherCriteria }.mapToSet { it.field }
+ val usedStrictFields = query.criteria.mapToSet { it.field }.intersect(strictFields)
if (usedStrictFields.isNotEmpty() && query.criteria.size > 1) {
throw IllegalArgumentException(
- "Query contains multiple criteria, but at least one field (${usedStrictFields.joinToString()}) does not support multiple criteria."
+ "Query contains multiple criteria, but at least one field (${usedStrictFields.joinToString()}) does not support multiple criteria.",
)
}
@@ -85,7 +29,7 @@ public data class MangaSearchQueryCapabilities(
if (criterion::class !in capability.criteriaTypes) {
throw IllegalArgumentException(
- "Unsupported search criterion: ${criterion::class.simpleName} for field ${criterion.field}"
+ "Unsupported search criterion: ${criterion::class.simpleName} for field ${criterion.field}",
)
}
@@ -94,8 +38,10 @@ public data class MangaSearchQueryCapabilities(
when (criterion) {
is Include<*> -> if (criterion.values.size > 1)
throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}")
+
is Exclude<*> -> if (criterion.values.size > 1)
throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}")
+
is Range<*> -> {} // Range is always valid (from, to)
is Match<*> -> {} // Match always has a single value
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt
index 8294844db..ffaf57595 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt
@@ -7,57 +7,53 @@ package org.koitharu.kotatsu.parsers.model.search
* @param T The type of value associated with the search criterion.
* @property field The field to which this search criterion applies.
*/
-public sealed class QueryCriteria(
- @JvmField public val field: SearchableField
-) {
- public class Include(field: SearchableField, @JvmField public val values: Set) : QueryCriteria(field) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Include<*>) return false
- return field == other.field && values == other.values
- }
+public sealed interface QueryCriteria {
+
+ public val field: SearchableField
+
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
- override fun hashCode(): Int {
- return 31 * field.hashCode() + values.hashCode()
+ public data class Include(
+ public override val field: SearchableField,
+ @JvmField public val values: Set,
+ ) : QueryCriteria {
+
+ init {
+ check(values.all { x -> field.type.isInstance(x) })
}
}
- public class Exclude(field: SearchableField, @JvmField public val values: Set) : QueryCriteria(field) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Exclude<*>) return false
- return field == other.field && values == other.values
- }
+ public data class Exclude(
+ public override val field: SearchableField,
+ @JvmField public val values: Set,
+ ) : QueryCriteria {
- override fun hashCode(): Int {
- return 31 * field.hashCode() + values.hashCode()
+ init {
+ check(values.all { x -> field.type.isInstance(x) })
}
}
- public class Range>(field: SearchableField, @JvmField public val from: T, @JvmField public val to: T) : QueryCriteria(field) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Range<*>) return false
- return field == other.field && from == other.from && to == other.to
- }
+ public data class Range>(
+ public override val field: SearchableField,
+ @JvmField public val from: T,
+ @JvmField public val to: T,
+ ) : QueryCriteria {
- override fun hashCode(): Int {
- var result = field.hashCode()
- result = 31 * result + from.hashCode()
- result = 31 * result + to.hashCode()
- return result
+ init {
+ check(field.type.isInstance(from))
+ check(field.type.isInstance(to))
}
}
- public class Match(field: SearchableField, @JvmField public val value: T) : QueryCriteria(field) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Match<*>) return false
- return field == other.field && value == other.value
- }
+ public data class Match(
+ public override val field: SearchableField,
+ @JvmField public val value: T,
+ ) : QueryCriteria {
- override fun hashCode(): Int {
- return 31 * field.hashCode() + value.hashCode()
+ init {
+ check(field.type.isInstance(value))
}
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt
index e906713a8..d14e5c165 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.model.search
import org.koitharu.kotatsu.parsers.model.*
-import java.util.Locale
+import java.util.*
/**
* Represents the various fields that can be used for searching manga.
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
index 6d5b41934..24dd603e4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
@@ -516,14 +516,14 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
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,
@@ -546,37 +546,37 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
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)
-
- "https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
- },
+ 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"
+ },
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),
@@ -600,15 +600,15 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
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)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
index 99b5de223..1305e3840 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
@@ -11,11 +11,13 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
-import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
-import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchCapability
+import org.koitharu.kotatsu.parsers.model.search.SearchableField
import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.*
@@ -79,17 +81,65 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
- capabilities = setOf(
- SearchCapability(field = TAG, criteriaTypes = setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true),
- SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = AUTHOR, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = CONTENT_TYPE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = CONTENT_RATING, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = DEMOGRAPHIC, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = ORIGINAL_LANGUAGE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = LANGUAGE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = PUBLICATION_YEAR, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true),
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class, Exclude::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = AUTHOR,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = CONTENT_TYPE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = CONTENT_RATING,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = DEMOGRAPHIC,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = ORIGINAL_LANGUAGE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = LANGUAGE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = PUBLICATION_YEAR,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
),
)
@@ -127,7 +177,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
ORIGINAL_LANGUAGE -> "originalLanguage[]"
LANGUAGE -> "availableTranslatedLanguage[]"
PUBLICATION_YEAR -> "year"
- else -> ""
}
private fun Any?.toQueryParam(): String = when (this) {
@@ -141,13 +190,14 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
MangaState.PAUSED -> "hiatus"
else -> ""
}
+
is ContentRating -> when (this) {
ContentRating.SAFE -> "safe"
// quick fix for double value
ContentRating.SUGGESTIVE -> "suggestive&contentRating[]=erotica"
ContentRating.ADULT -> "pornographic"
- else -> ""
}
+
is Demographic -> when (this) {
Demographic.SHOUNEN -> "shounen"
Demographic.SHOUJO -> "shoujo"
@@ -156,6 +206,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
Demographic.NONE -> "none"
else -> ""
}
+
is SortOrder -> when (this) {
SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
@@ -172,6 +223,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
SortOrder.RELEVANCE -> "&order[relevance]=desc"
else -> "[latestUploadedChapter]=desc"
}
+
else -> this.toString().urlEncoded()
}
@@ -183,14 +235,14 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
}
}
- override suspend fun validatedMangaSearch(searchQuery: MangaSearchQuery): List {
+ override suspend fun getList(query: MangaSearchQuery): List {
val url = buildString {
- append("https://api.$domain/manga?limit=$PAGE_SIZE&offset=${searchQuery.offset ?: 0}")
+ append("https://api.$domain/manga?limit=$PAGE_SIZE&offset=${query.offset}")
.append("&includes[]=cover_art&includes[]=author&includes[]=artist&includedTagsMode=AND&excludedTagsMode=OR")
var hasContentRating = false
- searchQuery.criteria.forEach { criterion ->
+ query.criteria.forEach { criterion ->
when (criterion) {
is Include<*> -> {
if (criterion.field == CONTENT_RATING) {
@@ -198,12 +250,15 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
}
criterion.values.forEach { appendCriterion(criterion.field, it) }
}
+
is Exclude<*> -> {
criterion.values.forEach { appendCriterion(criterion.field, it, "excludedTags[]") }
}
+
is Match<*> -> {
appendCriterion(criterion.field, criterion.value)
}
+
else -> {
// Not supported
}
@@ -216,7 +271,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
}
append("&order")
- append((searchQuery.order ?: defaultSortOrder).toQueryParam())
+ append((query.order ?: defaultSortOrder).toQueryParam())
}
val json = webClient.httpGet(url).parseJson().getJSONArray("data")
@@ -224,7 +279,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
}
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
- return searchManga(convertToMangaSearchQuery(offset, order, filter))
+ return queryManga(convertToMangaSearchQuery(offset, order, filter))
}
override suspend fun getDetails(manga: Manga): Manga {
@@ -304,15 +359,14 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
"https://uploads.$domain/covers/$id/$it"
}
val authors: Set = (relations["author"] ?: relations["artist"])
- ?.mapNotNull {
+ ?.mapNotNullToSet {
val key = it.getStringOrNull("id")
val title = it.getJSONObject("attributes")?.getStringOrNull("name")
key?.let { k ->
title?.let { t -> MangaTag(key = k, title = t, source = source) }
}
- }
- ?.toSet() ?: emptySet()
+ }.orEmpty()
return Manga(
id = generateUid(id),
@@ -452,7 +506,8 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
val locale = attrs.getStringOrNull("translatedLanguage")?.let { Locale.forLanguageTag(it) }
val lc = locale?.getDisplayName(locale)?.toTitleCase(locale)
val relations = jo.getJSONArray("relationships").associateByKey("type")
- val team = relations["scanlation_group"]?.firstOrNull()?.optJSONObject("attributes")?.getStringOrNull("name")
+ val team =
+ relations["scanlation_group"]?.firstOrNull()?.optJSONObject("attributes")?.getStringOrNull("name")
val branch = (list.indices).firstNotNullOf { i ->
val b = if (i == 0) lc else "$lc ($i)"
if (branchedChapters[b]?.get(volume to number) == null) b else null
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
index 26a8dcb39..d354fa555 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
@@ -64,27 +64,27 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
MangaTag(
title = it.selectFirstOrThrow(".label-text").text(),
key = it.selectFirstOrThrow("input[id$=value]").attr("value"),
- source = source
+ source = source,
)
}
val states = EnumSet.of(
- ONGOING, FINISHED, ABANDONED, PAUSED
+ ONGOING, FINISHED, ABANDONED, PAUSED,
)
val types = EnumSet.of(
- MANGA, MANHWA, MANHUA, COMICS
+ MANGA, MANHWA, MANHUA, COMICS,
)
val rating = EnumSet.of(
- SAFE, SUGGESTIVE
+ SAFE, SUGGESTIVE,
)
return MangaListFilterOptions(
availableTags = tags,
availableStates = states,
availableContentTypes = types,
- availableContentRating = rating
+ availableContentRating = rating,
)
}
@@ -109,7 +109,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
ADDED, ADDED_ASC -> "Recently Added"
UPDATED, UPDATED_ASC -> "Latest Updates"
else -> throw UnsupportedOperationException("unsupported order: $order")
- }
+ },
)
addQueryParameter(
name = "order",
@@ -117,11 +117,11 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
RELEVANCE, ALPHABETICAL, POPULARITY_ASC, RATING_ASC, ADDED_ASC, UPDATED_ASC -> "Ascending"
ALPHABETICAL_DESC, POPULARITY, RATING, ADDED, UPDATED -> "Descending"
else -> throw UnsupportedOperationException("unsupported order: $order")
- }
+ },
)
addQueryParameter("official", "Any")
addQueryParameter("anime", "Any")
- with (filter.contentRating) {
+ with(filter.contentRating) {
addQueryParameter(
name = "adult",
value = when {
@@ -130,7 +130,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
SAFE in this -> "False"
SUGGESTIVE in this -> "True"
else -> throw UnsupportedOperationException("unsupported content rating: $this")
- }
+ },
)
}
filter.states.forEach { state ->
@@ -142,7 +142,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
ABANDONED -> "Canceled"
PAUSED -> "Hiatus"
else -> throw UnsupportedOperationException("unsupported state: $state")
- }
+ },
)
}
filter.types.forEach { type ->
@@ -154,7 +154,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
MANHUA -> "Manhua"
COMICS -> "OEL"
else -> throw UnsupportedOperationException("unsupported type: $type")
- }
+ },
)
}
filter.tags.forEach { tag ->
@@ -193,11 +193,11 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
MangaTag(
title = it,
key = it,
- source = source
+ source = source,
)
}
.orEmpty(),
- state = when(document.selectFirst("div:contains(status) span")?.text()) {
+ state = when (document.selectFirst("div:contains(status) span")?.text()) {
"Ongoing" -> ONGOING
"Complete" -> FINISHED
"Canceled" -> ABANDONED
@@ -207,7 +207,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
author = document.select("div:contains(author) a").eachText().joinToString(),
largeCoverUrl = null,
chapters = null,
- source = source
+ source = source,
)
}
}
@@ -237,7 +237,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
MangaTag(
title = it.text(),
key = it.text(),
- source = source
+ source = source,
)
},
state = when (sectionLeft.selectFirst("ul > li:has(strong:contains(Status)) > a")?.text()) {
@@ -259,12 +259,12 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
abbr.selectFirst("a")?.attr("href")?.let { url ->
val a = Element("a")
.text(
- abbr.attr("title")
+ abbr.attr("title"),
)
.attr("href", url)
ul.appendChild(
- Element("li").appendChild(a)
+ Element("li").appendChild(a),
)
}
}
@@ -276,7 +276,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
}.outerHtml(),
chapters = chapters.await(),
- source = source
+ source = source,
)
}
@@ -308,10 +308,10 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
else -> null
},
uploadDate = dateFormat.tryParse(
- element.selectFirst("time[datetime]")?.attr("datetime")
+ element.selectFirst("time[datetime]")?.attr("datetime"),
),
branch = null,
- source = source
+ source = source,
)
}
}
@@ -336,7 +336,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
id = generateUid(pageUrl),
url = pageUrl,
preview = null,
- source = source
+ source = source,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
index e74d5e604..d5ad87536 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
@@ -46,11 +46,29 @@ internal abstract class MangaboxParser(
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
- capabilities = setOf(
- SearchCapability(field = TAG, criteriaTypes = setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = true),
- SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true),
- SearchCapability(field = AUTHOR, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = false),
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class, Exclude::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = AUTHOR,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = false,
),
)
@@ -94,12 +112,14 @@ internal abstract class MangaboxParser(
MangaState.FINISHED -> "completed"
else -> ""
}
+
is SortOrder -> when (this) {
SortOrder.ALPHABETICAL -> "az"
SortOrder.NEWEST -> "newest"
SortOrder.POPULARITY -> "topview"
else -> ""
}
+
else -> this.toString().replace(" ", "_").urlEncoded()
}
@@ -115,31 +135,35 @@ internal abstract class MangaboxParser(
Regex("[^/]+\$", RegexOption.IGNORE_CASE)
}
- override suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ override suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
var authorSearchUrl: String? = null
val url = buildString {
- val pageQueryParameter = "page=${searchQuery.offset ?: 0}"
+ val pageQueryParameter = "page=$page"
append("https://${domain}${listUrl}/?s=all")
- searchQuery.criteria.forEach { criterion ->
+ query.criteria.forEach { criterion ->
when (criterion) {
is Include<*> -> {
if (criterion.field == AUTHOR) {
- criterion.values.firstOrNull()?.toQueryParam()?.takeIf { it.isNotBlank() }?.let { authorKey ->
- authorSearchUrl = "https://${domain}${authorUrl}/${authorKey}/?$pageQueryParameter"
- }
+ criterion.values.firstOrNull()?.toQueryParam()?.takeIf { it.isNotBlank() }
+ ?.let { authorKey ->
+ authorSearchUrl = "https://${domain}${authorUrl}/${authorKey}/?$pageQueryParameter"
+ }
}
criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
append("&$param=${criterion.values.joinToString("_") { it.toQueryParam() }}")
}
}
+
is Exclude<*> -> {
append("&g_e=${criterion.values.joinToString("_") { it.toQueryParam() }}")
}
+
is Match<*> -> {
appendCriterion(criterion.field, criterion.value)
}
+
else -> {
// Not supported
}
@@ -147,7 +171,7 @@ internal abstract class MangaboxParser(
}
append("&${pageQueryParameter}")
- append("&orby=${(searchQuery.order ?: defaultSortOrder).toQueryParam()}")
+ append("&orby=${(query.order ?: defaultSortOrder).toQueryParam()}")
}
val doc = webClient.httpGet(authorSearchUrl ?: url).parseHtml()
@@ -174,7 +198,7 @@ internal abstract class MangaboxParser(
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- return searchManga(convertToMangaSearchQuery(page, order, filter))
+ return queryManga(convertToMangaSearchQuery(page, order, filter))
}
protected open val selectTagMap = "div.panel-genres-list a:not(.genres-select)"
@@ -215,7 +239,7 @@ internal abstract class MangaboxParser(
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "").nullIfEmpty()
val authors = doc.body().select(selectAut).mapToSet {
MangaTag(
- key = it.attribute("href").value.find(authorKeyRegex)?: it.text(),
+ key = it.attribute("href").value.find(authorKeyRegex) ?: it.text(),
title = it.text(),
source = source,
)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
index 895ef4739..7fb735569 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
@@ -6,9 +6,11 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
-import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Include
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Match
+import org.koitharu.kotatsu.parsers.model.search.SearchCapability
import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.*
@@ -44,10 +46,23 @@ internal class Mangairo(context: MangaLoaderContext) :
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
- capabilities = setOf(
- SearchCapability(field = TAG, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
- SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = false),
- SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = false,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
),
)
@@ -59,25 +74,27 @@ internal class Mangairo(context: MangaLoaderContext) :
MangaState.FINISHED -> "completed"
else -> "all"
}
+
is SortOrder -> when (this) {
SortOrder.POPULARITY -> "topview"
SortOrder.UPDATED -> "latest"
SortOrder.NEWEST -> "newest"
else -> "latest"
}
+
else -> this.toString().urlEncoded()
}
- override suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ override suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
var titleSearchUrl: String? = null
var category = "all"
var state = "all"
val url = buildString {
append("https://${domain}${listUrl}")
- append("/type-${(searchQuery.order ?: defaultSortOrder).toQueryParam()}")
+ append("/type-${(query.order ?: defaultSortOrder).toQueryParam()}")
- searchQuery.criteria.forEach { criterion ->
+ query.criteria.forEach { criterion ->
when (criterion) {
is Include<*> -> {
when (criterion.field) {
@@ -86,14 +103,16 @@ internal class Mangairo(context: MangaLoaderContext) :
else -> Unit
}
}
+
is Match<*> -> {
if (criterion.field == TITLE_NAME) {
criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
- "?page=${searchQuery.offset ?: 0}"
+ "?page=${query.offset}"
}
}
}
+
else -> {
// Not supported
}
@@ -101,7 +120,7 @@ internal class Mangairo(context: MangaLoaderContext) :
}
append("/ctg-$category")
append("/state-$state")
- append("/page-${searchQuery.offset ?: 0}")
+ append("/page-$page")
}
val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
index 4ed13a69d..81300ba20 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
@@ -31,10 +31,23 @@ internal class Mangakakalot(context: MangaLoaderContext) :
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
- capabilities = setOf(
- SearchCapability(field = TAG, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
- SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = false),
- SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = false,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
),
)
@@ -53,18 +66,21 @@ internal class Mangakakalot(context: MangaLoaderContext) :
.replace(" ", "_")
.urlEncoded()
}
+
is MangaTag -> key
is MangaState -> when (this) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
else -> "all"
}
+
is SortOrder -> when (this) {
SortOrder.POPULARITY -> "topview"
SortOrder.UPDATED -> "latest"
SortOrder.NEWEST -> "newest"
else -> ""
}
+
else -> this.toString().replace(" ", "_").urlEncoded()
}
@@ -80,19 +96,20 @@ internal class Mangakakalot(context: MangaLoaderContext) :
Regex("[^A-Za-z0-9 ]")
}
- override suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ override suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
var titleSearchUrl: String? = null
val url = buildString {
- val pageQueryParameter = "page=${searchQuery.offset ?: 0}"
+ val pageQueryParameter = "page=$page"
append("https://$domain/?")
- searchQuery.criteria.forEach { criterion ->
+ query.criteria.forEach { criterion ->
when (criterion) {
is Include<*> -> {
criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
append("&$param=${criterion.values.first().toQueryParam()}")
}
}
+
is Match<*> -> {
if (criterion.field == TITLE_NAME) {
criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
@@ -102,6 +119,7 @@ internal class Mangakakalot(context: MangaLoaderContext) :
}
appendCriterion(criterion.field, criterion.value)
}
+
else -> {
// Not supported
}
@@ -109,7 +127,7 @@ internal class Mangakakalot(context: MangaLoaderContext) :
}
append("&$pageQueryParameter")
- append("&type=${(searchQuery.order ?: defaultSortOrder).toQueryParam()}")
+ append("&type=${(query.order ?: defaultSortOrder).toQueryParam()}")
}
val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
index 418f1e290..7cfda5848 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
@@ -35,10 +35,23 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
- capabilities = setOf(
- SearchCapability(field = TAG, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
- SearchCapability(field = TITLE_NAME, criteriaTypes = setOf(Match::class), multiValue = false, otherCriteria = false),
- SearchCapability(field = STATE, criteriaTypes = setOf(Include::class), multiValue = false, otherCriteria = true),
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = false,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
),
)
@@ -56,12 +69,14 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
MangaState.FINISHED -> "completed"
else -> "all"
}
+
is SortOrder -> when (this) {
SortOrder.POPULARITY -> "topview"
SortOrder.UPDATED -> "latest"
SortOrder.NEWEST -> "newest"
else -> ""
}
+
else -> this.toString().urlEncoded()
}
@@ -73,19 +88,20 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
}
}
- override suspend fun searchPageManga(searchQuery: MangaSearchQuery): List {
+ override suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
var titleSearchUrl: String? = null
val url = buildString {
- val pageQueryParameter = "page=${searchQuery.offset ?: 0}"
+ val pageQueryParameter = "page=$page"
append("https://$domain/?")
- searchQuery.criteria.forEach { criterion ->
+ query.criteria.forEach { criterion ->
when (criterion) {
is Include<*> -> {
criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
append("&$param=${criterion.values.first().toQueryParam()}")
}
}
+
is Match<*> -> {
if (criterion.field == TITLE_NAME) {
criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
@@ -95,6 +111,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
}
appendCriterion(criterion.field, criterion.value)
}
+
else -> {
// Not supported
}
@@ -102,7 +119,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
}
append("&$pageQueryParameter")
- append("&type=${(searchQuery.order ?: defaultSortOrder).toQueryParam()}")
+ append("&type=${(query.order ?: defaultSortOrder).toQueryParam()}")
}
val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
index 74680b6ab..5fe41fc6b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
@@ -1,9 +1,15 @@
package org.koitharu.kotatsu.parsers.util
import org.koitharu.kotatsu.parsers.InternalParsersApi
-import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
+import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
+import org.koitharu.kotatsu.parsers.model.SortOrder
+import org.koitharu.kotatsu.parsers.model.YEAR_UNKNOWN
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchCapability
import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
/**
@@ -20,26 +26,26 @@ public fun convertToMangaSearchQuery(offset: Int, sortOrder: SortOrder, filter:
return MangaSearchQuery.Builder().apply {
offset(offset)
order(sortOrder)
- if (filter.tags.isNotEmpty()) criterion(QueryCriteria.Include(TAG, filter.tags))
- if (filter.tagsExclude.isNotEmpty()) criterion(QueryCriteria.Exclude(TAG, filter.tagsExclude))
- if (filter.states.isNotEmpty()) criterion(QueryCriteria.Include(STATE, filter.states))
- if (filter.types.isNotEmpty()) criterion(QueryCriteria.Include(CONTENT_TYPE, filter.types))
- if (filter.contentRating.isNotEmpty()) criterion(QueryCriteria.Include(CONTENT_RATING, filter.contentRating))
- if (filter.demographics.isNotEmpty()) criterion(QueryCriteria.Include(DEMOGRAPHIC, filter.demographics))
+ if (filter.tags.isNotEmpty()) criterion(Include(TAG, filter.tags))
+ if (filter.tagsExclude.isNotEmpty()) criterion(Exclude(TAG, filter.tagsExclude))
+ if (filter.states.isNotEmpty()) criterion(Include(STATE, filter.states))
+ if (filter.types.isNotEmpty()) criterion(Include(CONTENT_TYPE, filter.types))
+ if (filter.contentRating.isNotEmpty()) criterion(Include(CONTENT_RATING, filter.contentRating))
+ if (filter.demographics.isNotEmpty()) criterion(Include(DEMOGRAPHIC, filter.demographics))
if (validateYear(filter.yearFrom) || validateYear(filter.yearTo)) {
criterion(QueryCriteria.Range(PUBLICATION_YEAR, filter.yearFrom, filter.yearTo))
}
if (validateYear(filter.year)) {
- criterion(QueryCriteria.Match(PUBLICATION_YEAR, filter.year))
+ criterion(Match(PUBLICATION_YEAR, filter.year))
}
- filter.locale?.takeIf { it != null }?.let {
- criterion(QueryCriteria.Include(LANGUAGE, setOf(it)))
+ filter.locale?.let {
+ criterion(Include(LANGUAGE, setOf(it)))
}
- filter.originalLocale?.takeIf { it != null }?.let {
- criterion(QueryCriteria.Include(ORIGINAL_LANGUAGE, setOf(it)))
+ filter.originalLocale?.let {
+ criterion(Include(ORIGINAL_LANGUAGE, setOf(it)))
}
filter.query?.takeIf { it.isNotBlank() }?.let {
- criterion(QueryCriteria.Match(TITLE_NAME, it))
+ criterion(Match(TITLE_NAME, it))
}
}.build()
}
@@ -74,16 +80,99 @@ public fun convertToMangaListFilter(searchQuery: MangaSearchQuery): MangaListFil
return MangaListFilter.Builder().apply {
for (criterion in searchQuery.criteria) {
when (criterion) {
- is QueryCriteria.Include<*> -> handleInclude(this, criterion)
- is QueryCriteria.Exclude<*> -> handleExclude(this, criterion)
- is QueryCriteria.Range<*> -> handleBetween(this, criterion)
- is QueryCriteria.Match<*> -> handleMatch(this, criterion)
+ is Include<*> -> handleInclude(this, criterion)
+ is Exclude<*> -> handleExclude(this, criterion)
+ is Range<*> -> handleBetween(this, criterion)
+ is Match<*> -> handleMatch(this, criterion)
}
}
}.build()
}
-private fun handleInclude(builder: MangaListFilter.Builder, criterion: QueryCriteria.Include<*>) {
+internal fun MangaListFilterCapabilities.toMangaSearchQueryCapabilities(): MangaSearchQueryCapabilities =
+ MangaSearchQueryCapabilities(
+ capabilities = setOfNotNull(
+ isMultipleTagsSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TAG, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true,
+ )
+ },
+ isTagsExclusionSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TAG, criteriaTypes = setOf(Exclude::class), multiValue = true, otherCriteria = true,
+ )
+ },
+ isSearchSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = false,
+ )
+ },
+ isSearchWithFiltersSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ )
+ },
+ isYearSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = PUBLICATION_YEAR,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ )
+ },
+ isYearRangeSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = PUBLICATION_YEAR,
+ criteriaTypes = setOf(Range::class),
+ multiValue = false,
+ otherCriteria = true,
+ )
+ },
+ isOriginalLocaleSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = ORIGINAL_LANGUAGE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ )
+ },
+ SearchCapability(
+ field = LANGUAGE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true,
+ ),
+ SearchCapability(
+ field = CONTENT_TYPE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = CONTENT_RATING,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = DEMOGRAPHIC,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ ),
+ )
+
+private fun handleInclude(builder: MangaListFilter.Builder, criterion: Include<*>) {
val type = criterion.field.type
when (criterion.field) {
@@ -94,20 +183,20 @@ private fun handleInclude(builder: MangaListFilter.Builder, criterion: QueryCrit
DEMOGRAPHIC -> builder.addDemographics(filterValues(criterion, type))
LANGUAGE -> builder.locale(getFirstValue(criterion, type))
ORIGINAL_LANGUAGE -> builder.originalLocale(getFirstValue(criterion, type))
- else -> throw UnsupportedOperationException("Unsupported field for Include criterion: ${criterion.field}")
+ else -> throw IllegalArgumentException("Unsupported field for Include criterion: ${criterion.field}")
}
}
-private fun handleExclude(builder: MangaListFilter.Builder, criterion: QueryCriteria.Exclude<*>) {
+private fun handleExclude(builder: MangaListFilter.Builder, criterion: Exclude<*>) {
val type = criterion.field.type
when (criterion.field) {
TAG -> builder.excludeTags(filterValues(criterion, type))
- else -> throw UnsupportedOperationException("Unsupported field for Exclude criterion: ${criterion.field}")
+ else -> throw IllegalArgumentException("Unsupported field for Exclude criterion: ${criterion.field}")
}
}
-private fun handleBetween(builder: MangaListFilter.Builder, criterion: QueryCriteria.Range<*>) {
+private fun handleBetween(builder: MangaListFilter.Builder, criterion: Range<*>) {
val type = criterion.field.type
when (criterion.field) {
@@ -115,32 +204,33 @@ private fun handleBetween(builder: MangaListFilter.Builder, criterion: QueryCrit
builder.yearFrom(getValue(criterion.from, type, YEAR_UNKNOWN))
builder.yearTo(getValue(criterion.to, type, YEAR_UNKNOWN))
}
- else -> throw UnsupportedOperationException("Unsupported field for Between criterion: ${criterion.field}")
+
+ else -> throw IllegalArgumentException("Unsupported field for Between criterion: ${criterion.field}")
}
}
-private fun handleMatch(builder: MangaListFilter.Builder, criterion: QueryCriteria.Match<*>) {
+private fun handleMatch(builder: MangaListFilter.Builder, criterion: Match<*>) {
val type = criterion.field.type
when (criterion.field) {
TITLE_NAME -> builder.query(getValue(criterion.value, type, ""))
PUBLICATION_YEAR -> builder.year(getValue(criterion.value, type, YEAR_UNKNOWN))
- else -> throw UnsupportedOperationException("Unsupported field for Match criterion: ${criterion.field}")
+ else -> throw IllegalArgumentException("Unsupported field for Match criterion: ${criterion.field}")
}
}
@Suppress("UNCHECKED_CAST")
-private fun filterValues(criterion: QueryCriteria.Include<*>, type: Class<*>): List {
+private fun filterValues(criterion: Include<*>, type: Class<*>): List {
return criterion.values.filter { type.isInstance(it) } as List
}
@Suppress("UNCHECKED_CAST")
-private fun filterValues(criterion: QueryCriteria.Exclude<*>, type: Class<*>): List {
+private fun filterValues(criterion: Exclude<*>, type: Class<*>): List {
return criterion.values.filter { type.isInstance(it) } as List
}
@Suppress("UNCHECKED_CAST")
-private fun getFirstValue(criterion: QueryCriteria.Include<*>, type: Class<*>): T? {
+private fun getFirstValue(criterion: Include<*>, type: Class<*>): T? {
return criterion.values.firstOrNull { type.isInstance(it) } as? T
}
@@ -151,4 +241,4 @@ private fun getValue(value: Any?, type: Class<*>, default: T): T {
return if (type.isInstance(value) || isCompatibleIntType) value as T else default
}
-private fun validateYear(year: Int) = year != null && year != YEAR_UNKNOWN
+private fun validateYear(year: Int) = year != YEAR_UNKNOWN
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
index d667cf90b..8fad32438 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
@@ -38,8 +38,9 @@ internal class MangaParserTest {
if (parser is SinglePageMangaParser) {
return@runTest
}
- val page1 = parser.searchManga(MangaSearchQuery.builder().offset(0).order(parser.defaultSortOrder).build())
- val page2 = parser.searchManga(MangaSearchQuery.builder().offset(page1.size).order(parser.defaultSortOrder).build())
+ val page1 = parser.queryManga(MangaSearchQuery.Builder().offset(0).order(parser.defaultSortOrder).build())
+ val page2 =
+ parser.queryManga(MangaSearchQuery.Builder().offset(page1.size).order(parser.defaultSortOrder).build())
if (parser is PagedMangaParser) {
assert(parser.pageSize >= page1.size) {
"Page size is ${page1.size} but ${parser.pageSize} expected"
@@ -58,19 +59,19 @@ internal class MangaParserTest {
@MangaSources
fun searchByTitleName(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val subject = parser.searchManga(
- MangaSearchQuery.builder()
+ val subject = parser.queryManga(
+ MangaSearchQuery.Builder()
.offset(0)
.order(parser.defaultSortOrder)
- .build()
+ .build(),
).minByOrNull {
it.title.length
} ?: error("No manga found")
val query = subject.title
check(query.isNotBlank()) { "Manga title '$query' is blank" }
- val list = parser.searchManga(
- MangaSearchQuery.builder()
+ val list = parser.queryManga(
+ MangaSearchQuery.Builder()
.order(SortOrder.RELEVANCE)
.criterion(QueryCriteria.Match(TITLE_NAME, query))
.build(),
@@ -102,8 +103,8 @@ internal class MangaParserTest {
assert(tags.all { it.source == source })
val tag = tags.last()
- val list = parser.searchManga(
- MangaSearchQuery.builder()
+ val list = parser.queryManga(
+ MangaSearchQuery.Builder()
.offset(0)
.order(parser.defaultSortOrder)
.criterion(Include(TAG, setOf(tag)))
@@ -120,8 +121,8 @@ internal class MangaParserTest {
if (!parser.filterCapabilities.isMultipleTagsSupported) return@runTest
val tags = parser.getFilterOptions().availableTags.shuffled().take(2).toSet()
- val list = parser.searchManga(
- MangaSearchQuery.builder()
+ val list = parser.queryManga(
+ MangaSearchQuery.Builder()
.offset(0)
.order(parser.defaultSortOrder)
.criterion(Include(TAG, tags))
@@ -141,8 +142,8 @@ internal class MangaParserTest {
return@runTest
}
val locale = locales.random()
- val list = parser.searchManga(
- MangaSearchQuery.builder()
+ val list = parser.queryManga(
+ MangaSearchQuery.Builder()
.offset(0)
.order(parser.defaultSortOrder)
.criterion(Include(LANGUAGE, setOf(locale)))
@@ -159,7 +160,7 @@ internal class MangaParserTest {
@MangaSources
fun details(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.searchManga(MangaSearchQuery.builder().offset(0).order(parser.defaultSortOrder).build())
+ val list = parser.queryManga(MangaSearchQuery.Builder().offset(0).order(parser.defaultSortOrder).build())
val manga = list[0]
parser.getDetails(manga).apply {
@@ -190,7 +191,7 @@ internal class MangaParserTest {
@MangaSources
fun pages(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.searchManga(MangaSearchQuery.builder().offset(0).order(parser.defaultSortOrder).build())
+ val list = parser.queryManga(MangaSearchQuery.Builder().offset(0).order(parser.defaultSortOrder).build())
val manga = list.first()
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null at ${manga.publicUrl}")
val pages = parser.getPages(chapter)
@@ -245,7 +246,8 @@ internal class MangaParserTest {
@MangaSources
fun link(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val manga = parser.searchManga(MangaSearchQuery.builder().offset(0).order(parser.defaultSortOrder).build()).first()
+ val manga =
+ parser.queryManga(MangaSearchQuery.Builder().offset(0).order(parser.defaultSortOrder).build()).first()
val resolved = context.newLinkResolver(manga.publicUrl).getManga()
Assertions.assertNotNull(resolved)
resolved ?: return@runTest
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt
index 9aaddec61..b7be38327 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
-import java.util.Locale
+import java.util.*
class MangaSearchQueryCapabilitiesTest {
@@ -18,12 +18,12 @@ class MangaSearchQueryCapabilitiesTest {
SearchCapability(TAG, setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true),
SearchCapability(PUBLICATION_YEAR, setOf(Range::class), multiValue = false, otherCriteria = true),
SearchCapability(STATE, setOf(Include::class), multiValue = false, otherCriteria = true),
- )
+ ),
)
@Test
fun validateValidSingleCriterionQuery() {
- val query = MangaSearchQuery.builder()
+ val query = MangaSearchQuery.Builder()
.criterion(Match(TITLE_NAME, "title"))
.build()
@@ -32,7 +32,7 @@ class MangaSearchQueryCapabilitiesTest {
@Test
fun validateUnsupportedFieldThrowsException() {
- val query = MangaSearchQuery.builder()
+ val query = MangaSearchQuery.Builder()
.criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.ENGLISH)))
.build()
@@ -41,7 +41,7 @@ class MangaSearchQueryCapabilitiesTest {
@Test
fun validateUnsupportedMultiValueThrowsException() {
- val query = MangaSearchQuery.builder()
+ val query = MangaSearchQuery.Builder()
.criterion(Include(STATE, setOf(MangaState.ONGOING, MangaState.FINISHED)))
.build()
@@ -50,7 +50,7 @@ class MangaSearchQueryCapabilitiesTest {
@Test
fun validateMultipleCriteriaWithOtherCriteriaAllowed() {
- val query = MangaSearchQuery.builder()
+ val query = MangaSearchQuery.Builder()
.criterion(Include(TAG, setOf(buildTag("tag1"), buildTag("tag2"))))
.criterion(Exclude(TAG, setOf(buildTag("tag3"))))
.build()
@@ -60,7 +60,7 @@ class MangaSearchQueryCapabilitiesTest {
@Test
fun validateMultipleCriteriaWithStrictCapabilityThrowsException() {
- val query = MangaSearchQuery.builder()
+ val query = MangaSearchQuery.Builder()
.criterion(Match(TITLE_NAME, "title"))
.criterion(Range(PUBLICATION_YEAR, 1990, 2000))
.build()
@@ -68,5 +68,5 @@ class MangaSearchQueryCapabilitiesTest {
assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
}
- private fun buildTag(name: String) = MangaTag(title = name, key = "${name}Key", source = MangaParserSource.DUMMY)
+ private fun buildTag(name: String) = MangaTag(title = name, key = "${name}Key", source = MangaParserSource.DUMMY)
}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt
index d43db48e2..04530beca 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt
@@ -34,12 +34,12 @@ class ListFilterToSearchQueryConverterTest {
demographics = demographics,
year = 2020,
yearFrom = 1997,
- yearTo = 2024
+ yearTo = 2024,
)
val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter)
- val expectedQuery = MangaSearchQuery.builder()
+ val expectedQuery = MangaSearchQuery.Builder()
.offset(0)
.order(SortOrder.NEWEST)
.criterion(Match(TITLE_NAME, "title_name"))
@@ -64,14 +64,14 @@ class ListFilterToSearchQueryConverterTest {
val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter)
- assertEquals(MangaSearchQuery.builder().offset(0).order(SortOrder.NEWEST).build(), searchQuery)
+ assertEquals(MangaSearchQuery.Builder().offset(0).order(SortOrder.NEWEST).build(), searchQuery)
}
private fun buildMangaTag(name: String): MangaTag {
return MangaTag(
key = "${name}Key",
title = name,
- source = MangaParserSource.DUMMY
+ source = MangaParserSource.DUMMY,
)
}
}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt
index 1d7dfa7a5..ef2823783 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt
@@ -3,10 +3,14 @@ package org.koitharu.kotatsu.parsers.util
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
-import org.koitharu.kotatsu.parsers.model.*
-import org.koitharu.kotatsu.parsers.model.ContentType.*
+import org.koitharu.kotatsu.parsers.model.ContentRating
+import org.koitharu.kotatsu.parsers.model.ContentType.MANGA
+import org.koitharu.kotatsu.parsers.model.ContentType.MANHUA
import org.koitharu.kotatsu.parsers.model.Demographic.SEINEN
-import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.MangaState
+import org.koitharu.kotatsu.parsers.model.MangaTag
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import java.util.*
@@ -22,7 +26,7 @@ class ConvertToMangaListFilterTest {
val contentTypes = setOf(MANGA, MANHUA)
val demographics = setOf(SEINEN)
- val query = MangaSearchQuery.builder()
+ val query = MangaSearchQuery.Builder()
.criterion(Match(TITLE_NAME, "title_name"))
.criterion(Include(TAG, tags))
.criterion(Exclude(TAG, excludedTags))
@@ -57,7 +61,7 @@ class ConvertToMangaListFilterTest {
val tags1 = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
val tags2 = setOf(buildMangaTag("tag3"), buildMangaTag("tag4"))
- val query = MangaSearchQuery.builder()
+ val query = MangaSearchQuery.Builder()
.criterion(Include(TAG, tags1))
.criterion(Include(TAG, tags2))
.build()
@@ -69,11 +73,11 @@ class ConvertToMangaListFilterTest {
@Test
fun convertToMangaListFilterWithUnsupportedFieldTest() {
- val query = MangaSearchQuery.builder()
+ val query = MangaSearchQuery.Builder()
.criterion(Include(AUTHOR, setOf(buildMangaTag("author"))))
.build()
- val exception = assertThrows {
+ val exception = assertThrows {
convertToMangaListFilter(query)
}
From 29cf04c804ac733c5d4a730f669438a48aeef71f Mon Sep 17 00:00:00 2001
From: Koitharu
Date: Mon, 24 Feb 2025 17:59:40 +0200
Subject: [PATCH 3/4] MangaParser interface
---
.github/summary.yaml | 2 +-
.../kotatsu/parsers/AbstractMangaParser.kt | 128 ++++++++++++++++++
.../koitharu/kotatsu/parsers/MangaParser.kt | 121 +++--------------
.../kotatsu/parsers/PagedMangaParser.kt | 2 +-
.../kotatsu/parsers/SinglePageMangaParser.kt | 2 +-
.../parsers/model/search/MangaSearchQuery.kt | 5 +
.../parsers/site/all/HitomiLaParser.kt | 4 +-
.../parsers/site/all/LineWebtoonsParser.kt | 4 +-
.../parsers/site/all/MangaDexParser.kt | 4 +-
.../parsers/site/all/WebtoonsParser.kt | 4 +-
.../kotatsu/parsers/site/be/AnibelParser.kt | 4 +-
.../kotatsu/parsers/site/en/WeebCentral.kt | 2 +-
.../parsers/site/ja/NicovideoSeigaParser.kt | 4 +-
.../parsers/site/nepnep/NepnepParser.kt | 4 +-
.../kotatsu/parsers/site/ru/NudeMoonParser.kt | 4 +-
.../parsers/site/ru/grouple/GroupleParser.kt | 4 +-
.../parsers/site/ru/multichan/ChanParser.kt | 4 +-
.../parsers/site/uk/HentaiUkrParser.kt | 4 +-
.../kotatsu/parsers/site/vi/HentaiVNParser.kt | 4 +-
.../kotatsu/parsers/util/LinkResolver.kt | 11 +-
.../kotatsu/parsers/util/MangaParserEnv.kt | 7 +-
.../parsers/util/RelatedMangaFinder.kt | 11 +-
.../parsers/CommonHeadersInterceptor.kt | 6 -
.../kotatsu/parsers/MangaParserTest.kt | 27 ++--
.../parsers/util/IntentFilterGenerator.kt | 3 +-
25 files changed, 208 insertions(+), 167 deletions(-)
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/AbstractMangaParser.kt
diff --git a/.github/summary.yaml b/.github/summary.yaml
index 00836e522..b64f1850c 100644
--- a/.github/summary.yaml
+++ b/.github/summary.yaml
@@ -1 +1 @@
-total: 1189
+total: 1189
\ No newline at end of file
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/AbstractMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/AbstractMangaParser.kt
new file mode 100644
index 000000000..9f25f6718
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/AbstractMangaParser.kt
@@ -0,0 +1,128 @@
+package org.koitharu.kotatsu.parsers
+
+import androidx.annotation.CallSuper
+import okhttp3.Headers
+import okhttp3.HttpUrl
+import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
+import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
+import org.koitharu.kotatsu.parsers.network.WebClient
+import org.koitharu.kotatsu.parsers.util.*
+import java.util.*
+
+@InternalParsersApi
+public abstract class AbstractMangaParser @InternalParsersApi constructor(
+ @property:InternalParsersApi public val context: MangaLoaderContext,
+ public override val source: MangaParserSource,
+) : MangaParser {
+
+ @Deprecated("Please check searchQueryCapabilities")
+ public abstract val filterCapabilities: MangaListFilterCapabilities
+
+ public override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = filterCapabilities.toMangaSearchQueryCapabilities()
+
+ public override val config: MangaSourceConfig by lazy { context.getConfig(source) }
+
+ public open val sourceLocale: Locale
+ get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
+
+ protected val isNsfwSource: Boolean = source.contentType == ContentType.HENTAI
+
+ /**
+ * Provide default domain and available alternatives, if any.
+ *
+ * Never hardcode domain in requests, use [domain] instead.
+ */
+ @InternalParsersApi
+ public abstract val configKeyDomain: ConfigKey.Domain
+
+ protected open val userAgentKey: ConfigKey.UserAgent = ConfigKey.UserAgent(context.getDefaultUserAgent())
+
+ override fun getRequestHeaders(): Headers = Headers.Builder()
+ .add("User-Agent", config[userAgentKey])
+ .build()
+
+ /**
+ * Used as fallback if value of `order` passed to [getList] is null
+ */
+ public open val defaultSortOrder: SortOrder
+ get() {
+ val supported = availableSortOrders
+ return SortOrder.entries.first { it in supported }
+ }
+
+ override val domain: String
+ get() = config[configKeyDomain]
+
+ @JvmField
+ protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
+
+ /**
+ * Search list of manga by specified searchQuery
+ *
+ * @param searchQuery searchQuery
+ */
+ public override suspend fun queryManga(searchQuery: MangaSearchQuery): List {
+ if (!searchQuery.skipValidation) {
+ searchQueryCapabilities.validate(searchQuery)
+ }
+
+ return getList(searchQuery)
+ }
+
+ /**
+ * Search list of manga by specified searchQuery
+ *
+ * @param query searchQuery
+ */
+ protected open suspend fun getList(query: MangaSearchQuery): List = getList(
+ offset = query.offset,
+ order = query.order ?: defaultSortOrder,
+ filter = convertToMangaListFilter(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 order one of [availableSortOrders] or [defaultSortOrder] for default value
+ * @param filter is a set of filter rules
+ *
+ * @deprecated New [getList] should be preferred.
+ */
+ @Deprecated("New getList(query: MangaSearchQuery) method should be preferred")
+ public abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List
+
+ /**
+ * Fetch direct link to the page image.
+ */
+ public override suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
+
+ /**
+ * Parse favicons from the main page of the source`s website
+ */
+ public override suspend fun getFavicons(): Favicons {
+ return FaviconParser(webClient, domain).parseFavicons()
+ }
+
+ @CallSuper
+ public override fun onCreateConfig(keys: MutableCollection>) {
+ keys.add(configKeyDomain)
+ }
+
+ public override suspend fun getRelatedManga(seed: Manga): List {
+ return RelatedMangaFinder(listOf(this)).invoke(seed)
+ }
+
+ /**
+ * Return [Manga] object by web link to it
+ * @see [Manga.publicUrl]
+ */
+ internal open suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
index c11a99d90..37b49902c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
@@ -1,145 +1,60 @@
package org.koitharu.kotatsu.parsers
-import androidx.annotation.CallSuper
import okhttp3.Headers
-import okhttp3.HttpUrl
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.*
-import org.koitharu.kotatsu.parsers.model.search.*
-import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
-import org.koitharu.kotatsu.parsers.network.WebClient
-import org.koitharu.kotatsu.parsers.util.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
import java.util.*
-public abstract class MangaParser @InternalParsersApi constructor(
- @property:InternalParsersApi public val context: MangaLoaderContext,
- public val source: MangaParserSource,
-) {
+public interface MangaParser {
+
+ public val source: MangaParserSource
/**
* Supported [SortOrder] variants. Must not be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
- public abstract val availableSortOrders: Set
-
- @Deprecated("Please check searchQueryCapabilities")
- public abstract val filterCapabilities: MangaListFilterCapabilities
-
- public open val searchQueryCapabilities: MangaSearchQueryCapabilities
- get() = filterCapabilities.toMangaSearchQueryCapabilities()
-
- public val config: MangaSourceConfig by lazy { context.getConfig(source) }
+ public val availableSortOrders: Set
- public open val sourceLocale: Locale
- get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
+ public val searchQueryCapabilities: MangaSearchQueryCapabilities
- protected val isNsfwSource: Boolean = source.contentType == ContentType.HENTAI
-
- /**
- * Provide default domain and available alternatives, if any.
- *
- * Never hardcode domain in requests, use [domain] instead.
- */
- @InternalParsersApi
- public abstract val configKeyDomain: ConfigKey.Domain
+ public val config: MangaSourceConfig
- protected open val userAgentKey: ConfigKey.UserAgent = ConfigKey.UserAgent(context.getDefaultUserAgent())
+ public val domain: String
- public open fun getRequestHeaders(): Headers = Headers.Builder()
- .add("User-Agent", config[userAgentKey])
- .build()
-
- /**
- * Used as fallback if value of `order` passed to [getList] is null
- */
- public open val defaultSortOrder: SortOrder
- get() {
- val supported = availableSortOrders
- return SortOrder.entries.first { it in supported }
- }
-
- @JvmField
- protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
-
- /**
- * Search list of manga by specified searchQuery
- *
- * @param searchQuery searchQuery
- */
- public suspend fun queryManga(searchQuery: MangaSearchQuery): List {
- if (!searchQuery.skipValidation) {
- searchQueryCapabilities.validate(searchQuery)
- }
-
- return getList(searchQuery)
- }
-
- /**
- * Search list of manga by specified searchQuery
- *
- * @param query searchQuery
- */
- protected open suspend fun getList(query: MangaSearchQuery): List = getList(
- offset = query.offset,
- order = query.order ?: defaultSortOrder,
- filter = convertToMangaListFilter(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 order one of [availableSortOrders] or [defaultSortOrder] for default value
- * @param filter is a set of filter rules
- *
- * @deprecated New [getList] should be preferred.
- */
- @Deprecated("New getList(query: MangaSearchQuery) method should be preferred")
- public abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List
+ public suspend fun queryManga(searchQuery: MangaSearchQuery): List
/**
* Parse details for [Manga]: chapters list, description, large cover, etc.
* Must return the same manga, may change any fields excepts id, url and source
* @see Manga.copy
*/
- public abstract suspend fun getDetails(manga: Manga): Manga
+ public suspend fun getDetails(manga: Manga): Manga
/**
* Parse pages list for specified chapter.
* @see MangaPage for details
*/
- public abstract suspend fun getPages(chapter: MangaChapter): List
+ public suspend fun getPages(chapter: MangaChapter): List
/**
* Fetch direct link to the page image.
*/
- public open suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
+ public suspend fun getPageUrl(page: MangaPage): String
- public abstract suspend fun getFilterOptions(): MangaListFilterOptions
+ public suspend fun getFilterOptions(): MangaListFilterOptions
/**
* Parse favicons from the main page of the source`s website
*/
- public open suspend fun getFavicons(): Favicons {
- return FaviconParser(webClient, domain).parseFavicons()
- }
+ public suspend fun getFavicons(): Favicons
- @CallSuper
- public open fun onCreateConfig(keys: MutableCollection>) {
- keys.add(configKeyDomain)
- }
+ public fun onCreateConfig(keys: MutableCollection>)
- public open suspend fun getRelatedManga(seed: Manga): List {
- return RelatedMangaFinder(listOf(this)).invoke(seed)
- }
-
- /**
- * Return [Manga] object by web link to it
- * @see [Manga.publicUrl]
- */
- internal open suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+ public suspend fun getRelatedManga(seed: Manga): List
+ public fun getRequestHeaders(): Headers
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
index e80a07cc8..21e4aadad 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
@@ -16,7 +16,7 @@ public abstract class PagedMangaParser(
source: MangaParserSource,
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField public val pageSize: Int,
searchPageSize: Int = pageSize,
-) : MangaParser(context, source) {
+) : AbstractMangaParser(context, source) {
@JvmField
protected val paginator: Paginator = Paginator(pageSize)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
index 406f2181c..6883e9be4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.util.convertToMangaListFilter
public abstract class SinglePageMangaParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : MangaParser(context, source) {
+) : AbstractMangaParser(context, source) {
final override suspend fun getList(query: MangaSearchQuery): List {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
index 628b13f39..ae03fdee6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
@@ -81,4 +81,9 @@ public data class MangaSearchQuery private constructor(
return uniqueCriteria.values.toSet()
}
}
+
+ public companion object {
+
+ public val EMPTY: MangaSearchQuery = MangaSearchQuery(emptySet(), null, 0, false)
+ }
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
index 24dd603e4..f3a183e29 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
@@ -11,7 +11,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.jsoup.Jsoup
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -28,7 +28,7 @@ import kotlin.math.min
@OptIn(ExperimentalUnsignedTypes::class)
@MangaSourceParser("HITOMILA", "Hitomi.La", type = ContentType.HENTAI)
-internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.HITOMILA) {
+internal class HitomiLaParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.HITOMILA) {
override val configKeyDomain = ConfigKey.Domain("hitomi.la")
override fun onCreateConfig(keys: MutableCollection>) {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
index 492dc44b6..15e606122 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
@@ -6,7 +6,7 @@ import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
@@ -21,7 +21,7 @@ import javax.crypto.spec.SecretKeySpec
internal abstract class LineWebtoonsParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : MangaParser(context, source) {
+) : AbstractMangaParser(context, source) {
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
index 1305e3840..628b56652 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
@@ -8,7 +8,7 @@ import okhttp3.HttpUrl
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.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -34,7 +34,7 @@ private const val SERVER_DATA = "data"
private const val SERVER_DATA_SAVER = "data-saver"
@MangaSourceParser("MANGADEX", "MangaDex")
-internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.MANGADEX) {
+internal class MangaDexParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.MANGADEX) {
override val configKeyDomain = ConfigKey.Domain("mangadex.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
index 9e8815086..6a6b32644 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
@@ -8,7 +8,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
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.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
@@ -24,7 +24,7 @@ import javax.crypto.spec.SecretKeySpec
internal abstract class WebtoonsParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : MangaParser(context, source) {
+) : AbstractMangaParser(context, source) {
private val signer by lazy {
WebtoonsUrlSigner("gUtPzJFZch4ZyAGviiyH94P99lQ3pFdRTwpJWDlSGFfwgpr6ses5ALOxWHOIT7R1")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
index 04459d4aa..8dedb155b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
@@ -5,7 +5,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -18,7 +18,7 @@ import java.util.*
@Broken
@MangaSourceParser("ANIBEL", "Anibel", "be")
-internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.ANIBEL) {
+internal class AnibelParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.ANIBEL) {
override val configKeyDomain = ConfigKey.Domain("anibel.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
index d354fa555..51a2342ab 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
@@ -16,7 +16,7 @@ import java.util.EnumSet
import java.util.Locale
@MangaSourceParser("WEEBCENTRAL", "Weeb Central", "en")
-internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.WEEBCENTRAL),
+internal class WeebCentral(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.WEEBCENTRAL),
MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain("weebcentral.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
index 439c2f586..461adf165 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.ja
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -16,7 +16,7 @@ private const val STATUS_FINISHED = "完結"
@MangaSourceParser("NICOVIDEO_SEIGA", "NicoVideo Seiga", "ja")
internal class NicovideoSeigaParser(context: MangaLoaderContext) :
- MangaParser(context, MangaParserSource.NICOVIDEO_SEIGA),
+ AbstractMangaParser(context, MangaParserSource.NICOVIDEO_SEIGA),
MangaParserAuthProvider {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
index b518dc121..b99c62ebf 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.nepnep
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.AbstractMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -20,7 +20,7 @@ internal abstract class NepnepParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : MangaParser(context, source) {
+) : AbstractMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
index 7b1c0401a..d13ef133b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.ru
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("NUDEMOON", "Nude-Moon", "ru", type = ContentType.HENTAI)
internal class NudeMoonParser(
context: MangaLoaderContext,
-) : MangaParser(context, MangaParserSource.NUDEMOON), MangaParserAuthProvider {
+) : AbstractMangaParser(context, MangaParserSource.NUDEMOON), MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain(
"b.nude-moon.fun",
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
index 274ccf3e6..83c338ede 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
@@ -17,7 +17,7 @@ import okio.IOException
import org.json.JSONArray
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
@@ -43,7 +43,7 @@ internal abstract class GroupleParser(
context: MangaLoaderContext,
source: MangaParserSource,
private val siteId: Int,
-) : MangaParser(context, source), MangaParserAuthProvider, Interceptor {
+) : AbstractMangaParser(context, source), MangaParserAuthProvider, Interceptor {
@Volatile
private var cachedPagesServer: String? = null
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
index 42083a4c0..2b52ad802 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.ru.multichan
import okhttp3.HttpUrl
import org.jsoup.internal.StringUtil
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.*
@@ -14,7 +14,7 @@ import java.util.*
internal abstract class ChanParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : MangaParser(context, source), MangaParserAuthProvider {
+) : AbstractMangaParser(context, source), MangaParserAuthProvider {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.NEWEST,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
index b18e14c10..330a9b422 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
@@ -8,7 +8,7 @@ import okhttp3.Response
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.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -25,7 +25,7 @@ private const val PAGE_SIZE = 60
// NOTE High profile focus
@MangaSourceParser("HENTAIUKR", "HentaiUkr", "uk", ContentType.HENTAI)
-internal class HentaiUkrParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.HENTAIUKR),
+internal class HentaiUkrParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.HENTAIUKR),
Interceptor {
private val date = SimpleDateFormat("yyyy-MM-dd", Locale.US)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
index 08d7e4955..abebb8369 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.sync.withLock
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -21,7 +21,7 @@ private const val SEARCH_PAGE_SIZE = 10
@Broken
@MangaSourceParser("HENTAIVN", "HentaiVN", "vi", type = ContentType.HENTAI)
-internal class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.HENTAIVN) {
+internal class HentaiVNParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.HENTAIVN) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaihvn.tv")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
index 3160fe507..bf6fc4ea6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
@@ -4,8 +4,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
@@ -19,14 +19,15 @@ public class LinkResolver internal constructor(
public suspend fun getSource(): MangaParserSource? = source.get()
public suspend fun getManga(): Manga? {
- val parser = context.newParserInstance(source.get() ?: return null)
+ val parser = context.newParserInstance(source.get() ?: return null) as? AbstractMangaParser
+ ?: return null
return parser.resolveLink(this, link) ?: resolveManga(parser)
}
private suspend fun resolveSource(): MangaParserSource? = runInterruptible(Dispatchers.Default) {
val domains = setOfNotNull(link.host, link.topPrivateDomain())
for (s in MangaParserSource.entries) {
- val parser = context.newParserInstance(s)
+ val parser = context.newParserInstance(s) as AbstractMangaParser
for (d in parser.configKeyDomain.presetValues) {
if (d in domains) {
return@runInterruptible s
@@ -37,7 +38,7 @@ public class LinkResolver internal constructor(
}
internal suspend fun resolveManga(
- parser: MangaParser,
+ parser: AbstractMangaParser,
url: String = link.toString().toRelativeUrl(link.host),
id: Long = parser.generateUid(url),
title: String = STUB_TITLE,
@@ -62,7 +63,7 @@ public class LinkResolver internal constructor(
),
)
- private suspend fun resolveBySeed(parser: MangaParser, s: Manga): Manga? {
+ private suspend fun resolveBySeed(parser: AbstractMangaParser, s: Manga): Manga? {
val seed = parser.getDetails(s)
if (!parser.filterCapabilities.isSearchSupported) {
return seed.takeUnless { it.chapters.isNullOrEmpty() }
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
index 9a73a8370..c81dab017 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.util
import okhttp3.HttpUrl
import org.jsoup.nodes.Element
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaParser
@@ -81,17 +82,17 @@ private fun Set?.oneOrThrowIfMany(msg: String): T? = when {
else -> throw IllegalArgumentException(msg)
}
-public val MangaParser.domain: String
+public val AbstractMangaParser.domain: String
get() = config[configKeyDomain]
@InternalParsersApi
-public fun MangaParser.getDomain(subdomain: String): String {
+public fun AbstractMangaParser.getDomain(subdomain: String): String {
val domain = domain
return subdomain + "." + domain.removePrefix("www.")
}
@InternalParsersApi
-public fun MangaParser.urlBuilder(subdomain: String? = null): HttpUrl.Builder {
+public fun AbstractMangaParser.urlBuilder(subdomain: String? = null): HttpUrl.Builder {
return HttpUrl.Builder()
.scheme(SCHEME_HTTPS)
.host(if (subdomain == null) domain else "$subdomain.$domain")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
index 43bc9d3fb..4b1edf321 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
@@ -6,8 +6,10 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.SortOrder
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
+import org.koitharu.kotatsu.parsers.model.search.SearchableField
public class RelatedMangaFinder(
private val parsers: Collection,
@@ -34,7 +36,12 @@ public class RelatedMangaFinder(
}
val results = words.map { keyword ->
scope.async {
- val result = parser.getList(0, SortOrder.RELEVANCE, MangaListFilter(query = keyword))
+ val result = parser.queryManga(
+ MangaSearchQuery.Builder()
+ .order(SortOrder.RELEVANCE)
+ .criterion(QueryCriteria.Match(SearchableField.TITLE_NAME, keyword))
+ .build(),
+ )
result.filter { it.id != seed.id && it.containKeyword(keyword) }
}
}.awaitAll()
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/CommonHeadersInterceptor.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/CommonHeadersInterceptor.kt
index f51e76cec..62a239f37 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/CommonHeadersInterceptor.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/CommonHeadersInterceptor.kt
@@ -5,8 +5,6 @@ import okhttp3.Request
import okhttp3.Response
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.util.domain
-import org.koitharu.kotatsu.parsers.util.mergeWith
private const val HEADER_REFERER = "Referer"
@@ -20,11 +18,7 @@ internal class CommonHeadersInterceptor : Interceptor {
} else {
null
}
- val sourceHeaders = parser?.getRequestHeaders()
val headersBuilder = request.headers.newBuilder()
- if (sourceHeaders != null) {
- headersBuilder.mergeWith(sourceHeaders, replaceExisting = false)
- }
if (headersBuilder[HEADER_REFERER] == null && parser != null) {
headersBuilder[HEADER_REFERER] = "https://${parser.domain}/"
}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
index 8fad32438..46c761a64 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
@@ -10,7 +10,6 @@ import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Include
import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
-import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.medianOrNull
import org.koitharu.kotatsu.parsers.util.mimeType
import org.koitharu.kotatsu.test_util.*
@@ -26,7 +25,7 @@ internal class MangaParserTest {
@MangaSources
fun list(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY)
+ val list = parser.queryManga(MangaSearchQuery.Builder().build())
checkMangaList(list, "list")
assert(list.all { it.source == source })
}
@@ -38,9 +37,9 @@ internal class MangaParserTest {
if (parser is SinglePageMangaParser) {
return@runTest
}
- val page1 = parser.queryManga(MangaSearchQuery.Builder().offset(0).order(parser.defaultSortOrder).build())
+ val page1 = parser.queryManga(MangaSearchQuery.EMPTY)
val page2 =
- parser.queryManga(MangaSearchQuery.Builder().offset(page1.size).order(parser.defaultSortOrder).build())
+ parser.queryManga(MangaSearchQuery.Builder().offset(page1.size).build())
if (parser is PagedMangaParser) {
assert(parser.pageSize >= page1.size) {
"Page size is ${page1.size} but ${parser.pageSize} expected"
@@ -59,12 +58,7 @@ internal class MangaParserTest {
@MangaSources
fun searchByTitleName(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val subject = parser.queryManga(
- MangaSearchQuery.Builder()
- .offset(0)
- .order(parser.defaultSortOrder)
- .build(),
- ).minByOrNull {
+ val subject = parser.queryManga(MangaSearchQuery.EMPTY).minByOrNull {
it.title.length
} ?: error("No manga found")
@@ -106,7 +100,6 @@ internal class MangaParserTest {
val list = parser.queryManga(
MangaSearchQuery.Builder()
.offset(0)
- .order(parser.defaultSortOrder)
.criterion(Include(TAG, setOf(tag)))
.build(),
)
@@ -118,13 +111,12 @@ internal class MangaParserTest {
@MangaSources
fun tagsMultiple(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- if (!parser.filterCapabilities.isMultipleTagsSupported) return@runTest
+// if (!parser.filterCapabilities.isMultipleTagsSupported) return@runTest
val tags = parser.getFilterOptions().availableTags.shuffled().take(2).toSet()
val list = parser.queryManga(
MangaSearchQuery.Builder()
.offset(0)
- .order(parser.defaultSortOrder)
.criterion(Include(TAG, tags))
.build(),
)
@@ -144,8 +136,6 @@ internal class MangaParserTest {
val locale = locales.random()
val list = parser.queryManga(
MangaSearchQuery.Builder()
- .offset(0)
- .order(parser.defaultSortOrder)
.criterion(Include(LANGUAGE, setOf(locale)))
.criterion(Include(LANGUAGE, setOf(locale)))
.criterion(Include(ORIGINAL_LANGUAGE, setOf(locales.random())))
@@ -160,7 +150,7 @@ internal class MangaParserTest {
@MangaSources
fun details(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.queryManga(MangaSearchQuery.Builder().offset(0).order(parser.defaultSortOrder).build())
+ val list = parser.queryManga(MangaSearchQuery.EMPTY)
val manga = list[0]
parser.getDetails(manga).apply {
@@ -191,7 +181,7 @@ internal class MangaParserTest {
@MangaSources
fun pages(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.queryManga(MangaSearchQuery.Builder().offset(0).order(parser.defaultSortOrder).build())
+ val list = parser.queryManga(MangaSearchQuery.EMPTY)
val manga = list.first()
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null at ${manga.publicUrl}")
val pages = parser.getPages(chapter)
@@ -246,8 +236,7 @@ internal class MangaParserTest {
@MangaSources
fun link(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val manga =
- parser.queryManga(MangaSearchQuery.Builder().offset(0).order(parser.defaultSortOrder).build()).first()
+ val manga = parser.queryManga(MangaSearchQuery.Builder().build()).first()
val resolved = context.newLinkResolver(manga.publicUrl).getManga()
Assertions.assertNotNull(resolved)
resolved ?: return@runTest
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
index 6b997776f..a8ff362f9 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.parsers.util
import org.junit.jupiter.api.Test
+import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContextMock
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.newParser
@@ -25,7 +26,7 @@ class IntentFilterGenerator {
if (source == MangaParserSource.DUMMY) {
continue
}
- val parser = source.newParser(MangaLoaderContextMock)
+ val parser = source.newParser(MangaLoaderContextMock) as AbstractMangaParser
parser.configKeyDomain.presetValues.forEach { domain ->
writer.appendTab().append("")
}
From f681ed270b3bed5d0b8fd93cfc57e143792de2e7 Mon Sep 17 00:00:00 2001
From: Koitharu
Date: Wed, 26 Feb 2025 16:19:32 +0200
Subject: [PATCH 4/4] Update parsers structure
---
.../kotatsu/parsers/ksp/ParserProcessor.kt | 4 +-
.../koitharu/kotatsu/parsers/MangaParser.kt | 25 +-
.../kotatsu/parsers/SinglePageMangaParser.kt | 42 --
.../parsers/core/AbstractMangaParser.kt | 91 ++++
.../LegacyMangaParser.kt} | 48 +-
.../parsers/core/LegacyPagedMangaParser.kt | 52 ++
.../core/LegacySinglePageMangaParser.kt | 24 +
.../parsers/core/MangaParserWrapper.kt | 64 +++
.../parsers/{ => core}/PagedMangaParser.kt | 44 +-
.../parsers/core/SinglePageMangaParser.kt | 23 +
.../search/MangaSearchQueryCapabilities.kt | 2 +-
.../kotatsu/parsers/site/all/BatoToParser.kt | 6 +-
.../parsers/site/all/ComickFunParser.kt | 4 +-
.../parsers/site/all/ExHentaiParser.kt | 4 +-
.../parsers/site/all/HitomiLaParser.kt | 4 +-
.../kotatsu/parsers/site/all/ImHentai.kt | 4 +-
.../parsers/site/all/LineWebtoonsParser.kt | 4 +-
.../parsers/site/all/MangaDexParser.kt | 17 +-
.../parsers/site/all/MangaFireParser.kt | 4 +-
.../kotatsu/parsers/site/all/MangaPark.kt | 6 +-
.../parsers/site/all/MangaPlusParser.kt | 4 +-
.../parsers/site/all/MangaReaderToParser.kt | 4 +-
.../parsers/site/all/NineMangaParser.kt | 4 +-
.../site/all/NineNineNineHentaiParser.kt | 4 +-
.../parsers/site/all/WebtoonsParser.kt | 4 +-
.../animebootstrap/AnimeBootstrapParser.kt | 4 +-
.../kotatsu/parsers/site/ar/FlixScans.kt | 5 +-
.../kotatsu/parsers/site/ar/MangaStorm.kt | 5 +-
.../kotatsu/parsers/site/ar/TeamXNovel.kt | 5 +-
.../kotatsu/parsers/site/be/AnibelParser.kt | 9 +-
.../parsers/site/cupfox/CupFoxParser.kt | 4 +-
.../parsers/site/en/AsuraScansParser.kt | 4 +-
.../kotatsu/parsers/site/en/BeeToon.kt | 4 +-
.../parsers/site/en/CloneMangaParser.kt | 4 +-
.../kotatsu/parsers/site/en/ComicExtra.kt | 5 +-
.../kotatsu/parsers/site/en/DynastyScans.kt | 4 +-
.../kotatsu/parsers/site/en/FlameComics.kt | 4 +-
.../kotatsu/parsers/site/en/FlixScansOrg.kt | 3 +-
.../kotatsu/parsers/site/en/MangaGeko.kt | 5 +-
.../kotatsu/parsers/site/en/MangaKawaiiEn.kt | 4 +-
.../parsers/site/en/MangaTownParser.kt | 4 +-
.../kotatsu/parsers/site/en/Mangaowl.kt | 4 +-
.../kotatsu/parsers/site/en/Manhwa18Com.kt | 4 +-
.../kotatsu/parsers/site/en/Manhwa18Parser.kt | 4 +-
.../kotatsu/parsers/site/en/ManhwasMen.kt | 4 +-
.../kotatsu/parsers/site/en/MyComicList.kt | 5 +-
.../kotatsu/parsers/site/en/Po2Scans.kt | 5 +-
.../kotatsu/parsers/site/en/Pururin.kt | 4 +-
.../kotatsu/parsers/site/en/VyManga.kt | 4 +-
.../kotatsu/parsers/site/en/WeebCentral.kt | 3 +-
.../kotatsu/parsers/site/es/TempleScanEsp.kt | 4 +-
.../parsers/site/es/TuMangaOnlineParser.kt | 4 +-
.../parsers/site/fmreader/FmreaderParser.kt | 4 +-
.../parsers/site/foolslide/FoolSlideParser.kt | 4 +-
.../parsers/site/fr/BentomangaParser.kt | 4 +-
.../kotatsu/parsers/site/fr/FuryoSociety.kt | 4 +-
.../parsers/site/fr/LegacyScansParser.kt | 4 +-
.../kotatsu/parsers/site/fr/LireScan.kt | 4 +-
.../kotatsu/parsers/site/fr/LugnicaScans.kt | 4 +-
.../kotatsu/parsers/site/fr/MangaKawaii.kt | 5 +-
.../kotatsu/parsers/site/fr/MangaMana.kt | 5 +-
.../kotatsu/parsers/site/fr/ScansMangasMe.kt | 4 +-
.../kotatsu/parsers/site/fr/ScantradUnion.kt | 4 +-
.../site/fuzzydoodle/FuzzyDoodleParser.kt | 4 +-
.../site/galleryadults/GalleryAdultsParser.kt | 4 +-
.../parsers/site/gattsu/GattsuParser.kt | 4 +-
.../kotatsu/parsers/site/guya/GuyaParser.kt | 4 +-
.../kotatsu/parsers/site/heancms/HeanCms.kt | 4 +-
.../parsers/site/heancmsalt/HeanCmsAlt.kt | 4 +-
.../parsers/site/hotcomics/HotComicsParser.kt | 4 +-
.../parsers/site/id/DoujinDesuParser.kt | 4 +-
.../kotatsu/parsers/site/id/HentaiCrot.kt | 4 +-
.../kotatsu/parsers/site/id/PixHentai.kt | 4 +-
.../kotatsu/parsers/site/iken/IkenParser.kt | 4 +-
.../parsers/site/ja/NicovideoSeigaParser.kt | 4 +-
.../parsers/site/keyoapp/KeyoappParser.kt | 4 +-
.../parsers/site/likemanga/LikeMangaParser.kt | 4 +-
.../parsers/site/liliana/LilianaParser.kt | 4 +-
.../parsers/site/madara/MadaraParser.kt | 4 +-
.../parsers/site/madtheme/MadthemeParser.kt | 4 +-
.../parsers/site/manga18/Manga18Parser.kt | 4 +-
.../parsers/site/mangabox/MangaboxParser.kt | 16 +-
.../parsers/site/mangabox/en/Mangairo.kt | 79 +--
.../parsers/site/mangabox/en/Mangakakalot.kt | 8 +-
.../site/mangabox/en/MangakakalotTv.kt | 9 +-
.../site/mangadventure/MangAdventureParser.kt | 4 +-
.../site/mangareader/MangaReaderParser.kt | 4 +-
.../site/mangaworld/MangaWorldParser.kt | 4 +-
.../parsers/site/mmrcms/MmrcmsParser.kt | 4 +-
.../parsers/site/nepnep/NepnepParser.kt | 4 +-
.../parsers/site/onemanga/OneMangaParser.kt | 4 +-
.../otakusanctuary/OtakuSanctuaryParser.kt | 4 +-
.../site/pizzareader/PizzaReaderParser.kt | 4 +-
.../kotatsu/parsers/site/pt/BrMangas.kt | 4 +-
.../kotatsu/parsers/site/pt/LerManga.kt | 3 +-
.../kotatsu/parsers/site/pt/LerMangaOnline.kt | 4 +-
.../parsers/site/pt/LuratoonScansParser.kt | 3 +-
.../kotatsu/parsers/site/pt/MangaOnline.kt | 5 +-
.../kotatsu/parsers/site/pt/MuitoHentai.kt | 9 +-
.../kotatsu/parsers/site/pt/OnePieceEx.kt | 5 +-
.../kotatsu/parsers/site/pt/YugenMangas.kt | 4 +-
.../kotatsu/parsers/site/ru/AComics.kt | 4 +-
.../kotatsu/parsers/site/ru/DesuMeParser.kt | 5 +-
.../kotatsu/parsers/site/ru/MangaWtfParser.kt | 26 +-
.../kotatsu/parsers/site/ru/NudeMoonParser.kt | 4 +-
.../kotatsu/parsers/site/ru/RemangaParser.kt | 4 +-
.../kotatsu/parsers/site/ru/WaMangaParser.kt | 4 +-
.../parsers/site/ru/grouple/GroupleParser.kt | 4 +-
.../parsers/site/ru/multichan/ChanParser.kt | 4 +-
.../parsers/site/ru/rulib/LibSocialParser.kt | 4 +-
.../kotatsu/parsers/site/scan/ScanParser.kt | 4 +-
.../kotatsu/parsers/site/sinmh/SinmhParser.kt | 4 +-
.../kotatsu/parsers/site/tr/MangaAy.kt | 4 +-
.../kotatsu/parsers/site/tr/SadScans.kt | 5 +-
.../kotatsu/parsers/site/tr/TrWebtoon.kt | 4 +-
.../parsers/site/uk/HentaiUkrParser.kt | 4 +-
.../parsers/site/uk/HoneyMangaParser.kt | 4 +-
.../parsers/site/uk/MangaInUaParser.kt | 4 +-
.../parsers/site/vi/BlogTruyenParser.kt | 4 +-
.../kotatsu/parsers/site/vi/BlogTruyenVN.kt | 4 +-
.../kotatsu/parsers/site/vi/CMangaParser.kt | 7 +-
.../parsers/site/vi/CuuTruyenParser.kt | 4 +-
.../kotatsu/parsers/site/vi/DuaLeoTruyen.kt | 4 +-
.../kotatsu/parsers/site/vi/GocTruyenTranh.kt | 489 +++++++++---------
.../kotatsu/parsers/site/vi/Hentai18VN.kt | 309 ++++++-----
.../kotatsu/parsers/site/vi/HentaiVNParser.kt | 4 +-
.../kotatsu/parsers/site/vi/HentaiVnBuzz.kt | 38 +-
.../kotatsu/parsers/site/vi/KuroNeko.kt | 25 +-
.../kotatsu/parsers/site/vi/LxManga.kt | 9 +-
.../kotatsu/parsers/site/vi/SayHentai.kt | 5 +-
.../kotatsu/parsers/site/vi/TruyenGG.kt | 4 +-
.../kotatsu/parsers/site/vi/TruyenHentaiVN.kt | 120 ++---
.../kotatsu/parsers/site/vi/TruyenQQ.kt | 4 +-
.../kotatsu/parsers/site/vi/TruyenTranh3Q.kt | 4 +-
.../kotatsu/parsers/site/vi/VcomycsParser.kt | 6 +-
.../kotatsu/parsers/site/vi/YurinekoParser.kt | 5 +-
.../kotatsu/parsers/site/vmp/VmpParser.kt | 4 +-
.../parsers/site/wpcomics/WpComicsParser.kt | 4 +-
.../site/zeistmanga/ZeistMangaParser.kt | 4 +-
.../kotatsu/parsers/site/zh/Baozimh.kt | 4 +-
.../parsers/site/zmanga/ZMangaParser.kt | 4 +-
.../kotatsu/parsers/util/FaviconParser.kt | 6 +-
.../kotatsu/parsers/util/LinkResolver.kt | 12 +-
.../kotatsu/parsers/util/MangaParserEnv.kt | 8 +-
.../parsers/util/RelatedMangaFinder.kt | 23 +-
.../parsers/util/SearchQueryConverter.kt | 18 +-
.../kotatsu/parsers/MangaParserTest.kt | 28 +-
.../parsers/util/IntentFilterGenerator.kt | 4 +-
148 files changed, 1133 insertions(+), 1032 deletions(-)
delete mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/core/AbstractMangaParser.kt
rename src/main/kotlin/org/koitharu/kotatsu/parsers/{AbstractMangaParser.kt => core/LegacyMangaParser.kt} (66%)
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyPagedMangaParser.kt
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacySinglePageMangaParser.kt
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/core/MangaParserWrapper.kt
rename src/main/kotlin/org/koitharu/kotatsu/parsers/{ => core}/PagedMangaParser.kt (53%)
create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/core/SinglePageMangaParser.kt
diff --git a/kotatsu-parsers-ksp/src/main/kotlin/org/koitharu/kotatsu/parsers/ksp/ParserProcessor.kt b/kotatsu-parsers-ksp/src/main/kotlin/org/koitharu/kotatsu/parsers/ksp/ParserProcessor.kt
index d95f99b85..46f1293c1 100644
--- a/kotatsu-parsers-ksp/src/main/kotlin/org/koitharu/kotatsu/parsers/ksp/ParserProcessor.kt
+++ b/kotatsu-parsers-ksp/src/main/kotlin/org/koitharu/kotatsu/parsers/ksp/ParserProcessor.kt
@@ -70,6 +70,7 @@ class ParserProcessor(
package org.koitharu.kotatsu.parsers
import org.koitharu.kotatsu.parsers.model.MangaParserSource
+ import org.koitharu.kotatsu.parsers.core.MangaParserWrapper
internal fun MangaParserSource.newParser(context: MangaLoaderContext): MangaParser = when (this) {
@@ -98,10 +99,11 @@ class ParserProcessor(
factoryWriter?.write(
"""
MangaParserSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated")
- }.also {
+ }.let {
require(it.source == this) {
"Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}"
}
+ MangaParserWrapper(it)
}
""".trimIndent(),
)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
index 37b49902c..d2fa0fa65 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
@@ -1,14 +1,19 @@
package org.koitharu.kotatsu.parsers
import okhttp3.Headers
+import okhttp3.HttpUrl
+import okhttp3.Interceptor
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
+import org.koitharu.kotatsu.parsers.util.LinkResolver
+import org.koitharu.kotatsu.parsers.util.convertToMangaSearchQuery
+import org.koitharu.kotatsu.parsers.util.toMangaListFilterCapabilities
import java.util.*
-public interface MangaParser {
+public interface MangaParser : Interceptor {
public val source: MangaParserSource
@@ -25,7 +30,7 @@ public interface MangaParser {
public val domain: String
- public suspend fun queryManga(searchQuery: MangaSearchQuery): List
+ public suspend fun getList(query: MangaSearchQuery): List
/**
* Parse details for [Manga]: chapters list, description, large cover, etc.
@@ -57,4 +62,20 @@ public interface MangaParser {
public suspend fun getRelatedManga(seed: Manga): List
public fun getRequestHeaders(): Headers
+
+ /**
+ * Return [Manga] object by web link to it
+ * @see [Manga.publicUrl]
+ */
+ @InternalParsersApi
+ public suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga?
+
+ @Deprecated("Use getList(query: MangaSearchQuery) instead")
+ public suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
+ return getList(convertToMangaSearchQuery(offset, order, filter))
+ }
+
+ @Deprecated("Please check searchQueryCapabilities")
+ public val filterCapabilities: MangaListFilterCapabilities
+ get() = searchQueryCapabilities.toMangaListFilterCapabilities()
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
deleted file mode 100644
index 6883e9be4..000000000
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.koitharu.kotatsu.parsers
-
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaListFilter
-import org.koitharu.kotatsu.parsers.model.MangaParserSource
-import org.koitharu.kotatsu.parsers.model.SortOrder
-import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
-import org.koitharu.kotatsu.parsers.util.convertToMangaListFilter
-
-@InternalParsersApi
-public abstract class SinglePageMangaParser(
- context: MangaLoaderContext,
- source: MangaParserSource,
-) : AbstractMangaParser(context, source) {
-
-
- final override suspend fun getList(query: MangaSearchQuery): List {
- if (query.offset > 0) {
- return emptyList()
- }
- return searchSinglePageManga(query)
- }
-
- public open suspend fun searchSinglePageManga(searchQuery: MangaSearchQuery): List {
- return getList(
- searchQuery.offset,
- searchQuery.order ?: defaultSortOrder,
- convertToMangaListFilter(searchQuery),
- )
- }
-
- @Deprecated("New searchManga method should be preferred")
- final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
- if (offset > 0) {
- return emptyList()
- }
- return getList(order, filter)
- }
-
- @Deprecated("New searchManga method should be preferred")
- public abstract suspend fun getList(order: SortOrder, filter: MangaListFilter): List
-}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/AbstractMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/AbstractMangaParser.kt
new file mode 100644
index 000000000..28e58ba7a
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/AbstractMangaParser.kt
@@ -0,0 +1,91 @@
+package org.koitharu.kotatsu.parsers.core
+
+import androidx.annotation.CallSuper
+import okhttp3.Headers
+import okhttp3.HttpUrl
+import okhttp3.Interceptor
+import okhttp3.Response
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
+import org.koitharu.kotatsu.parsers.network.WebClient
+import org.koitharu.kotatsu.parsers.util.FaviconParser
+import org.koitharu.kotatsu.parsers.util.LinkResolver
+import org.koitharu.kotatsu.parsers.util.RelatedMangaFinder
+import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
+import java.util.*
+
+@InternalParsersApi
+public abstract class AbstractMangaParser @InternalParsersApi constructor(
+ @property:InternalParsersApi public val context: MangaLoaderContext,
+ public override val source: MangaParserSource,
+) : MangaParser {
+
+ public override val config: MangaSourceConfig by lazy { context.getConfig(source) }
+
+ public open val sourceLocale: Locale
+ get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
+
+ /**
+ * Provide default domain and available alternatives, if any.
+ *
+ * Never hardcode domain in requests, use [domain] instead.
+ */
+ @InternalParsersApi
+ public abstract val configKeyDomain: ConfigKey.Domain
+
+ protected open val userAgentKey: ConfigKey.UserAgent = ConfigKey.UserAgent(context.getDefaultUserAgent())
+
+ @Deprecated("Override intercept() instead")
+ override fun getRequestHeaders(): Headers = Headers.Builder()
+ .add("User-Agent", config[userAgentKey])
+ .build()
+
+ /**
+ * Used as fallback if value of `order` passed to [getList] is null
+ */
+ public open val defaultSortOrder: SortOrder
+ get() {
+ val supported = availableSortOrders
+ return SortOrder.entries.first { it in supported }
+ }
+
+ override val domain: String
+ get() = config[configKeyDomain]
+
+ @JvmField
+ protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
+
+ /**
+ * Fetch direct link to the page image.
+ */
+ public override suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
+
+ /**
+ * Parse favicons from the main page of the source`s website
+ */
+ public override suspend fun getFavicons(): Favicons {
+ return FaviconParser(webClient, domain).parseFavicons()
+ }
+
+ @CallSuper
+ public override fun onCreateConfig(keys: MutableCollection>) {
+ keys.add(configKeyDomain)
+ }
+
+ public override suspend fun getRelatedManga(seed: Manga): List {
+ return RelatedMangaFinder(listOf(this)).invoke(seed)
+ }
+
+ /**
+ * Return [Manga] object by web link to it
+ * @see [Manga.publicUrl]
+ */
+ override suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+
+ override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request())
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/AbstractMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyMangaParser.kt
similarity index 66%
rename from src/main/kotlin/org/koitharu/kotatsu/parsers/AbstractMangaParser.kt
rename to src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyMangaParser.kt
index 9f25f6718..18895d2f7 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/AbstractMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyMangaParser.kt
@@ -1,8 +1,13 @@
-package org.koitharu.kotatsu.parsers
+package org.koitharu.kotatsu.parsers.core
import androidx.annotation.CallSuper
import okhttp3.Headers
import okhttp3.HttpUrl
+import okhttp3.Interceptor
+import okhttp3.Response
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.*
@@ -13,18 +18,18 @@ import org.koitharu.kotatsu.parsers.network.WebClient
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
+@Suppress("OVERRIDE_DEPRECATION")
@InternalParsersApi
-public abstract class AbstractMangaParser @InternalParsersApi constructor(
+public abstract class LegacyMangaParser @InternalParsersApi constructor(
@property:InternalParsersApi public val context: MangaLoaderContext,
public override val source: MangaParserSource,
) : MangaParser {
- @Deprecated("Please check searchQueryCapabilities")
- public abstract val filterCapabilities: MangaListFilterCapabilities
-
- public override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ public final override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = filterCapabilities.toMangaSearchQueryCapabilities()
+ abstract override val filterCapabilities: MangaListFilterCapabilities
+
public override val config: MangaSourceConfig by lazy { context.getConfig(source) }
public open val sourceLocale: Locale
@@ -61,42 +66,18 @@ public abstract class AbstractMangaParser @InternalParsersApi constructor(
@JvmField
protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
- /**
- * Search list of manga by specified searchQuery
- *
- * @param searchQuery searchQuery
- */
- public override suspend fun queryManga(searchQuery: MangaSearchQuery): List {
- if (!searchQuery.skipValidation) {
- searchQueryCapabilities.validate(searchQuery)
- }
-
- return getList(searchQuery)
- }
-
/**
* Search list of manga by specified searchQuery
*
* @param query searchQuery
*/
- protected open suspend fun getList(query: MangaSearchQuery): List = getList(
+ public final override suspend fun getList(query: MangaSearchQuery): List = getList(
offset = query.offset,
order = query.order ?: defaultSortOrder,
filter = convertToMangaListFilter(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 order one of [availableSortOrders] or [defaultSortOrder] for default value
- * @param filter is a set of filter rules
- *
- * @deprecated New [getList] should be preferred.
- */
- @Deprecated("New getList(query: MangaSearchQuery) method should be preferred")
- public abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List
+ abstract override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List
/**
* Fetch direct link to the page image.
@@ -123,6 +104,7 @@ public abstract class AbstractMangaParser @InternalParsersApi constructor(
* Return [Manga] object by web link to it
* @see [Manga.publicUrl]
*/
- internal open suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+ override suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+ override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request())
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyPagedMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyPagedMangaParser.kt
new file mode 100644
index 000000000..57c51d39f
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyPagedMangaParser.kt
@@ -0,0 +1,52 @@
+package org.koitharu.kotatsu.parsers.core
+
+import androidx.annotation.VisibleForTesting
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.SortOrder
+import org.koitharu.kotatsu.parsers.util.Paginator
+
+@InternalParsersApi
+public abstract class LegacyPagedMangaParser(
+ context: MangaLoaderContext,
+ source: MangaParserSource,
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField public val pageSize: Int,
+ searchPageSize: Int = pageSize,
+) : LegacyMangaParser(context, source) {
+
+ @JvmField
+ protected val paginator: Paginator = Paginator(pageSize)
+
+ @JvmField
+ protected val searchPaginator: Paginator = Paginator(searchPageSize)
+
+ final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
+ return getList(
+ paginator = if (filter.query.isNullOrEmpty()) {
+ paginator
+ } else {
+ searchPaginator
+ },
+ offset = offset,
+ order = order,
+ filter = filter,
+ )
+ }
+
+ public abstract suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List
+
+ private suspend fun getList(
+ paginator: Paginator,
+ offset: Int,
+ order: SortOrder,
+ filter: MangaListFilter,
+ ): List {
+ val page = paginator.getPage(offset)
+ val list = getListPage(page, order, filter)
+ paginator.onListReceived(offset, page, list.size)
+ return list
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacySinglePageMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacySinglePageMangaParser.kt
new file mode 100644
index 000000000..898280ce9
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacySinglePageMangaParser.kt
@@ -0,0 +1,24 @@
+package org.koitharu.kotatsu.parsers.core
+
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.SortOrder
+
+@InternalParsersApi
+public abstract class LegacySinglePageMangaParser(
+ context: MangaLoaderContext,
+ source: MangaParserSource,
+) : LegacyMangaParser(context, source) {
+
+ final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
+ if (offset > 0) {
+ return emptyList()
+ }
+ return getList(order, filter)
+ }
+
+ public abstract suspend fun getList(order: SortOrder, filter: MangaListFilter): List
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/MangaParserWrapper.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/MangaParserWrapper.kt
new file mode 100644
index 000000000..4c13da50d
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/MangaParserWrapper.kt
@@ -0,0 +1,64 @@
+package org.koitharu.kotatsu.parsers.core
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import okhttp3.Interceptor
+import okhttp3.Request
+import okhttp3.Response
+import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.util.mergeWith
+
+internal class MangaParserWrapper(
+ private val delegate: MangaParser,
+) : MangaParser by delegate {
+
+ override suspend fun getList(searchQuery: MangaSearchQuery): List = withContext(Dispatchers.Default) {
+ if (!searchQuery.skipValidation) {
+ searchQueryCapabilities.validate(searchQuery)
+ }
+ delegate.getList(searchQuery)
+ }
+
+ override suspend fun getDetails(manga: Manga): Manga = withContext(Dispatchers.Default) {
+ delegate.getDetails(manga)
+ }
+
+ override suspend fun getPages(chapter: MangaChapter): List = withContext(Dispatchers.Default) {
+ delegate.getPages(chapter)
+ }
+
+ override suspend fun getPageUrl(page: MangaPage): String = withContext(Dispatchers.Default) {
+ delegate.getPageUrl(page)
+ }
+
+ override suspend fun getFilterOptions(): MangaListFilterOptions = withContext(Dispatchers.Default) {
+ delegate.getFilterOptions()
+ }
+
+ override suspend fun getFavicons(): Favicons = withContext(Dispatchers.Default) {
+ delegate.getFavicons()
+ }
+
+ override suspend fun getRelatedManga(seed: Manga): List = withContext(Dispatchers.Default) {
+ delegate.getRelatedManga(seed)
+ }
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request()
+ val headers = request.headers.newBuilder()
+ .mergeWith(delegate.getRequestHeaders(), replaceExisting = false)
+ .build()
+ val newRequest = request.newBuilder().headers(headers).build()
+ return delegate.intercept(ProxyChain(chain, newRequest))
+ }
+
+ private class ProxyChain(
+ private val delegate: Interceptor.Chain,
+ private val request: Request,
+ ) : Interceptor.Chain by delegate {
+
+ override fun request(): Request = request
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/PagedMangaParser.kt
similarity index 53%
rename from src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
rename to src/main/kotlin/org/koitharu/kotatsu/parsers/core/PagedMangaParser.kt
index 21e4aadad..87c994b09 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/PagedMangaParser.kt
@@ -1,14 +1,13 @@
-package org.koitharu.kotatsu.parsers
+package org.koitharu.kotatsu.parsers.core
import androidx.annotation.VisibleForTesting
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource
-import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
import org.koitharu.kotatsu.parsers.model.search.SearchableField
import org.koitharu.kotatsu.parsers.util.Paginator
-import org.koitharu.kotatsu.parsers.util.convertToMangaListFilter
@InternalParsersApi
public abstract class PagedMangaParser(
@@ -42,42 +41,7 @@ public abstract class PagedMangaParser(
)
}
- public open suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
- return getListPage(
- page = page,
- order = query.order ?: defaultSortOrder,
- filter = convertToMangaListFilter(query),
- )
- }
-
- @Deprecated("New searchManga method should be preferred")
- final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
- return getList(
- paginator = if (filter.query.isNullOrEmpty()) {
- paginator
- } else {
- searchPaginator
- },
- offset = offset,
- order = order,
- filter = filter,
- )
- }
-
- @Deprecated("New searchManga method should be preferred")
- public abstract suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List
-
- private suspend fun getList(
- paginator: Paginator,
- offset: Int,
- order: SortOrder,
- filter: MangaListFilter,
- ): List {
- val page = paginator.getPage(offset)
- val list = getListPage(page, order, filter)
- paginator.onListReceived(offset, page, list.size)
- return list
- }
+ public abstract suspend fun getListPage(query: MangaSearchQuery, page: Int): List
private suspend fun searchManga(
paginator: Paginator,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/SinglePageMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/SinglePageMangaParser.kt
new file mode 100644
index 000000000..1934cd5d5
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/SinglePageMangaParser.kt
@@ -0,0 +1,23 @@
+package org.koitharu.kotatsu.parsers.core
+
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+
+@InternalParsersApi
+public abstract class SinglePageMangaParser(
+ context: MangaLoaderContext,
+ source: MangaParserSource,
+) : AbstractMangaParser(context, source) {
+
+ final override suspend fun getList(query: MangaSearchQuery): List {
+ if (query.offset > 0) {
+ return emptyList()
+ }
+ return getSinglePageList(query)
+ }
+
+ public abstract suspend fun getSinglePageList(searchQuery: MangaSearchQuery): List
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
index 8295d18b6..818cc16cb 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.util.mapToSet
@ExposedCopyVisibility
public data class MangaSearchQueryCapabilities internal constructor(
- val capabilities: Set,
+ public val capabilities: Set,
) {
public constructor(vararg capabilities: SearchCapability) : this(ArraySet(capabilities))
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt
index 1257ccc04..4d6c7306c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt
@@ -7,7 +7,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -20,7 +20,7 @@ import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@MangaSourceParser("BATOTO", "Bato.To")
-internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
+internal class BatoToParser(context: MangaLoaderContext) : LegacyPagedMangaParser(
context = context,
source = MangaParserSource.BATOTO,
pageSize = 60,
@@ -116,7 +116,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
"zbato.net",
"zbato.org",
"fto.to",
- "jto.to"
+ "jto.to",
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt
index 22f0041c7..e0c58f9bd 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt
@@ -7,7 +7,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -20,7 +20,7 @@ private const val CHAPTERS_LIMIT = 99999
@MangaSourceParser("COMICK_FUN", "ComicK")
internal class ComickFunParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) {
override val configKeyDomain = ConfigKey.Domain("comick.io")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt
index 3cdfe301c..f3cd1f2c7 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt
@@ -13,7 +13,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions
@@ -31,7 +31,7 @@ private val TAG_PREFIXES = arrayOf("male:", "female:", "other:")
@MangaSourceParser("EXHENTAI", "ExHentai", type = ContentType.HENTAI)
internal class ExHentaiParser(
context: MangaLoaderContext,
-) : PagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider, Interceptor {
+) : LegacyPagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider, Interceptor {
override val availableSortOrders: Set = setOf(SortOrder.NEWEST)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
index f3a183e29..b618125b9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
@@ -11,7 +11,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.jsoup.Jsoup
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -28,7 +28,7 @@ import kotlin.math.min
@OptIn(ExperimentalUnsignedTypes::class)
@MangaSourceParser("HITOMILA", "Hitomi.La", type = ContentType.HENTAI)
-internal class HitomiLaParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.HITOMILA) {
+internal class HitomiLaParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.HITOMILA) {
override val configKeyDomain = ConfigKey.Domain("hitomi.la")
override fun onCreateConfig(keys: MutableCollection>) {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt
index fbdb9dc76..7a057c9f2 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt
@@ -7,7 +7,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("IMHENTAI", "ImHentai", type = ContentType.HENTAI)
internal class ImHentai(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.IMHENTAI, pageSize = 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.IMHENTAI, pageSize = 20) {
override val availableSortOrders: Set =
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
index 15e606122..508355376 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
@@ -6,7 +6,7 @@ import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
@@ -21,7 +21,7 @@ import javax.crypto.spec.SecretKeySpec
internal abstract class LineWebtoonsParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : AbstractMangaParser(context, source) {
+) : LegacyMangaParser(context, source) {
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
index 628b56652..30066fed9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
@@ -7,8 +7,8 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.HttpUrl
import org.json.JSONArray
import org.json.JSONObject
+import org.koitharu.kotatsu.parsers.core.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -68,17 +68,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : AbstractMangaParser
SortOrder.RELEVANCE,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = MangaListFilterCapabilities(
- isMultipleTagsSupported = true,
- isTagsExclusionSupported = true,
- isSearchSupported = true,
- isSearchWithFiltersSupported = true,
- isYearSupported = true,
- isOriginalLocaleSupported = true,
- isAuthorSearchSupported = true,
- )
-
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
SearchCapability(
@@ -278,10 +267,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : AbstractMangaParser
return json.mapJSON { jo -> jo.fetchManga(null) }
}
- override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
- return queryManga(convertToMangaSearchQuery(offset, order, filter))
- }
-
override suspend fun getDetails(manga: Manga): Manga {
val mangaId = manga.url.removePrefix("/")
return getDetails(mangaId)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt
index 84e36a6e5..fe6467451 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt
@@ -11,7 +11,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -28,7 +28,7 @@ internal abstract class MangaFireParser(
context: MangaLoaderContext,
source: MangaParserSource,
private val siteLang: String,
-) : PagedMangaParser(context, source, 30), Interceptor, MangaParserAuthProvider {
+) : LegacyPagedMangaParser(context, source, 30), Interceptor, MangaParserAuthProvider {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("mangafire.to")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt
index a51c78cb9..44615e75d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt
@@ -4,7 +4,7 @@ import androidx.collection.ArrayMap
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("MANGAPARK", "MangaPark")
internal class MangaPark(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) {
override val configKeyDomain = ConfigKey.Domain(
"mangapark.net",
@@ -31,7 +31,7 @@ internal class MangaPark(context: MangaLoaderContext) :
"parkmanga.com",
"parkmanga.net",
"parkmanga.org",
- "mpark.to"
+ "mpark.to",
)
override fun onCreateConfig(keys: MutableCollection>) {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPlusParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPlusParser.kt
index 76f2f0942..73de0e7ca 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPlusParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPlusParser.kt
@@ -10,7 +10,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -25,7 +25,7 @@ internal abstract class MangaPlusParser(
context: MangaLoaderContext,
source: MangaParserSource,
private val sourceLang: String,
-) : SinglePageMangaParser(context, source), Interceptor {
+) : LegacySinglePageMangaParser(context, source), Interceptor {
private val apiUrl = "https://jumpg-webapi.tokyo-cdn.com/api"
override val configKeyDomain = ConfigKey.Domain("mangaplus.shueisha.co.jp")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt
index 235876a42..6f83bac14 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt
@@ -9,7 +9,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -23,7 +23,7 @@ import kotlin.math.min
@MangaSourceParser("MANGAREADERTO", "MangaReader.To")
internal class MangaReaderToParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGAREADERTO, 16),
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAREADERTO, 16),
Interceptor, MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain("mangareader.to")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt
index edb67a4b2..4ab8f33b6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt
@@ -8,7 +8,7 @@ import okhttp3.Interceptor
import okhttp3.Response
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -19,7 +19,7 @@ internal abstract class NineMangaParser(
context: MangaLoaderContext,
source: MangaParserSource,
defaultDomain: String,
-) : PagedMangaParser(context, source, pageSize = 26), Interceptor {
+) : LegacyPagedMangaParser(context, source, pageSize = 26), Interceptor {
override val configKeyDomain = ConfigKey.Domain(defaultDomain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt
index 5928464b2..889ecf6c8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt
@@ -8,7 +8,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -23,7 +23,7 @@ import java.util.*
@Broken
@MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI)
internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.NINENINENINEHENTAI, PAGE_SIZE), Interceptor {
+ LegacyPagedMangaParser(context, MangaParserSource.NINENINENINEHENTAI, PAGE_SIZE), Interceptor {
override val configKeyDomain = ConfigKey.Domain("animeh.to")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
index 6a6b32644..aed3b9642 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
@@ -8,7 +8,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
@@ -24,7 +24,7 @@ import javax.crypto.spec.SecretKeySpec
internal abstract class WebtoonsParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : AbstractMangaParser(context, source) {
+) : LegacyMangaParser(context, source) {
private val signer by lazy {
WebtoonsUrlSigner("gUtPzJFZch4ZyAGviiyH94P99lQ3pFdRTwpJWDlSGFfwgpr6ses5ALOxWHOIT7R1")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt
index 994f65eef..f8edc5e96 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class AnimeBootstrapParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt
index f0e92ddbc..791ce35c1 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt
@@ -7,7 +7,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,8 @@ import java.util.*
@Broken
@MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar")
-internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) {
+internal class FlixScans(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("flixscans.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt
index e85b35346..4f0155670 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.ar
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -12,7 +12,8 @@ import java.util.*
@Broken
@MangaSourceParser("MANGASTORM", "MangaStorm", "ar")
-internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGASTORM, 30) {
+internal class MangaStorm(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGASTORM, 30) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("mangastorm.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt
index 876bd023c..a302c81bd 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("TEAMXNOVEL", "TeamXNovel", "ar")
-internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) {
+internal class TeamXNovel(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
index 8dedb155b..9d486a9d6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
@@ -4,21 +4,24 @@ import androidx.collection.ArraySet
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
-import org.koitharu.kotatsu.parsers.util.*
+import org.koitharu.kotatsu.parsers.util.generateUid
+import org.koitharu.kotatsu.parsers.util.getDomain
import org.koitharu.kotatsu.parsers.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
+import org.koitharu.kotatsu.parsers.util.nullIfEmpty
+import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import java.util.*
@Broken
@MangaSourceParser("ANIBEL", "Anibel", "be")
-internal class AnibelParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.ANIBEL) {
+internal class AnibelParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.ANIBEL) {
override val configKeyDomain = ConfigKey.Domain("anibel.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/cupfox/CupFoxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/cupfox/CupFoxParser.kt
index 17b3be28d..489726c1c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/cupfox/CupFoxParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/cupfox/CupFoxParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.cupfox
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +13,7 @@ internal abstract class CupFoxParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/AsuraScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/AsuraScansParser.kt
index 024275593..8c2b9cec7 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/AsuraScansParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/AsuraScansParser.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.sync.withLock
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,7 @@ import java.util.*
@MangaSourceParser("ASURASCANS", "AsuraComic", "en")
internal class AsuraScansParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.ASURASCANS, pageSize = 30) {
+ LegacyPagedMangaParser(context, MangaParserSource.ASURASCANS, pageSize = 30) {
override val configKeyDomain = ConfigKey.Domain("asuracomic.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt
index 0924068c3..e95b88d45 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -11,7 +11,7 @@ import java.util.*
@MangaSourceParser("BEETOON", "BeeToon.net", "en")
internal class BeeToon(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BEETOON, pageSize = 30) {
+ LegacyPagedMangaParser(context, MangaParserSource.BEETOON, pageSize = 30) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt
index 1505eb1a6..e37842165 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("CLONEMANGA", "CloneManga", "en")
internal class CloneMangaParser(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.CLONEMANGA) {
+ LegacySinglePageMangaParser(context, MangaParserSource.CLONEMANGA) {
override val availableSortOrders: Set = Collections.singleton(
SortOrder.POPULARITY,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt
index 99fc790bf..3391b5427 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -11,7 +11,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en", ContentType.COMICS)
-internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.COMICEXTRA, 36) {
+internal class ComicExtra(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.COMICEXTRA, 36) {
override val configKeyDomain = ConfigKey.Domain("azcomix.me")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt
index 6c27a14d5..1babf16df 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt
@@ -9,7 +9,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -19,7 +19,7 @@ import java.util.*
@MangaSourceParser("DYNASTYSCANS", "DynastyScans", "en")
internal class DynastyScans(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.DYNASTYSCANS, 117) {
+ LegacyPagedMangaParser(context, MangaParserSource.DYNASTYSCANS, 117) {
override val configKeyDomain = ConfigKey.Domain("dynasty-scans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlameComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlameComics.kt
index 3cb8fd6aa..df7c8cc0d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlameComics.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlameComics.kt
@@ -6,7 +6,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit
@MangaSourceParser("FLAMECOMICS", "FlameComics", "en")
internal class FlameComics(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.FLAMECOMICS) {
+ LegacySinglePageMangaParser(context, MangaParserSource.FLAMECOMICS) {
private val commonPrefix = suspendLazy(initializer = ::fetchCommonPrefix)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt
index 7f2c89668..5b2ba4b2b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt
@@ -6,6 +6,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.*
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.asTypedList
@@ -16,7 +17,7 @@ import java.util.*
@Broken
@MangaSourceParser("FLIXSCANSORG", "FlixScans.org", "en")
internal class FlixScansOrg(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("flixscans.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt
index bdfb6a85b..7cde28a0c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("MANGAGEKO", "MangaGeko", "en")
-internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAGEKO, 30) {
+internal class MangaGeko(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAGEKO, 30) {
override val availableSortOrders: Set =
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt
index 7b563ee35..3fd19fad8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("MANGAKAWAII_EN", "MangaKawaii En", "en")
internal class MangaKawaiiEn(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGAKAWAII_EN, 50) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAKAWAII_EN, 50) {
override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt
index 2916122bb..6a19264ca 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANGATOWN", "MangaTown", "en")
internal class MangaTownParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGATOWN, 30) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANGATOWN, 30) {
override val configKeyDomain = ConfigKey.Domain("www.mangatown.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt
index f28f91563..8d27d8a4a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt
@@ -5,7 +5,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -17,7 +17,7 @@ import java.util.*
@Broken
@MangaSourceParser("MANGAOWL", "MangaOwl.to", "en")
internal class Mangaowl(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGAOWL, pageSize = 24) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAOWL, pageSize = 24) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.POPULARITY,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt
index f957f62fe..6757a050b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", type = ContentType.HENTAI)
internal class Manhwa18Com(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANHWA18COM, pageSize = 18, searchPageSize = 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANHWA18COM, pageSize = 18, searchPageSize = 18) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt
index 738c541a0..3ef86cca9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI)
internal class Manhwa18Parser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt
index 0b2b41bf1..6c317424a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWASMEN", "ManhwasMen", "en", type = ContentType.HENTAI)
internal class ManhwasMen(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANHWASMEN, pageSize = 30, searchPageSize = 30) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANHWASMEN, pageSize = 30, searchPageSize = 30) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwas.men")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt
index 467b29976..d6be64bd8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt
@@ -2,14 +2,15 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("MYCOMICLIST", "MyComicList", "en", ContentType.COMICS)
-internal class MyComicList(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MYCOMICLIST, 24) {
+internal class MyComicList(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MYCOMICLIST, 24) {
override val configKeyDomain = ConfigKey.Domain("mycomiclist.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt
index f68bae248..4b3592d2e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,8 @@ import java.util.*
@Broken
@MangaSourceParser("PO2SCANS", "Po2Scans", "en")
-internal class Po2Scans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.PO2SCANS) {
+internal class Po2Scans(context: MangaLoaderContext) :
+ LegacySinglePageMangaParser(context, MangaParserSource.PO2SCANS) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL)
override val configKeyDomain = ConfigKey.Domain("po2scans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt
index 6c8650008..34e8e27a3 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ import java.util.*
@Broken
@MangaSourceParser("PURURIN", "Pururin", "en", ContentType.HENTAI)
internal class Pururin(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) {
override val configKeyDomain = ConfigKey.Domain("pururin.to")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/VyManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/VyManga.kt
index 51b51560f..de83f0276 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/VyManga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/VyManga.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("VYMANGA", "VyManga", "en")
internal class VyManga(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.VYMANGA, pageSize = 36) {
+ LegacyPagedMangaParser(context, MangaParserSource.VYMANGA, pageSize = 36) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("vymanga.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
index 51a2342ab..78a0aa564 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
@@ -5,6 +5,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.jsoup.nodes.*
import org.koitharu.kotatsu.parsers.*
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.MangaState.*
import org.koitharu.kotatsu.parsers.model.ContentType.*
@@ -16,7 +17,7 @@ import java.util.EnumSet
import java.util.Locale
@MangaSourceParser("WEEBCENTRAL", "Weeb Central", "en")
-internal class WeebCentral(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.WEEBCENTRAL),
+internal class WeebCentral(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.WEEBCENTRAL),
MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain("weebcentral.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt
index 28b7b31d1..91a065121 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.es
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
internal class TempleScanEsp(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.TEMPLESCANESP) {
+ LegacySinglePageMangaParser(context, MangaParserSource.TEMPLESCANESP) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.NEWEST_ASC)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt
index 06f18b9f5..c1ceb38e8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt
@@ -6,7 +6,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("TUMANGAONLINE", "TuMangaOnline", "es")
-internal class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
+internal class TuMangaOnlineParser(context: MangaLoaderContext) : LegacyPagedMangaParser(
context,
source = MangaParserSource.TUMANGAONLINE,
pageSize = 24,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt
index bb2b89f55..e0995e334 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class FmreaderParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt
index 343c90696..047fca3e8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class FoolSlideParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 25,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt
index 469e63915..9f4a5c9b5 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -19,7 +19,7 @@ import java.util.*
@Broken
@MangaSourceParser("BENTOMANGA", "BentoManga", "fr")
internal class BentomangaParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BENTOMANGA, 10) {
+ LegacyPagedMangaParser(context, MangaParserSource.BENTOMANGA, 10) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.UPDATED,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt
index 2fac73ac3..72604c122 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt
@@ -5,7 +5,7 @@ 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.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -16,7 +16,7 @@ import java.util.*
@MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr")
internal class FuryoSociety(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.FURYOSOCIETY) {
+ LegacySinglePageMangaParser(context, MangaParserSource.FURYOSOCIETY) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt
index b4cbf8e96..194a6ba45 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt
@@ -4,7 +4,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("LEGACY_SCANS", "LegacyScans", "fr")
internal class LegacyScansParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) {
override val configKeyDomain = ConfigKey.Domain("legacy-scans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt
index c62617c3b..7b7034232 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@Broken
@MangaSourceParser("LIRESCAN", "LireScan", "fr")
-internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LIRESCAN, 20) {
+internal class LireScan(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.LIRESCAN, 20) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt
index 1f04d9c10..1a69abd66 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt
@@ -4,7 +4,7 @@ import org.json.JSONArray
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -17,7 +17,7 @@ import java.util.*
@MangaSourceParser("LUGNICASCANS", "LugnicaScans", "fr")
internal class LugnicaScans(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) {
+ LegacyPagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) {
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt
index 18e5b5f85..52a436469 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt
@@ -5,14 +5,15 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("MANGAKAWAII", "MangaKawaii Fr", "fr")
-internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAKAWAII, 50) {
+internal class MangaKawaii(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAKAWAII, 50) {
override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt
index bf768540d..c80517c03 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt
@@ -11,7 +11,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -22,7 +22,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("MANGAMANA", "MangaMana", "fr")
-internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAMANA, 25) {
+internal class MangaMana(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAMANA, 25) {
override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt
index 6972afe37..f2cca594c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt
@@ -6,7 +6,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -16,7 +16,7 @@ import java.util.*
@Broken
@MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr")
internal class ScansMangasMe(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) {
+ LegacySinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.ALPHABETICAL,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt
index cd4628249..fd7a15eee 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("SCANTRADUNION", "ScantradUnion", "fr")
internal class ScantradUnion(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) {
+ LegacyPagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) {
override val configKeyDomain = ConfigKey.Domain("scantrad-union.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fuzzydoodle/FuzzyDoodleParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fuzzydoodle/FuzzyDoodleParser.kt
index d60886144..bfd7a0c95 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fuzzydoodle/FuzzyDoodleParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fuzzydoodle/FuzzyDoodleParser.kt
@@ -7,7 +7,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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -20,7 +20,7 @@ internal abstract class FuzzyDoodleParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt
index 000478e5c..e1caf3a5b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -19,7 +19,7 @@ internal abstract class GalleryAdultsParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/gattsu/GattsuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/gattsu/GattsuParser.kt
index c66f854e0..00a984b19 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/gattsu/GattsuParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/gattsu/GattsuParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.gattsu
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ internal abstract class GattsuParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/guya/GuyaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/guya/GuyaParser.kt
index 7e1ad7c4f..37073bc5e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/guya/GuyaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/guya/GuyaParser.kt
@@ -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.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ internal abstract class GuyaParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : SinglePageMangaParser(context, source) {
+) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt
index 48417f58c..5ab01c5cb 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.heancms
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -19,7 +19,7 @@ internal abstract class HeanCms(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt
index c2f944c89..7f285db7b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.heancmsalt
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class HeanCmsAlt(
source: MangaParserSource,
domain: String,
pageSize: Int = 18,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/hotcomics/HotComicsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/hotcomics/HotComicsParser.kt
index c00c2bf41..54bf1618f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/hotcomics/HotComicsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/hotcomics/HotComicsParser.kt
@@ -7,7 +7,7 @@ import kotlinx.coroutines.sync.withLock
import okhttp3.Headers
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -20,7 +20,7 @@ internal abstract class HotComicsParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt
index c92ae232e..939eb5773 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.id
import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("DOUJINDESU", "DoujinDesu.tv", "id")
internal class DoujinDesuParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.DOUJINDESU, pageSize = 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.DOUJINDESU, pageSize = 18) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("doujindesu.tv")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/HentaiCrot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/HentaiCrot.kt
index 16df73ad9..846369d5b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/HentaiCrot.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/HentaiCrot.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("HENTAICROT", "HentaiCrot", "id", ContentType.HENTAI)
internal class HentaiCrot(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.HENTAICROT, 8) {
+ LegacyPagedMangaParser(context, MangaParserSource.HENTAICROT, 8) {
override val configKeyDomain = ConfigKey.Domain("hentaicrot.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/PixHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/PixHentai.kt
index d597ec472..09cb73a43 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/PixHentai.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/PixHentai.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("PIXHENTAI", "PixHentai", "id", ContentType.HENTAI)
internal class PixHentai(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.PIXHENTAI, 8) {
+ LegacyPagedMangaParser(context, MangaParserSource.PIXHENTAI, 8) {
override val configKeyDomain = ConfigKey.Domain("pixhentai.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/iken/IkenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/iken/IkenParser.kt
index 62ca74dcc..ac1b16f33 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/iken/IkenParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/iken/IkenParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.iken
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,7 @@ internal abstract class IkenParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 18,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
index 461adf165..17868f71c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.ja
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -16,7 +16,7 @@ private const val STATUS_FINISHED = "完結"
@MangaSourceParser("NICOVIDEO_SEIGA", "NicoVideo Seiga", "ja")
internal class NicovideoSeigaParser(context: MangaLoaderContext) :
- AbstractMangaParser(context, MangaParserSource.NICOVIDEO_SEIGA),
+ LegacyMangaParser(context, MangaParserSource.NICOVIDEO_SEIGA),
MangaParserAuthProvider {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt
index f74ab8a9c..08de052e4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt
@@ -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.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class KeyoappParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : SinglePageMangaParser(context, source) {
+) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt
index 36826964b..af7319981 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt
@@ -7,7 +7,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -20,7 +20,7 @@ internal abstract class LikeMangaParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 36,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt
index 7e655c99c..d16ca46d1 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class LilianaParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt
index 5a61e5815..d0ca91ea7 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -23,7 +23,7 @@ internal abstract class MadaraParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 12,
-) : PagedMangaParser(context, source, pageSize), MangaParserAuthProvider {
+) : LegacyPagedMangaParser(context, source, pageSize), MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt
index 4ddd47b41..7f66c277b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class MadthemeParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 48,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt
index 4c0945e2c..f297393ac 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class Manga18Parser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
index d5ad87536..e636a7df4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
@@ -4,8 +4,8 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.PagedMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
@@ -36,14 +36,6 @@ internal abstract class MangaboxParser(
SortOrder.ALPHABETICAL,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = MangaListFilterCapabilities(
- isMultipleTagsSupported = true,
- isTagsExclusionSupported = true,
- isSearchSupported = true,
- isSearchWithFiltersSupported = true,
- )
-
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
SearchCapability(
@@ -192,15 +184,11 @@ internal abstract class MangaboxParser(
authors = emptySet(),
state = null,
source = source,
- contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
+ contentRating = if (source.contentType == ContentType.HENTAI) ContentRating.ADULT else ContentRating.SAFE,
)
}
}
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- return queryManga(convertToMangaSearchQuery(page, order, filter))
- }
-
protected open val selectTagMap = "div.panel-genres-list a:not(.genres-select)"
protected open suspend fun fetchAvailableTags(): Set {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
index 7fb735569..acecac637 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
@@ -37,12 +37,6 @@ internal class Mangairo(context: MangaLoaderContext) :
SortOrder.POPULARITY,
SortOrder.NEWEST,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = super.filterCapabilities.copy(
- isTagsExclusionSupported = false,
- isMultipleTagsSupported = false,
- isSearchWithFiltersSupported = false,
- )
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
@@ -139,78 +133,7 @@ internal class Mangairo(context: MangaLoaderContext) :
authors = emptySet(),
state = null,
source = source,
- contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
- )
- }
- }
-
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- val url = buildString {
- append("https://")
- append(domain)
- when {
-
- !filter.query.isNullOrEmpty() -> {
- append(searchUrl)
- append(filter.query.urlEncoded())
- append("?page=")
- }
-
- else -> {
- append(listUrl)
- append("/type-")
- when (order) {
- SortOrder.POPULARITY -> append("topview")
- SortOrder.UPDATED -> append("latest")
- SortOrder.NEWEST -> append("newest")
- else -> append("latest")
- }
-
- append("/ctg-")
- if (filter.tags.isNotEmpty()) {
- filter.tags.oneOrThrowIfMany()?.let {
- append(it.key)
- }
- } else {
- append("all")
- }
-
- append("/state-")
- if (filter.states.isNotEmpty()) {
- filter.states.oneOrThrowIfMany()?.let {
- append(
- when (it) {
- MangaState.ONGOING -> "ongoing"
- MangaState.FINISHED -> "completed"
- else -> "all"
- },
- )
- }
- } else {
- append("all")
- }
-
- append("/page-")
- }
- }
- append(page.toString())
- }
- val doc = webClient.httpGet(url).parseHtml()
- return doc.select("div.story-item").map { div ->
- val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
- Manga(
- id = generateUid(href),
- url = href,
- publicUrl = href.toAbsoluteUrl(div.host ?: domain),
- coverUrl = div.selectFirst("img")?.src().orEmpty(),
- title = (div.selectFirst("h2")?.text() ?: div.selectFirst("h3")?.text()).orEmpty(),
- altTitle = null,
- rating = RATING_UNKNOWN,
- tags = emptySet(),
- author = null,
- state = null,
- source = source,
- isNsfw = isNsfwSource,
+ contentRating = if (source.contentType == ContentType.HENTAI) ContentRating.ADULT else ContentRating.SAFE,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
index 81300ba20..f6e36932e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
@@ -22,12 +22,6 @@ internal class Mangakakalot(context: MangaLoaderContext) :
SortOrder.POPULARITY,
SortOrder.NEWEST,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = super.filterCapabilities.copy(
- isTagsExclusionSupported = false,
- isMultipleTagsSupported = false,
- isSearchWithFiltersSupported = false,
- )
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
@@ -148,7 +142,7 @@ internal class Mangakakalot(context: MangaLoaderContext) :
authors = emptySet(),
state = null,
source = source,
- contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
+ contentRating = null,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
index 7cfda5848..bf82bba12 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
@@ -25,13 +25,6 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
SortOrder.POPULARITY,
SortOrder.NEWEST,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = super.filterCapabilities.copy(
- isTagsExclusionSupported = false,
- isMultipleTagsSupported = false,
- isSearchWithFiltersSupported = false,
- )
-
override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaSearchQueryCapabilities(
@@ -140,7 +133,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
authors = emptySet(),
state = null,
source = source,
- contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
+ contentRating = null,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt
index 11b1d603a..8458d889c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.mangadventure
import okhttp3.HttpUrl
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.*
@@ -17,7 +17,7 @@ internal abstract class MangAdventureParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 25,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt
index 262e43bad..40b09d0f4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt
@@ -12,7 +12,7 @@ import okhttp3.internal.closeQuietly
import org.json.JSONObject
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -25,7 +25,7 @@ internal abstract class MangaReaderParser(
domain: String,
pageSize: Int,
searchPageSize: Int,
-) : PagedMangaParser(context, source, pageSize, searchPageSize), Interceptor {
+) : LegacyPagedMangaParser(context, source, pageSize, searchPageSize), Interceptor {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt
index a86c1ccff..3d7698a28 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.mangaworld
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ internal abstract class MangaWorldParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 16,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.POPULARITY,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt
index 6b6e51345..96cad9971 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class MmrcmsParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
index b99c62ebf..0e3863b8a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.nepnep
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -20,7 +20,7 @@ internal abstract class NepnepParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : AbstractMangaParser(context, source) {
+) : LegacyMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/onemanga/OneMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/onemanga/OneMangaParser.kt
index 78a449573..66bbcc442 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/onemanga/OneMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/onemanga/OneMangaParser.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.onemanga
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -11,7 +11,7 @@ internal abstract class OneMangaParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : SinglePageMangaParser(context, source) {
+) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt
index a82cb3850..c98a2e9b1 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.otakusanctuary
import kotlinx.coroutines.coroutineScope
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class OtakuSanctuaryParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 32,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pizzareader/PizzaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pizzareader/PizzaReaderParser.kt
index 720c3a78d..f0c412549 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pizzareader/PizzaReaderParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pizzareader/PizzaReaderParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -19,7 +19,7 @@ internal abstract class PizzaReaderParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : SinglePageMangaParser(context, source) {
+) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt
index f0122a50f..dbad70a7e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -12,7 +12,7 @@ import java.util.*
@Broken
@MangaSourceParser("BRMANGAS", "BrMangas", "pt")
-internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.BRMANGAS, 25) {
+internal class BrMangas(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.BRMANGAS, 25) {
override val configKeyDomain = ConfigKey.Domain("www.brmangas.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt
index cf91ec602..db9f18f60 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.*
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
@@ -9,7 +10,7 @@ import java.util.*
@Broken
@MangaSourceParser("LERMANGA", "LerManga", "pt")
-internal class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LERMANGA, 24) {
+internal class LerManga(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.LERMANGA, 24) {
override val configKeyDomain = ConfigKey.Domain("lermanga.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt
index cbcf2e046..28cfebeeb 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt
@@ -4,7 +4,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.util.*
@Broken
@MangaSourceParser("LERMANGAONLINE", "LerMangaOnline", "pt")
internal class LerMangaOnline(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.LERMANGAONLINE, 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.LERMANGAONLINE, 20) {
override val configKeyDomain = ConfigKey.Domain("lermangaonline.com.br")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt
index e58848ab4..698152c10 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt
@@ -8,6 +8,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody
import org.json.JSONArray
import org.koitharu.kotatsu.parsers.*
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
@@ -16,7 +17,7 @@ import java.util.zip.ZipInputStream
@Broken // Not dead but changed template
@MangaSourceParser("RANDOMSCANS", "LuratoonScan", "pt")
internal class LuratoonScansParser(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.RANDOMSCANS),
+ LegacySinglePageMangaParser(context, MangaParserSource.RANDOMSCANS),
Interceptor {
override val configKeyDomain = ConfigKey.Domain("luratoons.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt
index 678d28424..51779b90c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("MANGAONLINE", "MangaOnline.biz", "pt")
-internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAONLINE, 20) {
+internal class MangaOnline(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAONLINE, 20) {
override val configKeyDomain = ConfigKey.Domain("mangaonline.biz")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MuitoHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MuitoHentai.kt
index a9c376297..15d7bccc0 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MuitoHentai.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MuitoHentai.kt
@@ -2,14 +2,15 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("MUITOHENTAI", "MuitoHentai", "pt", ContentType.HENTAI)
-internal class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MUITOHENTAI, 24) {
+internal class MuitoHentai(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MUITOHENTAI, 24) {
override val configKeyDomain = ConfigKey.Domain("www.muitohentai.com")
@@ -125,7 +126,7 @@ internal class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(conte
.map { url ->
if (url.startsWith("https://$domain/")) {
url.substringAfter("$domain/")
- } else url
+ } else url
}
return src.map { url ->
@@ -133,7 +134,7 @@ internal class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(conte
id = generateUid(url),
url = url.takeUnless { it.startsWith("https://") }?.let { "https://$domain/$it" } ?: url,
preview = null,
- source = source
+ source = source,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt
index f224747b4..5ae1f8ac2 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -11,7 +11,8 @@ import java.util.*
@Broken
@MangaSourceParser("ONEPIECEEX", "OnePieceEx", "pt")
-internal class OnePieceEx(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.ONEPIECEEX) {
+internal class OnePieceEx(context: MangaLoaderContext) :
+ LegacySinglePageMangaParser(context, MangaParserSource.ONEPIECEEX) {
override val configKeyDomain = ConfigKey.Domain("onepieceex.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt
index c9c01d984..91027aa8f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("YUGENMANGAS", "YugenApp", "pt")
internal class YugenMangas(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.YUGENMANGAS) {
+ LegacySinglePageMangaParser(context, MangaParserSource.YUGENMANGAS) {
override val configKeyDomain = ConfigKey.Domain("yugenmangasbr.voblog.xyz")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/AComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/AComics.kt
index 6d78fb384..4cc30ceb4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/AComics.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/AComics.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.sync.withLock
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("ACOMICS", "AComics", "ru", ContentType.COMICS)
internal class AComics(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.ACOMICS, pageSize = 10) {
+ LegacyPagedMangaParser(context, MangaParserSource.ACOMICS, pageSize = 10) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.UPDATED,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/DesuMeParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/DesuMeParser.kt
index 8a091dabd..db04af545 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/DesuMeParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/DesuMeParser.kt
@@ -5,7 +5,7 @@ import okhttp3.Headers
import okhttp3.HttpUrl
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -17,7 +17,8 @@ import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
import java.util.*
@MangaSourceParser("DESUME", "Desu", "ru")
-internal class DesuMeParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.DESUME, 20) {
+internal class DesuMeParser(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.DESUME, 20) {
override val configKeyDomain = ConfigKey.Domain("desu.me", "desu.win")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/MangaWtfParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/MangaWtfParser.kt
index 4181644f2..46b9f1acd 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/MangaWtfParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/MangaWtfParser.kt
@@ -7,7 +7,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,7 @@ import java.util.*
@MangaSourceParser("MANGA_WTF", "MangaWtf", "ru")
internal class MangaWtfParser(
context: MangaLoaderContext,
-) : PagedMangaParser(context, MangaParserSource.MANGA_WTF, pageSize = 20) {
+) : LegacyPagedMangaParser(context, MangaParserSource.MANGA_WTF, pageSize = 20) {
override val availableSortOrders: Set =
EnumSet.of(
@@ -137,13 +137,13 @@ internal class MangaWtfParser(
tags = jo.getJSONArray("labels").mapJSONToSet { it.toMangaTag() },
state = jo.getStringOrNull("status")?.toMangaState(),
author =
- jo.getJSONArray("relations").asTypedList().firstNotNullOfOrNull {
- if (it.getStringOrNull("type") == "AUTHOR") {
- it.getJSONObject("publisher").getStringOrNull("name")
- } else {
- null
- }
- },
+ jo.getJSONArray("relations").asTypedList().firstNotNullOfOrNull {
+ if (it.getStringOrNull("type") == "AUTHOR") {
+ it.getJSONObject("publisher").getStringOrNull("name")
+ } else {
+ null
+ }
+ },
source = source,
largeCoverUrl = null,
description = jo.getString("description").nl2br(),
@@ -209,10 +209,10 @@ internal class MangaWtfParser(
MangaChapter(
id = generateUid(jo.getString("id")),
name =
- jo.getStringOrNull("name") ?: buildString {
- if (volume > 0) append("Том ").append(volume).append(' ')
- if (number > 0) append("Глава ").append(number) else append("Без имени")
- },
+ jo.getStringOrNull("name") ?: buildString {
+ if (volume > 0) append("Том ").append(volume).append(' ')
+ if (number > 0) append("Глава ").append(number) else append("Без имени")
+ },
number = number,
volume = volume,
url = jo.getString("id"),
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
index d13ef133b..dd29bb53a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.ru
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("NUDEMOON", "Nude-Moon", "ru", type = ContentType.HENTAI)
internal class NudeMoonParser(
context: MangaLoaderContext,
-) : AbstractMangaParser(context, MangaParserSource.NUDEMOON), MangaParserAuthProvider {
+) : LegacyMangaParser(context, MangaParserSource.NUDEMOON), MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain(
"b.nude-moon.fun",
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/RemangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/RemangaParser.kt
index 0c264b25d..25858c7d8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/RemangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/RemangaParser.kt
@@ -10,7 +10,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -29,7 +29,7 @@ private const val TOO_MANY_REQUESTS = 429
@MangaSourceParser("REMANGA", "Реманга", "ru")
internal class RemangaParser(
context: MangaLoaderContext,
-) : PagedMangaParser(context, MangaParserSource.REMANGA, PAGE_SIZE), MangaParserAuthProvider, Interceptor {
+) : LegacyPagedMangaParser(context, MangaParserSource.REMANGA, PAGE_SIZE), MangaParserAuthProvider, Interceptor {
private val baseHeaders: Headers
get() = Headers.Builder()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/WaMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/WaMangaParser.kt
index 943b5aa71..99850a045 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/WaMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/WaMangaParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.ru
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("WAMANGA", "WaManga", "ru", type = ContentType.MANGA)
internal class WaMangaParser(
context: MangaLoaderContext,
-) : SinglePageMangaParser(context, MangaParserSource.WAMANGA) {
+) : LegacySinglePageMangaParser(context, MangaParserSource.WAMANGA) {
override val configKeyDomain = ConfigKey.Domain("wamanga.me")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
index 83c338ede..f532e4031 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
@@ -17,7 +17,7 @@ import okio.IOException
import org.json.JSONArray
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
@@ -43,7 +43,7 @@ internal abstract class GroupleParser(
context: MangaLoaderContext,
source: MangaParserSource,
private val siteId: Int,
-) : AbstractMangaParser(context, source), MangaParserAuthProvider, Interceptor {
+) : LegacyMangaParser(context, source), MangaParserAuthProvider, Interceptor {
@Volatile
private var cachedPagesServer: String? = null
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
index 2b52ad802..2d013cba8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.ru.multichan
import okhttp3.HttpUrl
import org.jsoup.internal.StringUtil
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.*
@@ -14,7 +14,7 @@ import java.util.*
internal abstract class ChanParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : AbstractMangaParser(context, source), MangaParserAuthProvider {
+) : LegacyMangaParser(context, source), MangaParserAuthProvider {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.NEWEST,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt
index ab80425ca..70a113f2e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt
@@ -7,7 +7,7 @@ import okhttp3.HttpUrl
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -21,7 +21,7 @@ internal abstract class LibSocialParser(
source: MangaParserSource,
protected val siteDomain: String,
protected val siteId: Int,
-) : PagedMangaParser(context, source, pageSize = 60) {
+) : LegacyPagedMangaParser(context, source, pageSize = 60) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.UPDATED,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
index e2636664e..557c8b4ec 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.jsoup.Jsoup
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,7 @@ internal abstract class ScanParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 0,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt
index 28a1eccff..ad3bbe2fc 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.sinmh
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ internal abstract class SinmhParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 36,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt
index 21e28e928..41bfe9d28 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.sync.withLock
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("MANGAAY", "MangaAy", "tr")
-internal class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAAY, 45) {
+internal class MangaAy(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.MANGAAY, 45) {
override val configKeyDomain = ConfigKey.Domain("manga-ay.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt
index ccb6d26a2..bca4fbf66 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.tr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("SADSCANS", "SadScans", "tr")
-internal class SadScans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.SADSCANS) {
+internal class SadScans(context: MangaLoaderContext) :
+ LegacySinglePageMangaParser(context, MangaParserSource.SADSCANS) {
override val configKeyDomain = ConfigKey.Domain("sadscans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
index 19d14bbd0..44935e003 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.tr
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("TRWEBTOON", "TrWebtoon", "tr")
internal class TrWebtoon(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.TRWEBTOON, pageSize = 21) {
+ LegacyPagedMangaParser(context, MangaParserSource.TRWEBTOON, pageSize = 21) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("trwebtoon.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
index 330a9b422..bfd618e0e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
@@ -8,7 +8,7 @@ import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -25,7 +25,7 @@ private const val PAGE_SIZE = 60
// NOTE High profile focus
@MangaSourceParser("HENTAIUKR", "HentaiUkr", "uk", ContentType.HENTAI)
-internal class HentaiUkrParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.HENTAIUKR),
+internal class HentaiUkrParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.HENTAIUKR),
Interceptor {
private val date = SimpleDateFormat("yyyy-MM-dd", Locale.US)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
index 3c8428040..b5f6c7104 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
@@ -7,7 +7,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -27,7 +27,7 @@ private const val IMAGE_BASEURL_FALLBACK = "https://hmvolumestorage.b-cdn.net/pu
@MangaSourceParser("HONEYMANGA", "HoneyManga", "uk")
internal class HoneyMangaParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.HONEYMANGA, PAGE_SIZE),
+ LegacyPagedMangaParser(context, MangaParserSource.HONEYMANGA, PAGE_SIZE),
Interceptor {
private val urlApi get() = "https://data.api.$domain"
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
index ef0755baf..90c569611 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.uk
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
private const val DEF_BRANCH_NAME = "Основний переклад"
@MangaSourceParser("MANGAINUA", "MANGA/in/UA", "uk")
-internal class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
+internal class MangaInUaParser(context: MangaLoaderContext) : LegacyPagedMangaParser(
context = context,
source = MangaParserSource.MANGAINUA,
pageSize = 24,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt
index eb7b86a08..a8beef37e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt
@@ -5,7 +5,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -17,7 +17,7 @@ import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("BLOGTRUYEN", "Blog Truyện", "vi")
internal class BlogTruyenParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BLOGTRUYEN, pageSize = 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.BLOGTRUYEN, pageSize = 20) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("blogtruyenmoi.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt
index 74df4cc7b..43f97d04b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt
@@ -7,7 +7,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -20,7 +20,7 @@ import java.util.*
@Broken
@MangaSourceParser("BLOGTRUYENVN", "BlogTruyen.vn (Unofficial)", "vi")
internal class BlogTruyenVN(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BLOGTRUYENVN, pageSize = 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.BLOGTRUYENVN, pageSize = 20) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("blogtruyenvn.org", "blogtruyenvn.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CMangaParser.kt
index 6043ef059..ebd36e912 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CMangaParser.kt
@@ -6,7 +6,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.Manga
@@ -20,7 +20,6 @@ import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.model.SortOrder
-import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.getCookies
import org.koitharu.kotatsu.parsers.util.json.asTypedList
@@ -46,7 +45,7 @@ private const val PAGE_SIZE = 20
@MangaSourceParser("CMANGA", "CManga", "vi")
internal class CMangaParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.CMANGA, PAGE_SIZE), MangaParserAuthProvider {
+ LegacyPagedMangaParser(context, MangaParserSource.CMANGA, PAGE_SIZE), MangaParserAuthProvider {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("cmangax.com")
@@ -131,7 +130,7 @@ internal class CMangaParser(context: MangaLoaderContext) :
order == SortOrder.POPULARITY_TODAY ||
order == SortOrder.POPULARITY_WEEK ||
order == SortOrder.POPULARITY_MONTH
- )
+ )
) {
addPathSegments("api/home_album_top")
} else {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt
index 7ede58464..e3137c80d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt
@@ -10,7 +10,7 @@ import okio.IOException
import org.jsoup.HttpStatusException
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -25,7 +25,7 @@ import java.util.TimeZone
@MangaSourceParser("CUUTRUYEN", "Cứu Truyện", "vi")
internal class CuuTruyenParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor {
+ LegacyPagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor {
override val userAgentKey = ConfigKey.UserAgent(UserAgents.KOTATSU)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt
index 7758ed43f..8593b2767 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("DUALEOTRUYEN", "Dưa Leo Truyện", "vi", type = ContentType.HENTAI)
internal class DuaLeoTruyen(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.DUALEOTRUYEN, 60) {
+ LegacyPagedMangaParser(context, MangaParserSource.DUALEOTRUYEN, 60) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("dualeotruyenxxy.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/GocTruyenTranh.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/GocTruyenTranh.kt
index fa16abb2b..98c30f83b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/GocTruyenTranh.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/GocTruyenTranh.kt
@@ -3,85 +3,86 @@ package org.koitharu.kotatsu.parsers.site.vi
import androidx.collection.arraySetOf
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
-import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("GOCTRUYENTRANH", "Góc Truyện Tranh", "vi")
internal class GocTruyenTranh(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.GOCTRUYENTRANH, 30) {
-
- override val configKeyDomain = ConfigKey.Domain("goctruyentranh.net")
+ LegacyPagedMangaParser(context, MangaParserSource.GOCTRUYENTRANH, 30) {
- override fun onCreateConfig(keys: MutableCollection>) {
+ override val configKeyDomain = ConfigKey.Domain("goctruyentranh.net")
+
+ override fun onCreateConfig(keys: MutableCollection>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
- override val availableSortOrders: Set = EnumSet.of(
+ override val availableSortOrders: Set = EnumSet.of(
SortOrder.UPDATED,
- SortOrder.NEWEST,
+ SortOrder.NEWEST,
SortOrder.NEWEST_ASC,
SortOrder.ALPHABETICAL,
SortOrder.RATING,
- SortOrder.POPULARITY
+ SortOrder.POPULARITY,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = MangaListFilterCapabilities(
- isMultipleTagsSupported = true,
- isTagsExclusionSupported = false,
- isSearchSupported = true,
- isSearchWithFiltersSupported = true,
- )
+ override val filterCapabilities: MangaListFilterCapabilities
+ get() = MangaListFilterCapabilities(
+ isMultipleTagsSupported = true,
+ isTagsExclusionSupported = false,
+ isSearchSupported = true,
+ isSearchWithFiltersSupported = true,
+ )
- override suspend fun getFilterOptions() = MangaListFilterOptions(
- availableTags = availableTags(),
- availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
- availableContentTypes = EnumSet.of(
- ContentType.MANGA,
- ContentType.MANHWA,
- ContentType.MANHUA,
- ContentType.OTHER,
- ),
- )
+ override suspend fun getFilterOptions() = MangaListFilterOptions(
+ availableTags = availableTags(),
+ availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
+ availableContentTypes = EnumSet.of(
+ ContentType.MANGA,
+ ContentType.MANHWA,
+ ContentType.MANHUA,
+ ContentType.OTHER,
+ ),
+ )
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- val url = buildString {
- append("https://")
- append(domain)
- append("/baseapi/comics/filterComic")
- append("?keyword=")
- append(filter.query?.urlEncoded() ?: "")
-
- if (filter.tags.isNotEmpty()) {
- append("&categories=")
- append(filter.tags.joinToString(",") { tag ->
- availableTags().find { it.title == tag.title }?.key ?: tag.key
- })
- }
-
- append("&status=")
- when {
- filter.states.isEmpty() -> append("")
- filter.states.size > 1 -> append("")
- else -> append(
+ override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+ val url = buildString {
+ append("https://")
+ append(domain)
+ append("/baseapi/comics/filterComic")
+ append("?keyword=")
+ append(filter.query?.urlEncoded() ?: "")
+
+ if (filter.tags.isNotEmpty()) {
+ append("&categories=")
+ append(
+ filter.tags.joinToString(",") { tag ->
+ availableTags().find { it.title == tag.title }?.key ?: tag.key
+ },
+ )
+ }
+
+ append("&status=")
+ when {
+ filter.states.isEmpty() -> append("")
+ filter.states.size > 1 -> append("")
+ else -> append(
when (filter.states.first()) {
MangaState.ONGOING -> "0"
MangaState.FINISHED -> "1"
else -> ""
- }
+ },
)
- }
-
- append("&sort=")
- append(
+ }
+
+ append("&sort=")
+ append(
when (order) {
SortOrder.UPDATED -> "recently_updated"
SortOrder.NEWEST -> "latest"
@@ -90,12 +91,12 @@ internal class GocTruyenTranh(context: MangaLoaderContext) :
SortOrder.ALPHABETICAL -> "alphabet"
SortOrder.POPULARITY -> "mostView"
else -> "recently_updated"
- }
+ },
)
-
- if (filter.types.isNotEmpty()) {
- append("&country=")
- append(
+
+ if (filter.types.isNotEmpty()) {
+ append("&country=")
+ append(
filter.types.joinToString(",") {
when (it) {
ContentType.MANGA -> "manga"
@@ -104,197 +105,201 @@ internal class GocTruyenTranh(context: MangaLoaderContext) :
ContentType.OTHER -> "other"
else -> "manga"
}
- }
+ },
)
- }
-
- append("&page=")
- append(page)
- }
-
- val json = webClient.httpGet(url).parseJson()
- val data = json.getJSONObject("comics").getJSONArray("data")
-
- return List(data.length()) { i ->
- val item = data.getJSONObject(i)
- val slug = item.getString("slug")
- val mangaUrl = buildString {
- append("https://")
- append(domain)
- append("/")
- append(slug)
- }
-
- val categories = item.optJSONArray("categories")
- val tags = if (categories != null) {
- List(categories.length()) { j ->
- val category = categories.getJSONObject(j)
- MangaTag(
- key = category.getString("id"),
- title = category.getString("name").toTitleCase(sourceLocale),
- source = source,
- )
- }.toSet()
- } else {
- emptySet()
- }
+ }
+
+ append("&page=")
+ append(page)
+ }
+
+ val json = webClient.httpGet(url).parseJson()
+ val data = json.getJSONObject("comics").getJSONArray("data")
+
+ return List(data.length()) { i ->
+ val item = data.getJSONObject(i)
+ val slug = item.getString("slug")
+ val mangaUrl = buildString {
+ append("https://")
+ append(domain)
+ append("/")
+ append(slug)
+ }
+
+ val categories = item.optJSONArray("categories")
+ val tags = if (categories != null) {
+ List(categories.length()) { j ->
+ val category = categories.getJSONObject(j)
+ MangaTag(
+ key = category.getString("id"),
+ title = category.getString("name").toTitleCase(sourceLocale),
+ source = source,
+ )
+ }.toSet()
+ } else {
+ emptySet()
+ }
// Check NSFW manga by tags, API / Site not have this information
- val checkNsfw = tags.any { tag ->
- tag.key in setOf("25", "39", "41", "43", "57", "63")
- }
-
- Manga(
- id = generateUid(mangaUrl),
- url = "/$slug",
- publicUrl = mangaUrl,
- title = item.getString("name"),
- altTitle = item.optString("origin_name")?.takeUnless { it == "null" || it.isEmpty() },
- description = item.optString("content"),
- rating = RATING_UNKNOWN,
- isNsfw = checkNsfw || isNsfwSource,
- coverUrl = item.optString("thumbnail"),
- tags = tags,
- state = when (item.optString("status")) {
- "0" -> MangaState.ONGOING
- "1" -> MangaState.FINISHED
- else -> null
- },
- source = source,
- author = null,
- )
- }
- }
+ val checkNsfw = tags.any { tag ->
+ tag.key in setOf("25", "39", "41", "43", "57", "63")
+ }
- override suspend fun getDetails(manga: Manga): Manga {
- val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
- return manga.copy(
- rating = doc.selectFirst("div > span.leading-none")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
- author = doc.selectFirst("aside p:contains(Tác giả:) a[href^='/tac-gia/']")?.text(),
- chapters = doc.select("ul[itemtype='https://schema.org/ItemList'] li").mapChapters(reversed = true) { i, li ->
- val a = li.selectFirstOrThrow("a")
- val href = a.attrAsRelativeUrl("href")
- val name = li.selectFirst("div.w-\\[50\\%\\].truncate.flex")?.text() ?: ""
- val dateText = li.selectFirst("div.w-\\[50\\%\\].truncate.text-center")?.text()
- MangaChapter(
- id = generateUid(href),
- name = name,
- number = i + 1f,
- volume = 0,
- url = href,
- scanlator = null,
- uploadDate = parseChapterDate(dateText),
- branch = null,
- source = source,
- )
- },
- )
- }
+ Manga(
+ id = generateUid(mangaUrl),
+ url = "/$slug",
+ publicUrl = mangaUrl,
+ title = item.getString("name"),
+ altTitle = item.optString("origin_name")?.takeUnless { it == "null" || it.isEmpty() },
+ description = item.optString("content"),
+ rating = RATING_UNKNOWN,
+ isNsfw = checkNsfw || isNsfwSource,
+ coverUrl = item.optString("thumbnail"),
+ tags = tags,
+ state = when (item.optString("status")) {
+ "0" -> MangaState.ONGOING
+ "1" -> MangaState.FINISHED
+ else -> null
+ },
+ source = source,
+ author = null,
+ )
+ }
+ }
- override suspend fun getPages(chapter: MangaChapter): List {
- val fullUrl = chapter.url.toAbsoluteUrl(domain)
- val doc = webClient.httpGet(fullUrl).parseHtml()
- return doc.select("img.lozad[data-src]").map { img ->
- val url = img.attr("data-src")
- MangaPage(
- id = generateUid(url),
- url = url,
- preview = null,
- source = source,
- )
- }
- }
+ override suspend fun getDetails(manga: Manga): Manga {
+ val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
+ return manga.copy(
+ rating = doc.selectFirst("div > span.leading-none")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
+ author = doc.selectFirst("aside p:contains(Tác giả:) a[href^='/tac-gia/']")?.text(),
+ chapters = doc.select("ul[itemtype='https://schema.org/ItemList'] li")
+ .mapChapters(reversed = true) { i, li ->
+ val a = li.selectFirstOrThrow("a")
+ val href = a.attrAsRelativeUrl("href")
+ val name = li.selectFirst("div.w-\\[50\\%\\].truncate.flex")?.text() ?: ""
+ val dateText = li.selectFirst("div.w-\\[50\\%\\].truncate.text-center")?.text()
+ MangaChapter(
+ id = generateUid(href),
+ name = name,
+ number = i + 1f,
+ volume = 0,
+ url = href,
+ scanlator = null,
+ uploadDate = parseChapterDate(dateText),
+ branch = null,
+ source = source,
+ )
+ },
+ )
+ }
- private fun parseChapterDate(dateText: String?): Long {
- if (dateText == null) return 0
-
- val number = dateText.filter { it.isDigit() }.toIntOrNull() ?: return 0
- val now = System.currentTimeMillis()
-
- return when {
- dateText.contains("phút trước") -> {
- now - (number * 60 * 1000L)
- }
- dateText.contains("giờ trước") -> {
- now - (number * 60 * 60 * 1000L)
- }
- dateText.contains("ngày trước") -> {
- now - (number * 24 * 60 * 60 * 1000L)
- }
- else -> 0L
- }
- }
+ override suspend fun getPages(chapter: MangaChapter): List {
+ val fullUrl = chapter.url.toAbsoluteUrl(domain)
+ val doc = webClient.httpGet(fullUrl).parseHtml()
+ return doc.select("img.lozad[data-src]").map { img ->
+ val url = img.attr("data-src")
+ MangaPage(
+ id = generateUid(url),
+ url = url,
+ preview = null,
+ source = source,
+ )
+ }
+ }
- private fun availableTags() = arraySetOf(
- MangaTag("Action", "1", source),
- MangaTag("Adventure", "2", source),
- MangaTag("Fantasy", "3", source),
- MangaTag("Manhua", "4", source),
- MangaTag("Chuyển Sinh", "5", source),
- MangaTag("Truyện Màu", "6", source),
- MangaTag("Xuyên Không", "7", source),
- MangaTag("Manhwa", "8", source),
- MangaTag("Drama", "9", source),
- MangaTag("Historical", "10", source),
- MangaTag("Manga", "11", source),
- MangaTag("Seinen", "12", source),
- MangaTag("Comedy", "13", source),
- MangaTag("Martial Arts", "14", source),
- MangaTag("Mystery", "15", source),
- MangaTag("Romance", "16", source),
- MangaTag("Shounen", "17", source),
- MangaTag("Sports", "18", source),
- MangaTag("Supernatural", "19", source),
- MangaTag("Harem", "20", source),
- MangaTag("Webtoon", "21", source),
- MangaTag("School Life", "22", source),
- MangaTag("Psychological", "23", source),
- MangaTag("Cổ Đại", "24", source),
- MangaTag("Ecchi", "25", source),
- MangaTag("Gender Bender", "26", source),
- MangaTag("Shoujo", "27", source),
- MangaTag("Slice of Life", "28", source),
- MangaTag("Ngôn Tình", "29", source),
- MangaTag("Horror", "30", source),
- MangaTag("Sci-fi", "31", source),
- MangaTag("Tragedy", "32", source),
- MangaTag("Mecha", "33", source),
- MangaTag("Comic", "34", source),
- MangaTag("One shot", "35", source),
- MangaTag("Shoujo Ai", "36", source),
- MangaTag("Anime", "37", source),
- MangaTag("Josei", "38", source),
- MangaTag("Smut", "39", source),
- MangaTag("Shounen Ai", "40", source),
- MangaTag("Mature", "41", source),
- MangaTag("Soft Yuri", "42", source),
- MangaTag("Adult", "43", source),
- MangaTag("Doujinshi", "44", source),
- MangaTag("Live action", "45", source),
- MangaTag("Trinh Thám", "46", source),
- MangaTag("Việt Nam", "47", source),
- MangaTag("Truyện scan", "48", source),
- MangaTag("Cooking", "49", source),
- MangaTag("Tạp chí truyện tranh", "50", source),
- MangaTag("16+", "51", source),
- MangaTag("Thiếu Nhi", "52", source),
- MangaTag("Soft Yaoi", "53", source),
- MangaTag("Đam Mỹ", "54", source),
- MangaTag("BoyLove", "55", source),
- MangaTag("Yaoi", "56", source),
- MangaTag("18+", "57", source),
- MangaTag("Người Thú", "58", source),
- MangaTag("ABO", "59", source),
- MangaTag("Mafia", "60", source),
- MangaTag("Isekai", "61", source),
- MangaTag("Hệ Thống", "62", source),
- MangaTag("NTR", "63", source),
- MangaTag("Yuri", "64", source),
- MangaTag("Girl Love", "65", source),
- MangaTag("Demons", "66", source),
- MangaTag("Huyền Huyễn", "67", source),
- MangaTag("Detective", "68", source),
- MangaTag("Trọng Sinh", "69", source),
- MangaTag("Magic", "70", source),
- )
+ private fun parseChapterDate(dateText: String?): Long {
+ if (dateText == null) return 0
+
+ val number = dateText.filter { it.isDigit() }.toIntOrNull() ?: return 0
+ val now = System.currentTimeMillis()
+
+ return when {
+ dateText.contains("phút trước") -> {
+ now - (number * 60 * 1000L)
+ }
+
+ dateText.contains("giờ trước") -> {
+ now - (number * 60 * 60 * 1000L)
+ }
+
+ dateText.contains("ngày trước") -> {
+ now - (number * 24 * 60 * 60 * 1000L)
+ }
+
+ else -> 0L
+ }
+ }
+
+ private fun availableTags() = arraySetOf(
+ MangaTag("Action", "1", source),
+ MangaTag("Adventure", "2", source),
+ MangaTag("Fantasy", "3", source),
+ MangaTag("Manhua", "4", source),
+ MangaTag("Chuyển Sinh", "5", source),
+ MangaTag("Truyện Màu", "6", source),
+ MangaTag("Xuyên Không", "7", source),
+ MangaTag("Manhwa", "8", source),
+ MangaTag("Drama", "9", source),
+ MangaTag("Historical", "10", source),
+ MangaTag("Manga", "11", source),
+ MangaTag("Seinen", "12", source),
+ MangaTag("Comedy", "13", source),
+ MangaTag("Martial Arts", "14", source),
+ MangaTag("Mystery", "15", source),
+ MangaTag("Romance", "16", source),
+ MangaTag("Shounen", "17", source),
+ MangaTag("Sports", "18", source),
+ MangaTag("Supernatural", "19", source),
+ MangaTag("Harem", "20", source),
+ MangaTag("Webtoon", "21", source),
+ MangaTag("School Life", "22", source),
+ MangaTag("Psychological", "23", source),
+ MangaTag("Cổ Đại", "24", source),
+ MangaTag("Ecchi", "25", source),
+ MangaTag("Gender Bender", "26", source),
+ MangaTag("Shoujo", "27", source),
+ MangaTag("Slice of Life", "28", source),
+ MangaTag("Ngôn Tình", "29", source),
+ MangaTag("Horror", "30", source),
+ MangaTag("Sci-fi", "31", source),
+ MangaTag("Tragedy", "32", source),
+ MangaTag("Mecha", "33", source),
+ MangaTag("Comic", "34", source),
+ MangaTag("One shot", "35", source),
+ MangaTag("Shoujo Ai", "36", source),
+ MangaTag("Anime", "37", source),
+ MangaTag("Josei", "38", source),
+ MangaTag("Smut", "39", source),
+ MangaTag("Shounen Ai", "40", source),
+ MangaTag("Mature", "41", source),
+ MangaTag("Soft Yuri", "42", source),
+ MangaTag("Adult", "43", source),
+ MangaTag("Doujinshi", "44", source),
+ MangaTag("Live action", "45", source),
+ MangaTag("Trinh Thám", "46", source),
+ MangaTag("Việt Nam", "47", source),
+ MangaTag("Truyện scan", "48", source),
+ MangaTag("Cooking", "49", source),
+ MangaTag("Tạp chí truyện tranh", "50", source),
+ MangaTag("16+", "51", source),
+ MangaTag("Thiếu Nhi", "52", source),
+ MangaTag("Soft Yaoi", "53", source),
+ MangaTag("Đam Mỹ", "54", source),
+ MangaTag("BoyLove", "55", source),
+ MangaTag("Yaoi", "56", source),
+ MangaTag("18+", "57", source),
+ MangaTag("Người Thú", "58", source),
+ MangaTag("ABO", "59", source),
+ MangaTag("Mafia", "60", source),
+ MangaTag("Isekai", "61", source),
+ MangaTag("Hệ Thống", "62", source),
+ MangaTag("NTR", "63", source),
+ MangaTag("Yuri", "64", source),
+ MangaTag("Girl Love", "65", source),
+ MangaTag("Demons", "66", source),
+ MangaTag("Huyền Huyễn", "67", source),
+ MangaTag("Detective", "68", source),
+ MangaTag("Trọng Sinh", "69", source),
+ MangaTag("Magic", "70", source),
+ )
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Hentai18VN.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Hentai18VN.kt
index 71ad6ec70..9bc5befae 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Hentai18VN.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Hentai18VN.kt
@@ -1,13 +1,11 @@
package org.koitharu.kotatsu.parsers.site.vi
-import androidx.collection.arraySetOf
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
-import org.json.JSONObject
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +13,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("HENTAI18VN", "Hentai18VN", "vi", type = ContentType.HENTAI)
-internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.HENTAI18VN, 30) {
+internal class Hentai18VN(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.HENTAI18VN, 30) {
override val configKeyDomain = ConfigKey.Domain("hentai18vn.art")
@@ -24,79 +23,79 @@ internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(contex
keys.add(userAgentKey)
}
- override val filterCapabilities: MangaListFilterCapabilities
- get() = MangaListFilterCapabilities(
+ override val filterCapabilities: MangaListFilterCapabilities
+ get() = MangaListFilterCapabilities(
isSearchSupported = true,
- isSearchWithFiltersSupported = false
+ isSearchWithFiltersSupported = false,
)
- override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = fetchAvailableTags())
- override val availableSortOrders: Set = EnumSet.of(
+ override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = fetchAvailableTags())
+ override val availableSortOrders: Set = EnumSet.of(
SortOrder.NEWEST,
SortOrder.POPULARITY,
- SortOrder.UPDATED
+ SortOrder.UPDATED,
)
-
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- return when {
- !filter.query.isNullOrEmpty() -> {
- if (page > 1) {
- return emptyList()
- }
-
- val keyword = filter.query
- val url = "http://$domain/search/html/1"
- val headers = Headers.Builder().add("X-Requested-With", "XMLHttpRequest").build()
- val response = webClient.httpPost(url.toHttpUrl(), payload = "keyword=$keyword", headers).parseHtml()
- parseMangaSearch(response)
- }
-
- !filter.tags.isNullOrEmpty() -> {
- val tag = filter.tags.first()
- val url = buildString {
- append("https://")
- append(domain)
- append("/the-loai/")
- append(tag.key)
- if (page > 1) {
- append("?page=")
- append(page)
- }
- }
- val response = webClient.httpGet(url).parseHtml()
- parseMangaList(response)
- }
-
- else -> {
- val url = buildString {
- append("https://")
- append(domain)
- append("/")
- append(
+
+ override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+ return when {
+ !filter.query.isNullOrEmpty() -> {
+ if (page > 1) {
+ return emptyList()
+ }
+
+ val keyword = filter.query
+ val url = "http://$domain/search/html/1"
+ val headers = Headers.Builder().add("X-Requested-With", "XMLHttpRequest").build()
+ val response = webClient.httpPost(url.toHttpUrl(), payload = "keyword=$keyword", headers).parseHtml()
+ parseMangaSearch(response)
+ }
+
+ !filter.tags.isNullOrEmpty() -> {
+ val tag = filter.tags.first()
+ val url = buildString {
+ append("https://")
+ append(domain)
+ append("/the-loai/")
+ append(tag.key)
+ if (page > 1) {
+ append("?page=")
+ append(page)
+ }
+ }
+ val response = webClient.httpGet(url).parseHtml()
+ parseMangaList(response)
+ }
+
+ else -> {
+ val url = buildString {
+ append("https://")
+ append(domain)
+ append("/")
+ append(
when (order) {
SortOrder.NEWEST -> "danh-sach/truyen-hentai-moi"
SortOrder.POPULARITY -> "danh-sach/truyen-hentai-hot"
SortOrder.UPDATED -> "danh-sach/truyen-hentai-hoan-thanh"
else -> "danh-sach/truyen-hentai-hay"
- }
+ },
)
- if (page > 1) {
- append("?page=")
- append(page)
- }
- }
-
- val response = webClient.httpGet(url).parseHtml()
- parseMangaList(response)
- }
- }
- }
-
- private fun parseMangaSearch(doc: Document): List {
- return doc.select("a.item").map { a ->
- val href = a.attr("href")
- val mangaInfo = a.selectFirst("img")
- Manga(
+ if (page > 1) {
+ append("?page=")
+ append(page)
+ }
+ }
+
+ val response = webClient.httpGet(url).parseHtml()
+ parseMangaList(response)
+ }
+ }
+ }
+
+ private fun parseMangaSearch(doc: Document): List {
+ return doc.select("a.item").map { a ->
+ val href = a.attr("href")
+ val mangaInfo = a.selectFirst("img")
+ Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(domain),
@@ -108,48 +107,48 @@ internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(contex
state = null,
coverUrl = mangaInfo.requireSrc(),
isNsfw = isNsfwSource,
- source = source
- )
- }
- }
-
- private fun parseMangaList(doc: Document): List {
- return doc.select("div.visual").map { div ->
- val a = div.selectFirst("div.main_text h3.title a")
- val img = div.selectFirst("div.hentai-cover img")
- val mangaUrl = a.attr("href")
- Manga(
- id = generateUid(mangaUrl),
- publicUrl = mangaUrl,
- url = mangaUrl.removePrefix("https://$domain"),
- title = a.text(),
- altTitle = null,
- author = null,
- description = null,
- tags = emptySet(),
- rating = RATING_UNKNOWN,
- state = null,
- coverUrl = img.attr("data-original").takeIf { it.isNotEmpty() } ?: img.attr("src"),
- isNsfw = isNsfwSource,
source = source,
)
- }
- }
-
- override suspend fun getDetails(manga: Manga): Manga {
- val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
- val tags = doc.select("div.hentai-info .line-content a.item-tag")
- .mapNotNull { a ->
- MangaTag(
+ }
+ }
+
+ private fun parseMangaList(doc: Document): List {
+ return doc.select("div.visual").map { div ->
+ val a = div.selectFirst("div.main_text h3.title a")
+ val img = div.selectFirst("div.hentai-cover img")
+ val mangaUrl = a.attr("href")
+ Manga(
+ id = generateUid(mangaUrl),
+ publicUrl = mangaUrl,
+ url = mangaUrl.removePrefix("https://$domain"),
+ title = a.text(),
+ altTitle = null,
+ author = null,
+ description = null,
+ tags = emptySet(),
+ rating = RATING_UNKNOWN,
+ state = null,
+ coverUrl = img.attr("data-original").takeIf { it.isNotEmpty() } ?: img.attr("src"),
+ isNsfw = isNsfwSource,
+ source = source,
+ )
+ }
+ }
+
+ override suspend fun getDetails(manga: Manga): Manga {
+ val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
+ val tags = doc.select("div.hentai-info .line-content a.item-tag")
+ .mapNotNull { a ->
+ MangaTag(
title = a.text(),
key = a.attr("href").substringAfterLast("/"),
- source = source
+ source = source,
)
- }.toSet()
+ }.toSet()
- val chapters = doc.select("ul#chapter-list li.citem").mapChapters(reversed=true) { i, li ->
- val a = li.selectFirst("a")
- MangaChapter(
+ val chapters = doc.select("ul#chapter-list li.citem").mapChapters(reversed = true) { i, li ->
+ val a = li.selectFirst("a")
+ MangaChapter(
id = generateUid(a.attr("href")),
name = a.text(),
number = i + 1f,
@@ -158,71 +157,71 @@ internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(contex
source = source,
scanlator = null,
branch = null,
- volume = 0
+ volume = 0,
)
- }
+ }
- val altTitle = doc.selectFirst("h2.alternative")?.text()
- val author = doc.selectFirst("div.hentai-info .line:contains(Tác giả) .line-content")?.text()
- val state = when(doc.selectFirst("div.hentai-info .line:contains(Tình trạng) .line-content")?.text()) {
- "Đang cập nhật" -> MangaState.ONGOING
- "Hoàn thành" -> MangaState.FINISHED
- else -> null
- }
+ val altTitle = doc.selectFirst("h2.alternative")?.text()
+ val author = doc.selectFirst("div.hentai-info .line:contains(Tác giả) .line-content")?.text()
+ val state = when (doc.selectFirst("div.hentai-info .line:contains(Tình trạng) .line-content")?.text()) {
+ "Đang cập nhật" -> MangaState.ONGOING
+ "Hoàn thành" -> MangaState.FINISHED
+ else -> null
+ }
- return manga.copy(
+ return manga.copy(
tags = tags,
author = author,
altTitle = altTitle,
state = state,
chapters = chapters,
- description = doc.select("div.about").text()
+ description = doc.select("div.about").text(),
)
- }
+ }
- override suspend fun getPages(chapter: MangaChapter): List {
- val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
- return doc.select("div.chapter-content div.item-photo img").mapNotNull { img ->
- val url = img.requireSrc()
- MangaPage(
+ override suspend fun getPages(chapter: MangaChapter): List {
+ val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
+ return doc.select("div.chapter-content div.item-photo img").mapNotNull { img ->
+ val url = img.requireSrc()
+ MangaPage(
id = generateUid(url),
url = url,
preview = null,
- source = source
+ source = source,
)
- }
- }
-
- private suspend fun fetchAvailableTags(): Set {
- val firstPage = webClient.httpGet("https://$domain/tim-the-loai").parseHtml()
- val lastPage = firstPage.selectFirst("a[aria-label=Last]")
- ?.attr("href")
- ?.substringAfter("page=")
- ?.toIntOrNull() ?: 1
-
- return (1..lastPage).flatMap { page ->
- val doc = if (page == 1) {
- firstPage
- } else {
- webClient.httpGet("https://$domain/tim-the-loai?page=$page").parseHtml()
- }
-
- doc.select("ul.list-tags li").mapNotNull { li ->
- val a = li.selectFirst("a") ?: return@mapNotNull null
- val title = a.selectFirst("h3.tag-name")?.text()?.trim() ?: return@mapNotNull null
- val key = a.attr("href").substringAfterLast("/")
- MangaTag( title = title, key = key, source = source )
- }
- }.toSet()
- }
-
- private fun parseChapterDate(date: String?): Long {
- if (date == null) return 0
- return try {
- val now = SimpleDateFormat("dd/MM/yyyy", Locale.US)
- now.parse(date)?.time ?: 0L
- } catch (e: Exception) {
- 0L
- }
- }
-}
\ No newline at end of file
+ }
+ }
+
+ private suspend fun fetchAvailableTags(): Set {
+ val firstPage = webClient.httpGet("https://$domain/tim-the-loai").parseHtml()
+ val lastPage = firstPage.selectFirst("a[aria-label=Last]")
+ ?.attr("href")
+ ?.substringAfter("page=")
+ ?.toIntOrNull() ?: 1
+
+ return (1..lastPage).flatMap { page ->
+ val doc = if (page == 1) {
+ firstPage
+ } else {
+ webClient.httpGet("https://$domain/tim-the-loai?page=$page").parseHtml()
+ }
+
+ doc.select("ul.list-tags li").mapNotNull { li ->
+ val a = li.selectFirst("a") ?: return@mapNotNull null
+ val title = a.selectFirst("h3.tag-name")?.text()?.trim() ?: return@mapNotNull null
+ val key = a.attr("href").substringAfterLast("/")
+ MangaTag(title = title, key = key, source = source)
+ }
+ }.toSet()
+ }
+
+ private fun parseChapterDate(date: String?): Long {
+ if (date == null) return 0
+ return try {
+ val now = SimpleDateFormat("dd/MM/yyyy", Locale.US)
+ now.parse(date)?.time ?: 0L
+ } catch (e: Exception) {
+ 0L
+ }
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
index abebb8369..90834e4b6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.sync.withLock
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -21,7 +21,7 @@ private const val SEARCH_PAGE_SIZE = 10
@Broken
@MangaSourceParser("HENTAIVN", "HentaiVN", "vi", type = ContentType.HENTAI)
-internal class HentaiVNParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.HENTAIVN) {
+internal class HentaiVNParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.HENTAIVN) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaihvn.tv")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVnBuzz.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVnBuzz.kt
index 50076ea06..9b8cad3a1 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVnBuzz.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVnBuzz.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.vi
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -11,7 +11,8 @@ import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("HENTAIVNBUZZ", "HentaiVn.buzz", "vi", type = ContentType.HENTAI)
-internal class HentaiVnBuzz(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.HENTAIVNBUZZ, 24) {
+internal class HentaiVnBuzz(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.HENTAIVNBUZZ, 24) {
override val configKeyDomain = ConfigKey.Domain("hentaivn.buzz")
@@ -176,25 +177,26 @@ internal class HentaiVnBuzz(context: MangaLoaderContext) : PagedMangaParser(cont
"Hoàn thành" -> MangaState.FINISHED
else -> null
},
- chapters = doc.select("div.story-detail__list-chapter--list ul.list-unstyled li a").mapIndexed { i, element ->
- val href = element.attrAsRelativeUrl("href")
- val name = element.text().removePrefix("- ")
- MangaChapter(
- id = generateUid(href),
- name = name,
- number = i + 1f,
- volume = 0,
- url = href,
- scanlator = null,
- uploadDate = 0,
- branch = null,
- source = source,
- )
- }
+ chapters = doc.select("div.story-detail__list-chapter--list ul.list-unstyled li a")
+ .mapIndexed { i, element ->
+ val href = element.attrAsRelativeUrl("href")
+ val name = element.text().removePrefix("- ")
+ MangaChapter(
+ id = generateUid(href),
+ name = name,
+ number = i + 1f,
+ volume = 0,
+ url = href,
+ scanlator = null,
+ uploadDate = 0,
+ branch = null,
+ source = source,
+ )
+ },
)
}
- override suspend fun getPages(chapter: MangaChapter): List {
+ override suspend fun getPages(chapter: MangaChapter): List {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val imageUrls = doc.select("meta[property='og:image']").map { it.attr("content") }
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/KuroNeko.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/KuroNeko.kt
index 2bd46af78..e40d3bebe 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/KuroNeko.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/KuroNeko.kt
@@ -1,16 +1,15 @@
package org.koitharu.kotatsu.parsers.site.vi
-import androidx.collection.arraySetOf
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("KURONEKO", "Việt Hentai", "vi", type = ContentType.HENTAI)
-internal class KuroNeko(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.KURONEKO, 60) {
+internal class KuroNeko(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.KURONEKO, 60) {
override val configKeyDomain = ConfigKey.Domain("vi-hentai.com")
@@ -217,14 +216,14 @@ internal class KuroNeko(context: MangaLoaderContext) : PagedMangaParser(context,
calendar.timeInMillis
}.getOrDefault(0L)
- private suspend fun availableTags(): Set {
- val doc = webClient.httpGet("https://$domain").parseHtml()
- return doc.select("ul.grid.grid-cols-2 a").mapToSet { a ->
- MangaTag(
- key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
- title = a.text(),
- source = source,
- )
- }
- }
+ private suspend fun availableTags(): Set {
+ val doc = webClient.httpGet("https://$domain").parseHtml()
+ return doc.select("ul.grid.grid-cols-2 a").mapToSet { a ->
+ MangaTag(
+ key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
+ title = a.text(),
+ source = source,
+ )
+ }
+ }
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
index 4287cd03f..37d34fb56 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
@@ -1,16 +1,15 @@
package org.koitharu.kotatsu.parsers.site.vi
-import androidx.collection.arraySetOf
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("LXMANGA", "LXManga", "vi", type = ContentType.HENTAI)
-internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LXMANGA, 60) {
+internal class LxManga(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.LXMANGA, 60) {
override val configKeyDomain = ConfigKey.Domain("lxmanga.cloud")
@@ -211,13 +210,13 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context,
private suspend fun availableTags(): Set {
val url = "https://$domain/the-loai"
val doc = webClient.httpGet(url).parseHtml()
-
+
return doc.select("nav.grid.grid-cols-3.md\\:grid-cols-8 button").map { button ->
val key = button.attr("wire:click").substringAfterLast(", '").substringBeforeLast("')")
MangaTag(
key = key,
title = button.select("span.text-ellipsis").text(),
- source = source
+ source = source,
)
}.toSet()
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/SayHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/SayHentai.kt
index 79bd21ed5..83936909a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/SayHentai.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/SayHentai.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("SAYHENTAI", "SayHentai", "vi", ContentType.HENTAI)
-internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.SAYHENTAI, 20) {
+internal class SayHentai(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.SAYHENTAI, 20) {
override val configKeyDomain = ConfigKey.Domain("sayhentai.ink")
override fun onCreateConfig(keys: MutableCollection>) {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenGG.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenGG.kt
index d6524f8e2..dca2871a5 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenGG.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenGG.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("TRUYENGG", "TruyenGG", "vi")
-internal class TruyenGG(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TRUYENGG, 42) {
+internal class TruyenGG(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.TRUYENGG, 42) {
override val configKeyDomain = ConfigKey.Domain("truyengg.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenHentaiVN.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenHentaiVN.kt
index 2c3bd5c93..60605e95c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenHentaiVN.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenHentaiVN.kt
@@ -1,10 +1,9 @@
package org.koitharu.kotatsu.parsers.site.vi
-import androidx.collection.arraySetOf
import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +12,8 @@ import java.util.*
import java.text.SimpleDateFormat
@MangaSourceParser("TRUYENHENTAIVN", "TruyenHentaiVN", "vi", type = ContentType.HENTAI)
-internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TRUYENHENTAIVN, 30) {
+internal class TruyenHentaiVN(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.TRUYENHENTAIVN, 30) {
private var cacheTags = suspendLazy(initializer = ::fetchTags)
override val configKeyDomain = ConfigKey.Domain("truyenhentaivn.live")
@@ -26,19 +26,19 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isSearchSupported = true,
- isSearchWithFiltersSupported = false
+ isSearchWithFiltersSupported = false,
)
- override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = cacheTags.get().values.toSet())
+ override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = cacheTags.get().values.toSet())
- override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
+ override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
val url = buildString {
append("https://")
append(domain)
-
- when {
+
+ when {
!filter.tags.isNullOrEmpty() -> {
val tag = filter.tags.first()
append(tag.key)
@@ -47,7 +47,7 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
append(page)
}
}
-
+
!filter.query.isNullOrEmpty() -> {
append("/tim-kiem-truyen/?q=")
append(filter.query.urlEncoded())
@@ -56,7 +56,7 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
append(page)
}
}
-
+
else -> {
append("/chap-moi")
if (page > 1) {
@@ -87,60 +87,60 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
state = null,
coverUrl = cover,
isNsfw = true,
- source = source
+ source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
- return manga.copy(
- author = doc.selectFirst("div.author i")?.text(),
- tags = doc.select("div.genre.mb-3.mgen a").mapNotNull { a ->
- val key = a.attr("href").substringAfterLast("-")
- val title = a.text().trim()
- if (key.isNotEmpty() && title.isNotEmpty()) {
- MangaTag(
- key = key,
- title = title,
- source = source
- )
- } else null
- }.toSet(),
- description = doc.selectFirst("div.inner.mb-1.full")?.let { div ->
- div.select("p").joinToString("\n") { it.wholeText() }
- },
- coverUrl = doc.selectFirst("div.book img")?.src(),
- state = when(doc.selectFirst("div.tsinfo .imptdt i")?.text()?.trim()) {
- "Đã hoàn thành" -> MangaState.FINISHED
- "Đang tiến hành" -> MangaState.ONGOING
- else -> null
- },
- chapters = doc.select("div.chap-list .d-flex").mapChapters(reversed = true) { i, div ->
- val url = div.selectFirst("a")?.attrAsRelativeUrl("href") ?: ""
- val name = div.selectFirst("a .name")?.text() ?: ""
- val dateStr = div.selectFirst("a span:last-child")?.text()
-
- val uploadDate = dateStr?.let {
- try {
- SimpleDateFormat("dd-MM-yyyy", Locale.US).parse(it)?.time ?: 0L
- } catch (e: Exception) {
- 0L
- }
- } ?: 0L
-
- MangaChapter(
- id = generateUid(url),
- name = name,
- number = i + 1f,
- url = url,
- scanlator = null,
- uploadDate = uploadDate,
- branch = null,
- source = source,
- volume = 0
- )
- }
+ return manga.copy(
+ author = doc.selectFirst("div.author i")?.text(),
+ tags = doc.select("div.genre.mb-3.mgen a").mapNotNull { a ->
+ val key = a.attr("href").substringAfterLast("-")
+ val title = a.text().trim()
+ if (key.isNotEmpty() && title.isNotEmpty()) {
+ MangaTag(
+ key = key,
+ title = title,
+ source = source,
+ )
+ } else null
+ }.toSet(),
+ description = doc.selectFirst("div.inner.mb-1.full")?.let { div ->
+ div.select("p").joinToString("\n") { it.wholeText() }
+ },
+ coverUrl = doc.selectFirst("div.book img")?.src(),
+ state = when (doc.selectFirst("div.tsinfo .imptdt i")?.text()?.trim()) {
+ "Đã hoàn thành" -> MangaState.FINISHED
+ "Đang tiến hành" -> MangaState.ONGOING
+ else -> null
+ },
+ chapters = doc.select("div.chap-list .d-flex").mapChapters(reversed = true) { i, div ->
+ val url = div.selectFirst("a")?.attrAsRelativeUrl("href") ?: ""
+ val name = div.selectFirst("a .name")?.text() ?: ""
+ val dateStr = div.selectFirst("a span:last-child")?.text()
+
+ val uploadDate = dateStr?.let {
+ try {
+ SimpleDateFormat("dd-MM-yyyy", Locale.US).parse(it)?.time ?: 0L
+ } catch (e: Exception) {
+ 0L
+ }
+ } ?: 0L
+
+ MangaChapter(
+ id = generateUid(url),
+ name = name,
+ number = i + 1f,
+ url = url,
+ scanlator = null,
+ uploadDate = uploadDate,
+ branch = null,
+ source = source,
+ volume = 0,
+ )
+ },
)
}
@@ -153,7 +153,7 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
id = generateUid(url),
url = url,
preview = null,
- source = source
+ source = source,
)
} else null
}
@@ -172,4 +172,4 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
}
return tagMap
}
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenQQ.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenQQ.kt
index 7200cb77e..712264565 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenQQ.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenQQ.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("TRUYENQQ", "TruyenQQ", "vi")
-internal class TruyenQQ(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TRUYENQQ, 42) {
+internal class TruyenQQ(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.TRUYENQQ, 42) {
override val configKeyDomain = ConfigKey.Domain("truyenqqto.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenTranh3Q.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenTranh3Q.kt
index bf75bb992..de30476f6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenTranh3Q.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenTranh3Q.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -11,7 +11,7 @@ import java.util.*
@MangaSourceParser("TRUYENTRANH3Q", "TruyenTranh3Q", "vi")
internal class TruyenTranh3Q(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.TRUYENTRANH3Q, 42) {
+ LegacyPagedMangaParser(context, MangaParserSource.TRUYENTRANH3Q, 42) {
private val relativeTimePattern = Regex("(\\d+)\\s*(phút|giờ|ngày|tuần) trước")
private val absoluteTimePattern = Regex("(\\d{2}-\\d{2}-\\d{4})")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/VcomycsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/VcomycsParser.kt
index 20ea712db..e93c6a18c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/VcomycsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/VcomycsParser.kt
@@ -6,7 +6,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.ContentType
@@ -24,7 +24,6 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
import org.koitharu.kotatsu.parsers.util.attrOrNull
import org.koitharu.kotatsu.parsers.util.attrOrThrow
-import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
import org.koitharu.kotatsu.parsers.util.mapChapters
@@ -47,7 +46,8 @@ import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
@MangaSourceParser("VCOMYCS", "Vcomycs", "vi", ContentType.MANGA)
-internal class VcomycsParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.VCOMYCS, 36) {
+internal class VcomycsParser(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.VCOMYCS, 36) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("vivicomi.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt
index b4ed8840a..acaa3a10d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +15,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("YURINEKO", "YuriNeko", "vi", ContentType.HENTAI)
-internal class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.YURINEKO, 20) {
+internal class YurinekoParser(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.YURINEKO, 20) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("yurineko.site")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt
index eba126d6c..4d31a680f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vmp
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +13,7 @@ internal abstract class VmpParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt
index 6a1c7275d..ebe3cfa8c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt
@@ -10,7 +10,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.*
@@ -24,7 +24,7 @@ internal abstract class WpComicsParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 48,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
index 6eebd3ae2..cebe121b5 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
@@ -8,7 +8,7 @@ import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -23,7 +23,7 @@ internal abstract class ZeistMangaParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 12,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt
index ea2f89643..4f931d496 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt
@@ -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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -16,7 +16,7 @@ import java.util.*
@MangaSourceParser("BAOZIMH", "Baozimh", "zh")
internal class Baozimh(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BAOZIMH, pageSize = 36) {
+ LegacyPagedMangaParser(context, MangaParserSource.BAOZIMH, pageSize = 36) {
override val configKeyDomain = ConfigKey.Domain("www.baozimh.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
index 161fe7fbd..2da43b90f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class ZMangaParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 16,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/FaviconParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/FaviconParser.kt
index e41f9931f..242960483 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/FaviconParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/FaviconParser.kt
@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.parsers.util
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.model.Favicon
import org.koitharu.kotatsu.parsers.model.Favicons
@@ -11,7 +13,7 @@ public class FaviconParser(
private val domain: String,
) {
- public suspend fun parseFavicons(): Favicons {
+ public suspend fun parseFavicons(): Favicons = withContext(Dispatchers.Default) {
val url = "https://$domain"
val doc = webClient.httpGet(url).parseHtml()
val result = HashSet()
@@ -31,7 +33,7 @@ public class FaviconParser(
if (result.isEmpty()) {
result.add(createFallback())
}
- return Favicons(result, url)
+ Favicons(result, url)
}
private fun parseLink(link: Element): Favicon? {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
index bf6fc4ea6..5dea91285 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
@@ -4,8 +4,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
@@ -14,12 +14,12 @@ public class LinkResolver internal constructor(
public val link: HttpUrl,
) {
- private val source = suspendLazy(initializer = ::resolveSource)
+ private val source = suspendLazy(Dispatchers.Default, ::resolveSource)
public suspend fun getSource(): MangaParserSource? = source.get()
public suspend fun getManga(): Manga? {
- val parser = context.newParserInstance(source.get() ?: return null) as? AbstractMangaParser
+ val parser = context.newParserInstance(source.get() ?: return null) as? LegacyMangaParser
?: return null
return parser.resolveLink(this, link) ?: resolveManga(parser)
}
@@ -27,7 +27,7 @@ public class LinkResolver internal constructor(
private suspend fun resolveSource(): MangaParserSource? = runInterruptible(Dispatchers.Default) {
val domains = setOfNotNull(link.host, link.topPrivateDomain())
for (s in MangaParserSource.entries) {
- val parser = context.newParserInstance(s) as AbstractMangaParser
+ val parser = context.newParserInstance(s) as LegacyMangaParser
for (d in parser.configKeyDomain.presetValues) {
if (d in domains) {
return@runInterruptible s
@@ -38,7 +38,7 @@ public class LinkResolver internal constructor(
}
internal suspend fun resolveManga(
- parser: AbstractMangaParser,
+ parser: LegacyMangaParser,
url: String = link.toString().toRelativeUrl(link.host),
id: Long = parser.generateUid(url),
title: String = STUB_TITLE,
@@ -63,7 +63,7 @@ public class LinkResolver internal constructor(
),
)
- private suspend fun resolveBySeed(parser: AbstractMangaParser, s: Manga): Manga? {
+ private suspend fun resolveBySeed(parser: LegacyMangaParser, s: Manga): Manga? {
val seed = parser.getDetails(s)
if (!parser.filterCapabilities.isSearchSupported) {
return seed.takeUnless { it.chapters.isNullOrEmpty() }
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
index c81dab017..8c22c7a0a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.util
import okhttp3.HttpUrl
import org.jsoup.nodes.Element
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaParser
@@ -82,17 +82,17 @@ private fun Set?.oneOrThrowIfMany(msg: String): T? = when {
else -> throw IllegalArgumentException(msg)
}
-public val AbstractMangaParser.domain: String
+public val LegacyMangaParser.domain: String
get() = config[configKeyDomain]
@InternalParsersApi
-public fun AbstractMangaParser.getDomain(subdomain: String): String {
+public fun LegacyMangaParser.getDomain(subdomain: String): String {
val domain = domain
return subdomain + "." + domain.removePrefix("www.")
}
@InternalParsersApi
-public fun AbstractMangaParser.urlBuilder(subdomain: String? = null): HttpUrl.Builder {
+public fun LegacyMangaParser.urlBuilder(subdomain: String? = null): HttpUrl.Builder {
return HttpUrl.Builder()
.scheme(SCHEME_HTTPS)
.host(if (subdomain == null) domain else "$subdomain.$domain")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
index 4b1edf321..03b76f9ca 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
@@ -1,9 +1,6 @@
package org.koitharu.kotatsu.parsers.util
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.*
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.SortOrder
@@ -15,14 +12,16 @@ public class RelatedMangaFinder(
private val parsers: Collection,
) {
- public suspend operator fun invoke(seed: Manga): List = coroutineScope {
- parsers.singleOrNull()?.let { parser ->
- findRelatedImpl(this, parser, seed)
- } ?: parsers.map { parser ->
- async {
+ public suspend operator fun invoke(seed: Manga): List = withContext(Dispatchers.Default) {
+ coroutineScope {
+ parsers.singleOrNull()?.let { parser ->
findRelatedImpl(this, parser, seed)
- }
- }.awaitAll().flatten()
+ } ?: parsers.map { parser ->
+ async {
+ findRelatedImpl(this, parser, seed)
+ }
+ }.awaitAll().flatten()
+ }
}
private suspend fun findRelatedImpl(scope: CoroutineScope, parser: MangaParser, seed: Manga): List {
@@ -36,7 +35,7 @@ public class RelatedMangaFinder(
}
val results = words.map { keyword ->
scope.async {
- val result = parser.queryManga(
+ val result = parser.getList(
MangaSearchQuery.Builder()
.order(SortOrder.RELEVANCE)
.criterion(QueryCriteria.Match(SearchableField.TITLE_NAME, keyword))
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
index 5fe41fc6b..af7e79706 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.parsers.util
-import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
import org.koitharu.kotatsu.parsers.model.SortOrder
@@ -21,8 +20,7 @@ import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
* @param filter The [MangaListFilter] to convert.
* @return A [MangaSearchQuery] constructed based on the given [filter].
*/
-@InternalParsersApi
-public fun convertToMangaSearchQuery(offset: Int, sortOrder: SortOrder, filter: MangaListFilter): MangaSearchQuery {
+internal fun convertToMangaSearchQuery(offset: Int, sortOrder: SortOrder, filter: MangaListFilter): MangaSearchQuery {
return MangaSearchQuery.Builder().apply {
offset(offset)
order(sortOrder)
@@ -75,8 +73,7 @@ public fun convertToMangaSearchQuery(offset: Int, sortOrder: SortOrder, filter:
* @return A {@link MangaListFilter} constructed based on the given {@code searchQuery}.
* @throws UnsupportedOperationException If the search criteria contain unsupported fields.
*/
-@InternalParsersApi
-public fun convertToMangaListFilter(searchQuery: MangaSearchQuery): MangaListFilter {
+internal fun convertToMangaListFilter(searchQuery: MangaSearchQuery): MangaListFilter {
return MangaListFilter.Builder().apply {
for (criterion in searchQuery.criteria) {
when (criterion) {
@@ -89,6 +86,17 @@ public fun convertToMangaListFilter(searchQuery: MangaSearchQuery): MangaListFil
}.build()
}
+internal fun MangaSearchQueryCapabilities.toMangaListFilterCapabilities() = MangaListFilterCapabilities(
+ isMultipleTagsSupported = capabilities.any { x -> x.field == TAG && x.multiValue },
+ isTagsExclusionSupported = capabilities.any { x -> x.field == TAG && x.criteriaTypes.contains(Exclude::class) },
+ isSearchSupported = capabilities.any { x -> x.field == TITLE_NAME },
+ isSearchWithFiltersSupported = capabilities.any { x -> x.field == TITLE_NAME && x.otherCriteria },
+ isYearSupported = capabilities.any { x -> x.field == PUBLICATION_YEAR && x.criteriaTypes.contains(Match::class) },
+ isYearRangeSupported = capabilities.any { x -> x.field == PUBLICATION_YEAR && x.criteriaTypes.contains(Range::class) },
+ isOriginalLocaleSupported = capabilities.any { x -> x.field == ORIGINAL_LANGUAGE },
+ isAuthorSearchSupported = capabilities.any { x -> x.field == AUTHOR },
+)
+
internal fun MangaListFilterCapabilities.toMangaSearchQueryCapabilities(): MangaSearchQueryCapabilities =
MangaSearchQueryCapabilities(
capabilities = setOfNotNull(
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
index 46c761a64..4e80719fb 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
@@ -5,6 +5,8 @@ import okhttp3.HttpUrl
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.params.ParameterizedTest
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
@@ -25,7 +27,7 @@ internal class MangaParserTest {
@MangaSources
fun list(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.queryManga(MangaSearchQuery.Builder().build())
+ val list = parser.getList(MangaSearchQuery.Builder().build())
checkMangaList(list, "list")
assert(list.all { it.source == source })
}
@@ -34,13 +36,13 @@ internal class MangaParserTest {
@MangaSources
fun pagination(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- if (parser is SinglePageMangaParser) {
+ if (parser is LegacySinglePageMangaParser) {
return@runTest
}
- val page1 = parser.queryManga(MangaSearchQuery.EMPTY)
+ val page1 = parser.getList(MangaSearchQuery.EMPTY)
val page2 =
- parser.queryManga(MangaSearchQuery.Builder().offset(page1.size).build())
- if (parser is PagedMangaParser) {
+ parser.getList(MangaSearchQuery.Builder().offset(page1.size).build())
+ if (parser is LegacyPagedMangaParser) {
assert(parser.pageSize >= page1.size) {
"Page size is ${page1.size} but ${parser.pageSize} expected"
}
@@ -58,13 +60,13 @@ internal class MangaParserTest {
@MangaSources
fun searchByTitleName(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val subject = parser.queryManga(MangaSearchQuery.EMPTY).minByOrNull {
+ val subject = parser.getList(MangaSearchQuery.EMPTY).minByOrNull {
it.title.length
} ?: error("No manga found")
val query = subject.title
check(query.isNotBlank()) { "Manga title '$query' is blank" }
- val list = parser.queryManga(
+ val list = parser.getList(
MangaSearchQuery.Builder()
.order(SortOrder.RELEVANCE)
.criterion(QueryCriteria.Match(TITLE_NAME, query))
@@ -97,7 +99,7 @@ internal class MangaParserTest {
assert(tags.all { it.source == source })
val tag = tags.last()
- val list = parser.queryManga(
+ val list = parser.getList(
MangaSearchQuery.Builder()
.offset(0)
.criterion(Include(TAG, setOf(tag)))
@@ -114,7 +116,7 @@ internal class MangaParserTest {
// if (!parser.filterCapabilities.isMultipleTagsSupported) return@runTest
val tags = parser.getFilterOptions().availableTags.shuffled().take(2).toSet()
- val list = parser.queryManga(
+ val list = parser.getList(
MangaSearchQuery.Builder()
.offset(0)
.criterion(Include(TAG, tags))
@@ -134,7 +136,7 @@ internal class MangaParserTest {
return@runTest
}
val locale = locales.random()
- val list = parser.queryManga(
+ val list = parser.getList(
MangaSearchQuery.Builder()
.criterion(Include(LANGUAGE, setOf(locale)))
.criterion(Include(LANGUAGE, setOf(locale)))
@@ -150,7 +152,7 @@ internal class MangaParserTest {
@MangaSources
fun details(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.queryManga(MangaSearchQuery.EMPTY)
+ val list = parser.getList(MangaSearchQuery.EMPTY)
val manga = list[0]
parser.getDetails(manga).apply {
@@ -181,7 +183,7 @@ internal class MangaParserTest {
@MangaSources
fun pages(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.queryManga(MangaSearchQuery.EMPTY)
+ val list = parser.getList(MangaSearchQuery.EMPTY)
val manga = list.first()
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null at ${manga.publicUrl}")
val pages = parser.getPages(chapter)
@@ -236,7 +238,7 @@ internal class MangaParserTest {
@MangaSources
fun link(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val manga = parser.queryManga(MangaSearchQuery.Builder().build()).first()
+ val manga = parser.getList(MangaSearchQuery.Builder().build()).first()
val resolved = context.newLinkResolver(manga.publicUrl).getManga()
Assertions.assertNotNull(resolved)
resolved ?: return@runTest
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
index a8ff362f9..fd5b78df3 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.util
import org.junit.jupiter.api.Test
-import org.koitharu.kotatsu.parsers.AbstractMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContextMock
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.newParser
@@ -26,7 +26,7 @@ class IntentFilterGenerator {
if (source == MangaParserSource.DUMMY) {
continue
}
- val parser = source.newParser(MangaLoaderContextMock) as AbstractMangaParser
+ val parser = source.newParser(MangaLoaderContextMock) as LegacyMangaParser
parser.configKeyDomain.presetValues.forEach { domain ->
writer.appendTab().append("")
}