From 2f9ebadb08ddd8d64953b0a138a351f985c05cf0 Mon Sep 17 00:00:00 2001 From: KenjieDec <65448230+KenjieDec@users.noreply.github.com> Date: Sat, 27 Jul 2024 12:23:04 +0700 Subject: [PATCH] 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> --- src/en/mangarawclub/build.gradle | 2 +- .../extension/en/mangarawclub/MangaRawClub.kt | 283 ++++++------------ .../en/mangarawclub/MangaRawClubFilters.kt | 78 +++++ 3 files changed, 176 insertions(+), 187 deletions(-) create mode 100644 src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClubFilters.kt diff --git a/src/en/mangarawclub/build.gradle b/src/en/mangarawclub/build.gradle index b4f774df9..5d395ba7c 100644 --- a/src/en/mangarawclub/build.gradle +++ b/src/en/mangarawclub/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'MangaGeko' extClass = '.MangaRawClub' - extVersionCode = 24 + extVersionCode = 25 isNsfw = true } diff --git a/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClub.kt b/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClub.kt index cbaa1049d..7712a6e15 100644 --- a/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClub.kt +++ b/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClub.kt @@ -1,13 +1,11 @@ package eu.kanade.tachiyomi.extension.en.mangarawclub 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.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.FormBody import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient 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) } } + // Popular override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/browse-comics/?results=$page&filter=views", headers) } + // Latest override fun latestUpdatesRequest(page: Int): Request { 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 = mutableListOf() + val tagsExcl: MutableList = mutableListOf() + val genreIncl: MutableList = mutableListOf() + val genreExcl: MutableList = 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 popularMangaSelector() = searchMangaSelector() 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 popularMangaNextPageSelector() = searchMangaNextPageSelector() override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - override fun mangaDetailsParse(document: Document): SManga { - if (document.select(".novel-header").first() == null) { - throw Exception("Page not found") + // Manga from Element + override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { + 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) + + // Details + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + document.selectFirst(".novel-header") ?: throw Exception("Page not found") + + 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 { + append("\n\n$altName ${it.trim()}") + } } - val manga = SManga.create() - val author = document.select(".author a").first()?.attr("title")?.trim() ?: "" - if (author.lowercase(Locale.ROOT) != "updating") { - manga.author = author - } - - var description = document.select(".description").first()?.text() ?: "" - 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() } + genre = document.select(".categories a[href*=genre]").joinToString(", ") { + it.ownText().trim() + .split(" ").joinToString(" ") { word -> + word.lowercase().replaceFirstChar { c -> c.uppercase() } } } - val statusElement = document.select("div.header-stats") - manga.status = when { - statusElement.select("strong.completed").isNotEmpty() -> SManga.COMPLETED - statusElement.select("strong.ongoing").isNotEmpty() -> SManga.ONGOING + 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 } - val coverElement = document.select(".cover img") - manga.thumbnail_url = when { - coverElement.attr("data-src").isNotEmpty() -> coverElement.attr("data-src") - else -> coverElement.attr("src") - } - return manga + thumbnail_url = document.selectFirst(".cover img")?.let { img -> + img.attr("data-src").takeIf { it.isNotEmpty() } ?: img.attr("src") + } ?: thumbnail_url } + // Chapters override fun chapterListSelector() = "ul.chapter-list > li" override fun chapterListRequest(manga: SManga): Request { @@ -111,19 +156,13 @@ class MangaRawClub : ParsedHttpSource() { return GET(url, headers) } - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.select("a").attr("href")) + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + setUrlWithoutDomain(element.select("a").attr("href")) val name = element.select(".chapter-title").text().removeSuffix("-eng-li") - chapter.name = "Chapter $name" - val number = parseChapterNumber(name) - if (number != null) { - chapter.chapter_number = number - } - val date = parseChapterDate(element.select(".chapter-update").attr("datetime")) - chapter.date_upload = date - return chapter + this.name = "Chapter $name" + + date_upload = parseChapterDate(element.select(".chapter-update").attr("datetime")) } private fun parseChapterDate(string: String): Long { @@ -133,140 +172,12 @@ class MangaRawClub : ParsedHttpSource() { ?: runCatching { DATE_FORMATTER_2.parse(date)?.time }.getOrNull() ?: 0L } - private fun parseChapterNumber(string: String): Float? { - if (string.isEmpty()) { - return null - } - return string.split("-")[0].toFloatOrNull() - ?: string.split(".")[0].toFloatOrNull() - } - + // Pages override fun pageListParse(document: Document): List { - val pages = mutableListOf() - document.select(".page-in img[onerror]").forEachIndexed { i, it -> - pages.add(Page(i, imageUrl = it.attr("src"))) + return document.select(".page-in img[onerror]").mapIndexed { i, it -> + Page(i, imageUrl = it.attr("src")) } - return pages } 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) : Filter.Group("Genres+", genres) - - private open class UriPartFilter(displayName: String, val vals: Array>) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } } diff --git a/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClubFilters.kt b/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClubFilters.kt new file mode 100644 index 000000000..5751075c7 --- /dev/null +++ b/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClubFilters.kt @@ -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) : + Filter.Group(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, state: Int = 0) : + Filter.Select(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", +)