MangaPark v3: Rewrite (#9210)

* Rewrite and Update to v3.3

* Fix build.gradle
This commit is contained in:
FourTOne5 2021-09-26 16:41:29 +06:00 committed by GitHub
parent 2676b70068
commit b8308a3ace
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 430 additions and 297 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'MangaPark v3'
pkgNameSuffix = 'all.mangapark'
extClass = '.MangaParkFactory'
extVersionCode = 9
extVersionCode = 10
containsNsfw = true
}

View File

@ -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<MangasPage> {
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<SortFilter>()!!
val reverseSortFilter = filters.findInstance<ReverseSortFilter>()!!
val statusFilter = filters.findInstance<StatusFilter>()!!
val genreFilter = filters.findInstance<GenreGroupFilter>()!!
val minChapterFilter = filters.findInstance<MinChapterTextFilter>()!!
val maxChapterFilter = filters.findInstance<MaxChapterTextFilter>()!!
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<MangasPage> {
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<MangasPage> {
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<MangasPage> {
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<Page> {
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<SelectFilterOption>, default: Int = 0) : Filter.Select<String>(name, options.map { it.name }.toTypedArray(), default) {
val selected: String
get() = options[state].value
}
abstract class CheckboxGroupFilter(name: String, options: List<CheckboxFilterOption>) : Filter.Group<CheckboxFilterOption>(name, options) {
val selected: List<String>
get() = state.filter { it.state }.map { it.value }
}
abstract class TriStateGroupFilter(name: String, options: List<TriStateFilterOption>) : Filter.Group<TriStateFilterOption>(name, options) {
val included: List<String>
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<String>
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<SelectFilterOption>, default: Int) : SelectFilter("Sort By", options, default)
class ReverseSortFilter(default: Boolean = false) : Filter.CheckBox("Revers Sort", default)
class StatusFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Status", options, default)
class GenreGroupFilter(options: List<TriStateFilterOption>) : 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 <reified T> 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"
}
}

View File

@ -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<String>()
val excludedGenre = mutableListOf<String>()
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<SortItem> = 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<SortItem>) :
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<PublicationItem> = listOf(
PublicationItem("All", ""),
PublicationItem("Pending", "pending"),
PublicationItem("Ongoing", "ongoing"),
PublicationItem("Completed", "completed"),
PublicationItem("Hiatus", "hiatus"),
PublicationItem("Cancelled", "cancelled"),
)
class PublicationFilter(
name: String,
statusList: List<PublicationItem>,
defaultStatusIndex: Int
) :
Filter.Select<String>(
name,
statusList.map { it.name }.toTypedArray(),
defaultStatusIndex
)
//Type
class TypeItem(name: String, val value: String) : Filter.TriState(name)
private val typeList: List<TypeItem> = 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<TypeItem>) :
Filter.Group<TypeItem>(name, typeList)
//Demographic
class DemographicItem(name: String, val value: String) : Filter.TriState(name)
private val demographicList: List<DemographicItem> = listOf(
DemographicItem("Shounen", "shounen"),
DemographicItem("Shoujo", "shoujo"),
DemographicItem("Seinen", "seinen"),
DemographicItem("Josei", "josei"),
)
class DemographicFilter(name: String, demographicList: List<DemographicItem>) :
Filter.Group<DemographicItem>(name, demographicList)
// Content
class ContentItem(name: String, val value: String) : Filter.TriState(name)
private val contentList: List<ContentItem> = 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<ContentItem>) :
Filter.Group<ContentItem>(name, contentList)
// Genre
class GenreItem(name: String, val value: String) : Filter.TriState(name)
private val genreList: List<GenreItem> = 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<GenreItem>) :
Filter.Group<GenreItem>(name, genreList)
}