From ffd98958eefd7894ccd1b5339d96b3e2df4e2ec1 Mon Sep 17 00:00:00 2001 From: Jake Date: Sat, 1 Mar 2025 11:22:58 +0800 Subject: [PATCH] Mangakakalot, Manganato: Updated Domain, Selectors and Filters (#7781) * Mangakakalot - updated domain paths * Mangakakalot - Fixed description query and filters Also fixed certain cases that could result in 403 when opening chapters * Mangakakalot - Updated baseUrl * Manganato - Updated domain, selector, and filters Manganato essentially became an exact copy mangakakalot so all changes (except for URLs) are the same with mangakakalot * review changes, fixed upload date * Replaced duplicated `GET` request logic with `super.imageRequest(page)` to avoid redundancy * review changes, moved `SimpleDateFormat` outside the function --- .../tachiyomi/multisrc/mangabox/MangaBox.kt | 11 +- src/en/mangakakalot/build.gradle | 4 +- .../extension/en/mangakakalot/Mangakakalot.kt | 109 ++++++++++++++++- src/en/manganelo/build.gradle | 4 +- .../extension/en/manganelo/Manganato.kt | 114 ++++++++++++++++-- 5 files changed, 222 insertions(+), 20 deletions(-) diff --git a/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBox.kt b/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBox.kt index 42aecad9f..df3f5165b 100644 --- a/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBox.kt +++ b/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBox.kt @@ -199,7 +199,7 @@ abstract class MangaBox( protected open val alternateChapterDateSelector = String() - private fun Element.selectDateFromElement(): Element { + protected fun Element.selectDateFromElement(): Element { val defaultChapterDateSelector = "span" return this.select(defaultChapterDateSelector).lastOrNull() ?: this.select(alternateChapterDateSelector).last()!! } @@ -305,10 +305,11 @@ abstract class MangaBox( ) } - private class KeywordFilter(vals: Array>) : UriPartFilter("Keyword search ", vals) - private class SortFilter(vals: Array>) : UriPartFilter("Order by", vals) - private class StatusFilter(vals: Array>) : UriPartFilter("Status", vals) - private class GenreFilter(vals: Array>) : UriPartFilter("Category", vals) + // Technically, only Sort, Status, and Genre need to be non-private for Mangakakalot and Manganato, but I'll include Keyword to make it uniform. + protected class KeywordFilter(vals: Array>) : UriPartFilter("Keyword search ", vals) + protected class SortFilter(vals: Array>) : UriPartFilter("Order by", vals) + protected class StatusFilter(vals: Array>) : UriPartFilter("Status", vals) + protected class GenreFilter(vals: Array>) : UriPartFilter("Category", vals) // For advanced search, specifically tri-state genres private class AdvGenreFilter(vals: List) : Filter.Group("Category", vals) diff --git a/src/en/mangakakalot/build.gradle b/src/en/mangakakalot/build.gradle index 18523a010..41d9788ca 100644 --- a/src/en/mangakakalot/build.gradle +++ b/src/en/mangakakalot/build.gradle @@ -2,8 +2,8 @@ ext { extName = 'Mangakakalot' extClass = '.Mangakakalot' themePkg = 'mangabox' - baseUrl = 'https://mangakakalot.com' - overrideVersionCode = 3 + baseUrl = 'https://www.mangakakalot.gg' + overrideVersionCode = 4 } apply from: "$rootDir/common.gradle" diff --git a/src/en/mangakakalot/src/eu/kanade/tachiyomi/extension/en/mangakakalot/Mangakakalot.kt b/src/en/mangakakalot/src/eu/kanade/tachiyomi/extension/en/mangakakalot/Mangakakalot.kt index 93ebc819e..ab998e042 100644 --- a/src/en/mangakakalot/src/eu/kanade/tachiyomi/extension/en/mangakakalot/Mangakakalot.kt +++ b/src/en/mangakakalot/src/eu/kanade/tachiyomi/extension/en/mangakakalot/Mangakakalot.kt @@ -1,10 +1,115 @@ package eu.kanade.tachiyomi.extension.en.mangakakalot import eu.kanade.tachiyomi.multisrc.mangabox.MangaBox +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import org.jsoup.nodes.Element +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Locale -class Mangakakalot : MangaBox("Mangakakalot", "https://mangakakalot.com", "en") { - override fun headersBuilder(): Headers.Builder = super.headersBuilder().set("Referer", "https://manganato.com") // for covers +class Mangakakalot : MangaBox("Mangakakalot", "https://www.mangakakalot.gg", "en") { + private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yyyy HH:mm", Locale.ENGLISH) + + override fun headersBuilder(): Headers.Builder = super.headersBuilder().set("Referer", "$baseUrl/") // for covers + override val popularUrlPath = "manga-list/hot-manga?page=" + override val latestUrlPath = "manga-list/latest-manga?page=" override val simpleQueryPath = "search/story/" override fun searchMangaSelector() = "${super.searchMangaSelector()}, div.list-truyen-item-wrap" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return if (query.isNotBlank() && getAdvancedGenreFilters().isEmpty()) { + val url = "$baseUrl/$simpleQueryPath".toHttpUrl().newBuilder() + .addPathSegment(normalizeSearchQuery(query)) + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, headers) + } else { + val url = "$baseUrl/genre".toHttpUrl().newBuilder() + url.addQueryParameter("page", page.toString()) + filters.forEach { filter -> + when (filter) { + is SortFilter -> url.addQueryParameter("type", filter.toUriPart()) + is StatusFilter -> url.addQueryParameter("state", filter.toUriPart()) + is GenreFilter -> url.addPathSegment(filter.toUriPart()!!) + else -> {} + } + } + + GET(url.build(), headers) + } + } + + override fun chapterFromElement(element: Element): SChapter { + // parse on title attribute rather than the value + val dateUploadAttr: Long? = try { + dateFormat.parse(element.selectDateFromElement().attr("title"))?.time + } catch (e: ParseException) { + null + } + + return super.chapterFromElement(element).apply { + date_upload = dateUploadAttr ?: date_upload + } + } + + override val descriptionSelector = "div#contentBox" + + override fun imageRequest(page: Page): Request { + return if (page.url.contains(baseUrl)) { + GET(page.imageUrl!!, headersBuilder().build()) + } else { // Avoid 403 errors on non-migrated mangas + super.imageRequest(page) + } + } + + override fun getGenreFilters(): Array> = arrayOf( + Pair("all", "ALL"), + 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("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("shoujo-ai", "Shoujo ai"), + Pair("shounen", "Shounen"), + Pair("shounen-ai", "Shounen ai"), + Pair("slice-of-life", "Slice of life"), + Pair("smut", "Smut"), + Pair("sports", "Sports"), + Pair("supernatural", "Supernatural"), + Pair("tragedy", "Tragedy"), + Pair("webtoons", "Webtoons"), + Pair("yaoi", "Yaoi"), + Pair("yuri", "Yuri"), + ) } diff --git a/src/en/manganelo/build.gradle b/src/en/manganelo/build.gradle index d34b6c9a1..3eed9ffcd 100644 --- a/src/en/manganelo/build.gradle +++ b/src/en/manganelo/build.gradle @@ -2,8 +2,8 @@ ext { extName = 'Manganato' extClass = '.Manganato' themePkg = 'mangabox' - baseUrl = 'https://manganato.com' - overrideVersionCode = 2 + baseUrl = 'https://www.natomanga.com' + overrideVersionCode = 3 } apply from: "$rootDir/common.gradle" diff --git a/src/en/manganelo/src/eu/kanade/tachiyomi/extension/en/manganelo/Manganato.kt b/src/en/manganelo/src/eu/kanade/tachiyomi/extension/en/manganelo/Manganato.kt index 92c1e937b..4a877b629 100644 --- a/src/en/manganelo/src/eu/kanade/tachiyomi/extension/en/manganelo/Manganato.kt +++ b/src/en/manganelo/src/eu/kanade/tachiyomi/extension/en/manganelo/Manganato.kt @@ -2,20 +2,116 @@ package eu.kanade.tachiyomi.extension.en.manganelo import eu.kanade.tachiyomi.multisrc.mangabox.MangaBox import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request +import org.jsoup.nodes.Element +import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale -class Manganato : MangaBox("Manganato", "https://manganato.com", "en", SimpleDateFormat("MMM dd,yy", Locale.ENGLISH)) { +class Manganato : MangaBox("Manganato", "https://www.natomanga.com", "en") { override val id: Long = 1024627298672457456 - // Nelo's date format is part of the base class - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/genre-all/$page?type=topview", headers) - override fun popularMangaSelector() = "div.content-genres-item" - override val latestUrlPath = "genre-all/" + private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yyyy HH:mm", Locale.ENGLISH) + + override fun headersBuilder(): Headers.Builder = super.headersBuilder().set("Referer", "$baseUrl/") // for covers + override val popularUrlPath = "manga-list/hot-manga?page=" + override val latestUrlPath = "manga-list/latest-manga?page=" override val simpleQueryPath = "search/story/" - override fun searchMangaSelector() = "div.search-story-item, div.content-genres-item" - override fun getAdvancedGenreFilters(): List = getGenreFilters() - .drop(1) - .map { AdvGenre(it.first, it.second) } + override fun searchMangaSelector() = "${super.searchMangaSelector()}, div.list-truyen-item-wrap" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return if (query.isNotBlank() && getAdvancedGenreFilters().isEmpty()) { + val url = "$baseUrl/$simpleQueryPath".toHttpUrl().newBuilder() + .addPathSegment(normalizeSearchQuery(query)) + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, headers) + } else { + val url = "$baseUrl/genre".toHttpUrl().newBuilder() + url.addQueryParameter("page", page.toString()) + filters.forEach { filter -> + when (filter) { + is SortFilter -> url.addQueryParameter("type", filter.toUriPart()) + is StatusFilter -> url.addQueryParameter("state", filter.toUriPart()) + is GenreFilter -> url.addPathSegment(filter.toUriPart()!!) + else -> {} + } + } + + GET(url.build(), headers) + } + } + + override fun chapterFromElement(element: Element): SChapter { + // parse on title attribute rather than the value + val dateUploadAttr: Long? = try { + dateFormat.parse(element.selectDateFromElement().attr("title"))?.time + } catch (e: ParseException) { + null + } + + return super.chapterFromElement(element).apply { + date_upload = dateUploadAttr ?: date_upload + } + } + + override val descriptionSelector = "div#contentBox" + + override fun imageRequest(page: Page): Request { + return if (page.url.contains(baseUrl)) { + GET(page.imageUrl!!, headersBuilder().build()) + } else { // Avoid 403 errors on non-migrated mangas + super.imageRequest(page) + } + } + + override fun getGenreFilters(): Array> = arrayOf( + Pair("all", "ALL"), + 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("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("shoujo-ai", "Shoujo ai"), + Pair("shounen", "Shounen"), + Pair("shounen-ai", "Shounen ai"), + Pair("slice-of-life", "Slice of life"), + Pair("smut", "Smut"), + Pair("sports", "Sports"), + Pair("supernatural", "Supernatural"), + Pair("tragedy", "Tragedy"), + Pair("webtoons", "Webtoons"), + Pair("yaoi", "Yaoi"), + Pair("yuri", "Yuri"), + ) }