fix(en/gogoanime): Update baseUrl (#2550)

This commit is contained in:
Claudemirovsky 2023-11-25 15:13:20 -03:00 committed by GitHub
parent 135faf95f0
commit 15fbd2149f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 215 deletions

View File

@ -7,7 +7,7 @@ ext {
extName = 'Gogoanime' extName = 'Gogoanime'
pkgNameSuffix = 'en.gogoanime' pkgNameSuffix = 'en.gogoanime'
extClass = '.GogoAnime' extClass = '.GogoAnime'
extVersionCode = 76 extVersionCode = 77
libVersion = '13' libVersion = '13'
} }
@ -15,8 +15,7 @@ dependencies {
implementation(project(':lib-streamwish-extractor')) implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-mp4upload-extractor')) implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-dood-extractor')) implementation(project(':lib-dood-extractor'))
implementation(project(':lib-playlist-utils')) implementation(project(':lib-gogostream-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,14 +1,12 @@
package eu.kanade.tachiyomi.animeextension.en.gogoanime package eu.kanade.tachiyomi.animeextension.en.gogoanime
import android.app.Application import android.app.Application
import android.content.SharedPreferences
import android.widget.Toast import android.widget.Toast
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.BuildConfig import eu.kanade.tachiyomi.animeextension.BuildConfig
import eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors.GogoCdnExtractor
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
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
@ -16,6 +14,7 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.gogostreamextractor.GogoStreamExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
@ -24,19 +23,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
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
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.lang.Exception import java.lang.Exception
@ExperimentalSerializationApi
class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Gogoanime" override val name = "Gogoanime"
@ -47,16 +41,17 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client = network.cloudflareClient
private val json: Json by injectLazy() override fun headersBuilder() = super.headersBuilder()
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
private val preferences: SharedPreferences by lazy { private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/popular.html?page=$page", headers) override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/popular.html?page=$page", headers)
override fun popularAnimeSelector(): String = "div.img a" override fun popularAnimeSelector(): String = "div.img a"
@ -70,44 +65,28 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeNextPageSelector(): String = "ul.pagination-list li:last-child:not(.selected)" override fun popularAnimeNextPageSelector(): String = "ul.pagination-list li:last-child:not(.selected)"
// =============================== Latest =============================== // =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/home.html?page=$page", headers)
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/?page=$page", headers)
override fun latestUpdatesSelector(): String = "div.img a" override fun latestUpdatesSelector(): String = "div.img a"
override fun latestUpdatesFromElement(element: Element): SAnime { override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
val imgUrl = element.selectFirst("img")!!.attr("src") thumbnail_url = element.selectFirst("img")?.attr("src")
val newUrl = imgUrl.replaceFirst("https://", "").substringAfter("/").replaceFirst("cover", "/category").substringBeforeLast('.')
val finalUrl = newUrl.let { url ->
url.lastIndexOf('-').let { lastIndex ->
val suffix = url.substring(lastIndex + 1)
if (lastIndex == -1 || !suffix.all { it.isDigit() } || suffix.length < 3) {
newUrl
} else {
url.substring(0, lastIndex)
}
}
}
return SAnime.create().apply {
setUrlWithoutDomain(finalUrl)
thumbnail_url = element.selectFirst("img")!!.attr("src")
title = element.attr("title") title = element.attr("title")
} val slug = element.attr("href").substringAfter(baseUrl)
.trimStart('/')
.substringBefore("-episode-")
setUrlWithoutDomain("/category/$slug")
} }
override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector() override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector()
// =============================== Search =============================== // =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = GogoAnimeFilters.getSearchParameters(filters) val params = GogoAnimeFilters.getSearchParameters(filters)
return when { return when {
params.genre.isNotEmpty() -> GET("$baseUrl/genre/${params.genre}?page=$page", headers) params.genre.isNotEmpty() -> GET("$baseUrl/genre/${params.genre}?page=$page", headers)
params.recent.isNotEmpty() -> GET("https://ajax.gogo-load.com/ajax/page-recent-release.html?page=$page&type=${params.recent}", headers) params.recent.isNotEmpty() -> GET("$AJAX_URL/page-recent-release.html?page=$page&type=${params.recent}", headers)
params.season.isNotEmpty() -> GET("$baseUrl/${params.season}?page=$page", headers) params.season.isNotEmpty() -> GET("$baseUrl/${params.season}?page=$page", headers)
else -> GET("$baseUrl/filter.html?keyword=$query&${params.filter}&page=$page", headers) else -> GET("$baseUrl/filter.html?keyword=$query&${params.filter}&page=$page", headers)
} }
@ -120,42 +99,37 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
// ============================== Filters =============================== // ============================== Filters ===============================
override fun getFilterList(): AnimeFilterList = GogoAnimeFilters.FILTER_LIST override fun getFilterList(): AnimeFilterList = GogoAnimeFilters.FILTER_LIST
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime { override fun animeDetailsParse(document: Document): SAnime {
val infoDocument = document.selectFirst("div.anime-info a[href]")?.let { val infoDocument = document.selectFirst("div.anime-info a[href]")?.let {
client.newCall(GET(it.attr("abs:href"), headers)).execute().asJsoup() client.newCall(GET(it.absUrl("href"), headers)).execute().asJsoup()
} ?: document } ?: document
return SAnime.create().apply { return SAnime.create().apply {
title = infoDocument.select("div.anime_info_body_bg h1").text() title = infoDocument.selectFirst("div.anime_info_body_bg > h1")!!.text()
genre = infoDocument.select("p.type:eq(5) a").joinToString("") { it.text() } genre = infoDocument.getInfo("Genre:")
description = infoDocument.selectFirst("p.type:eq(4)")!!.ownText() status = parseStatus(infoDocument.getInfo("Status:").orEmpty())
status = parseStatus(infoDocument.select("p.type:eq(7) a").text())
description = buildString {
infoDocument.getInfo("Plot Summary:")?.also(::append)
// add alternative name to anime description // add alternative name to anime description
val altName = "Other name(s): " infoDocument.getInfo("Other name:")?.also {
infoDocument.selectFirst("p.type:eq(8)")?.ownText()?.let { if (isNotBlank()) append("\n\n")
if (it.isBlank().not()) { append("Other name(s): $it")
description = when {
description.isNullOrBlank() -> altName + it
else -> description + "\n\n$altName" + it
}
} }
} }
} }
} }
// ============================== Episodes ============================== // ============================== Episodes ==============================
private fun episodesRequest(totalEpisodes: String, id: String): List<SEpisode> { private fun episodesRequest(totalEpisodes: String, id: String): List<SEpisode> {
val request = GET("https://ajax.gogo-load.com/ajax/load-list-episode?ep_start=0&ep_end=$totalEpisodes&id=$id", headers) val request = GET("$AJAX_URL/load-list-episode?ep_start=0&ep_end=$totalEpisodes&id=$id", headers)
val epResponse = client.newCall(request).execute() val epResponse = client.newCall(request).execute()
val document = epResponse.asJsoup() val document = epResponse.asJsoup()
return document.select("a").map { episodeFromElement(it) } return document.select("a").map(::episodeFromElement)
} }
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
@ -177,8 +151,7 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
// ============================ Video Links ============================= // ============================ Video Links =============================
private val gogoExtractor by lazy { GogoStreamExtractor(client) }
private val gogoExtractor by lazy { GogoCdnExtractor(client, json) }
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) } private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
private val doodExtractor by lazy { DoodExtractor(client) } private val doodExtractor by lazy { DoodExtractor(client) }
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) } private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
@ -187,17 +160,15 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val document = response.asJsoup() val document = response.asJsoup()
val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!! val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!!
return document.select("div.anime_muti_link > ul > li").parallelMap { server -> return document.select("div.anime_muti_link > ul > li").parallelCatchingFlatMap { server ->
runCatching {
val className = server.className() val className = server.className()
if (!hosterSelection.contains(className)) return@runCatching emptyList() if (!hosterSelection.contains(className)) return@parallelCatchingFlatMap emptyList()
val serverUrl = server.selectFirst("a") val serverUrl = server.selectFirst("a")
?.attr("abs:data-video") ?.attr("abs:data-video")
?: return@runCatching emptyList() ?: return@parallelCatchingFlatMap emptyList()
getHosterVideos(className, serverUrl) getHosterVideos(className, serverUrl)
}.getOrElse { emptyList() } }
}.flatten().sort().ifEmpty { throw Exception("Failed to extract videos") }
} }
private fun getHosterVideos(className: String, serverUrl: String): List<Video> { private fun getHosterVideos(className: String, serverUrl: String): List<Video> {
@ -220,12 +191,18 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoUrlParse(document: Document) = throw Exception("not used") override fun videoUrlParse(document: Document) = throw Exception("not used")
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun Document.getInfo(text: String): String? {
val base = selectFirst("p.type:has(span:containsOwn($text))") ?: return null
return base.select("a").eachText().joinToString("")
.ifBlank { base.ownText() }
.takeUnless(String::isBlank)
}
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)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!! val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith( return sortedWith(
compareBy( compareBy(
{ it.quality.contains(quality) }, { it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 }, { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
@ -242,13 +219,18 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
// From Dopebox private inline fun <A, B> Iterable<A>.parallelCatchingFlatMap(crossinline f: suspend (A) -> Iterable<B>): List<B> =
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking { runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll() map {
async(Dispatchers.Default) {
runCatching { f(it) }.getOrElse { emptyList() }
}
}.awaitAll().flatten()
} }
companion object { companion object {
private const val AJAX_URL = "https://ajax.gogo-load.com/ajax"
private val HOSTERS = arrayOf( private val HOSTERS = arrayOf(
"Gogostream", "Gogostream",
"Vidstreaming", "Vidstreaming",
@ -266,44 +248,51 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
"filelions", "filelions",
) )
private val PREF_DOMAIN_KEY = "preferred_domain_name_v${BuildConfig.VERSION_CODE}" private const val PREF_DOMAIN_KEY = "preferred_domain_name_v${BuildConfig.VERSION_CODE}"
private const val PREF_DOMAIN_TITLE = "Override BaseUrl" private const val PREF_DOMAIN_TITLE = "Override BaseUrl"
private const val PREF_DOMAIN_DEFAULT = "https://gogoanimehd.io" private const val PREF_DOMAIN_DEFAULT = "https://anitaku.to"
private const val PREF_DOMAIN_SUMMARY = "For temporary uses. Updating the extension will erase this setting." private const val PREF_DOMAIN_SUMMARY = "For temporary uses. Updating the extension will erase this setting."
private const val PREF_DOMAIN_DIALOG_MESSAGE = "Default: $PREF_DOMAIN_DEFAULT"
private const val PREF_QUALITY_KEY = "preferred_quality" private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "1080" private const val PREF_QUALITY_DEFAULT = "1080"
private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p")
private val PREF_QUALITY_VALUES = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server" private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_TITLE = "Preferred server"
private const val PREF_SERVER_DEFAULT = "Gogostream" private const val PREF_SERVER_DEFAULT = "Gogostream"
private const val PREF_HOSTER_KEY = "hoster_selection" private const val PREF_HOSTER_KEY = "hoster_selection"
private const val PREF_HOSTER_TITLE = "Enable/Disable Hosts"
private val PREF_HOSTER_DEFAULT = HOSTERS_NAMES.toSet() private val PREF_HOSTER_DEFAULT = HOSTERS_NAMES.toSet()
} }
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
EditTextPreference(screen.context).apply { EditTextPreference(screen.context).apply {
key = PREF_DOMAIN_KEY key = PREF_DOMAIN_KEY
title = PREF_DOMAIN_TITLE title = PREF_DOMAIN_TITLE
summary = PREF_DOMAIN_SUMMARY
dialogTitle = PREF_DOMAIN_TITLE dialogTitle = PREF_DOMAIN_TITLE
dialogMessage = "Default: $PREF_DOMAIN_DEFAULT" dialogMessage = PREF_DOMAIN_DIALOG_MESSAGE
setDefaultValue(PREF_DOMAIN_DEFAULT) setDefaultValue(PREF_DOMAIN_DEFAULT)
summary = PREF_DOMAIN_SUMMARY
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val newValueString = newValue as String runCatching {
val value = (newValue as String).trim().ifEmpty { PREF_DOMAIN_DEFAULT }
Toast.makeText(screen.context, "Restart Aniyomi to apply new setting.", Toast.LENGTH_LONG).show() Toast.makeText(screen.context, "Restart Aniyomi to apply new setting.", Toast.LENGTH_LONG).show()
preferences.edit().putString(key, newValueString.trim()).commit() preferences.edit().putString(key, value).commit()
}.getOrDefault(false)
} }
}.also(screen::addPreference) }.also(screen::addPreference)
ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY key = PREF_QUALITY_KEY
title = "Preferred quality" title = PREF_QUALITY_TITLE
entries = arrayOf("1080p", "720p", "480p", "360p") entries = PREF_QUALITY_ENTRIES
entryValues = arrayOf("1080", "720", "480", "360") entryValues = PREF_QUALITY_VALUES
setDefaultValue(PREF_QUALITY_DEFAULT) setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s" summary = "%s"
@ -317,7 +306,7 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_SERVER_KEY key = PREF_SERVER_KEY
title = "Preferred server" title = PREF_SERVER_TITLE
entries = HOSTERS entries = HOSTERS
entryValues = HOSTERS entryValues = HOSTERS
setDefaultValue(PREF_SERVER_DEFAULT) setDefaultValue(PREF_SERVER_DEFAULT)
@ -333,7 +322,7 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
MultiSelectListPreference(screen.context).apply { MultiSelectListPreference(screen.context).apply {
key = PREF_HOSTER_KEY key = PREF_HOSTER_KEY
title = "Enable/Disable Hosts" title = PREF_HOSTER_TITLE
entries = HOSTERS entries = HOSTERS
entryValues = HOSTERS_NAMES entryValues = HOSTERS_NAMES
setDefaultValue(PREF_HOSTER_DEFAULT) setDefaultValue(PREF_HOSTER_DEFAULT)

View File

@ -1,129 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors
import android.util.Base64
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import java.lang.Exception
import java.util.Locale
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@ExperimentalSerializationApi
class GogoCdnExtractor(private val client: OkHttpClient, private val json: Json) {
fun videosFromUrl(serverUrl: String): List<Video> {
try {
val document = client.newCall(GET(serverUrl)).execute().asJsoup()
val iv = document.select("div.wrapper")
.attr("class").substringAfter("container-")
.filter { it.isDigit() }.toByteArray()
val secretKey = document.select("body[class]")
.attr("class").substringAfter("container-")
.filter { it.isDigit() }.toByteArray()
val decryptionKey = document.select("div.videocontent")
.attr("class").substringAfter("videocontent-")
.filter { it.isDigit() }.toByteArray()
val encryptAjaxParams = cryptoHandler(
document.select("script[data-value]")
.attr("data-value"),
iv,
secretKey,
false,
).substringAfter("&")
val httpUrl = serverUrl.toHttpUrl()
val host = "https://" + httpUrl.host + "/"
val id = httpUrl.queryParameter("id") ?: throw Exception("error getting id")
val encryptedId = cryptoHandler(id, iv, secretKey)
val token = httpUrl.queryParameter("token")
val qualityPrefix = if (token != null) "Gogostream - " else "Vidstreaming - "
val jsonResponse = client.newCall(
GET(
"${host}encrypt-ajax.php?id=$encryptedId&$encryptAjaxParams&alias=$id",
Headers.headersOf(
"X-Requested-With",
"XMLHttpRequest",
),
),
).execute().body.string()
val data = json.decodeFromString<JsonObject>(jsonResponse)["data"]!!.jsonPrimitive.content
val decryptedData = cryptoHandler(data, iv, decryptionKey, false)
val videoList = mutableListOf<Video>()
val autoList = mutableListOf<Video>()
val array = json.decodeFromString<JsonObject>(decryptedData)["source"]!!.jsonArray
if (array.size == 1 && array[0].jsonObject["type"]!!.jsonPrimitive.content == "hls") {
val fileURL = array[0].jsonObject["file"].toString().trim('"')
val separator = "#EXT-X-STREAM-INF:"
val masterPlaylist = client.newCall(GET(fileURL)).execute().body.string()
if (masterPlaylist.contains(separator)) {
masterPlaylist.substringAfter(separator)
.split(separator).forEach {
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",").substringBefore("\n") + "p"
var videoUrl = it.substringAfter("\n").substringBefore("\n")
if (!videoUrl.startsWith("http")) {
videoUrl = fileURL.substringBeforeLast("/") + "/$videoUrl"
}
videoList.add(Video(videoUrl, qualityPrefix + quality, videoUrl))
}
} else {
videoList.add(Video(fileURL, "${qualityPrefix}Original", fileURL))
}
} else {
array.forEach {
val label = it.jsonObject["label"].toString().lowercase(Locale.ROOT)
.trim('"').replace(" ", "")
val fileURL = it.jsonObject["file"].toString().trim('"')
val videoHeaders = Headers.headersOf("Referer", serverUrl)
if (label == "auto") {
autoList.add(
Video(
fileURL,
qualityPrefix + label,
fileURL,
headers = videoHeaders,
),
)
} else {
videoList.add(Video(fileURL, qualityPrefix + label, fileURL, headers = videoHeaders))
}
}
}
return videoList.sortedByDescending {
it.quality.substringAfter(qualityPrefix).substringBefore("p").toIntOrNull() ?: -1
} + autoList
} catch (e: Exception) {
return emptyList()
}
}
private fun cryptoHandler(
string: String,
iv: ByteArray,
secretKeyString: ByteArray,
encrypt: Boolean = true,
): String {
val ivParameterSpec = IvParameterSpec(iv)
val secretKey = SecretKeySpec(secretKeyString, "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
return if (!encrypt) {
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec)
String(cipher.doFinal(Base64.decode(string, Base64.DEFAULT)))
} else {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
Base64.encodeToString(cipher.doFinal(string.toByteArray()), Base64.NO_WRAP)
}
}
}