Added filters (#8828)
This commit is contained in:
parent
f2d745d540
commit
fb91d67c36
@ -5,7 +5,7 @@ ext {
|
|||||||
extName = 'MangaKatana'
|
extName = 'MangaKatana'
|
||||||
pkgNameSuffix = 'en.mangakatana'
|
pkgNameSuffix = 'en.mangakatana'
|
||||||
extClass = '.MangaKatana'
|
extClass = '.MangaKatana'
|
||||||
extVersionCode = 5
|
extVersionCode = 6
|
||||||
libVersion = '1.2'
|
libVersion = '1.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import androidx.preference.ListPreference
|
|||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
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.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
|
||||||
@ -13,6 +14,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@ -53,36 +55,89 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = "div#book_list > div.item"
|
// Latest
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/page/$page", headers)
|
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 {
|
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
|
||||||
setUrlWithoutDomain(element.select("div.text > h3 > a").attr("href"))
|
setUrlWithoutDomain(element.selectFirst("div.text > h3 > a").attr("href"))
|
||||||
title = element.select("div.text > h3 > a").text()
|
title = element.selectFirst("div.text > h3 > a").ownText()
|
||||||
thumbnail_url = element.select("img").attr("abs:src")
|
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 popularMangaRequest(page: Int) = GET("$baseUrl/manga/page/$page", headers)
|
||||||
|
|
||||||
|
override fun popularMangaSelector() = latestUpdatesSelector()
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
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 searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
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 document = response.asJsoup()
|
||||||
val manga = SManga.create().apply {
|
val manga = SManga.create().apply {
|
||||||
thumbnail_url = parseThumbnail(document)
|
thumbnail_url = parseThumbnail(document)
|
||||||
@ -95,6 +150,8 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Details
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||||
author = document.select(".author").eachText().joinToString()
|
author = document.select(".author").eachText().joinToString()
|
||||||
description = document.select(".summary > p").text() +
|
description = document.select(".summary > p").text() +
|
||||||
@ -112,6 +169,8 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
|||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chapters
|
||||||
|
|
||||||
override fun chapterListSelector() = "tr:has(.chapter)"
|
override fun chapterListSelector() = "tr:has(.chapter)"
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
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
|
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 imageArrayRegex = Regex("""var ytaw=\[([^\[]*)]""")
|
||||||
private val imageUrlRegex = Regex("""'([^']*)'""")
|
private val imageUrlRegex = Regex("""'([^']*)'""")
|
||||||
|
|
||||||
|
// Page List
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
val serverSuffix = preferences.getString(serverPreference, "")?.takeIf { it.isNotBlank() }?.let { "?sv=$it" } ?: ""
|
val serverSuffix = preferences.getString(serverPreference, "")?.takeIf { it.isNotBlank() }?.let { "?sv=$it" } ?: ""
|
||||||
return GET(baseUrl + chapter.url + serverSuffix, headers)
|
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")
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
val serverPref = ListPreference(screen.context).apply {
|
val serverPref = ListPreference(screen.context).apply {
|
||||||
key = "server_preference"
|
key = "server_preference"
|
||||||
@ -162,4 +219,126 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() {
|
|||||||
|
|
||||||
screen.addPreference(serverPref)
|
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