Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View file

@ -0,0 +1,15 @@
ext {
extName = 'AnimeLatinoHD'
extClass = '.AnimeLatinoHD'
extVersionCode = 32
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:streamwish-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -0,0 +1,435 @@
package eu.kanade.tachiyomi.animeextension.es.animelatinohd
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.animelatinohd.extractors.SolidFilesExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeLatinoHD"
override val baseUrl = "https://www.animelatinohd.com"
override val lang = "es"
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
const val PREF_QUALITY_KEY = "preferred_quality"
const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "FileLions"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"FileLions", "StreamHideVid", "SolidFiles", "Od.lk", "CldUp",
)
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]")
}
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes/populares")
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animeList = mutableListOf<SAnime>()
val url = response.request.url.toString().lowercase()
val hasNextPage = document.select("#__next > main > div > div[class*=\"Animes_paginate\"] a:last-child svg").any()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
if (url.contains("status=1")) {
val latestData = data["data"]!!.jsonArray
latestData.forEach { item ->
val animeItem = item!!.jsonObject
val anime = SAnime.create()
anime.setUrlWithoutDomain(externalOrInternalImg("anime/${animeItem["slug"]!!.jsonPrimitive!!.content}"))
anime.thumbnail_url = "https://image.tmdb.org/t/p/w200${animeItem["poster"]!!.jsonPrimitive!!.content}"
anime.title = animeItem["name"]!!.jsonPrimitive!!.content
animeList.add(anime)
}
} else {
val popularToday = data["popular_today"]!!.jsonArray
popularToday.forEach { item ->
val animeItem = item!!.jsonObject
val anime = SAnime.create()
anime.setUrlWithoutDomain(externalOrInternalImg("anime/${animeItem["slug"]!!.jsonPrimitive!!.content}"))
anime.thumbnail_url = "https://image.tmdb.org/t/p/w200${animeItem["poster"]!!.jsonPrimitive!!.content}"
anime.title = animeItem["name"]!!.jsonPrimitive!!.content
animeList.add(anime)
}
}
}
}
return AnimesPage(animeList, hasNextPage)
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?page=$page&status=1")
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
val newAnime = SAnime.create()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
newAnime.title = data["name"]!!.jsonPrimitive!!.content
newAnime.genre = data["genres"]!!.jsonPrimitive!!.content.split(",").joinToString()
newAnime.description = data["overview"]!!.jsonPrimitive!!.content
newAnime.status = parseStatus(data["status"]!!.jsonPrimitive!!.content)
newAnime.thumbnail_url = "https://image.tmdb.org/t/p/w600_and_h900_bestv2${data["poster"]!!.jsonPrimitive!!.content}"
newAnime.setUrlWithoutDomain(externalOrInternalImg("anime/${data["slug"]!!.jsonPrimitive!!.content}"))
}
}
return newAnime
}
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
val arrEpisode = data["episodes"]!!.jsonArray
arrEpisode.forEach { item ->
val animeItem = item!!.jsonObject
val episode = SEpisode.create()
episode.setUrlWithoutDomain(externalOrInternalImg("ver/${data["slug"]!!.jsonPrimitive!!.content}/${animeItem["number"]!!.jsonPrimitive!!.content!!.toFloat()}"))
episode.episode_number = animeItem["number"]!!.jsonPrimitive!!.content!!.toFloat()
episode.name = "Episodio ${animeItem["number"]!!.jsonPrimitive!!.content!!.toFloat()}"
episodeList.add(episode)
}
}
}
return episodeList
}
private fun parseJsonArray(json: JsonElement?): List<JsonElement> {
val list = mutableListOf<JsonElement>()
json!!.jsonObject!!.entries!!.forEach { list.add(it.value) }
return list
}
private fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) return listOf()
val linkRegex = "(https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))".toRegex()
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
val playersElement = data["players"]
val players = if (playersElement !is JsonArray) JsonArray(parseJsonArray(playersElement)) else playersElement!!.jsonArray
players.forEach { player ->
val servers = player!!.jsonArray
servers.forEach { server ->
val item = server!!.jsonObject
val request = client.newCall(
GET(
url = "https://api.animelatinohd.com/stream/${item["id"]!!.jsonPrimitive.content}",
headers = headers.newBuilder()
.add("Referer", "https://www.animelatinohd.com/")
.add("authority", "api.animelatinohd.com")
.add("upgrade-insecure-requests", "1")
.build(),
),
).execute()
val locationsDdh = request!!.networkResponse.toString()
fetchUrls(locationsDdh).map { url ->
val language = if (item["languaje"]!!.jsonPrimitive!!.content == "1") "[LAT]" else "[SUB]"
val embedUrl = url.lowercase()
if (embedUrl.contains("filemoon")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$language Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$language FileLions:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url, "$language Streamtape")?.let { videoList.add(it) }
}
if (embedUrl.contains("dood")) {
DoodExtractor(client).videoFromUrl(url, "$language DoodStream")?.let { videoList.add(it) }
}
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
OkruExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
}
if (embedUrl.contains("solidfiles")) {
SolidFilesExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
}
if (embedUrl.contains("od.lk")) {
videoList.add(Video(url, language + "Od.lk", url))
}
if (embedUrl.contains("cldup.com")) {
videoList.add(Video(url, language + "CldUp", url))
}
}
}
}
}
}
return videoList
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val stateFilter = filterList.find { it is StateFilter } as StateFilter
val typeFilter = filterList.find { it is TypeFilter } as TypeFilter
val filterUrl = if (query.isBlank()) {
"$baseUrl/animes?page=$page&genre=${genreFilter.toUriPart()}&status=${stateFilter.toUriPart()}&type=${typeFilter.toUriPart()}"
} else {
"$baseUrl/animes?page=$page&search=$query"
}
return GET(filterUrl)
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora los filtros"),
GenreFilter(),
StateFilter(),
TypeFilter(),
)
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animeList = mutableListOf<SAnime>()
val hasNextPage = document.select("#__next > main > div > div[class*=\"Animes_paginate\"] a:last-child svg").any()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
val arrData = data["data"]!!.jsonArray
arrData.forEach { item ->
val animeItem = item!!.jsonObject
val anime = SAnime.create()
anime.setUrlWithoutDomain(externalOrInternalImg("anime/${animeItem["slug"]!!.jsonPrimitive!!.content}"))
anime.thumbnail_url = "https://image.tmdb.org/t/p/w200${animeItem["poster"]!!.jsonPrimitive!!.content}"
anime.title = animeItem["name"]!!.jsonPrimitive!!.content
animeList.add(anime)
}
}
}
return AnimesPage(animeList, hasNextPage)
}
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<Selecionar>", ""),
Pair("Acción", "accion"),
Pair("Aliens", "aliens"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Aventura", "aventura"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Cyberpunk", "cyberpunk"),
Pair("Demonios", "demonios"),
Pair("Deportes", "deportes"),
Pair("Detectives", "detectives"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolar", "escolar"),
Pair("Espacio", "espacio"),
Pair("Fantasía", "fantasia"),
Pair("Gore", "gore"),
Pair("Harem", "harem"),
Pair("Histórico", "historico"),
Pair("Horror", "horror"),
Pair("Josei", "josei"),
Pair("Juegos", "juegos"),
Pair("Kodomo", "kodomo"),
Pair("Magia", "magia"),
Pair("Maho Shoujo", "maho-shoujo"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Musica", "musica"),
Pair("Parodia", "parodia"),
Pair("Policial", "policial"),
Pair("Psicológico", "psicologico"),
Pair("Recuentos De La Vida", "recuentos-de-la-vida"),
Pair("Romance", "romance"),
Pair("Samurais", "samurais"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("Shounen Ai", "shounen-ai"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Soft Hentai", "soft-hentai"),
Pair("Super Poderes", "super-poderes"),
Pair("Suspenso", "suspenso"),
Pair("Terror", "terror"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
),
)
private class StateFilter : UriPartFilter(
"Estado",
arrayOf(
Pair("Todos", ""),
Pair("Finalizado", "0"),
Pair("En emisión", "1"),
),
)
private class TypeFilter : UriPartFilter(
"Tipo",
arrayOf(
Pair("Todos", ""),
Pair("Animes", "tv"),
Pair("Películas", "movie"),
Pair("Especiales", "special"),
Pair("OVAS", "ova"),
Pair("ONAS", "ona"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
private fun externalOrInternalImg(url: String) = if (url.contains("https")) url else "$baseUrl/$url"
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("1") -> SAnime.ONGOING
statusString.contains("0") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
}

View file

@ -0,0 +1,205 @@
package eu.kanade.tachiyomi.animeextension.es.animelatinohd.extractors
import java.util.regex.Pattern
import kotlin.math.pow
// https://github.com/cylonu87/JsUnpacker
class JsUnpacker(packedJS: String?) {
private var packedJS: String? = null
/**
* Detects whether the javascript is P.A.C.K.E.R. coded.
*
* @return true if it's P.A.C.K.E.R. coded.
*/
fun detect(): Boolean {
val js = packedJS!!.replace(" ", "")
val p = Pattern.compile("eval\\(function\\(p,a,c,k,e,[rd]")
val m = p.matcher(js)
return m.find()
}
/**
* Unpack the javascript
*
* @return the javascript unpacked or null.
*/
fun unpack(): String? {
val js = packedJS
runCatching {
var p =
Pattern.compile("""\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", Pattern.DOTALL)
var m = p.matcher(js)
if (m.find() && m.groupCount() == 4) {
val payload = m.group(1).replace("\\'", "'")
val radixStr = m.group(2)
val countStr = m.group(3)
val symtab = m.group(4).split("\\|".toRegex()).toTypedArray()
val radix = radixStr.toIntOrNull() ?: 36
val count = countStr.toIntOrNull() ?: 0
if (symtab.size != count) {
throw Exception("Unknown p.a.c.k.e.r. encoding")
}
val unbase = Unbase(radix)
p = Pattern.compile("\\b\\w+\\b")
m = p.matcher(payload)
val decoded = StringBuilder(payload)
var replaceOffset = 0
while (m.find()) {
val word = m.group(0)
val x = unbase.unbase(word)
var value: String? = null
if (x < symtab.size && x >= 0) {
value = symtab[x]
}
if (value != null && value.isNotEmpty()) {
decoded.replace(m.start() + replaceOffset, m.end() + replaceOffset, value)
replaceOffset += value.length - word.length
}
}
return decoded.toString()
}
}
return null
}
private inner class Unbase(private val radix: Int) {
private val alphabet62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private val alphabet95 =
" !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
private var alphabet: String? = null
private var dictionary: HashMap<String, Int>? = null
fun unbase(str: String): Int {
var ret = 0
if (alphabet == null) {
ret = str.toInt(radix)
} else {
val tmp = StringBuilder(str).reverse().toString()
for (i in tmp.indices) {
ret += (radix.toDouble().pow(i.toDouble()) * dictionary!![tmp.substring(i, i + 1)]!!).toInt()
}
}
return ret
}
init {
if (radix > 36) {
when {
radix < 62 -> {
alphabet = alphabet62.substring(0, radix)
}
radix in 63..94 -> {
alphabet = alphabet95.substring(0, radix)
}
radix == 62 -> {
alphabet = alphabet62
}
radix == 95 -> {
alphabet = alphabet95
}
}
dictionary = HashMap(95)
for (i in 0 until alphabet!!.length) {
dictionary!![alphabet!!.substring(i, i + 1)] = i
}
}
}
}
/**
* @param packedJS javascript P.A.C.K.E.R. coded.
*/
init {
this.packedJS = packedJS
}
companion object {
private val C =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x67,
0x6f,
0x6f,
0x67,
0x6c,
0x65,
0x2e,
0x61,
0x6e,
0x64,
0x72,
0x6f,
0x69,
0x64,
0x2e,
0x67,
0x6d,
0x73,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x4d,
0x6f,
0x62,
0x69,
0x6c,
0x65,
0x41,
0x64,
0x73,
)
private val Z =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x66,
0x61,
0x63,
0x65,
0x62,
0x6f,
0x6f,
0x6b,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x41,
0x64,
)
fun String.load(): String? {
return try {
var load = this
for (q in C.indices) {
if (C[q % 4] > 270) {
load += C[q % 3]
} else {
load += C[q].toChar()
}
}
Class.forName(load.substring(load.length - C.size, load.length)).name
} catch (_: Exception) {
try {
var f = C[2].toChar().toString()
for (w in Z.indices) {
f += Z[w].toChar()
}
return Class.forName(f.substring(0b001, f.length)).name
} catch (_: Exception) {
null
}
}
}
}
}

View file

@ -0,0 +1,27 @@
package eu.kanade.tachiyomi.animeextension.es.animelatinohd.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class SolidFilesExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>()
return try {
val document = client.newCall(GET(url)).execute().asJsoup()
document.select("script").forEach { script ->
if (script.data().contains("\"downloadUrl\":")) {
val data = script.data().substringAfter("\"downloadUrl\":").substringBefore(",")
val url = data.replace("\"", "")
val videoUrl = url
val quality = prefix + "SolidFiles"
videoList.add(Video(videoUrl, quality, videoUrl))
}
}
videoList
} catch (e: Exception) {
videoList
}
}
}