From c5d3a7b0c16fdecda249597ca42b4e58d07184cf Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 18 Sep 2024 15:22:19 +0300 Subject: [PATCH] Global refactoring: migrate getList to new filter --- .../koitharu/kotatsu/parsers/MangaParser.kt | 178 +++--------------- .../kotatsu/parsers/PagedMangaParser.kt | 66 ++----- .../kotatsu/parsers/SinglePageMangaParser.kt | 22 +++ .../model/MangaListFilterCapabilities.kt | 16 +- .../parsers/model/MangaListFilterOptions.kt | 13 ++ .../kotatsu/parsers/site/all/BatoToParser.kt | 76 ++++---- .../parsers/site/all/ComickFunParser.kt | 59 +++--- .../parsers/site/all/ExHentaiParser.kt | 73 ++++--- .../parsers/site/all/HitomiLaParser.kt | 105 +++++------ .../kotatsu/parsers/site/all/ImHentai.kt | 18 +- .../parsers/site/all/LineWebtoonsParser.kt | 171 +++++++---------- .../parsers/site/all/MangaDexParser.kt | 43 +++-- .../parsers/site/all/MangaFireParser.kt | 60 +++--- .../kotatsu/parsers/site/all/MangaPark.kt | 84 +++++---- .../parsers/site/all/MangaPlusParser.kt | 19 +- .../parsers/site/all/MangaReaderToParser.kt | 39 ++-- .../parsers/site/all/NineMangaParser.kt | 44 +++-- .../site/all/NineNineNineHentaiParser.kt | 54 +++--- .../parsers/site/all/WebtoonsParser.kt | 34 ++-- .../animebootstrap/AnimeBootstrapParser.kt | 37 ++-- .../parsers/site/animebootstrap/fr/PapScan.kt | 15 +- .../kotatsu/parsers/site/ar/FlixScans.kt | 38 ++-- .../kotatsu/parsers/site/ar/MangaStorm.kt | 15 +- .../kotatsu/parsers/site/ar/TeamXNovel.kt | 37 ++-- .../kotatsu/parsers/site/be/AnibelParser.kt | 37 ++-- .../parsers/site/cupfox/CupFoxParser.kt | 38 ++-- .../parsers/site/en/AsuraScansParser.kt | 40 ++-- .../kotatsu/parsers/site/en/BeeToon.kt | 34 +++- .../parsers/site/en/CloneMangaParser.kt | 30 +-- .../kotatsu/parsers/site/en/ComicExtra.kt | 20 +- .../kotatsu/parsers/site/en/DynastyScans.kt | 18 +- .../kotatsu/parsers/site/en/FlixScansOrg.kt | 14 +- .../kotatsu/parsers/site/en/MangaGeko.kt | 17 +- .../kotatsu/parsers/site/en/MangaKawaiiEn.kt | 19 +- .../parsers/site/en/MangaTownParser.kt | 12 +- .../kotatsu/parsers/site/en/Mangaowl.kt | 44 +++-- .../kotatsu/parsers/site/en/Manhwa18Parser.kt | 13 +- .../kotatsu/parsers/site/en/ManhwasMen.kt | 15 +- .../kotatsu/parsers/site/en/Po2Scans.kt | 20 +- .../kotatsu/parsers/site/en/Pururin.kt | 15 +- .../kotatsu/parsers/site/en/VyManga.kt | 16 +- .../kotatsu/parsers/site/es/TempleScanEsp.kt | 49 ++--- .../parsers/site/es/TuMangaOnlineParser.kt | 14 +- .../parsers/site/fmreader/FmreaderParser.kt | 46 +++-- .../parsers/site/fmreader/en/Manhwa18Com.kt | 12 +- .../parsers/site/fmreader/es/OlimpoScans.kt | 17 +- .../parsers/site/foolslide/FoolSlideParser.kt | 99 +++++----- .../parsers/site/fr/BentomangaParser.kt | 12 +- .../kotatsu/parsers/site/fr/FuryoSociety.kt | 20 +- .../parsers/site/fr/LegacyScansParser.kt | 20 +- .../kotatsu/parsers/site/fr/LireScan.kt | 59 +++--- .../kotatsu/parsers/site/fr/LugnicaScans.kt | 48 ++--- .../kotatsu/parsers/site/fr/MangaKawaii.kt | 19 +- .../kotatsu/parsers/site/fr/MangaMana.kt | 15 +- .../kotatsu/parsers/site/fr/ScansMangasMe.kt | 43 +++-- .../kotatsu/parsers/site/fr/ScantradUnion.kt | 42 +++-- .../site/fuzzydoodle/FuzzyDoodleParser.kt | 10 +- .../site/galleryadults/GalleryAdultsParser.kt | 68 ++++--- .../parsers/site/galleryadults/all/Hentai3.kt | 19 +- .../site/galleryadults/all/HentaiEnvy.kt | 12 +- .../site/galleryadults/all/HentaiEra.kt | 40 ++-- .../site/galleryadults/all/HentaiForce.kt | 16 +- .../site/galleryadults/all/HentaiFox.kt | 26 +-- .../site/galleryadults/all/NHentaiParser.kt | 32 ++-- .../parsers/site/gattsu/GattsuParser.kt | 14 +- .../kotatsu/parsers/site/guya/GuyaParser.kt | 45 +++-- .../kotatsu/parsers/site/heancms/HeanCms.kt | 38 ++-- .../parsers/site/heancmsalt/HeanCmsAlt.kt | 13 +- .../parsers/site/hotcomics/HotComicsParser.kt | 37 ++-- .../parsers/site/id/DoujinDesuParser.kt | 12 +- .../kotatsu/parsers/site/id/HentaiCrot.kt | 15 +- .../kotatsu/parsers/site/id/PixHentai.kt | 15 +- .../kotatsu/parsers/site/iken/IkenParser.kt | 40 ++-- .../parsers/site/ja/NicovideoSeigaParser.kt | 28 ++- .../parsers/site/keyoapp/KeyoappParser.kt | 26 +-- .../parsers/site/likemanga/LikeMangaParser.kt | 16 +- .../parsers/site/madara/MadaraParser.kt | 56 +++--- .../parsers/site/madara/all/Manga18Fx.kt | 16 +- .../parsers/site/madara/all/Manhwa18Cc.kt | 14 +- .../parsers/site/madara/en/AdultWebtoon.kt | 31 ++- .../parsers/site/madara/en/Hentai4Free.kt | 18 +- .../parsers/site/madara/en/HentaiManga.kt | 20 +- .../parsers/site/madara/en/HentaiWebtoon.kt | 33 ++-- .../parsers/site/madara/en/IsekaiScan.kt | 18 +- .../site/madara/en/IsekaiScanEuParser.kt | 15 +- .../parsers/site/madara/en/MangaDass.kt | 18 +- .../parsers/site/madara/en/MangaDna.kt | 17 +- .../parsers/site/madara/en/MangaPure.kt | 17 +- .../kotatsu/parsers/site/madara/en/Manhwaz.kt | 17 +- .../parsers/site/madara/en/ManyToon.kt | 20 +- .../site/madara/es/DragonTranslationParser.kt | 13 +- .../parsers/site/madara/es/TmoManga.kt | 16 +- .../parsers/site/madara/id/ManhwaHub.kt | 28 +-- .../parsers/site/madara/vi/Saytruyenhay.kt | 31 +-- .../parsers/site/madtheme/MadthemeParser.kt | 36 +++- .../parsers/site/madtheme/en/ManhuaScan.kt | 12 +- .../parsers/site/manga18/Manga18Parser.kt | 16 +- .../parsers/site/mangabox/MangaboxParser.kt | 12 +- .../parsers/site/mangabox/en/Mangairo.kt | 16 +- .../parsers/site/mangabox/en/Mangakakalot.kt | 15 +- .../site/mangabox/en/MangakakalotTv.kt | 15 +- .../site/mangadventure/MangAdventureParser.kt | 53 ++++-- .../site/mangareader/MangaReaderParser.kt | 47 ++--- .../parsers/site/mangareader/ar/Normoyun.kt | 16 +- .../parsers/site/mangareader/en/RizzComic.kt | 18 +- .../parsers/site/mangareader/en/Zahard.kt | 24 ++- .../site/mangareader/es/HentaiReader.kt | 17 +- .../site/mangareader/es/LectorHentai.kt | 17 +- .../parsers/site/mangareader/es/MangaTv.kt | 17 +- .../parsers/site/mangareader/es/TuManhwas.kt | 11 +- .../site/mangareader/fr/RevolutionScantrad.kt | 109 ++++------- .../mangareader/fr/XxxRevolutionScantrad.kt | 120 +++++------- .../parsers/site/mangareader/id/KomikSan.kt | 16 +- .../parsers/site/mangareader/id/Komikcast.kt | 17 +- .../site/mangaworld/MangaWorldParser.kt | 75 ++++---- .../parsers/site/mmrcms/MmrcmsParser.kt | 44 +++-- .../parsers/site/nepnep/NepnepParser.kt | 29 +-- .../parsers/site/onemanga/OneMangaParser.kt | 34 ++-- .../otakusanctuary/OtakuSanctuaryParser.kt | 80 ++++---- .../site/pizzareader/PizzaReaderParser.kt | 26 +-- .../kotatsu/parsers/site/pt/BrMangas.kt | 41 ++-- .../kotatsu/parsers/site/pt/LerManga.kt | 13 +- .../kotatsu/parsers/site/pt/LerMangaOnline.kt | 17 +- .../parsers/site/pt/LuratoonScansParser.kt | 12 +- .../kotatsu/parsers/site/pt/MangaOnline.kt | 11 +- .../kotatsu/parsers/site/pt/MuitoHentai.kt | 15 +- .../kotatsu/parsers/site/pt/OnePieceEx.kt | 29 ++- .../kotatsu/parsers/site/pt/YugenMangas.kt | 79 ++++---- .../kotatsu/parsers/site/ru/AComics.kt | 54 ++++-- .../kotatsu/parsers/site/ru/DesuMeParser.kt | 21 +-- .../kotatsu/parsers/site/ru/MangaWtfParser.kt | 39 ++-- .../kotatsu/parsers/site/ru/NudeMoonParser.kt | 53 +++--- .../kotatsu/parsers/site/ru/RemangaParser.kt | 16 +- .../parsers/site/ru/grouple/GroupleParser.kt | 56 ++++-- .../parsers/site/ru/multichan/ChanParser.kt | 24 +-- .../site/ru/multichan/HenChanParser.kt | 15 +- .../parsers/site/ru/rulib/LibSocialParser.kt | 66 ++++--- .../kotatsu/parsers/site/scan/ScanParser.kt | 42 +++-- .../kotatsu/parsers/site/sinmh/SinmhParser.kt | 18 +- .../kotatsu/parsers/site/tr/MangaAy.kt | 22 +-- .../kotatsu/parsers/site/tr/SadScans.kt | 20 +- .../kotatsu/parsers/site/tr/TrWebtoon.kt | 22 +-- .../kotatsu/parsers/site/tr/YaoiFlix.kt | 39 ++-- .../parsers/site/uk/HentaiUkrParser.kt | 41 ++-- .../parsers/site/uk/HoneyMangaParser.kt | 20 +- .../parsers/site/uk/MangaInUaParser.kt | 16 +- .../parsers/site/vi/BlogTruyenParser.kt | 13 +- .../parsers/site/vi/BlogTruyenVNParser.kt | 13 +- .../parsers/site/vi/CuuTruyenParser.kt | 10 +- .../kotatsu/parsers/site/vi/HentaiVNParser.kt | 19 +- .../kotatsu/parsers/site/vi/LxManga.kt | 15 +- .../kotatsu/parsers/site/vi/Truyenqq.kt | 91 ++++----- .../kotatsu/parsers/site/vi/YurinekoParser.kt | 48 +++-- .../kotatsu/parsers/site/vmp/VmpParser.kt | 15 +- .../parsers/site/wpcomics/WpComicsParser.kt | 51 ++--- .../parsers/site/wpcomics/en/XoxoComics.kt | 18 +- .../parsers/site/wpcomics/ja/MangaRaw.kt | 122 +++++------- .../parsers/site/wpcomics/vi/NetTruyenHE.kt | 24 +-- .../parsers/site/wpcomics/vi/NetTruyenLL.kt | 24 +-- .../parsers/site/wpcomics/vi/NetTruyenSSR.kt | 32 +--- .../site/zeistmanga/ZeistMangaParser.kt | 43 +++-- .../kotatsu/parsers/site/zh/Baozimh.kt | 19 +- .../parsers/site/zmanga/ZMangaParser.kt | 35 +++- .../koitharu/kotatsu/parsers/util/Number.kt | 11 +- .../parsers/util/RelatedMangaFinder.kt | 4 +- .../kotatsu/parsers/MangaParserTest.kt | 21 ++- 166 files changed, 2513 insertions(+), 2961 deletions(-) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterOptions.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt index 3f395d55..bcd633ce 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt @@ -1,7 +1,6 @@ package org.koitharu.kotatsu.parsers import androidx.annotation.CallSuper -import androidx.annotation.VisibleForTesting import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import okhttp3.Headers @@ -32,15 +31,27 @@ abstract class MangaParser @InternalParsersApi constructor( * * For better performance use [EnumSet] for more than one item. */ + @Deprecated("") open val availableStates: Set get() = emptySet() + open val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = isMultipleTagsSupported, + isTagsExclusionSupported = isTagsExclusionSupported, + isSearchSupported = isSearchSupported, + isSearchWithFiltersSupported = searchSupportedWithMultipleFilters, + isYearSupported = isSearchYearSupported, + isYearRangeSupported = isSearchYearRangeSupported, + isSourceLocaleSupported = isSearchOriginalLanguages, + ) /** * Supported [ContentRating] variants for filtering. May be empty. * * For better performance use [EnumSet] for more than one item. */ + @Deprecated("Use getListFilterCapabilities instead") open val availableContentRating: Set get() = emptySet() @@ -49,6 +60,7 @@ abstract class MangaParser @InternalParsersApi constructor( * * For better performance use [EnumSet] for more than one item. */ + @Deprecated("Use getListFilterCapabilities instead") open val availableContentTypes: Set get() = emptySet() @@ -57,44 +69,50 @@ abstract class MangaParser @InternalParsersApi constructor( * * For better performance use [EnumSet] for more than one item. */ + @Deprecated("Use getListFilterCapabilities instead") open val availableDemographics: Set get() = emptySet() /** * Whether parser supports filtering by more than one tag */ + @Deprecated("Use getListFilterCapabilities instead") open val isMultipleTagsSupported: Boolean = true /** * Whether parser supports tagsExclude field in filter */ + @Deprecated("Use getListFilterCapabilities instead") open val isTagsExclusionSupported: Boolean = false /** * Whether parser supports searching by string query using [MangaListFilter.Search] */ + @Deprecated("Use getListFilterCapabilities instead") open val isSearchSupported: Boolean = true /** * Whether parser supports searching by string query using [MangaListFilter.Advanced] */ + @Deprecated("Use getListFilterCapabilities instead") open val searchSupportedWithMultipleFilters: Boolean = false /** * Whether parser supports searching by year */ - + @Deprecated("Use getListFilterCapabilities instead") open val isSearchYearSupported: Boolean = false /** * Whether parser supports searching by year range */ - + @Deprecated("Use getListFilterCapabilities instead") open val isSearchYearRangeSupported: Boolean = false /** * Whether parser supports searching Original Languages */ + @Deprecated("Use getListFilterCapabilities instead") open val isSearchOriginalLanguages: Boolean = false @@ -129,7 +147,6 @@ abstract class MangaParser @InternalParsersApi constructor( /** * Used as fallback if value of `sortOrder` passed to [getList] is null */ - @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) open val defaultSortOrder: SortOrder get() { val supported = availableSortOrders @@ -144,141 +161,10 @@ abstract class MangaParser @InternalParsersApi constructor( * * @param offset starting from 0 and used for pagination. * Note than passed value may not be divisible by internal page size, so you should adjust it manually. - * @param query search query, may be null or empty if no search needed - * @param tags genres for filtering, values from [getAvailableTags] and [Manga.tags]. May be null or empty - * @param sortOrder one of [availableSortOrders] or null for default value - */ - @JvmSynthetic - @InternalParsersApi - @Deprecated( - "Use getList with filter instead", - replaceWith = ReplaceWith("getList(offset, filter)"), - ) - open suspend fun getList( - offset: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, - ): List = throw NotImplementedError("Please implement getList(offset, filter) instead") - - /** - * Parse list of manga with search by text query - * - * @param offset starting from 0 and used for pagination. - * @param query search query - */ - @Deprecated( - "Use getList with filter instead", - ReplaceWith( - "getList(offset, SortOrder.RELEVANCE, MangaListFilterV2(query = query))", - "org.koitharu.kotatsu.parsers.model.MangaListFilter", - ), - ) - open suspend fun getList(offset: Int, query: String): List { - return getList(offset, MangaListFilter.Search(query)) - } - - /** - * Parse list of manga by specified criteria - * - * @param offset starting from 0 and used for pagination. - * Note than passed value may not be divisible by internal page size, so you should adjust it manually. - * @param tags genres for filtering, values from [getAvailableTags] and [Manga.tags]. May be null or empty - * @param sortOrder one of [availableSortOrders] or null for default value + * @param order one of [availableSortOrders] or null for default value + * @param filter is a set of filter rules */ - @Deprecated( - "Use getList with filter instead", - ReplaceWith( - "getList(offset, sortOrder, MangaListFilterV2(tags = tags))", - "org.koitharu.kotatsu.parsers.model.MangaListFilter", - ), - ) - open suspend fun getList( - offset: Int, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder?, - ): List { - return getList( - offset, - MangaListFilter.Advanced( - sortOrder = sortOrder ?: defaultSortOrder, - tags = tags.orEmpty(), - tagsExclude = tagsExclude.orEmpty(), - locale = null, - localeMangas = null, - states = emptySet(), - contentRating = emptySet(), - query = null, - year = null, - yearFrom = null, - yearTo = null, - types = emptySet(), - demographics = emptySet(), - ), - ) - } - - @Deprecated( - "Use getList with filter instead", - ReplaceWith( - "getList(offset, filter.sortOrder, filter)", - "org.koitharu.kotatsu.parsers.model.MangaListFilter", - ), - ) - open suspend fun getList(offset: Int, filter: MangaListFilter?): List { - return when (filter) { - is MangaListFilter.Advanced -> getList( - offset = offset, - query = null, - tags = filter.tags, - tagsExclude = filter.tagsExclude, - sortOrder = filter.sortOrder, - ) - - is MangaListFilter.Search -> getList( - offset = offset, - query = filter.query, - tags = null, - tagsExclude = null, - sortOrder = defaultSortOrder, - ) - - null -> getList( - offset = offset, - query = null, - tags = null, - tagsExclude = null, - sortOrder = defaultSortOrder, - ) - } - } - - open suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2) = getList( - offset = offset, - filter = when { - filter.query.isNullOrEmpty() -> MangaListFilter.Advanced( - sortOrder = order, - tags = filter.tags, - tagsExclude = filter.tagsExclude, - locale = filter.locale, - localeMangas = filter.sourceLocale, - states = filter.states, - contentRating = filter.contentRating, - query = filter.query, - year = filter.year, - yearFrom = filter.yearFrom, - yearTo = filter.yearTo, - types = filter.types, - demographics = filter.demographics, - ) - - else -> MangaListFilter.Search( - query = filter.query, - ) - }, - ) + abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List /** * Parse details for [Manga]: chapters list, description, large cover, etc. @@ -301,32 +187,26 @@ abstract class MangaParser @InternalParsersApi constructor( /** * Fetch available tags (genres) for source */ - abstract suspend fun getAvailableTags(): Set + @Deprecated("Use getListFilterDatasets instead") + open suspend fun getAvailableTags(): Set = emptySet() - open suspend fun getListFilterCapabilities(): MangaListFilterCapabilities = coroutineScope { + open suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope { val tagsDeferred = async { getAvailableTags() } val localesDeferred = async { getAvailableLocales() } - MangaListFilterCapabilities( - availableSortOrders = availableSortOrders, + MangaListFilterOptions( availableTags = tagsDeferred.await(), availableStates = availableStates, availableContentRating = availableContentRating, availableContentTypes = availableContentTypes, availableDemographics = availableDemographics, availableLocales = localesDeferred.await(), - isMultipleTagsSupported = isMultipleTagsSupported, - isTagsExclusionSupported = isTagsExclusionSupported, - isSearchSupported = isSearchSupported, - searchSupportedWithMultipleFilters = searchSupportedWithMultipleFilters, - isSearchYearSupported = isSearchYearSupported, - isSearchYearRangeSupported = isSearchYearRangeSupported, - isSearchOriginalLanguages = isSearchOriginalLanguages, ) } /** * Fetch available locales for multilingual sources */ + @Deprecated("Use getListFilterDatasets instead") open suspend fun getAvailableLocales(): Set = emptySet() @Deprecated( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt index 3d38152a..27967860 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt @@ -1,7 +1,10 @@ package org.koitharu.kotatsu.parsers import androidx.annotation.VisibleForTesting -import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 +import org.koitharu.kotatsu.parsers.model.MangaParserSource +import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.util.Paginator @InternalParsersApi @@ -18,72 +21,29 @@ abstract class PagedMangaParser( @JvmField protected val searchPaginator = Paginator(searchPageSize) - final override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { return getList( - paginator = if (filter is MangaListFilter.Search) { - searchPaginator - } else { + paginator = if (filter.query.isNullOrEmpty()) { paginator + } else { + searchPaginator }, offset = offset, + order = order, filter = filter, ) } - @InternalParsersApi - @Deprecated("You should use getListPage for PagedMangaParser", level = DeprecationLevel.HIDDEN) - final override suspend fun getList( - offset: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, - ): List = throw UnsupportedOperationException("You should use getListPage for PagedMangaParser") - - @Deprecated("") - open suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, - ): List = throw NotImplementedError("Please implement getListPage(page, filter) instead") - - open suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - return when (filter) { - is MangaListFilter.Advanced -> getListPage( - page = page, - query = null, - tags = filter.tags, - tagsExclude = filter.tagsExclude, - sortOrder = filter.sortOrder, - ) - - is MangaListFilter.Search -> getListPage( - page = page, - query = filter.query, - tags = null, - tagsExclude = null, - sortOrder = defaultSortOrder, - ) - - null -> getListPage( - page = page, - query = null, - tags = null, - tagsExclude = null, - sortOrder = defaultSortOrder, - ) - } - } + abstract suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List private suspend fun getList( paginator: Paginator, offset: Int, - filter: MangaListFilter?, + order: SortOrder, + filter: MangaListFilterV2, ): List { val page = paginator.getPage(offset) - val list = getListPage(page, filter) + val list = getListPage(page, order, filter) 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 new file mode 100644 index 00000000..33bb1a05 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt @@ -0,0 +1,22 @@ +package org.koitharu.kotatsu.parsers + +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 +import org.koitharu.kotatsu.parsers.model.MangaParserSource +import org.koitharu.kotatsu.parsers.model.SortOrder + +@InternalParsersApi +abstract class SinglePageMangaParser( + context: MangaLoaderContext, + source: MangaParserSource, +) : MangaParser(context, source) { + + final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { + if (offset > 0) { + return emptyList() + } + return getList(order, filter) + } + + abstract suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List +} 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 669f5856..d2c00779 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt @@ -1,21 +1,13 @@ package org.koitharu.kotatsu.parsers.model import org.koitharu.kotatsu.parsers.InternalParsersApi -import java.util.* data class MangaListFilterCapabilities @InternalParsersApi constructor( - val availableSortOrders: Set, - val availableTags: Set, - val availableStates: Set, - val availableContentRating: Set, - val availableContentTypes: Set, - val availableDemographics: Set, - val availableLocales: Set, val isMultipleTagsSupported: Boolean, val isTagsExclusionSupported: Boolean, val isSearchSupported: Boolean, - val searchSupportedWithMultipleFilters: Boolean, - val isSearchYearSupported: Boolean, - val isSearchYearRangeSupported: Boolean, - val isSearchOriginalLanguages: Boolean, + val isSearchWithFiltersSupported: Boolean, + val isYearSupported: Boolean = false, + val isYearRangeSupported: Boolean = false, + val isSourceLocaleSupported: Boolean = false, ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterOptions.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterOptions.kt new file mode 100644 index 00000000..6266e62c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterOptions.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.model + +import org.koitharu.kotatsu.parsers.InternalParsersApi +import java.util.* + +data class MangaListFilterOptions @InternalParsersApi constructor( + val availableTags: Set, + val availableStates: Set = emptySet(), + val availableContentRating: Set = emptySet(), + val availableContentTypes: Set = emptySet(), + val availableDemographics: Set = emptySet(), + val availableLocales: Set = emptySet(), +) 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 79036fde..73384fdd 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 @@ -60,11 +60,39 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( SortOrder.POPULARITY_HOUR, ) - override val availableStates: Set = EnumSet.allOf(MangaState::class.java) - - override val isTagsExclusionSupported: Boolean = true + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override val availableContentRating: Set = EnumSet.of(ContentRating.SAFE) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = EnumSet.of(ContentRating.SAFE), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = setOf( + Locale.CHINESE, Locale.ENGLISH, Locale.US, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.JAPANESE, + Locale("af"), Locale("ar"), Locale("az"), Locale("eu"), Locale("be"), + Locale("bn"), Locale("bs"), Locale("bg"), Locale("my"), Locale("km"), + Locale("ceb"), Locale("zh_hk"), Locale("zh_tw"), Locale("hr"), Locale("cs"), + Locale("da"), Locale("nl"), Locale("eo"), Locale("et"), Locale("fil"), + Locale("fi"), Locale("ka"), Locale("el"), Locale("ht"), Locale("he"), + Locale("hi"), Locale("hu"), Locale("id"), Locale("kk"), Locale("ko"), + Locale("lv"), Locale("ms"), Locale("ml"), Locale("mo"), Locale("mn"), + Locale("ne"), Locale("no"), Locale("fa"), Locale("pl"), Locale("pt"), + Locale("pt_br"), Locale("pt_pt"), Locale("ro"), Locale("ru"), Locale("sr"), + Locale("si"), Locale("sk"), Locale("es"), Locale("es_419"), Locale("ta"), + Locale("te"), Locale("th"), Locale("ti"), Locale("tr"), Locale("uk"), + Locale("vi"), Locale("zu"), + ), + ) override val configKeyDomain = ConfigKey.Domain( "bato.to", @@ -94,21 +122,20 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( "zbato.org", ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - when (filter) { - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + when { + !filter.query.isNullOrEmpty() -> { return search(page, filter.query) } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://") append(domain) append("/browse?sort=") - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> append("update.za") SortOrder.POPULARITY -> append("views_a.za") SortOrder.NEWEST -> append("create.za") @@ -171,17 +198,6 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( return parseList(url, page) } - - null -> { - val url = buildString { - append("https://") - append(domain) - append("/browse?sort=update.za") - append("&page=") - append(page.toString()) - } - return parseList(url, page) - } } } @@ -252,7 +268,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( throw ParseException("Cannot find images list", fullUrl) } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val scripts = webClient.httpGet( "https://${domain}/browse", ).parseHtml().selectOrThrow("script") @@ -273,22 +289,6 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( throw ParseException("Cannot find gernes list", scripts[0].baseUri()) } - override suspend fun getAvailableLocales(): Set = setOf( - Locale.CHINESE, Locale.ENGLISH, Locale.US, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.JAPANESE, - Locale("af"), Locale("ar"), Locale("az"), Locale("eu"), Locale("be"), - Locale("bn"), Locale("bs"), Locale("bg"), Locale("my"), Locale("km"), - Locale("ceb"), Locale("zh_hk"), Locale("zh_tw"), Locale("hr"), Locale("cs"), - Locale("da"), Locale("nl"), Locale("eo"), Locale("et"), Locale("fil"), - Locale("fi"), Locale("ka"), Locale("el"), Locale("ht"), Locale("he"), - Locale("hi"), Locale("hu"), Locale("id"), Locale("kk"), Locale("ko"), - Locale("lv"), Locale("ms"), Locale("ml"), Locale("mo"), Locale("mn"), - Locale("ne"), Locale("no"), Locale("fa"), Locale("pl"), Locale("pt"), - Locale("pt_br"), Locale("pt_pt"), Locale("ro"), Locale("ru"), Locale("sr"), - Locale("si"), Locale("sk"), Locale("es"), Locale("es_419"), Locale("ta"), - Locale("te"), Locale("th"), Locale("ti"), Locale("tr"), Locale("uk"), - Locale("vi"), Locale("zu"), - ) - private suspend fun search(page: Int, query: String): List { val url = buildString { append("https://") 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 a737edd1..9b34a879 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 @@ -27,10 +27,6 @@ internal class ComickFunParser(context: MangaLoaderContext) : keys.add(userAgentKey) } - override val isTagsExclusionSupported = true - - override val isSearchYearRangeSupported = true - override val availableSortOrders: Set = EnumSet.of( SortOrder.POPULARITY, SortOrder.UPDATED, @@ -38,21 +34,34 @@ internal class ComickFunParser(context: MangaLoaderContext) : SortOrder.NEWEST, ) - override val availableContentTypes: Set = EnumSet.of( - ContentType.MANGA, - ContentType.MANHWA, - ContentType.MANHUA, - ContentType.OTHER, - ) - - override val availableDemographics: Set = EnumSet.allOf(Demographic::class.java) + private val tagsArray = SuspendLazy(::loadTags) - override val availableStates: Set = - EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = true, + isSourceLocaleSupported = false, + ) - private val tagsArray = SuspendLazy(::loadTags) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), + availableContentRating = emptySet(), + availableContentTypes = EnumSet.of( + ContentType.MANGA, + ContentType.MANHWA, + ContentType.MANHUA, + ContentType.OTHER, + ), + availableDemographics = EnumSet.allOf(Demographic::class.java), + availableLocales = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val domain = domain val url = urlBuilder() .host("api.$domain") @@ -62,12 +71,12 @@ internal class ComickFunParser(context: MangaLoaderContext) : .addQueryParameter("tachiyomi", "true") .addQueryParameter("limit", pageSize.toString()) .addQueryParameter("page", page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { url.addQueryParameter("q", filter.query) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.forEach { url.addQueryParameter("genres", it.key) @@ -79,7 +88,7 @@ internal class ComickFunParser(context: MangaLoaderContext) : url.addQueryParameter( "sort", - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> "view" SortOrder.UPDATED -> "uploaded" SortOrder.NEWEST -> "created_at" @@ -101,11 +110,11 @@ internal class ComickFunParser(context: MangaLoaderContext) : ) } - filter.yearFrom?.let { + if (filter.yearFrom != 0) { url.addQueryParameter("from", filter.yearFrom.toString()) } - filter.yearTo?.let { + if (filter.yearTo != 0) { url.addQueryParameter("to", filter.yearTo.toString()) } @@ -135,10 +144,6 @@ internal class ComickFunParser(context: MangaLoaderContext) : ) } } - - null -> { - url.addQueryParameter("sort", "uploaded") - } } val ja = webClient.httpGet(url.build()).parseJsonArray() val tagsMap = tagsArray.get() @@ -208,7 +213,7 @@ internal class ComickFunParser(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val sparseArray = tagsArray.get() val set = ArraySet(sparseArray.size()) for (i in 0 until sparseArray.size()) { 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 305cb98e..705f4814 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 @@ -33,7 +33,6 @@ internal class ExHentaiParser( ) : PagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider, Interceptor { override val availableSortOrders: Set = setOf(SortOrder.NEWEST) - override val isTagsExclusionSupported: Boolean = true override val configKeyDomain: ConfigKey.Domain get() = ConfigKey.Domain( @@ -51,6 +50,17 @@ internal class ExHentaiParser( private val suspiciousContentKey = ConfigKey.ShowSuspiciousContent(false) private val tagsMap = SuspendLazy(::fetchTags) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + override val isAuthorized: Boolean get() { val authorized = isAuthorized(DOMAIN_UNAUTHORIZED) @@ -75,7 +85,32 @@ internal class ExHentaiParser( searchPaginator.firstPage = 0 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = tagsMap.get().values.toSet(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = setOf( + Locale.JAPANESE, + Locale.ENGLISH, + Locale.CHINESE, + Locale("nl"), + Locale.FRENCH, + Locale.GERMAN, + Locale("hu"), + Locale.ITALIAN, + Locale("kr"), + Locale("pl"), + Locale("pt"), + Locale("ru"), + Locale("es"), + Locale("th"), + Locale("vi"), + ), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val next = nextPages.get(page, 0L) if (page > 0 && next == 0L) { @@ -90,15 +125,15 @@ internal class ExHentaiParser( append(domain) append("/?next=") append(next) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { search += filter.query.urlEncoded() append("&f_search=") append(search.trim().replace(' ', '+')) } - is MangaListFilter.Advanced -> { + else -> { filter.toSearchQuery()?.let { sq -> append("&f_search=") @@ -121,8 +156,6 @@ internal class ExHentaiParser( append(fCats) } } - - null -> {} } // by unknown reason cookie "sl=dm_2" is ignored, so, we should request it again if (updateDm) { @@ -141,7 +174,7 @@ internal class ExHentaiParser( body.parseFailed("Cannot find root") } else { updateDm = true - return getListPage(page, filter) + return getListPage(page, order, filter) } updateDm = false nextPages[page + 1] = getNextTimestamp(body) @@ -267,10 +300,6 @@ internal class ExHentaiParser( "unusual pupils,urination,vore,vtuber,widow,wings,witch,wolf girl,x-ray,yuri,zombie,sole male,males only,yaoi," + "tomgirl,tall man,oni,shotacon,prostate massage,policeman,males only,huge penis,fox boy,feminization,dog boy,dickgirl on male,big penis" - override suspend fun getAvailableTags(): Set { - return tagsMap.get().values.toSet() - } - private suspend fun fetchTags(): Map { val tagMap = ArrayMap() val tagElements = tags.split(",") @@ -297,24 +326,6 @@ internal class ExHentaiParser( return tagMap } - override suspend fun getAvailableLocales(): Set = setOf( - Locale.JAPANESE, - Locale.ENGLISH, - Locale.CHINESE, - Locale("nl"), - Locale.FRENCH, - Locale.GERMAN, - Locale("hu"), - Locale.ITALIAN, - Locale("kr"), - Locale("pl"), - Locale("pt"), - Locale("ru"), - Locale("es"), - Locale("th"), - Locale("vi"), - ) - override fun intercept(chain: Interceptor.Chain): Response { val response = chain.proceed(chain.request()) if (response.headersContentLength() <= 256) { @@ -420,7 +431,7 @@ internal class ExHentaiParser( ?.toLongOrNull() ?: 1 } - private fun MangaListFilter.Advanced.toSearchQuery(): String? { + private fun MangaListFilterV2.toSearchQuery(): String? { val joiner = StringUtil.StringJoiner(" ") for (tag in tags) { if (tag.key.isNumeric()) { 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 3f8863fb..a3dec9c9 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 @@ -109,13 +109,10 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa private var cachedSearchIds: List = emptyList() - override suspend fun getList( - offset: Int, - filter: MangaListFilter?, - ): List = when (filter) { - is MangaListFilter.Advanced -> { + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List = when { + filter.query.isNullOrEmpty() -> { if (filter.tags.isEmpty()) { - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> { getGalleryIDsFromNozomi( "popular", @@ -134,7 +131,7 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa cachedSearchIds = hitomiSearch( filter.tags.joinToString(" ") { it.key }, - filter.sortOrder == SortOrder.POPULARITY, + order == SortOrder.POPULARITY, filter.locale.getSiteLang(), ).toList() } @@ -142,14 +139,12 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa } } - is MangaListFilter.Search -> { + else -> { if (offset == 0) { - cachedSearchIds = hitomiSearch(filter.query, filter.sortOrder == SortOrder.POPULARITY).toList() + cachedSearchIds = hitomiSearch(filter.query, order == SortOrder.POPULARITY).toList() } cachedSearchIds.subList(offset, min(offset + 25, cachedSearchIds.size)) } - - else -> getGalleryIDsFromNozomi(null, "popular", "all", offset.nextOffsetRange()) }.toMangaList() private fun Int.nextOffsetRange(): LongRange { @@ -478,14 +473,14 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa title = doc.selectFirstOrThrow("h1").text(), url = id.toString(), coverUrl = - "https:" + - doc.selectFirstOrThrow("picture > source") - .attr("data-srcset") - .substringBefore(" "), + "https:" + + doc.selectFirstOrThrow("picture > source") + .attr("data-srcset") + .substringBefore(" "), publicUrl = - doc.selectFirstOrThrow("h1 > a") - .attrAsRelativeUrl("href") - .toAbsoluteUrl(domain), + doc.selectFirstOrThrow("h1 > a") + .attrAsRelativeUrl("href") + .toAbsoluteUrl(domain), author = null, tags = emptySet(), isNsfw = true, @@ -508,37 +503,37 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa return manga.copy( title = json.getString("title"), largeCoverUrl = - json.getJSONArray("files").getJSONObject(0).let { - val hash = it.getString("hash") - val commonId = commonImageId() - val imageId = imageIdFromHash(hash) - val subDomain = 'a' + subdomainOffset(imageId) - - "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), @@ -562,15 +557,15 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa mapJSON { MangaTag( title = - it.getString(key).toCamelCase().let { title -> - if (it.getStringOrNull("female")?.toIntOrNull() == 1) { - "$title ♀" - } else if (it.getStringOrNull("male")?.toIntOrNull() == 1) { - "$title ♂" - } else { - title - } - }, + it.getString(key).toCamelCase().let { title -> + if (it.getStringOrNull("female")?.toIntOrNull() == 1) { + "$title ♀" + } else if (it.getStringOrNull("male")?.toIntOrNull() == 1) { + "$title ♂" + } else { + title + } + }, key = it.getString("url").tagUrlToTag(), source = source, ).let(tags::add) 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 27f2ee3e..7f2f6222 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 @@ -27,19 +27,23 @@ internal class ImHentai(context: MangaLoaderContext) : keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage( + page: Int, + order: SortOrder, + filter: MangaListFilterV2, + ): List { val url = buildString { append("https://") append(domain) append("/search/?page=") append(page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&key=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { append("&key=") @@ -53,17 +57,13 @@ internal class ImHentai(context: MangaLoaderContext) : } append(lang) - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> append("<=1&pp=0") SortOrder.POPULARITY -> append("<=0&pp=1") SortOrder.RATING -> append("<=0&pp=0") else -> append("<=1&pp=0") } } - - null -> { - append("<=1&pp=0") - } } } 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 d0260a42..a60a378a 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 @@ -135,116 +135,79 @@ internal abstract class LineWebtoonsParser( } } - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { - val manga = - when (filter) { - is MangaListFilter.Search -> { - makeRequest("/lineWebtoon/webtoon/searchChallenge?query=${filter.query.urlEncoded()}&startIndex=${offset + 1}&pageSize=20") - .getJSONObject("challengeSearch") - .getJSONArray("titleList") - .mapJSON { jo -> - val titleNo = jo.getLong("titleNo") - - Manga( - id = generateUid(titleNo), - title = jo.getString("title"), - altTitle = null, - url = titleNo.toString(), - publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo", - rating = RATING_UNKNOWN, - isNsfw = isNsfwSource, - coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), - largeCoverUrl = null, - tags = emptySet(), - author = jo.getStringOrNull("writingAuthorName"), - description = null, - state = null, - source = source, - ) - } - } + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { + val manga = when { + !filter.query.isNullOrEmpty() -> { + makeRequest("/lineWebtoon/webtoon/searchChallenge?query=${filter.query.urlEncoded()}&startIndex=${offset + 1}&pageSize=20") + .getJSONObject("challengeSearch") + .getJSONArray("titleList") + .mapJSON { jo -> + val titleNo = jo.getLong("titleNo") + + Manga( + id = generateUid(titleNo), + title = jo.getString("title"), + altTitle = null, + url = titleNo.toString(), + publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo", + rating = RATING_UNKNOWN, + isNsfw = isNsfwSource, + coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), + largeCoverUrl = null, + tags = emptySet(), + author = jo.getStringOrNull("writingAuthorName"), + description = null, + state = null, + source = source, + ) + } + } - is MangaListFilter.Advanced -> { + else -> { - val genre = filter.tags.oneOrThrowIfMany()?.key ?: "ALL" + val genre = filter.tags.oneOrThrowIfMany()?.key ?: "ALL" - val sortOrderStr = when (filter.sortOrder) { - SortOrder.UPDATED -> "UPDATE" - SortOrder.POPULARITY -> "READ_COUNT" - SortOrder.RATING -> "LIKEIT" - else -> throw IllegalArgumentException("Unsupported sort order: ${filter.sortOrder}") - } - - val result = - makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=$genre&sortOrder=$sortOrderStr&startIndex=${offset + 1}&pageSize=20") - - val genres = result.getJSONObject("genreList") - .getJSONArray("challengeGenres") - .mapJSON { jo -> parseTag(jo) } - .associateBy { tag -> tag.key } - - result - .getJSONObject("titleList") - .getJSONArray("titles") - .mapJSON { jo -> - val titleNo = jo.getLong("titleNo") - - Manga( - id = generateUid(titleNo), - title = jo.getString("title"), - altTitle = null, - url = titleNo.toString(), - publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo", - rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f, - isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource), - coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), - largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain), - tags = setOfNotNull(genres[jo.getString("representGenre")]), - author = jo.getStringOrNull("writingAuthorName"), - description = jo.getString("synopsis"), - // I don't think the API provides this info - state = null, - source = source, - ) - } + val sortOrderStr = when (order) { + SortOrder.UPDATED -> "UPDATE" + SortOrder.POPULARITY -> "READ_COUNT" + SortOrder.RATING -> "LIKEIT" + else -> throw IllegalArgumentException("Unsupported sort order: $order") } - null -> { - - val result = - makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=ALL&sortOrder=UPDATE&startIndex=${offset + 1}&pageSize=20") - - val genres = result.getJSONObject("genreList") - .getJSONArray("challengeGenres") - .mapJSON { jo -> parseTag(jo) } - .associateBy { tag -> tag.key } - - result - .getJSONObject("titleList") - .getJSONArray("titles") - .mapJSON { jo -> - val titleNo = jo.getLong("titleNo") - - Manga( - id = generateUid(titleNo), - title = jo.getString("title"), - altTitle = null, - url = titleNo.toString(), - publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo", - rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f, - isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource), - coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), - largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain), - tags = setOfNotNull(genres[jo.getString("representGenre")]), - author = jo.getStringOrNull("writingAuthorName"), - description = jo.getString("synopsis"), - // I don't think the API provides this info - state = null, - source = source, - ) - } - } + val result = + makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=$genre&sortOrder=$sortOrderStr&startIndex=${offset + 1}&pageSize=20") + + val genres = result.getJSONObject("genreList") + .getJSONArray("challengeGenres") + .mapJSON { jo -> parseTag(jo) } + .associateBy { tag -> tag.key } + + result + .getJSONObject("titleList") + .getJSONArray("titles") + .mapJSON { jo -> + val titleNo = jo.getLong("titleNo") + + Manga( + id = generateUid(titleNo), + title = jo.getString("title"), + altTitle = null, + url = titleNo.toString(), + publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo", + rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f, + isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource), + coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), + largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain), + tags = setOfNotNull(genres[jo.getString("representGenre")]), + author = jo.getStringOrNull("writingAuthorName"), + description = jo.getString("synopsis"), + // I don't think the API provides this info + state = null, + source = source, + ) + } } + } return manga 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 ae464ee1..4a2be43e 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 @@ -33,19 +33,36 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context keys.add(userAgentKey) } - override val availableSortOrders: EnumSet = EnumSet.allOf(SortOrder::class.java) - - override val availableContentRating: Set = EnumSet.allOf(ContentRating::class.java) - - override val availableDemographics: Set = EnumSet.allOf(Demographic::class.java) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = true, + isYearSupported = true, + isYearRangeSupported = true, + isSourceLocaleSupported = true, + ) - override val availableStates: Set = - EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED) + override val availableSortOrders: EnumSet = EnumSet.allOf(SortOrder::class.java) - override val isTagsExclusionSupported: Boolean = true - override val searchSupportedWithMultipleFilters: Boolean = true - override val isSearchYearSupported: Boolean = true - override val isSearchOriginalLanguages: Boolean = true + override suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope { + val localesDeferred = async { fetchAvailableLocales() } + val tagsDeferred = async { fetchAvailableTags() } + MangaListFilterOptions( + availableTags = tagsDeferred.await(), + availableStates = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + MangaState.PAUSED, + MangaState.ABANDONED, + ), + availableContentRating = EnumSet.allOf(ContentRating::class.java), + availableContentTypes = emptySet(), + availableDemographics = EnumSet.allOf(Demographic::class.java), + availableLocales = localesDeferred.await(), + ) + } override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { val domain = domain @@ -231,7 +248,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val tags = webClient.httpGet("https://api.${domain}/manga/tag").parseJson() .getJSONArray("data") return tags.mapJSONToSet { jo -> @@ -245,7 +262,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context } } - override suspend fun getAvailableLocales(): Set { + private suspend fun fetchAvailableLocales(): Set { val head = webClient.httpGet("https://$domain/").parseHtml().head() return head.getElementsByAttributeValue("property", "og:locale:alternate") .mapNotNullToSet { meta -> 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 0d3b1665..d210965b 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 @@ -60,8 +60,6 @@ internal abstract class MangaFireParser( ?: body.parseFailed("Cannot find username") } - override val availableStates: Set = EnumSet.allOf(MangaState::class.java) - private val tags = SoftSuspendLazy { webClient.httpGet("https://$domain/filter").parseHtml() .select(".genres > li").map { @@ -73,36 +71,48 @@ internal abstract class MangaFireParser( }.associateBy { it.title } } - override suspend fun getAvailableTags(): Set { - return tags.get().values.toSet() - } + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override val isTagsExclusionSupported = true + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = tags.get().values.toSet(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = "https://$domain/filter".toHttpUrl().newBuilder().apply { addQueryParameter("page", page.toString()) addQueryParameter("language[]", siteLang) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { addQueryParameter("keyword", filter.query) - filter.sortOrder?.let { - addQueryParameter( - name = "sort", - value = when (it) { - SortOrder.UPDATED -> "recently_updated" - SortOrder.POPULARITY -> "most_viewed" - SortOrder.RATING -> "scores" - SortOrder.NEWEST -> "release_date" - SortOrder.ALPHABETICAL -> "title_az" - else -> "" - }, - ) - } + addQueryParameter( + name = "sort", + value = when (order) { + SortOrder.UPDATED -> "recently_updated" + SortOrder.POPULARITY -> "most_viewed" + SortOrder.RATING -> "scores" + SortOrder.NEWEST -> "release_date" + SortOrder.ALPHABETICAL -> "title_az" + else -> "" + }, + ) } - is MangaListFilter.Advanced -> { + else -> { filter.tagsExclude.forEach { tag -> addQueryParameter("genre[]", "-${tag.key}") } @@ -126,7 +136,7 @@ internal abstract class MangaFireParser( } addQueryParameter( name = "sort", - value = when (filter.sortOrder) { + value = when (order) { SortOrder.UPDATED -> "recently_updated" SortOrder.POPULARITY -> "most_viewed" SortOrder.RATING -> "scores" @@ -136,8 +146,6 @@ internal abstract class MangaFireParser( }, ) } - - null -> {} } }.build() 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 eeb9480f..d8e69346 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 @@ -19,13 +19,48 @@ internal class MangaPark(context: MangaLoaderContext) : override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING) - override val availableStates: Set = EnumSet.allOf(MangaState::class.java) - - override val availableContentRating: Set = EnumSet.of(ContentRating.SAFE) + override val configKeyDomain = ConfigKey.Domain("mangapark.net") - override val isTagsExclusionSupported: Boolean = true + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override val configKeyDomain = ConfigKey.Domain("mangapark.net") + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = tagsMap.get().values.toSet(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = EnumSet.of(ContentRating.SAFE), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = setOf( + Locale("af"), Locale("sq"), Locale("am"), Locale("ar"), Locale("hy"), + Locale("az"), Locale("be"), Locale("bn"), Locale("zh_hk"), Locale("zh_tw"), + Locale.CHINESE, Locale("ceb"), Locale("ca"), Locale("km"), Locale("my"), + Locale("bg"), Locale("bs"), Locale("hr"), Locale("cs"), Locale("da"), + Locale("nl"), Locale.ENGLISH, Locale("et"), Locale("fo"), Locale("fil"), + Locale("fi"), Locale("he"), Locale("ha"), Locale("jv"), Locale("lb"), + Locale("mn"), Locale("ro"), Locale("si"), Locale("ta"), Locale("uz"), + Locale("ur"), Locale("tg"), Locale("sd"), Locale("pt_br"), Locale("mo"), + Locale("lt"), Locale.JAPANESE, Locale.ITALIAN, Locale("ht"), Locale("lv"), + Locale("mr"), Locale("pt"), Locale("sn"), Locale("sv"), Locale("uk"), + Locale("tk"), Locale("sw"), Locale("st"), Locale("pl"), Locale("mi"), + Locale("lo"), Locale("ga"), Locale("gu"), Locale("gn"), Locale("id"), + Locale("ky"), Locale("mt"), Locale("fa"), Locale("sh"), Locale("es_419"), + Locale("tr"), Locale("to"), Locale("vi"), Locale("es"), Locale("sr"), + Locale("ps"), Locale("ml"), Locale("ku"), Locale("ig"), Locale("el"), + Locale.GERMAN, Locale("is"), Locale.KOREAN, Locale("ms"), Locale("ny"), Locale("sm"), + Locale("so"), Locale("ti"), Locale("zu"), Locale("yo"), Locale("th"), + Locale("sl"), Locale("ru"), Locale("no"), Locale("mg"), Locale("kk"), + Locale("hu"), Locale("ka"), Locale.FRENCH, Locale("hi"), Locale("kn"), + Locale("mk"), Locale("ne"), Locale("rm"), Locale("sk"), Locale("te"), + ), + ) override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) @@ -38,19 +73,19 @@ internal class MangaPark(context: MangaLoaderContext) : context.cookieJar.insertCookies(domain, "nsfw", "2") } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/search?page=") append(page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&word=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("&genres=") if (filter.tags.isNotEmpty()) { @@ -88,7 +123,7 @@ internal class MangaPark(context: MangaLoaderContext) : append("&sortby=") append( - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> "views_d000" SortOrder.UPDATED -> "field_update" SortOrder.NEWEST -> "field_create" @@ -104,8 +139,6 @@ internal class MangaPark(context: MangaLoaderContext) : append(it.language) } } - - null -> append("&sortby=field_update") } } @@ -129,10 +162,6 @@ internal class MangaPark(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { - return tagsMap.get().values.toSet() - } - private suspend fun parseTags(): Map { val tagElements = webClient.httpGet("https://$domain/search").parseHtml() .select("div.flex-col:contains(Genres) div.whitespace-nowrap") @@ -149,29 +178,6 @@ internal class MangaPark(context: MangaLoaderContext) : return tagMap } - override suspend fun getAvailableLocales(): Set = setOf( - Locale("af"), Locale("sq"), Locale("am"), Locale("ar"), Locale("hy"), - Locale("az"), Locale("be"), Locale("bn"), Locale("zh_hk"), Locale("zh_tw"), - Locale.CHINESE, Locale("ceb"), Locale("ca"), Locale("km"), Locale("my"), - Locale("bg"), Locale("bs"), Locale("hr"), Locale("cs"), Locale("da"), - Locale("nl"), Locale.ENGLISH, Locale("et"), Locale("fo"), Locale("fil"), - Locale("fi"), Locale("he"), Locale("ha"), Locale("jv"), Locale("lb"), - Locale("mn"), Locale("ro"), Locale("si"), Locale("ta"), Locale("uz"), - Locale("ur"), Locale("tg"), Locale("sd"), Locale("pt_br"), Locale("mo"), - Locale("lt"), Locale.JAPANESE, Locale.ITALIAN, Locale("ht"), Locale("lv"), - Locale("mr"), Locale("pt"), Locale("sn"), Locale("sv"), Locale("uk"), - Locale("tk"), Locale("sw"), Locale("st"), Locale("pl"), Locale("mi"), - Locale("lo"), Locale("ga"), Locale("gu"), Locale("gn"), Locale("id"), - Locale("ky"), Locale("mt"), Locale("fa"), Locale("sh"), Locale("es_419"), - Locale("tr"), Locale("to"), Locale("vi"), Locale("es"), Locale("sr"), - Locale("ps"), Locale("ml"), Locale("ku"), Locale("ig"), Locale("el"), - Locale.GERMAN, Locale("is"), Locale.KOREAN, Locale("ms"), Locale("ny"), Locale("sm"), - Locale("so"), Locale("ti"), Locale("zu"), Locale("yo"), Locale("th"), - Locale("sl"), Locale("ru"), Locale("no"), Locale("mg"), Locale("kk"), - Locale("hu"), Locale("ka"), Locale.FRENCH, Locale("hi"), Locale("kn"), - Locale("mk"), Locale("ne"), Locale("rm"), Locale("sk"), Locale("te"), - ) - override suspend fun getDetails(manga: Manga): Manga = coroutineScope { val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val tagMap = tagsMap.get() 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 69a15811..6c644504 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 @@ -9,8 +9,8 @@ import okhttp3.ResponseBody.Companion.toResponseBody import org.json.JSONArray import org.json.JSONObject import org.koitharu.kotatsu.parsers.MangaLoaderContext -import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -24,7 +24,7 @@ internal abstract class MangaPlusParser( context: MangaLoaderContext, source: MangaParserSource, private val sourceLang: String, -) : MangaParser(context, source), Interceptor { +) : SinglePageMangaParser(context, source), Interceptor { private val apiUrl = "https://jumpg-webapi.tokyo-cdn.com/api" override val configKeyDomain = ConfigKey.Domain("mangaplus.shueisha.co.jp") @@ -45,22 +45,17 @@ internal abstract class MangaPlusParser( // no tags or tag search available override suspend fun getAvailableTags(): Set = emptySet() - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { - if (offset > 0) { - return emptyList() - } - - return when (filter) { - is MangaListFilter.Advanced -> { - when (filter.sortOrder) { + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { + return when { + filter.query.isNullOrEmpty() -> { + when (order) { SortOrder.POPULARITY -> getPopularList() SortOrder.UPDATED -> getLatestList() else -> getAllTitleList() } } - is MangaListFilter.Search -> getAllTitleList(filter.query) - else -> getAllTitleList() + else -> getAllTitleList(filter.query) } } 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 2d7c85c6..1edb3733 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 @@ -55,8 +55,6 @@ class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(contex SortOrder.ALPHABETICAL, ) - override val availableStates: Set = EnumSet.allOf(MangaState::class.java) - val tags = SoftSuspendLazy { val document = webClient.httpGet("https://$domain/filter").parseHtml() @@ -69,27 +67,41 @@ class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(contex }.associateBy { it.title } } - override suspend fun getAvailableTags(): Set { - return tags.get().values.toSet() - } + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override val isTagsExclusionSupported = false + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = tags.get().values.toSet(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = "https://$domain".toHttpUrl().newBuilder().apply { - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { addPathSegment("search") addQueryParameter("keyword", filter.query) addQueryParameter("page", page.toString()) } - is MangaListFilter.Advanced -> { + else -> { addPathSegment("filter") addQueryParameter("page", page.toString()) addQueryParameter( name = "sort", - value = when (filter.sortOrder) { + value = when (order) { SortOrder.POPULARITY -> "most-viewed" SortOrder.RATING -> "score" SortOrder.UPDATED -> "latest-updated" @@ -111,11 +123,6 @@ class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(contex }, ) } - - null -> { - addPathSegment("filter") - addQueryParameter("page", page.toString()) - } } }.build() 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 7de52d1c..d24d3af1 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 @@ -40,12 +40,28 @@ internal abstract class NineMangaParser( SortOrder.POPULARITY, ) - override val availableStates: Set = EnumSet.of( - MangaState.ONGOING, - MangaState.FINISHED, - ) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override val isTagsExclusionSupported: Boolean = true + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = getOrCreateTagMap().values.toSet(), + availableStates = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + ), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() @@ -57,12 +73,12 @@ internal abstract class NineMangaParser( return chain.proceed(newRequest) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search/?name_sel=&wd=") append(filter.query.urlEncoded()) append("&page=") @@ -70,7 +86,7 @@ internal abstract class NineMangaParser( append(".html") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty() || filter.states.isNotEmpty()) { append("/search/?category_id=") @@ -94,12 +110,6 @@ internal abstract class NineMangaParser( append(page.toString()) append(".html") } - - null -> { - append("/category/index_") - append(page) - append(".html") - } } } val doc = webClient.httpGet(url).parseHtml() @@ -184,10 +194,6 @@ internal abstract class NineMangaParser( private var tagCache: ArrayMap? = null private val mutex = Mutex() - override suspend fun getAvailableTags(): Set { - return getOrCreateTagMap().values.toSet() - } - private suspend fun getOrCreateTagMap(): Map = mutex.withLock { tagCache?.let { return@withLock it } val tagMap = ArrayMap() 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 c3f155e3..9d032061 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 @@ -34,13 +34,29 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) : SortOrder.NEWEST, ) - override val isMultipleTagsSupported = false + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override suspend fun getAvailableLocales() = setOf( - Locale.ENGLISH, - Locale.CHINESE, - Locale.JAPANESE, - Locale("es"), + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = setOf( + Locale.ENGLISH, + Locale.CHINESE, + Locale.JAPANESE, + Locale("es"), + ), ) private fun Locale?.getSiteLang(): String { @@ -75,7 +91,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) : return cdn?.toHttpUrlOrNull()?.host ?: "edge.fast4speed.rsvp" } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val query = """ queryTags( search: {format:"tagchapter",sortBy:Popular} @@ -102,23 +118,15 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) : } } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - return when (filter) { - is MangaListFilter.Advanced -> { - if (filter.tags.isEmpty() && filter.sortOrder == SortOrder.POPULARITY) { - getPopularList(page, filter.locale) - } else { - getSearchList(page, null, filter.locale, filter.tags, filter.sortOrder) - } - } - - is MangaListFilter.Search -> { - getSearchList(page, filter.query, null, null, filter.sortOrder) - } - - else -> { - getPopularList(page, null) + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + return if (filter.query.isNullOrEmpty()) { + if (filter.tags.isEmpty() && order == SortOrder.POPULARITY) { + getPopularList(page, filter.locale) + } else { + getSearchList(page, null, filter.locale, filter.tags, order) } + } else { + getSearchList(page, filter.query, null, null, order) } } 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 93ec230f..84f40273 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 @@ -25,8 +25,6 @@ internal abstract class WebtoonsParser( source: MangaParserSource, ) : MangaParser(context, source) { - override val isMultipleTagsSupported = false - private val signer by lazy { WebtoonsUrlSigner("gUtPzJFZch4ZyAGviiyH94P99lQ3pFdRTwpJWDlSGFfwgpr6ses5ALOxWHOIT7R1") } @@ -49,8 +47,20 @@ internal abstract class WebtoonsParser( SortOrder.UPDATED, ) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + ) + override val userAgentKey = ConfigKey.UserAgent("nApps (Android 12;; linewebtoon; 3.1.0)") + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = getAllGenreList().values.toSet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) @@ -182,10 +192,9 @@ internal abstract class WebtoonsParser( } - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { - - val webtoons = when (filter) { - is MangaListFilter.Search -> { + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { + val webtoons = when { + !filter.query.isNullOrEmpty() -> { makeRequest("/lineWebtoon/webtoon/searchWebtoon?query=${filter.query.urlEncoded()}").getJSONObject("webtoonSearch") .getJSONArray("titleList").mapJSON { jo -> val titleNo = jo.getLong("titleNo") @@ -210,7 +219,7 @@ internal abstract class WebtoonsParser( } } - is MangaListFilter.Advanced -> { + else -> { val genre = filter.tags.oneOrThrowIfMany()?.key ?: "ALL" val genres = getAllGenreList() @@ -220,17 +229,14 @@ internal abstract class WebtoonsParser( result = result.filter { it.manga.tags.contains(genres[genre]) } } - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> result.sortedByDescending { it.date } SortOrder.POPULARITY -> result.sortedByDescending { it.readCount } SortOrder.RATING -> result.sortedByDescending { it.manga.rating } //SortOrder.LIKE -> result.sortedBy { it.likeitCount } - else -> throw IllegalArgumentException("Unsupported sort order: ${filter.sortOrder}") + else -> throw IllegalArgumentException("Unsupported sort order: $order") } } - - else -> getAllTitleList() - } return webtoons.map { it.manga }.subList(offset, (offset + 20).coerceAtMost(webtoons.size)) } @@ -257,10 +263,6 @@ internal abstract class WebtoonsParser( ) } - override suspend fun getAvailableTags(): Set { - return getAllGenreList().values.toSet() - } - private suspend fun makeRequest(url: String): JSONObject { val resp = webClient.httpGet(finalizeUrl(url)) val message: JSONObject? = resp.parseJson().optJSONObject("message") 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 67a28410..433553df 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 @@ -25,8 +25,6 @@ internal abstract class AnimeBootstrapParser( keys.add(userAgentKey) } - override val isMultipleTagsSupported = false - override val availableSortOrders: Set = EnumSet.of( SortOrder.UPDATED, SortOrder.POPULARITY, @@ -37,13 +35,32 @@ internal abstract class AnimeBootstrapParser( protected open val listUrl = "/manga" protected open val datePattern = "dd MMM. yyyy" - init { paginator.firstPage = 1 searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) @@ -52,13 +69,13 @@ internal abstract class AnimeBootstrapParser( append(page.toString()) append("&type=all") - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&search=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append("&categorie=") @@ -66,7 +83,7 @@ internal abstract class AnimeBootstrapParser( } append("&sort=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("view") SortOrder.UPDATED -> append("updated") SortOrder.ALPHABETICAL -> append("default") @@ -75,8 +92,6 @@ internal abstract class AnimeBootstrapParser( } } - - null -> append("&sort=updated") } } val doc = webClient.httpGet(url).parseHtml() @@ -100,7 +115,7 @@ internal abstract class AnimeBootstrapParser( } } - override suspend fun getAvailableTags(): Set { + protected open suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain$listUrl").parseHtml() return doc.select("div.product__page__filter div:contains(Genre:) option ").mapNotNullToSet { option -> val key = option.attr("value") ?: return@mapNotNullToSet null diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt index edb06554..b845ffc5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt @@ -17,7 +17,6 @@ import java.util.* internal class PapScan(context: MangaLoaderContext) : AnimeBootstrapParser(context, MangaParserSource.PAPSCAN, "papscan.com") { override val sourceLocale: Locale = Locale.ENGLISH - override val isMultipleTagsSupported = false override val listUrl = "/liste-manga" override val selectState = "div.anime__details__widget li:contains(En cours)" override val selectTag = "div.anime__details__widget li:contains(Genre) a" @@ -29,20 +28,20 @@ internal class PapScan(context: MangaLoaderContext) : SortOrder.ALPHABETICAL_DESC, ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/filterList") append("?page=") append(page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&alpha=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append("&cat=") @@ -50,7 +49,7 @@ internal class PapScan(context: MangaLoaderContext) : } append("&sortBy=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.ALPHABETICAL_DESC -> append("name&asc=false") SortOrder.ALPHABETICAL -> append("name&asc=true") @@ -58,8 +57,6 @@ internal class PapScan(context: MangaLoaderContext) : } } - - null -> append("&sortBy=updated") } } val doc = webClient.httpGet(url).parseHtml() @@ -82,7 +79,7 @@ internal class PapScan(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { + override suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain$listUrl").parseHtml() return doc.select("a.category ").mapNotNullToSet { a -> val key = a.attr("href").substringAfterLast('=') 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 1f84bddc..351bf3c6 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 @@ -19,19 +19,36 @@ import java.util.* internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) { override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val availableStates: Set = EnumSet.allOf(MangaState::class.java) - override val availableContentRating: Set = EnumSet.of(ContentRating.ADULT) override val configKeyDomain = ConfigKey.Domain("flixscans.net") + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = EnumSet.of(ContentRating.ADULT), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - val json = when (filter) { - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + val json = when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { return emptyList() } @@ -41,7 +58,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context webClient.httpPost(url, body).parseJson().getJSONArray("data") } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://api.") append(domain) @@ -90,11 +107,6 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context webClient.httpGet(url).parseJson().getJSONArray("data") } - - null -> { - val url = "https://api.$domain/api/v1/webtoon/pages/latest/romance?page=$page" - webClient.httpGet(url).parseJson().getJSONArray("data") - } } return json.mapJSON { j -> val href = "https://$domain/series/${j.getString("prefix")}-${j.getString("id")}-${j.getString("slug")}" @@ -122,7 +134,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/search/advance").parseHtml() val json = JSONArray(doc.requireElementById("__NUXT_DATA__").data()) val tagsList = json.getJSONArray(3).toString().replace("[", "").replace("]", "").split(",") 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 9eea168f..ec42ea53 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 @@ -25,19 +25,19 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/mangas?page=") append(page) append("&query=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { val tag = filter.tags.oneOrThrowIfMany() @@ -46,7 +46,7 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex append("?page=") append(page) } else { - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("/mangas?page=") append(page) } else { @@ -56,11 +56,6 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex } } } - - null -> { - append("/mangas?page=") - append(page) - } } } val doc = webClient.httpGet(url).parseHtml() 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 9ef6ab20..91d2c902 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 @@ -17,8 +17,6 @@ import java.util.* internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) { override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) - override val availableStates: Set = - EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED) override val configKeyDomain = ConfigKey.Domain("teamoney.site") @@ -27,16 +25,33 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex keys.add(userAgentKey) } - override val isMultipleTagsSupported = false + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/?search=") append(filter.query.urlEncoded()) if (page > 1) { @@ -45,7 +60,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex } } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { val tag = filter.tags.oneOrThrowIfMany() append("/series?genre=") @@ -56,7 +71,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex } append("&") } else { - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("/series") SortOrder.UPDATED -> append("/") else -> append("/") @@ -70,7 +85,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex } } - if (filter.sortOrder == SortOrder.POPULARITY || filter.tags.isNotEmpty()) { + if (order == SortOrder.POPULARITY || filter.tags.isNotEmpty()) { filter.states.oneOrThrowIfMany()?.let { append("status=") append( @@ -84,8 +99,6 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex } } } - - null -> append("/?page=$page") } } val doc = webClient.httpGet(url).parseHtml() @@ -115,7 +128,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/series").parseHtml() return doc.requireElementById("select_genre").select("option").mapNotNullToSet { MangaTag( 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 89c24638..40f8cce6 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 @@ -34,31 +34,26 @@ internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context, SortOrder.NEWEST, ) - override suspend fun getList( - offset: Int, - filter: MangaListFilter?, - ): List { - val filters = - when (filter) { - is MangaListFilter.Search -> { - return if (offset == 0) { - search(filter.query) - } else { - emptyList() - } - } - - is MangaListFilter.Advanced -> { - filter.tags.takeUnless { it.isEmpty() }?.joinToString( - separator = ",", - prefix = "genres: [", - postfix = "]", - ) { "\"${it.key}\"" }.orEmpty() + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { + val filters = when { + !filter.query.isNullOrEmpty() -> { + return if (offset == 0) { + search(filter.query) + } else { + emptyList() } + } - null -> "" + else -> { + filter.tags.takeUnless { it.isEmpty() }?.joinToString( + separator = ",", + prefix = "genres: [", + postfix = "]", + ) { "\"${it.key}\"" }.orEmpty() } + } + val array = apiCall( """ getMediaList(offset: $offset, limit: 20, mediaType: manga, filters: {$filters}) { 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 05734d3a..63020616 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 @@ -27,26 +27,42 @@ internal abstract class CupFoxParser( SortOrder.UPDATED, ) - override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override val isMultipleTagsSupported = false + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search/") append(filter.query.urlEncoded()) append('/') append(page) } - is MangaListFilter.Advanced -> { + else -> { append("/category/") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("order/hits/") SortOrder.UPDATED -> append("order/addtime/") else -> append("order/addtime/") @@ -73,11 +89,6 @@ internal abstract class CupFoxParser( append("page/") append(page) } - - null -> { - append("/category/order/addtime/page/") - append(page) - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) @@ -193,7 +204,8 @@ internal abstract class CupFoxParser( } protected open val selectAvailableTags = "div.swiper-wrapper a[href*=tags], ul.stui-screen__list li a[href*=tags]" - override suspend fun getAvailableTags(): Set { + + protected open suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/category/").parseHtml() return doc.select(selectAvailableTags) .mapNotNullToSet { a -> 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 3c364c07..c5651ec3 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 @@ -25,31 +25,47 @@ internal class AsuraScansParser(context: MangaLoaderContext) : SortOrder.ALPHABETICAL, ) - override val availableStates: Set = EnumSet.allOf(MangaState::class.java) - override val configKeyDomain = ConfigKey.Domain("asuracomic.net") + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = getOrCreateTagMap().values.toSet(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) } - override val isMultipleTagsSupported = true - - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/series?page=") append(page) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&name=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { append("&genres=") @@ -70,7 +86,7 @@ internal class AsuraScansParser(context: MangaLoaderContext) : } append("&types=-1&order=") - when (filter.sortOrder) { + when (order) { SortOrder.RATING -> append("rating") SortOrder.UPDATED -> append("update") SortOrder.NEWEST -> append("latest") @@ -79,8 +95,6 @@ internal class AsuraScansParser(context: MangaLoaderContext) : else -> append("update") } } - - null -> append("&genres=&status=-1&order=update&types=-1") } } val doc = webClient.httpGet(url).parseHtml() @@ -113,10 +127,6 @@ internal class AsuraScansParser(context: MangaLoaderContext) : private var tagCache: ArrayMap? = null private val mutex = Mutex() - override suspend fun getAvailableTags(): Set { - return getOrCreateTagMap().values.toSet() - } - private suspend fun getOrCreateTagMap(): Map = mutex.withLock { tagCache?.let { return@withLock it } val tagMap = ArrayMap() 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 d33473ef..dbf8f340 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 @@ -22,14 +22,32 @@ internal class BeeToon(context: MangaLoaderContext) : keys.add(userAgentKey) } - override val isMultipleTagsSupported = false + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { return emptyList() } @@ -37,7 +55,7 @@ internal class BeeToon(context: MangaLoaderContext) : append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { val tag = filter.tags.oneOrThrowIfMany() @@ -47,7 +65,7 @@ internal class BeeToon(context: MangaLoaderContext) : append(page) append("/") } else { - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> append("/latest-update/") SortOrder.POPULARITY -> append("/popular-manga/") else -> append("/latest-update/") @@ -57,8 +75,6 @@ internal class BeeToon(context: MangaLoaderContext) : append("/") } } - - null -> append("/latest-update/page-$page/") } } val doc = webClient.httpGet(url).parseHtml() @@ -85,7 +101,7 @@ internal class BeeToon(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/").parseHtml() return doc.requireElementById("menu-item-3").select("ul.sub-menu li a").mapNotNullToSet { MangaTag( 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 fc373e93..02d110c6 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 @@ -1,16 +1,16 @@ package org.koitharu.kotatsu.parsers.site.en -import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.MangaLoaderContext -import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.util.* @MangaSourceParser("CLONEMANGA", "CloneManga", "en") -internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.CLONEMANGA) { +internal class CloneMangaParser(context: MangaLoaderContext) : + SinglePageMangaParser(context, MangaParserSource.CLONEMANGA) { override val availableSortOrders: Set = Collections.singleton( SortOrder.POPULARITY, @@ -23,27 +23,11 @@ internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(conte keys.add(userAgentKey) } - @InternalParsersApi - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { - - val link = when (filter) { - is MangaListFilter.Search -> { - return emptyList() - } - - is MangaListFilter.Advanced -> { - if (offset > 0) { - return emptyList() - } - - "https://$domain/viewer_landing.php" - } - - null -> "https://$domain/viewer_landing.php" - + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { + if (!filter.query.isNullOrEmpty()) { + return emptyList() } - - val doc = webClient.httpGet(link).parseHtml() + val doc = webClient.httpGet("https://$domain/viewer_landing.php").parseHtml() val mangas = doc.getElementsByClass("comicPreviewContainer") return mangas.mapNotNull { item -> val background = item.selectFirstOrThrow(".comicPreview").styleValueOrNull("background") 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 af06d6be..4f9a3c35 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 @@ -30,14 +30,13 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex override val isMultipleTagsSupported = false - - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/") - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("search?keyword=") append(filter.query.urlEncoded()) if (page > 1) { @@ -46,7 +45,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex } } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty() && filter.states.isEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append(it.key) @@ -65,7 +64,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex } else if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) { throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED) } else { - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("popular-comic") SortOrder.UPDATED -> append("new-comic") SortOrder.NEWEST -> append("recent-comic") @@ -78,15 +77,6 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex append(page.toString()) } } - - null -> { - append("popular-comic") - if (page > 1) { - append("/") - append(page.toString()) - } - } - } } val doc = webClient.httpGet(url).parseHtml() 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 d7a42055..cc7744ab 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 @@ -32,9 +32,9 @@ internal class DynastyScans(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - when (filter) { - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + when { + !filter.query.isNullOrEmpty() -> { val url = buildString { append("https://") append(domain) @@ -47,7 +47,7 @@ internal class DynastyScans(context: MangaLoaderContext) : return parseMangaListQuery(webClient.httpGet(url).parseHtml()) } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://") @@ -68,16 +68,6 @@ internal class DynastyScans(context: MangaLoaderContext) : } return parseMangaList(webClient.httpGet(url).parseHtml()) } - - null -> { - val url = buildString { - append("https://") - append(domain) - append("/series?view=cover&page=") - append(page.toString()) - } - return parseMangaList(webClient.httpGet(url).parseHtml()) - } } } 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 94b988ff..863e56a1 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 @@ -31,14 +31,13 @@ internal class FlixScansOrg(context: MangaLoaderContext) : override val isSearchSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - val json = when (filter) { - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + val json = when { + !filter.query.isNullOrEmpty() -> { throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://api.") @@ -75,11 +74,6 @@ internal class FlixScansOrg(context: MangaLoaderContext) : } webClient.httpGet(url).parseJson().getJSONArray("data") } - - null -> { - val url = "https://api.$domain/api/v1/search/advance?=&serie_type=webtoon&page=$page" - webClient.httpGet(url).parseJson().getJSONArray("data") - } } return json.mapJSON { j -> val href = "https://$domain/series/${j.getString("prefix")}-${j.getString("id")}-${j.getString("slug")}" 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 2a7aa0cb..6e339a2e 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 @@ -26,13 +26,12 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { return emptyList() } @@ -40,13 +39,13 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("/browse-comics/?results=") append(page) append("&filter=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("Updated") SortOrder.NEWEST -> append("New") @@ -60,12 +59,6 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context } } } - - null -> { - append("/browse-comics/?results=") - append(page) - append("&filter=Updated") - } } } val doc = webClient.httpGet(url).parseHtml() 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 9c849dc9..fe8c2bfd 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 @@ -31,26 +31,25 @@ internal class MangaKawaiiEn(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?query=") append(filter.query.urlEncoded()) append("&search_type=manga&page=") append(page) } - is MangaListFilter.Advanced -> { + else -> { - if (filter.sortOrder == SortOrder.UPDATED && filter.tags.isNotEmpty()) { + if (order == SortOrder.UPDATED && filter.tags.isNotEmpty()) { throw IllegalArgumentException("Filter part tag is not available with sort not updated") } - if (filter.sortOrder == SortOrder.ALPHABETICAL) { + if (order == SortOrder.ALPHABETICAL) { append("/manga-list") filter.tags.oneOrThrowIfMany()?.let { append("/category/") @@ -62,12 +61,6 @@ internal class MangaKawaiiEn(context: MangaLoaderContext) : return emptyList() } } - - null -> { - if (page > 1) { - return emptyList() - } - } } } 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 612114b5..1049e0ac 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 @@ -35,19 +35,19 @@ internal class MangaTownParser(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?name=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { append("/directory/") append("0-") @@ -79,7 +79,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : append(".htm") append( - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> "" SortOrder.UPDATED -> "?last_chapter_time.za" SortOrder.ALPHABETICAL -> "?name.az" @@ -88,8 +88,6 @@ internal class MangaTownParser(context: MangaLoaderContext) : }, ) } - - null -> append("/directory/$page.htm?last_chapter_time.za") } } val doc = webClient.httpGet(url).parseHtml() 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 92b3ea3c..01a797ea 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 @@ -25,32 +25,53 @@ internal class Mangaowl(context: MangaLoaderContext) : SortOrder.UPDATED, SortOrder.RATING, ) - override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) override val configKeyDomain = ConfigKey.Domain("mangaowl.to") override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) } - - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage( + page: Int, + order: SortOrder, + filter: MangaListFilterV2, + ): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/10-search?q=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { append("/10-comics") append("?page=") @@ -74,7 +95,7 @@ internal class Mangaowl(context: MangaLoaderContext) : append("&ordering=") append( - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> "view_count" SortOrder.UPDATED -> "-modified_at" SortOrder.NEWEST -> "created_at" @@ -83,11 +104,6 @@ internal class Mangaowl(context: MangaLoaderContext) : }, ) } - - null -> { - append("/10-comics?ordering=-modified_at&page=") - append(page.toString()) - } } } val doc = webClient.httpGet(url).parseHtml() @@ -110,7 +126,7 @@ internal class Mangaowl(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/10-genres").parseHtml() return doc.select("div.genres-container span.genre-item a").mapNotNullToSet { a -> val key = a.attr("href").removeSuffix('/').substringAfterLast('/').substringBefore("-") 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 11ebaebe..1589d6f3 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 @@ -48,21 +48,20 @@ class Manhwa18Parser(context: MangaLoaderContext) : ) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/tim-kiem?page=") append(page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&q=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("&accept_genres=") if (filter.tags.isNotEmpty()) { @@ -80,7 +79,7 @@ class Manhwa18Parser(context: MangaLoaderContext) : append("&sort=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "az" SortOrder.ALPHABETICAL_DESC -> "za" SortOrder.POPULARITY -> "top" @@ -103,8 +102,6 @@ class Manhwa18Parser(context: MangaLoaderContext) : ) } } - - null -> append("&sort=update") } } 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 da308af1..4add6550 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 @@ -28,21 +28,24 @@ class ManhwasMen(context: MangaLoaderContext) : override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage( + page: Int, + order: SortOrder, + filter: MangaListFilterV2, + ): List { val url = buildString { append("https://") append(domain) append("/manga-list") append("?page=") append(page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&search=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append("&genero=") @@ -60,8 +63,6 @@ class ManhwasMen(context: MangaLoaderContext) : ) } } - - null -> {} } } val doc = webClient.httpGet(url).parseHtml() 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 90e478fa..535c6038 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,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -10,7 +11,7 @@ import java.text.SimpleDateFormat import java.util.* @MangaSourceParser("PO2SCANS", "Po2Scans", "en") -internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.PO2SCANS) { +internal class Po2Scans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.PO2SCANS) { override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("po2scans.com") @@ -20,23 +21,14 @@ internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, Mang keys.add(userAgentKey) } - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { - if (offset > 0) { - return emptyList() - } + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/series") - when (filter) { - is MangaListFilter.Search -> { - append("?search=") - append(filter.query.urlEncoded()) - } - - is MangaListFilter.Advanced -> {} - - null -> {} + if (!filter.query.isNullOrEmpty()) { + append("?search=") + append(filter.query.urlEncoded()) } } val doc = webClient.httpGet(url).parseHtml() 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 4d141636..454a9680 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 @@ -29,19 +29,19 @@ internal class Pururin(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?q=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { append("/browse") filter.tags.oneOrThrowIfMany()?.let { @@ -54,7 +54,7 @@ internal class Pururin(context: MangaLoaderContext) : append(page) append("&sort=") - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> append("") SortOrder.POPULARITY -> append("most-viewed") SortOrder.RATING -> append("highest-rated") @@ -62,11 +62,6 @@ internal class Pururin(context: MangaLoaderContext) : else -> append("") } } - - null -> { - append("/browse?page=") - append(page) - } } } val doc = webClient.httpGet(url).parseHtml() 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 82e3d797..eddb75ab 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 @@ -36,20 +36,19 @@ class VyManga(context: MangaLoaderContext) : override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?search_po=0&q=") append(filter.query.urlEncoded()) append("&author_po=0&author=&completed=2&sort=updated_at&sort_type=desc&page=") append(page) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isEmpty()) { @@ -84,7 +83,7 @@ class VyManga(context: MangaLoaderContext) : } append("&sort=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("viewed&sort_type=desc") SortOrder.POPULARITY_ASC -> append("viewed&sort_type=asc") SortOrder.RATING -> append("scored&sort_type=desc") @@ -99,11 +98,6 @@ class VyManga(context: MangaLoaderContext) : append("&page=") append(page) } - - null -> { - append("/search?search_po=0&q=&author_po=0&author=&completed=2&sort=updated_at&sort_type=desc&page=") - append(page) - } } } val doc = webClient.httpGet(url).parseHtml() 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 2312ff8f..27ab95de 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 @@ -2,7 +2,6 @@ package org.koitharu.kotatsu.parsers.site.es import kotlinx.coroutines.coroutineScope import org.jsoup.nodes.Document -import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -27,32 +26,36 @@ internal class TempleScanEsp(context: MangaLoaderContext) : keys.add(userAgentKey) } - override val isSearchSupported = false + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = false, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = emptySet(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { - throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) - } - - is MangaListFilter.Advanced -> { - if (filter.sortOrder == SortOrder.NEWEST) { - append("/comics?page=") - append(page.toString()) - } else { - if (page > 1) { - return emptyList() - } - } - } - - null -> { - append("/comics?page=") - append(page.toString()) + if (order == SortOrder.NEWEST) { + append("/comics?page=") + append(page) + } else { + if (page > 1) { + return emptyList() } } } @@ -79,8 +82,6 @@ internal class TempleScanEsp(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set = emptySet() - override suspend fun getDetails(manga: Manga): Manga = coroutineScope { val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() 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 877ebc7b..586284f6 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 @@ -40,22 +40,22 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser( SortOrder.RATING, ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/library") - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("?title=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("?order_item=") append( - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> "likes_count&order_dir=desc" SortOrder.POPULARITY_ASC -> "likes_count&order_dir=asc" SortOrder.UPDATED -> "release_date&order_dir=desc" @@ -88,10 +88,6 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser( ) } } - - null -> { - append("?order_item=release_date&order_dir=desc&filter_by=title") - } } append("&_pg=1&page=") append(page.toString()) 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 1bbdd9e4..8b639c50 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 @@ -35,18 +35,21 @@ internal abstract class FmreaderParser( SortOrder.ALPHABETICAL_DESC, ) - override val availableStates: Set = EnumSet.of( - MangaState.ONGOING, - MangaState.FINISHED, - MangaState.ABANDONED, - ) - - override val isTagsExclusionSupported = true - protected open val listUrl = "/manga-list.html" protected open val datePattern = "MMMM d, yyyy" protected open val tagPrefix = "manga-list-genre-" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + init { paginator.firstPage = 1 searchPaginator.firstPage = 1 @@ -72,20 +75,33 @@ internal abstract class FmreaderParser( "drop", ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + MangaState.ABANDONED, + ), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append(listUrl) append("?page=") append(page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&name=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("&genre=") append(filter.tags.joinToString(",") { it.key }) @@ -95,7 +111,7 @@ internal abstract class FmreaderParser( append("&sort=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views&sort_type=DESC") SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC") SortOrder.UPDATED -> append("last_update&sort_type=DESC") @@ -118,8 +134,6 @@ internal abstract class FmreaderParser( } } - - null -> append("&sort=last_update") } } return parseMangaList(webClient.httpGet(url).parseHtml()) @@ -150,7 +164,7 @@ internal abstract class FmreaderParser( protected open val selectBodyTag = "ul.filter-type li a" - override suspend fun getAvailableTags(): Set { + protected open suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() return doc.select(selectBodyTag).mapNotNullToSet { a -> val href = a.attr("href").substringAfter(tagPrefix).substringBeforeLast(".html") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt index f8afd14f..90a81bd0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt @@ -22,20 +22,20 @@ internal class Manhwa18Com(context: MangaLoaderContext) : override val selectPage = "div#chapter-content img" override val selectBodyTag = "div.advanced-wrapper .genre_label" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/tim-kiem?page=") append(page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&q=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("&accept_genres=") append(filter.tags.joinToString(",") { it.key }) @@ -45,7 +45,7 @@ internal class Manhwa18Com(context: MangaLoaderContext) : append("&sort=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "az" SortOrder.ALPHABETICAL_DESC -> "za" SortOrder.POPULARITY -> "top" @@ -68,8 +68,6 @@ internal class Manhwa18Com(context: MangaLoaderContext) : ) } } - - null -> append("&sort=update") } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/es/OlimpoScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/es/OlimpoScans.kt index 7fa39583..26f30379 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/es/OlimpoScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/es/OlimpoScans.kt @@ -17,12 +17,12 @@ internal class OlimpoScans(context: MangaLoaderContext) : override val isMultipleTagsSupported = false override val isTagsExclusionSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append(listUrl) append("?page=") append(page.toString()) @@ -30,7 +30,7 @@ internal class OlimpoScans(context: MangaLoaderContext) : append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append("/lista-de-comics-genero-") @@ -42,7 +42,7 @@ internal class OlimpoScans(context: MangaLoaderContext) : append("?page=") append(page.toString()) append("&sort=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views&sort_type=DESC") SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC") SortOrder.UPDATED -> append("last_update&sort_type=DESC") @@ -65,13 +65,6 @@ internal class OlimpoScans(context: MangaLoaderContext) : ) } } - - null -> { - append(listUrl) - append("?page=") - append(page.toString()) - append("&sort=last_update") - } } } val doc = webClient.httpGet(url).parseHtml() 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 f9b3ee0c..23605ebe 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 @@ -32,67 +32,68 @@ internal abstract class FoolSlideParser( protected open val pagination = true // false if the manga list has no pages protected open val datePattern = "yyyy.MM.dd" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + init { paginator.firstPage = 1 searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - val doc = - when (filter) { - is MangaListFilter.Search -> { - if (page > 1) { - return emptyList() - } - - val url = buildString { - append("https://") - append(domain) - append("/") - append(searchUrl) - } - - webClient.httpPost(url, "search=${filter.query.urlEncoded()}").parseHtml() + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = emptySet(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + val doc = when { + !filter.query.isNullOrEmpty() -> { + if (page > 1) { + return emptyList() } - is MangaListFilter.Advanced -> { - - val url = buildString { - append("https://") - append(domain) - append("/") - append(listUrl) - // For some sites that don't have enough manga and page 2 links to page 1 - if (!pagination) { - if (page > 1) { - return emptyList() - } - } else { - append(page.toString()) - } - } - webClient.httpGet(url).parseHtml() - + val url = buildString { + append("https://") + append(domain) + append("/") + append(searchUrl) } - null -> { - val url = buildString { - append("https://") - append(domain) - append("/") - append(listUrl) - if (!pagination) { - if (page > 1) { - return emptyList() - } - } else { - append(page.toString()) + webClient.httpPost(url, "search=${filter.query.urlEncoded()}").parseHtml() + } + + else -> { + + val url = buildString { + append("https://") + append(domain) + append("/") + append(listUrl) + // For some sites that don't have enough manga and page 2 links to page 1 + if (!pagination) { + if (page > 1) { + return emptyList() } + } else { + append(page.toString()) } - webClient.httpGet(url).parseHtml() - } + webClient.httpGet(url).parseHtml() + } + } return doc.select("div.list div.group").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") @@ -114,8 +115,6 @@ internal abstract class FoolSlideParser( } - override suspend fun getAvailableTags(): Set = emptySet() - protected open val selectInfo = "div.info" override suspend fun getDetails(manga: Manga): Manga = coroutineScope { 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 eb14491d..a17a0572 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 @@ -47,19 +47,19 @@ internal class BentomangaParser(context: MangaLoaderContext) : searchPaginator.firstPage = 0 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = urlBuilder() .host(domain) .addPathSegment("manga_list") .addQueryParameter("limit", page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { url.addQueryParameter("search", filter.query) } - is MangaListFilter.Advanced -> { + else -> { - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> url.addQueryParameter("order_by", "update") .addQueryParameter("order", "desc") @@ -104,8 +104,6 @@ internal class BentomangaParser(context: MangaLoaderContext) : } } - - null -> url.addQueryParameter("order_by", "update") } val root = webClient.httpGet(url.build()).parseHtml().requireElementById("mangas_content") return root.select(".manga[data-manga]").map { div -> 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 c7d25ff8..ed40fa46 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 @@ -6,6 +6,7 @@ import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.network.UserAgents @@ -16,7 +17,7 @@ import java.util.* @MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr") internal class FuryoSociety(context: MangaLoaderContext) : - PagedMangaParser(context, MangaParserSource.FURYOSOCIETY, 0) { + SinglePageMangaParser(context, MangaParserSource.FURYOSOCIETY) { override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED) @@ -40,27 +41,20 @@ internal class FuryoSociety(context: MangaLoaderContext) : ) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - if (page > 1) { - return emptyList() - } - + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) } - is MangaListFilter.Advanced -> { - - if (filter.sortOrder == SortOrder.ALPHABETICAL) { + else -> { + if (order == SortOrder.ALPHABETICAL) { append("/mangas") } } - - null -> {} } } 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 6d50790f..7b8aa2f6 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 @@ -27,12 +27,12 @@ internal class LegacyScansParser(context: MangaLoaderContext) : keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val end = page * pageSize val start = end - (pageSize - 1) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { return emptyList() } @@ -43,7 +43,7 @@ internal class LegacyScansParser(context: MangaLoaderContext) : return parseMangaListQuery(webClient.httpGet(url).parseJson()) } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://api.") append(domain) @@ -68,18 +68,6 @@ internal class LegacyScansParser(context: MangaLoaderContext) : } return parseMangaList(webClient.httpGet(url).parseJson()) } - - null -> { - val url = buildString { - append("https://api.") - append(domain) - append("/misc/comic/search/query?status=&order=&genreNames=&type=&start=") - append(start) - append("&end=") - append(end) - } - return parseMangaList(webClient.httpGet(url).parseJson()) - } } } 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 57ac1c51..f4b98734 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 @@ -25,51 +25,36 @@ internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context, override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - val doc = - when (filter) { - is MangaListFilter.Search -> { - if (page > 1) { - return emptyList() - } - val q = filter.query.urlEncoded().replace("%20", "+") - val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q" - webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml() + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + val doc = when { + !filter.query.isNullOrEmpty() -> { + if (page > 1) { + return emptyList() } + val q = filter.query.urlEncoded().replace("%20", "+") + val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q" + webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml() + } - is MangaListFilter.Advanced -> { - val url = buildString { - append("https://") - append(domain) - - filter.tags.oneOrThrowIfMany()?.let { - append("/manga/") - append(it.key) - } + else -> { + val url = buildString { + append("https://") + append(domain) - if (page > 1) { - append("/page/") - append(page) - append('/') - } + filter.tags.oneOrThrowIfMany()?.let { + append("/manga/") + append(it.key) } - webClient.httpGet(url).parseHtml() - } - null -> { - val url = buildString { - append("https://") - append(domain) - if (page > 1) { - append("/page/") - append(page) - append('/') - } + if (page > 1) { + append("/page/") + append(page) + append('/') } - webClient.httpGet(url).parseHtml() } + webClient.httpGet(url).parseHtml() } + } return doc.select("div.sect__content.grid-items div.item-poster").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") 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 4f4a4e12..fec9ac7b 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 @@ -23,15 +23,30 @@ internal class LugnicaScans(context: MangaLoaderContext) : SortOrder.UPDATED, ) - override val availableStates: Set = - EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED) - - override val isSearchSupported = false - override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com") override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = false, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = emptySet(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) @@ -53,15 +68,15 @@ internal class LugnicaScans(context: MangaLoaderContext) : ) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - when (filter) { - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + when { + !filter.query.isNullOrEmpty() -> { throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) } - is MangaListFilter.Advanced -> { + else -> { - if (filter.sortOrder == SortOrder.ALPHABETICAL) { + if (order == SortOrder.ALPHABETICAL) { if (page > 1) { return emptyList() } @@ -92,16 +107,6 @@ internal class LugnicaScans(context: MangaLoaderContext) : return parseMangaList(webClient.httpGet(url).parseJsonArray()) } } - - null -> { - val url = buildString { - append("https://") - append(domain) - append("/api/get/homegrid/") - append(page) - } - return parseMangaList(webClient.httpGet(url).parseJsonArray()) - } } } @@ -220,7 +225,4 @@ internal class LugnicaScans(context: MangaLoaderContext) : } return pages } - - override suspend fun getAvailableTags(): Set = emptySet() - } 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 a773597e..f681926c 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 @@ -30,26 +30,25 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?query=") append(filter.query.urlEncoded()) append("&search_type=manga&page=") append(page) } - is MangaListFilter.Advanced -> { + else -> { - if (filter.sortOrder == SortOrder.UPDATED && filter.tags.isNotEmpty()) { + if (order == SortOrder.UPDATED && filter.tags.isNotEmpty()) { throw IllegalArgumentException("Filtrer part tag n'est pas disponible avec le tri pas mis à jour") } - if (filter.sortOrder == SortOrder.ALPHABETICAL) { + if (order == SortOrder.ALPHABETICAL) { append("/manga-list") filter.tags.oneOrThrowIfMany()?.let { append("/category/") @@ -61,12 +60,6 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte return emptyList() } } - - null -> { - if (page > 1) { - return emptyList() - } - } } } 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 faba6a93..eff8a44c 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 @@ -44,13 +44,12 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val postData = buildString { append("page=") append(page) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { return emptyList() } @@ -88,9 +87,9 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context } - is MangaListFilter.Advanced -> { + else -> { - if (filter.sortOrder == SortOrder.UPDATED) { + if (order == SortOrder.UPDATED) { if (filter.tags.isNotEmpty() or filter.states.isNotEmpty()) { throw IllegalArgumentException("Le filtrage par « tri par : mis à jour » avec les genres ou les statuts n'est pas pris en charge par cette source.") @@ -140,7 +139,7 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context } append("&sort_by=") - when (filter.sortOrder) { + when (order) { SortOrder.RATING -> append("score&sort_dir=desc") SortOrder.NEWEST -> append("updated_at&sort_dir=desc") SortOrder.ALPHABETICAL -> append("name&sort_dir=asc") @@ -149,8 +148,6 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context } } } - - null -> append("&sort_by=updated_at&sort_dir=desc") } } 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 87e15c21..4a509b60 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 @@ -5,7 +5,7 @@ import org.json.JSONArray import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser -import org.koitharu.kotatsu.parsers.PagedMangaParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.network.UserAgents @@ -14,7 +14,7 @@ import java.util.* @MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr") internal class ScansMangasMe(context: MangaLoaderContext) : - PagedMangaParser(context, MangaParserSource.SCANS_MANGAS_ME, 0) { + SinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) { override val availableSortOrders: Set = EnumSet.of( SortOrder.ALPHABETICAL, @@ -27,28 +27,43 @@ internal class ScansMangasMe(context: MangaLoaderContext) : override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) } - override val isMultipleTagsSupported = false - - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - if (page > 1) { - return emptyList() - } + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/?s=") append(filter.query.urlEncoded()) append("&post_type=manga") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { append("/genres/") filter.tags.oneOrThrowIfMany()?.let { @@ -56,7 +71,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) : } } else { append("/tous-nos-mangas/?order=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("popular") SortOrder.UPDATED -> append("update") SortOrder.ALPHABETICAL -> append("title") @@ -65,8 +80,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) : } } } - - null -> append("/tous-nos-mangas/?order=update") } } @@ -91,7 +104,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/tous-nos-mangas/").parseHtml() return doc.select("ul.genre li").mapNotNullToSet { li -> val key = li.selectFirstOrThrow("a").attr("href").removeSuffix('/').substringAfterLast('/') 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 482bed09..976b9e11 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 @@ -20,30 +20,48 @@ internal class ScantradUnion(context: MangaLoaderContext) : SortOrder.UPDATED, ) - override val isMultipleTagsSupported = false - override val configKeyDomain = ConfigKey.Domain("scantrad-union.com") override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/page/") append(page.toString()) append("/?s=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append("/tag/") @@ -53,25 +71,19 @@ internal class ScantradUnion(context: MangaLoaderContext) : append("/") } } else { - if (filter.sortOrder == SortOrder.ALPHABETICAL) { + if (order == SortOrder.ALPHABETICAL) { append("/manga/page/") append(page.toString()) append("/") } - if (filter.sortOrder == SortOrder.UPDATED && page > 1) { + if (order == SortOrder.UPDATED && page > 1) { return emptyList() } } } - - null -> { - append("/manga/page/") - append(page.toString()) - append("/") - } } } val doc = webClient.httpGet(url).parseHtml() @@ -183,7 +195,7 @@ internal class ScantradUnion(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/").parseHtml() val body = doc.body() val list = body.select(".asp_gochosen")[1].select("option").orEmpty() 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 4149a8c4..5d2d2025 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 @@ -73,21 +73,21 @@ internal abstract class FuzzyDoodleParser( protected open val pausedValue = "haitus" protected open val abandonedValue = "dropped" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/manga?page=") append(page) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("&title=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("&type=") append("&status=") @@ -110,8 +110,6 @@ internal abstract class FuzzyDoodleParser( append(it.key) } } - - null -> {} } } 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 faf1bd26..9c60ccaa 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 @@ -30,20 +30,57 @@ internal abstract class GalleryAdultsParser( override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val isMultipleTagsSupported = false + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = setOf( + Locale.ENGLISH, + Locale.FRENCH, + Locale.JAPANESE, + Locale.CHINESE, + Locale("es"), + Locale("ru"), + Locale("ko"), + Locale.GERMAN, + Locale("id"), + Locale.ITALIAN, + Locale("pt"), + Locale("tr"), + Locale("th"), + Locale("vi"), + ), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage( + page: Int, + order: SortOrder, + filter: MangaListFilterV2, + ): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search/?q=") append(filter.query.urlEncoded()) append("&") } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() val lang = filter.locale if (tag != null && lang != null) { @@ -61,8 +98,6 @@ internal abstract class GalleryAdultsParser( append("/?") } } - - null -> append("/?") } append("page=") append(page) @@ -102,7 +137,7 @@ internal abstract class GalleryAdultsParser( //Tags are deliberately reduced because there are too many and this slows down the application. //only the most popular ones are taken. - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { return coroutineScope { (1..3).map { page -> async { getTags(page) } @@ -110,23 +145,6 @@ internal abstract class GalleryAdultsParser( }.awaitAll().flattenTo(ArraySet(360)) } - override suspend fun getAvailableLocales(): Set = setOf( - Locale.ENGLISH, - Locale.FRENCH, - Locale.JAPANESE, - Locale.CHINESE, - Locale("es"), - Locale("ru"), - Locale("ko"), - Locale.GERMAN, - Locale("id"), - Locale.ITALIAN, - Locale("pt"), - Locale("tr"), - Locale("th"), - Locale("vi"), - ) - protected open val pathTagUrl = "/tags/popular/?page=" protected open val selectTags = ".tags_page ul.tags li" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/Hentai3.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/Hentai3.kt index aed6ed58..54be7dc4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/Hentai3.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/Hentai3.kt @@ -37,24 +37,24 @@ internal class Hentai3(context: MangaLoaderContext) : Locale("pt"), ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/search?q=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) { append("/search?q=") append(buildQuery(filter.tags, filter.locale)) - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("&sort=popular") } append("&page=") @@ -64,7 +64,7 @@ internal class Hentai3(context: MangaLoaderContext) : append(filter.locale.toLanguagePath()) append("/") append(page.toString()) - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("?sort=popular") } } else if (filter.tags.isNotEmpty()) { @@ -74,7 +74,7 @@ internal class Hentai3(context: MangaLoaderContext) : } append("/") append(page.toString()) - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("?sort=popular") } } else { @@ -82,11 +82,6 @@ internal class Hentai3(context: MangaLoaderContext) : append(page) } } - - null -> { - append("/") - append(page) - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt index d71a21f0..22f10dfa 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt @@ -24,18 +24,18 @@ internal class HentaiEnvy(context: MangaLoaderContext) : override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search/?s_key=") append(filter.query.urlEncoded()) append("&") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { if (filter.locale != null) { throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED) @@ -43,7 +43,7 @@ internal class HentaiEnvy(context: MangaLoaderContext) : filter.tags.oneOrThrowIfMany()?.let { append("/tag/") append(it.key) - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("/popular") } append("/?") @@ -56,8 +56,6 @@ internal class HentaiEnvy(context: MangaLoaderContext) : append("/?") } } - - null -> append("/?") } append("page=") append(page) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt index af2565c5..e9aab678 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt @@ -18,17 +18,21 @@ internal class HentaiEra(context: MangaLoaderContext) : override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) - override val isMultipleTagsSupported = true - + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isMultipleTagsSupported = true, + ) - override suspend fun getAvailableLocales(): Set = setOf( - Locale.ENGLISH, - Locale.FRENCH, - Locale.JAPANESE, - Locale("es"), - Locale("ru"), - Locale("ko"), - Locale.GERMAN, + override suspend fun getFilterOptions() = super.getFilterOptions().copy( + availableLocales = setOf( + Locale.ENGLISH, + Locale.FRENCH, + Locale.JAPANESE, + Locale("es"), + Locale("ru"), + Locale("ko"), + Locale.GERMAN, + ), ) override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet { @@ -41,22 +45,22 @@ internal class HentaiEra(context: MangaLoaderContext) : ) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/search/?key=") append(filter.query.urlEncoded()) append("&") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) { append("/search/?key=") - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append( buildQuery(filter.tags, filter.locale) .replace("<=1&dl=0&pp=0&tr=0", "<=0&dl=0&pp=1&tr=0"), @@ -72,7 +76,7 @@ internal class HentaiEra(context: MangaLoaderContext) : } append("/") - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("popular/") } append("?") @@ -81,7 +85,7 @@ internal class HentaiEra(context: MangaLoaderContext) : append(filter.locale.toLanguagePath()) append("/") - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("popular/") } append("?") @@ -89,8 +93,6 @@ internal class HentaiEra(context: MangaLoaderContext) : append("/?") } } - - null -> append("/?") } append("page=") append(page.toString()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt index 5b22bdac..45a955c2 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt @@ -46,22 +46,22 @@ internal class HentaiForce(context: MangaLoaderContext) : return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found") } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?q=") append(filter.query.urlEncoded()) append("&page=") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) { append("/search?q=") append(buildQuery(filter.tags, filter.locale)) - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("&sort=popular") } append("&page=") @@ -72,7 +72,7 @@ internal class HentaiForce(context: MangaLoaderContext) : } append("/") - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("popular/") } append("?") @@ -81,7 +81,7 @@ internal class HentaiForce(context: MangaLoaderContext) : append(filter.locale.toLanguagePath()) append("/") - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("popular/") } append("?") @@ -89,8 +89,6 @@ internal class HentaiForce(context: MangaLoaderContext) : append("/page/") } } - - null -> append("/page/") } append(page.toString()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt index e61d9b90..0adbcfa4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt @@ -22,12 +22,12 @@ internal class HentaiFox(context: MangaLoaderContext) : override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search/?q=") append(filter.query.urlEncoded()) if (page > 1) { @@ -36,7 +36,7 @@ internal class HentaiFox(context: MangaLoaderContext) : } } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) { append("/search/?q=") append(buildQuery(filter.tags, filter.locale)) @@ -45,7 +45,7 @@ internal class HentaiFox(context: MangaLoaderContext) : append(page.toString()) } - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("&sort=popular") } } else if (filter.tags.isNotEmpty()) { @@ -54,7 +54,7 @@ internal class HentaiFox(context: MangaLoaderContext) : append(it.key) } append("/") - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("popular/") } @@ -67,7 +67,7 @@ internal class HentaiFox(context: MangaLoaderContext) : append("/language/") append(filter.locale.toLanguagePath()) append("/") - if (filter.sortOrder == SortOrder.POPULARITY) { + if (order == SortOrder.POPULARITY) { append("popular/") } @@ -88,18 +88,6 @@ internal class HentaiFox(context: MangaLoaderContext) : } } } - - null -> { - if (page > 2) { - append("/pag/") - append(page.toString()) - append("/") - } else if (page > 1) { - append("/page/") - append(page.toString()) - append("/") - } - } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt index da99f89e..6cec2e02 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt @@ -26,22 +26,28 @@ internal class NHentaiParser(context: MangaLoaderContext) : ".tag-container:contains(Languages:) span.tags a:not(.tag-17249) span.name" // tag-17249 = translated override val idImg = "image-container" - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.POPULARITY_TODAY, SortOrder.POPULARITY_WEEK) + override val availableSortOrders: Set = + EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.POPULARITY_TODAY, SortOrder.POPULARITY_WEEK) - override val isMultipleTagsSupported = true + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isMultipleTagsSupported = true, + ) + + override suspend fun getFilterOptions() = super.getFilterOptions().copy( + availableLocales = setOf(Locale.ENGLISH, Locale.JAPANESE, Locale.CHINESE), + ) override fun getRequestHeaders(): Headers = super.getRequestHeaders().newBuilder() .set("User-Agent", config[userAgentKey]) .build() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { // Check if the query is all numbers val numericQuery = filter.query.trim() if (numericQuery.matches("\\d+".toRegex())) { @@ -56,13 +62,13 @@ internal class NHentaiParser(context: MangaLoaderContext) : } } - is MangaListFilter.Advanced -> { + else -> { append("/search/?q=pages:>0 ") // for Search with query // append(filter.query.urlEncoded()) // append(' ') append(buildQuery(filter.tags, filter.locale).urlEncoded()) - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("&sort=popular") SortOrder.POPULARITY_TODAY -> append("&sort=popular-today") SortOrder.POPULARITY_WEEK -> append("&sort=popular-week") @@ -70,8 +76,6 @@ internal class NHentaiParser(context: MangaLoaderContext) : else -> {} } } - - null -> append("/search/?q=pages:>0 ") } if (page > 1) { append("&page=") @@ -122,12 +126,6 @@ internal class NHentaiParser(context: MangaLoaderContext) : ) } - override suspend fun getAvailableLocales(): Set = setOf( - Locale.ENGLISH, - Locale.JAPANESE, - Locale.CHINESE, - ) - override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) 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 e3152db4..b69b8368 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 @@ -29,21 +29,20 @@ internal abstract class GattsuParser( protected open val tagPrefix = "tag" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/page/") append(page.toString()) append("/?s=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append("/$tagPrefix/") @@ -54,11 +53,6 @@ internal abstract class GattsuParser( append(page.toString()) } - - null -> { - append("/page/") - append(page.toString()) - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) 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 a507a961..729f60b0 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.PagedMangaParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -12,11 +12,30 @@ internal abstract class GuyaParser( context: MangaLoaderContext, source: MangaParserSource, domain: String, - pageSize: Int = 0, -) : PagedMangaParser(context, source, pageSize) { +) : SinglePageMangaParser(context, source) { override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = emptySet(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) @@ -24,25 +43,17 @@ internal abstract class GuyaParser( override val configKeyDomain = ConfigKey.Domain(domain) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - if (page > 1) return emptyList() + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/api/get_all_series/") } - when (filter) { - - is MangaListFilter.Search -> { - return parseMangaList(webClient.httpGet(url).parseJson(), filter.query) - } - - is MangaListFilter.Advanced -> {} - - null -> {} + return if (!filter.query.isNullOrEmpty()) { + parseMangaList(webClient.httpGet(url).parseJson(), filter.query) + } else { + parseMangaList(webClient.httpGet(url).parseJson(), "") } - - return parseMangaList(webClient.httpGet(url).parseJson(), "") } protected open fun parseMangaList(json: JSONObject, query: String): List { @@ -79,8 +90,6 @@ internal abstract class GuyaParser( ) } - override suspend fun getAvailableTags(): Set = emptySet() - override suspend fun getDetails(manga: Manga): Manga { val json = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseJson().getJSONObject("chapters") val slug = manga.url.removeSuffix('/').substringAfterLast('/') 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 b0403174..85dfe58c 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 @@ -39,27 +39,43 @@ internal abstract class HeanCms( SortOrder.POPULARITY_ASC, ) - override val availableStates: Set = - EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED) - - protected open val pathManga = "series" protected open val apiPath get() = getDomain("api") protected open val paramsUpdated = "latest" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(apiPath) append("/query?query_string=") - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.states.oneOrThrowIfMany()?.let { append("&status=") @@ -75,7 +91,7 @@ internal abstract class HeanCms( } append("&orderBy=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("total_views&order=desc") SortOrder.POPULARITY_ASC -> append("total_views&order=asc") SortOrder.UPDATED -> append("$paramsUpdated&order=desc") @@ -94,8 +110,6 @@ internal abstract class HeanCms( append("]".urlEncoded()) } - - null -> append("&status=All&orderBy=$paramsUpdated&order=desc&series_type=Comic&perPage=20") } append("&page=") append(page.toString()) @@ -184,7 +198,7 @@ internal abstract class HeanCms( } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/comics").parseHtml() val regex = Regex("\"tags\\\\.*?(\\[.+?])") val tags = doc.select("script").firstNotNullOf { script -> 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 3ec1f299..5f604cd6 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 @@ -41,20 +41,13 @@ internal abstract class HeanCmsAlt( protected open val selectManga = "div.grid.grid-cols-2 div:not([class]):contains(M)" protected open val selectMangaTitle = "h5" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append(listUrl) - when (filter) { - is MangaListFilter.Search -> { - throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) - } - - is MangaListFilter.Advanced -> { - } - - null -> {} + if (!filter.query.isNullOrEmpty()) { + throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) } if (page > 1) { append("?page=") 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 460c52fd..13be6565 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 @@ -31,17 +31,35 @@ internal abstract class HotComicsParser( override val availableSortOrders: Set = EnumSet.of(SortOrder.NEWEST) - override val isMultipleTagsSupported = false - protected open val mangasUrl = "/genres" protected open val onePage = false + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun getRequestHeaders(): Headers = Headers.Builder() .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { if (onePage && page > 1) { return emptyList() } @@ -49,16 +67,16 @@ internal abstract class HotComicsParser( val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/search?keyword=") append(filter.query.urlEncoded()) append("&page=") append(page) } - is MangaListFilter.Advanced -> { + else -> { append(mangasUrl) filter.tags.oneOrThrowIfMany()?.let { append('/') @@ -70,11 +88,6 @@ internal abstract class HotComicsParser( append(page) } } - - null -> { - append("/genres?page=") - append(page) - } } } val tagMap = getOrCreateTagMap() @@ -174,7 +187,7 @@ internal abstract class HotComicsParser( } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val map = getOrCreateTagMap() val tagSet = ArraySet(map.size) for (entry in map) { 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 1dc40e26..fd571086 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 @@ -33,22 +33,22 @@ class DoujinDesuParser(context: MangaLoaderContext) : .add("Referer", "https://$domain/") .build() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = urlBuilder().apply { addPathSegment("manga") addPathSegment("page") addPathSegment("$page/") - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { addQueryParameter("title", filter.query) } - is MangaListFilter.Advanced -> { + else -> { addQueryParameter("title", "") addQueryParameter( "order", - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> "update" SortOrder.POPULARITY -> "popular" SortOrder.ALPHABETICAL -> "title" @@ -72,8 +72,6 @@ class DoujinDesuParser(context: MangaLoaderContext) : ) } } - - null -> addQueryParameter("order", "update") } }.build() 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 22f5ded2..b533d5a9 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 @@ -24,20 +24,19 @@ internal class HentaiCrot(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/page/") append(page) append("/?s=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append("/category/") @@ -49,12 +48,6 @@ internal class HentaiCrot(context: MangaLoaderContext) : append(page) append('/') } - - null -> { - append("/page/") - append(page) - append('/') - } } } 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 4980ad64..ed6853d3 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 @@ -24,20 +24,19 @@ internal class PixHentai(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/page/") append(page) append("/?s=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append("/genre/") @@ -49,12 +48,6 @@ internal class PixHentai(context: MangaLoaderContext) : append(page) append('/') } - - null -> { - append("/page/") - append(page) - append('/') - } } } 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 bf4fc712..e0bc8f1c 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 @@ -28,25 +28,45 @@ internal abstract class IkenParser( override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY) - override val availableStates: Set = - EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.UPCOMING) - - override val isMultipleTagsSupported = true + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + MangaState.ABANDONED, + MangaState.UPCOMING, + ), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/api/query?page=") append(page) append("&perPage=18&searchTerm=") - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { append("&genreIds=") @@ -66,8 +86,6 @@ internal abstract class IkenParser( ) } } - - null -> {} } } return parseMangaList(webClient.httpGet(url).parseJson()) @@ -145,7 +163,7 @@ internal abstract class IkenParser( } } - override suspend fun getAvailableTags(): Set { + protected open suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/series").parseHtml() return doc.selectLastOrThrow("select").select("option[value]").mapNotNullToSet { val key = it.attr("value") ?: return@mapNotNullToSet null 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 0d5e422c..9ffc767a 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 @@ -45,32 +45,28 @@ class NicovideoSeigaParser(context: MangaLoaderContext) : override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp") - @InternalParsersApi - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { val page = (offset / 20f).toIntUp().inc() val domain = getDomain("seiga") - val url = - when (filter) { - is MangaListFilter.Search -> { - return if (offset == 0) getSearchList(filter.query, page) else emptyList() - } - - is MangaListFilter.Advanced -> { + val url = when { + !filter.query.isNullOrEmpty() -> { + return if (offset == 0) getSearchList(filter.query, page) else emptyList() + } + else -> { - if (filter.tags.isNotEmpty()) { - filter.tags.oneOrThrowIfMany().let { - "https://$domain/manga/list?category=${it?.key}&page=$page&sort=${getSortKey(filter.sortOrder)}" - } - } else { - "https://$domain/manga/list?page=$page&sort=${getSortKey(filter.sortOrder)}" + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany().let { + "https://$domain/manga/list?category=${it?.key}&page=$page&sort=${getSortKey(order)}" } + } else { + "https://$domain/manga/list?page=$page&sort=${getSortKey(order)}" } - null -> "https://$domain/manga/list?page=$page" } + } val doc = webClient.httpGet(url).parseHtml() val comicList = doc.body().select("#comic_list > ul > li") ?: doc.parseFailed("Container not found") 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 95bbece8..399ab863 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.PagedMangaParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -17,8 +17,7 @@ internal abstract class KeyoappParser( context: MangaLoaderContext, source: MangaParserSource, domain: String, - pageSize: Int = 24, -) : PagedMangaParser(context, source, pageSize) { +) : SinglePageMangaParser(context, source) { override val configKeyDomain = ConfigKey.Domain(domain) @@ -58,29 +57,20 @@ internal abstract class KeyoappParser( "dropped", ) - init { - paginator.firstPage = 1 - searchPaginator.firstPage = 1 - } - - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { var query = "" var tag = "" - if (page > 1) { - return emptyList() - } - val url = urlBuilder().apply { - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { addPathSegment("series") query = filter.query } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { @@ -88,15 +78,13 @@ internal abstract class KeyoappParser( } } - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> addPathSegment("latest") SortOrder.NEWEST -> addPathSegment("series") else -> addPathSegment("latest") } } - - null -> addPathSegment("latest") } }.build() 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 8ea7aae5..b6282831 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 @@ -37,25 +37,25 @@ internal abstract class LikeMangaParser( override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/?act=search") - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&f") append("[keyword]".urlEncoded()) append("=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("&f") append("[sortby]".urlEncoded()) append("=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("hot") SortOrder.UPDATED -> append("lastest-chap") SortOrder.NEWEST -> append("lastest-manga") @@ -85,12 +85,6 @@ internal abstract class LikeMangaParser( ) } } - - null -> { - append("&f") - append("[sortby]".urlEncoded()) - append("=lastest-chap") - } } if (page > 1) { 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 9c4bda49..c7315bda 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 @@ -27,6 +27,26 @@ internal abstract class MadaraParser( override val configKeyDomain = ConfigKey.Domain(domain) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) @@ -54,8 +74,6 @@ internal abstract class MadaraParser( } } - override val isMultipleTagsSupported = true - override val availableSortOrders: Set = EnumSet.of( SortOrder.UPDATED, SortOrder.UPDATED_ASC, @@ -69,12 +87,6 @@ internal abstract class MadaraParser( SortOrder.RATING_ASC, ) - override val availableStates: Set = EnumSet.allOf(MangaState::class.java) - - override val availableContentRating: Set = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT) - - override val isTagsExclusionSupported = true - protected open val tagPrefix = "manga-genre/" protected open val datePattern = "MMMM d, yyyy" protected open val stylePage = "?style=list" @@ -196,7 +208,7 @@ internal abstract class MadaraParser( // can be changed to retrieve tags see getTags protected open val listUrl = "manga/" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { if (withoutAjax) { val pages = page + 1 @@ -204,9 +216,9 @@ internal abstract class MadaraParser( append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { if (pages > 1) { append("/page/") append(pages.toString()) @@ -216,7 +228,7 @@ internal abstract class MadaraParser( append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { if (pages > 1) { append("/page/") append(pages.toString()) @@ -278,7 +290,7 @@ internal abstract class MadaraParser( append("&m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") @@ -288,10 +300,6 @@ internal abstract class MadaraParser( else -> append("latest") } } - - null -> { - append("/?s=&post_type=wp-manga&m_orderby=latest") - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) @@ -301,13 +309,13 @@ internal abstract class MadaraParser( payload["page"] = page.toString() - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { payload["vars[s]"] = filter.query.urlEncoded() } - is MangaListFilter.Advanced -> { + else -> { // Support query @@ -362,7 +370,7 @@ internal abstract class MadaraParser( payload["vars[tax_query][relation]"] = "AND" } - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> { payload["vars[meta_key]"] = "_wp_manga_views" payload["vars[orderby]"] = "meta_value_num" @@ -453,10 +461,6 @@ internal abstract class MadaraParser( } } } - - null -> { - payload["vars[meta_key]"] = "_latest_update" - } } return parseMangaList( @@ -510,7 +514,7 @@ internal abstract class MadaraParser( } } - override suspend fun getAvailableTags(): Set { + protected open suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() val body = doc.body() val root1 = body.selectFirst("header")?.selectFirst("ul.second-menu") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manga18Fx.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manga18Fx.kt index 757a6590..d838b1dd 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manga18Fx.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manga18Fx.kt @@ -29,20 +29,19 @@ internal class Manga18Fx(context: MangaLoaderContext) : override val availableStates: Set get() = emptySet() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?q=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -59,13 +58,6 @@ internal class Manga18Fx(context: MangaLoaderContext) : } } } - - null -> { - if (page > 1) { - append("/page/") - append(page) - } - } } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manhwa18Cc.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manhwa18Cc.kt index 1090b618..05b8501a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manhwa18Cc.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manhwa18Cc.kt @@ -32,20 +32,20 @@ internal class Manhwa18Cc(context: MangaLoaderContext) : override val availableStates: Set get() = emptySet() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/search?q=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -60,7 +60,7 @@ internal class Manhwa18Cc(context: MangaLoaderContext) : } append("?orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("trending") SortOrder.UPDATED -> append("latest") SortOrder.ALPHABETICAL -> append("alphabet") @@ -68,10 +68,6 @@ internal class Manhwa18Cc(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append("?s&post_type=wp-manga&m_orderby=latest") - } } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AdultWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AdultWebtoon.kt index e2cc5c71..dd2425e8 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AdultWebtoon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AdultWebtoon.kt @@ -25,22 +25,27 @@ internal class AdultWebtoon(context: MangaLoaderContext) : override val listUrl = "adult-webtoon/" override val postReq = true override val withoutAjax = true - override val isTagsExclusionSupported = false override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING) - override val availableStates: Set = emptySet() + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isTagsExclusionSupported = false, + ) - override val availableContentRating: Set = emptySet() + override suspend fun getFilterOptions() = super.getFilterOptions().copy( + availableStates = emptySet(), + availableContentRating = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val pages = page + 1 val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (pages > 1) { append("/page/") append(pages.toString()) @@ -50,7 +55,7 @@ internal class AdultWebtoon(context: MangaLoaderContext) : append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { @@ -71,7 +76,7 @@ internal class AdultWebtoon(context: MangaLoaderContext) : } append("?m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") @@ -80,16 +85,6 @@ internal class AdultWebtoon(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append('/') - append(listUrl) - if (pages > 1) { - append("page/") - append(pages) - append("/?m_orderby=latest") - } - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Hentai4Free.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Hentai4Free.kt index fc47770e..b332b07b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Hentai4Free.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Hentai4Free.kt @@ -26,12 +26,12 @@ internal class Hentai4Free(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/page/") append(page.toString()) append("/?s=") @@ -39,7 +39,7 @@ internal class Hentai4Free(context: MangaLoaderContext) : append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -85,7 +85,7 @@ internal class Hentai4Free(context: MangaLoaderContext) : } append("m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") @@ -96,14 +96,6 @@ internal class Hentai4Free(context: MangaLoaderContext) : } - - null -> { - if (page > 1) { - append("/page/") - append(page.toString()) - } - append("/?m_orderby=latest") - } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiManga.kt index 637b06e8..f4ab47a5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiManga.kt @@ -29,14 +29,14 @@ internal class HentaiManga(context: MangaLoaderContext) : override val availableStates: Set = emptySet() override val availableContentRating: Set = emptySet() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val pages = page + 1 val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (pages > 1) { append("/page/") append(pages.toString()) @@ -46,7 +46,7 @@ internal class HentaiManga(context: MangaLoaderContext) : append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { @@ -67,7 +67,7 @@ internal class HentaiManga(context: MangaLoaderContext) : } append("?m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") @@ -76,16 +76,6 @@ internal class HentaiManga(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append('/') - append(listUrl) - if (pages > 1) { - append("page/") - append(pages) - append("/?m_orderby=latest") - } - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiWebtoon.kt index 4848a5d1..39196be1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiWebtoon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiWebtoon.kt @@ -23,20 +23,27 @@ internal class HentaiWebtoon(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.HENTAIWEBTOON, "hentaiwebtoon.com") { override val postReq = true override val withoutAjax = true - override val isTagsExclusionSupported = false override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING) - override val availableStates: Set = emptySet() - override val availableContentRating: Set = emptySet() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isMultipleTagsSupported = false, + ) + + override suspend fun getFilterOptions() = super.getFilterOptions().copy( + availableContentRating = emptySet(), + availableStates = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val pages = page + 1 val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (pages > 1) { append("/page/") append(pages.toString()) @@ -46,7 +53,7 @@ internal class HentaiWebtoon(context: MangaLoaderContext) : append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { @@ -67,7 +74,7 @@ internal class HentaiWebtoon(context: MangaLoaderContext) : } append("?m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") @@ -76,16 +83,6 @@ internal class HentaiWebtoon(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append('/') - append(listUrl) - if (pages > 1) { - append("page/") - append(pages) - append("/?m_orderby=latest") - } - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt index abd9b902..0bdbea34 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt @@ -28,13 +28,12 @@ internal class IsekaiScan(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/?search=") append(filter.query.urlEncoded()) append("&page=") @@ -42,14 +41,14 @@ internal class IsekaiScan(context: MangaLoaderContext) : append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { append("/$tagPrefix") append(tag?.key.orEmpty()) append("?orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("2") SortOrder.UPDATED -> append("3") else -> append("3") @@ -57,7 +56,7 @@ internal class IsekaiScan(context: MangaLoaderContext) : append("&page=") append(page.toString()) } else { - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("/popular-manga") SortOrder.UPDATED -> append("/latest-manga") else -> append("/latest-manga") @@ -66,11 +65,6 @@ internal class IsekaiScan(context: MangaLoaderContext) : append(page.toString()) } } - - null -> { - append("/latest-manga?page=") - append(page.toString()) - } } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScanEuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScanEuParser.kt index 157c3c9b..63983610 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScanEuParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScanEuParser.kt @@ -23,15 +23,14 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { if (page > 1) { append("/page/") append(page.toString()) @@ -41,7 +40,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) : append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -84,7 +83,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) : } append("m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") @@ -93,10 +92,6 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append("/?s&post_type=wp-manga&m_orderby=latest") - } } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDass.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDass.kt index c4653fa3..77deec03 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDass.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDass.kt @@ -32,20 +32,19 @@ internal class MangaDass(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?q=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -62,7 +61,7 @@ internal class MangaDass(context: MangaLoaderContext) : } append("orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") @@ -71,13 +70,6 @@ internal class MangaDass(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append("/$listUrl") - append("/") - append(page.toString()) - append("?orderby=latest") - } } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDna.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDna.kt index 502fe325..a61e2d8d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDna.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDna.kt @@ -25,19 +25,19 @@ internal class MangaDna(context: MangaLoaderContext) : override val availableStates: Set = emptySet() override val availableContentRating: Set = emptySet() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?q=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -52,7 +52,7 @@ internal class MangaDna(context: MangaLoaderContext) : } append("?orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("trending") SortOrder.UPDATED -> append("latest") SortOrder.ALPHABETICAL -> append("alphabet") @@ -60,13 +60,6 @@ internal class MangaDna(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append("/$listUrl") - append("/page/") - append(page.toString()) - append("?orderby=latest") - } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt index f1e8a37c..acc5566e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt @@ -32,12 +32,12 @@ internal class MangaPure(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?s=") append(filter.query.urlEncoded()) append("&page=") @@ -45,14 +45,14 @@ internal class MangaPure(context: MangaLoaderContext) : append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { append("/$tagPrefix") append(tag?.key.orEmpty()) append("?orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("2") SortOrder.UPDATED -> append("3") else -> append("3") @@ -60,7 +60,7 @@ internal class MangaPure(context: MangaLoaderContext) : append("&page=") append(page.toString()) } else { - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("/popular-manga") SortOrder.UPDATED -> append("/latest-manga") else -> append("/latest-manga") @@ -69,11 +69,6 @@ internal class MangaPure(context: MangaLoaderContext) : append(page.toString()) } } - - null -> { - append("/latest-manga?page=") - append(page.toString()) - } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwaz.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwaz.kt index cfcbea71..0c11b8fb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwaz.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwaz.kt @@ -33,19 +33,19 @@ internal class Manhwaz(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?s=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -62,7 +62,7 @@ internal class Manhwaz(context: MangaLoaderContext) : } append("m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new") @@ -70,13 +70,6 @@ internal class Manhwaz(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append("/$listUrl") - append("?page=") - append(page.toString()) - append("&m_orderby=latest") - } } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManyToon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManyToon.kt index 0ab378b0..f22242d4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManyToon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManyToon.kt @@ -32,14 +32,14 @@ internal class ManyToon(context: MangaLoaderContext) : override val availableContentRating: Set = emptySet() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val pages = page + 1 val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (pages > 1) { append("/page/") append(pages.toString()) @@ -49,7 +49,7 @@ internal class ManyToon(context: MangaLoaderContext) : append("&post_type=wp-manga") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { @@ -70,7 +70,7 @@ internal class ManyToon(context: MangaLoaderContext) : } append("?m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") @@ -79,16 +79,6 @@ internal class ManyToon(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append('/') - append(listUrl) - if (pages > 1) { - append("page/") - append(pages) - append("/?m_orderby=latest") - } - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/DragonTranslationParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/DragonTranslationParser.kt index b50f133d..3277973a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/DragonTranslationParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/DragonTranslationParser.kt @@ -20,19 +20,19 @@ internal class DragonTranslationParser(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/mangas?buscar=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { append("/mangas?page=") append(page.toString()) @@ -43,11 +43,6 @@ internal class DragonTranslationParser(context: MangaLoaderContext) : append(tag?.key.orEmpty()) } } - - null -> { - append("/mangas?page=") - append(page.toString()) - } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TmoManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TmoManga.kt index 283d790d..3516f04a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TmoManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TmoManga.kt @@ -26,12 +26,12 @@ internal class TmoManga(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/$listUrl") append("?search=") append(filter.query.urlEncoded()) @@ -41,7 +41,7 @@ internal class TmoManga(context: MangaLoaderContext) : } } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -59,14 +59,6 @@ internal class TmoManga(context: MangaLoaderContext) : } } } - - null -> { - append("/$listUrl") - if (page > 1) { - append("?page=") - append(page) - } - } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt index 9d8e69e8..2cb5fba7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt @@ -15,31 +15,38 @@ internal class ManhwaHub(context: MangaLoaderContext) : override val datePattern = "MMMM d, yyyy" override val sourceLocale: Locale = Locale.ENGLISH override val withoutAjax = true - override val isTagsExclusionSupported = false override val listUrl = "genre/manhwa" override val selectTestAsync = "ul.box-list-chapter" - override val availableStates: Set = emptySet() - override val availableContentRating: Set = emptySet() override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isTagsExclusionSupported = false, + ) + init { paginator.firstPage = 1 searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = super.getFilterOptions().copy( + availableStates = emptySet(), + availableContentRating = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?s=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -55,11 +62,6 @@ internal class ManhwaHub(context: MangaLoaderContext) : } - - null -> { - append("/?page=") - append(page.toString()) - } } } val doc = webClient.httpGet(url).parseHtml() @@ -98,7 +100,7 @@ internal class ManhwaHub(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { + override suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain").parseHtml() return doc.select("div.genres li").mapNotNullToSet { li -> val a = li.selectFirst("a") ?: return@mapNotNullToSet null diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Saytruyenhay.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Saytruyenhay.kt index 5099a107..97e962b9 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Saytruyenhay.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Saytruyenhay.kt @@ -6,9 +6,11 @@ import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.util.* import java.util.* +import kotlin.collections.Set @Broken @MangaSourceParser("SAYTRUYENHAY", "PheTruyen", "vi") @@ -17,31 +19,38 @@ internal class Saytruyenhay(context: MangaLoaderContext) : override val tagPrefix = "genre/" override val withoutAjax = true - override val isTagsExclusionSupported = false override val listUrl = "public/genre/manga/" - override val availableStates: Set = emptySet() - override val availableContentRating: Set = emptySet() override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.RATING, SortOrder.NEWEST) + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isTagsExclusionSupported = false, + ) + init { paginator.firstPage = 1 searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = super.getFilterOptions().copy( + availableStates = emptySet(), + availableContentRating = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?s=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { @@ -54,7 +63,7 @@ internal class Saytruyenhay(context: MangaLoaderContext) : append("?page=") append(page.toString()) append("&m_orderby=") - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> append("latest") SortOrder.RATING -> append("rating") SortOrder.POPULARITY -> append("views") @@ -62,12 +71,6 @@ internal class Saytruyenhay(context: MangaLoaderContext) : else -> append("latest") } } - - null -> { - append("/$listUrl") - append("?page=") - append(page.toString()) - } } } 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 81c4d9b8..fee5278e 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 @@ -34,8 +34,6 @@ internal abstract class MadthemeParser( SortOrder.RATING, ) - override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) - protected open val listUrl = "search/" protected open val datePattern = "MMM dd, yyyy" @@ -59,23 +57,43 @@ internal abstract class MadthemeParser( "COMPLETED", ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append('/') append(listUrl) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("?sort=updated_at&q=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("?sort=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("updated_at") SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty. @@ -104,8 +122,6 @@ internal abstract class MadthemeParser( } } - - null -> append("?sort=updated_at") } append("&page=") @@ -140,7 +156,7 @@ internal abstract class MadthemeParser( } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() return doc.select("div.genres .checkbox").mapNotNullToSet { checkbox -> val key = checkbox.selectFirstOrThrow("input").attr("value") ?: return@mapNotNullToSet null diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ManhuaScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ManhuaScan.kt index cd29c461..dd56d843 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ManhuaScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ManhuaScan.kt @@ -17,23 +17,23 @@ internal class ManhuaScan(context: MangaLoaderContext) : override val sourceLocale: Locale = Locale.ENGLISH override val listUrl = "search" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append('/') append(listUrl) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("?sort=updated_at&q=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("?sort=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("updated_at") SortOrder.ALPHABETICAL -> append("name") @@ -62,8 +62,6 @@ internal class ManhuaScan(context: MangaLoaderContext) : } } - - null -> append("?sort=updated_at") } append("&page=") 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 9d7a98f2..61a60559 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 @@ -54,14 +54,14 @@ internal abstract class Manga18Parser( "Completed", ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append('/') - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(listUrl) append(page.toString()) append("?search=") @@ -69,7 +69,7 @@ internal abstract class Manga18Parser( append("&order_by=latest") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append(tagUrl) @@ -82,19 +82,13 @@ internal abstract class Manga18Parser( append(page.toString()) append("?order_by=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views") SortOrder.UPDATED -> append("lastest") SortOrder.ALPHABETICAL -> append("name") else -> append("latest") } } - - null -> { - append(listUrl) - append(page.toString()) - append("?order_by=latest") - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) 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 f3b96354..472a83cb 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 @@ -55,20 +55,20 @@ internal abstract class MangaboxParser( "completed", ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append(listUrl) append("/?s=all") - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("&keyw=") append(filter.query.replace(" ", "_").urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { append("&g_i=") @@ -100,7 +100,7 @@ internal abstract class MangaboxParser( } append("&orby=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("topview") SortOrder.UPDATED -> append("") SortOrder.NEWEST -> append("newest") @@ -108,8 +108,6 @@ internal abstract class MangaboxParser( else -> append("") } } - - null -> {} } append("&page=") 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 c6cac7e0..09851381 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 @@ -33,22 +33,23 @@ internal class Mangairo(context: MangaLoaderContext) : ) override val isTagsExclusionSupported = false override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(searchUrl) append(filter.query.urlEncoded()) append("?page=") } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("/type-") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("topview") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("newest") @@ -81,11 +82,6 @@ internal class Mangairo(context: MangaLoaderContext) : append("/page-") } - - null -> { - append(listUrl) - append("/type-latest/ctg-all/state-all/page-") - } } append(page.toString()) } 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 9e95a365..2a3974f8 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 @@ -24,13 +24,13 @@ internal class Mangakakalot(context: MangaLoaderContext) : override val otherDomain = "chapmanganato.com" override val listUrl = "/manga_list" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(searchUrl) val regex = Regex("[^A-Za-z0-9 ]") val q = regex.replace(filter.query, "") @@ -38,10 +38,10 @@ internal class Mangakakalot(context: MangaLoaderContext) : append("?page=") } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("?type=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("topview") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("newest") @@ -67,11 +67,6 @@ internal class Mangakakalot(context: MangaLoaderContext) : append("&page=") } - - null -> { - append(listUrl) - append("?type=latest&page=") - } } append(page.toString()) } 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 da550d59..0cb4735c 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,22 +25,22 @@ internal class MangakakalotTv(context: MangaLoaderContext) : override val isMultipleTagsSupported = false override val isTagsExclusionSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(searchUrl) append(filter.query.urlEncoded()) append("?page=") } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("?type=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("topview") SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("newest") @@ -66,11 +66,6 @@ internal class MangakakalotTv(context: MangaLoaderContext) : append("&page=") } - - null -> { - append(listUrl) - append("?type=latest&page=") - } } append(page.toString()) } 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 ce987662..486b377d 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 @@ -27,16 +27,6 @@ internal abstract class MangAdventureParser( keys.add(userAgentKey) } - override val availableStates: Set = EnumSet.of( - MangaState.ONGOING, - MangaState.FINISHED, - MangaState.ABANDONED, - MangaState.PAUSED, - ) - - override val availableContentRating: Set = - EnumSet.of(ContentRating.SAFE) - override val availableSortOrders: Set = EnumSet.of( SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC, @@ -46,18 +36,45 @@ internal abstract class MangAdventureParser( override val defaultSortOrder = SortOrder.ALPHABETICAL - override val isTagsExclusionSupported = true + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + MangaState.ABANDONED, + MangaState.PAUSED, + ), + availableContentRating = EnumSet.of(ContentRating.SAFE), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage( + page: Int, + order: SortOrder, + filter: MangaListFilterV2, + ): List { val url = apiUrl.addEncodedPathSegment("series") .addEncodedQueryParameter("limit", pageSize.toString()) .addEncodedQueryParameter("page", page.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { url.addQueryParameter("title", filter.query) } - is MangaListFilter.Advanced -> { + else -> { url.addQueryParameter( "categories", buildString { @@ -79,7 +96,7 @@ internal abstract class MangAdventureParser( MangaState.PAUSED -> url.addEncodedQueryParameter("status", "hiatus") else -> throw IllegalArgumentException(ERROR_UNSUPPORTED_STATE) } - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> url.addEncodedQueryParameter("sort", "title") SortOrder.ALPHABETICAL_DESC -> url.addEncodedQueryParameter("sort", "-title") SortOrder.UPDATED -> url.addEncodedQueryParameter("sort", "-latest_upload") @@ -87,8 +104,6 @@ internal abstract class MangAdventureParser( else -> throw IllegalArgumentException(ERROR_UNSUPPORTED_SORT_ORDER) } } - - else -> {} } return runCatchingCancellable { getManga(url.get()) }.getOrElse { if (it is NotFoundException) emptyList() else throw it @@ -159,7 +174,7 @@ internal abstract class MangAdventureParser( override suspend fun getPageUrl(page: MangaPage) = page.url - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val url = apiUrl.addEncodedPathSegment("categories") return url.get()?.optJSONArray("results")?.mapJSONToSet { val name = it.getString("name") 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 a6a81023..5d698b56 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 @@ -43,20 +43,31 @@ internal abstract class MangaReaderParser( SortOrder.NEWEST, ) - override val availableStates: Set - get() = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override val availableContentTypes: Set - get() = EnumSet.of( + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = getOrCreateTagMap().values.toSet(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED), + availableContentRating = emptySet(), + availableContentTypes = EnumSet.of( ContentType.MANGA, ContentType.MANHWA, ContentType.MANHUA, ContentType.COMICS, ContentType.NOVEL, - ) - - - override val isTagsExclusionSupported = true + ), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) protected open val listUrl = "/manga" protected open val datePattern = "MMMM d, yyyy" @@ -65,26 +76,26 @@ internal abstract class MangaReaderParser( protected var tagCache: ArrayMap? = null protected val mutex = Mutex() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/page/") append(page.toString()) append("/?s=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("/?order=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "title" SortOrder.ALPHABETICAL_DESC -> "titlereverse" SortOrder.NEWEST -> "latest" @@ -137,12 +148,6 @@ internal abstract class MangaReaderParser( append("&page=") append(page.toString()) } - - null -> { - append(listUrl) - append("/?order=update&page=") - append(page.toString()) - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) @@ -348,10 +353,6 @@ internal abstract class MangaReaderParser( } } - override suspend fun getAvailableTags(): Set { - return getOrCreateTagMap().values.toSet() - } - protected open suspend fun getOrCreateTagMap(): Map = mutex.withLock { tagCache?.let { return@withLock it } val tagMap = ArrayMap() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Normoyun.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Normoyun.kt index 640b80a9..388e3f12 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Normoyun.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Normoyun.kt @@ -18,24 +18,23 @@ internal class Normoyun(context: MangaLoaderContext) : override val isNetShieldProtected = true override val isTagsExclusionSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/?s=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("/?order=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "a-z" SortOrder.ALPHABETICAL_DESC -> "z-a" SortOrder.NEWEST -> "added" @@ -63,11 +62,6 @@ internal class Normoyun(context: MangaLoaderContext) : } } } - - null -> { - append(listUrl) - append("/?order=update") - } } append("&page=") append(page.toString()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RizzComic.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RizzComic.kt index 37dbdc85..46e5969c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RizzComic.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RizzComic.kt @@ -55,14 +55,14 @@ internal class RizzComic(context: MangaLoaderContext) : return randomPartRegex.find(slug)?.groupValues?.get(1) ?: "" } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { if (page > 1) { return emptyList() } var url = "https://$domain$filterUrl" - val payload = when (filter) { - is MangaListFilter.Search -> { + val payload = when { + !filter.query.isNullOrEmpty() -> { url = "https://$domain$searchUrl" if (filter.query != "") { FormBody.Builder() @@ -73,7 +73,7 @@ internal class RizzComic(context: MangaLoaderContext) : } } - is MangaListFilter.Advanced -> { + else -> { val state = filter.states.oneOrThrowIfMany()?.toPayloadValue() ?: "all" val genres = filter.tags.map { it.key } @@ -81,21 +81,13 @@ internal class RizzComic(context: MangaLoaderContext) : val formBuilder = FormBody.Builder() .add("StatusValue", state) .add("TypeValue", "all") - .add("OrderValue", filter.sortOrder.toPayloadValue()) + .add("OrderValue", order.toPayloadValue()) genres.forEach { genre -> formBuilder.add("genres_checked[]", genre) } formBuilder.build() } - - else -> { - FormBody.Builder() - .add("StatusValue", "all") - .add("TypeValue", "all") - .add("OrderValue", "all") - .build() - } } val request = Request.Builder() .url(url) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Zahard.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Zahard.kt index 72325ae2..c5cf8300 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Zahard.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Zahard.kt @@ -18,32 +18,36 @@ internal class Zahard(context: MangaLoaderContext) : override val selectChapter = "#chapterlist > ul > a" override val selectPage = "div#chapter_imgs img" override val availableSortOrders: Set = EnumSet.of(SortOrder.NEWEST) - override val availableStates: Set = emptySet() - override val isMultipleTagsSupported = false - override val isTagsExclusionSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + ) + + override suspend fun getFilterOptions() = super.getFilterOptions().copy( + availableStates = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append(listUrl) append("?page=") append(page.toString()) - when (filter) { - - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&search=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append("tag=") append(it.key) } } - - null -> {} } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/HentaiReader.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/HentaiReader.kt index f3c54129..a18ceca1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/HentaiReader.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/HentaiReader.kt @@ -7,6 +7,7 @@ import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaListFilter +import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaState @@ -22,14 +23,14 @@ internal class HentaiReader(context: MangaLoaderContext) : override val listUrl = "/tipo/all" override val isTagsExclusionSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(listUrl) append("?s=") append(filter.query.urlEncoded()) @@ -37,11 +38,11 @@ internal class HentaiReader(context: MangaLoaderContext) : append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("?order=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "title" SortOrder.ALPHABETICAL_DESC -> "titlereverse" SortOrder.NEWEST -> "latest" @@ -73,12 +74,6 @@ internal class HentaiReader(context: MangaLoaderContext) : append("&page=") append(page.toString()) } - - null -> { - append(listUrl) - append("/?order=update&page=") - append(page.toString()) - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/LectorHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/LectorHentai.kt index 6dec4109..7a34f2b0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/LectorHentai.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/LectorHentai.kt @@ -6,6 +6,7 @@ import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaListFilter +import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaState @@ -20,14 +21,14 @@ internal class LectorHentai(context: MangaLoaderContext) : override val listUrl = "/tipo/all" override val isTagsExclusionSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(listUrl) append("?s=") append(filter.query.urlEncoded()) @@ -35,11 +36,11 @@ internal class LectorHentai(context: MangaLoaderContext) : append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("?order=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "title" SortOrder.ALPHABETICAL_DESC -> "titlereverse" SortOrder.NEWEST -> "latest" @@ -71,12 +72,6 @@ internal class LectorHentai(context: MangaLoaderContext) : append("&page=") append(page.toString()) } - - null -> { - append(listUrl) - append("/?order=update&page=") - append(page.toString()) - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/MangaTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/MangaTv.kt index 2f679c7d..1093d9c6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/MangaTv.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/MangaTv.kt @@ -6,6 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaListFilter +import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaState @@ -27,26 +28,26 @@ internal class MangaTv(context: MangaLoaderContext) : override val isTagsExclusionSupported = false override val datePattern = "yyyy-MM-dd" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/lista?s=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("/?order=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "title" SortOrder.ALPHABETICAL_DESC -> "titlereverse" SortOrder.NEWEST -> "latest" @@ -85,12 +86,6 @@ internal class MangaTv(context: MangaLoaderContext) : append("&page=") append(page.toString()) } - - null -> { - append(listUrl) - append("/?order=update&page=") - append(page.toString()) - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt index 1314b7cd..99bf7d6b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt @@ -22,28 +22,25 @@ internal class TuManhwas(context: MangaLoaderContext) : override suspend fun getAvailableTags(): Set = emptySet() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append(listUrl) append("?page=") append(page.toString()) - when (filter) { - - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("&search=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append("&genero=") append(it.key) } } - - null -> {} } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/RevolutionScantrad.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/RevolutionScantrad.kt index da94e929..8d34b6a4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/RevolutionScantrad.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/RevolutionScantrad.kt @@ -4,25 +4,9 @@ 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.model.Manga -import org.koitharu.kotatsu.parsers.model.MangaChapter -import org.koitharu.kotatsu.parsers.model.MangaListFilter -import org.koitharu.kotatsu.parsers.model.MangaParserSource -import org.koitharu.kotatsu.parsers.model.MangaState -import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN -import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl -import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl -import org.koitharu.kotatsu.parsers.util.domain -import org.koitharu.kotatsu.parsers.util.generateUid -import org.koitharu.kotatsu.parsers.util.mapChapters -import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany -import org.koitharu.kotatsu.parsers.util.parseHtml -import org.koitharu.kotatsu.parsers.util.src -import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl -import org.koitharu.kotatsu.parsers.util.tryParse -import org.koitharu.kotatsu.parsers.util.urlEncoded +import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat @MangaSourceParser("REVOLUTIONSCANTRAD", "RevolutionScantrad", "fr") @@ -39,71 +23,60 @@ internal class RevolutionScantrad(context: MangaLoaderContext) : override val isTagsExclusionSupported = false override val isSearchSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { if (page > 1) { return emptyList() } + if (!filter.query.isNullOrEmpty()) { + throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) + } val url = buildString { append("https://") append(domain) - when (filter) { - - is MangaListFilter.Search -> { - throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) - } - is MangaListFilter.Advanced -> { - append(listUrl) + append(listUrl) - append("?order=") - append( - when (filter.sortOrder) { - SortOrder.ALPHABETICAL -> "title" - SortOrder.ALPHABETICAL_DESC -> "titlereverse" - SortOrder.NEWEST -> "latest" - SortOrder.POPULARITY -> "popular" - SortOrder.UPDATED -> "update" - else -> "" - }, - ) + append("?order=") + append( + when (order) { + SortOrder.ALPHABETICAL -> "title" + SortOrder.ALPHABETICAL_DESC -> "titlereverse" + SortOrder.NEWEST -> "latest" + SortOrder.POPULARITY -> "popular" + SortOrder.UPDATED -> "update" + else -> "" + }, + ) - filter.tags.forEach { - append("&") - append("genre[]".urlEncoded()) - append("=") - append(it.key) - } + filter.tags.forEach { + append("&") + append("genre[]".urlEncoded()) + append("=") + append(it.key) + } - filter.tagsExclude.forEach { - append("&") - append("genre[]".urlEncoded()) - append("=-") - append(it.key) - } + filter.tagsExclude.forEach { + append("&") + append("genre[]".urlEncoded()) + append("=-") + append(it.key) + } - if (filter.states.isNotEmpty()) { - filter.states.oneOrThrowIfMany()?.let { - append("&status=") - when (it) { - MangaState.ONGOING -> append("ongoing") - MangaState.FINISHED -> append("completed") - MangaState.PAUSED -> append("hiatus") - else -> append("") - } - } + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + when (it) { + MangaState.ONGOING -> append("ongoing") + MangaState.FINISHED -> append("completed") + MangaState.PAUSED -> append("hiatus") + else -> append("") } - - append("&page=") - append(page.toString()) - } - - null -> { - append(listUrl) - append("/?order=update&page=") - append(page.toString()) } } + + append("&page=") + append(page.toString()) } return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/XxxRevolutionScantrad.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/XxxRevolutionScantrad.kt index 3aeb4ff4..82570c25 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/XxxRevolutionScantrad.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/XxxRevolutionScantrad.kt @@ -1,29 +1,11 @@ package org.koitharu.kotatsu.parsers.site.mangareader.fr 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.model.ContentType -import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.MangaChapter -import org.koitharu.kotatsu.parsers.model.MangaListFilter -import org.koitharu.kotatsu.parsers.model.MangaParserSource -import org.koitharu.kotatsu.parsers.model.MangaState -import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN -import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl -import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl -import org.koitharu.kotatsu.parsers.util.domain -import org.koitharu.kotatsu.parsers.util.generateUid -import org.koitharu.kotatsu.parsers.util.mapChapters -import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany -import org.koitharu.kotatsu.parsers.util.parseHtml -import org.koitharu.kotatsu.parsers.util.src -import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl -import org.koitharu.kotatsu.parsers.util.tryParse -import org.koitharu.kotatsu.parsers.util.urlEncoded +import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat @MangaSourceParser("XXXREVOLUTIONSCANTRAD", "Xxx.RevolutionScantrad", "fr", ContentType.HENTAI) @@ -37,74 +19,62 @@ internal class XxxRevolutionScantrad(context: MangaLoaderContext) : ) { override val listUrl = "/series.html" override val datePattern = "yyyy" - override val isTagsExclusionSupported = false - override val isSearchSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isSearchSupported = false, + isTagsExclusionSupported = false, + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { if (page > 1) { return emptyList() } val url = buildString { append("https://") append(domain) + append(listUrl) + + append("?order=") + append( + when (order) { + SortOrder.ALPHABETICAL -> "title" + SortOrder.ALPHABETICAL_DESC -> "titlereverse" + SortOrder.NEWEST -> "latest" + SortOrder.POPULARITY -> "popular" + SortOrder.UPDATED -> "update" + else -> "" + }, + ) - when (filter) { - - is MangaListFilter.Search -> { - throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) - } - - is MangaListFilter.Advanced -> { - append(listUrl) - - append("?order=") - append( - when (filter.sortOrder) { - SortOrder.ALPHABETICAL -> "title" - SortOrder.ALPHABETICAL_DESC -> "titlereverse" - SortOrder.NEWEST -> "latest" - SortOrder.POPULARITY -> "popular" - SortOrder.UPDATED -> "update" - else -> "" - }, - ) - - filter.tags.forEach { - append("&") - append("genre[]".urlEncoded()) - append("=") - append(it.key) - } + filter.tags.forEach { + append("&") + append("genre[]".urlEncoded()) + append("=") + append(it.key) + } - filter.tagsExclude.forEach { - append("&") - append("genre[]".urlEncoded()) - append("=-") - append(it.key) - } + filter.tagsExclude.forEach { + append("&") + append("genre[]".urlEncoded()) + append("=-") + append(it.key) + } - if (filter.states.isNotEmpty()) { - filter.states.oneOrThrowIfMany()?.let { - append("&status=") - when (it) { - MangaState.ONGOING -> append("ongoing") - MangaState.FINISHED -> append("completed") - MangaState.PAUSED -> append("hiatus") - else -> append("") - } - } + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + when (it) { + MangaState.ONGOING -> append("ongoing") + MangaState.FINISHED -> append("completed") + MangaState.PAUSED -> append("hiatus") + else -> append("") } - - append("&page=") - append(page.toString()) - } - - null -> { - append(listUrl) - append("/?order=update&page=") - append(page.toString()) } } + + append("&page=") + append(page.toString()) } return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikSan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikSan.kt index 05a8c428..e9fd353d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikSan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikSan.kt @@ -20,25 +20,25 @@ internal class KomikSan(context: MangaLoaderContext) : override val datePattern = "MMM d, yyyy" override val isTagsExclusionSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/search?search=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("/?order=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "title" SortOrder.ALPHABETICAL_DESC -> "titlereverse" SortOrder.NEWEST -> "latest" @@ -69,12 +69,6 @@ internal class KomikSan(context: MangaLoaderContext) : append("&page=") append(page.toString()) } - - null -> { - append(listUrl) - append("/?order=update&page=") - append(page.toString()) - } } } return parseMangaList(webClient.httpGet(url).parseHtml()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt index f0792ad9..327287fe 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt @@ -23,27 +23,27 @@ internal class Komikcast(context: MangaLoaderContext) : override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) override val isTagsExclusionSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/page/") append(page.toString()) append("/?s=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("/page/") append(page.toString()) append("/?type=") append( - when (filter.sortOrder) { + when (order) { SortOrder.ALPHABETICAL -> "&orderby=titleasc" SortOrder.ALPHABETICAL_DESC -> "&orderby=titledesc" SortOrder.POPULARITY -> "&orderby=popular" @@ -68,13 +68,6 @@ internal class Komikcast(context: MangaLoaderContext) : } } } - - null -> { - append("/page/") - append(page.toString()) - append(listUrl) - append("/?order=update") - } } } 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 1c137a78..351a4dfa 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 @@ -39,23 +39,20 @@ abstract class MangaWorldParser( override val isMultipleTagsSupported = true - override suspend fun getListPage( - page: Int, - filter: MangaListFilter?, - ): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/archive?") - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("keyword=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { - if (filter.tags.isEmpty() && filter.states.isEmpty() && filter.sortOrder == SortOrder.UPDATED) return parseMangaList( + else -> { + if (filter.tags.isEmpty() && filter.states.isEmpty() && order == SortOrder.UPDATED) return parseMangaList( webClient.httpGet("https://$domain/?page=$page").parseHtml(), ) @@ -63,7 +60,7 @@ abstract class MangaWorldParser( filter.tags.joinTo(this, "&") { it.key.substringAfter("archive?") } } - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("&sort=most_read") SortOrder.ALPHABETICAL -> append("&sort=a-z") SortOrder.NEWEST -> append("&sort=newest") @@ -78,8 +75,6 @@ abstract class MangaWorldParser( else -> Unit } } - - null -> Unit } append("&page=$page") } @@ -103,13 +98,13 @@ abstract class MangaWorldParser( tags = tags, author = div.selectFirst(".author a")?.text(), state = - when (div.selectFirst(".status a")?.text()) { - "In corso" -> MangaState.ONGOING - "Finito" -> MangaState.FINISHED - "Droppato" -> MangaState.ABANDONED - "In pausa" -> MangaState.PAUSED - else -> null - }, + when (div.selectFirst(".status a")?.text()) { + "In corso" -> MangaState.ONGOING + "Finito" -> MangaState.FINISHED + "Droppato" -> MangaState.ABANDONED + "In pausa" -> MangaState.PAUSED + else -> null + }, source = source, isNsfw = isNsfwSource, ) @@ -142,30 +137,30 @@ abstract class MangaWorldParser( val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() return manga.copy( altTitle = - doc.selectFirst(".meta-data .font-weight-bold:contains(Titoli alternativi:)") - ?.parent() - ?.ownText() - ?.substringAfter(": ") - ?.trim(), + doc.selectFirst(".meta-data .font-weight-bold:contains(Titoli alternativi:)") + ?.parent() + ?.ownText() + ?.substringAfter(": ") + ?.trim(), description = doc.getElementById("noidungm")?.text().orEmpty(), chapters = - doc.select(".chapters-wrapper .chapter a").mapChapters(reversed = true) { i, a -> - val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) - MangaChapter( - id = generateUid(url), - name = a.selectFirstOrThrow("span.d-inline-block").text(), - number = i + 1f, - volume = 0, - url = "$url?style=list", - scanlator = null, - uploadDate = - SimpleDateFormat("dd MMMM yyyy", Locale.ITALIAN).tryParse( - a.selectFirst(".chap-date")?.text(), - ), - branch = null, - source = source, - ) - }, + doc.select(".chapters-wrapper .chapter a").mapChapters(reversed = true) { i, a -> + val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) + MangaChapter( + id = generateUid(url), + name = a.selectFirstOrThrow("span.d-inline-block").text(), + number = i + 1f, + volume = 0, + url = "$url?style=list", + scanlator = null, + uploadDate = + SimpleDateFormat("dd MMMM yyyy", Locale.ITALIAN).tryParse( + a.selectFirst(".chap-date")?.text(), + ), + branch = null, + source = source, + ) + }, ) } 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 3e6b7949..aeb253f1 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 @@ -62,15 +62,31 @@ internal abstract class MmrcmsParser( "مكتملة", ) - override val isMultipleTagsSupported = false - protected open val imgUpdated = "/cover/cover_250x350.jpg" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - when (filter) { + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + when { + !filter.query.isNullOrEmpty() -> { val url = buildString { append("https://") append(domain) @@ -85,9 +101,9 @@ internal abstract class MmrcmsParser( return parseMangaList(webClient.httpGet(url).parseHtml()) } - is MangaListFilter.Advanced -> { + else -> { - if (filter.sortOrder == SortOrder.UPDATED) { + if (order == SortOrder.UPDATED) { val url = buildString { append("https://") append(domain) @@ -109,7 +125,7 @@ internal abstract class MmrcmsParser( append(it.key) } append("&sortBy=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views&asc=false") SortOrder.POPULARITY_ASC -> append("views&asc=true") SortOrder.ALPHABETICAL -> append("name&asc=true") @@ -120,16 +136,6 @@ internal abstract class MmrcmsParser( return parseMangaList(webClient.httpGet(url).parseHtml()) } } - - null -> { - val url = buildString { - append("https://") - append(domain) - append("/latest-release?page=") - append(page.toString()) - } - return parseMangaList(webClient.httpGet(url).parseHtml()) - } } } @@ -174,7 +180,7 @@ internal abstract class MmrcmsParser( } } - override suspend fun getAvailableTags(): Set { + protected open suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/$tagUrl/").parseHtml() return doc.select("ul.list-category li").mapNotNullToSet { li -> val a = li.selectFirst("a") ?: return@mapNotNullToSet null 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 8e5951f1..ec79e346 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 @@ -45,7 +45,7 @@ internal abstract class NepnepParser( val views: String, ) - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { val doc = searchDoc.get() val json = JSONArray( doc.selectFirstOrThrow("script:containsData(MainFunction)").data() @@ -65,9 +65,8 @@ internal abstract class NepnepParser( val views = m.getString("v") //val viewMonth = m.getString("vm") - when (filter) { - - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (m.getString("s").contains(filter.query, ignoreCase = true) || (m.getJSONArray("al") .length() > 0 && m.getJSONArray("al").getString(0) .contains(filter.query, ignoreCase = true)) @@ -78,7 +77,7 @@ internal abstract class NepnepParser( } } - is MangaListFilter.Advanced -> { + else -> { val tags = filter.tags val tagsExcluded = filter.tagsExclude val tagsJson = m.getJSONArray("g").toString() @@ -111,22 +110,14 @@ internal abstract class NepnepParser( } sort = true } - - null -> { - mangaWithLastUpdateList.add( - MangaWithLastUpdate(addManga(href, imgUrl, m), lastUpdate, views), - ) - } } } if (sort) { - when (filter?.sortOrder) { + when (order) { SortOrder.POPULARITY -> mangaWithLastUpdateList.sortByDescending { it.views } SortOrder.UPDATED -> mangaWithLastUpdateList.sortByDescending { it.lastUpdate } SortOrder.ALPHABETICAL -> {} - else -> if (filter != null) { - throw IllegalArgumentException("Unsupported sort order: ${filter.sortOrder}") - } + else -> throw IllegalArgumentException("Unsupported sort order: $order") } } return mangaWithLastUpdateList.map { it.manga } @@ -186,17 +177,17 @@ internal abstract class NepnepParser( altTitle = null, state = when (doc.selectFirstOrThrow(".list-group-item:contains(Status:) a").text()) { "Ongoing (Scan)", "Ongoing (Publish)", - -> MangaState.ONGOING + -> MangaState.ONGOING "Complete (Scan)", "Complete (Publish)", - -> MangaState.FINISHED + -> MangaState.FINISHED "Cancelled (Scan)", "Cancelled (Publish)", "Discontinued (Scan)", "Discontinued (Publish)", - -> MangaState.ABANDONED + -> MangaState.ABANDONED "Hiatus (Scan)", "Hiatus (Publish)", - -> MangaState.PAUSED + -> MangaState.PAUSED else -> null }, 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 883dfe25..649b294d 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.PagedMangaParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -11,8 +11,7 @@ internal abstract class OneMangaParser( context: MangaLoaderContext, source: MangaParserSource, domain: String, - pageSize: Int = 1, -) : PagedMangaParser(context, source, pageSize) { +) : SinglePageMangaParser(context, source) { override val configKeyDomain = ConfigKey.Domain(domain) @@ -21,16 +20,29 @@ internal abstract class OneMangaParser( keys.add(userAgentKey) } - override val isMultipleTagsSupported = false + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val isSearchSupported = false + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = false, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = emptySet(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - if (page > 1) { - return emptyList() - } + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { val url = "https://$domain" val doc = webClient.httpGet(url).parseHtml() val manga = ArrayList() @@ -56,8 +68,6 @@ internal abstract class OneMangaParser( return manga } - override suspend fun getAvailableTags(): Set = emptySet() - override suspend fun getDetails(manga: Manga): Manga { val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() 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 c2b71bbd..dbdc6a75 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 @@ -47,66 +47,54 @@ internal abstract class OtakuSanctuaryParser( override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - val doc = - when (filter) { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + val doc = when { + !filter.query.isNullOrEmpty() -> { + if (page > 1) { + return emptyList() + } + val url = buildString { + append("https://") + append(domain) + append("/Home/Search?search=") + append(filter.query.urlEncoded()) + } + webClient.httpGet(url).parseHtml().requireElementById("collection-manga") + } - is MangaListFilter.Search -> { - if (page > 1) { - return emptyList() - } + else -> { + if (filter.tags.isNotEmpty()) { val url = buildString { append("https://") append(domain) - append("/Home/Search?search=") - append(filter.query.urlEncoded()) - } - webClient.httpGet(url).parseHtml().requireElementById("collection-manga") - } - - is MangaListFilter.Advanced -> { - - if (filter.tags.isNotEmpty()) { - val url = buildString { - append("https://") - append(domain) - append("/Genre/MangaGenrePartial?id=") - filter.tags.oneOrThrowIfMany()?.let { - append(it.key) - } - append("&lang=") - append(lang) - append("&offset=") - append(page * pageSize) - append("&pagesize=") - append(pageSize) + append("/Genre/MangaGenrePartial?id=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) } - webClient.httpGet(url).parseHtml() - - } else { - val payload = HashMap() - payload["Lang"] = lang - payload["Page"] = page.toString() - payload["Type"] = "Include" - when (filter.sortOrder) { - SortOrder.NEWEST -> payload["Dir"] = "CreatedDate" - SortOrder.UPDATED -> payload["Dir"] = "NewPostedDate" - else -> payload["Dir"] = "NewPostedDate" - } - webClient.httpPost("https://$domain/$listUrl", payload).parseHtml() + append("&lang=") + append(lang) + append("&offset=") + append(page * pageSize) + append("&pagesize=") + append(pageSize) } + webClient.httpGet(url).parseHtml() - } - - null -> { + } else { val payload = HashMap() payload["Lang"] = lang payload["Page"] = page.toString() payload["Type"] = "Include" - payload["Dir"] = "NewPostedDate" + when (order) { + SortOrder.NEWEST -> payload["Dir"] = "CreatedDate" + SortOrder.UPDATED -> payload["Dir"] = "NewPostedDate" + else -> payload["Dir"] = "NewPostedDate" + } webClient.httpPost("https://$domain/$listUrl", payload).parseHtml() } + } + } return doc.select("div.picture-card").map { div -> 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 241a5262..08161d4e 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 @@ -5,6 +5,7 @@ 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.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -18,8 +19,7 @@ internal abstract class PizzaReaderParser( context: MangaLoaderContext, source: MangaParserSource, domain: String, - pageSize: Int = 20, -) : PagedMangaParser(context, source, pageSize) { +) : SinglePageMangaParser(context, source) { override val configKeyDomain = ConfigKey.Domain(domain) @@ -71,10 +71,7 @@ internal abstract class PizzaReaderParser( protected open val hiatusFilter = "in pausa" protected open val abandonedFilter = "droppato" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - if (page > 1) { - return emptyList() - } + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { var foundTag = true var foundTagExclude = true var foundState = true @@ -82,8 +79,8 @@ internal abstract class PizzaReaderParser( val manga = ArrayList() - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { val jsonManga = webClient.httpGet("https://$domain/api/search/${filter.query.urlEncoded()}").parseJson() .getJSONArray("comics") for (i in 0 until jsonManga.length()) { @@ -93,7 +90,7 @@ internal abstract class PizzaReaderParser( } } - is MangaListFilter.Advanced -> { + else -> { val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics") for (i in 0 until jsonManga.length()) { @@ -164,17 +161,6 @@ internal abstract class PizzaReaderParser( } } } - - null -> { - val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics") - for (i in 0 until jsonManga.length()) { - val j = jsonManga.getJSONObject(i) - val href = "/api" + j.getString("url") - manga.add( - addManga(href, j), - ) - } - } } return manga 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 d39dc3d9..4208d2f0 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 @@ -27,13 +27,13 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append('/') - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { append("/page/$page/") } @@ -41,7 +41,7 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append("category/") @@ -51,7 +51,7 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, } } } else { - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("/") SortOrder.UPDATED -> append("manga/") else -> append("manga/") @@ -62,37 +62,26 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, } } - - null -> { - append("manga/") - if (page > 1) { - append("page/$page/") - } - } } } val doc = webClient.httpGet(url).parseHtml() - val item = - when (filter) { + val item = when { - is MangaListFilter.Search -> { - doc.select("div.listagem div.item") - } + !filter.query.isNullOrEmpty() -> { + doc.select("div.listagem div.item") + } - is MangaListFilter.Advanced -> { - if (filter.sortOrder == SortOrder.POPULARITY && filter.tags.isEmpty()) { - doc.select("div.listagem")[1].select("div.item") // To remove the 6 mangas updated on the home page - } else { - doc.select("div.listagem div.item") - } + else -> { + if (order == SortOrder.POPULARITY && filter.tags.isEmpty()) { + doc.select("div.listagem")[1].select("div.item") // To remove the 6 mangas updated on the home page + } else { + doc.select("div.listagem div.item") } - - null -> doc.select("div.listagem div.item") - } + } return item.map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( 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 460179e7..33bd9157 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 @@ -33,8 +33,7 @@ class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar override val isMultipleTagsSupported = false override val isSearchSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) @@ -45,13 +44,13 @@ class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar append(page.toString()) } - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append("/genero/") @@ -61,7 +60,7 @@ class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar append("/?orderby=") append( - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> "modified&order=desc" SortOrder.UPDATED_ASC -> "modified&order=asc" SortOrder.POPULARITY -> "views&order=desc" @@ -74,8 +73,6 @@ class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar }, ) } - - null -> append("/?orderby=modified&order=desc") } } val doc = webClient.httpGet(url).parseHtml() 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 4abefa32..61f84e52 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 @@ -26,14 +26,13 @@ class LerMangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Ma override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append('/') - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { append("page/") append(page.toString()) @@ -43,7 +42,7 @@ class LerMangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Ma append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { filter.tags.oneOrThrowIfMany()?.let { append(it.key) append('/') @@ -55,14 +54,6 @@ class LerMangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Ma append('/') } } - - null -> { - if (page > 1) { - append("page/") - append(page.toString()) - append('/') - } - } } } 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 fadcbe0f..4359c671 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 @@ -15,7 +15,8 @@ import java.util.zip.ZipInputStream @Broken // Not dead but totally changed structure @MangaSourceParser("RANDOMSCANS", "LuratoonScan", "pt") -internal class LuratoonScansParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.RANDOMSCANS), +internal class LuratoonScansParser(context: MangaLoaderContext) : + SinglePageMangaParser(context, MangaParserSource.RANDOMSCANS), Interceptor { override val availableSortOrders = setOf(SortOrder.ALPHABETICAL) @@ -28,13 +29,10 @@ internal class LuratoonScansParser(context: MangaLoaderContext) : MangaParser(co override val isTagsExclusionSupported = false override val isMultipleTagsSupported = false - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { - if (offset > 0) { - return emptyList() - } - require(filter !is MangaListFilter.Search) { ErrorMessages.SEARCH_NOT_SUPPORTED } + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { + require(filter.query.isNullOrEmpty()) { ErrorMessages.SEARCH_NOT_SUPPORTED } val url = urlBuilder() - val tag = (filter as? MangaListFilter.Advanced)?.tags?.oneOrThrowIfMany() + val tag = filter.tags.oneOrThrowIfMany() if (tag == null) { url.addPathSegment("todas-as-obras") } else { 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 0b69c8eb..5a47081b 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 @@ -23,20 +23,19 @@ class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Manga override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/search/") append(filter.query.urlEncoded()) append('/') } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append("/genero/") @@ -48,8 +47,6 @@ class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Manga } } - null -> append("/manga/") - } if (page > 1) { append("page/") 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 7985c9b4..1acfdcb9 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 @@ -22,20 +22,19 @@ class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(context, Manga override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { if (page > 1) return emptyList() append("/buscar-manga/?q=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("/mangas") filter.tags.oneOrThrowIfMany()?.let { @@ -47,12 +46,6 @@ class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(context, Manga append(page.toString()) append('/') } - - null -> { - append("/mangas/") - append(page.toString()) - append('/') - } } } val doc = webClient.httpGet(url).parseHtml() 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 3dd0902e..a968a3c7 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.PagedMangaParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -11,19 +11,38 @@ import java.util.* @Broken @MangaSourceParser("ONEPIECEEX", "OnePieceEx", "pt") -class OnePieceEx(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.ONEPIECEEX, 1) { +class OnePieceEx(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.ONEPIECEEX) { override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) override val configKeyDomain = ConfigKey.Domain("onepieceex.net") + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = false, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = emptySet(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - if (page > 1) return emptyList() + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { return listOf( Manga( id = generateUid("https://$domain/mangas/leitor/"), @@ -67,8 +86,6 @@ class OnePieceEx(context: MangaLoaderContext) : PagedMangaParser(context, MangaP ) } - override suspend fun getAvailableTags(): Set = emptySet() - override suspend fun getDetails(manga: Manga): Manga { if (manga.url.endsWith("/leitor/")) { val chap = 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 9c7f3bfa..a1919abb 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.PagedMangaParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -13,65 +13,70 @@ import java.text.SimpleDateFormat import java.util.* @MangaSourceParser("YUGENMANGAS", "YugenApp", "pt") -class YugenMangas(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.YUGENMANGAS, 28) { +class YugenMangas(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.YUGENMANGAS) { override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("yugenmangasbr.voblog.xyz") + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = emptySet(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { + val json = when { - if (page > 1) { - return emptyList() - } + !filter.query.isNullOrEmpty() -> { - val json = - when (filter) { + val url = buildString { + append("https://api.") + append(domain) + append("/api/series/?search=") + append(filter.query.urlEncoded()) + } + webClient.httpGet(url).parseJsonArray() + } - is MangaListFilter.Search -> { + else -> { + if (order == SortOrder.UPDATED) { val url = buildString { append("https://api.") append(domain) - append("/api/series/?search=") - append(filter.query.urlEncoded()) + append("/api/latest_updates/") } webClient.httpGet(url).parseJsonArray() - } - - is MangaListFilter.Advanced -> { - - if (filter.sortOrder == SortOrder.UPDATED) { - val url = buildString { - append("https://api.") - append(domain) - append("/api/latest_updates/") - } - webClient.httpGet(url).parseJsonArray() - } else { - val url = buildString { - append("https://api.") - append(domain) - append("/api/series_novels/all_series/") - } - webClient.httpGet(url).parseJson().getJSONArray("series") - } - - } - - null -> { + } else { val url = buildString { append("https://api.") append(domain) - append("/api/latest_updates/") + append("/api/series_novels/all_series/") } - webClient.httpGet(url).parseJsonArray() + webClient.httpGet(url).parseJson().getJSONArray("series") } + } + } return json.mapJSON { j -> val slug = j.getString("slug") @@ -176,6 +181,4 @@ class YugenMangas(context: MangaLoaderContext) : PagedMangaParser(context, Manga } return pages } - - override suspend fun getAvailableTags(): Set = emptySet() } 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 28a51de2..8765725c 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 @@ -16,25 +16,50 @@ import java.util.* internal class AComics(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.ACOMICS, pageSize = 10) { - override val availableSortOrders: Set = - EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL, SortOrder.POPULARITY) - - override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.ALPHABETICAL, + SortOrder.POPULARITY, + ) override val configKeyDomain = ConfigKey.Domain("acomics.ru") + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + init { paginator.firstPage = 0 searchPaginator.firstPage = 0 context.cookieJar.insertCookies(domain, "ageRestrict=18") } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = getOrCreateTagMap().values.toSet(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage( + page: Int, + order: SortOrder, + filter: MangaListFilterV2, + ): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (page > 0) { return emptyList() } @@ -42,12 +67,12 @@ internal class AComics(context: MangaLoaderContext) : append(filter.query) } - is MangaListFilter.Advanced -> { + else -> { append("/comics?ratings[]=1&ratings[]=2&ratings[]=3&ratings[]=4&ratings[]=5&ratings[]=6&skip=") - append((page * 10).toString()) + append(page * 10) append("&sort=") append( - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> "last_update" SortOrder.ALPHABETICAL -> "serial_name" SortOrder.POPULARITY -> "subscr_count" @@ -73,11 +98,6 @@ internal class AComics(context: MangaLoaderContext) : ) } } - - null -> { - append("/comics?ratings[]=1&ratings[]=2&ratings[]=3&ratings[]=4&ratings[]=5&ratings[]=6&sort=last_update&skip=") - append((page * 20).toString()) - } } } @@ -108,10 +128,6 @@ internal class AComics(context: MangaLoaderContext) : private var tagCache: ArrayMap? = null private val mutex = Mutex() - override suspend fun getAvailableTags(): Set { - return getOrCreateTagMap().values.toSet() - } - private suspend fun getOrCreateTagMap(): Map = mutex.withLock { tagCache?.let { return@withLock it } val tagMap = ArrayMap() 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 92133213..22c1b541 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 @@ -31,14 +31,8 @@ internal class DesuMeParser(context: MangaLoaderContext) : PagedMangaParser(cont private val tagsCache = SuspendLazy(::fetchTags) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, - ): List { - if (query != null && page != searchPaginator.firstPage) { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + if (!filter.query.isNullOrEmpty() && page != searchPaginator.firstPage) { return emptyList() } val domain = domain @@ -46,16 +40,16 @@ internal class DesuMeParser(context: MangaLoaderContext) : PagedMangaParser(cont append("https://") append(domain) append("/manga/api/?limit=20&order=") - append(getSortKey(sortOrder)) + append(getSortKey(order)) append("&page=") append(page) - if (!tags.isNullOrEmpty()) { + if (filter.tags.isNotEmpty()) { append("&genres=") - appendAll(tags, ",") { it.key } + appendAll(filter.tags, ",") { it.key } } - if (query != null) { + if (!filter.query.isNullOrEmpty()) { append("&search=") - append(query) + append(filter.query) } } val json = webClient.httpGet(url).parseJson().getJSONArray("response") @@ -104,7 +98,6 @@ internal class DesuMeParser(context: MangaLoaderContext) : PagedMangaParser(cont ?: throw ParseException("Invalid response", url) val baseChapterUrl = manga.url + "/chapter/" val chaptersList = json.getJSONObject("chapters").getJSONArray("list") - val totalChapters = chaptersList.length() return manga.copy( tags = json.getJSONArray("genres").mapJSONToSet { MangaTag( 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 83ffb7b0..d068ea23 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 @@ -47,10 +47,7 @@ class MangaWtfParser( searchPaginator.firstPage = 0 } - override suspend fun getListPage( - page: Int, - filter: MangaListFilter?, - ): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = urlBuilder("api") .addPathSegment("v2") @@ -58,16 +55,16 @@ class MangaWtfParser( .addQueryParameter("page", page.toString()) .addQueryParameter("size", pageSize.toString()) .addQueryParameter("type", "COMIC") - when (filter) { - is MangaListFilter.Advanced -> { + when { + filter.query.isNullOrEmpty() -> { url.addQueryParameter( "sort", - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> "updatedAt,desc" SortOrder.POPULARITY -> "viewsCount,desc" SortOrder.RATING -> "likesCount,desc" SortOrder.NEWEST -> "createdAt,desc" - else -> throw IllegalArgumentException("Unsupported ${filter.sortOrder}") + else -> throw IllegalArgumentException("Unsupported ${order}") }, ) if (filter.tags.isNotEmpty()) { @@ -104,11 +101,9 @@ class MangaWtfParser( } } - is MangaListFilter.Search -> { + else -> { url.addQueryParameter("search", filter.query) } - - null -> Unit } val ja = webClient.httpGet(url.build()).parseJsonArray() return ja.mapJSON { jo -> jo.toManga() } @@ -135,13 +130,13 @@ class MangaWtfParser( tags = jo.getJSONArray("labels").mapJSONToSet { it.toMangaTag() }, state = jo.getStringOrNull("status")?.toMangaState(), author = - jo.getJSONArray("relations").toJSONList().firstNotNullOfOrNull { - if (it.getStringOrNull("type") == "AUTHOR") { - it.getJSONObject("publisher").getStringOrNull("name") - } else { - null - } - }, + jo.getJSONArray("relations").toJSONList().firstNotNullOfOrNull { + if (it.getStringOrNull("type") == "AUTHOR") { + it.getJSONObject("publisher").getStringOrNull("name") + } else { + null + } + }, source = source, largeCoverUrl = null, description = jo.getString("description").nl2br(), @@ -209,10 +204,10 @@ 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 ad2c4cf3..79f93a0c 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 @@ -45,43 +45,38 @@ internal class NudeMoonParser( ) } - override suspend fun getList( - offset: Int, - filter: MangaListFilter?, - ): List { + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { val domain = domain - val url = - when (filter) { - is MangaListFilter.Search -> { - if (!isAuthorized) { - throw AuthRequiredException(source) - } - "https://$domain/search?stext=${filter.query.urlEncoded()}&rowstart=$offset" + val url = when { + !filter.query.isNullOrEmpty() -> { + if (!isAuthorized) { + throw AuthRequiredException(source) } + "https://$domain/search?stext=${filter.query.urlEncoded()}&rowstart=$offset" + } - is MangaListFilter.Advanced -> { - if (filter.tags.isNotEmpty()) { - filter.tags.joinToString( - separator = "_", - prefix = "https://$domain/tags/", - postfix = "&rowstart=$offset", - transform = { it.key.urlEncoded() }, - ) - } else { - val order = when (filter.sortOrder) { - SortOrder.POPULARITY -> "views" - SortOrder.NEWEST -> "date" - SortOrder.RATING -> "like" - else -> "like" - } - "https://$domain/all_manga?$order&rowstart=$offset" + else -> { + if (filter.tags.isNotEmpty()) { + filter.tags.joinToString( + separator = "_", + prefix = "https://$domain/tags/", + postfix = "&rowstart=$offset", + transform = { it.key.urlEncoded() }, + ) + } else { + val order = when (order) { + SortOrder.POPULARITY -> "views" + SortOrder.NEWEST -> "date" + SortOrder.RATING -> "like" + else -> "like" } + "https://$domain/all_manga?$order&rowstart=$offset" } - - null -> "https://$domain/all_manga?views&rowstart=$offset" } + } + val doc = webClient.httpGet(url).parseHtml() return doc.body().select("table.news_pic2").mapNotNull { row -> val a = row.selectFirstOrThrow("a") 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 811067c0..f565a98c 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 @@ -69,25 +69,19 @@ internal class RemangaParser( return response } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { copyCookies() val domain = domain val urlBuilder = StringBuilder() .append("https://api.") .append(domain) - if (query != null) { + if (!filter.query.isNullOrEmpty()) { urlBuilder.append("/api/search/?query=") - .append(query.urlEncoded()) + .append(filter.query.urlEncoded()) } else { urlBuilder.append("/api/search/catalog/?ordering=") - .append(getSortKey(sortOrder)) - tags?.forEach { tag -> + .append(getSortKey(order)) + filter.tags.forEach { tag -> urlBuilder.append("&genres=") urlBuilder.append(tag.key) } 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 1266aedb..a7b6e674 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 @@ -66,10 +66,30 @@ internal abstract class GroupleParser( override val isAuthorized: Boolean get() = context.cookieJar.getCookies(domain).any { it.name == "gwt" } - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = true, + isYearSupported = false, + isYearRangeSupported = true, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { val domain = domain - val doc = when (filter) { - is MangaListFilter.Search -> webClient.httpPost( + val doc = when { + !filter.query.isNullOrEmpty() && filter.tags.isEmpty() -> webClient.httpPost( "https://$domain/search", mapOf( "q" to filter.query.urlEncoded(), @@ -78,27 +98,21 @@ internal abstract class GroupleParser( ), ) - null -> webClient.httpGet( - "https://$domain/list?sortType=${ - getSortKey(defaultSortOrder) - }&offset=${offset upBy PAGE_SIZE}", - ) - - is MangaListFilter.Advanced -> when { + else -> when { filter.tags.isEmpty() -> webClient.httpGet( "https://$domain/list?sortType=${ - getSortKey(filter.sortOrder) + getSortKey(order) }&offset=${offset upBy PAGE_SIZE}", ) filter.tags.size == 1 -> webClient.httpGet( "https://$domain/list/genre/${filter.tags.first().key}?sortType=${ - getSortKey(filter.sortOrder) + getSortKey(order) }&offset=${offset upBy PAGE_SIZE}", ) offset > 0 -> return emptyList() - else -> advancedSearch(domain, filter.tags) + else -> advancedSearch(domain, filter) } }.parseHtml().body() val root = (doc.getElementById("mangaBox") ?: doc.getElementById("mangaResults")) @@ -255,7 +269,7 @@ internal abstract class GroupleParser( } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://${domain}/list/genres/sort_name").parseHtml() val root = doc.body().getElementById("mangaBox")?.selectFirst("div.leftContent")?.selectFirst("table.table") ?: doc.parseFailed("Cannot find root") @@ -323,14 +337,14 @@ internal abstract class GroupleParser( else -> null } - private suspend fun advancedSearch(domain: String, tags: Set): Response { + private suspend fun advancedSearch(domain: String, filter: MangaListFilterV2): Response { val url = "https://$domain/search/advanced" // Step 1: map catalog genres names to advanced-search genres ids val tagsIndex = webClient.httpGet(url).parseHtml().body().selectFirst("form.search-form")?.select("div.form-group") ?.find { it.selectFirst("li.property") != null } ?: throw ParseException("Genres filter element not found", url) - val tagNames = tags.map { it.title.lowercase() } + val tagNames = filter.tags.map { it.title.lowercase() } val payload = HashMap() var foundGenres = 0 tagsIndex.select("li.property").forEach { li -> @@ -341,11 +355,11 @@ internal abstract class GroupleParser( "in" } else "" } - if (foundGenres != tags.size) { + if (foundGenres != filter.tags.size) { tagsIndex.parseFailed("Some genres are not found") } // Step 2: advanced search - payload["q"] = "" + payload["q"] = filter.query.orEmpty() payload["s_high_rate"] = "" payload["s_single"] = "" payload["s_mature"] = "" @@ -354,7 +368,11 @@ internal abstract class GroupleParser( payload["s_many_chapters"] = "" payload["s_wait_upload"] = "" payload["s_sale"] = "" - payload["years"] = "1900,2099" + payload["years"] = buildString { + append(filter.yearFrom.ifZero { 1900 }) + append(',') + append(filter.yearTo.ifZero { 2099 }) + } payload["+"] = "Искать".urlEncoded() return webClient.httpPost(url, payload) } 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 a43f51f4..1c36f1ca 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 @@ -31,9 +31,9 @@ internal abstract class ChanParser( override val isAuthorized: Boolean get() = context.cookieJar.getCookies(domain).any { it.name == "dle_user_id" } - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { val domain = domain - val doc = webClient.httpGet(buildUrl(offset, filter)).parseHtml() + val doc = webClient.httpGet(buildUrl(offset, order, filter)).parseHtml() val root = doc.body().selectFirst("div.main_fon")?.getElementById("content") ?: doc.parseFailed("Cannot find root") return root.select("div.content_row").mapNotNull { row -> @@ -178,12 +178,13 @@ internal abstract class ChanParser( protected open fun buildUrl( offset: Int, - filter: MangaListFilter?, + order: SortOrder, + filter: MangaListFilterV2, ): HttpUrl { val builder = urlBuilder() builder.addQueryParameter("offset", offset.toString()) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { builder.addQueryParameter("do", "search") builder.addQueryParameter("subaction", "search") builder.addQueryParameter("search_start", ((offset / 40) + 1).toString()) @@ -194,7 +195,7 @@ internal abstract class ChanParser( builder.addQueryParameter("need_sort_date", "false") } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty()) { builder.addPathSegment("tags") val joiner = StringUtil.StringJoiner("+") @@ -203,17 +204,17 @@ internal abstract class ChanParser( builder.addPathSegment(joiner.complete()) builder.addQueryParameter( "n", - when (filter.sortOrder) { + when (order) { SortOrder.RATING, SortOrder.POPULARITY, - -> "favdesc" + -> "favdesc" SortOrder.ALPHABETICAL -> "abcasc" else -> "" // SortOrder.NEWEST }, ) } else { - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> builder.addPathSegment("mostviews") SortOrder.ALPHABETICAL -> builder.addPathSegment("catalog") SortOrder.RATING -> builder.addPathSegment("mostfavorites") @@ -224,11 +225,6 @@ internal abstract class ChanParser( } } } - - null -> { - builder.addPathSegment("manga") - builder.addPathSegment("new") - } } return builder.build() } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/HenChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/HenChanParser.kt index 9c9b05eb..7d41370f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/HenChanParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/HenChanParser.kt @@ -59,10 +59,10 @@ internal class HenChanParser(context: MangaLoaderContext) : ChanParser(context, ) } - override fun buildUrl(offset: Int, filter: MangaListFilter?): HttpUrl = when { - filter is MangaListFilter.Advanced && filter.tags.isEmpty() && filter.tagsExclude.isEmpty() -> { + override fun buildUrl(offset: Int, order: SortOrder, filter: MangaListFilterV2): HttpUrl = when { + filter.query.isNullOrEmpty() && filter.tags.isEmpty() && filter.tagsExclude.isEmpty() -> { val builder = urlBuilder().addQueryParameter("offset", offset.toString()) - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> { builder.addPathSegment("mostviews") builder.addQueryParameter("sort", "manga") @@ -81,15 +81,8 @@ internal class HenChanParser(context: MangaLoaderContext) : ChanParser(context, builder.build() } - filter == null -> { - val builder = urlBuilder().addQueryParameter("offset", offset.toString()) - builder.addPathSegment("manga") - builder.addPathSegment("newest") - builder.build() - } - else -> { - super.buildUrl(offset, filter) + super.buildUrl(offset, order, filter) } } } 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 cddb9d13..3c7ce120 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 @@ -31,10 +31,26 @@ internal abstract class LibSocialParser( ) final override val configKeyDomain = ConfigKey.Domain("lib.social") - override val availableStates: Set = EnumSet.allOf(MangaState::class.java) - override val isMultipleTagsSupported = true - override val isTagsExclusionSupported = true - override val isSearchSupported = true + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = true, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) private val statesMap = intObjectMapOf( 1, MangaState.ONGOING, @@ -57,7 +73,7 @@ internal abstract class LibSocialParser( defaultValue = null, ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val urlBuilder = urlBuilder("api") .addPathSegment("api") .addPathSegment("manga") @@ -65,49 +81,41 @@ internal abstract class LibSocialParser( .addQueryParameter("fields[]", "rate") .addQueryParameter("fields[]", "rate_avg") .addQueryParameter("page", page.toString()) - when (filter) { - is MangaListFilter.Advanced -> { - for (state in filter.states) { - urlBuilder.addQueryParameter("status[]", statesMap.keyOf(state).toString()) - } - for (tag in filter.tags) { - urlBuilder.addQueryParameter("${tag.typeKey()}[]", tag.key.drop(1)) - } - for (tag in filter.tagsExclude) { - urlBuilder.addQueryParameter("${tag.typeKey()}_exclude[]", tag.key.drop(1)) - } - } - - is MangaListFilter.Search -> { - urlBuilder.addQueryParameter("q", filter.query) - } - - null -> Unit + for (state in filter.states) { + urlBuilder.addQueryParameter("status[]", statesMap.keyOf(state).toString()) + } + for (tag in filter.tags) { + urlBuilder.addQueryParameter("${tag.typeKey()}[]", tag.key.drop(1)) + } + for (tag in filter.tagsExclude) { + urlBuilder.addQueryParameter("${tag.typeKey()}_exclude[]", tag.key.drop(1)) + } + if (!filter.query.isNullOrEmpty()) { + urlBuilder.addQueryParameter("q", filter.query) } - val sortOrder = filter?.sortOrder ?: defaultSortOrder urlBuilder.addQueryParameter( "sort_by", - when (sortOrder) { + when (order) { SortOrder.UPDATED -> "last_chapter_at" SortOrder.POPULARITY -> "views" SortOrder.RATING -> "rate_avg" SortOrder.NEWEST -> "created_at" SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC, - -> "rus_name" + -> "rus_name" else -> null }, ) urlBuilder.addQueryParameter( "sort_type", - when (sortOrder) { + when (order) { SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING, SortOrder.NEWEST, SortOrder.ALPHABETICAL_DESC, - -> "desc" + -> "desc" SortOrder.ALPHABETICAL -> "asc" else -> null @@ -166,7 +174,7 @@ internal abstract class LibSocialParser( } } - override suspend fun getAvailableTags(): Set = coroutineScope { + private suspend fun fetchAvailableTags(): Set = coroutineScope { val tags = async { fetchTags("tags") } val genres = async { fetchTags("genres") } tagsSetOf(tags.await(), genres.await()) 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 5f077daa..83e7fd2c 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 @@ -19,8 +19,29 @@ internal abstract class ScanParser( domain: String, pageSize: Int = 0, ) : PagedMangaParser(context, source, pageSize) { + override val configKeyDomain = ConfigKey.Domain(domain) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = getOrCreateTagMap().values.toSet(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) @@ -31,26 +52,25 @@ internal abstract class ScanParser( protected open val listUrl = "/manga" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { var query = false val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/search?q=") append(filter.query.urlEncoded()) query = true } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) append("?q=") append( - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> "u" SortOrder.ALPHABETICAL -> "a" SortOrder.POPULARITY -> "p" @@ -67,12 +87,6 @@ internal abstract class ScanParser( append("&page=") append(page.toString()) } - - null -> { - append(listUrl) - append("?page=") - append(page.toString()) - } } } @@ -110,10 +124,6 @@ internal abstract class ScanParser( private var tagCache: ArrayMap? = null private val mutex = Mutex() - override suspend fun getAvailableTags(): Set { - return getOrCreateTagMap().values.toSet() - } - protected suspend fun getOrCreateTagMap(): Map = mutex.withLock { tagCache?.let { return@withLock it } val tagMap = ArrayMap() 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 520ecc8d..526e1eeb 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 @@ -50,14 +50,13 @@ internal abstract class SinmhParser( "已完结", ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append('/') - when (filter) { - - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append(searchUrl) append("?keywords=") append(filter.query.urlEncoded()) @@ -65,7 +64,7 @@ internal abstract class SinmhParser( append(page) } - is MangaListFilter.Advanced -> { + else -> { append(listUrl) filter.tags.oneOrThrowIfMany()?.let { append(it.key) @@ -85,7 +84,7 @@ internal abstract class SinmhParser( append('/') } - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("click/") SortOrder.UPDATED -> append("update/") else -> append("/") @@ -93,13 +92,6 @@ internal abstract class SinmhParser( append(page.toString()) append('/') } - - null -> { - append(listUrl) - append("update/") - append(page.toString()) - append('/') - } } } 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 5849423b..9f88c355 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 @@ -27,10 +27,9 @@ class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, MangaPars override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - when (filter) { - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { return emptyList() } @@ -42,7 +41,7 @@ class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, MangaPars ) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { @@ -70,19 +69,6 @@ class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, MangaPars } } - - null -> { - val url = buildString { - append("https://") - append(domain) - append("/seriler") - if (page > 1) { - append("/") - append(page) - } - } - return parseMangaList(webClient.httpGet(url).parseHtml()) - } } return emptyList() 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 8db23d58..b85f6390 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 @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.tr import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -10,7 +11,7 @@ import java.text.SimpleDateFormat import java.util.* @MangaSourceParser("SADSCANS", "SadScans", "tr") -internal class SadScans(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.SADSCANS) { +internal class SadScans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.SADSCANS) { override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("sadscans.com") @@ -20,23 +21,14 @@ internal class SadScans(context: MangaLoaderContext) : MangaParser(context, Mang keys.add(userAgentKey) } - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { - if (offset > 0) { - return emptyList() - } - + override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append("/series") - when (filter) { - is MangaListFilter.Search -> { - append("?search=") - append(filter.query.urlEncoded()) - } - - is MangaListFilter.Advanced -> {} - null -> {} + if (!filter.query.isNullOrEmpty()) { + append("?search=") + append(filter.query.urlEncoded()) } } 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 1f2f8b55..0e1768b7 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 @@ -35,9 +35,9 @@ class TrWebtoon(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - when (filter) { - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + when { + !filter.query.isNullOrEmpty() -> { val url = buildString { append("https://") append(domain) @@ -50,9 +50,9 @@ class TrWebtoon(context: MangaLoaderContext) : return parseMangaList(webClient.httpGet(url).parseHtml()) } - is MangaListFilter.Advanced -> { + else -> { - if (filter.sortOrder == SortOrder.UPDATED) { + if (order == SortOrder.UPDATED) { if (filter.tags.isNotEmpty()) { throw IllegalArgumentException("Sort order updated + Tags or States is not supported by this source") } @@ -84,7 +84,7 @@ class TrWebtoon(context: MangaLoaderContext) : ) } append("&sort=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("views&short_type=DESC") SortOrder.POPULARITY_ASC -> append("views&short_type=ASC") SortOrder.ALPHABETICAL -> append("name&short_type=ASC") @@ -97,16 +97,6 @@ class TrWebtoon(context: MangaLoaderContext) : } } - - null -> { - val url = buildString { - append("https://") - append(domain) - append("/son-eklenenler?page=") - append(page.toString()) - } - return parseMangaListUpdated(webClient.httpGet(url).parseHtml()) - } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt index 1c7226a7..b4947951 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt @@ -21,14 +21,32 @@ class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar keys.add(userAgentKey) } - override val isMultipleTagsSupported = false + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) { append("/page/") append(page.toString()) @@ -37,7 +55,7 @@ class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append("/dizi-kategori/") @@ -58,15 +76,6 @@ class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar } } } - - null -> { - append("/tum-seriler/") - if (page > 1) { - append("page/") - append(page.toString()) - append('/') - } - } } } val doc = webClient.httpGet(url).parseHtml() @@ -91,7 +100,7 @@ class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain").parseHtml() return doc.select(".tags .cat-item a").mapNotNullToSet { a -> MangaTag( 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 e45349a3..9dc81987 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 @@ -73,38 +73,27 @@ class HentaiUkrParser(context: MangaLoaderContext) : MangaParser(context, MangaP ) } - override suspend fun getList( - offset: Int, - filter: MangaListFilter?, - ): List { + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { // Get all manga val json = allManga.get().toMutableList() - when (filter) { - is MangaListFilter.Search -> { - json.retainAll { item -> - item.getString("name").contains(filter.query, ignoreCase = true) || - item.getStringOrNull("eng_name")?.contains(filter.query, ignoreCase = true) == true || - item.getStringOrNull("orig_name")?.contains(filter.query, ignoreCase = true) == true || - item.getStringOrNull("author")?.contains(filter.query, ignoreCase = true) == true || - item.getStringOrNull("team")?.contains(filter.query, ignoreCase = true) == true - } + if (!filter.query.isNullOrEmpty()) { + json.retainAll { item -> + item.getString("name").contains(filter.query, ignoreCase = true) || + item.getStringOrNull("eng_name")?.contains(filter.query, ignoreCase = true) == true || + item.getStringOrNull("orig_name")?.contains(filter.query, ignoreCase = true) == true || + item.getStringOrNull("author")?.contains(filter.query, ignoreCase = true) == true || + item.getStringOrNull("team")?.contains(filter.query, ignoreCase = true) == true } - - is MangaListFilter.Advanced -> { - if (filter.tags.isNotEmpty()) { - val ids = filter.tags.mapToSet { it.key } - json.retainAll { item -> - item.getJSONArray("tags") - .mapJSON { it.getAsString() } - .any { x -> x in ids } - } - } + } + if (filter.tags.isNotEmpty()) { + val ids = filter.tags.mapToSet { it.key } + json.retainAll { item -> + item.getJSONArray("tags") + .mapJSON { it.getAsString() } + .any { x -> x in ids } } - - null -> {} } - // Return to app return json.drop(offset).take(PAGE_SIZE).map { jo -> val id = jo.getAsLong() 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 e712960a..63b4eba5 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 @@ -86,30 +86,24 @@ class HoneyMangaParser(context: MangaLoaderContext) : ) } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val body = JSONObject() body.put("page", page) body.put("pageSize", PAGE_SIZE) val sort = JSONObject() - sort.put("sortBy", getSortKey(sortOrder)) + sort.put("sortBy", getSortKey(order)) sort.put("sortOrder", "DESC") body.put("sort", sort) val content = when { - !tags.isNullOrEmpty() -> { + filter.tags.isNotEmpty() -> { // Tags val filters = JSONArray() val tagFilter = JSONObject() tagFilter.put("filterBy", "genres") tagFilter.put("filterOperator", "ALL") val tag = JSONArray() - tags.forEach { + filter.tags.forEach { tag.put(it.title) } tagFilter.put("filterValue", tag) @@ -119,15 +113,15 @@ class HoneyMangaParser(context: MangaLoaderContext) : } - !query.isNullOrEmpty() -> { + !filter.query.isNullOrEmpty() -> { // Search when { - query.length < 3 -> throw IllegalArgumentException( + filter.query.length < 3 -> throw IllegalArgumentException( "The query must contain at least 3 characters (Запит має містити щонайменше 3 символи)", ) page == searchPaginator.firstPage -> webClient - .httpGet(searchApi + query.urlEncoded()) + .httpGet(searchApi + filter.query.urlEncoded()) .parseJsonArray() else -> JSONArray() 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 9cea5b6d..99763080 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 @@ -35,21 +35,15 @@ class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser( Regex("site_login_hash\\s*=\\s*\'([^\']+)\'", RegexOption.IGNORE_CASE) } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = when { - !query.isNullOrEmpty() -> ("/index.php?do=search&subaction=search&search_start=$page&full_search=1&story=$query&titleonly=3").toAbsoluteUrl( + !filter.query.isNullOrEmpty() -> ("/index.php?do=search&subaction=search&search_start=$page&full_search=1&story=${filter.query}&titleonly=3").toAbsoluteUrl( domain, ) - tags.isNullOrEmpty() -> "/mangas/page/$page".toAbsoluteUrl(domain) - tags.size == 1 -> "${tags.first().key}/page/$page" - tags.size > 1 -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED) + filter.tags.isNullOrEmpty() -> "/mangas/page/$page".toAbsoluteUrl(domain) + filter.tags.size == 1 -> "${filter.tags.first().key}/page/$page" + filter.tags.size > 1 -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED) else -> "/mangas/page/$page".toAbsoluteUrl(domain) } val doc = webClient.httpGet(url).parseHtml() 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 8d1350a9..4f2176c2 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 @@ -36,19 +36,16 @@ class BlogTruyenParser(context: MangaLoaderContext) : private val dateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US) private var cacheTags = SuspendLazy(::fetchTags) - - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - return when (filter) { - - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + return when { + !filter.query.isNullOrEmpty() -> { val searchUrl = "https://${domain}/timkiem/nangcao/1/0/-1/-1?txt=${filter.query.urlEncoded()}&p=$page" val searchContent = webClient.httpGet(searchUrl).parseHtml() .selectFirst("section.list-manga-bycate > div.list") parseMangaList(searchContent) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany().let { @@ -61,8 +58,6 @@ class BlogTruyenParser(context: MangaLoaderContext) : getNormalList(page) } } - - null -> getNormalList(page) } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVNParser.kt index 061340a7..c302adec 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVNParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVNParser.kt @@ -36,19 +36,16 @@ class BlogTruyenVNParser(context: MangaLoaderContext) : private val dateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US) private var cacheTags = SuspendLazy(::fetchTags) - - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - return when (filter) { - - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + return when { + !filter.query.isNullOrEmpty() -> { val searchUrl = "https://${domain}/timkiem/nangcao/1/0/-1/-1?txt=${filter.query.urlEncoded()}&p=$page" val searchContent = webClient.httpGet(searchUrl).parseHtml() .selectFirst("section.list-manga-bycate > div.list") parseMangaList(searchContent) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany().let { @@ -61,8 +58,6 @@ class BlogTruyenVNParser(context: MangaLoaderContext) : getNormalList(page) } } - - null -> getNormalList(page) } } 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 e302709a..833a55d9 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 @@ -40,12 +40,12 @@ internal class CuuTruyenParser(context: MangaLoaderContext) : override suspend fun getAvailableTags(): Set = emptySet() - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { append("/api/v2/mangas/search?q=") append(filter.query.urlEncoded()) append("&page=") @@ -53,13 +53,13 @@ internal class CuuTruyenParser(context: MangaLoaderContext) : } else -> { - val tag = (filter as? MangaListFilter.Advanced)?.tags?.oneOrThrowIfMany() + val tag = filter.tags.oneOrThrowIfMany() if (tag != null) { append("/api/v2/tags/") append(tag.key) } else { append("/api/v2/mangas") - when (filter?.sortOrder) { + when (order) { SortOrder.UPDATED -> append("/recently_updated") SortOrder.POPULARITY -> append("/top") SortOrder.NEWEST -> append("/recently_updated") 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 15ce1c33..dd14a3eb 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 @@ -35,10 +35,9 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaPa SortOrder.NEWEST, ) - override suspend fun getList(offset: Int, filter: MangaListFilter?): List { - return when (filter) { - - is MangaListFilter.Search -> { + override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List { + return when { + !filter.query.isNullOrEmpty() -> { val page = (offset / PAGE_SIZE.toFloat()).toIntUp() + 1 urlBuilder() val searchUrl = @@ -47,7 +46,7 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaPa parseMainList(docs, page) } - is MangaListFilter.Advanced -> { + else -> { val pageSize = if (filter.tags.isEmpty()) PAGE_SIZE else SEARCH_PAGE_SIZE val page = (offset / pageSize.toFloat()).toIntUp() + 1 @@ -70,19 +69,13 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaPa val docs = webClient.httpGet(url).parseHtml() return parseAdvanceSearch(docs, page) } else { - val site = if (filter.sortOrder == SortOrder.UPDATED) "/chap-moi" else "/danh-sach" + val site = if (order == SortOrder.UPDATED) "/chap-moi" else "/danh-sach" val url = "$site.html?page=$page".toAbsoluteUrl(domain) - context.cookieJar.insertCookies(domain, *getSortCookies(filter.sortOrder)) + context.cookieJar.insertCookies(domain, *getSortCookies(order)) val docs = webClient.httpGet(url).parseHtml() parseMainList(docs, page) } } - - null -> { - val page = (offset / PAGE_SIZE.toFloat()).toIntUp() + 1 - val url = "/chap-moi.html?page=$page".toAbsoluteUrl(domain) - parseMainList(webClient.httpGet(url).parseHtml(), page) - } } } 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 4932452f..57a8ba42 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 @@ -33,14 +33,14 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { val skey = "filter[name]=".urlEncoded() append("/tim-kiem?$skey") append(filter.query.urlEncoded()) @@ -48,7 +48,7 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { append("/the-loai/") @@ -74,7 +74,7 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, } append("&sort=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("-views") SortOrder.UPDATED -> append("-updated_at") SortOrder.NEWEST -> append("-created_at") @@ -83,11 +83,6 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, else -> append("-updated_at") } } - - null -> { - append("/danh-sach?sort=-updated_at&page=") - append(page.toString()) - } } } 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 947444ad..57bfcae0 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 @@ -24,65 +24,52 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, keys.add(userAgentKey) } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - val url = - when (filter) { - is MangaListFilter.Search -> { - buildString { - append("https://") - append(domain) - append("/tim-kiem/trang-$page.html") - append("?q=") - append(filter.query.urlEncoded()) - } + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + val url = when { + !filter.query.isNullOrEmpty() -> { + buildString { + append("https://") + append(domain) + append("/tim-kiem/trang-$page.html") + append("?q=") + append(filter.query.urlEncoded()) } + } - is MangaListFilter.Advanced -> { - buildString { - append("https://") - append(domain) - append("/tim-kiem-nang-cao/trang-") - append(page.toString()) - append(".html?country=0&sort=") - when (filter.sortOrder) { - SortOrder.POPULARITY -> append("4") - SortOrder.UPDATED -> append("2") - SortOrder.NEWEST -> append("0") - else -> append("2") - } - if (filter.states.isNotEmpty()) { - filter.states.oneOrThrowIfMany()?.let { - append("&status=") - append( - when (it) { - MangaState.ONGOING -> "0" - MangaState.FINISHED -> "1" - else -> "-1" - }, - ) - } - } else { - append("&status=-1") - } - - append("&category=") - append(filter.tags.joinToString(separator = ",") { it.key }) - append("¬category=&minchapter=0") + else -> { + buildString { + append("https://") + append(domain) + append("/tim-kiem-nang-cao/trang-") + append(page.toString()) + append(".html?country=0&sort=") + when (order) { + SortOrder.POPULARITY -> append("4") + SortOrder.UPDATED -> append("2") + SortOrder.NEWEST -> append("0") + else -> append("2") } - } - - null -> { - buildString { - append("https://") - append(domain) - append("/tim-kiem-nang-cao/trang-") - append(page.toString()) - append(".html?status=-1&country=0&sort=2&category=¬category=&minchapter=0") + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "0" + MangaState.FINISHED -> "1" + else -> "-1" + }, + ) + } + } else { + append("&status=-1") } + append("&category=") + append(filter.tags.joinToString(separator = ",") { it.key }) + append("¬category=&minchapter=0") } } + } val doc = webClient.httpGet(url).parseHtml() return doc.requireElementById("main_homepage").select("li").map { li -> val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") 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 bc25cc12..623a3a2b 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 @@ -27,25 +27,41 @@ class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(context, Ma private val apiDomain get() = "api.$domain" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - val listUrl = - when (filter) { + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) - is MangaListFilter.Search -> { - "/search?query=${filter.query.urlEncoded()}&page=$page" - } + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = emptySet(), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) - is MangaListFilter.Advanced -> { - if (filter.tags.isNotEmpty()) { - val tagKeys = filter.tags.joinToString(separator = ",") { it.key } - "/advancedSearch?genre=$tagKeys¬Genre=&sort=7&minChapter=1&status=0&page=$page" - } else { - "/lastest2?page=$page" - } - } + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + val listUrl = when { + !filter.query.isNullOrEmpty() -> { + "/search?query=${filter.query.urlEncoded()}&page=$page" + } - null -> "/lastest2?page=$page" + else -> { + if (filter.tags.isNotEmpty()) { + val tagKeys = filter.tags.joinToString(separator = ",") { it.key } + "/advancedSearch?genre=$tagKeys¬Genre=&sort=7&minChapter=1&status=0&page=$page" + } else { + "/lastest2?page=$page" + } } + } val jsonResponse = webClient.httpGet(listUrl.toAbsoluteUrl(apiDomain)).parseJson() return jsonResponse.getJSONArray("result") .mapJSON { jo -> @@ -125,7 +141,7 @@ class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(context, Ma } } - override suspend fun getAvailableTags(): Set { + private suspend fun fetchAvailableTags(): Set { return webClient.httpGet("https://$apiDomain/tag/find?query=") .parseJsonArray() .mapJSONToSet { jo -> 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 ec427f42..f7e75a9f 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 @@ -34,15 +34,14 @@ internal abstract class VmpParser( searchPaginator.firstPage = 1 } - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) append('/') - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(listUrl) append("/page/") append(page.toString()) @@ -50,7 +49,7 @@ internal abstract class VmpParser( append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { @@ -65,12 +64,6 @@ internal abstract class VmpParser( append(page.toString()) } } - - null -> { - append(listUrl) - append("/page/") - append(page.toString()) - } } } 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 eb454537..c94d81bc 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 @@ -38,10 +38,6 @@ internal abstract class WpComicsParser( SortOrder.RATING, ) - override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) - - override val isMultipleTagsSupported = false - protected open val listUrl = "/tim-truyen" protected open val datePattern = "dd/MM/yy" @@ -68,10 +64,30 @@ internal abstract class WpComicsParser( "完結済み", ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val response = - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { val url = buildString { append("https://") append(domain) @@ -90,7 +106,7 @@ internal abstract class WpComicsParser( result.getOrThrow() } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://") append(domain) @@ -103,12 +119,12 @@ internal abstract class WpComicsParser( } append("?sort=") append( - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> 0 SortOrder.POPULARITY -> 10 SortOrder.NEWEST -> 15 SortOrder.RATING -> 20 - else -> throw IllegalArgumentException("Sort order ${filter.sortOrder.name} not supported") + else -> throw IllegalArgumentException("Sort order ${order.name} not supported") }, ) filter.states.oneOrThrowIfMany()?.let { @@ -127,17 +143,6 @@ internal abstract class WpComicsParser( webClient.httpGet(url) } - - null -> { - val url = buildString { - append("https://") - append(domain) - append(listUrl) - append("?sort=0&status=-1&page=") - append(page.toString()) - } - webClient.httpGet(url) - } } val tagMap = getOrCreateTagMap() @@ -178,7 +183,7 @@ internal abstract class WpComicsParser( } } - override suspend fun getAvailableTags(): Set { + protected open suspend fun fetchAvailableTags(): Set { val map = getOrCreateTagMap() val tagSet = ArraySet(map.size) for (entry in map) { @@ -288,7 +293,7 @@ internal abstract class WpComicsParser( return when { d.endsWith(" ago") || d.endsWith(" trước") - -> parseRelativeDate(date) + -> parseRelativeDate(date) d.startsWith("year") -> Calendar.getInstance().apply { add(Calendar.DAY_OF_MONTH, -1) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt index e3b5eacd..b5f31d71 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt @@ -24,20 +24,20 @@ internal class XoxoComics(context: MangaLoaderContext) : SortOrder.ALPHABETICAL, ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("/search-comic?keyword=") append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { @@ -63,7 +63,7 @@ internal class XoxoComics(context: MangaLoaderContext) : append(listUrl) } - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("/popular") SortOrder.UPDATED -> append("/latest") SortOrder.NEWEST -> append("/newest") @@ -73,12 +73,6 @@ internal class XoxoComics(context: MangaLoaderContext) : append("?page=") append(page.toString()) } - - null -> { - append(listUrl) - append("/?page=") - append(page.toString()) - } } } val doc = webClient.httpGet(url).parseHtml() @@ -102,7 +96,7 @@ internal class XoxoComics(context: MangaLoaderContext) : } } - override suspend fun getAvailableTags(): Set { + override suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain$listUrl").parseHtml() return doc.select("div.genres ul li:not(.active)").mapNotNullToSet { li -> val a = li.selectFirst("a") ?: return@mapNotNullToSet null diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/ja/MangaRaw.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/ja/MangaRaw.kt index d07a7c87..619df08b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/ja/MangaRaw.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/ja/MangaRaw.kt @@ -3,17 +3,9 @@ package org.koitharu.kotatsu.parsers.site.wpcomics.ja import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.exception.NotFoundException -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.MangaState -import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser -import org.koitharu.kotatsu.parsers.util.domain -import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany -import org.koitharu.kotatsu.parsers.util.parseHtml -import org.koitharu.kotatsu.parsers.util.runCatchingCancellable -import org.koitharu.kotatsu.parsers.util.urlEncoded +import org.koitharu.kotatsu.parsers.util.* // Need to use 0ms.dev Proxy @@ -22,77 +14,65 @@ internal class MangaRaw(context: MangaLoaderContext) : WpComicsParser(context, MangaParserSource.MANGARAW, "mangaraw.xyz") { override val listUrl = "/search/manga" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - val response = - when (filter) { - is MangaListFilter.Search -> { - val url = buildString { - append("https://") - append(domain) - append(listUrl) - append("?keyword=") - append(filter.query.urlEncoded()) - append("&page=") - append(page.toString()) - } + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + val response = when { + !filter.query.isNullOrEmpty() -> { + val url = buildString { + append("https://") + append(domain) + append(listUrl) + append("?keyword=") + append(filter.query.urlEncoded()) + append("&page=") + append(page.toString()) + } - val result = runCatchingCancellable { webClient.httpGet(url) } - val exception = result.exceptionOrNull() - if (exception is NotFoundException) { - return emptyList() - } - result.getOrThrow() + val result = runCatchingCancellable { webClient.httpGet(url) } + val exception = result.exceptionOrNull() + if (exception is NotFoundException) { + return emptyList() } + result.getOrThrow() + } - is MangaListFilter.Advanced -> { - val url = buildString { - append("https://") - append(domain) - append(listUrl) - append("?sort=") + else -> { + val url = buildString { + append("https://") + append(domain) + append(listUrl) + append("?sort=") + append( + when (order) { + SortOrder.UPDATED -> 0 + SortOrder.POPULARITY -> 10 + SortOrder.NEWEST -> 15 + SortOrder.RATING -> 20 + else -> throw IllegalArgumentException("Sort order ${order.name} not supported") + }, + ) + if (filter.tags.isNotEmpty()) { + append("&genre=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + } + filter.states.oneOrThrowIfMany()?.let { + append("&status=") append( - when (filter.sortOrder) { - SortOrder.UPDATED -> 0 - SortOrder.POPULARITY -> 10 - SortOrder.NEWEST -> 15 - SortOrder.RATING -> 20 - else -> throw IllegalArgumentException("Sort order ${filter.sortOrder.name} not supported") + when (it) { + MangaState.ONGOING -> "1" + MangaState.FINISHED -> "2" + else -> "-1" }, ) - if (filter.tags.isNotEmpty()) { - append("&genre=") - filter.tags.oneOrThrowIfMany()?.let { - append(it.key) - } - } - filter.states.oneOrThrowIfMany()?.let { - append("&status=") - append( - when (it) { - MangaState.ONGOING -> "1" - MangaState.FINISHED -> "2" - else -> "-1" - }, - ) - } - append("&page=") - append(page.toString()) } - - webClient.httpGet(url) + append("&page=") + append(page.toString()) } - null -> { - val url = buildString { - append("https://") - append(domain) - append(listUrl) - append("?genres=¬genres=&gender=-1&status=-1&minchapter=1&sort=0&page=") - append(page.toString()) - } - webClient.httpGet(url) - } + webClient.httpGet(url) } + } val tagMap = getOrCreateTagMap() return parseMangaList(response.parseHtml(), tagMap) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenHE.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenHE.kt index b4ed1b0c..24914b51 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenHE.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenHE.kt @@ -7,6 +7,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaListFilter +import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaTag @@ -33,10 +34,10 @@ internal class NetTruyenHE(context: MangaLoaderContext) : SortOrder.ALPHABETICAL_DESC, ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val response = - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { val url = buildString { append("https://") append(domain) @@ -56,7 +57,7 @@ internal class NetTruyenHE(context: MangaLoaderContext) : result.getOrThrow() } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://") append(domain) @@ -93,7 +94,7 @@ internal class NetTruyenHE(context: MangaLoaderContext) : append("&sort=") append( - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> "latest-updated" SortOrder.POPULARITY -> "views" SortOrder.NEWEST -> "new" @@ -107,19 +108,6 @@ internal class NetTruyenHE(context: MangaLoaderContext) : webClient.httpGet(url) } - - null -> { - val url = buildString { - append("https://") - append(domain) - append(listUrl) - append('/') - append(page.toString()) - append('/') - append("?genres=¬Genres=&sex=All&status=&chapter_count=0&sort=latest-updated") - } - webClient.httpGet(url) - } } val tagMap = getOrCreateTagMap() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt index 63cb5ece..932ff5a7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt @@ -7,6 +7,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaListFilter +import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaTag @@ -33,10 +34,10 @@ internal class NetTruyenLL(context: MangaLoaderContext) : SortOrder.ALPHABETICAL_DESC, ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val response = - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { val url = buildString { append("https://") append(domain) @@ -56,7 +57,7 @@ internal class NetTruyenLL(context: MangaLoaderContext) : result.getOrThrow() } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://") append(domain) @@ -93,7 +94,7 @@ internal class NetTruyenLL(context: MangaLoaderContext) : append("&sort=") append( - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> "latest-updated" SortOrder.POPULARITY -> "views" SortOrder.NEWEST -> "new" @@ -107,19 +108,6 @@ internal class NetTruyenLL(context: MangaLoaderContext) : webClient.httpGet(url) } - - null -> { - val url = buildString { - append("https://") - append(domain) - append(listUrl) - append('/') - append(page.toString()) - append('/') - append("?genres=¬Genres=&sex=All&status=&chapter_count=0&sort=latest-updated") - } - webClient.httpGet(url) - } } val tagMap = getOrCreateTagMap() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt index ff0447d5..4fa386d3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt @@ -5,15 +5,10 @@ import kotlinx.coroutines.sync.withLock import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.exception.NotFoundException -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.MangaState -import org.koitharu.kotatsu.parsers.model.MangaTag -import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.util.* -import java.util.EnumSet +import java.util.* @MangaSourceParser("NETTRUYENSSR", "NetTruyenSSR", "vi") internal class NetTruyenSSR(context: MangaLoaderContext) : @@ -33,10 +28,10 @@ internal class NetTruyenSSR(context: MangaLoaderContext) : SortOrder.ALPHABETICAL_DESC, ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val response = - when (filter) { - is MangaListFilter.Search -> { + when { + !filter.query.isNullOrEmpty() -> { val url = buildString { append("https://") append(domain) @@ -56,7 +51,7 @@ internal class NetTruyenSSR(context: MangaLoaderContext) : result.getOrThrow() } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://") append(domain) @@ -93,7 +88,7 @@ internal class NetTruyenSSR(context: MangaLoaderContext) : append("&sort=") append( - when (filter.sortOrder) { + when (order) { SortOrder.UPDATED -> "latest-updated" SortOrder.POPULARITY -> "views" SortOrder.NEWEST -> "new" @@ -107,19 +102,6 @@ internal class NetTruyenSSR(context: MangaLoaderContext) : webClient.httpGet(url) } - - null -> { - val url = buildString { - append("https://") - append(domain) - append(listUrl) - append('/') - append(page.toString()) - append('/') - append("?genres=¬Genres=&sex=All&status=&chapter_count=0&sort=latest-updated") - } - webClient.httpGet(url) - } } val tagMap = getOrCreateTagMap() 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 df66b316..5f667d0a 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 @@ -30,15 +30,21 @@ internal abstract class ZeistMangaParser( keys.add(userAgentKey) } - override val isMultipleTagsSupported = false - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val availableStates: Set = - EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED) - protected open val datePattern = "yyyy-MM-dd" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = false, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + @JvmField protected val ongoing: Set = hashSetOf( "ongoing", @@ -82,16 +88,25 @@ internal abstract class ZeistMangaParser( protected open val mangaCategory: String = "Series" - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val startIndex = maxMangaResults * (page - 1) + 1 val url = buildString { append("https://") append(domain) append("/feeds/posts/default/-/") - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append(mangaCategory) append("?alt=json&orderby=published&max-results=") append((maxMangaResults + 1).toString()) @@ -103,7 +118,7 @@ internal abstract class ZeistMangaParser( append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) { @@ -132,14 +147,6 @@ internal abstract class ZeistMangaParser( append("&start-index=") append(startIndex.toString()) } - - null -> { - append(mangaCategory) - append("?alt=json&orderby=published&max-results=") - append((maxMangaResults + 1).toString()) - append("&start-index=") - append(startIndex.toString()) - } } } @@ -183,7 +190,7 @@ internal abstract class ZeistMangaParser( } } - override suspend fun getAvailableTags(): Set { + protected open suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain").parseHtml() return doc.selectFirstOrThrow("div.filter").select("ul li").mapNotNullToSet { MangaTag( 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 0467eb45..b91627ff 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 @@ -32,10 +32,9 @@ internal class Baozimh(context: MangaLoaderContext) : private val tagsMap = SuspendLazy(::parseTags) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - - when (filter) { - is MangaListFilter.Search -> { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { + when { + !filter.query.isNullOrEmpty() -> { if (page > 1) return emptyList() val url = buildString { append("https://") @@ -46,7 +45,7 @@ internal class Baozimh(context: MangaLoaderContext) : return parseMangaListSearch(webClient.httpGet(url).parseHtml()) } - is MangaListFilter.Advanced -> { + else -> { val url = buildString { append("https://") append(domain) @@ -82,16 +81,6 @@ internal class Baozimh(context: MangaLoaderContext) : return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("items")) } - - null -> { - val url = buildString { - append("https://") - append(domain) - append("/api/bzmhq/amp_comic_list?filter=*®ion=all&type=all&state=all&limit=36&page=") - append(page.toString()) - } - return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("items")) - } } } 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 b1d04e23..b6b950fd 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 @@ -35,11 +35,28 @@ internal abstract class ZMangaParser( SortOrder.ALPHABETICAL_DESC, ) - override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) - protected open val listUrl = "advanced-search/" protected open val datePattern = "MMMM d, yyyy" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = false, + isSearchSupported = true, + isSearchWithFiltersSupported = false, + isYearSupported = false, + isYearRangeSupported = false, + isSourceLocaleSupported = false, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentRating = emptySet(), + availableContentTypes = emptySet(), + availableDemographics = emptySet(), + availableLocales = emptySet(), + ) init { paginator.firstPage = 1 @@ -58,7 +75,7 @@ internal abstract class ZMangaParser( "Completed", ) - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List { val url = buildString { append("https://") append(domain) @@ -70,17 +87,17 @@ internal abstract class ZMangaParser( append('/') } - when (filter) { + when { - is MangaListFilter.Search -> { + !filter.query.isNullOrEmpty() -> { append("&title=") append(filter.query.urlEncoded()) } - is MangaListFilter.Advanced -> { + else -> { append("?order=") - when (filter.sortOrder) { + when (order) { SortOrder.POPULARITY -> append("popular") SortOrder.UPDATED -> append("update") SortOrder.ALPHABETICAL -> append("title") @@ -108,8 +125,6 @@ internal abstract class ZMangaParser( ) } } - - null -> append("?order=update") } } @@ -141,7 +156,7 @@ internal abstract class ZMangaParser( } } - override suspend fun getAvailableTags(): Set { + protected open suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() return doc.select("tr.gnrx div.custom-control").mapNotNullToSet { checkbox -> val key = checkbox.selectFirstOrThrow("input").attr("value") ?: return@mapNotNullToSet null diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Number.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Number.kt index 4eacc6d7..464ce78c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Number.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Number.kt @@ -5,6 +5,7 @@ package org.koitharu.kotatsu.parsers.util import java.text.DecimalFormat import java.text.NumberFormat import java.util.* +import kotlin.math.absoluteValue fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = ' '): String { val formatter = NumberFormat.getInstance(Locale.US) as DecimalFormat @@ -22,7 +23,7 @@ fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = return when (this) { is Float, is Double, - -> formatter.format(this.toDouble()) + -> formatter.format(this.toDouble()) else -> formatter.format(this.toLong()) } @@ -30,7 +31,7 @@ fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = fun Float.toIntUp(): Int { val intValue = toInt() - return if (this == intValue.toFloat()) { + return if ((this - intValue.toFloat()).absoluteValue <= 0.00001) { intValue } else { intValue + 1 @@ -54,3 +55,9 @@ fun Number.formatSimple(): String { raw } } + +inline fun Int.ifZero(defaultVale: () -> Int): Int = if (this == 0) { + defaultVale() +} else { + this +} 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 516a5b9c..36792c0a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt @@ -6,6 +6,8 @@ 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.MangaListFilterV2 +import org.koitharu.kotatsu.parsers.model.SortOrder class RelatedMangaFinder( private val parsers: Collection, @@ -34,7 +36,7 @@ class RelatedMangaFinder( } val results = words.map { keyword -> scope.async { - val result = parser.getList(0, keyword) + val result = parser.getList(0, SortOrder.RELEVANCE, MangaListFilterV2(query = keyword)) result.filter { it.id != seed.id && it.containKeyword(keyword) } } }.awaitAll() diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt index 8794393b..be80284a 100644 --- a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt +++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt @@ -21,7 +21,7 @@ internal class MangaParserTest { @MangaSources fun list(source: MangaParserSource) = runTest(timeout = timeout) { val parser = context.newParserInstance(source) - val list = parser.getList(0, null) + val list = parser.getList(0, parser.defaultSortOrder, MangaListFilterV2.EMPTY) checkMangaList(list, "list") assert(list.all { it.source == source }) } @@ -30,8 +30,11 @@ internal class MangaParserTest { @MangaSources fun pagination(source: MangaParserSource) = runTest(timeout = timeout) { val parser = context.newParserInstance(source) - val page1 = parser.getList(0, filter = null) - val page2 = parser.getList(page1.size, filter = null) + if (parser is SinglePageMangaParser) { + return@runTest + } + val page1 = parser.getList(0, parser.defaultSortOrder, MangaListFilterV2.EMPTY) + val page2 = parser.getList(page1.size, parser.defaultSortOrder, MangaListFilterV2.EMPTY) if (parser is PagedMangaParser) { assert(parser.pageSize >= page1.size) { "Page size is ${page1.size} but ${parser.pageSize} expected" @@ -72,7 +75,7 @@ internal class MangaParserTest { @MangaSources fun tags(source: MangaParserSource) = runTest(timeout = timeout) { val parser = context.newParserInstance(source) - val tags = parser.getAvailableTags() + val tags = parser.getFilterOptions().availableTags assert(tags.isNotEmpty()) { "No tags found" } val keys = tags.map { it.key } assert(keys.isDistinct()) @@ -100,8 +103,8 @@ internal class MangaParserTest { @MangaSources fun tagsMultiple(source: MangaParserSource) = runTest(timeout = timeout) { val parser = context.newParserInstance(source) - if (!parser.isMultipleTagsSupported) return@runTest - val tags = parser.getAvailableTags().shuffled().take(2).toSet() + if (!parser.filterCapabilities.isMultipleTagsSupported) return@runTest + val tags = parser.getFilterOptions().availableTags.shuffled().take(2).toSet() val filter = MangaListFilterV2(tags = tags) val list = parser.getList(0, parser.defaultSortOrder, filter) @@ -113,7 +116,7 @@ internal class MangaParserTest { @MangaSources fun locale(source: MangaParserSource) = runTest(timeout = timeout) { val parser = context.newParserInstance(source) - val locales = parser.getAvailableLocales() + val locales = parser.getFilterOptions().availableLocales if (locales.isEmpty()) { return@runTest } @@ -131,7 +134,7 @@ internal class MangaParserTest { @MangaSources fun details(source: MangaParserSource) = runTest(timeout = timeout) { val parser = context.newParserInstance(source) - val list = parser.getList(0, null) + val list = parser.getList(0, parser.defaultSortOrder, MangaListFilterV2.EMPTY) val manga = list[3] parser.getDetails(manga).apply { assert(!chapters.isNullOrEmpty()) { "Chapters are null or empty" } @@ -161,7 +164,7 @@ internal class MangaParserTest { @MangaSources fun pages(source: MangaParserSource) = runTest(timeout = timeout) { val parser = context.newParserInstance(source) - val list = parser.getList(0, null) + val list = parser.getList(0, parser.defaultSortOrder, MangaListFilterV2.EMPTY) val manga = list.first() val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null at ${manga.publicUrl}") val pages = parser.getPages(chapter)