Added filters (#8828)
This commit is contained in:
parent
f2d745d540
commit
fb91d67c36
@ -5,7 +5,7 @@ ext {
|
||||
extName = 'MangaKatana'
|
||||
pkgNameSuffix = 'en.mangakatana'
|
||||
extClass = '.MangaKatana'
|
||||
extVersionCode = 5
|
||||
extVersionCode = 6
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
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
|
||||
@ -13,6 +14,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@ -53,36 +55,89 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
||||
}
|
||||
}.build()
|
||||
|
||||
override fun latestUpdatesSelector() = "div#book_list > div.item"
|
||||
// Latest
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/page/$page", headers)
|
||||
|
||||
override fun latestUpdatesSelector() = "div#book_list > div.item"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
|
||||
setUrlWithoutDomain(element.select("div.text > h3 > a").attr("href"))
|
||||
title = element.select("div.text > h3 > a").text()
|
||||
thumbnail_url = element.select("img").attr("abs:src")
|
||||
setUrlWithoutDomain(element.selectFirst("div.text > h3 > a").attr("href"))
|
||||
title = element.selectFirst("div.text > h3 > a").ownText()
|
||||
thumbnail_url = element.selectFirst("img").attr("abs:src")
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = ".next.page-numbers"
|
||||
override fun latestUpdatesNextPageSelector() = "a.next.page-numbers"
|
||||
|
||||
override fun popularMangaSelector() = latestUpdatesSelector()
|
||||
// Popular (is actually alphabetical)
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga/page/$page", headers)
|
||||
|
||||
override fun popularMangaSelector() = latestUpdatesSelector()
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
||||
|
||||
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
|
||||
override fun searchMangaSelector() = latestUpdatesSelector()
|
||||
// Search
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/page/$page?search=$query&search_by=book_name", headers)
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||
|
||||
if (query.isNotEmpty()) {
|
||||
val type = filterList.find { it is TypeFilter } as TypeFilter
|
||||
val url = "$baseUrl/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
||||
.addQueryParameter("search", query)
|
||||
.addQueryParameter("search_by", type.toUriPart())
|
||||
return GET(url.toString(), headers)
|
||||
} else {
|
||||
val url = "$baseUrl/manga/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
||||
.addQueryParameter("filter", "1")
|
||||
for (filter in filterList) {
|
||||
when (filter) {
|
||||
is GenreList -> {
|
||||
val includedGenres = mutableListOf<String>()
|
||||
val excludedGenres = mutableListOf<String>()
|
||||
filter.state.forEach {
|
||||
if (it.isIncluded()) {
|
||||
includedGenres.add(it.id)
|
||||
} else if (it.isExcluded()) {
|
||||
excludedGenres.add(it.id)
|
||||
}
|
||||
}
|
||||
if (includedGenres.isNotEmpty()) url.addQueryParameter("include", includedGenres.joinToString("_"))
|
||||
if (excludedGenres.isNotEmpty()) url.addQueryParameter("exclude", excludedGenres.joinToString("_"))
|
||||
}
|
||||
is GenreInclusionMode -> url.addQueryParameter("include_mode", filter.toUriPart())
|
||||
is SortFilter -> url.addQueryParameter("order", filter.toUriPart())
|
||||
is StatusFilter -> {
|
||||
if (filter.toUriPart().isNotEmpty()) {
|
||||
url.addQueryParameter("status", filter.toUriPart())
|
||||
}
|
||||
}
|
||||
is ChaptersFilter -> {
|
||||
when (filter.state.trim()) {
|
||||
"-1" -> url.addQueryParameter("chapters", "e1")
|
||||
"" -> url.addQueryParameter("chapters", "1")
|
||||
else -> url.addQueryParameter("chapters", filter.state.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return GET(url.toString(), headers)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = latestUpdatesSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
||||
|
||||
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
return if (response.request.url.toString().contains("/manga/")) {
|
||||
// If search request redirects to a single manga page, use alternative parsing
|
||||
val pathSegments = response.request.url.pathSegments
|
||||
return if (pathSegments[0] == "manga" && pathSegments[1] != "page") {
|
||||
val document = response.asJsoup()
|
||||
val manga = SManga.create().apply {
|
||||
thumbnail_url = parseThumbnail(document)
|
||||
@ -95,6 +150,8 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
||||
}
|
||||
}
|
||||
|
||||
// Details
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
author = document.select(".author").eachText().joinToString()
|
||||
description = document.select(".summary > p").text() +
|
||||
@ -112,6 +169,8 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
// Chapters
|
||||
|
||||
override fun chapterListSelector() = "tr:has(.chapter)"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
@ -120,15 +179,11 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
||||
date_upload = dateFormat.parse(element.select(".update_time").text())?.time ?: 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
val dateFormat by lazy {
|
||||
SimpleDateFormat("MMM-dd-yyyy", Locale.US)
|
||||
}
|
||||
}
|
||||
|
||||
private val imageArrayRegex = Regex("""var ytaw=\[([^\[]*)]""")
|
||||
private val imageUrlRegex = Regex("""'([^']*)'""")
|
||||
|
||||
// Page List
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
val serverSuffix = preferences.getString(serverPreference, "")?.takeIf { it.isNotBlank() }?.let { "?sv=$it" } ?: ""
|
||||
return GET(baseUrl + chapter.url + serverSuffix, headers)
|
||||
@ -145,6 +200,8 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
||||
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
|
||||
|
||||
// Preferences
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val serverPref = ListPreference(screen.context).apply {
|
||||
key = "server_preference"
|
||||
@ -162,4 +219,126 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
||||
|
||||
screen.addPreference(serverPref)
|
||||
}
|
||||
|
||||
// Filters
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
// MangaKarate does not support genre filtering and text search at the same time
|
||||
Filter.Header("NOTE: Other filters ignored if using text search!"),
|
||||
TypeFilter(),
|
||||
Filter.Separator(),
|
||||
GenreList(genres),
|
||||
GenreInclusionMode(),
|
||||
SortFilter(),
|
||||
StatusFilter(),
|
||||
Filter.Separator(),
|
||||
Filter.Header("Input -1 to search for only oneshots"),
|
||||
ChaptersFilter()
|
||||
)
|
||||
|
||||
private class TypeFilter : UriPartFilter(
|
||||
"Text search by",
|
||||
arrayOf(
|
||||
Pair("Title", "book_name"),
|
||||
Pair("Author", "author")
|
||||
)
|
||||
)
|
||||
|
||||
private class Genre(val id: String, name: String) : Filter.TriState(name)
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
||||
private val genres = listOf(
|
||||
Genre("4-koma", "4 koma"),
|
||||
Genre("action", "Action"),
|
||||
Genre("adult", "Adult"),
|
||||
Genre("adventure", "Adventure"),
|
||||
Genre("artbook", "Artbook"),
|
||||
Genre("award-winning", "Award winning"),
|
||||
Genre("comedy", "Comedy"),
|
||||
Genre("cooking", "Cooking"),
|
||||
Genre("doujinshi", "Doujinshi"),
|
||||
Genre("drama", "Drama"),
|
||||
Genre("ecchi", "Ecchi"),
|
||||
Genre("fantasy", "Fantasy"),
|
||||
Genre("gender-bender", "Gender Bender"),
|
||||
Genre("gore", "Gore"),
|
||||
Genre("harem", "Harem"),
|
||||
Genre("historical", "Historical"),
|
||||
Genre("horror", "Horror"),
|
||||
Genre("isekai", "Isekai"),
|
||||
Genre("josei", "Josei"),
|
||||
Genre("loli", "Loli"),
|
||||
Genre("manhua", "Manhua"),
|
||||
Genre("manhwa", "Manhwa"),
|
||||
Genre("martial-arts", "Martial Arts"),
|
||||
Genre("mecha", "Mecha"),
|
||||
Genre("medical", "Medical"),
|
||||
Genre("music", "Music"),
|
||||
Genre("mystery", "Mystery"),
|
||||
Genre("one-shot", "One shot"),
|
||||
Genre("overpowered-mc", "Overpowered MC"),
|
||||
Genre("psychological", "Psychological"),
|
||||
Genre("reincarnation", "Reincarnation"),
|
||||
Genre("romance", "Romance"),
|
||||
Genre("school-life", "School Life"),
|
||||
Genre("sci-fi", "Sci-fi"),
|
||||
Genre("seinen", "Seinen"),
|
||||
Genre("sexual-violence", "Sexual violence"),
|
||||
Genre("shota", "Shota"),
|
||||
Genre("shoujo", "Shoujo"),
|
||||
Genre("shoujo-ai", "Shoujo Ai"),
|
||||
Genre("shounen", "Shounen"),
|
||||
Genre("shounen-ai", "Shounen Ai"),
|
||||
Genre("slice-of-life", "Slice of Life"),
|
||||
Genre("smut", "Smut"),
|
||||
Genre("sports", "Sports"),
|
||||
Genre("super-power", "Super power"),
|
||||
Genre("supernatural", "Supernatural"),
|
||||
Genre("survival", "Survival"),
|
||||
Genre("time-travel", "Time Travel"),
|
||||
Genre("tragedy", "Tragedy"),
|
||||
Genre("webtoon", "Webtoon"),
|
||||
Genre("yaoi", "Yaoi"),
|
||||
Genre("yuri", "Yuri"),
|
||||
)
|
||||
|
||||
private class GenreInclusionMode : UriPartFilter(
|
||||
"Genre inclusion mode",
|
||||
arrayOf(
|
||||
Pair("And", "and"),
|
||||
Pair("Or", "or")
|
||||
)
|
||||
)
|
||||
|
||||
private class ChaptersFilter : Filter.Text("Minimum Chapters")
|
||||
|
||||
private class SortFilter : UriPartFilter(
|
||||
"Sort by",
|
||||
arrayOf(
|
||||
Pair("Latest update", "latest"),
|
||||
Pair("New manga", "new"),
|
||||
Pair("A-Z", "az"),
|
||||
Pair("Number of chapters", "numc")
|
||||
)
|
||||
)
|
||||
|
||||
private class StatusFilter : UriPartFilter(
|
||||
"Status",
|
||||
arrayOf(
|
||||
Pair("All", ""),
|
||||
Pair("Cancelled", "0"),
|
||||
Pair("Ongoing", "1"),
|
||||
Pair("Completed", "2")
|
||||
)
|
||||
)
|
||||
|
||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
companion object {
|
||||
val dateFormat by lazy {
|
||||
SimpleDateFormat("MMM-dd-yyyy", Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user