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("") }