From 36ebc198d3d5fa0b5220bc9a08c42917a20fd4e7 Mon Sep 17 00:00:00 2001 From: WebDitto <webditto@proton.me> Date: Wed, 21 Aug 2024 21:15:27 -0300 Subject: [PATCH] fix(pt/anitube): Fixed pt/Anitube and pt/HinataSoul sources (#167) --- src/pt/anitube/build.gradle | 2 +- .../animeextension/pt/anitube/Anitube.kt | 22 +++- .../pt/anitube/extractors/AnitubeExtractor.kt | 123 +++++++++++++----- src/pt/hinatasoul/build.gradle | 2 +- .../pt/hinatasoul/HinataSoul.kt | 2 +- .../extractors/HinataSoulExtractor.kt | 105 ++++++++++++++- 6 files changed, 211 insertions(+), 45 deletions(-) diff --git a/src/pt/anitube/build.gradle b/src/pt/anitube/build.gradle index d88cedbb..9b47e747 100644 --- a/src/pt/anitube/build.gradle +++ b/src/pt/anitube/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Anitube' extClass = '.Anitube' - extVersionCode = 15 + extVersionCode = 16 } apply from: "$rootDir/common.gradle" diff --git a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/Anitube.kt b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/Anitube.kt index d5ab8012..63937fde 100644 --- a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/Anitube.kt +++ b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/Anitube.kt @@ -38,7 +38,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() { } override fun headersBuilder() = super.headersBuilder() - .add("Referer", baseUrl) + .add("Referer", "$baseUrl/") .add("Accept-Language", ACCEPT_LANGUAGE) // ============================== Popular =============================== @@ -78,7 +78,11 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() // =============================== Search =============================== - override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage { + override suspend fun getSearchAnime( + page: Int, + query: String, + filters: AnimeFilterList, + ): AnimesPage { return if (query.startsWith(PREFIX_SEARCH)) { val path = query.removePrefix(PREFIX_SEARCH) client.newCall(GET("$baseUrl/$path")) @@ -97,6 +101,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() { return AnimesPage(listOf(details), false) } + override fun getFilterList(): AnimeFilterList = AnitubeFilters.FILTER_LIST override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { @@ -108,7 +113,14 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() { val char = params.initialChar when { season.isNotBlank() -> "$baseUrl/temporada/$season/$year" - genre.isNotBlank() -> "$baseUrl/genero/$genre/page/$page/${char.replace("todos", "")}" + genre.isNotBlank() -> + "$baseUrl/genero/$genre/page/$page/${ + char.replace( + "todos", + "", + ) + }" + else -> "$baseUrl/anime/page/$page/letra/$char" } } else { @@ -176,7 +188,9 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() { } // ============================ Video Links ============================= - override fun videoListParse(response: Response) = AnitubeExtractor.getVideoList(response, headers, client) + private val extractor by lazy { AnitubeExtractor(headers, client) } + + override fun videoListParse(response: Response) = extractor.getVideoList(response) override fun videoListSelector() = throw UnsupportedOperationException() override fun videoFromElement(element: Element) = throw UnsupportedOperationException() override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() diff --git a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeExtractor.kt b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeExtractor.kt index e2c3854c..7d218de9 100644 --- a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeExtractor.kt +++ b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeExtractor.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.animeextension.pt.anitube.extractors +import android.util.Log import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST @@ -8,9 +9,90 @@ import okhttp3.FormBody import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Response +import java.util.Calendar +import java.util.Date -object AnitubeExtractor { - fun getVideoList(response: Response, headers: Headers, client: OkHttpClient): List<Video> { +class AnitubeExtractor(private val headers: Headers, private val client: OkHttpClient) { + + private var authCodeCache: String = "" + private var authCodeDate: Date = Calendar.getInstance().getTime() + + private fun getAuthCode(serverUrl: String, thumbUrl: String): String { + val duration = Calendar.getInstance().getTime().time - authCodeDate.time + + // check the authCode in cache for 1 hour + if (authCodeCache.isNotBlank() && duration < 60 * 60 * 1000) { + Log.d("AnitubeExtractor", "Using authCode from cache") + return authCodeCache + } + + Log.d("AnitubeExtractor", "Fetching new authCode") + val videoName = serverUrl.split('/').last() + + val adsUrl = + client.newCall(GET("$SITE_URL/playerricas.php?name=apphd/$videoName&img=$thumbUrl&url=$serverUrl")) + .execute() + .body.string() + .substringAfter("ADS_URL") + .substringAfter('"') + .substringBefore('"') + + Log.d("AnitubeExtractor", "ADS URL: $adsUrl") + val adsContent = client.newCall(GET(adsUrl)).execute().body.string() + + val body = FormBody.Builder() + .add("category", "client") + .add("type", "premium") + .add("ad", adsContent) + .build() + + val newHeaders = headers.newBuilder() + .set("Referer", SITE_URL) + .add("Accept", "*/*") + .add("Cache-Control", "no-cache") + .add("Pragma", "no-cache") + .add("Connection", "keep-alive") + .add("Sec-Fetch-Dest", "empty") + .add("Sec-Fetch-Mode", "cors") + .add("Sec-Fetch-Site", "same-site") + .build() + + val publicidade = + client.newCall(POST("$ADS_URL/", headers = newHeaders, body = body)) + .execute() + .body.string() + .substringAfter("\"publicidade\"") + .substringAfter('"') + .substringBefore('"') + + if (publicidade.isBlank()) { + Log.e("AnitubeExtractor", "Failed to fetch \"publicidade\" code") + return "" + } + + authCodeCache = + client.newCall( + GET( + "$ADS_URL/?token=$publicidade", + headers = newHeaders, + ), + ) + .execute() + .body.string() + .substringAfter("\"publicidade\"") + .substringAfter('"') + .substringBefore('"') + + if (authCodeCache.isBlank()) { + Log.e("AnitubeExtractor", "Failed to fetch auth code") + } else { + Log.d("AnitubeExtractor", "Auth code fetched successfully") + } + + return authCodeCache + } + + fun getVideoList(response: Response): List<Video> { val doc = response.asJsoup() val hasFHD = doc.selectFirst("div.abaItem:contains(FULLHD)") != null val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!! @@ -28,37 +110,7 @@ object AnitubeExtractor { } } + listOf("appfullhd") - val videoName = serverUrl.split('/').last() - - val adsUrl = - client.newCall(GET("https://www.anitube.vip/playerricas.php?name=apphd/$videoName&img=$thumbUrl&url=$serverUrl")) - .execute() - .body.string() - .substringAfter("ADS_URL") - .substringAfter('"') - .substringBefore('"') - - val adsContent = client.newCall(GET(adsUrl)).execute().body.string() - - val body = FormBody.Builder() - .add("category", "client") - .add("type", "premium") - .add("ad", adsContent) - .build() - - val publicidade = client.newCall(POST("https://ads.anitube.vip/", body = body)) - .execute() - .body.string() - .substringAfter("\"publicidade\"") - .substringAfter('"') - .substringBefore('"') - - val authCode = client.newCall(GET("https://ads.anitube.vip/?token=$publicidade")) - .execute() - .body.string() - .substringAfter("\"publicidade\"") - .substringAfter('"') - .substringBefore('"') + val authCode = getAuthCode(serverUrl, thumbUrl) return qualities.mapIndexed { index, quality -> val path = paths[index] @@ -66,4 +118,9 @@ object AnitubeExtractor { Video(url, quality, url, headers = headers) }.reversed() } + + companion object { + val ADS_URL = "https://ads.anitube.vip" + val SITE_URL = "https://www.anitube.vip" + } } diff --git a/src/pt/hinatasoul/build.gradle b/src/pt/hinatasoul/build.gradle index 09a04203..d35ece40 100644 --- a/src/pt/hinatasoul/build.gradle +++ b/src/pt/hinatasoul/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Hinata Soul' extClass = '.HinataSoul' - extVersionCode = 5 + extVersionCode = 6 } apply from: "$rootDir/common.gradle" diff --git a/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HinataSoul.kt b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HinataSoul.kt index e641cfe4..0bdb4ec3 100644 --- a/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HinataSoul.kt +++ b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HinataSoul.kt @@ -162,7 +162,7 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() { } // ============================ Video Links ============================= - private val extractor by lazy { HinataSoulExtractor(headers) } + private val extractor by lazy { HinataSoulExtractor(headers, client) } override fun videoListParse(response: Response) = extractor.getVideoList(response) diff --git a/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/extractors/HinataSoulExtractor.kt b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/extractors/HinataSoulExtractor.kt index fd9c551b..e4a6bbde 100644 --- a/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/extractors/HinataSoulExtractor.kt +++ b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/extractors/HinataSoulExtractor.kt @@ -1,11 +1,96 @@ package eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors +import android.util.Log import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.FormBody import okhttp3.Headers +import okhttp3.OkHttpClient import okhttp3.Response +import java.util.Calendar +import java.util.Date -class HinataSoulExtractor(private val headers: Headers) { +class HinataSoulExtractor(private val headers: Headers, private val client: OkHttpClient) { + + private var authCodeCache: String = "" + private var authCodeDate: Date = Calendar.getInstance().getTime() + + private fun getAuthCode(serverUrl: String, thumbUrl: String): String { + val duration = Calendar.getInstance().getTime().time - authCodeDate.time + + // check the authCode in cache for 1 hour + if (authCodeCache.isNotBlank() && duration < 60 * 60 * 1000) { + Log.d("HinataSoulExtractor", "Using authCode from cache") + return authCodeCache + } + + Log.d("HinataSoulExtractor", "Fetching new authCode") + val videoName = serverUrl.split('/').last() + + val adsUrl = + client.newCall(GET("$SITE_URL/playerricas.php?name=apphd/$videoName&img=$thumbUrl&url=$serverUrl")) + .execute() + .body.string() + .substringAfter("ADS_URL") + .substringAfter('"') + .substringBefore('"') + + Log.d("HinataSoulExtractor", "ADS URL: $adsUrl") + val adsContent = client.newCall(GET(adsUrl)).execute().body.string() + + val body = FormBody.Builder() + .add("category", "client") + .add("type", "premium") + .add("ad", adsContent) + .build() + + val newHeaders = headers.newBuilder() + .set("Referer", SITE_URL) + .add("Accept", "*/*") + .add("Cache-Control", "no-cache") + .add("Pragma", "no-cache") + .add("Connection", "keep-alive") + .add("Sec-Fetch-Dest", "empty") + .add("Sec-Fetch-Mode", "cors") + .add("Sec-Fetch-Site", "same-site") + .build() + + val publicidade = + client.newCall(POST("$ADS_URL/", headers = newHeaders, body = body)) + .execute() + .body.string() + .substringAfter("\"publicidade\"") + .substringAfter('"') + .substringBefore('"') + + if (publicidade.isBlank()) { + Log.e("HinataSoulExtractor", "Failed to fetch \"publicidade\" code") + return "" + } + + authCodeCache = + client.newCall( + GET( + "$ADS_URL/?token=$publicidade", + headers = newHeaders, + ), + ) + .execute() + .body.string() + .substringAfter("\"publicidade\"") + .substringAfter('"') + .substringBefore('"') + + if (authCodeCache.isBlank()) { + Log.e("HinataSoulExtractor", "Failed to fetch auth code") + } else { + Log.d("HinataSoulExtractor", "Auth code fetched successfully") + } + + return authCodeCache + } fun getVideoList(response: Response): List<Video> { val doc = response.asJsoup() @@ -13,19 +98,29 @@ class HinataSoulExtractor(private val headers: Headers) { val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!! .attr("content") .replace("cdn1", "cdn3") - val type = serverUrl.split('/').get(3) - val qualities = listOfNotNull("SD", "HD", "FULLHD".takeIf { hasFHD }) + val thumbUrl = doc.selectFirst("meta[itemprop=thumbnailUrl]")!! + .attr("content") + val type = serverUrl.split("/").get(3) + val qualities = listOfNotNull("SD", "HD", if (hasFHD) "FULLHD" else null) val paths = listOf("appsd", "apphd").let { if (type.endsWith("2")) { it.map { path -> path + "2" } } else { it } - } + listOfNotNull("appfullhd".takeIf { hasFHD }) + } + listOf("appfullhd") + + val authCode = getAuthCode(serverUrl, thumbUrl) + return qualities.mapIndexed { index, quality -> val path = paths[index] - val url = serverUrl.replace(type, path) + val url = serverUrl.replace(type, path) + authCode Video(url, quality, url, headers = headers) }.reversed() } + + companion object { + val ADS_URL = "https://ads.anitube.vip" + val SITE_URL = "https://www.anitube.vip" + } }