MangaPark v3: Rewrite (#9210)
* Rewrite and Update to v3.3 * Fix build.gradle
This commit is contained in:
parent
2676b70068
commit
b8308a3ace
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'MangaPark v3'
|
extName = 'MangaPark v3'
|
||||||
pkgNameSuffix = 'all.mangapark'
|
pkgNameSuffix = 'all.mangapark'
|
||||||
extClass = '.MangaParkFactory'
|
extClass = '.MangaParkFactory'
|
||||||
extVersionCode = 9
|
extVersionCode = 10
|
||||||
containsNsfw = true
|
containsNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import com.squareup.duktape.Duktape
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
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.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
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.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
@ -30,6 +29,7 @@ import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
open class MangaPark(
|
open class MangaPark(
|
||||||
|
@ -45,20 +45,20 @@ open class MangaPark(
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
private val mpFilters = MangaParkFilters()
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
.connectTimeout(10, TimeUnit.SECONDS)
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
// Site Browse Helper
|
||||||
return GET("$baseUrl/browse?sort=update&page=$page")
|
private fun browseMangaSelector(): String = "div#subject-list div.col"
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector(): String {
|
private fun browseNextPageSelector(): String =
|
||||||
return "div#subject-list div.col"
|
"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 {
|
return SManga.create().apply {
|
||||||
setUrlWithoutDomain(element.select("a.fw-bold").attr("href"))
|
setUrlWithoutDomain(element.select("a.fw-bold").attr("href"))
|
||||||
title = element.select("a.fw-bold").text()
|
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 {
|
override fun latestUpdatesSelector(): String = browseMangaSelector()
|
||||||
return GET("$baseUrl/browse?sort=d007&page=$page")
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
return when {
|
return when {
|
||||||
query.startsWith(PREFIX_ID_SEARCH) -> {
|
query.startsWith(PREFIX_ID_SEARCH) -> fetchSearchIdManga(query)
|
||||||
val id = query.removePrefix(PREFIX_ID_SEARCH)
|
query.isNotBlank() -> fetchSearchManga(page, query)
|
||||||
client.newCall(GET("$baseUrl/comic/$id", headers)).asObservableSuccess()
|
else -> fetchGenreSearchManga(page, filters)
|
||||||
.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun genreSearchMangaParse(response: Response): MangasPage {
|
// Search With Manga ID
|
||||||
val document = response.asJsoup()
|
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 ->
|
// Search WIth Query
|
||||||
searchMangaFromElement(element)
|
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)
|
return MangasPage(mangas, hasNextPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mangaFromID(response: Response, id: String): MangasPage {
|
// Manga Details
|
||||||
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()
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
val infoElement = document.select("div#mainer div.container-fluid")
|
val infoElement = document.select("div#mainer div.container-fluid")
|
||||||
val statusStr = infoElement.select("div.attr-item:contains(status) span").text()
|
|
||||||
|
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
|
setUrlWithoutDomain(infoElement.select("h3.item-title a").attr("href"))
|
||||||
|
|
||||||
title = infoElement.select("h3.item-title").text()
|
title = infoElement.select("h3.item-title").text()
|
||||||
|
|
||||||
description = infoElement.select("div.limit-height-body")
|
description = infoElement.select("div.limit-height-body")
|
||||||
.select("h5.text-muted, div.limit-html")
|
.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")
|
author = infoElement.select("div.attr-item:contains(author) a")
|
||||||
.joinToString { it.text().trim() }
|
.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")
|
thumbnail_url = infoElement.select("div.detail-set div.attr-cover img").attr("abs:src")
|
||||||
|
|
||||||
genre = infoElement.select("div.attr-item:contains(genres) span span")
|
genre = infoElement.select("div.attr-item:contains(genres) span span")
|
||||||
.joinToString { it.text().trim() }
|
.joinToString { it.text().trim() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String?.parseStatus() = when {
|
private fun String?.parseStatus() = if (this == null) {
|
||||||
this == null -> SManga.UNKNOWN
|
SManga.UNKNOWN
|
||||||
this.contains("Ongoing") -> SManga.ONGOING
|
} else when {
|
||||||
this.contains("Hiatus") -> SManga.ONGOING
|
this.toLowerCase(Locale.US).contains("ongoing") -> SManga.ONGOING
|
||||||
this.contains("Completed") -> SManga.COMPLETED
|
this.toLowerCase(Locale.US).contains("hiatus") -> SManga.ONGOING
|
||||||
|
this.toLowerCase(Locale.US).contains("completed") -> SManga.COMPLETED
|
||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
|
val sid = "$baseUrl/${manga.url}".toHttpUrl().pathSegments[1].toInt()
|
||||||
val url = manga.url
|
|
||||||
val sid = url.split("/")[2]
|
|
||||||
|
|
||||||
val jsonPayload = buildJsonObject {
|
val jsonPayload = buildJsonObject {
|
||||||
put("lang", siteLang)
|
put("lang", siteLang)
|
||||||
put("sid", sid)
|
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()
|
.toString()
|
||||||
val newHeaders = headersBuilder()
|
val newHeaders = headersBuilder()
|
||||||
.add("Content-Length", requestBody.contentLength().toString())
|
.add("Content-Length", requestBody.contentLength().toString())
|
||||||
|
@ -229,19 +229,17 @@ open class MangaPark(
|
||||||
override fun chapterListSelector() = "div.episode-item"
|
override fun chapterListSelector() = "div.episode-item"
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
|
|
||||||
val urlElement = element.select("a.chapt")
|
val urlElement = element.select("a.chapt")
|
||||||
val time = element.select("div.extra > i.ps-2").text()
|
|
||||||
|
|
||||||
return SChapter.create().apply {
|
return SChapter.create().apply {
|
||||||
name = urlElement.text()
|
name = urlElement.text()
|
||||||
chapter_number = urlElement.attr("href").substringAfterLast("/").toFloat()
|
date_upload = element.select("div.extra > i.ps-2").text().parseChapterDate()
|
||||||
if (time != "") { date_upload = time.parseChapterDate() }
|
setUrlWithoutDomain(urlElement.attr("href").removeSuffix("/"))
|
||||||
setUrlWithoutDomain(urlElement.attr("href"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.parseChapterDate(): Long {
|
private fun String?.parseChapterDate(): Long {
|
||||||
|
if (this == null) return 0L
|
||||||
val value = this.split(' ')[0].toInt()
|
val value = this.split(' ')[0].toInt()
|
||||||
|
|
||||||
return when (this.split(' ')[1].removeSuffix("s")) {
|
return when (this.split(' ')[1].removeSuffix("s")) {
|
||||||
|
@ -267,12 +265,16 @@ open class MangaPark(
|
||||||
add(Calendar.YEAR, value * -1)
|
add(Calendar.YEAR, value * -1)
|
||||||
}.timeInMillis
|
}.timeInMillis
|
||||||
else -> {
|
else -> {
|
||||||
return 0
|
return 0L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
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 duktape = Duktape.create()
|
||||||
val script = document.select("script").html()
|
val script = document.select("script").html()
|
||||||
val imgCdnHost = script.substringAfter("const imgCdnHost = \"").substringBefore("\";")
|
val imgCdnHost = script.substringAfter("const imgCdnHost = \"").substringBefore("\";")
|
||||||
|
@ -281,7 +283,8 @@ open class MangaPark(
|
||||||
val amPass = script.substringAfter("const amPass = ").substringBefore(";")
|
val amPass = script.substringAfter("const amPass = ").substringBefore(";")
|
||||||
val amWord = script.substringAfter("const amWord = ").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 imgWordLisRaw = duktape.evaluate(decryptScript).toString()
|
||||||
val imgWordLis = json.parseToJsonElement(imgWordLisRaw).jsonArray
|
val imgWordLis = json.parseToJsonElement(imgWordLisRaw).jsonArray
|
||||||
|
@ -295,201 +298,28 @@ open class MangaPark(
|
||||||
}
|
}
|
||||||
|
|
||||||
private val cryptoJS by lazy {
|
private val cryptoJS by lazy {
|
||||||
client.newCall(
|
client.newCall(GET(CryptoJSUrl, headers)).execute().body!!.string()
|
||||||
GET(
|
|
||||||
CryptoJSUrl,
|
|
||||||
headers
|
|
||||||
)
|
|
||||||
).execute().body!!.string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = mpFilters.getFilterList()
|
||||||
// LetterFilter(),
|
|
||||||
Filter.Header("NOTE: Ignored if using text search!"),
|
|
||||||
Filter.Separator(),
|
|
||||||
SortFilter(getSortFilter(), 10),
|
|
||||||
ReverseSortFilter(),
|
|
||||||
StatusFilter(getStatusFilter(), 0),
|
|
||||||
MinChapterTextFilter(),
|
|
||||||
MaxChapterTextFilter(),
|
|
||||||
GenreGroupFilter(getGenreFilter()),
|
|
||||||
)
|
|
||||||
|
|
||||||
class SelectFilterOption(val name: String, val value: String)
|
//Unused Stuff
|
||||||
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)
|
|
||||||
|
|
||||||
abstract class SelectFilter(name: String, private val options: List<SelectFilterOption>, default: Int = 0) : Filter.Select<String>(name, options.map { it.name }.toTypedArray(), default) {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
|
||||||
val selected: String
|
throw UnsupportedOperationException("Not used")
|
||||||
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 }
|
|
||||||
|
|
||||||
val excluded: List<String>
|
override fun imageUrlParse(document: Document): String =
|
||||||
get() = state.filter { it.isExcluded() }.map { it.value }
|
throw UnsupportedOperationException("Not used")
|
||||||
}
|
|
||||||
abstract class TextFilter(name: String) : Filter.Text(name)
|
|
||||||
|
|
||||||
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 {
|
companion object {
|
||||||
|
|
||||||
const val PREFIX_ID_SEARCH = "id:"
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue