fix(pt/anitube): Fix search filters + refactor (#1817)

This commit is contained in:
Claudemirovsky
2023-07-02 17:34:48 +00:00
committed by GitHub
parent 6ed1d89d30
commit d3571ebfe7
4 changed files with 81 additions and 115 deletions

View File

@ -7,7 +7,7 @@ ext {
extName = 'Anitube'
pkgNameSuffix = 'pt.anitube'
extClass = '.Anitube'
extVersionCode = 11
extVersionCode = 12
libVersion = '13'
}

View File

@ -7,7 +7,6 @@ import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.anitube.extractors.AnitubeExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
@ -46,8 +45,8 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.add("Accept-Language", ACCEPT_LANGUAGE)
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/anime/page/$page", headers)
override fun popularAnimeSelector() = "div.lista_de_animes div.ani_loop_item_img > a"
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/anime/page/$page")
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
@ -56,81 +55,57 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
thumbnail_url = img.attr("src")
}
override fun popularAnimeNextPageSelector(): String = "a.page-numbers:contains(Próximo)"
/**
* Translation of this abomination:
* First it tries to get the `a.current` element IF its not the second-to-last,
* and then gets the next `a` element (only useful for the `episodeListParser`).
*
* If the first selector fails, then it tries to match a `div.pagination`
* element that does not have any `a.current` element inside it,
* and also doesn't have just three elements (previous - current - next),
* and finally gets the last `a` element("next" button, only useful to `episodeListParser`).
*
* I hate the antichrist.
*/
override fun popularAnimeNextPageSelector() =
"div.pagination > a.current:not(:nth-last-child(2)) + a, " +
"div.pagination:not(:has(.current)):not(:has(a:first-child + a + a:last-child)) > a:last-child"
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document.select(popularAnimeSelector()).map { element ->
popularAnimeFromElement(element)
}
val hasNextPage = hasNextPage(document)
return AnimesPage(animes, hasNextPage)
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
// ============================== Episodes ==============================
override fun episodeListSelector() = "div.animepag_episodios_container > div.animepag_episodios_item > a"
override fun latestUpdatesSelector() = "div.threeItensPerContent > div.epi_loop_item > a"
override fun episodeListParse(response: Response): List<SEpisode> {
var doc = getRealDoc(response.asJsoup())
var first = true
val epList = buildList {
do {
if (!first) {
val path = doc.selectFirst(popularAnimeNextPageSelector())!!.attr("href")
doc = client.newCall(GET(baseUrl + path)).execute().asJsoup()
}
first = false
doc.select(episodeListSelector())
.map(::episodeFromElement)
.let(::addAll)
} while (hasNextPage(doc))
}
return epList.reversed()
}
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
episode_number = element.selectFirst("div.animepag_episodios_item_views")!!
.text()
.substringAfter(" ")
.toFloatOrNull() ?: 0F
name = element.selectFirst("div.animepag_episodios_item_nome")!!.text()
date_upload = element.selectFirst("div.animepag_episodios_item_date")!!
.text()
.toDate()
}
// ============================ Video Links =============================
override fun videoListParse(response: Response) = AnitubeExtractor.getVideoList(response)
override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
// =============================== Search ===============================
override fun searchAnimeFromElement(element: Element) = throw Exception("not used")
override fun searchAnimeNextPageSelector() = throw Exception("not used")
override fun searchAnimeSelector() = throw Exception("not used")
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun getFilterList(): AnimeFilterList = AnitubeFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return if (query.isBlank()) {
val url = if (query.isBlank()) {
val params = AnitubeFilters.getSearchParameters(filters)
val season = params.season
val genre = params.genre
val year = params.year
val char = params.initialChar
when {
!season.isBlank() -> GET("$baseUrl/temporada/$season/$year")
!genre.isBlank() -> GET("$baseUrl/genero/$genre/page/$page/${char.replace("todos", "")}")
else -> GET("$baseUrl/anime/page/$page/letra/$char")
season.isNotBlank() -> "$baseUrl/temporada/$season/$year"
genre.isNotBlank() -> "$baseUrl/genero/$genre/page/$page/${char.replace("todos", "")}"
else -> "$baseUrl/anime/page/$page/letra/$char"
}
} else {
GET("$baseUrl/busca.php?s=$query&submit=Buscar")
"$baseUrl/busca.php?s=$query&submit=Buscar"
}
return GET(url, headers)
}
override fun searchAnimeSelector() = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
@ -155,32 +130,44 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
}
// =============================== Latest ===============================
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
// ============================== Episodes ==============================
override fun episodeListSelector() = "div.animepag_episodios_item > a"
override fun latestUpdatesSelector() = "div.mContainer_content.threeItensPerContent > div.epi_loop_item"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
val img = element.selectFirst("img")!!
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
title = img.attr("title")
thumbnail_url = img.attr("src")
override fun episodeListParse(response: Response) = buildList {
var doc = getRealDoc(response.asJsoup())
do {
if (isNotEmpty()) {
val path = doc.selectFirst(popularAnimeNextPageSelector())!!.attr("href")
doc = client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
}
doc.select(episodeListSelector())
.map(::episodeFromElement)
.let(::addAll)
} while (doc.selectFirst(popularAnimeNextPageSelector()) != null)
reverse()
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page")
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
episode_number = element.selectFirst("div.animepag_episodios_item_views")!!
.text()
.substringAfter(" ")
.toFloatOrNull() ?: 0F
name = element.selectFirst("div.animepag_episodios_item_nome")!!.text()
date_upload = element.selectFirst("div.animepag_episodios_item_date")!!
.text()
.toDate()
}
override fun latestUpdatesParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document.select(latestUpdatesSelector()).map { element ->
latestUpdatesFromElement(element)
}
val hasNextPage = hasNextPage(document)
return AnimesPage(animes, hasNextPage)
}
// ============================ Video Links =============================
override fun videoListParse(response: Response) = AnitubeExtractor.getVideoList(response, headers)
override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
@ -193,16 +180,19 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
screen.addPreference(videoQualityPref)
}.let(screen::addPreference)
}
// ============================= Utilities ==============================
private fun getRealDoc(document: Document): Document {
if (!document.location().contains("/video/")) {
return document
}
return document.selectFirst("div.controles_ep > a[href]:has(i.spr.listaEP)")
?.let {
val path = it.attr("href")
client.newCall(GET(baseUrl + path)).execute().asJsoup()
client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
} ?: document
}
@ -214,31 +204,11 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
}
private fun hasNextPage(document: Document): Boolean {
val pagination = document.selectFirst("div.pagination")
val items = pagination?.select("a.page-numbers")
if (pagination == null || items!!.size < 2) return false
val currentPage = pagination.selectFirst("a.page-numbers.current")
?.attr("href")
?.toPageNum() ?: 1
val lastPage = items[items.lastIndex - 1]
.attr("href")
.toPageNum()
return currentPage != lastPage
}
private fun String.toPageNum(): Int =
substringAfter("page/")
.substringAfter("page=")
.substringBefore("/")
.substringBefore("&")
.toIntOrNull() ?: 1
private fun Element.getInfo(key: String): String? {
val parent = selectFirst("b:contains($key)")?.parent()
val genres = parent?.select("a")
val element = selectFirst("div.anime_info:has(b:contains($key))")
val genres = element?.select("a")
val text = if (genres?.size == 0) {
parent.ownText()
element.ownText()
} else {
genres?.eachText()?.joinToString()
}

View File

@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AnitubeFilters {
open class QueryPartFilter(
displayName: String,
val vals: Array<Pair<String, String>>,
@ -16,9 +15,7 @@ object AnitubeFilters {
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return this.filterIsInstance<R>().joinToString("") {
(it as QueryPartFilter).toQueryPart()
}
return (first { it is R } as QueryPartFilter).toQueryPart()
}
class GenreFilter : QueryPartFilter("Gênero", AnitubeFiltersData.GENRES)
@ -38,14 +35,16 @@ object AnitubeFilters {
data class FilterSearchParams(
val genre: String = "",
val season: String = "",
val year: String = "",
val initialChar: String = "",
val year: String = "2023",
val initialChar: String = "todos",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
return FilterSearchParams(
filters.asQueryPart<GenreFilter>(),
filters.asQueryPart<SeasonFilter>(),
filters.asQueryPart<YearFilter>(),
filters.asQueryPart<CharacterFilter>(),
)
}
@ -63,7 +62,7 @@ object AnitubeFilters {
Pair("Verão", "verao"),
)
val YEARS = (2022 downTo 1979).map {
val YEARS = (2023 downTo 1979).map {
Pair(it.toString(), it.toString())
}.toTypedArray()

View File

@ -6,10 +6,7 @@ import okhttp3.Headers
import okhttp3.Response
object AnitubeExtractor {
private val HEADERS = Headers.headersOf("Referer", "https://www.anitube.vip/")
fun getVideoList(response: Response): List<Video> {
fun getVideoList(response: Response, headers: Headers): List<Video> {
val doc = response.asJsoup()
val hasFHD = doc.selectFirst("div.abaItem:contains(FULLHD)") != null
val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!!
@ -27,7 +24,7 @@ object AnitubeExtractor {
return qualities.mapIndexed { index, quality ->
val path = paths[index]
val url = serverUrl.replace(type, path)
Video(url, quality, url, headers = HEADERS)
Video(url, quality, url, headers = headers)
}.reversed()
}
}