Madara: Expose genresList (#7674)

* Madara: Allow ext to preload with a list of genres

Help with fork like SY which allows saving search

* fix extensions

* update the fetchSearchManga to using HttpUrlBuilder
This commit is contained in:
Cuong-Tran 2025-02-20 21:45:30 +07:00 committed by Draff
parent 63558a4cdb
commit 6c1e55053c
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
10 changed files with 74 additions and 131 deletions

View File

@ -2,7 +2,7 @@ plugins {
id("lib-multisrc") id("lib-multisrc")
} }
baseVersionCode = 39 baseVersionCode = 40
dependencies { dependencies {
api(project(":lib:cryptoaes")) api(project(":lib:cryptoaes"))

View File

@ -82,7 +82,12 @@ abstract class Madara(
/** /**
* Automatically fetched genres from the source to be used in the filters. * Automatically fetched genres from the source to be used in the filters.
*/ */
private var genresList: List<Genre> = emptyList() protected open var genresList: List<Genre> = emptyList()
/**
* Whether genres have been fetched
*/
private var genresFetched: Boolean = false
/** /**
* Inner variable to control how much tries the genres request was called. * Inner variable to control how much tries the genres request was called.
@ -237,11 +242,14 @@ abstract class Madara(
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(URL_SEARCH_PREFIX)) { if (query.startsWith(URL_SEARCH_PREFIX)) {
val mangaUrl = "/$mangaSubString/${query.substringAfter(URL_SEARCH_PREFIX)}/" val mangaUrl = baseUrl.toHttpUrl().newBuilder().apply {
return client.newCall(GET("$baseUrl$mangaUrl", headers)) addPathSegment(mangaSubString)
addPathSegment(query.substringAfter(URL_SEARCH_PREFIX))
}.build()
return client.newCall(GET(mangaUrl, headers))
.asObservableSuccess().map { response -> .asObservableSuccess().map { response ->
val manga = mangaDetailsParse(response).apply { val manga = mangaDetailsParse(response).apply {
url = mangaUrl setUrlWithoutDomain(mangaUrl.toString())
} }
MangasPage(listOf(manga), false) MangasPage(listOf(manga), false)
@ -1069,10 +1077,17 @@ abstract class Madara(
* Fetch the genres from the source to be used in the filters. * Fetch the genres from the source to be used in the filters.
*/ */
protected fun fetchGenres() { protected fun fetchGenres() {
if (fetchGenres && fetchGenresAttempts < 3 && genresList.isEmpty()) { if (fetchGenres && fetchGenresAttempts < 3 && !genresFetched) {
try { try {
genresList = client.newCall(genresRequest()).execute() client.newCall(genresRequest()).execute()
.use { parseGenres(it.asJsoup()) } .use { parseGenres(it.asJsoup()) }
.also {
genresFetched = true
}
.takeIf { it.isNotEmpty() }
?.also {
genresList = it
}
} catch (_: Exception) { } catch (_: Exception) {
} finally { } finally {
fetchGenresAttempts++ fetchGenresAttempts++

View File

@ -61,24 +61,22 @@ class GourmetScans : Madara(
override fun genresRequest(): Request = GET("$baseUrl/$mangaSubString", headers) override fun genresRequest(): Request = GET("$baseUrl/$mangaSubString", headers)
override fun parseGenres(document: Document): List<Genre> { override fun parseGenres(document: Document): List<Genre> {
genresList = document.select("div.row.genres ul li a") return document.select("div.row.genres ul li a")
.orEmpty() .orEmpty()
.map { li -> .map { li ->
Pair( Genre(
li.text(), li.text(),
li.attr("href").split("/").last { it.isNotBlank() }, li.attr("href").split("/").last { it.isNotBlank() },
) )
} }
return emptyList()
} }
private var genresList: List<Pair<String, String>> = emptyList() class GenreFilter(vals: List<Genre>) :
UriPartFilter("Genre", vals.map { Pair(it.name, it.id) }.toTypedArray())
class GenreFilter(vals: List<Pair<String, String>>) :
UriPartFilter("Genre", vals.toTypedArray())
override fun getFilterList(): FilterList { override fun getFilterList(): FilterList {
launchIO { fetchGenres() }
val filters = buildList(4) { val filters = buildList(4) {
add(YearFilter(intl["year_filter_title"])) add(YearFilter(intl["year_filter_title"]))
add( add(
@ -93,7 +91,7 @@ class GourmetScans : Madara(
if (genresList.isEmpty()) { if (genresList.isEmpty()) {
add(Filter.Header(intl["genre_missing_warning"])) add(Filter.Header(intl["genre_missing_warning"]))
} else { } else {
add(GenreFilter(listOf(Pair("<select>", "")) + genresList)) add(GenreFilter(listOf(Genre("<select>", "")) + genresList))
} }
} }

View File

@ -68,7 +68,7 @@ class Manga18fx : Madara(
if (query.isEmpty()) { if (query.isEmpty()) {
filters.forEach { filter -> filters.forEach { filter ->
if (filter is GenreFilter) { if (filter is GenreFilter) {
return GET(filter.vals[filter.state].second, headers) return GET(filter.vals[filter.state].id, headers)
} }
} }
return latestUpdatesRequest(page) return latestUpdatesRequest(page)
@ -94,23 +94,22 @@ class Manga18fx : Madara(
override fun chapterDateSelector() = "span.chapter-time" override fun chapterDateSelector() = "span.chapter-time"
class GenreFilter(val vals: List<Pair<String, String>>) : class GenreFilter(val vals: List<Genre>) :
Filter.Select<String>("Genre", vals.map { it.first }.toTypedArray()) Filter.Select<String>("Genre", vals.map { it.name }.toTypedArray())
private fun loadGenres(document: Document) { private fun loadGenres(document: Document) {
genresList = document.select(".header-bottom li a").map { genresList = document.select(".header-bottom li a").map {
val href = it.attr("href") val href = it.attr("href")
val url = if (href.startsWith("http")) href else "$baseUrl/$href" val url = if (href.startsWith("http")) href else "$baseUrl/$href"
Pair(it.text(), url) Genre(it.text(), url)
} }
} }
private var genresList: List<Pair<String, String>> = emptyList() private var hardCodedTypes: List<Genre> = listOf(
private var hardCodedTypes: List<Pair<String, String>> = listOf( Genre("Manhwa", "$baseUrl/manga-genre/manhwa"),
Pair("Manhwa", "$baseUrl/manga-genre/manhwa"), Genre("Manhua", "$baseUrl/manga-genre/manhua"),
Pair("Manhua", "$baseUrl/manga-genre/manhua"), Genre("Raw", "$baseUrl/manga-genre/raw"),
Pair("Raw", "$baseUrl/manga-genre/raw"),
) )
override fun getFilterList(): FilterList { override fun getFilterList(): FilterList {

View File

@ -104,35 +104,22 @@ class ManhwahentaiMe : Madara("Manhwahentai.me", "https://manhwahentai.me", "en"
intl["order_by_filter_new"] to "new-manga", intl["order_by_filter_new"] to "new-manga",
) )
private var genresList: List<Pair<String, String>> = emptyList() override fun parseGenres(document: Document): List<Genre> {
private var fetchGenresAttempts: Int = 0
private fun fetchGenresMe() {
if (fetchGenres && fetchGenresAttempts < 3 && genresList.isEmpty()) {
try {
genresList = client.newCall(genresRequest()).execute()
.use { parseGenresMe(it.asJsoup()) }
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
}
}
private fun parseGenresMe(document: Document): List<Pair<String, String>> {
return document.selectFirst("div.genres") return document.selectFirst("div.genres")
?.select("a") ?.select("a")
.orEmpty() .orEmpty()
.map { a -> .map { a ->
a.ownText() to Genre(
a.attr("href").substringBeforeLast("/").substringAfterLast("/") a.ownText(),
a.attr("href").substringBeforeLast("/").substringAfterLast("/"),
)
}.let { }.let {
listOf(("All" to "all")) + it listOf(Genre("All", "all")) + it
} }
} }
override fun getFilterList(): FilterList { override fun getFilterList(): FilterList {
launchIO { fetchGenresMe() } launchIO { fetchGenres() }
val filters = mutableListOf( val filters = mutableListOf(
Filter.Header("All filters except the orderby filter are incompatible with each other"), Filter.Header("All filters except the orderby filter are incompatible with each other"),
@ -145,17 +132,16 @@ class ManhwahentaiMe : Madara("Manhwahentai.me", "https://manhwahentai.me", "en"
), ),
) )
if (genresList.isNotEmpty()) { filters += if (genresList.isNotEmpty()) {
filters += listOf( listOf(
Filter.Separator(), Filter.Separator(),
Filter.Header(intl["genre_filter_header"]),
GenreConditionFilter( GenreConditionFilter(
title = intl["genre_filter_title"], title = intl["genre_filter_title"],
options = genresList, options = genresList.map { it.name to it.id },
), ),
) )
} else if (fetchGenres) { } else {
filters += listOf( listOf(
Filter.Separator(), Filter.Separator(),
Filter.Header(intl["genre_missing_warning"]), Filter.Header(intl["genre_missing_warning"]),
) )

View File

@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter 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.util.asJsoup
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
@ -67,7 +66,7 @@ class ManyComic : Madara("ManyComic", "https://manycomic.com", "en") {
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun getFilterList(): FilterList { override fun getFilterList(): FilterList {
launchIO { fetchGenres_() } launchIO { fetchGenres() }
val filters: MutableList<Filter<out Any>> = mutableListOf( val filters: MutableList<Filter<out Any>> = mutableListOf(
OrderByFilter( OrderByFilter(
@ -77,18 +76,18 @@ class ManyComic : Madara("ManyComic", "https://manycomic.com", "en") {
), ),
) )
if (genresList.isNotEmpty()) { filters += if (genresList.isNotEmpty()) {
filters += listOf( listOf(
Filter.Separator(), Filter.Separator(),
Filter.Header("Genre filter is ignored when searching by title"), Filter.Header("Genre filter is ignored when searching by title"),
GenreFilter( GenreFilter(
title = intl["genre_filter_title"], title = intl["genre_filter_title"],
options = genresList, options = listOf(Genre("<All>", "")) + genresList,
state = 0, state = 0,
), ),
) )
} else if (fetchGenres) { } else {
filters += listOf( listOf(
Filter.Separator(), Filter.Separator(),
Filter.Header(intl["genre_missing_warning"]), Filter.Header(intl["genre_missing_warning"]),
) )
@ -106,22 +105,6 @@ class ManyComic : Madara("ManyComic", "https://manycomic.com", "en") {
intl["order_by_filter_new"] to "new-manga", intl["order_by_filter_new"] to "new-manga",
) )
private var genresList: List<Genre> = emptyList()
private var fetchGenresAttempts: Int = 0
private fun fetchGenres_() {
if (fetchGenres && fetchGenresAttempts < 3 && genresList.isEmpty()) {
try {
genresList = listOf(Genre("<All>", "")) +
client.newCall(genresRequest()).execute()
.use { parseGenres(it.asJsoup()) }
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
}
}
private class GenreFilter(title: String, options: List<Genre>, state: Int = 0) : private class GenreFilter(title: String, options: List<Genre>, state: Int = 0) :
UriPartFilter(title, options.map { Pair(it.name, it.id) }.toTypedArray(), state) UriPartFilter(title, options.map { Pair(it.name, it.id) }.toTypedArray(), state)
} }

View File

@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter 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.util.asJsoup
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
@ -94,35 +93,22 @@ class ManyToon : Madara("ManyToon", "https://manytoon.com", "en") {
intl["order_by_filter_new"] to "new-manga", intl["order_by_filter_new"] to "new-manga",
) )
private var genresList: List<Pair<String, String>> = emptyList() override fun parseGenres(document: Document): List<Genre> {
private var fetchGenresAttempts: Int = 0
private fun fetchGenresMe() {
if (fetchGenres && fetchGenresAttempts < 3 && genresList.isEmpty()) {
try {
genresList = client.newCall(genresRequest()).execute()
.use { parseGenresMe(it.asJsoup()) }
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
}
}
private fun parseGenresMe(document: Document): List<Pair<String, String>> {
return document.selectFirst("div.genres") return document.selectFirst("div.genres")
?.select("a") ?.select("a")
.orEmpty() .orEmpty()
.map { a -> .map { a ->
a.ownText() to Genre(
a.attr("href").substringBeforeLast("/").substringAfterLast("/") a.ownText(),
a.attr("href").substringBeforeLast("/").substringAfterLast("/"),
)
}.let { }.let {
listOf(("All" to "all")) + it listOf(Genre("All", "all")) + it
} }
} }
override fun getFilterList(): FilterList { override fun getFilterList(): FilterList {
launchIO { fetchGenresMe() } launchIO { fetchGenres() }
val filters = mutableListOf( val filters = mutableListOf(
Filter.Header("All filters except the orderby filter are incompatible with each other"), Filter.Header("All filters except the orderby filter are incompatible with each other"),
@ -135,17 +121,16 @@ class ManyToon : Madara("ManyToon", "https://manytoon.com", "en") {
), ),
) )
if (genresList.isNotEmpty()) { filters += if (genresList.isNotEmpty()) {
filters += listOf( listOf(
Filter.Separator(), Filter.Separator(),
Filter.Header(intl["genre_filter_header"]),
GenreConditionFilter( GenreConditionFilter(
title = intl["genre_filter_title"], title = intl["genre_filter_title"],
options = genresList, options = genresList.map { it.name to it.id },
), ),
) )
} else if (fetchGenres) { } else {
filters += listOf( listOf(
Filter.Separator(), Filter.Separator(),
Filter.Header(intl["genre_missing_warning"]), Filter.Header(intl["genre_missing_warning"]),
) )

View File

@ -80,19 +80,16 @@ class LeerManga : Madara(
// =============================== Filters =============================== // =============================== Filters ===============================
private var genresList: List<Genre> = emptyList()
override fun getFilterList(): FilterList { override fun getFilterList(): FilterList {
val filters = mutableListOf<Filter<*>>() val filters = mutableListOf<Filter<*>>()
if (genresList.isNotEmpty()) { if (genresList.isNotEmpty()) {
filters += listOf( filters += listOf(
Filter.Header(intl["genre_filter_header"]),
GenreGroup( GenreGroup(
displayName = intl["genre_filter_title"], displayName = intl["genre_filter_title"],
genres = genresList, genres = genresList,
), ),
) )
} else if (fetchGenres) { } else {
filters += Filter.Header(intl["genre_missing_warning"]) filters += Filter.Header(intl["genre_missing_warning"])
} }
return FilterList(filters) return FilterList(filters)

View File

@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter 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.util.asJsoup
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -84,26 +83,21 @@ class MGKomik : Madara(
// ================================ Filters ================================ // ================================ Filters ================================
private var genresList: List<Genre> = emptyList()
override val fetchGenres = false
override fun getFilterList(): FilterList { override fun getFilterList(): FilterList {
launchIO { fetchGenresOption() } launchIO { fetchGenres() }
val filters = super.getFilterList().list.toMutableList() val filters = super.getFilterList().list.toMutableList()
if (genresList.isNotEmpty()) { filters += if (genresList.isNotEmpty()) {
filters += listOf( listOf(
Filter.Separator(), Filter.Separator(),
Filter.Header(intl["genre_filter_header"]),
GenreContentFilter( GenreContentFilter(
title = intl["genre_filter_title"], title = intl["genre_filter_title"],
options = genresList.map { it.name to it.id }, options = genresList.map { it.name to it.id },
), ),
) )
} else if (fetchGenres) { } else {
filters += listOf( listOf(
Filter.Separator(), Filter.Separator(),
Filter.Header(intl["genre_missing_warning"]), Filter.Header(intl["genre_missing_warning"]),
) )
@ -119,20 +113,6 @@ class MGKomik : Madara(
override fun genresRequest() = GET("$baseUrl/$mangaSubString", headers) override fun genresRequest() = GET("$baseUrl/$mangaSubString", headers)
private var fetchGenresAttempts: Int = 0
fun fetchGenresOption() {
if (fetchGenresAttempts < 3 && genresList.isEmpty()) {
try {
genresList = client.newCall(genresRequest()).execute()
.use { parseGenres(it.asJsoup()) }
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
}
}
override fun parseGenres(document: Document): List<Genre> { override fun parseGenres(document: Document): List<Genre> {
val genres = mutableListOf<Genre>() val genres = mutableListOf<Genre>()
genres += Genre("All", "") genres += Genre("All", "")

View File

@ -83,7 +83,7 @@ class PojokManga : Madara("Pojok Manga", "https://pojokmanga.info", "id", Simple
override val mangaDetailsSelectorTag = "#toNotBeUsed" override val mangaDetailsSelectorTag = "#toNotBeUsed"
protected class ProjectFilter : UriPartFilter( private class ProjectFilter : UriPartFilter(
"Filter Project", "Filter Project",
arrayOf( arrayOf(
Pair("Show all manga", ""), Pair("Show all manga", ""),