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")
}
baseVersionCode = 39
baseVersionCode = 40
dependencies {
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.
*/
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.
@ -237,11 +242,14 @@ abstract class Madara(
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(URL_SEARCH_PREFIX)) {
val mangaUrl = "/$mangaSubString/${query.substringAfter(URL_SEARCH_PREFIX)}/"
return client.newCall(GET("$baseUrl$mangaUrl", headers))
val mangaUrl = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment(mangaSubString)
addPathSegment(query.substringAfter(URL_SEARCH_PREFIX))
}.build()
return client.newCall(GET(mangaUrl, headers))
.asObservableSuccess().map { response ->
val manga = mangaDetailsParse(response).apply {
url = mangaUrl
setUrlWithoutDomain(mangaUrl.toString())
}
MangasPage(listOf(manga), false)
@ -1069,10 +1077,17 @@ abstract class Madara(
* Fetch the genres from the source to be used in the filters.
*/
protected fun fetchGenres() {
if (fetchGenres && fetchGenresAttempts < 3 && genresList.isEmpty()) {
if (fetchGenres && fetchGenresAttempts < 3 && !genresFetched) {
try {
genresList = client.newCall(genresRequest()).execute()
client.newCall(genresRequest()).execute()
.use { parseGenres(it.asJsoup()) }
.also {
genresFetched = true
}
.takeIf { it.isNotEmpty() }
?.also {
genresList = it
}
} catch (_: Exception) {
} finally {
fetchGenresAttempts++

View File

@ -61,24 +61,22 @@ class GourmetScans : Madara(
override fun genresRequest(): Request = GET("$baseUrl/$mangaSubString", headers)
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()
.map { li ->
Pair(
Genre(
li.text(),
li.attr("href").split("/").last { it.isNotBlank() },
)
}
return emptyList()
}
private var genresList: List<Pair<String, String>> = emptyList()
class GenreFilter(vals: List<Pair<String, String>>) :
UriPartFilter("Genre", vals.toTypedArray())
class GenreFilter(vals: List<Genre>) :
UriPartFilter("Genre", vals.map { Pair(it.name, it.id) }.toTypedArray())
override fun getFilterList(): FilterList {
launchIO { fetchGenres() }
val filters = buildList(4) {
add(YearFilter(intl["year_filter_title"]))
add(
@ -93,7 +91,7 @@ class GourmetScans : Madara(
if (genresList.isEmpty()) {
add(Filter.Header(intl["genre_missing_warning"]))
} 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()) {
filters.forEach { filter ->
if (filter is GenreFilter) {
return GET(filter.vals[filter.state].second, headers)
return GET(filter.vals[filter.state].id, headers)
}
}
return latestUpdatesRequest(page)
@ -94,23 +94,22 @@ class Manga18fx : Madara(
override fun chapterDateSelector() = "span.chapter-time"
class GenreFilter(val vals: List<Pair<String, String>>) :
Filter.Select<String>("Genre", vals.map { it.first }.toTypedArray())
class GenreFilter(val vals: List<Genre>) :
Filter.Select<String>("Genre", vals.map { it.name }.toTypedArray())
private fun loadGenres(document: Document) {
genresList = document.select(".header-bottom li a").map {
val href = it.attr("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<Pair<String, String>> = listOf(
Pair("Manhwa", "$baseUrl/manga-genre/manhwa"),
Pair("Manhua", "$baseUrl/manga-genre/manhua"),
Pair("Raw", "$baseUrl/manga-genre/raw"),
private var hardCodedTypes: List<Genre> = listOf(
Genre("Manhwa", "$baseUrl/manga-genre/manhwa"),
Genre("Manhua", "$baseUrl/manga-genre/manhua"),
Genre("Raw", "$baseUrl/manga-genre/raw"),
)
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",
)
private var genresList: List<Pair<String, String>> = emptyList()
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>> {
override fun parseGenres(document: Document): List<Genre> {
return document.selectFirst("div.genres")
?.select("a")
.orEmpty()
.map { a ->
a.ownText() to
a.attr("href").substringBeforeLast("/").substringAfterLast("/")
Genre(
a.ownText(),
a.attr("href").substringBeforeLast("/").substringAfterLast("/"),
)
}.let {
listOf(("All" to "all")) + it
listOf(Genre("All", "all")) + it
}
}
override fun getFilterList(): FilterList {
launchIO { fetchGenresMe() }
launchIO { fetchGenres() }
val filters = mutableListOf(
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 += listOf(
filters += if (genresList.isNotEmpty()) {
listOf(
Filter.Separator(),
Filter.Header(intl["genre_filter_header"]),
GenreConditionFilter(
title = intl["genre_filter_title"],
options = genresList,
options = genresList.map { it.name to it.id },
),
)
} else if (fetchGenres) {
filters += listOf(
} else {
listOf(
Filter.Separator(),
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.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
@ -67,7 +66,7 @@ class ManyComic : Madara("ManyComic", "https://manycomic.com", "en") {
override fun searchMangaSelector() = popularMangaSelector()
override fun getFilterList(): FilterList {
launchIO { fetchGenres_() }
launchIO { fetchGenres() }
val filters: MutableList<Filter<out Any>> = mutableListOf(
OrderByFilter(
@ -77,18 +76,18 @@ class ManyComic : Madara("ManyComic", "https://manycomic.com", "en") {
),
)
if (genresList.isNotEmpty()) {
filters += listOf(
filters += if (genresList.isNotEmpty()) {
listOf(
Filter.Separator(),
Filter.Header("Genre filter is ignored when searching by title"),
GenreFilter(
title = intl["genre_filter_title"],
options = genresList,
options = listOf(Genre("<All>", "")) + genresList,
state = 0,
),
)
} else if (fetchGenres) {
filters += listOf(
} else {
listOf(
Filter.Separator(),
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",
)
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) :
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.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
@ -94,35 +93,22 @@ class ManyToon : Madara("ManyToon", "https://manytoon.com", "en") {
intl["order_by_filter_new"] to "new-manga",
)
private var genresList: List<Pair<String, String>> = emptyList()
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>> {
override fun parseGenres(document: Document): List<Genre> {
return document.selectFirst("div.genres")
?.select("a")
.orEmpty()
.map { a ->
a.ownText() to
a.attr("href").substringBeforeLast("/").substringAfterLast("/")
Genre(
a.ownText(),
a.attr("href").substringBeforeLast("/").substringAfterLast("/"),
)
}.let {
listOf(("All" to "all")) + it
listOf(Genre("All", "all")) + it
}
}
override fun getFilterList(): FilterList {
launchIO { fetchGenresMe() }
launchIO { fetchGenres() }
val filters = mutableListOf(
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 += listOf(
filters += if (genresList.isNotEmpty()) {
listOf(
Filter.Separator(),
Filter.Header(intl["genre_filter_header"]),
GenreConditionFilter(
title = intl["genre_filter_title"],
options = genresList,
options = genresList.map { it.name to it.id },
),
)
} else if (fetchGenres) {
filters += listOf(
} else {
listOf(
Filter.Separator(),
Filter.Header(intl["genre_missing_warning"]),
)

View File

@ -80,19 +80,16 @@ class LeerManga : Madara(
// =============================== Filters ===============================
private var genresList: List<Genre> = emptyList()
override fun getFilterList(): FilterList {
val filters = mutableListOf<Filter<*>>()
if (genresList.isNotEmpty()) {
filters += listOf(
Filter.Header(intl["genre_filter_header"]),
GenreGroup(
displayName = intl["genre_filter_title"],
genres = genresList,
),
)
} else if (fetchGenres) {
} else {
filters += Filter.Header(intl["genre_missing_warning"])
}
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.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
@ -84,26 +83,21 @@ class MGKomik : Madara(
// ================================ Filters ================================
private var genresList: List<Genre> = emptyList()
override val fetchGenres = false
override fun getFilterList(): FilterList {
launchIO { fetchGenresOption() }
launchIO { fetchGenres() }
val filters = super.getFilterList().list.toMutableList()
if (genresList.isNotEmpty()) {
filters += listOf(
filters += if (genresList.isNotEmpty()) {
listOf(
Filter.Separator(),
Filter.Header(intl["genre_filter_header"]),
GenreContentFilter(
title = intl["genre_filter_title"],
options = genresList.map { it.name to it.id },
),
)
} else if (fetchGenres) {
filters += listOf(
} else {
listOf(
Filter.Separator(),
Filter.Header(intl["genre_missing_warning"]),
)
@ -119,20 +113,6 @@ class MGKomik : Madara(
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> {
val genres = mutableListOf<Genre>()
genres += Genre("All", "")

View File

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