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 c85659a5..76170f9c 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 bfa0ff4b..a2fc3f5d 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 de3bffed..4c61d6fc 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 1f3d6802..3c75e13e 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 f795b153..9fda0868 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 f0c440e1..6d1590f7 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 48fe2eaf..c710c568 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 11edcc74..c237ceb9 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 3b5fb7da..834700e3 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 276cdba7..dfd5b392 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 1f002180..c5ec0018 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 53daf2cd..23e563e2 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 55547087..4015a885 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 892e4b6d..d51efd2f 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 8c957f0b..03fbca92 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 50ef33ab..0c43641f 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 b5263435..1b8a5229 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 90d99dea..e10176ab 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 8cf82798..b0f19835 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 b76c1c9b..021c440e 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 aec5ffc3..138bb63c 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 ef6db3e4..475c0022 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 } }