MangaGeko: Fix Filters (#4221)

* Fix Filters

* Apply suggestion1

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Apply suggestion2

* Apply Suggetions

- Apply AwkwardPeak7's suggestions

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
KenjieDec 2024-07-27 12:23:04 +07:00 committed by Draff
parent ac57f5e3dd
commit 2f9ebadb08
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
3 changed files with 176 additions and 187 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'MangaGeko' extName = 'MangaGeko'
extClass = '.MangaRawClub' extClass = '.MangaRawClub'
extVersionCode = 24 extVersionCode = 25
isNsfw = true isNsfw = true
} }

View File

@ -1,13 +1,11 @@
package eu.kanade.tachiyomi.extension.en.mangarawclub package eu.kanade.tachiyomi.extension.en.mangarawclub
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
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.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter 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 okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -36,74 +34,121 @@ class MangaRawClub : ParsedHttpSource() {
private val DATE_FORMATTER_2 by lazy { SimpleDateFormat("MMMMM dd, yyyy, h a", Locale.ENGLISH) } private val DATE_FORMATTER_2 by lazy { SimpleDateFormat("MMMMM dd, yyyy, h a", Locale.ENGLISH) }
} }
// Popular
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/browse-comics/?results=$page&filter=views", headers) return GET("$baseUrl/browse-comics/?results=$page&filter=views", headers)
} }
// Latest
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/jumbo/manga/?results=$page", headers) return GET("$baseUrl/jumbo/manga/?results=$page", headers)
} }
// Search
override fun getFilterList(): FilterList = getFilters()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotEmpty()) {
// Query search
val url = "$baseUrl/search/".toHttpUrl().newBuilder()
.addQueryParameter("search", query)
.build()
return GET(url, headers)
}
// Filter search
val url = "$baseUrl/browse-comics/".toHttpUrl().newBuilder().apply {
val tagsIncl: MutableList<String> = mutableListOf()
val tagsExcl: MutableList<String> = mutableListOf()
val genreIncl: MutableList<String> = mutableListOf()
val genreExcl: MutableList<String> = mutableListOf()
filters.forEach { filter ->
when (filter) {
is SelectFilter -> addQueryParameter("filter", filter.vals[filter.state])
is GenreFilter -> {
filter.state.forEach {
when {
it.isIncluded() -> genreIncl.add(it.name)
it.isExcluded() -> genreExcl.add(it.name)
}
}
}
is ChapterFilter -> addQueryParameter("minchap", filter.state)
is TextFilter -> {
if (filter.state.isNotEmpty()) {
filter.state.split(",").filter(String::isNotBlank).map { tag ->
val trimmed = tag.trim()
when {
trimmed.startsWith('-') -> tagsExcl.add(trimmed.removePrefix("-"))
else -> tagsIncl.add(trimmed)
}
}
}
}
else -> {}
}
}
addQueryParameter("results", page.toString())
addQueryParameter("genre_included", genreIncl.joinToString(","))
addQueryParameter("genre_excluded", genreExcl.joinToString(","))
addQueryParameter("tags_include", tagsIncl.joinToString(","))
addQueryParameter("tags_exclude", tagsExcl.joinToString(","))
}.build()
return GET(url, headers)
}
// Selectors
override fun searchMangaSelector() = "ul.novel-list > li.novel-item" override fun searchMangaSelector() = "ul.novel-list > li.novel-item"
override fun popularMangaSelector() = searchMangaSelector() override fun popularMangaSelector() = searchMangaSelector()
override fun latestUpdatesSelector() = "ul.novel-list.chapters > li.novel-item" override fun latestUpdatesSelector() = "ul.novel-list.chapters > li.novel-item"
override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create()
manga.title = element.select(".novel-title").first()?.text() ?: ""
manga.thumbnail_url = element.select(".novel-cover img").attr("abs:data-src")
manga.setUrlWithoutDomain(element.select("a").first()!!.attr("href"))
return manga
}
override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
override fun searchMangaNextPageSelector() = ".paging .mg-pagination-chev:last-child:not(.chev-disabled)" override fun searchMangaNextPageSelector() = ".paging .mg-pagination-chev:last-child:not(.chev-disabled)"
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga { // Manga from Element
if (document.select(".novel-header").first() == null) { override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
throw Exception("Page not found") title = element.selectFirst(".novel-title")!!.ownText()
thumbnail_url = element.select(".novel-cover img").attr("abs:data-src")
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
} }
override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
val manga = SManga.create() // Details
val author = document.select(".author a").first()?.attr("title")?.trim() ?: "" override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
if (author.lowercase(Locale.ROOT) != "updating") { document.selectFirst(".novel-header") ?: throw Exception("Page not found")
manga.author = author
author = document.selectFirst(".author a")?.attr("title")?.trim()?.takeIf { it.lowercase() != "updating" }
description = buildString {
document.selectFirst(".description")?.ownText()?.substringAfter("Summary is")?.trim()?.let {
append(it)
} }
document.selectFirst(".alternative-title")?.ownText()?.trim()?.takeIf { it.isNotEmpty() && it.lowercase() != "updating" }?.let {
var description = document.select(".description").first()?.text() ?: "" append("\n\n$altName ${it.trim()}")
description = description.substringAfter("Summary is").trim()
val otherTitle = document.select(".alternative-title").first()?.text()?.trim() ?: ""
if (otherTitle.isNotEmpty() && otherTitle.lowercase(Locale.ROOT) != "updating") {
description += "\n\n$altName $otherTitle"
}
manga.description = description.trim()
manga.genre = document.select(".categories a[href*=genre]").joinToString(", ") {
it.attr("title").removeSuffix("Genre").trim()
.split(" ").joinToString(" ") { char ->
char.lowercase().replaceFirstChar { c -> c.uppercase() }
} }
} }
val statusElement = document.select("div.header-stats") genre = document.select(".categories a[href*=genre]").joinToString(", ") {
manga.status = when { it.ownText().trim()
statusElement.select("strong.completed").isNotEmpty() -> SManga.COMPLETED .split(" ").joinToString(" ") { word ->
statusElement.select("strong.ongoing").isNotEmpty() -> SManga.ONGOING word.lowercase().replaceFirstChar { c -> c.uppercase() }
}
}
status = when {
document.select("div.header-stats strong.completed").isNotEmpty() -> SManga.COMPLETED
document.select("div.header-stats strong.ongoing").isNotEmpty() -> SManga.ONGOING
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
val coverElement = document.select(".cover img") thumbnail_url = document.selectFirst(".cover img")?.let { img ->
manga.thumbnail_url = when { img.attr("data-src").takeIf { it.isNotEmpty() } ?: img.attr("src")
coverElement.attr("data-src").isNotEmpty() -> coverElement.attr("data-src") } ?: thumbnail_url
else -> coverElement.attr("src")
}
return manga
} }
// Chapters
override fun chapterListSelector() = "ul.chapter-list > li" override fun chapterListSelector() = "ul.chapter-list > li"
override fun chapterListRequest(manga: SManga): Request { override fun chapterListRequest(manga: SManga): Request {
@ -111,19 +156,13 @@ class MangaRawClub : ParsedHttpSource() {
return GET(url, headers) return GET(url, headers)
} }
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
val chapter = SChapter.create() setUrlWithoutDomain(element.select("a").attr("href"))
chapter.setUrlWithoutDomain(element.select("a").attr("href"))
val name = element.select(".chapter-title").text().removeSuffix("-eng-li") val name = element.select(".chapter-title").text().removeSuffix("-eng-li")
chapter.name = "Chapter $name" this.name = "Chapter $name"
val number = parseChapterNumber(name)
if (number != null) { date_upload = parseChapterDate(element.select(".chapter-update").attr("datetime"))
chapter.chapter_number = number
}
val date = parseChapterDate(element.select(".chapter-update").attr("datetime"))
chapter.date_upload = date
return chapter
} }
private fun parseChapterDate(string: String): Long { private fun parseChapterDate(string: String): Long {
@ -133,140 +172,12 @@ class MangaRawClub : ParsedHttpSource() {
?: runCatching { DATE_FORMATTER_2.parse(date)?.time }.getOrNull() ?: 0L ?: runCatching { DATE_FORMATTER_2.parse(date)?.time }.getOrNull() ?: 0L
} }
private fun parseChapterNumber(string: String): Float? { // Pages
if (string.isEmpty()) {
return null
}
return string.split("-")[0].toFloatOrNull()
?: string.split(".")[0].toFloatOrNull()
}
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() return document.select(".page-in img[onerror]").mapIndexed { i, it ->
document.select(".page-in img[onerror]").forEachIndexed { i, it -> Page(i, imageUrl = it.attr("src"))
pages.add(Page(i, imageUrl = it.attr("src")))
} }
return pages
} }
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotEmpty()) {
// Query search
return GET("$baseUrl/search/?search=$query", headers)
}
// Filter search
val url = "$baseUrl/browse-comics/".toHttpUrl().newBuilder()
val requestBody = FormBody.Builder()
url.addQueryParameter("results", page.toString())
filters.forEach { filter ->
when (filter) {
is GenrePairList -> url.addQueryParameter("genre", filter.toUriPart()) // GET
is Order -> url.addQueryParameter("filter", filter.toUriPart()) // GET
is Status -> requestBody.add("status", filter.toUriPart()) // POST
is Action -> requestBody.add("action", filter.toUriPart()) // POST
is GenreList -> { // POST
filter.state.filter { it.state == 1 }.forEach {
requestBody.add("options[]", it.name)
}
}
else -> {}
}
}
return GET(url.build(), headers)
// return POST("$baseUrl/search", headers, requestBody.build()) // csrfmiddlewaretoken required
}
override fun getFilterList() = FilterList(
Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(),
Order(),
GenrePairList(),
// Action(),
// Status(),
// GenreList(getGenreList())
)
private class Action : UriPartFilter(
"Action",
arrayOf(
Pair("All", ""),
Pair("Include", "include"),
Pair("Exclude", "exclude"),
),
)
private class Order : UriPartFilter(
"Order",
arrayOf(
Pair("Random", "Random"),
Pair("Updated", "Updated"),
Pair("New", "New"),
Pair("Views", "views"),
),
)
private class Status : UriPartFilter(
"Status",
arrayOf(
Pair("All", ""),
Pair("Completed", "Completed"),
Pair("Ongoing", "Ongoing"),
),
)
private class GenrePairList : UriPartFilter(
"Genres",
arrayOf(
Pair("All", ""),
Pair("R-18", "R-18"),
Pair("Action", "Action"),
Pair("Adult", "Adult"),
Pair("Adventure", "Adventure"),
Pair("Comedy", "Comedy"),
Pair("Cooking", "Cooking"),
Pair("Doujinshi", "Doujinshi"),
Pair("Drama", "Drama"),
Pair("Ecchi", "Ecchi"),
Pair("Fantasy", "Fantasy"),
Pair("Gender bender", "Gender bender"),
Pair("Harem", "Harem"),
Pair("Historical", "Historical"),
Pair("Horror", "Horror"),
Pair("Isekai", "Isekai"),
Pair("Josei", "Josei"),
Pair("Ladies", "ladies"),
Pair("Manhua", "Manhua"),
Pair("Manhwa", "Manhwa"),
Pair("Martial arts", "Martial arts"),
Pair("Mature", "Mature"),
Pair("Mecha", "Mecha"),
Pair("Medical", "Medical"),
Pair("Mystery", "Mystery"),
Pair("One shot", "One shot"),
Pair("Psychological", "Psychological"),
Pair("Romance", "Romance"),
Pair("School life", "School life"),
Pair("Sci fi", "Sci fi"),
Pair("Seinen", "Seinen"),
Pair("Shoujo", "Shoujo"),
Pair("Shounen", "Shounen"),
Pair("Slice of life", "Slice of life"),
Pair("Sports", "Sports"),
Pair("Supernatural", "Supernatural"),
Pair("Tragedy", "Tragedy"),
Pair("Webtoons", "Webtoons"),
),
)
private class Genre(name: String) : Filter.TriState(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres+", genres)
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
}
} }

View File

@ -0,0 +1,78 @@
package eu.kanade.tachiyomi.extension.en.mangarawclub
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
fun getFilters(): FilterList {
return FilterList(
Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(),
SelectFilter("Sort by", getSortsList),
GenreFilter("Genre", getGenres),
Filter.Separator(),
Filter.Header("Separate tags with commas (,)"),
Filter.Header("Prepend with dash (-) to exclude"),
TextFilter("Tags"),
Filter.Separator(),
ChapterFilter("Minimum Chapter"),
)
}
internal class GenreFilter(name: String, genreList: List<String>) :
Filter.Group<TriFilter>(name, genreList.map { TriFilter(it) })
internal open class TriFilter(name: String) : Filter.TriState(name)
internal open class ChapterFilter(name: String) : Filter.Text(name)
internal open class TextFilter(name: String) : Filter.Text(name)
internal open class SelectFilter(name: String, val vals: List<String>, state: Int = 0) :
Filter.Select<String>(name, vals.map { it }.toTypedArray(), state)
private val getGenres = listOf(
"R-18",
"Action",
"Adult",
"Adventure",
"Comedy",
"Cooking",
"Doujinshi",
"Drama",
"Ecchi",
"Fantasy",
"Gender bender",
"Harem",
"Historical",
"Horror",
"Isekai",
"Josei",
"Ladies",
"Manhua",
"Manhwa",
"Martial arts",
"Mature",
"Mecha",
"Medical",
"Mystery",
"One shot",
"Psychological",
"Romance",
"School life",
"Sci fi",
"Seinen",
"Shoujo",
"Shounen",
"Slice of life",
"Sports",
"Supernatural",
"Tragedy",
"Webtoons",
)
private val getSortsList = listOf(
"Random",
"New",
"Updated",
"Views",
)