From b8308a3ace6233bd7f96c85ed0419d8b8d1a0f4c Mon Sep 17 00:00:00 2001 From: FourTOne5 <59261191+FourTOne5@users.noreply.github.com> Date: Sun, 26 Sep 2021 16:41:29 +0600 Subject: [PATCH] MangaPark v3: Rewrite (#9210) * Rewrite and Update to v3.3 * Fix build.gradle --- src/all/mangapark/build.gradle | 2 +- .../extension/all/mangapark/MangaPark.kt | 422 ++++++------------ .../all/mangapark/MangaParkFilters.kt | 303 +++++++++++++ 3 files changed, 430 insertions(+), 297 deletions(-) create mode 100644 src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkFilters.kt diff --git a/src/all/mangapark/build.gradle b/src/all/mangapark/build.gradle index 2bf2e838c..8ecae5803 100644 --- a/src/all/mangapark/build.gradle +++ b/src/all/mangapark/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'MangaPark v3' pkgNameSuffix = 'all.mangapark' extClass = '.MangaParkFactory' - extVersionCode = 9 + extVersionCode = 10 containsNsfw = true } diff --git a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaPark.kt b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaPark.kt index 72ece4814..c0ed81033 100644 --- a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaPark.kt +++ b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaPark.kt @@ -4,7 +4,6 @@ import com.squareup.duktape.Duktape import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page @@ -18,7 +17,7 @@ import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request @@ -30,6 +29,7 @@ import org.jsoup.nodes.Element import rx.Observable import uy.kohesive.injekt.injectLazy import java.util.Calendar +import java.util.Locale import java.util.concurrent.TimeUnit open class MangaPark( @@ -45,20 +45,20 @@ open class MangaPark( private val json: Json by injectLazy() + private val mpFilters = MangaParkFilters() + override val client: OkHttpClient = network.cloudflareClient.newBuilder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build() - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/browse?sort=update&page=$page") - } + // Site Browse Helper + private fun browseMangaSelector(): String = "div#subject-list div.col" - override fun latestUpdatesSelector(): String { - return "div#subject-list div.col" - } + private fun browseNextPageSelector(): String = + "div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)" - override fun latestUpdatesFromElement(element: Element): SManga { + private fun browseMangaFromElement(element: Element): SManga { return SManga.create().apply { setUrlWithoutDomain(element.select("a.fw-bold").attr("href")) title = element.select("a.fw-bold").text() @@ -66,146 +66,146 @@ open class MangaPark( } } - override fun latestUpdatesNextPageSelector() = "div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)" + // Latest + override fun latestUpdatesRequest(page: Int): Request = + GET("$baseUrl/browse?sort=update&page=$page") - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/browse?sort=d007&page=$page") - } + override fun latestUpdatesSelector(): String = browseMangaSelector() - override fun popularMangaSelector() = latestUpdatesSelector() + override fun latestUpdatesNextPageSelector(): String = browseNextPageSelector() - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) + override fun latestUpdatesFromElement(element: Element): SManga = + browseMangaFromElement(element) - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() + // Popular + override fun popularMangaRequest(page: Int): Request = + GET("$baseUrl/browse?sort=d007&page=$page") + override fun popularMangaSelector(): String = browseMangaSelector() + + override fun popularMangaNextPageSelector(): String = browseNextPageSelector() + + override fun popularMangaFromElement(element: Element): SManga = + browseMangaFromElement(element) + + + // Search override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { return when { - query.startsWith(PREFIX_ID_SEARCH) -> { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(GET("$baseUrl/comic/$id", headers)).asObservableSuccess() - .map { response -> - mangaFromID(response, id) - } - } - - query.isNotBlank() -> { - val url = "$baseUrl/search?word=$query&page=$page" - client.newCall(GET(url, headers)).asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - - else -> { - val sortFilter = filters.findInstance()!! - val reverseSortFilter = filters.findInstance()!! - val statusFilter = filters.findInstance()!! - val genreFilter = filters.findInstance()!! - val minChapterFilter = filters.findInstance()!! - val maxChapterFilter = filters.findInstance()!! - val url = "$baseUrl/browse".toHttpUrlOrNull()!!.newBuilder() - url.addQueryParameter("page", page.toString()) - - with(sortFilter) { - if (reverseSortFilter.state) { - url.addQueryParameter("sort", "${this.selected}.az") - } else { - url.addQueryParameter("sort", "${this.selected}.za") - } - } - - with(genreFilter) { - url.addQueryParameter( - "genres", included.joinToString(",") + "|" + excluded.joinToString(",") - ) - } - - with(statusFilter) { - url.addQueryParameter("release", this.selected) - } - - if (maxChapterFilter.state.isNotEmpty() or minChapterFilter.state.isNotEmpty()) { - url.addQueryParameter("chapters", minChapterFilter.state + "-" + maxChapterFilter.state) - } - - client.newCall(GET(url.build().toString(), headers)).asObservableSuccess() - .map { response -> - genreSearchMangaParse(response) - } - } + query.startsWith(PREFIX_ID_SEARCH) -> fetchSearchIdManga(query) + query.isNotBlank() -> fetchSearchManga(page, query) + else -> fetchGenreSearchManga(page, filters) } } - private fun genreSearchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() + // Search With Manga ID + private fun fetchSearchIdManga(idWithPrefix: String): Observable { + val id = idWithPrefix.removePrefix(PREFIX_ID_SEARCH) + return client.newCall(GET("$baseUrl/comic/$id", headers)) + .asObservableSuccess() + .map { response -> + MangasPage(listOf(mangaDetailsParse(response.asJsoup())), false) + } + } - val mangas = document.select("div#subject-list div.col").map { element -> - searchMangaFromElement(element) + // Search WIth Query + private fun fetchSearchManga(page: Int, query: String): Observable { + return client.newCall(GET("$baseUrl/search?word=$query&page=$page", headers)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response) + } + } + + // Search With Filter + private fun fetchGenreSearchManga(page: Int, filters: FilterList): Observable { + val url = "$baseUrl/browse".toHttpUrl().newBuilder() + .addQueryParameter("page", page.toString()).let { mpFilters.addFiltersToUrl(it, filters) } + + return client.newCall(GET(url, headers)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response) + } + } + + override fun searchMangaSelector(): String = "div#search-list div.col" + + override fun searchMangaNextPageSelector(): String = + "div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)" + + override fun searchMangaFromElement(element: Element): SManga { + return SManga.create().apply { + setUrlWithoutDomain(element.select("a.fw-bold").attr("href")) + title = element.select("a.fw-bold").text() + thumbnail_url = element.select("a.position-relative img").attr("abs:src") + } + } + + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val isBrowse = response.request.url.pathSegments[0] == "browse" + val mangaSelector = if (isBrowse) browseMangaSelector() else searchMangaSelector() + val nextPageSelector = if (isBrowse) browseNextPageSelector() else searchMangaNextPageSelector() + + val mangas = document.select(mangaSelector).map { element -> + if (isBrowse) browseMangaFromElement(element) else searchMangaFromElement(element) } - val hasNextPage = document.select(latestUpdatesNextPageSelector()).first() != null + val hasNextPage = document.select(nextPageSelector).first() != null return MangasPage(mangas, hasNextPage) } - private fun mangaFromID(response: Response, id: String): MangasPage { - val infoElement = response.asJsoup().select("div#mainer div.container-fluid") - val manga = SManga.create().apply { - url = "/comic/$id" - title = infoElement.select("h3.item-title").text() - thumbnail_url = infoElement.select("div.detail-set div.attr-cover img").attr("abs:src") - } - - return MangasPage(listOf(manga), false) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - - override fun searchMangaSelector() = "div#search-list div.col" - - override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - + // Manga Details override fun mangaDetailsParse(document: Document): SManga { val infoElement = document.select("div#mainer div.container-fluid") - val statusStr = infoElement.select("div.attr-item:contains(status) span").text() return SManga.create().apply { + setUrlWithoutDomain(infoElement.select("h3.item-title a").attr("href")) + title = infoElement.select("h3.item-title").text() + description = infoElement.select("div.limit-height-body") .select("h5.text-muted, div.limit-html") - .joinToString("\n\n") { it.text() } + .joinToString("\n\n") { it.text().trim() } + "\n\nAlt. Titles" + infoElement + .select("div.alias-set").text() + .split("/").joinToString(", ") { it.trim() } + author = infoElement.select("div.attr-item:contains(author) a") .joinToString { it.text().trim() } - status = statusStr.parseStatus() + + status = infoElement.select("div.attr-item:contains(status) span") + .text().parseStatus() + thumbnail_url = infoElement.select("div.detail-set div.attr-cover img").attr("abs:src") + genre = infoElement.select("div.attr-item:contains(genres) span span") .joinToString { it.text().trim() } } } - private fun String?.parseStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("Ongoing") -> SManga.ONGOING - this.contains("Hiatus") -> SManga.ONGOING - this.contains("Completed") -> SManga.COMPLETED + private fun String?.parseStatus() = if (this == null) { + SManga.UNKNOWN + } else when { + this.toLowerCase(Locale.US).contains("ongoing") -> SManga.ONGOING + this.toLowerCase(Locale.US).contains("hiatus") -> SManga.ONGOING + this.toLowerCase(Locale.US).contains("completed") -> SManga.COMPLETED else -> SManga.UNKNOWN } override fun chapterListRequest(manga: SManga): Request { - - val url = manga.url - val sid = url.split("/")[2] + val sid = "$baseUrl/${manga.url}".toHttpUrl().pathSegments[1].toInt() val jsonPayload = buildJsonObject { put("lang", siteLang) put("sid", sid) } - val requestBody = jsonPayload.toString().toRequestBody("application/json;charset=UTF-8".toMediaType()) + val requestBody = + jsonPayload.toString().toRequestBody("application/json;charset=UTF-8".toMediaType()) - val refererUrl = "$baseUrl/$url".toHttpUrlOrNull()!!.newBuilder() + val refererUrl = "$baseUrl/${manga.url}".toHttpUrl().newBuilder() .toString() val newHeaders = headersBuilder() .add("Content-Length", requestBody.contentLength().toString()) @@ -229,19 +229,17 @@ open class MangaPark( override fun chapterListSelector() = "div.episode-item" override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a.chapt") - val time = element.select("div.extra > i.ps-2").text() return SChapter.create().apply { name = urlElement.text() - chapter_number = urlElement.attr("href").substringAfterLast("/").toFloat() - if (time != "") { date_upload = time.parseChapterDate() } - setUrlWithoutDomain(urlElement.attr("href")) + date_upload = element.select("div.extra > i.ps-2").text().parseChapterDate() + setUrlWithoutDomain(urlElement.attr("href").removeSuffix("/")) } } - private fun String.parseChapterDate(): Long { + private fun String?.parseChapterDate(): Long { + if (this == null) return 0L val value = this.split(' ')[0].toInt() return when (this.split(' ')[1].removeSuffix("s")) { @@ -267,12 +265,16 @@ open class MangaPark( add(Calendar.YEAR, value * -1) }.timeInMillis else -> { - return 0 + return 0L } } } override fun pageListParse(document: Document): List { + if (document.select("div.wrapper-deleted") != null) { + throw Exception("The chapter content seems to be deleted. Contact the site owner if possible.") + } + val duktape = Duktape.create() val script = document.select("script").html() val imgCdnHost = script.substringAfter("const imgCdnHost = \"").substringBefore("\";") @@ -281,7 +283,8 @@ open class MangaPark( val amPass = script.substringAfter("const amPass = ").substringBefore(";") val amWord = script.substringAfter("const amWord = ").substringBefore(";") - val decryptScript = cryptoJS + "CryptoJS.AES.decrypt($amWord, $amPass).toString(CryptoJS.enc.Utf8);" + val decryptScript = + cryptoJS + "CryptoJS.AES.decrypt($amWord, $amPass).toString(CryptoJS.enc.Utf8);" val imgWordLisRaw = duktape.evaluate(decryptScript).toString() val imgWordLis = json.parseToJsonElement(imgWordLisRaw).jsonArray @@ -295,201 +298,28 @@ open class MangaPark( } private val cryptoJS by lazy { - client.newCall( - GET( - CryptoJSUrl, - headers - ) - ).execute().body!!.string() + client.newCall(GET(CryptoJSUrl, headers)).execute().body!!.string() } - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - override fun getFilterList() = FilterList( - // LetterFilter(), - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - SortFilter(getSortFilter(), 10), - ReverseSortFilter(), - StatusFilter(getStatusFilter(), 0), - MinChapterTextFilter(), - MaxChapterTextFilter(), - GenreGroupFilter(getGenreFilter()), - ) + override fun getFilterList() = mpFilters.getFilterList() - class SelectFilterOption(val name: String, val value: String) - class CheckboxFilterOption(val value: String, name: String, default: Boolean = false) : Filter.CheckBox(name, default) - class TriStateFilterOption(val value: String, name: String, default: Int = 0) : Filter.TriState(name, default) + //Unused Stuff - abstract class SelectFilter(name: String, private val options: List, default: Int = 0) : Filter.Select(name, options.map { it.name }.toTypedArray(), default) { - val selected: String - get() = options[state].value - } - abstract class CheckboxGroupFilter(name: String, options: List) : Filter.Group(name, options) { - val selected: List - get() = state.filter { it.state }.map { it.value } - } - abstract class TriStateGroupFilter(name: String, options: List) : Filter.Group(name, options) { - val included: List - get() = state.filter { it.isIncluded() }.map { it.value } + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + throw UnsupportedOperationException("Not used") - val excluded: List - get() = state.filter { it.isExcluded() }.map { it.value } - } - abstract class TextFilter(name: String) : Filter.Text(name) + override fun imageUrlParse(document: Document): String = + throw UnsupportedOperationException("Not used") - class SortFilter(options: List, default: Int) : SelectFilter("Sort By", options, default) - class ReverseSortFilter(default: Boolean = false) : Filter.CheckBox("Revers Sort", default) - class StatusFilter(options: List, default: Int) : SelectFilter("Status", options, default) - class GenreGroupFilter(options: List) : TriStateGroupFilter("Genre", options) - class MinChapterTextFilter : TextFilter("Min. Chapters") - class MaxChapterTextFilter : TextFilter("Max. Chapters") - private fun getSortFilter() = listOf( - SelectFilterOption("Rating", "rating"), - SelectFilterOption("Comments", "comments"), - SelectFilterOption("Discuss", "discuss"), - SelectFilterOption("Update", "update"), - SelectFilterOption("Create", "create"), - SelectFilterOption("Name", "name"), - SelectFilterOption("Total Views", "d000"), - SelectFilterOption("Most Views 360 days", "d360"), - SelectFilterOption("Most Views 180 days", "d180"), - SelectFilterOption("Most Views 90 days", "d090"), - SelectFilterOption("Most Views 30 days", "d030"), - SelectFilterOption("Most Views 7 days", "d007"), - SelectFilterOption("Most Views 24 hours", "h024"), - SelectFilterOption("Most Views 12 hours", "h012"), - SelectFilterOption("Most Views 6 hours", "h006"), - SelectFilterOption("Most Views 60 minutes", "h001"), - ) - private fun getStatusFilter() = listOf( - SelectFilterOption("All", ""), - SelectFilterOption("Pending", "pending"), - SelectFilterOption("Ongoing", "ongoing"), - SelectFilterOption("Completed", "completed"), - SelectFilterOption("Hiatus", "hiatus"), - SelectFilterOption("Cancelled", "cancelled"), - ) - - private fun getGenreFilter() = listOf( - TriStateFilterOption("artbook", "Artbook"), - TriStateFilterOption("cartoon", "Cartoon"), - TriStateFilterOption("comic", "Comic"), - TriStateFilterOption("doujinshi", "Doujinshi"), - TriStateFilterOption("imageset", "Imageset"), - TriStateFilterOption("manga", "Manga"), - TriStateFilterOption("manhua", "Manhua"), - TriStateFilterOption("manhwa", "Manhwa"), - TriStateFilterOption("webtoon", "Webtoon"), - TriStateFilterOption("western", "Western"), - TriStateFilterOption("josei", "Josei"), - TriStateFilterOption("seinen", "Seinen"), - TriStateFilterOption("shoujo", "Shoujo"), - TriStateFilterOption("shoujo_ai", "Shoujo ai"), - TriStateFilterOption("shounen", "Shounen"), - TriStateFilterOption("shounen_ai", "Shounen ai"), - TriStateFilterOption("yaoi", "Yaoi"), - TriStateFilterOption("yuri", "Yuri"), - TriStateFilterOption("ecchi", "Ecchi"), - TriStateFilterOption("mature", "Mature"), - TriStateFilterOption("adult", "Adult"), - TriStateFilterOption("gore", "Gore"), - TriStateFilterOption("violence", "Violence"), - TriStateFilterOption("smut", "Smut"), - TriStateFilterOption("hentai", "Hentai"), - TriStateFilterOption("_4_koma", "4-Koma"), - TriStateFilterOption("action", "Action"), - TriStateFilterOption("adaptation", "Adaptation"), - TriStateFilterOption("adventure", "Adventure"), - TriStateFilterOption("aliens", "Aliens"), - TriStateFilterOption("animals", "Animals"), - TriStateFilterOption("anthology", "Anthology"), - TriStateFilterOption("cars", "cars"), - TriStateFilterOption("comedy", "Comedy"), - TriStateFilterOption("cooking", "Cooking"), - TriStateFilterOption("crime", "crime"), - TriStateFilterOption("crossdressing", "Crossdressing"), - TriStateFilterOption("delinquents", "Delinquents"), - TriStateFilterOption("dementia", "Dementia"), - TriStateFilterOption("demons", "Demons"), - TriStateFilterOption("drama", "Drama"), - TriStateFilterOption("fantasy", "Fantasy"), - TriStateFilterOption("fan_colored", "Fan-Colored"), - TriStateFilterOption("full_color", "Full Color"), - TriStateFilterOption("game", "Game"), - TriStateFilterOption("gender_bender", "Gender Bender"), - TriStateFilterOption("genderswap", "Genderswap"), - TriStateFilterOption("ghosts", "Ghosts"), - TriStateFilterOption("gyaru", "Gyaru"), - TriStateFilterOption("harem", "Harem"), - TriStateFilterOption("harlequin", "Harlequin"), - TriStateFilterOption("historical", "Historical"), - TriStateFilterOption("horror", "Horror"), - TriStateFilterOption("incest", "Incest"), - TriStateFilterOption("isekai", "Isekai"), - TriStateFilterOption("kids", "Kids"), - TriStateFilterOption("loli", "Loli"), - TriStateFilterOption("lolicon", "lolicon"), - TriStateFilterOption("magic", "Magic"), - TriStateFilterOption("magical_girls", "Magical Girls"), - TriStateFilterOption("martial_arts", "Martial Arts"), - TriStateFilterOption("mecha", "Mecha"), - TriStateFilterOption("medical", "Medical"), - TriStateFilterOption("military", "Military"), - TriStateFilterOption("monster_girls", "Monster Girls"), - TriStateFilterOption("monsters", "Monsters"), - TriStateFilterOption("music", "Music"), - TriStateFilterOption("mystery", "Mystery"), - TriStateFilterOption("netorare", "Netorare/NTR"), - TriStateFilterOption("ninja", "Ninja"), - TriStateFilterOption("office_workers", "Office Workers"), - TriStateFilterOption("oneshot", "Oneshot"), - TriStateFilterOption("parody", "parody"), - TriStateFilterOption("philosophical", "Philosophical"), - TriStateFilterOption("police", "Police"), - TriStateFilterOption("post_apocalyptic", "Post-Apocalyptic"), - TriStateFilterOption("psychological", "Psychological"), - TriStateFilterOption("reincarnation", "Reincarnation"), - TriStateFilterOption("reverse_harem", "Reverse Harem"), - TriStateFilterOption("romance", "Romance"), - TriStateFilterOption("samurai", "Samurai"), - TriStateFilterOption("school_life", "School Life"), - TriStateFilterOption("sci_fi", "Sci-Fi"), - TriStateFilterOption("shota", "Shota"), - TriStateFilterOption("shotacon", "shotacon"), - TriStateFilterOption("slice_of_life", "Slice of Life"), - TriStateFilterOption("sm_bdsm", "SM/BDSM"), - TriStateFilterOption("space", "Space"), - TriStateFilterOption("sports", "Sports"), - TriStateFilterOption("super_power", "Super Power"), - TriStateFilterOption("superhero", "Superhero"), - TriStateFilterOption("supernatural", "Supernatural"), - TriStateFilterOption("survival", "Survival"), - TriStateFilterOption("thriller", "Thriller"), - TriStateFilterOption("time_travel", "Time Travel"), - TriStateFilterOption("traditional_games", "Traditional Games"), - TriStateFilterOption("tragedy", "Tragedy"), - TriStateFilterOption("vampires", "Vampires"), - TriStateFilterOption("video_games", "Video Games"), - TriStateFilterOption("virtual_reality", "Virtual Reality"), - TriStateFilterOption("wuxia", "Wuxia"), - TriStateFilterOption("xianxia", "Xianxia"), - TriStateFilterOption("xuanhuan", "Xuanhuan"), - TriStateFilterOption("zombies", "Zombies"), - // Hidden Genres - TriStateFilterOption("award_winning", "Award Winning"), - TriStateFilterOption("youkai", "Youkai"), - TriStateFilterOption("uncategorized", "Uncategorized") - ) - - private inline fun Iterable<*>.findInstance() = find { it is T } as? T companion object { const val PREFIX_ID_SEARCH = "id:" - const val CryptoJSUrl = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js" + const val CryptoJSUrl = + "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js" } } diff --git a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkFilters.kt b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkFilters.kt new file mode 100644 index 000000000..c825932e5 --- /dev/null +++ b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkFilters.kt @@ -0,0 +1,303 @@ +package eu.kanade.tachiyomi.extension.all.mangapark + +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import okhttp3.HttpUrl + +class MangaParkFilters { + + internal fun getFilterList(): FilterList { + return FilterList( + Filter.Header("NOTE: Ignored if using text search!"), + Filter.Separator(), + SortFilter("Sort By", defaultSort, sortList), + Filter.Separator(), + MinChapterFilter(), + MaxChapterFilter(), + Filter.Separator(), + PublicationFilter("Status", publicationList, 0), + TypeFilter("Type", typeList), + DemographicFilter("Demographic", demographicList), + ContentFilter("Content", contentList), + GenreFilter("Genre", genreList) + ) + } + + internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList): String { + var sort = "rating.za" + var minChap: Int? = null + var maxChap: Int? = null + var publication: String? = null + val includedGenre = mutableListOf() + val excludedGenre = mutableListOf() + + filters.forEach { filter -> + when (filter) { + is SortFilter -> { + val sortType = sortList[filter.state!!.index].value + val sortDirection = if (filter.state!!.ascending) "az" else "za" + sort = "$sortType.$sortDirection" + } + is MinChapterFilter -> { + try { + minChap = filter.state.toInt() + } catch (_: NumberFormatException) { + // Do Nothing + } + } + is MaxChapterFilter -> { + try { + maxChap = filter.state.toInt() + } catch (_: NumberFormatException) { + // Do Nothing + } + } + is PublicationFilter -> { + if (filter.state != 0) { + publication = publicationList[filter.state].value + } + } + is TypeFilter -> { + includedGenre += filter.state.filter { it.isIncluded() } .map { it.value } + excludedGenre += filter.state.filter { it.isExcluded() } .map { it.value } + } + is DemographicFilter -> { + includedGenre += filter.state.filter { it.isIncluded() } .map { it.value } + excludedGenre += filter.state.filter { it.isExcluded() } .map { it.value } + } + is ContentFilter -> { + includedGenre += filter.state.filter { it.isIncluded() } .map { it.value } + excludedGenre += filter.state.filter { it.isExcluded() } .map { it.value } + } + is GenreFilter -> { + includedGenre += filter.state.filter { it.isIncluded() } .map { it.value } + excludedGenre += filter.state.filter { it.isExcluded() } .map { it.value } + } + + } + } + + return url.apply { + if (sort != "rating.za") { + addQueryParameter( + "sort", + sort + ) + } + if (includedGenre.isNotEmpty() || excludedGenre.isNotEmpty()) { + addQueryParameter( + "genres", + includedGenre.joinToString(",") + "|" + excludedGenre.joinToString(",") + ) + } + if (publication != null) { + addQueryParameter( + "release", + publication + ) + } + addQueryParameter( + "chapters", + minMaxToChapter(minChap, maxChap) + ) + }.toString() + + } + + private fun minMaxToChapter(minChap: Int?, maxChap: Int?): String? { + if (minChap == null && maxChap == null) return null + return when { + minChap != null && maxChap == null -> minChap + minChap == null && maxChap != null -> "0-$maxChap" + else -> "$minChap-$maxChap" + }.toString() + } + + // Sort Filter + class SortItem(val name: String, val value: String) + + private val sortList: List = listOf( + SortItem("Rating", "rating"), + SortItem("Comments", "comments"), + SortItem("Discuss", "discuss"), + SortItem("Update", "update"), + SortItem("Create", "create"), + SortItem("Name", "name"), + SortItem("Total Views", "d000"), + SortItem("Most Views 360 days", "d360"), + SortItem("Most Views 180 days", "d180"), + SortItem("Most Views 90 days", "d090"), + SortItem("Most Views 30 days", "d030"), + SortItem("Most Views 7 days", "d007"), + SortItem("Most Views 24 hours", "h024"), + SortItem("Most Views 12 hours", "h012"), + SortItem("Most Views 6 hours", "h006"), + SortItem("Most Views 60 minutes", "h001"), + ) + + class SortDefault(val defaultSortIndex: Int, val ascending: Boolean) + + private val defaultSort: SortDefault = SortDefault(0, false) + + class SortFilter(name: String, default: SortDefault, sorts: List) : + Filter.Sort( + name, + sorts.map { it.name }.toTypedArray(), + Selection(default.defaultSortIndex, default.ascending) + ) + + // Min - Max Chapter Filter + abstract class TextFilter(name: String) : Filter.Text(name) + + class MinChapterFilter : TextFilter("Min. Chapters") + class MaxChapterFilter : TextFilter("Max. Chapters") + + + // Publication Filter + class PublicationItem(val name: String, val value: String) + + private val publicationList: List = listOf( + PublicationItem("All", ""), + PublicationItem("Pending", "pending"), + PublicationItem("Ongoing", "ongoing"), + PublicationItem("Completed", "completed"), + PublicationItem("Hiatus", "hiatus"), + PublicationItem("Cancelled", "cancelled"), + ) + + class PublicationFilter( + name: String, + statusList: List, + defaultStatusIndex: Int + ) : + Filter.Select( + name, + statusList.map { it.name }.toTypedArray(), + defaultStatusIndex + ) + + //Type + class TypeItem(name: String, val value: String) : Filter.TriState(name) + + private val typeList: List = listOf( + TypeItem("Cartoon", "cartoon"), + TypeItem("Comic", "comic"), + TypeItem("Doujinshi", "doujinshi"), + TypeItem("Manga", "manga"), + TypeItem("Manhua", "manhua"), + TypeItem("Manhwa", "manhwa"), + TypeItem("Webtoon", "webtoon"), + ) + + class TypeFilter(name: String, typeList: List) : + Filter.Group(name, typeList) + + //Demographic + class DemographicItem(name: String, val value: String) : Filter.TriState(name) + + private val demographicList: List = listOf( + DemographicItem("Shounen", "shounen"), + DemographicItem("Shoujo", "shoujo"), + DemographicItem("Seinen", "seinen"), + DemographicItem("Josei", "josei"), + ) + + class DemographicFilter(name: String, demographicList: List) : + Filter.Group(name, demographicList) + + // Content + class ContentItem(name: String, val value: String) : Filter.TriState(name) + + private val contentList: List = listOf( + ContentItem("Adult", "adult"), + ContentItem("Ecchi", "ecchi"), + ContentItem("Gore", "gore"), + ContentItem("Hentai", "hentai"), + ContentItem("Mature", "mature"), + ContentItem("Smut", "smut"), + ) + + class ContentFilter(name: String, contentList: List) : + Filter.Group(name, contentList) + + // Genre + class GenreItem(name: String, val value: String) : Filter.TriState(name) + + private val genreList: List = listOf( + GenreItem("Action", "action"), + GenreItem("Adaptation", "adaptation"), + GenreItem("Adventure", "adventure"), + GenreItem("Aliens", "aliens"), + GenreItem("Animals", "animals"), + GenreItem("Anthology", "anthology"), + GenreItem("Award Winning", "award_winning"), // This Is Hidden In Web + GenreItem("Cars", "cars"), + GenreItem("Comedy", "comedy"), + GenreItem("Cooking", "cooking"), + GenreItem("Crime", "crime"), + GenreItem("Crossdressing", "crossdressing"), + GenreItem("Delinquents", "delinquents"), + GenreItem("Dementia", "dementia"), + GenreItem("Demons", "demons"), + GenreItem("Drama", "drama"), + GenreItem("Fantasy", "fantasy"), + GenreItem("Full Color", "full_color"), + GenreItem("Game", "game"), + GenreItem("Gender Bender", "gender_bender"), + GenreItem("Genderswap", "genderswap"), + GenreItem("Gyaru", "gyaru"), + GenreItem("Harem", "harem"), + GenreItem("Historical", "historical"), + GenreItem("Horror", "horror"), + GenreItem("Incest", "incest"), + GenreItem("Isekai", "isekai"), + GenreItem("Kids", "kids"), + GenreItem("Loli", "loli"), + GenreItem("Lolicon", "lolicon"), + GenreItem("Magic", "magic"), + GenreItem("Magical Girls", "magical_girls"), + GenreItem("Martial Arts", "martial_arts"), + GenreItem("Mecha", "mecha"), + GenreItem("Medical", "medical"), + GenreItem("Military", "military"), + GenreItem("Monster Girls", "monster_girls"), + GenreItem("Monsters", "monsters"), + GenreItem("Music", "music"), + GenreItem("Mystery", "mystery"), + GenreItem("Office Workers", "office_workers"), + GenreItem("Oneshot", "oneshot"), + GenreItem("Parody", "parody"), + GenreItem("Philosophical", "philosophical"), + GenreItem("Police", "police"), + GenreItem("Post Apocalyptic", "post_apocalyptic"), + GenreItem("Psychological", "psychological"), + GenreItem("Reincarnation", "reincarnation"), + GenreItem("Romance", "romance"), + GenreItem("Samurai", "samurai"), + GenreItem("School Life", "school_life"), + GenreItem("Sci-fi", "sci_fi"), + GenreItem("Shotacon", "shotacon"), + GenreItem("Shounen Ai", "shounen_ai"), + GenreItem("Shoujo Ai", "shoujo_ai"), + GenreItem("Slice of Life", "slice_of_life"), + GenreItem("Space", "space"), + GenreItem("Sports", "sports"), + GenreItem("Super Power", "super_power"), + GenreItem("Superhero", "superhero"), + GenreItem("Supernatural", "supernatural"), + GenreItem("Survival", "survival"), + GenreItem("Thriller", "thriller"), + GenreItem("Traditional Games", "traditional_games"), + GenreItem("Tragedy", "tragedy"), + GenreItem("Vampires", "vampires"), + GenreItem("Video Games", "video_games"), + GenreItem("Virtual Reality", "virtual_reality"), + GenreItem("Wuxia", "wuxia"), + GenreItem("Yaoi", "yaoi"), + GenreItem("Yuri", "yuri"), + GenreItem("Zombies", "zombies"), + ) + + class GenreFilter(name: String, genreList: List) : + Filter.Group(name, genreList) +}