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
This commit is contained in:
Jake 2025-03-01 11:22:58 +08:00 committed by Draff
parent e8fed7ce6d
commit ffd98958ee
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
5 changed files with 222 additions and 20 deletions

View File

@ -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<Pair<String?, String>>) : UriPartFilter("Keyword search ", vals)
private class SortFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Order by", vals)
private class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals)
private class GenreFilter(vals: Array<Pair<String?, String>>) : 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<Pair<String?, String>>) : UriPartFilter("Keyword search ", vals)
protected class SortFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Order by", vals)
protected class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals)
protected class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Category", vals)
// For advanced search, specifically tri-state genres
private class AdvGenreFilter(vals: List<AdvGenre>) : Filter.Group<AdvGenre>("Category", vals)

View File

@ -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"

View File

@ -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<Pair<String?, String>> = 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"),
)
}

View File

@ -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"

View File

@ -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<AdvGenre> = 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<Pair<String?, String>> = 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"),
)
}