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' extName = 'Anitube'
pkgNameSuffix = 'pt.anitube' pkgNameSuffix = 'pt.anitube'
extClass = '.Anitube' extClass = '.Anitube'
extVersionCode = 11 extVersionCode = 12
libVersion = '13' 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.animeextension.pt.anitube.extractors.AnitubeExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList 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.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
@ -46,8 +45,8 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.add("Accept-Language", ACCEPT_LANGUAGE) .add("Accept-Language", ACCEPT_LANGUAGE)
// ============================== Popular =============================== // ============================== 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 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 { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
@ -56,81 +55,57 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
thumbnail_url = img.attr("src") 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 { // =============================== Latest ===============================
val document = response.asJsoup() override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
val animes = document.select(popularAnimeSelector()).map { element ->
popularAnimeFromElement(element)
}
val hasNextPage = hasNextPage(document)
return AnimesPage(animes, hasNextPage)
}
// ============================== Episodes ============================== override fun latestUpdatesSelector() = "div.threeItensPerContent > div.epi_loop_item > a"
override fun episodeListSelector() = "div.animepag_episodios_container > div.animepag_episodios_item > a"
override fun episodeListParse(response: Response): List<SEpisode> { override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
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 episodeFromElement(element: Element) = SEpisode.create().apply { override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
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")
// =============================== Search =============================== // =============================== 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 getFilterList(): AnimeFilterList = AnitubeFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { 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 params = AnitubeFilters.getSearchParameters(filters)
val season = params.season val season = params.season
val genre = params.genre val genre = params.genre
val year = params.year val year = params.year
val char = params.initialChar val char = params.initialChar
when { when {
!season.isBlank() -> GET("$baseUrl/temporada/$season/$year") season.isNotBlank() -> "$baseUrl/temporada/$season/$year"
!genre.isBlank() -> GET("$baseUrl/genero/$genre/page/$page/${char.replace("todos", "")}") genre.isNotBlank() -> "$baseUrl/genero/$genre/page/$page/${char.replace("todos", "")}"
else -> GET("$baseUrl/anime/page/$page/letra/$char") else -> "$baseUrl/anime/page/$page/letra/$char"
} }
} else { } 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 ============================ // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply { override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document) val doc = getRealDoc(document)
@ -155,32 +130,44 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
// =============================== Latest =============================== // ============================== Episodes ==============================
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() override fun episodeListSelector() = "div.animepag_episodios_item > a"
override fun latestUpdatesSelector() = "div.mContainer_content.threeItensPerContent > div.epi_loop_item" override fun episodeListParse(response: Response) = buildList {
var doc = getRealDoc(response.asJsoup())
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { do {
val img = element.selectFirst("img")!! if (isNotEmpty()) {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) val path = doc.selectFirst(popularAnimeNextPageSelector())!!.attr("href")
title = img.attr("title") doc = client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
thumbnail_url = img.attr("src") }
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"))
override fun latestUpdatesParse(response: Response): AnimesPage { episode_number = element.selectFirst("div.animepag_episodios_item_views")!!
val document = response.asJsoup() .text()
val animes = document.select(latestUpdatesSelector()).map { element -> .substringAfter(" ")
latestUpdatesFromElement(element) .toFloatOrNull() ?: 0F
} name = element.selectFirst("div.animepag_episodios_item_nome")!!.text()
val hasNextPage = hasNextPage(document) date_upload = element.selectFirst("div.animepag_episodios_item_date")!!
return AnimesPage(animes, hasNextPage) .text()
.toDate()
} }
// ============================ 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 ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES entries = PREF_QUALITY_ENTRIES
@ -193,16 +180,19 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.let(screen::addPreference)
screen.addPreference(videoQualityPref)
} }
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun getRealDoc(document: Document): Document { 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)") return document.selectFirst("div.controles_ep > a[href]:has(i.spr.listaEP)")
?.let { ?.let {
val path = it.attr("href") val path = it.attr("href")
client.newCall(GET(baseUrl + path)).execute().asJsoup() client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
} ?: document } ?: 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? { private fun Element.getInfo(key: String): String? {
val parent = selectFirst("b:contains($key)")?.parent() val element = selectFirst("div.anime_info:has(b:contains($key))")
val genres = parent?.select("a") val genres = element?.select("a")
val text = if (genres?.size == 0) { val text = if (genres?.size == 0) {
parent.ownText() element.ownText()
} else { } else {
genres?.eachText()?.joinToString() genres?.eachText()?.joinToString()
} }

View File

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

View File

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