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 c85659a50..76170f9c7 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 @@ -49,10 +49,10 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( } override val availableSortOrders: Set = EnumSet.of( - SortOrder.NEWEST, + SortOrder.ALPHABETICAL, SortOrder.UPDATED, + SortOrder.NEWEST, SortOrder.POPULARITY, - SortOrder.ALPHABETICAL, SortOrder.POPULARITY_YEAR, SortOrder.POPULARITY_MONTH, SortOrder.POPULARITY_WEEK, @@ -65,6 +65,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( isMultipleTagsSupported = true, isTagsExclusionSupported = true, isSearchSupported = true, + isOriginalLocaleSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( @@ -165,6 +166,15 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( } + filter.originalLocale?.let { + append("&origs=") + if (it.language == "in") { + append("id") + } else { + append(it.language) + } + } + append("&genres=") if (filter.tags.isNotEmpty()) { appendAll(filter.tags, ",") { it.key } 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 bfa0ff4bc..a2fc3f5d5 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 @@ -34,13 +34,12 @@ internal class ComickFunParser(context: MangaLoaderContext) : SortOrder.NEWEST, ) - private val tagsArray = SuspendLazy(::loadTags) - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, isTagsExclusionSupported = true, isSearchSupported = true, + isSearchWithFiltersSupported = true, isYearRangeSupported = true, ) @@ -66,80 +65,77 @@ internal class ComickFunParser(context: MangaLoaderContext) : .addQueryParameter("tachiyomi", "true") .addQueryParameter("limit", pageSize.toString()) .addQueryParameter("page", page.toString()) - when { - !filter.query.isNullOrEmpty() -> { - url.addQueryParameter("q", filter.query) - } - else -> { + filter.query?.let { + url.addQueryParameter("q", filter.query) + } - filter.tags.forEach { - url.addQueryParameter("genres", it.key) - } + filter.tags.forEach { + url.addQueryParameter("genres", it.key) + } - filter.tagsExclude.forEach { - url.addQueryParameter("excludes", it.key) - } + filter.tagsExclude.forEach { + url.addQueryParameter("excludes", it.key) + } - url.addQueryParameter( - "sort", - when (order) { - SortOrder.POPULARITY -> "view" - SortOrder.UPDATED -> "uploaded" - SortOrder.NEWEST -> "created_at" - SortOrder.RATING -> "rating" - else -> "uploaded" - }, - ) + url.addQueryParameter( + "sort", + when (order) { + SortOrder.NEWEST -> "created_at" + SortOrder.POPULARITY -> "view" + SortOrder.RATING -> "rating" + SortOrder.UPDATED -> "uploaded" + else -> "uploaded" + }, + ) - filter.states.oneOrThrowIfMany()?.let { - url.addQueryParameter( - "status", - when (it) { - MangaState.ONGOING -> "1" - MangaState.FINISHED -> "2" - MangaState.ABANDONED -> "3" - MangaState.PAUSED -> "4" - else -> "" - }, - ) - } + filter.states.oneOrThrowIfMany()?.let { + url.addQueryParameter( + "status", + when (it) { + MangaState.ONGOING -> "1" + MangaState.FINISHED -> "2" + MangaState.ABANDONED -> "3" + MangaState.PAUSED -> "4" + else -> "" + }, + ) + } - if (filter.yearFrom != YEAR_UNKNOWN) { - url.addQueryParameter("from", filter.yearFrom.toString()) - } + if (filter.yearFrom != YEAR_UNKNOWN) { + url.addQueryParameter("from", filter.yearFrom.toString()) + } - if (filter.yearTo != YEAR_UNKNOWN) { - url.addQueryParameter("to", filter.yearTo.toString()) - } + if (filter.yearTo != YEAR_UNKNOWN) { + url.addQueryParameter("to", filter.yearTo.toString()) + } - filter.types.forEach { - url.addQueryParameter( - "country", - when (it) { - ContentType.MANGA -> "jp" - ContentType.MANHWA -> "kr" - ContentType.MANHUA -> "cn" - ContentType.OTHER -> "others" - else -> "" - }, - ) - } + filter.types.forEach { + url.addQueryParameter( + "country", + when (it) { + ContentType.MANGA -> "jp" + ContentType.MANHWA -> "kr" + ContentType.MANHUA -> "cn" + ContentType.OTHER -> "others" + else -> "" + }, + ) + } - filter.demographics.forEach { - url.addQueryParameter( - "demographic", - when (it) { - Demographic.SHOUNEN -> "1" - Demographic.SHOUJO -> "2" - Demographic.SEINEN -> "3" - Demographic.JOSEI -> "4" - Demographic.NONE -> "5" - }, - ) - } - } + filter.demographics.forEach { + url.addQueryParameter( + "demographic", + when (it) { + Demographic.SHOUNEN -> "1" + Demographic.SHOUJO -> "2" + Demographic.SEINEN -> "3" + Demographic.JOSEI -> "4" + Demographic.NONE -> "5" + }, + ) } + val ja = webClient.httpGet(url.build()).parseJsonArray() val tagsMap = tagsArray.get() return ja.mapJSON { jo -> @@ -193,6 +189,45 @@ internal class ComickFunParser(context: MangaLoaderContext) : ) } + private suspend fun getChapters(hid: String): List { + val ja = webClient.httpGet( + url = "https://api.${domain}/comic/$hid/chapters?limit=$CHAPTERS_LIMIT", + ).parseJson().getJSONArray("chapters") + val dateFormat = SimpleDateFormat("yyyy-MM-dd") + return ja.toJSONList().reversed().mapChapters { _, jo -> + val vol = jo.getIntOrDefault("vol", 0) + val chap = jo.getFloatOrDefault("chap", 0f) + val locale = Locale.forLanguageTag(jo.getString("lang")) + val group = jo.optJSONArray("group_name")?.joinToString(", ") + val branch = buildString { + append(locale.getDisplayName(locale).toTitleCase(locale)) + if (!group.isNullOrEmpty()) { + append(" (") + append(group) + append(')') + } + } + MangaChapter( + id = generateUid(jo.getLong("id")), + name = buildString { + if (vol > 0) { + append("Vol ").append(vol).append(' ') + } + append("Chap ").append(chap) + jo.getStringOrNull("title")?.let { append(": ").append(it) } + }, + number = chap, + volume = vol, + url = jo.getString("hid"), + scanlator = jo.optJSONArray("group_name")?.asIterable()?.joinToString() + ?.takeUnless { it.isBlank() }, + uploadDate = dateFormat.tryParse(jo.getString("created_at").substringBefore('T')), + branch = branch, + source = source, + ) + } + } + override suspend fun getPages(chapter: MangaChapter): List { val jo = webClient.httpGet( "https://api.${domain}/chapter/${chapter.url}?tachiyomi=true", @@ -208,6 +243,8 @@ internal class ComickFunParser(context: MangaLoaderContext) : } } + private val tagsArray = SuspendLazy(::loadTags) + private suspend fun fetchAvailableTags(): Set { val sparseArray = tagsArray.get() val set = ArraySet(sparseArray.size()) @@ -233,45 +270,6 @@ internal class ComickFunParser(context: MangaLoaderContext) : return tags } - private suspend fun getChapters(hid: String): List { - val ja = webClient.httpGet( - url = "https://api.${domain}/comic/$hid/chapters?limit=$CHAPTERS_LIMIT", - ).parseJson().getJSONArray("chapters") - val dateFormat = SimpleDateFormat("yyyy-MM-dd") - return ja.toJSONList().reversed().mapChapters { _, jo -> - val vol = jo.getIntOrDefault("vol", 0) - val chap = jo.getFloatOrDefault("chap", 0f) - val locale = Locale.forLanguageTag(jo.getString("lang")) - val group = jo.optJSONArray("group_name")?.joinToString(", ") - val branch = buildString { - append(locale.getDisplayName(locale).toTitleCase(locale)) - if (!group.isNullOrEmpty()) { - append(" (") - append(group) - append(')') - } - } - MangaChapter( - id = generateUid(jo.getLong("id")), - name = buildString { - if (vol > 0) { - append("Vol ").append(vol).append(' ') - } - append("Chap ").append(chap) - jo.getStringOrNull("title")?.let { append(": ").append(it) } - }, - number = chap, - volume = vol, - url = jo.getString("hid"), - scanlator = jo.optJSONArray("group_name")?.asIterable()?.joinToString() - ?.takeUnless { it.isBlank() }, - uploadDate = dateFormat.tryParse(jo.getString("created_at").substringBefore('T')), - branch = branch, - source = source, - ) - } - } - private fun JSONObject.selectGenres(tags: SparseArrayCompat): Set { val array = optJSONArray("genres") ?: return emptySet() val res = ArraySet(array.length()) 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 de3bffedd..4c61d6fcb 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 @@ -44,6 +44,22 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context keys.add(preferredServerKey) } + override val availableSortOrders: EnumSet = EnumSet.of( + SortOrder.UPDATED, + SortOrder.UPDATED_ASC, + SortOrder.POPULARITY, + SortOrder.POPULARITY_ASC, + SortOrder.RATING, + SortOrder.RATING_ASC, + SortOrder.NEWEST, + SortOrder.NEWEST_ASC, + SortOrder.ALPHABETICAL, + SortOrder.ALPHABETICAL_DESC, + SortOrder.ADDED, + SortOrder.ADDED_ASC, + SortOrder.RELEVANCE, + ) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, @@ -54,8 +70,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context isOriginalLocaleSupported = true, ) - override val availableSortOrders: EnumSet = EnumSet.allOf(SortOrder::class.java) - override suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope { val localesDeferred = async { fetchAvailableLocales() } val tagsDeferred = async { fetchAvailableTags() } 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 1f3d68027..3c75e13ec 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 @@ -16,16 +16,23 @@ import java.util.* internal class MangaPark(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) { + override val configKeyDomain = ConfigKey.Domain("mangapark.net") + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING) - override val configKeyDomain = ConfigKey.Domain("mangapark.net") - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, isTagsExclusionSupported = true, isSearchSupported = true, + isSearchWithFiltersSupported = true, + isOriginalLocaleSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( @@ -56,13 +63,6 @@ internal class MangaPark(context: MangaLoaderContext) : ), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - - private val tagsMap = SuspendLazy(::parseTags) - init { context.cookieJar.insertCookies(domain, "nsfw", "2") } @@ -73,66 +73,66 @@ internal class MangaPark(context: MangaLoaderContext) : append(domain) append("/search?page=") append(page.toString()) - when { - !filter.query.isNullOrEmpty() -> { - append("&word=") - append(filter.query.urlEncoded()) - } + filter.query?.let { + append("&word=") + append(filter.query.urlEncoded()) + } - else -> { + append("&genres=") + if (filter.tags.isNotEmpty()) { + appendAll(filter.tags, ",") { it.key } + } - append("&genres=") - if (filter.tags.isNotEmpty()) { - appendAll(filter.tags, ",") { it.key } - } + append("|") + if (filter.tagsExclude.isNotEmpty()) { + appendAll(filter.tagsExclude, ",") { it.key } + } - append("|") - if (filter.tagsExclude.isNotEmpty()) { - appendAll(filter.tagsExclude, ",") { it.key } - } + if (filter.contentRating.isNotEmpty()) { + filter.contentRating.oneOrThrowIfMany()?.let { + append( + when (it) { + ContentRating.SAFE -> append(",gore,bloody,violence,ecchi,adult,mature,smut,hentai") + else -> append("") + }, + ) + } + } - if (filter.contentRating.isNotEmpty()) { - filter.contentRating.oneOrThrowIfMany()?.let { - append( - when (it) { - ContentRating.SAFE -> append(",gore,bloody,violence,ecchi,adult,mature,smut,hentai") - else -> append("") - }, - ) - } - } + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + MangaState.PAUSED -> "hiatus" + MangaState.ABANDONED -> "cancelled" + MangaState.UPCOMING -> "pending" + }, + ) + } - filter.states.oneOrThrowIfMany()?.let { - append("&status=") - append( - when (it) { - MangaState.ONGOING -> "ongoing" - MangaState.FINISHED -> "completed" - MangaState.PAUSED -> "hiatus" - MangaState.ABANDONED -> "cancelled" - MangaState.UPCOMING -> "pending" - }, - ) - } + append("&sortby=") + append( + when (order) { + SortOrder.POPULARITY -> "views_d000" + SortOrder.UPDATED -> "field_update" + SortOrder.NEWEST -> "field_create" + SortOrder.ALPHABETICAL -> "field_name" + SortOrder.RATING -> "field_score" + else -> "" - append("&sortby=") - append( - when (order) { - SortOrder.POPULARITY -> "views_d000" - SortOrder.UPDATED -> "field_update" - SortOrder.NEWEST -> "field_create" - SortOrder.ALPHABETICAL -> "field_name" - SortOrder.RATING -> "field_score" - else -> "" + }, + ) - }, - ) + filter.locale?.let { + append("&lang=") + append(it.language) + } - filter.locale?.let { - append("&lang=") - append(it.language) - } - } + filter.originalLocale?.let { + append("&orig=") + append(it.language) } } @@ -156,6 +156,8 @@ internal class MangaPark(context: MangaLoaderContext) : } } + private val tagsMap = SuspendLazy(::parseTags) + private suspend fun parseTags(): Map { val tagElements = webClient.httpGet("https://$domain/search").parseHtml() .select("div.flex-col:contains(Genres) div.whitespace-nowrap") @@ -217,13 +219,18 @@ internal class MangaPark(context: MangaLoaderContext) : private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { val d = date?.lowercase() ?: return 0 return when { - d.endsWith(" ago") -> parseRelativeDate(date) - d.startsWith("just now") -> Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis + WordSet(" ago").endsWith(d) -> { + parseRelativeDate(d) + } + + WordSet("just now").startsWith(d) -> { + Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } else -> dateFormat.tryParse(date) } 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 f795b1533..9fda08685 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 @@ -44,6 +44,7 @@ internal abstract class NineMangaParser( get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, isTagsExclusionSupported = true, + isSearchWithFiltersSupported = true, isSearchSupported = true, ) @@ -69,39 +70,35 @@ internal abstract class NineMangaParser( val url = buildString { append("https://") append(domain) - when { - !filter.query.isNullOrEmpty() -> { - append("/search/?name_sel=&wd=") + + if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty() || filter.states.isNotEmpty() || !filter.query.isNullOrEmpty()) { + append("/search/") + append("?page=") + append(page.toString()) + + filter.query?.let { + append("&name_sel=contain&wd=") append(filter.query.urlEncoded()) - append("&page=") - append(page) - append(".html") } - else -> { - - if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty() || filter.states.isNotEmpty()) { - append("/search/?category_id=") - append(filter.tags.joinToString(separator = ",") { it.key }) - - append("&out_category_id=") - append(filter.tagsExclude.joinToString(separator = ",") { it.key }) - - filter.states.oneOrThrowIfMany()?.let { - append("&completed_series=") - when (it) { - MangaState.ONGOING -> append("no") - MangaState.FINISHED -> append("yes") - else -> append("either") - } - } - append("&page=") - } else { - append("/category/index_") + append("&category_id=") + append(filter.tags.joinToString(separator = ",") { it.key }) + + append("&out_category_id=") + append(filter.tagsExclude.joinToString(separator = ",") { it.key }) + + filter.states.oneOrThrowIfMany()?.let { + append("&completed_series=") + when (it) { + MangaState.ONGOING -> append("no") + MangaState.FINISHED -> append("yes") + else -> append("either") } - append(page.toString()) - append(".html") } + + } else { + append("/category/index_") + append(page.toString()) } } val doc = webClient.httpGet(url).parseHtml() 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 f0c440e19..6d1590f72 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 @@ -43,10 +43,16 @@ internal abstract class AnimeBootstrapParser( override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, + isSearchWithFiltersSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = fetchAvailableTags(), + availableContentTypes = EnumSet.of( + ContentType.MANGA, + ContentType.MANHWA, + ContentType.MANHUA, + ), ) override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { @@ -58,30 +64,37 @@ internal abstract class AnimeBootstrapParser( append(page.toString()) append("&type=all") - when { - !filter.query.isNullOrEmpty() -> { - append("&search=") - append(filter.query.urlEncoded()) - } - - else -> { - - filter.tags.oneOrThrowIfMany()?.let { - append("&categorie=") - append(it.key) - } - - append("&sort=") - when (order) { - SortOrder.POPULARITY -> append("view") - SortOrder.UPDATED -> append("updated") - SortOrder.ALPHABETICAL -> append("default") - SortOrder.NEWEST -> append("published") - else -> append("updated") - } - - } + filter.query?.let { + append("&search=") + append(filter.query.urlEncoded()) } + + filter.tags.oneOrThrowIfMany()?.let { + append("&categorie=") + append(it.key) + } + + filter.types.oneOrThrowIfMany()?.let { + append("&type=") + append( + when (it) { + ContentType.MANGA -> "manga" + ContentType.MANHWA -> "manhwa" + ContentType.MANHUA -> "manhua" + else -> "all" + }, + ) + } + + append("&sort=") + when (order) { + SortOrder.POPULARITY -> append("view") + SortOrder.UPDATED -> append("updated") + SortOrder.ALPHABETICAL -> append("default") + SortOrder.NEWEST -> append("published") + else -> append("updated") + } + } val doc = webClient.httpGet(url).parseHtml() 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 48fe2eafc..c710c5687 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 @@ -4,6 +4,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import org.json.JSONArray import org.json.JSONObject +import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -15,6 +16,7 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed import java.text.SimpleDateFormat import java.util.* +@Broken @MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar") internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) { 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 11edcc74a..c237ceb9b 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 @@ -28,66 +28,66 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, + isSearchWithFiltersSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = fetchAvailableTags(), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED), + availableContentTypes = EnumSet.of( + ContentType.MANGA, + ContentType.MANHWA, + ContentType.MANHUA, + ), ) override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") append(domain) - when { - !filter.query.isNullOrEmpty() -> { - append("/?search=") + if (order == SortOrder.UPDATED) { + if (filter.tags.isNotEmpty() || filter.demographics.isNotEmpty()) { + throw IllegalArgumentException("Updated sorting does not support other sorting filters") + } + append("/?page=") + append(page.toString()) + } else { + append("/series?page=") + append(page.toString()) + + filter.query?.let { + append("&search=") append(filter.query.urlEncoded()) - if (page > 1) { - append("&page=") - append(page.toString()) - } } - else -> { - if (filter.tags.isNotEmpty()) { - val tag = filter.tags.oneOrThrowIfMany() - append("/series?genre=") - append(tag?.key.orEmpty()) - if (page > 1) { - append("&page=") - append(page.toString()) - } - append("&") - } else { - when (order) { - SortOrder.POPULARITY -> append("/series") - SortOrder.UPDATED -> append("/") - else -> append("/") - } - if (page > 1) { - append("?page=") - append(page.toString()) - append("&") - } else { - append("?") - } - } + filter.tags.oneOrThrowIfMany()?.let { + append("&genre=") + append(it.key) + } - if (order == SortOrder.POPULARITY || filter.tags.isNotEmpty()) { - filter.states.oneOrThrowIfMany()?.let { - append("status=") - append( - when (it) { - MangaState.ONGOING -> "مستمرة" - MangaState.FINISHED -> "مكتمل" - MangaState.ABANDONED -> "متوقف" - else -> "مستمرة" - }, - ) - } - } + filter.types.forEach { + append("&type=") + append( + when (it) { + ContentType.MANGA -> "مانجا ياباني" + ContentType.MANHWA -> "مانهوا كورية" + ContentType.MANHUA -> "مانها صيني" + else -> "" + }, + ) + } + + filter.states.oneOrThrowIfMany()?.let { + append("status=") + append( + when (it) { + MangaState.ONGOING -> "مستمرة" + MangaState.FINISHED -> "مكتمل" + MangaState.ABANDONED -> "متوقف" + else -> "مستمرة" + }, + ) } } } 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 3b5fb7da7..834700e34 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 @@ -252,7 +252,9 @@ internal abstract class FmreaderParser( val d = date?.lowercase() ?: return 0 return when { - WordSet(" ago", " atrás", " h", " d").endsWith(d) -> { parseRelativeDate(d) } + WordSet(" ago", " atrás", " h", " d").endsWith(d) -> { + parseRelativeDate(d) + } WordSet("today").startsWith(d) -> { Calendar.getInstance().apply { @@ -281,18 +283,25 @@ internal abstract class FmreaderParser( return when { WordSet("second") .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("min", "minute", "minutes", "minuto", "minutos") .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hour", "hours", "hora", "horas", "h") .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("day", "days", "día", "dia") .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("week", "weeks", "semana", "semanas") .anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis + WordSet("month", "months", "mes", "meses") .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year", "año", "años") .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 } } 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 276cdba70..dfd5b3923 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 @@ -267,7 +267,9 @@ internal abstract class KeyoappParser( val d = date?.lowercase() ?: return 0 return when { - WordSet(" ago").endsWith(d) -> { parseRelativeDate(d) } + WordSet(" ago").endsWith(d) -> { + parseRelativeDate(d) + } WordSet("today").startsWith(d) -> { Calendar.getInstance().apply { @@ -296,16 +298,22 @@ internal abstract class KeyoappParser( return when { WordSet("second") .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("minute", "minutes") .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hour", "hours") .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("day", "days") .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("month", "months") .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year") .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 } } 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 1f002180e..c5ec00180 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 @@ -40,6 +40,8 @@ internal abstract class MadaraParser( isMultipleTagsSupported = true, isTagsExclusionSupported = !withoutAjax, isSearchSupported = true, + isSearchWithFiltersSupported = true, + isYearSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( @@ -51,8 +53,7 @@ internal abstract class MadaraParser( override val availableSortOrders: Set = setupAvailableSortOrders() private fun setupAvailableSortOrders(): Set { - return if(!withoutAjax) - { + return if (!withoutAjax) { EnumSet.of( SortOrder.UPDATED, SortOrder.UPDATED_ASC, @@ -64,10 +65,17 @@ internal abstract class MadaraParser( SortOrder.ALPHABETICAL_DESC, SortOrder.RATING, SortOrder.RATING_ASC, + SortOrder.RELEVANCE, + ) + } else { + EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + SortOrder.ALPHABETICAL, + SortOrder.RATING, + SortOrder.RELEVANCE, ) - }else - { - EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING) } } @@ -219,91 +227,74 @@ internal abstract class MadaraParser( append("https://") append(domain) - when { + if (pages > 1) { + append("/page/") + append(pages.toString()) + } + append("/?s=") + + append(filter.query?.urlEncoded()) - !filter.query.isNullOrEmpty() -> { - if (pages > 1) { - append("/page/") - append(pages.toString()) - } - append("/?s=") - append(filter.query.urlEncoded()) - append("&post_type=wp-manga") + append("&post_type=wp-manga") + + // Known bug: in some cases, if there are no manga with the associated tags, the source returns the full list of manga + if (filter.tags.isNotEmpty()) { + filter.tags.forEach { + append("&genre[]=") + append(it.key) } + } - else -> { - if (pages > 1) { - append("/page/") - append(pages.toString()) - } - append("/?s=") - - //Support query - //append(filter.query.urlEncoded()) - - append("&post_type=wp-manga") - - // Known bug: in some cases, if there are no manga with the associated tags, the source returns the full list of manga - if (filter.tags.isNotEmpty()) { - filter.tags.forEach { - append("&genre[]=") - append(it.key) - } - } - - filter.states.forEach { - append("&status[]=") - when (it) { - MangaState.ONGOING -> append("on-going") - MangaState.FINISHED -> append("end") - MangaState.ABANDONED -> append("canceled") - MangaState.PAUSED -> append("on-hold") - MangaState.UPCOMING -> append("upcoming") - } - } - - filter.contentRating.oneOrThrowIfMany()?.let { - append("&adult=") - append( - when (it) { - ContentRating.SAFE -> "0" - ContentRating.ADULT -> "1" - else -> "" - }, - ) - } - - // Support year - //filter.year?.let { - // append("&release=") - // append(filter.year) - //} - - // Support author - //filter.author?.let { - // append("&author=") - // append(filter.author) - //} - - // Support artist - //filter.artist?.let { - // append("&artist=") - // append(filter.artist) - //} - - - append("&m_orderby=") - when (order) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("latest") - SortOrder.NEWEST -> append("new-manga") - SortOrder.ALPHABETICAL -> append("alphabet") - SortOrder.RATING -> append("rating") - // SortOrder.RELEVANCE -> {} - else -> append("latest") - } + filter.states.forEach { + append("&status[]=") + when (it) { + MangaState.ONGOING -> append("on-going") + MangaState.FINISHED -> append("end") + MangaState.ABANDONED -> append("canceled") + MangaState.PAUSED -> append("on-hold") + MangaState.UPCOMING -> append("upcoming") } } + + filter.contentRating.oneOrThrowIfMany()?.let { + append("&adult=") + append( + when (it) { + ContentRating.SAFE -> "0" + ContentRating.ADULT -> "1" + else -> "" + }, + ) + } + + if (filter.year != 0) { + append("&release=") + append(filter.year.toString()) + } + + // Support author + //filter.author?.let { + // append("&author=") + // append(filter.author) + //} + + // Support artist + //filter.artist?.let { + // append("&artist=") + // append(filter.artist) + //} + + + append("&m_orderby=") + when (order) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("new-manga") + SortOrder.ALPHABETICAL -> append("alphabet") + SortOrder.RATING -> append("rating") + SortOrder.RELEVANCE -> {} + else -> {} + } } return parseMangaList(webClient.httpGet(url).parseHtml()) } else { @@ -312,158 +303,144 @@ internal abstract class MadaraParser( payload["page"] = page.toString() - when { + filter.query?.let { + payload["vars[s]"] = filter.query.urlEncoded() + } - !filter.query.isNullOrEmpty() -> { - payload["vars[s]"] = filter.query.urlEncoded() + if (filter.tags.isNotEmpty()) { + payload["vars[tax_query][0][taxonomy]"] = "wp-manga-genre" + payload["vars[tax_query][0][field]"] = "slug" + filter.tags.forEachIndexed { i, it -> + payload["vars[tax_query][0][terms][$i]"] = it.key } + payload["vars[tax_query][0][operator]"] = "IN" + } - else -> { + if (filter.tagsExclude.isNotEmpty()) { + payload["vars[tax_query][1][taxonomy]"] = "wp-manga-genre" + payload["vars[tax_query][1][field]"] = "slug" + filter.tagsExclude.forEachIndexed { i, it -> + payload["vars[tax_query][1][terms][$i]"] = it.key + } + payload["vars[tax_query][1][operator]"] = "NOT IN" + } + if (filter.year != 0) { + payload["vars[tax_query][2][taxonomy]"] = "wp-manga-release" + payload["vars[tax_query][2][field]"] = "slug" + payload["vars[tax_query][2][terms][]"] = filter.year.toString() + } - // Support query - // filter.query.let { - // payload["vars[s]"] = filter.query.urlEncoded() - // } + // Support author + // filter.author.let { + // payload["vars[tax_query][3][taxonomy]"] = "wp-manga-author" + // payload["vars[tax_query][3][field]"] = "name" + // payload["vars[tax_query][3][terms][0]"] = filter.author + // payload["vars[tax_query][3][operator]"] = "IN" + //} + + + // Support artist + // filter.artist.let { + // payload["vars[tax_query][4][taxonomy]"] = "wp-manga-artist" + // payload["vars[tax_query][4][field]"] = "name" + // payload["vars[tax_query][4][terms][0]"] = filter.artist + // payload["vars[tax_query][4][operator]"] = "IN" + //} + + if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty() || filter.year != 0) { + payload["vars[tax_query][relation]"] = "AND" + } - if (filter.tags.isNotEmpty()) { - payload["vars[tax_query][0][taxonomy]"] = "wp-manga-genre" - payload["vars[tax_query][0][field]"] = "slug" - filter.tags.forEachIndexed { i, it -> - payload["vars[tax_query][0][terms][$i]"] = it.key - } - payload["vars[tax_query][0][operator]"] = "IN" - } + when (order) { + SortOrder.POPULARITY -> { + payload["vars[meta_key]"] = "_wp_manga_views" + payload["vars[orderby]"] = "meta_value_num" + payload["vars[order]"] = "desc" + } - if (filter.tagsExclude.isNotEmpty()) { - payload["vars[tax_query][1][taxonomy]"] = "wp-manga-genre" - payload["vars[tax_query][1][field]"] = "slug" - filter.tagsExclude.forEachIndexed { i, it -> - payload["vars[tax_query][1][terms][$i]"] = it.key - } - payload["vars[tax_query][1][operator]"] = "NOT IN" - } + SortOrder.POPULARITY_ASC -> { + payload["vars[meta_key]"] = "_wp_manga_views" + payload["vars[orderby]"] = "meta_value_num" + payload["vars[order]"] = "asc" + } - // Support year - //filter.year?.let { - // payload["vars[tax_query][2][taxonomy]"] = wp-manga-release - // payload["vars[tax_query][2][field]"] = slug - // payload["vars[tax_query][2][terms][]"] = filter.year - //} - - // Support author - // filter.author.let { - // payload["vars[tax_query][3][taxonomy]"] = "wp-manga-author" - // payload["vars[tax_query][3][field]"] = "name" - // payload["vars[tax_query][3][terms][0]"] = filter.author - // payload["vars[tax_query][3][operator]"] = "IN" - //} - - - // Support artist - // filter.artist.let { - // payload["vars[tax_query][4][taxonomy]"] = "wp-manga-artist" - // payload["vars[tax_query][4][field]"] = "name" - // payload["vars[tax_query][4][terms][0]"] = filter.artist - // payload["vars[tax_query][4][operator]"] = "IN" - //} - - /// for add filter.year need to add || filter.year - if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty()) { - payload["vars[tax_query][relation]"] = "AND" - } + SortOrder.UPDATED -> { + payload["vars[meta_key]"] = "_latest_update" + payload["vars[orderby]"] = "meta_value_num" + payload["vars[order]"] = "desc" + } - when (order) { - SortOrder.POPULARITY -> { - payload["vars[meta_key]"] = "_wp_manga_views" - payload["vars[orderby]"] = "meta_value_num" - payload["vars[order]"] = "desc" - } - - SortOrder.POPULARITY_ASC -> { - payload["vars[meta_key]"] = "_wp_manga_views" - payload["vars[orderby]"] = "meta_value_num" - payload["vars[order]"] = "asc" - } - - SortOrder.UPDATED -> { - payload["vars[meta_key]"] = "_latest_update" - payload["vars[orderby]"] = "meta_value_num" - payload["vars[order]"] = "desc" - } - - SortOrder.UPDATED_ASC -> { - payload["vars[meta_key]"] = "_latest_update" - payload["vars[orderby]"] = "meta_value_num" - payload["vars[order]"] = "asc" - } - - SortOrder.NEWEST -> { - payload["vars[orderby]"] = "date" - payload["vars[order]"] = "desc" - } - - SortOrder.NEWEST_ASC -> { - payload["vars[orderby]"] = "date" - payload["vars[order]"] = "asc" - } - - SortOrder.ALPHABETICAL -> { - payload["vars[orderby]"] = "post_title" - payload["vars[order]"] = "asc" - } - - SortOrder.ALPHABETICAL_DESC -> { - payload["vars[orderby]"] = "post_title" - payload["vars[order]"] = "desc" - } - - SortOrder.RATING -> { - payload["vars[meta_query][0][query_avarage_reviews][key]"] = "_manga_avarage_reviews" - payload["vars[meta_query][0][query_total_reviews][key]"] = "_manga_total_votes" - - payload["vars[orderby][query_avarage_reviews]"] = "DESC" - payload["vars[orderby][query_total_reviews]"] = "DESC" - } - - SortOrder.RATING_ASC -> { - payload["vars[meta_query][0][query_avarage_reviews][key]"] = "_manga_avarage_reviews" - payload["vars[meta_query][0][query_total_reviews][key]"] = "_manga_total_votes" - - payload["vars[orderby][query_avarage_reviews]"] = "ASC" - payload["vars[orderby][query_total_reviews]"] = "ASC" - } - - // SortOrder.RELEVANCE -> { - // payload["vars[orderby]"] = "" - // } - - else -> payload["vars[meta_key]"] = "_latest_update" - } + SortOrder.UPDATED_ASC -> { + payload["vars[meta_key]"] = "_latest_update" + payload["vars[orderby]"] = "meta_value_num" + payload["vars[order]"] = "asc" + } + + SortOrder.NEWEST -> { + payload["vars[orderby]"] = "date" + payload["vars[order]"] = "desc" + } + + SortOrder.NEWEST_ASC -> { + payload["vars[orderby]"] = "date" + payload["vars[order]"] = "asc" + } + + SortOrder.ALPHABETICAL -> { + payload["vars[orderby]"] = "post_title" + payload["vars[order]"] = "asc" + } + + SortOrder.ALPHABETICAL_DESC -> { + payload["vars[orderby]"] = "post_title" + payload["vars[order]"] = "desc" + } + + SortOrder.RATING -> { + payload["vars[meta_query][0][query_avarage_reviews][key]"] = "_manga_avarage_reviews" + payload["vars[meta_query][0][query_total_reviews][key]"] = "_manga_total_votes" + + payload["vars[orderby][query_avarage_reviews]"] = "DESC" + payload["vars[orderby][query_total_reviews]"] = "DESC" + } + + SortOrder.RATING_ASC -> { + payload["vars[meta_query][0][query_avarage_reviews][key]"] = "_manga_avarage_reviews" + payload["vars[meta_query][0][query_total_reviews][key]"] = "_manga_total_votes" - filter.states.forEach { - payload["vars[meta_query][0][0][key]"] = "_wp_manga_status" - payload["vars[meta_query][0][0][compare]"] = "IN" - payload["vars[meta_query][0][0][value][]"] = - when (it) { - MangaState.ONGOING -> "on-going" - MangaState.FINISHED -> "end" - MangaState.ABANDONED -> "canceled" - MangaState.PAUSED -> "on-hold" - MangaState.UPCOMING -> "upcoming" - } + payload["vars[orderby][query_avarage_reviews]"] = "ASC" + payload["vars[orderby][query_total_reviews]"] = "ASC" + } + + SortOrder.RELEVANCE -> { + payload["vars[orderby]"] = "" + } + + else -> payload["vars[orderby]"] = "" + } + + filter.states.forEach { + payload["vars[meta_query][0][0][key]"] = "_wp_manga_status" + payload["vars[meta_query][0][0][compare]"] = "IN" + payload["vars[meta_query][0][0][value][]"] = + when (it) { + MangaState.ONGOING -> "on-going" + MangaState.FINISHED -> "end" + MangaState.ABANDONED -> "canceled" + MangaState.PAUSED -> "on-hold" + MangaState.UPCOMING -> "upcoming" } + } - filter.contentRating.oneOrThrowIfMany()?.let { - payload["vars[meta_query][0][1][key]"] = "manga_adult_content" - payload["vars[meta_query][0][1][value]"] = - when (it) { - ContentRating.SAFE -> "" - ContentRating.ADULT -> "a%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22yes%22%3B%7D" - else -> "" - } + filter.contentRating.oneOrThrowIfMany()?.let { + payload["vars[meta_query][0][1][key]"] = "manga_adult_content" + payload["vars[meta_query][0][1][value]"] = + when (it) { + ContentRating.SAFE -> "" + ContentRating.ADULT -> "a%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22yes%22%3B%7D" + else -> "" } - } } return parseMangaList( @@ -749,12 +726,14 @@ internal abstract class MadaraParser( val d = date?.lowercase() ?: return 0 return when { - WordSet(" ago", "atrás", " hace", " publicado"," назад", " önce", " trước", "مضت", - " h", " d", " días", " jour", " horas", " heure", " mins", " minutos", " minute", " mois").endsWith(d) -> { + WordSet( + " ago", "atrás", " hace", " publicado", " назад", " önce", " trước", "مضت", + " h", " d", " días", " jour", " horas", " heure", " mins", " minutos", " minute", " mois", + ).endsWith(d) -> { parseRelativeDate(d) } - WordSet("há ", "منذ", "il y a" ).startsWith(d) -> { + WordSet("há ", "منذ", "il y a").startsWith(d) -> { parseRelativeDate(d) } @@ -805,16 +784,22 @@ internal abstract class MadaraParser( return when { WordSet("detik", "segundo", "second", "ثوان") .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("menit", "dakika", "min", "minute", "minutes", "minuto", "mins", "phút", "минут", "دقيقة") .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h", "ساعات", "ساعة") .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("hari", "gün", "jour", "día", "dia", "day", "days", "d", "день") .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("month", "months", "أشهر", "mois") .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year") .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 } } 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 53daf2cd6..23e563e26 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 @@ -274,7 +274,9 @@ internal abstract class MadthemeParser( val d = date?.lowercase() ?: return 0 return when { - WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) } + WordSet(" ago", " h", " d").endsWith(d) -> { + parseRelativeDate(d) + } WordSet("today").startsWith(d) -> { Calendar.getInstance().apply { @@ -303,16 +305,22 @@ internal abstract class MadthemeParser( return when { WordSet("second") .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("min", "minute", "minutes") .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hour", "hours", "h") .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("day", "days") .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("month", "months") .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year") .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/zh/Hanman18.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/zh/Hanman18.kt index 555470874..4015a8853 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/zh/Hanman18.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/zh/Hanman18.kt @@ -18,7 +18,7 @@ internal class Hanman18(context: MangaLoaderContext) : Manga18Parser(context, MangaParserSource.HANMAN18, "hanman18.com") { override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = emptySet() + availableTags = emptySet(), ) override suspend fun getChapters(doc: Document): List { 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 892e4b6d7..d51efd2f2 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 @@ -265,7 +265,9 @@ internal abstract class MangaboxParser( protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { val d = date?.lowercase() ?: return 0 return when { - WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) } + WordSet(" ago", " h", " d").endsWith(d) -> { + parseRelativeDate(d) + } WordSet("today").startsWith(d) -> { Calendar.getInstance().apply { @@ -294,16 +296,22 @@ internal abstract class MangaboxParser( return when { WordSet("second") .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("min", "minute", "minutes") .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hour", "hours", "h") .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("day", "days") .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("month", "months") .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year") .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 } } 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 8c957f0ba..03fbca928 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 @@ -245,18 +245,18 @@ internal abstract class MangaReaderParser( "En cours de publication", "Đang tiến hành", "Em lançamento", "em lançamento", "Em Lançamento", "Онгоінг", "Publishing", "Devam Ediyor", "Em Andamento", "In Corso", "Güncel", "Berjalan", "Продолжается", "Updating", "Lançando", "In Arrivo", "Emision", "En emision", "مستمر", "Curso", "En marcha", "Publicandose", "Publicando", "连载中", "Devam ediyor", "Devam Etmekte", - -> MangaState.ONGOING + -> MangaState.ONGOING "Completed", "Completo", "Complété", "Fini", "Achevé", "Terminé", "Terminé ⚫", "Tamamlandı", "Đã hoàn thành", "Hoàn Thành", "مكتملة", "Завершено", "Finished", "Finalizado", "Completata", "One-Shot", "Bitti", "Tamat", "Completado", "Concluído", "Concluido", "已完结", "Bitmiş", - -> MangaState.FINISHED + -> MangaState.FINISHED "Canceled", "Cancelled", "Cancelado", "cancellato", "Cancelados", "Dropped", "Discontinued", "abandonné", "Abandonné", - -> MangaState.ABANDONED + -> MangaState.ABANDONED "Hiatus", "On Hold", "Pausado", "En espera", "En pause", "En Pause", "En attente", - -> MangaState.PAUSED + -> MangaState.PAUSED else -> null } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/AduManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/AduManga.kt index 50ef33ab2..0c43641f3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/AduManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/AduManga.kt @@ -9,12 +9,12 @@ import java.util.* @MangaSourceParser("ADUMANGA", "AduManga", "tr") internal class AduManga(context: MangaLoaderContext) : - MangaReaderParser(context, MangaParserSource.ADUMANGA, "adumanga.com", pageSize = 20, searchPageSize = 10) { + MangaReaderParser(context, MangaParserSource.ADUMANGA, "adumanga.com", pageSize = 20, searchPageSize = 10) { - override val sourceLocale: Locale = Locale.ENGLISH + override val sourceLocale: Locale = Locale.ENGLISH - override val filterCapabilities: MangaListFilterCapabilities - get() = super.filterCapabilities.copy( - isTagsExclusionSupported = false, - ) + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isTagsExclusionSupported = false, + ) } 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 b5263435f..1b8a52295 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 @@ -104,13 +104,13 @@ internal 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, ) @@ -143,30 +143,30 @@ internal 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/nepnep/NepnepParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt index 90d99dea4..e10176ab9 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 @@ -186,17 +186,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/otakusanctuary/OtakuSanctuaryParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt index 8cf82798f..b0f198356 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 @@ -243,8 +243,14 @@ internal abstract class OtakuSanctuaryParser( protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { val d = date?.lowercase() ?: return 0 return when { - WordSet(" ago", " atrás").endsWith(d) -> { parseRelativeDate(d) } - WordSet("cách đây ").startsWith(d) -> { parseRelativeDate(d) } + WordSet(" ago", " atrás").endsWith(d) -> { + parseRelativeDate(d) + } + + WordSet("cách đây ").startsWith(d) -> { + parseRelativeDate(d) + } + else -> dateFormat.tryParse(date) } } @@ -255,16 +261,22 @@ internal abstract class OtakuSanctuaryParser( return when { WordSet("second", "giây") .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("min", "minute", "minutes", "phút") .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("tiếng", "hour", "hours") .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("day", "days", "d", "ngày") .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("month", "months") .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year") .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/fr/MangaFr.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/fr/MangaFr.kt index b76c1c9bc..021c440e9 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/fr/MangaFr.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/fr/MangaFr.kt @@ -13,8 +13,9 @@ internal class MangaFr(context: MangaLoaderContext) : override val listUrl = "/series" override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = emptySet() + availableTags = emptySet(), ) + override suspend fun getDetails(manga: Manga): Manga { val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val dateFormat = SimpleDateFormat("MM-dd-yyyy", sourceLocale) 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 aec5ffc37..138bb63c0 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 @@ -280,7 +280,9 @@ internal abstract class WpComicsParser( val d = date?.lowercase() ?: return 0 return when { - WordSet(" ago", " trước").endsWith(d) -> { parseRelativeDate(d) } + WordSet(" ago", " trước").endsWith(d) -> { + parseRelativeDate(d) + } WordSet("today").startsWith(d) -> { Calendar.getInstance().apply { @@ -309,14 +311,19 @@ internal abstract class WpComicsParser( return when { WordSet("second", "giây") .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("min", "minute", "minutes", "mins", "phút") .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h", "giờ") .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("day", "days", "d", "ngày") .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("month", "months", "tháng") .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year", "năm").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis else -> 0 } 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 ef6db3e42..475c00222 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 @@ -252,7 +252,9 @@ internal abstract class ZMangaParser( protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { val d = date?.lowercase() ?: return 0 return when { - WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) } + WordSet(" ago", " h", " d").endsWith(d) -> { + parseRelativeDate(d) + } WordSet("today").startsWith(d) -> { Calendar.getInstance().apply { @@ -282,15 +284,20 @@ internal abstract class ZMangaParser( return when { WordSet("second") .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("min", "minute", "minutes") .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hour", "hours", "h") .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("day", "days").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis WordSet("month", "months") .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year") .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 } }