fix(pt/anitube): Fixed pt/Anitube and pt/HinataSoul sources (#167)

This commit is contained in:
WebDitto 2024-08-21 21:15:27 -03:00 committed by GitHub
parent 5ddb5f061b
commit 36ebc198d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 211 additions and 45 deletions

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Anitube' extName = 'Anitube'
extClass = '.Anitube' extClass = '.Anitube'
extVersionCode = 15 extVersionCode = 16
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -38,7 +38,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
override fun headersBuilder() = super.headersBuilder() override fun headersBuilder() = super.headersBuilder()
.add("Referer", baseUrl) .add("Referer", "$baseUrl/")
.add("Accept-Language", ACCEPT_LANGUAGE) .add("Accept-Language", ACCEPT_LANGUAGE)
// ============================== Popular =============================== // ============================== Popular ===============================
@ -78,7 +78,11 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
// =============================== Search =============================== // =============================== 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)) { return if (query.startsWith(PREFIX_SEARCH)) {
val path = query.removePrefix(PREFIX_SEARCH) val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/$path")) client.newCall(GET("$baseUrl/$path"))
@ -97,6 +101,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return AnimesPage(listOf(details), false) return AnimesPage(listOf(details), false)
} }
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 {
@ -108,7 +113,14 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val char = params.initialChar val char = params.initialChar
when { when {
season.isNotBlank() -> "$baseUrl/temporada/$season/$year" 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 -> "$baseUrl/anime/page/$page/letra/$char"
} }
} else { } else {
@ -176,7 +188,9 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
// ============================ Video Links ============================= // ============================ 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 videoListSelector() = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException() override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()

View file

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.anitube.extractors package eu.kanade.tachiyomi.animeextension.pt.anitube.extractors
import android.util.Log
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
@ -8,9 +9,90 @@ import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import java.util.Calendar
import java.util.Date
object AnitubeExtractor { class AnitubeExtractor(private val headers: Headers, private val client: OkHttpClient) {
fun getVideoList(response: Response, headers: Headers, client: OkHttpClient): List<Video> {
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 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]")!!
@ -28,37 +110,7 @@ object AnitubeExtractor {
} }
} + listOf("appfullhd") } + listOf("appfullhd")
val videoName = serverUrl.split('/').last() val authCode = getAuthCode(serverUrl, thumbUrl)
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('"')
return qualities.mapIndexed { index, quality -> return qualities.mapIndexed { index, quality ->
val path = paths[index] val path = paths[index]
@ -66,4 +118,9 @@ object AnitubeExtractor {
Video(url, quality, url, headers = headers) Video(url, quality, url, headers = headers)
}.reversed() }.reversed()
} }
companion object {
val ADS_URL = "https://ads.anitube.vip"
val SITE_URL = "https://www.anitube.vip"
}
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Hinata Soul' extName = 'Hinata Soul'
extClass = '.HinataSoul' extClass = '.HinataSoul'
extVersionCode = 5 extVersionCode = 6
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -162,7 +162,7 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
// ============================ Video Links ============================= // ============================ 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) override fun videoListParse(response: Response) = extractor.getVideoList(response)

View file

@ -1,11 +1,96 @@
package eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors package eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors
import android.util.Log
import eu.kanade.tachiyomi.animesource.model.Video 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 eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Response 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> { fun getVideoList(response: Response): List<Video> {
val doc = response.asJsoup() val doc = response.asJsoup()
@ -13,19 +98,29 @@ class HinataSoulExtractor(private val headers: Headers) {
val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!! val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!!
.attr("content") .attr("content")
.replace("cdn1", "cdn3") .replace("cdn1", "cdn3")
val type = serverUrl.split('/').get(3) val thumbUrl = doc.selectFirst("meta[itemprop=thumbnailUrl]")!!
val qualities = listOfNotNull("SD", "HD", "FULLHD".takeIf { hasFHD }) .attr("content")
val type = serverUrl.split("/").get(3)
val qualities = listOfNotNull("SD", "HD", if (hasFHD) "FULLHD" else null)
val paths = listOf("appsd", "apphd").let { val paths = listOf("appsd", "apphd").let {
if (type.endsWith("2")) { if (type.endsWith("2")) {
it.map { path -> path + "2" } it.map { path -> path + "2" }
} else { } else {
it it
} }
} + listOfNotNull("appfullhd".takeIf { hasFHD }) } + listOf("appfullhd")
val authCode = getAuthCode(serverUrl, thumbUrl)
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) + authCode
Video(url, quality, url, headers = headers) Video(url, quality, url, headers = headers)
}.reversed() }.reversed()
} }
companion object {
val ADS_URL = "https://ads.anitube.vip"
val SITE_URL = "https://www.anitube.vip"
}
} }