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'
|
||||
pkgNameSuffix = 'all.mangapark'
|
||||
extClass = '.MangaParkFactory'
|
||||
extVersionCode = 9
|
||||
extVersionCode = 10
|
||||
containsNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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