fix(pt/anitube): Fixed Anitube and HinataSoul for old videos (#189)

This commit is contained in:
WebDitto 2024-08-29 13:54:14 -03:00 committed by GitHub
parent dec0bd412c
commit c7b610a444
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 425 additions and 83 deletions

View file

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

View file

@ -4,6 +4,7 @@ import android.app.Application
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.anitube.extractors.AnitubeDownloadExtractor
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
@ -15,6 +16,7 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -189,9 +191,29 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
// ============================ Video Links ============================= // ============================ Video Links =============================
private val extractor by lazy { AnitubeExtractor(headers, client, preferences) } private val anitubeExtractor by lazy { AnitubeExtractor(headers, client, preferences) }
private val downloadExtractor by lazy { AnitubeDownloadExtractor(headers, client) }
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val links = mutableListOf(document.location())
document.selectFirst("div.abaItemDown > a")?.attr("href")?.let {
links.add(it)
}
val epName = document.selectFirst("meta[itemprop=name]")!!.attr("content")
return links.parallelCatchingFlatMapBlocking {
when {
it.contains("/download/") -> downloadExtractor.videosFromUrl(it, epName)
it.contains("file4go.net") -> downloadExtractor.videosFromUrl(it, epName)
else -> anitubeExtractor.getVideoList(document)
}
}
}
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()
@ -264,8 +286,11 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return sortedWith( return sortedWith(
compareByDescending { it.quality.equals(quality) }, compareBy<Video>(
) { it.quality.startsWith(quality) },
{ PREF_QUALITY_ENTRIES.indexOf(it.quality.substringBefore(" ")) },
).thenByDescending { it.quality },
).reversed()
} }
private fun String.toDate(): Long { private fun String.toDate(): Long {

View file

@ -0,0 +1,98 @@
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
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelMapNotNullBlocking
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
class AnitubeDownloadExtractor(
private val headers: Headers,
private val client: OkHttpClient,
) {
private val qualities = listOf("SD", "HD", "FULLHD")
private val tag by lazy { javaClass.simpleName }
private fun videosFromFile4Go(url: String, quality: String): Video? {
Log.d(tag, "Checking download for $url")
val docDownload = client.newCall(GET(url)).execute().asJsoup()
val form =
docDownload.selectFirst("button.download")?.closest("form")
if (form == null) {
Log.d(tag, "Download form not found for $url")
return null
}
val body = FormBody.Builder().apply {
form.select("input[name]").forEach {
add(it.attr("name"), it.attr("value"))
}
}.build()
val postUrl = form.attr("action")
val postHeaders = headers.newBuilder()
.set("Referer", url)
.build()
val docFinal =
client.newCall(POST(postUrl, headers = postHeaders, body = body))
.execute().asJsoup()
val videoUrl = docFinal.selectFirst("a.novobotao.download")?.attr("href")
if (videoUrl == null) {
Log.d(tag, "Download link not found for $url")
return null
}
return Video(videoUrl, "$quality - File4Go", videoUrl)
}
private fun videosFromDownloadPage(url: String, epName: String): List<Video> {
Log.d(tag, "Extracting videos links for URL: $url")
val docDownload = client.newCall(GET(url)).execute().asJsoup()
val row = docDownload.select("table.downloadpag_episodios tr").firstOrNull {
it.text().contains(epName)
}
if (row == null) {
Log.d(tag, "Episode $epName not found in download page")
return emptyList()
}
val links = row.select("td").mapIndexedNotNull { index, el ->
val link = el.selectFirst("a") ?: return@mapIndexedNotNull null
object {
var quality = qualities.get(index - 1)
var url = link.attr("href")
}
}
Log.d(tag, "Found ${links.size} links for $epName")
return links.parallelMapNotNullBlocking {
if (!it.url.contains("file4go.net")) {
return@parallelMapNotNullBlocking null
}
videosFromFile4Go(it.url, it.quality)
}.reversed()
}
fun videosFromUrl(url: String, epName: String, quality: String = "Default"): List<Video> {
if (url.contains("file4go.net")) {
return listOfNotNull(videosFromFile4Go(url, quality))
}
return videosFromDownloadPage(url, epName)
}
}

View file

@ -6,11 +6,14 @@ 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
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelMapNotNullBlocking
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import org.jsoup.nodes.Document
import java.net.ProtocolException
class AnitubeExtractor( class AnitubeExtractor(
private val headers: Headers, private val headers: Headers,
@ -20,6 +23,32 @@ class AnitubeExtractor(
private val tag by lazy { javaClass.simpleName } private val tag by lazy { javaClass.simpleName }
private data class VideoExists(
val exists: Boolean,
val code: Int,
)
private fun checkVideoExists(url: String): VideoExists {
try {
val request = Request.Builder()
.head()
.url(url)
.headers(headers)
.build()
val response = client.newCall(request).execute()
return VideoExists(response.isSuccessful, response.code)
} catch (e: ProtocolException) {
// There are a bug in the response that sometimes that the content is without headers
if (e.message?.contains("Unexpected status line") == true) {
return VideoExists(true, 200)
}
}
return VideoExists(false, 404)
}
private fun getAdsUrl( private fun getAdsUrl(
serverUrl: String, serverUrl: String,
thumbUrl: String, thumbUrl: String,
@ -28,15 +57,21 @@ class AnitubeExtractor(
): String { ): String {
val videoName = serverUrl.split('/').last() val videoName = serverUrl.split('/').last()
Log.d(tag, "Accessing the link $link") val finalLink =
val response = client.newCall(GET(link, headers = linkHeaders)).execute() if (link.startsWith("//")) {
"https:$link"
} else {
link
}
Log.d(tag, "Accessing the link $finalLink")
val response = client.newCall(GET(finalLink, headers = linkHeaders)).execute()
val docLink = response.asJsoup() val docLink = response.asJsoup()
val refresh = docLink.selectFirst("meta[http-equiv=refresh]")?.attr("content") val refresh = docLink.selectFirst("meta[http-equiv=refresh]")?.attr("content")
if (!refresh.isNullOrBlank()) { if (!refresh.isNullOrBlank()) {
val newLink = refresh.substringAfter("=") val newLink = refresh.substringAfter("=")
val newHeaders = linkHeaders.newBuilder().set("Referer", link).build() val newHeaders = linkHeaders.newBuilder().set("Referer", finalLink).build()
Log.d(tag, "Following link redirection to $newLink") Log.d(tag, "Following link redirection to $newLink")
return getAdsUrl(serverUrl, thumbUrl, newLink, newHeaders) return getAdsUrl(serverUrl, thumbUrl, newLink, newHeaders)
@ -47,30 +82,32 @@ class AnitubeExtractor(
Log.d(tag, "Final URL: $referer") Log.d(tag, "Final URL: $referer")
Log.d(tag, "Fetching ADS URL") Log.d(tag, "Fetching ADS URL")
val newHeaders = linkHeaders.newBuilder().set("Referer", referer).build() val newHeaders =
linkHeaders.newBuilder().set("Referer", "https://${referer.toHttpUrl().host}/").build()
try { try {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val adsUrl = val body = client.newCall(
client.newCall( GET(
GET( "$SITE_URL?name=apphd/$videoName&img=$thumbUrl&pais=pais=BR&time=$now&url=$serverUrl",
"$SITE_URL/playerricas.php?name=apphd/$videoName&img=$thumbUrl&pais=pais=BR&time=$now&url=$serverUrl", headers = newHeaders,
headers = newHeaders, ),
), )
) .execute()
.execute() .body.string()
.body.string()
.let { val adsUrl = body.let {
Regex("""ADS_URL\s*=\s*['"]([^'"]+)['"]""") Regex("""ADS_URL\s*=\s*['"]([^'"]+)['"]""")
.find(it)?.groups?.get(1)?.value .find(it)?.groups?.get(1)?.value
?: "" ?: ""
} }
if (adsUrl.startsWith("http")) { if (adsUrl.startsWith("http")) {
Log.d(tag, "ADS URL: $adsUrl") Log.d(tag, "ADS URL: $adsUrl")
return adsUrl return adsUrl
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, e.toString())
} }
// Try default url // Try default url
@ -84,15 +121,9 @@ class AnitubeExtractor(
if (authCode.isNotBlank()) { if (authCode.isNotBlank()) {
Log.d(tag, "AuthCode found in preferences") Log.d(tag, "AuthCode found in preferences")
val request = Request.Builder() val response = checkVideoExists("${serverUrl}$authCode")
.head()
.url("${serverUrl}$authCode")
.headers(headers)
.build()
val response = client.newCall(request).execute() if (response.exists || response.code == 500) {
if (response.isSuccessful || response.code == 500) {
Log.d(tag, "AuthCode is OK") Log.d(tag, "AuthCode is OK")
return authCode return authCode
} }
@ -112,7 +143,7 @@ class AnitubeExtractor(
.build() .build()
val newHeaders = headers.newBuilder() val newHeaders = headers.newBuilder()
.set("Referer", SITE_URL) .set("Referer", "https://${SITE_URL.toHttpUrl().host}/")
.add("Accept", "*/*") .add("Accept", "*/*")
.add("Cache-Control", "no-cache") .add("Cache-Control", "no-cache")
.add("Pragma", "no-cache") .add("Pragma", "no-cache")
@ -165,8 +196,7 @@ class AnitubeExtractor(
return authCode return authCode
} }
fun getVideoList(response: Response): List<Video> { fun getVideoList(doc: Document): List<Video> {
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]")!!
.attr("content") .attr("content")
@ -188,16 +218,27 @@ class AnitubeExtractor(
val authCode = getAuthCode(serverUrl, thumbUrl, firstLink) val authCode = getAuthCode(serverUrl, thumbUrl, firstLink)
return qualities.mapIndexed { index, quality -> return qualities
val path = paths[index] .mapIndexed { index, quality ->
val url = serverUrl.replace(type, path) + authCode object {
Video(url, quality, url, headers = headers) var path = paths[index]
}.reversed() var url = serverUrl.replace(type, path) + authCode
var quality = "$quality - Anitube"
}
}
.parallelMapNotNullBlocking {
if (!checkVideoExists(it.url).exists) {
Log.d(tag, "Video not exists: ${it.url.substringBefore("?")}")
return@parallelMapNotNullBlocking null
}
Video(it.url, it.quality, it.url, headers = headers)
}
.reversed()
} }
companion object { companion object {
private const val PREF_AUTHCODE_KEY = "authcode" private const val PREF_AUTHCODE_KEY = "authcode"
private const val ADS_URL = "https://ads.anitube.vip" private const val ADS_URL = "https://ads.anitube.vip"
private const val SITE_URL = "https://www.anitube.vip" private const val SITE_URL = "https://www.anitube.vip/playerricas.php"
} }
} }

View file

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

View file

@ -4,6 +4,7 @@ import android.app.Application
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors.HinataSoulDownloadExtractor
import eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors.HinataSoulExtractor import eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors.HinataSoulExtractor
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
@ -15,6 +16,7 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -67,7 +69,11 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesNextPageSelector() = null override fun latestUpdatesNextPageSelector() = null
// =============================== 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 slug = query.removePrefix(PREFIX_SEARCH) val slug = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/animes/$slug")) client.newCall(GET("$baseUrl/animes/$slug"))
@ -156,16 +162,47 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val title = element.attr("title") val title = element.attr("title")
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
name = title name = title
episode_number = title.substringBeforeLast(" - FINAL").substringAfterLast(" ").toFloatOrNull() ?: 0F episode_number =
title.substringBeforeLast(" - FINAL").substringAfterLast(" ").toFloatOrNull() ?: 0F
date_upload = element.selectFirst("div.lancaster_episodio_info_data")!! date_upload = element.selectFirst("div.lancaster_episodio_info_data")!!
.text() .text()
.toDate() .toDate()
} }
// ============================ Video Links ============================= // ============================ Video Links =============================
private val extractor by lazy { HinataSoulExtractor(headers, client, preferences) } private val hinataExtractor by lazy { HinataSoulExtractor(headers, client, preferences) }
private val downloadExtractor by lazy { HinataSoulDownloadExtractor(headers, client) }
override fun videoListParse(response: Response) = extractor.getVideoList(response) override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val links = mutableListOf(document.location())
val downloadsLinks = document.select("div.reportaBox .reportContent > a")
downloadsLinks.forEach {
it.attr("href")?.let {
links.add(it)
}
}
val epName = document.selectFirst("meta[itemprop=name]")!!.attr("content")
return links.parallelCatchingFlatMapBlocking { url ->
when {
url.contains("file4go.net") -> {
val quality =
downloadsLinks.first { it.attr("href") == url }
.textNodes().first().toString()
.trim().replace(" ", "")
downloadExtractor.videosFromUrl(url, epName, quality)
}
else -> hinataExtractor.getVideoList(document)
}
}
}
override fun videoListSelector() = throw UnsupportedOperationException() override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException() override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
@ -246,7 +283,10 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return sortedWith( return sortedWith(
compareBy { it.quality.contains(quality) }, compareBy<Video>(
{ it.quality.startsWith(quality) },
{ PREF_QUALITY_VALUES.indexOf(it.quality.substringBefore(" ")) },
).thenByDescending { it.quality },
).reversed() ).reversed()
} }

View file

@ -0,0 +1,98 @@
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 eu.kanade.tachiyomi.util.parallelMapNotNullBlocking
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
class HinataSoulDownloadExtractor(
private val headers: Headers,
private val client: OkHttpClient,
) {
private val qualities = listOf("SD", "HD", "FULLHD")
private val tag by lazy { javaClass.simpleName }
private fun videosFromFile4Go(url: String, quality: String): Video? {
Log.d(tag, "Checking download for $url")
val docDownload = client.newCall(GET(url)).execute().asJsoup()
val form =
docDownload.selectFirst("button.download")?.closest("form")
if (form == null) {
Log.d(tag, "Download form not found for $url")
return null
}
val body = FormBody.Builder().apply {
form.select("input[name]").forEach {
add(it.attr("name"), it.attr("value"))
}
}.build()
val postUrl = form.attr("action")
val postHeaders = headers.newBuilder()
.set("Referer", url)
.build()
val docFinal =
client.newCall(POST(postUrl, headers = postHeaders, body = body))
.execute().asJsoup()
val videoUrl = docFinal.selectFirst("a.novobotao.download")?.attr("href")
if (videoUrl == null) {
Log.d(tag, "Download link not found for $url")
return null
}
return Video(videoUrl, "$quality - File4Go", videoUrl)
}
private fun videosFromDownloadPage(url: String, epName: String): List<Video> {
Log.d(tag, "Extracting videos links for URL: $url")
val docDownload = client.newCall(GET(url)).execute().asJsoup()
val row = docDownload.select("table.downloadpag_episodios tr").firstOrNull {
it.text().contains(epName)
}
if (row == null) {
Log.d(tag, "Episode $epName not found in download page")
return emptyList()
}
val links = row.select("td").mapIndexedNotNull { index, el ->
val link = el.selectFirst("a") ?: return@mapIndexedNotNull null
object {
var quality = qualities.get(index - 1)
var url = link.attr("href")
}
}
Log.d(tag, "Found ${links.size} links for $epName")
return links.parallelMapNotNullBlocking {
if (!it.url.contains("file4go.net")) {
return@parallelMapNotNullBlocking null
}
videosFromFile4Go(it.url, it.quality)
}.reversed()
}
fun videosFromUrl(url: String, epName: String, quality: String = "Default"): List<Video> {
if (url.contains("file4go.net")) {
return listOfNotNull(videosFromFile4Go(url, quality))
}
return videosFromDownloadPage(url, epName)
}
}

View file

@ -6,11 +6,14 @@ 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
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelMapNotNullBlocking
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import org.jsoup.nodes.Document
import java.net.ProtocolException
class HinataSoulExtractor( class HinataSoulExtractor(
private val headers: Headers, private val headers: Headers,
@ -20,6 +23,32 @@ class HinataSoulExtractor(
private val tag by lazy { javaClass.simpleName } private val tag by lazy { javaClass.simpleName }
private data class VideoExists(
val exists: Boolean,
val code: Int,
)
private fun checkVideoExists(url: String): VideoExists {
try {
val request = Request.Builder()
.head()
.url(url)
.headers(headers)
.build()
val response = client.newCall(request).execute()
return VideoExists(response.isSuccessful, response.code)
} catch (e: ProtocolException) {
// There are a bug in the response that sometimes that the content is without headers
if (e.message?.contains("Unexpected status line") == true) {
return VideoExists(true, 200)
}
}
return VideoExists(false, 404)
}
private fun getAdsUrl( private fun getAdsUrl(
serverUrl: String, serverUrl: String,
thumbUrl: String, thumbUrl: String,
@ -28,15 +57,21 @@ class HinataSoulExtractor(
): String { ): String {
val videoName = serverUrl.split('/').last() val videoName = serverUrl.split('/').last()
Log.d(tag, "Accessing the link $link") val finalLink =
val response = client.newCall(GET(link, headers = linkHeaders)).execute() if (link.startsWith("//")) {
"https:$link"
} else {
link
}
Log.d(tag, "Accessing the link $finalLink")
val response = client.newCall(GET(finalLink, headers = linkHeaders)).execute()
val docLink = response.asJsoup() val docLink = response.asJsoup()
val refresh = docLink.selectFirst("meta[http-equiv=refresh]")?.attr("content") val refresh = docLink.selectFirst("meta[http-equiv=refresh]")?.attr("content")
if (!refresh.isNullOrBlank()) { if (!refresh.isNullOrBlank()) {
val newLink = refresh.substringAfter("=") val newLink = refresh.substringAfter("=")
val newHeaders = linkHeaders.newBuilder().set("Referer", link).build() val newHeaders = linkHeaders.newBuilder().set("Referer", finalLink).build()
Log.d(tag, "Following link redirection to $newLink") Log.d(tag, "Following link redirection to $newLink")
return getAdsUrl(serverUrl, thumbUrl, newLink, newHeaders) return getAdsUrl(serverUrl, thumbUrl, newLink, newHeaders)
@ -47,30 +82,31 @@ class HinataSoulExtractor(
Log.d(tag, "Final URL: $referer") Log.d(tag, "Final URL: $referer")
Log.d(tag, "Fetching ADS URL") Log.d(tag, "Fetching ADS URL")
val newHeaders = linkHeaders.newBuilder().set("Referer", referer).build() val newHeaders = linkHeaders.newBuilder().set("Referer", "https://${referer.toHttpUrl().host}/").build()
try { try {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val adsUrl = val body = client.newCall(
client.newCall( GET(
GET( "$SITE_URL?name=apphd/$videoName&img=$thumbUrl&pais=pais=BR&time=$now&url=$serverUrl",
"$SITE_URL/playerricas.php?name=apphd/$videoName&img=$thumbUrl&pais=pais=BR&time=$now&url=$serverUrl", headers = newHeaders,
headers = newHeaders, ),
), )
) .execute()
.execute() .body.string()
.body.string()
.let { val adsUrl = body.let {
Regex("""ADS_URL\s*=\s*['"]([^'"]+)['"]""") Regex("""ADS_URL\s*=\s*['"]([^'"]+)['"]""")
.find(it)?.groups?.get(1)?.value .find(it)?.groups?.get(1)?.value
?: "" ?: ""
} }
if (adsUrl.startsWith("http")) { if (adsUrl.startsWith("http")) {
Log.d(tag, "ADS URL: $adsUrl") Log.d(tag, "ADS URL: $adsUrl")
return adsUrl return adsUrl
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, e.toString())
} }
// Try default url // Try default url
@ -84,15 +120,9 @@ class HinataSoulExtractor(
if (authCode.isNotBlank()) { if (authCode.isNotBlank()) {
Log.d(tag, "AuthCode found in preferences") Log.d(tag, "AuthCode found in preferences")
val request = Request.Builder() val response = checkVideoExists("${serverUrl}$authCode")
.head()
.url("${serverUrl}$authCode")
.headers(headers)
.build()
val response = client.newCall(request).execute() if (response.exists || response.code == 500) {
if (response.isSuccessful || response.code == 500) {
Log.d(tag, "AuthCode is OK") Log.d(tag, "AuthCode is OK")
return authCode return authCode
} }
@ -112,7 +142,7 @@ class HinataSoulExtractor(
.build() .build()
val newHeaders = headers.newBuilder() val newHeaders = headers.newBuilder()
.set("Referer", SITE_URL) .set("Referer", "https://${SITE_URL.toHttpUrl().host}/")
.add("Accept", "*/*") .add("Accept", "*/*")
.add("Cache-Control", "no-cache") .add("Cache-Control", "no-cache")
.add("Pragma", "no-cache") .add("Pragma", "no-cache")
@ -165,8 +195,7 @@ class HinataSoulExtractor(
return authCode return authCode
} }
fun getVideoList(response: Response): List<Video> { fun getVideoList(doc: Document): List<Video> {
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]")!!
.attr("content") .attr("content")
@ -188,16 +217,27 @@ class HinataSoulExtractor(
val authCode = getAuthCode(serverUrl, thumbUrl, firstLink) val authCode = getAuthCode(serverUrl, thumbUrl, firstLink)
return qualities.mapIndexed { index, quality -> return qualities
val path = paths[index] .mapIndexed { index, quality ->
val url = serverUrl.replace(type, path) + authCode object {
Video(url, quality, url, headers = headers) var path = paths[index]
}.reversed() var url = serverUrl.replace(type, path) + authCode
var quality = "$quality - Anitube"
}
}
.parallelMapNotNullBlocking {
if (!checkVideoExists(it.url).exists) {
Log.d(tag, "Video not exists: ${it.url.substringBefore("?")}")
return@parallelMapNotNullBlocking null
}
Video(it.url, it.quality, it.url, headers = headers)
}
.reversed()
} }
companion object { companion object {
private const val PREF_AUTHCODE_KEY = "authcode" private const val PREF_AUTHCODE_KEY = "authcode"
private const val ADS_URL = "https://ads.anitube.vip" private const val ADS_URL = "https://ads.anitube.vip"
private const val SITE_URL = "https://www.anitube.vip" private const val SITE_URL = "https://www.hinatasoul.com/luffy.php"
} }
} }