diff --git a/src/pt/anitube/build.gradle b/src/pt/anitube/build.gradle index a70e1061..a468f926 100644 --- a/src/pt/anitube/build.gradle +++ b/src/pt/anitube/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Anitube' extClass = '.Anitube' - extVersionCode = 18 + extVersionCode = 19 } 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 953b5f7e..9d5e4d02 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 @@ -4,6 +4,7 @@ import android.app.Application import androidx.preference.EditTextPreference import androidx.preference.ListPreference 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.animesource.ConfigurableAnimeSource 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.awaitSuccess import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document @@ -189,9 +191,29 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() { } // ============================ 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 videoFromElement(element: Element) = throw UnsupportedOperationException() override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() @@ -264,8 +286,11 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun List<Video>.sort(): List<Video> { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! 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 { diff --git a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeDownloadExtractor.kt b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeDownloadExtractor.kt new file mode 100644 index 00000000..2d0bb1d6 --- /dev/null +++ b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeDownloadExtractor.kt @@ -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) + } +} 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 e6eeeeda..6c96aad9 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 @@ -6,11 +6,14 @@ 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.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.Response +import org.jsoup.nodes.Document +import java.net.ProtocolException class AnitubeExtractor( private val headers: Headers, @@ -20,6 +23,32 @@ class AnitubeExtractor( 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( serverUrl: String, thumbUrl: String, @@ -28,15 +57,21 @@ class AnitubeExtractor( ): String { val videoName = serverUrl.split('/').last() - Log.d(tag, "Accessing the link $link") - val response = client.newCall(GET(link, headers = linkHeaders)).execute() + val finalLink = + 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 refresh = docLink.selectFirst("meta[http-equiv=refresh]")?.attr("content") if (!refresh.isNullOrBlank()) { 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") return getAdsUrl(serverUrl, thumbUrl, newLink, newHeaders) @@ -47,30 +82,32 @@ class AnitubeExtractor( Log.d(tag, "Final URL: $referer") 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 { val now = System.currentTimeMillis() - val adsUrl = - client.newCall( - GET( - "$SITE_URL/playerricas.php?name=apphd/$videoName&img=$thumbUrl&pais=pais=BR&time=$now&url=$serverUrl", - headers = newHeaders, - ), - ) - .execute() - .body.string() - .let { - Regex("""ADS_URL\s*=\s*['"]([^'"]+)['"]""") - .find(it)?.groups?.get(1)?.value - ?: "" - } + val body = client.newCall( + GET( + "$SITE_URL?name=apphd/$videoName&img=$thumbUrl&pais=pais=BR&time=$now&url=$serverUrl", + headers = newHeaders, + ), + ) + .execute() + .body.string() + + val adsUrl = body.let { + Regex("""ADS_URL\s*=\s*['"]([^'"]+)['"]""") + .find(it)?.groups?.get(1)?.value + ?: "" + } if (adsUrl.startsWith("http")) { Log.d(tag, "ADS URL: $adsUrl") return adsUrl } } catch (e: Exception) { + Log.e(tag, e.toString()) } // Try default url @@ -84,15 +121,9 @@ class AnitubeExtractor( if (authCode.isNotBlank()) { Log.d(tag, "AuthCode found in preferences") - val request = Request.Builder() - .head() - .url("${serverUrl}$authCode") - .headers(headers) - .build() + val response = checkVideoExists("${serverUrl}$authCode") - val response = client.newCall(request).execute() - - if (response.isSuccessful || response.code == 500) { + if (response.exists || response.code == 500) { Log.d(tag, "AuthCode is OK") return authCode } @@ -112,7 +143,7 @@ class AnitubeExtractor( .build() val newHeaders = headers.newBuilder() - .set("Referer", SITE_URL) + .set("Referer", "https://${SITE_URL.toHttpUrl().host}/") .add("Accept", "*/*") .add("Cache-Control", "no-cache") .add("Pragma", "no-cache") @@ -165,8 +196,7 @@ class AnitubeExtractor( return authCode } - fun getVideoList(response: Response): List<Video> { - val doc = response.asJsoup() + fun getVideoList(doc: Document): List<Video> { val hasFHD = doc.selectFirst("div.abaItem:contains(FULLHD)") != null val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!! .attr("content") @@ -188,16 +218,27 @@ class AnitubeExtractor( val authCode = getAuthCode(serverUrl, thumbUrl, firstLink) - return qualities.mapIndexed { index, quality -> - val path = paths[index] - val url = serverUrl.replace(type, path) + authCode - Video(url, quality, url, headers = headers) - }.reversed() + return qualities + .mapIndexed { index, quality -> + object { + var path = paths[index] + 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 { private const val PREF_AUTHCODE_KEY = "authcode" 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" } } diff --git a/src/pt/hinatasoul/build.gradle b/src/pt/hinatasoul/build.gradle index ff464aa0..084d175a 100644 --- a/src/pt/hinatasoul/build.gradle +++ b/src/pt/hinatasoul/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Hinata Soul' extClass = '.HinataSoul' - extVersionCode = 8 + extVersionCode = 9 } 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 8e49face..3d636c45 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 @@ -4,6 +4,7 @@ import android.app.Application import androidx.preference.EditTextPreference import androidx.preference.ListPreference 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.animesource.ConfigurableAnimeSource 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.awaitSuccess import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element @@ -67,7 +69,11 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun latestUpdatesNextPageSelector() = null // =============================== 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 slug = query.removePrefix(PREFIX_SEARCH) client.newCall(GET("$baseUrl/animes/$slug")) @@ -156,16 +162,47 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() { val title = element.attr("title") setUrlWithoutDomain(element.attr("href")) 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")!! .text() .toDate() } // ============================ 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 videoFromElement(element: Element) = throw UnsupportedOperationException() @@ -246,7 +283,10 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun List<Video>.sort(): List<Video> { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! return sortedWith( - compareBy { it.quality.contains(quality) }, + compareBy<Video>( + { it.quality.startsWith(quality) }, + { PREF_QUALITY_VALUES.indexOf(it.quality.substringBefore(" ")) }, + ).thenByDescending { it.quality }, ).reversed() } diff --git a/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/extractors/HinataSoulDownloadExtractor.kt b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/extractors/HinataSoulDownloadExtractor.kt new file mode 100644 index 00000000..71b47df9 --- /dev/null +++ b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/extractors/HinataSoulDownloadExtractor.kt @@ -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) + } +} 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 4d4897d1..abb6bc8d 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 @@ -6,11 +6,14 @@ 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.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.Response +import org.jsoup.nodes.Document +import java.net.ProtocolException class HinataSoulExtractor( private val headers: Headers, @@ -20,6 +23,32 @@ class HinataSoulExtractor( 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( serverUrl: String, thumbUrl: String, @@ -28,15 +57,21 @@ class HinataSoulExtractor( ): String { val videoName = serverUrl.split('/').last() - Log.d(tag, "Accessing the link $link") - val response = client.newCall(GET(link, headers = linkHeaders)).execute() + val finalLink = + 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 refresh = docLink.selectFirst("meta[http-equiv=refresh]")?.attr("content") if (!refresh.isNullOrBlank()) { 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") return getAdsUrl(serverUrl, thumbUrl, newLink, newHeaders) @@ -47,30 +82,31 @@ class HinataSoulExtractor( Log.d(tag, "Final URL: $referer") 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 { val now = System.currentTimeMillis() - val adsUrl = - client.newCall( - GET( - "$SITE_URL/playerricas.php?name=apphd/$videoName&img=$thumbUrl&pais=pais=BR&time=$now&url=$serverUrl", - headers = newHeaders, - ), - ) - .execute() - .body.string() - .let { - Regex("""ADS_URL\s*=\s*['"]([^'"]+)['"]""") - .find(it)?.groups?.get(1)?.value - ?: "" - } + val body = client.newCall( + GET( + "$SITE_URL?name=apphd/$videoName&img=$thumbUrl&pais=pais=BR&time=$now&url=$serverUrl", + headers = newHeaders, + ), + ) + .execute() + .body.string() + + val adsUrl = body.let { + Regex("""ADS_URL\s*=\s*['"]([^'"]+)['"]""") + .find(it)?.groups?.get(1)?.value + ?: "" + } if (adsUrl.startsWith("http")) { Log.d(tag, "ADS URL: $adsUrl") return adsUrl } } catch (e: Exception) { + Log.e(tag, e.toString()) } // Try default url @@ -84,15 +120,9 @@ class HinataSoulExtractor( if (authCode.isNotBlank()) { Log.d(tag, "AuthCode found in preferences") - val request = Request.Builder() - .head() - .url("${serverUrl}$authCode") - .headers(headers) - .build() + val response = checkVideoExists("${serverUrl}$authCode") - val response = client.newCall(request).execute() - - if (response.isSuccessful || response.code == 500) { + if (response.exists || response.code == 500) { Log.d(tag, "AuthCode is OK") return authCode } @@ -112,7 +142,7 @@ class HinataSoulExtractor( .build() val newHeaders = headers.newBuilder() - .set("Referer", SITE_URL) + .set("Referer", "https://${SITE_URL.toHttpUrl().host}/") .add("Accept", "*/*") .add("Cache-Control", "no-cache") .add("Pragma", "no-cache") @@ -165,8 +195,7 @@ class HinataSoulExtractor( return authCode } - fun getVideoList(response: Response): List<Video> { - val doc = response.asJsoup() + fun getVideoList(doc: Document): List<Video> { val hasFHD = doc.selectFirst("div.abaItem:contains(FULLHD)") != null val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!! .attr("content") @@ -188,16 +217,27 @@ class HinataSoulExtractor( val authCode = getAuthCode(serverUrl, thumbUrl, firstLink) - return qualities.mapIndexed { index, quality -> - val path = paths[index] - val url = serverUrl.replace(type, path) + authCode - Video(url, quality, url, headers = headers) - }.reversed() + return qualities + .mapIndexed { index, quality -> + object { + var path = paths[index] + 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 { private const val PREF_AUTHCODE_KEY = "authcode" 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" } }