diff --git a/src/pt/animeszone/build.gradle b/src/pt/animeszone/build.gradle index 988298bd1..695d2fd7d 100644 --- a/src/pt/animeszone/build.gradle +++ b/src/pt/animeszone/build.gradle @@ -1,12 +1,14 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) +} ext { extName = 'AnimesZone' pkgNameSuffix = 'pt.animeszone' extClass = '.AnimesZone' - extVersionCode = 5 + extVersionCode = 6 libVersion = '13' containsNsfw = true } diff --git a/src/pt/animeszone/src/eu/kanade/tachiyomi/animeextension/pt/animeszone/AnimesZone.kt b/src/pt/animeszone/src/eu/kanade/tachiyomi/animeextension/pt/animeszone/AnimesZone.kt index 13c9212ee..29cfaf83e 100644 --- a/src/pt/animeszone/src/eu/kanade/tachiyomi/animeextension/pt/animeszone/AnimesZone.kt +++ b/src/pt/animeszone/src/eu/kanade/tachiyomi/animeextension/pt/animeszone/AnimesZone.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.animeextension.pt.animeszone import android.app.Application -import android.content.SharedPreferences import androidx.preference.ListPreference import androidx.preference.PreferenceScreen import app.cash.quickjs.QuickJs @@ -17,11 +16,9 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request @@ -30,7 +27,6 @@ import okhttp3.Response import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -47,38 +43,32 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val client: OkHttpClient = network.cloudflareClient + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + .add("Origin", baseUrl) + private val json: Json by injectLazy() - private val preferences: SharedPreferences by lazy { + private val preferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } - companion object { - private val EPISODE_REGEX = Regex("""Episódio ?\d+\.?\d* ?""") - } - // ============================== Popular =============================== - override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/tendencia/") override fun popularAnimeSelector(): String = "div.items > div.seriesList" override fun popularAnimeNextPageSelector(): String? = null - override fun popularAnimeFromElement(element: Element): SAnime { - return SAnime.create().apply { - setUrlWithoutDomain( - element.selectFirst("a[href]")!!.attr("href").toHttpUrl().encodedPath, - ) - thumbnail_url = element.selectFirst("div.cover-image")?.let { - it.attr("style").substringAfter("url('").substringBefore("'") - } ?: "" - title = element.selectFirst("span.series-title")!!.text() - } + override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { + setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href")) + thumbnail_url = element.selectFirst("div.cover-image")?.let { + it.attr("style").substringAfter("url('").substringBefore("'") + } ?: "" + title = element.selectFirst("span.series-title")!!.text() } // =============================== Latest =============================== - override fun latestUpdatesRequest(page: Int): Request { return if (page == 1) { GET("$baseUrl/animes-legendados/") @@ -91,41 +81,27 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun latestUpdatesNextPageSelector(): String = "div.paginadorplay > a.active + a" - override fun latestUpdatesFromElement(element: Element): SAnime { - return SAnime.create().apply { - setUrlWithoutDomain( - element.selectFirst("div.aniItem > a[href]")!!.attr("href").toHttpUrl().encodedPath, - ) - thumbnail_url = element.selectFirst("div.aniItemImg img[src]")?.attr("src") ?: "" - title = element.selectFirst("h2.aniTitulo")!!.text() - } + override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { + setUrlWithoutDomain(element.selectFirst("div.aniItem > a[href]")!!.attr("abs:href")) + thumbnail_url = element.selectFirst("div.aniItemImg img[src]")?.attr("abs:src") + title = element.selectFirst("h2.aniTitulo")!!.text() } // =============================== Search =============================== - - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("Not used") - - override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable { + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { val params = AnimesZoneFilters.getSearchParameters(filters) - return client.newCall(searchAnimeRequest(page, query, params)) - .asObservableSuccess() - .map { response -> - searchAnimeParse(response) - } - } - private fun searchAnimeRequest(page: Int, query: String, filters: AnimesZoneFilters.FilterSearchParams): Request { val cleanQuery = query.replace(" ", "%20") val queryQuery = if (query.isBlank()) { "" } else { "&_pesquisa=$cleanQuery" } - val url = "$baseUrl/?s=${filters.genre}${filters.year}${filters.version}${filters.studio}${filters.type}${filters.adult}$queryQuery" + val url = "$baseUrl/?s=${params.genre}${params.year}${params.version}${params.studio}${params.type}${params.adult}$queryQuery" val httpParams = url.substringAfter("$baseUrl/?").split("&").joinToString(",") { - val data = it.split("=") - "\"${data[0]}\":\"${data[1]}\"" + val (key, value) = it.split("=", limit = 2) + "\"$key\":\"$value\"" } val softRefresh = if (page == 1) 0 else 1 val jsonBody = """ @@ -134,22 +110,22 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() { "data":{ "facets":{ "generos":[ - ${queryToJsonList(filters.genre)} + ${queryToJsonList(params.genre)} ], "versao":[ - ${queryToJsonList(filters.year)} + ${queryToJsonList(params.year)} ], "tipo":[ - ${queryToJsonList(filters.version)} + ${queryToJsonList(params.version)} ], "estudio":[ - ${queryToJsonList(filters.studio)} + ${queryToJsonList(params.studio)} ], "tipototal":[ - ${queryToJsonList(filters.type)} + ${queryToJsonList(params.type)} ], "adulto":[ - ${queryToJsonList(filters.adult)} + ${queryToJsonList(params.adult)} ], "pesquisa":"$query", "paginar":[ @@ -180,112 +156,90 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() { } """.trimIndent().toRequestBody("application/json".toMediaType()) - val postHeaders = headers.newBuilder() - .add("Accept", "*/*") - .add("Content-Type", "application/json") - .add("Host", baseUrl.toHttpUrl().host) - .add("Origin", baseUrl) - .add("Referer", url) - .build() - - return POST(url, body = jsonBody, headers = postHeaders) + return POST(url, headers, jsonBody) } override fun searchAnimeParse(response: Response): AnimesPage { - val parsed = json.decodeFromString(response.body.string()) + val parsed = response.parseAs() val document = Jsoup.parse(parsed.template) - val animes = document.select(searchAnimeSelector()).map { element -> - searchAnimeFromElement(element) - } + val animes = document.select(searchAnimeSelector()).map(::searchAnimeFromElement) - return AnimesPage(animes, parsed.settings.pager.page < parsed.settings.pager.total_pages) + val hasNextPage = parsed.settings.pager.run { page < total_pages } + + return AnimesPage(animes, hasNextPage) } override fun searchAnimeSelector(): String = "div.aniItem" override fun searchAnimeNextPageSelector(): String? = null - override fun searchAnimeFromElement(element: Element): SAnime { - return SAnime.create().apply { - setUrlWithoutDomain( - element.selectFirst("a[href]")!!.attr("href").toHttpUrl().encodedPath, - ) - thumbnail_url = element.selectFirst("img[src]")?.attr("src") ?: "" - title = element.selectFirst("div.aniInfos")?.text() ?: "Anime" - } + override fun searchAnimeFromElement(element: Element) = SAnime.create().apply { + setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href")) + thumbnail_url = element.selectFirst("img[src]")?.attr("abs:src") ?: "" + title = element.selectFirst("div.aniInfos")?.text() ?: "Anime" } // ============================== FILTERS =============================== - override fun getFilterList(): AnimeFilterList = AnimesZoneFilters.FILTER_LIST // =========================== Anime Details ============================ - - override fun animeDetailsParse(document: Document): SAnime { - return SAnime.create().apply { - title = document.selectFirst("div.container > div div.bottom-div > h4")?.ownText() ?: "Anime" - thumbnail_url = document.selectFirst("div.container > div > img[src]")?.attr("src") - description = document.selectFirst("section#sinopse p:matches(.)")?.text() - genre = document.select("div.card-body table > tbody > tr:has(>td:contains(Genres)) td > a").joinToString(", ") { it.text() } - } + override fun animeDetailsParse(document: Document) = SAnime.create().apply { + title = document.selectFirst("div.container > div div.bottom-div > h4")?.ownText() + ?: document.selectFirst("div#info > h1")?.text() + ?: "Anime" + thumbnail_url = document.selectFirst("div.container > div > img[src]")?.attr("abs:src") + description = document.selectFirst("section#sinopse p:matches(.)")?.text() + ?: document.selectFirst("div.content.right > dialog > p:matches(.)")?.text() + genre = document.select("div.card-body table > tbody > tr:has(>td:contains(Genres)) td > a").joinToString { it.text() } } // ============================== Episodes ============================== - override fun episodeListParse(response: Response): List { - val episodeList = mutableListOf() - val document = response.asJsoup() + val document = response.use { it.asJsoup() } - // Used only for fallback - var counter = 1 val singleVideo = document.selectFirst("div.anime__video__player") // First check if single episode - if (singleVideo != null) { - val episode = SEpisode.create() - episode.name = document.selectFirst("div#info h1")?.text() ?: "Episódio" - episode.episode_number = 1F - episode.setUrlWithoutDomain(response.request.url.encodedPath) - episodeList.add(episode) + return if (singleVideo != null) { + SEpisode.create().apply { + name = document.selectFirst("div#info h1")?.text() ?: "Episódio" + episode_number = 1F + setUrlWithoutDomain(document.location()) + }.let(::listOf) } else { - document.select(episodeListSelector()).forEach { ep -> - val name = ep.selectFirst("h2.aniTitulo")?.text()?.trim() - // Check if it's multi-season - if (name != null && name.startsWith("temporada ", true)) { - var nextPageUrl: String? = ep.selectFirst("a[href]")!!.attr("href") + buildList { + document.select(episodeListSelector()).forEach { ep -> + val name = ep.selectFirst("h2.aniTitulo")?.text()?.trim() + // Check if it's multi-season + var nextPageUrl = when { + name != null && name.startsWith("temporada ", true) -> ep.selectFirst("a[href]")!!.attr("href") + else -> { + add(episodeFromElement(ep, size + 1)) + document.nextPageUrl() + } + } + while (nextPageUrl != null) { - val seasonDocument = client.newCall(GET(nextPageUrl)).execute().asJsoup() + val seasonDocument = client.newCall(GET(nextPageUrl)).execute() + .use { it.asJsoup() } seasonDocument.select(episodeListSelector()).forEach { seasonEp -> - episodeList.add(episodeFromElement(seasonEp, counter, name)) - counter++ + add(episodeFromElement(seasonEp, size + 1, name)) } - nextPageUrl = seasonDocument.selectFirst("div.paginadorplay > a:contains(Próxima Pagina)")?.absUrl("href") - } - } else { - episodeList.add(episodeFromElement(ep, counter)) - counter++ - - var nextPageUrl: String? = document.selectFirst("div.paginadorplay > a:contains(Próxima Pagina)")?.absUrl("href") - while (nextPageUrl != null) { - val document = client.newCall(GET(nextPageUrl)).execute().asJsoup() - document.select(episodeListSelector()).forEach { ep -> - episodeList.add(episodeFromElement(ep, counter)) - counter++ - } - - nextPageUrl = document.selectFirst("div.paginadorplay > a:contains(Próxima Pagina)")?.absUrl("href") + nextPageUrl = seasonDocument.nextPageUrl() } } + + reverse() } } - - return episodeList.reversed() } + private fun Document.nextPageUrl() = selectFirst("div.paginadorplay > a:contains(Próxima Pagina)")?.absUrl("href") + override fun episodeListSelector(): String = "main.site-main ul.post-lst > li" private fun episodeFromElement(element: Element, counter: Int, info: String? = null): SEpisode { @@ -299,122 +253,124 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() { it.text().trim().toFloatOrNull() } ?: counter.toFloat() scanlator = info - setUrlWithoutDomain( - element.selectFirst("article > a")!!.attr("href").toHttpUrl().encodedPath, - ) + setUrlWithoutDomain(element.selectFirst("article > a")!!.attr("href")) } } override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not used") // ============================ Video Links ============================= - override fun videoListParse(response: Response): List