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() protected open val alternateChapterDateSelector = String()
private fun Element.selectDateFromElement(): Element { protected fun Element.selectDateFromElement(): Element {
val defaultChapterDateSelector = "span" val defaultChapterDateSelector = "span"
return this.select(defaultChapterDateSelector).lastOrNull() ?: this.select(alternateChapterDateSelector).last()!! 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) // Technically, only Sort, Status, and Genre need to be non-private for Mangakakalot and Manganato, but I'll include Keyword to make it uniform.
private class SortFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Order by", vals) protected class KeywordFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Keyword search ", vals)
private class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals) protected class SortFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Order by", vals)
private class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Category", 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 // For advanced search, specifically tri-state genres
private class AdvGenreFilter(vals: List<AdvGenre>) : Filter.Group<AdvGenre>("Category", vals) private class AdvGenreFilter(vals: List<AdvGenre>) : Filter.Group<AdvGenre>("Category", vals)

View File

@ -2,8 +2,8 @@ ext {
extName = 'Mangakakalot' extName = 'Mangakakalot'
extClass = '.Mangakakalot' extClass = '.Mangakakalot'
themePkg = 'mangabox' themePkg = 'mangabox'
baseUrl = 'https://mangakakalot.com' baseUrl = 'https://www.mangakakalot.gg'
overrideVersionCode = 3 overrideVersionCode = 4
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,10 +1,115 @@
package eu.kanade.tachiyomi.extension.en.mangakakalot package eu.kanade.tachiyomi.extension.en.mangakakalot
import eu.kanade.tachiyomi.multisrc.mangabox.MangaBox 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.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") { class Mangakakalot : MangaBox("Mangakakalot", "https://www.mangakakalot.gg", "en") {
override fun headersBuilder(): Headers.Builder = super.headersBuilder().set("Referer", "https://manganato.com") // for covers 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 val simpleQueryPath = "search/story/"
override fun searchMangaSelector() = "${super.searchMangaSelector()}, div.list-truyen-item-wrap" 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' extName = 'Manganato'
extClass = '.Manganato' extClass = '.Manganato'
themePkg = 'mangabox' themePkg = 'mangabox'
baseUrl = 'https://manganato.com' baseUrl = 'https://www.natomanga.com'
overrideVersionCode = 2 overrideVersionCode = 3
} }
apply from: "$rootDir/common.gradle" 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.multisrc.mangabox.MangaBox
import eu.kanade.tachiyomi.network.GET 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 okhttp3.Request
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale 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 override val id: Long = 1024627298672457456
// Nelo's date format is part of the base class private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yyyy HH:mm", Locale.ENGLISH)
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/genre-all/$page?type=topview", headers)
override fun popularMangaSelector() = "div.content-genres-item" override fun headersBuilder(): Headers.Builder = super.headersBuilder().set("Referer", "$baseUrl/") // for covers
override val latestUrlPath = "genre-all/" override val popularUrlPath = "manga-list/hot-manga?page="
override val latestUrlPath = "manga-list/latest-manga?page="
override val simpleQueryPath = "search/story/" override val simpleQueryPath = "search/story/"
override fun searchMangaSelector() = "div.search-story-item, div.content-genres-item" override fun searchMangaSelector() = "${super.searchMangaSelector()}, div.list-truyen-item-wrap"
override fun getAdvancedGenreFilters(): List<AdvGenre> = getGenreFilters()
.drop(1) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
.map { AdvGenre(it.first, it.second) } 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"),
)
} }