KickAssAnime: Fixes (#758)
* KickAssAnime: handle gogo redirects for old anime * KickAssAnime: extract more reliable from MAVERICKKI * KickAssAnime: wait a bit more when intercepting playlists to mitigate slow networks
This commit is contained in:
@ -5,7 +5,7 @@ ext {
|
|||||||
extName = 'KickAssAnime'
|
extName = 'KickAssAnime'
|
||||||
pkgNameSuffix = 'en.kickassanime'
|
pkgNameSuffix = 'en.kickassanime'
|
||||||
extClass = '.KickAssAnime'
|
extClass = '.KickAssAnime'
|
||||||
extVersionCode = 2
|
extVersionCode = 3
|
||||||
libVersion = '13'
|
libVersion = '13'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,9 @@ import android.content.SharedPreferences
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.DoodExtractor
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.GogoCdnExtractor
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.StreamSBExtractor
|
||||||
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.AnimesPage
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
@ -28,7 +31,6 @@ import okhttp3.Headers
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -90,9 +92,16 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
val data = getAppdata(response.asJsoup())
|
val data = getAppdata(response.asJsoup())
|
||||||
val episode = data["episode"]!!.jsonObject
|
val episode = data["episode"]!!.jsonObject
|
||||||
val link1 = episode["link1"]!!.jsonPrimitive.content
|
val link1 = episode["link1"]!!.jsonPrimitive.content
|
||||||
|
val videoList = mutableListOf<Video>()
|
||||||
|
|
||||||
|
if (link1.contains("gogoplay4.com")) {
|
||||||
|
videoList.addAll(
|
||||||
|
extractGogoVideo(link1)
|
||||||
|
)
|
||||||
|
return videoList
|
||||||
|
}
|
||||||
val resp = client.newCall(GET(link1)).execute()
|
val resp = client.newCall(GET(link1)).execute()
|
||||||
val sources = getVideoSource(resp.asJsoup())
|
val sources = getVideoSource(resp.asJsoup())
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
|
|
||||||
sources.forEach { source ->
|
sources.forEach { source ->
|
||||||
when (source.jsonObject["name"]!!.jsonPrimitive.content) {
|
when (source.jsonObject["name"]!!.jsonPrimitive.content) {
|
||||||
@ -119,29 +128,33 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun extractVideo(serverLink: String, server: String): List<Video> {
|
private fun extractVideo(serverLink: String, server: String): List<Video> {
|
||||||
val playlistInterceptor = MasterPlaylistInterceptor()
|
|
||||||
val kickAssClient = client.newBuilder().addInterceptor(playlistInterceptor).build()
|
|
||||||
kickAssClient.newCall(GET(serverLink)).execute()
|
|
||||||
val data = playlistInterceptor.playlist
|
|
||||||
val playlist = mutableListOf<Video>()
|
val playlist = mutableListOf<Video>()
|
||||||
val subsList = mutableListOf<Track>()
|
val subsList = mutableListOf<Track>()
|
||||||
|
val data: MutableList<Pair<String, Headers>>
|
||||||
|
|
||||||
if (server == "MAVERICKKI") {
|
if (server == "MAVERICKKI") {
|
||||||
val subLink = serverLink.replace("embed", "api/source")
|
val apiLink = serverLink.replace("embed", "api/source")
|
||||||
val subResponse = Jsoup.connect(subLink).ignoreContentType(true).execute().body()
|
// for some reason the request to the api is only working reliably this way
|
||||||
val json = Json.decodeFromString<JsonObject>(subResponse)
|
val apiResponse = client.newCall(GET(apiLink, headers)).execute().asJsoup().text()
|
||||||
|
val json = Json.decodeFromString<JsonObject>(apiResponse)
|
||||||
|
val uri = Uri.parse(serverLink)
|
||||||
|
|
||||||
json["subtitles"]!!.jsonArray.forEach {
|
json["subtitles"]!!.jsonArray.forEach {
|
||||||
val subLang = it.jsonObject["name"]!!.jsonPrimitive.content
|
val subLang = it.jsonObject["name"]!!.jsonPrimitive.content
|
||||||
val uri = Uri.parse(serverLink)
|
|
||||||
val subUrl = "${uri.scheme}://${uri.host}" + it.jsonObject["src"]!!.jsonPrimitive.content
|
val subUrl = "${uri.scheme}://${uri.host}" + it.jsonObject["src"]!!.jsonPrimitive.content
|
||||||
try {
|
try {
|
||||||
subsList.add(Track(subUrl, subLang))
|
subsList.add(Track(subUrl, subLang))
|
||||||
} catch (e: Error) {}
|
} catch (e: Error) {}
|
||||||
}
|
}
|
||||||
|
data = mutableListOf(Pair("${uri.scheme}://${uri.host}" + json["hls"]!!.jsonPrimitive.content, headers))
|
||||||
|
} else {
|
||||||
|
val playlistInterceptor = MasterPlaylistInterceptor()
|
||||||
|
val kickAssClient = client.newBuilder().addInterceptor(playlistInterceptor).build()
|
||||||
|
kickAssClient.newCall(GET(serverLink, headers)).execute()
|
||||||
|
data = playlistInterceptor.playlist
|
||||||
}
|
}
|
||||||
|
|
||||||
data.forEach { playlistPair ->
|
data.forEach { (videoLink, headers) ->
|
||||||
val (videoLink, headers) = playlistPair
|
|
||||||
val masterPlaylist = client.newCall(GET(videoLink, headers)).execute().body!!.string()
|
val masterPlaylist = client.newCall(GET(videoLink, headers)).execute().body!!.string()
|
||||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:")
|
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:")
|
||||||
.split("#EXT-X-STREAM-INF:").map {
|
.split("#EXT-X-STREAM-INF:").map {
|
||||||
@ -188,6 +201,24 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
return playlist
|
return playlist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun extractGogoVideo(link: String): List<Video> {
|
||||||
|
val url = "https:" + decode(link).substringAfter("data=").substringBefore("&vref")
|
||||||
|
val videoList = mutableListOf<Video>()
|
||||||
|
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||||
|
|
||||||
|
// Vidstreaming:
|
||||||
|
videoList.addAll(GogoCdnExtractor(network.client, json).videosFromUrl(url))
|
||||||
|
// Doodstream mirror:
|
||||||
|
document.select("div#list-server-more > ul > li.linkserver:contains(Doodstream)")
|
||||||
|
.firstOrNull()?.attr("data-video")
|
||||||
|
?.let { videoList.addAll(DoodExtractor(client).videosFromUrl(it)) }
|
||||||
|
// StreamSB mirror:
|
||||||
|
document.select("div#list-server-more > ul > li.linkserver:contains(StreamSB)")
|
||||||
|
.firstOrNull()?.attr("data-video")
|
||||||
|
?.let { videoList.addAll(StreamSBExtractor(client).videosFromUrl(it, headers)) }
|
||||||
|
return videoList
|
||||||
|
}
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
override fun List<Video>.sort(): List<Video> {
|
||||||
val quality = preferences.getString("preferred_quality", "1080")
|
val quality = preferences.getString("preferred_quality", "1080")
|
||||||
|
|
||||||
@ -323,4 +354,6 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun encode(input: String): String = java.net.URLEncoder.encode(input, "utf-8")
|
private fun encode(input: String): String = java.net.URLEncoder.encode(input, "utf-8")
|
||||||
|
|
||||||
|
private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8")
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ class MasterPlaylistInterceptor : Interceptor {
|
|||||||
useWideViewPort = false
|
useWideViewPort = false
|
||||||
loadWithOverviewMode = false
|
loadWithOverviewMode = false
|
||||||
userAgentString = request.header("User-Agent")
|
userAgentString = request.header("User-Agent")
|
||||||
?: "\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63\""
|
?: "\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 Edg/88.0.705.63\""
|
||||||
}
|
}
|
||||||
|
|
||||||
webview.webViewClient = object : WebViewClient() {
|
webview.webViewClient = object : WebViewClient() {
|
||||||
@ -73,7 +73,7 @@ class MasterPlaylistInterceptor : Interceptor {
|
|||||||
|
|
||||||
// Wait a reasonable amount of time to retrieve the solution. The minimum should be
|
// Wait a reasonable amount of time to retrieve the solution. The minimum should be
|
||||||
// around 4 seconds but it can take more due to slow networks or server issues.
|
// around 4 seconds but it can take more due to slow networks or server issues.
|
||||||
latch.await(7, TimeUnit.SECONDS)
|
latch.await(9, TimeUnit.SECONDS)
|
||||||
|
|
||||||
handler.post {
|
handler.post {
|
||||||
webView?.stopLoading()
|
webView?.stopLoading()
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
class DoodExtractor(private val client: OkHttpClient) {
|
||||||
|
fun videosFromUrl(serverUrl: String): List<Video> {
|
||||||
|
try {
|
||||||
|
val response = client.newCall(GET(serverUrl)).execute()
|
||||||
|
val doodTld = serverUrl.substringAfter("https://dood.").substringBefore("/")
|
||||||
|
val content = response.body!!.string()
|
||||||
|
if (!content.contains("'/pass_md5/")) return emptyList()
|
||||||
|
val md5 = content.substringAfter("'/pass_md5/").substringBefore("',")
|
||||||
|
val token = md5.substringAfterLast("/")
|
||||||
|
val randomString = getRandomString()
|
||||||
|
val expiry = System.currentTimeMillis()
|
||||||
|
val videoUrlStart = client.newCall(
|
||||||
|
GET(
|
||||||
|
"https://dood.$doodTld/pass_md5/$md5",
|
||||||
|
Headers.headersOf("referer", serverUrl)
|
||||||
|
)
|
||||||
|
).execute().body!!.string()
|
||||||
|
val videoUrl = "$videoUrlStart$randomString?token=$token&expiry=$expiry"
|
||||||
|
return listOf(Video(serverUrl, "Doodstream", videoUrl, headers = doodHeaders(doodTld)))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRandomString(length: Int = 10): String {
|
||||||
|
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||||
|
return (1..length)
|
||||||
|
.map { allowedChars.random() }
|
||||||
|
.joinToString("")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doodHeaders(tld: String) = Headers.Builder().apply {
|
||||||
|
add("User-Agent", "Aniyomi")
|
||||||
|
add("Referer", "https://dood.$tld/")
|
||||||
|
}.build()
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.kickassanime.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.decodeFromString
|
||||||
|
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 masterPlaylist = client.newCall(GET(fileURL)).execute().body!!.string()
|
||||||
|
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:")
|
||||||
|
.split("#EXT-X-STREAM-INF:").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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
|
class StreamSBExtractor(private val client: OkHttpClient) {
|
||||||
|
|
||||||
|
private val hexArray = "0123456789ABCDEF".toCharArray()
|
||||||
|
|
||||||
|
private fun bytesToHex(bytes: ByteArray): String {
|
||||||
|
val hexChars = CharArray(bytes.size * 2)
|
||||||
|
for (j in bytes.indices) {
|
||||||
|
val v = bytes[j].toInt() and 0xFF
|
||||||
|
|
||||||
|
hexChars[j * 2] = hexArray[v ushr 4]
|
||||||
|
hexChars[j * 2 + 1] = hexArray[v and 0x0F]
|
||||||
|
}
|
||||||
|
return String(hexChars)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun videosFromUrl(url: String, headers: Headers): List<Video> {
|
||||||
|
try {
|
||||||
|
val sbHeaders = headers.newBuilder()
|
||||||
|
.set("Referer", url)
|
||||||
|
.set(
|
||||||
|
"User-Agent",
|
||||||
|
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0"
|
||||||
|
)
|
||||||
|
.set("Accept-Language", "en-US,en;q=0.5")
|
||||||
|
.set("watchsb", "streamsb")
|
||||||
|
.build()
|
||||||
|
val sbUrl = url.substringBefore("/e")
|
||||||
|
val id = url.substringAfter("e/").substringBefore("?")
|
||||||
|
val bytes = id.toByteArray()
|
||||||
|
val bytesToHex = bytesToHex(bytes)
|
||||||
|
val master = "$sbUrl/sources43/566d337678566f743674494a7c7c${bytesToHex}7c7c346b6767586d6934774855537c7c73747265616d7362/6565417268755339773461447c7c346133383438333436313335376136323337373433383634376337633465366534393338373136643732373736343735373237613763376334363733353737303533366236333463353333363534366137633763373337343732363536313664373336327c7c6b586c3163614468645a47617c7c73747265616d7362"
|
||||||
|
val json = Json.decodeFromString<JsonObject>(
|
||||||
|
client.newCall(GET(master, sbHeaders))
|
||||||
|
.execute().body!!.string()
|
||||||
|
)
|
||||||
|
val masterUrl = json["stream_data"]!!.jsonObject["file"].toString().trim('"')
|
||||||
|
val masterPlaylist = client.newCall(GET(masterUrl, sbHeaders)).execute().body!!.string()
|
||||||
|
val videoList = mutableListOf<Video>()
|
||||||
|
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
|
||||||
|
.forEach {
|
||||||
|
val quality = "StreamSB: " + it.substringAfter("RESOLUTION=").substringAfter("x")
|
||||||
|
.substringBefore(",") + "p"
|
||||||
|
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||||
|
videoList.add(Video(videoUrl, quality, videoUrl, headers = sbHeaders))
|
||||||
|
}
|
||||||
|
return videoList.reversed()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user