This commit is contained in:
imper1aldev
2023-03-19 12:25:42 -06:00
committed by GitHub
parent 04fbbb1bbb
commit 68db5b45b7
3 changed files with 311 additions and 62 deletions

View File

@ -5,8 +5,16 @@ ext {
extName = 'EnNovelas' extName = 'EnNovelas'
pkgNameSuffix = 'es.ennovelas' pkgNameSuffix = 'es.ennovelas'
extClass = '.EnNovelas' extClass = '.EnNovelas'
extVersionCode = 1 extVersionCode = 2
libVersion = '13' libVersion = '13'
} }
dependencies {
implementation(project(':lib-okru-extractor'))
implementation project(path: ':lib-streamsb-extractor')
implementation project(path: ':lib-fembed-extractor')
implementation project(path: ':lib-voe-extractor')
implementation project(path: ':lib-streamtape-extractor')
implementation project(path: ':lib-dood-extractor')
}
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -4,29 +4,40 @@ import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.ennovelas.extractors.StreamlareExtractor
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.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode 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.fembedextractor.FembedExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
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 java.lang.Exception import kotlin.Exception
import java.text.Normalizer
class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "EnNovelas" override val name = "EnNovelas"
override val baseUrl = "https://www.ennovelas.com" override val baseUrl = "https://w.ennovelas.net"
override val lang = "es" override val lang = "es"
@ -38,42 +49,59 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
override fun popularAnimeSelector(): String = "#container section.search-videos div.section-content div.row div div.col-xs-6 div.video-post" override fun popularAnimeSelector(): String = ".block-post"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/?op=categories_all&per_page=60&page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/telenovelas/page/$page")
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
anime.setUrlWithoutDomain(changeUrlFormat(element.select("a").attr("href"))) anime.setUrlWithoutDomain(element.select("a").attr("href"))
anime.title = element.select("a p").text() anime.title = element.select("a .title").text()
anime.thumbnail_url = element.select("a div.thumb").attr("style") anime.thumbnail_url = element.select("a img").attr("data-img")
.substringAfter("background-image:url(").substringBefore(")")
anime.description = "" anime.description = ""
return anime return anime
} }
private fun changeUrlFormat(link: String): String { override fun popularAnimeNextPageSelector(): String = ".pagination .current ~ a"
val novel = link.substringAfter("/category/").replace("+", "%20")
return "$baseUrl/?cat_name=$novel&op=search&per_page=all"
}
override fun popularAnimeNextPageSelector(): String = "#container section div.section-content div.paging a:last-of-type"
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup() val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>() val episodeList = mutableListOf<SEpisode>()
document.select("#col3 div.videobox").forEach { element -> val seasonIds = document.select(".listSeasons li[data-season]")
val ep = SEpisode.create() var noEp = 1F
val noEpisode = getNumberFromEpsString( if (seasonIds.any()) {
element.selectFirst("a:nth-child(2)")!!.text().substringAfter("Cap") seasonIds.reversed().map {
.substringBefore("FIN").substringBefore("fin"), val headers = headers.newBuilder()
) .add("authority", "w.ennovelas.net")
ep.setUrlWithoutDomain(element.selectFirst("a.video200")!!.attr("href")) .add("referer", response.request.url.toString())
ep.name = "Cap" + element.selectFirst("a:nth-child(2)")!!.text().substringAfter("Cap") .add("accept", "*/*")
ep.episode_number = noEpisode.toFloat() .add("accept-language", "es-MX,es;q=0.9,en;q=0.8")
episodeList.add(ep) .add("sec-fetch-mode", "cors")
.add("x-requested-with", "XMLHttpRequest")
.build()
val season = getNumberFromEpsString(it.text())
client.newCall(GET("$baseUrl/wp-content/themes/vo2022/temp/ajax/seasons.php?seriesID=${it.attr("data-season")}", headers = headers))
.execute().asJsoup().select(".block-post").forEach { element ->
val ep = SEpisode.create()
val noEpisode = getNumberFromEpsString(element.selectFirst("a .episodeNum span:nth-child(2)")!!.text()).ifEmpty { noEp }
ep.setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
ep.name = "T$season - E$noEpisode - Cap" + element.selectFirst("a .title")!!.text().substringAfter("Cap")
ep.episode_number = noEp
episodeList.add(ep)
noEp += 1
}
}
} else {
document.select(".block-post").forEach { element ->
val ep = SEpisode.create()
val noEpisode = getNumberFromEpsString(element.selectFirst("a .episodeNum span:nth-child(2)")!!.text())
ep.setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
ep.name = "Cap" + element.selectFirst("a .title")!!.text().substringAfter("Cap")
ep.episode_number = noEpisode.toFloat()
episodeList.add(ep)
}
} }
return episodeList.sortedByDescending { x -> x.episode_number } return episodeList.reversed()
} }
override fun episodeListSelector() = "uwu" override fun episodeListSelector() = "uwu"
@ -87,16 +115,126 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
document.select("script").forEach { script -> val form = document.selectFirst("#btnServers form")
if (script.data().contains("window.hola_player({")) { val urlRequest = form?.attr("action") ?: ""
val url = script.data().substringAfter("sources: [{src: \"").substringBefore("\",") val watch = form?.selectFirst("input")?.attr("value")
val quality = script.data().substringAfter("res: ").substringBefore(",") val domainRegex = Regex("^(?:https?:\\/\\/)?(?:[^@\\/\\n]+@)?(?:www\\.)?([^:\\/?\\n]+)")
videoList.add(Video(url, "${quality}p", url, headers = null)) val domainUrl = domainRegex.findAll(urlRequest).firstOrNull()?.value ?: ""
val mediaType = "application/x-www-form-urlencoded".toMediaType()
val body = "watch=$watch&submit=".toRequestBody(mediaType)
val headers = headers.newBuilder()
.add("authority", domainUrl.substringAfter("//"))
.add("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
.add("accept-language", "es-MX,es;q=0.9,en;q=0.8")
.add("content-type", "application/x-www-form-urlencoded")
.add("origin", baseUrl)
.add("referer", "$baseUrl/")
.add("sec-ch-ua-mobile", "?0")
.add("upgrade-insecure-requests", "1")
.build()
client.newCall(POST(urlRequest, headers, body)).execute().asJsoup().select(".serversList li").map {
val frameString = it.attr("data-server")
val link = urlResolve(frameString.substringAfter("src='").substringBefore("'"))
.replace("https://api.mycdn.moe/sblink.php?id=", "https://streamsb.net/e/")
.replace("https://api.mycdn.moe/uqlink.php?id=", "https://uqload.co/embed-")
if (link.contains("ok.ru")) {
try {
OkruExtractor(client).videosFromUrl(link).let { videoList.addAll(it) }
} catch (_: Exception) {}
}
if (link.contains("sbembed.com") || link.contains("sbembed1.com") || link.contains("sbplay.org") ||
link.contains("sbvideo.net") || link.contains("streamsb.net") || link.contains("sbplay.one") ||
link.contains("cloudemb.com") || link.contains("playersb.com") || link.contains("tubesb.com") ||
link.contains("sbplay1.com") || link.contains("embedsb.com") || link.contains("watchsb.com") ||
link.contains("sbplay2.com") || link.contains("japopav.tv") || link.contains("viewsb.com") ||
link.contains("sbfast") || link.contains("sbfull.com") || link.contains("javplaya.com") ||
link.contains("ssbstream.net") || link.contains("p1ayerjavseen.com") || link.contains("sbthe.com") ||
link.contains("sbchill.com") || link.contains("sblongvu.com") || link.contains("sbanh.com") ||
link.contains("sblanh.com") || link.contains("sbhight.com") || link.contains("sbbrisk.com") ||
link.contains("sbspeed.com") || link.contains("multimovies.website")
) {
try {
StreamSBExtractor(client).videosFromUrl(link, headers).let { videoList.addAll(it) }
} catch (_: Exception) {}
}
if (link.contains("vidmoly")) {
try {
VidmolyExtractor(client).getVideoList(link, "").let { videoList.addAll(it) }
} catch (_: Exception) {}
}
if (link.contains("fembed") || link.contains("anime789.com") || link.contains("24hd.club") ||
link.contains("fembad.org") || link.contains("vcdn.io") || link.contains("sharinglink.club") ||
link.contains("moviemaniac.org") || link.contains("votrefiles.club") || link.contains("femoload.xyz") ||
link.contains("albavido.xyz") || link.contains("feurl.com") || link.contains("dailyplanet.pw") ||
link.contains("ncdnstm.com") || link.contains("jplayer.net") || link.contains("xstreamcdn.com") ||
link.contains("fembed-hd.com") || link.contains("gcloud.live") || link.contains("vcdnplay.com") ||
link.contains("superplayxyz.club") || link.contains("vidohd.com") || link.contains("vidsource.me") ||
link.contains("cinegrabber.com") || link.contains("votrefile.xyz") || link.contains("zidiplay.com") ||
link.contains("ndrama.xyz") || link.contains("fcdn.stream") || link.contains("mediashore.org") ||
link.contains("suzihaza.com") || link.contains("there.to") || link.contains("femax20.com") ||
link.contains("javstream.top") || link.contains("viplayer.cc") || link.contains("sexhd.co") ||
link.contains("fembed.net") || link.contains("mrdhan.com") || link.contains("votrefilms.xyz") ||
link.contains("embedsito.com") || link.contains("dutrag.com") || link.contains("youvideos.ru") ||
link.contains("streamm4u.club") || link.contains("moviepl.xyz") || link.contains("asianclub.tv") ||
link.contains("vidcloud.fun") || link.contains("fplayer.info") || link.contains("diasfem.com") ||
link.contains("javpoll.com") || link.contains("reeoov.tube") || link.contains("suzihaza.com") ||
link.contains("ezsubz.com") || link.contains("vidsrc.xyz") || link.contains("diampokusy.com") ||
link.contains("diampokusy.com") || link.contains("i18n.pw") || link.contains("vanfem.com") ||
link.contains("fembed9hd.com") || link.contains("votrefilms.xyz") || link.contains("watchjavnow.xyz")
) {
try {
FembedExtractor(client).videosFromUrl(link, redirect = !link.contains("fembed")).let { videoList.addAll(it) }
} catch (_: Exception) {}
}
if (link.contains("voe")) {
try {
VoeExtractor(client).videoFromUrl(link, "Voex")?.let { videoList.add(it) }
} catch (_: Exception) {}
}
if (link.contains("streamtape")) {
try {
StreamTapeExtractor(client).videoFromUrl(link)?.let { videoList.add(it) }
} catch (_: Exception) {}
}
if (link.contains("uqload")) {
// uqload.co/embed-rxkjujtas0po.html
try {
val headers = headers.newBuilder()
.add("Accept", "*/*")
.add("authority", "uqload.co")
.add("Accept-Language", "es-MX,es;q=0.9,en;q=0.8")
.add("Connection", "keep-alive")
.add("Range", "bytes=0-")
.add("Referer", "https://uqload.co/")
.add("Sec-Fetch-Dest", "video")
.add("Sec-Fetch-Mode", "no-cors")
.add("Sec-Fetch-Site", "same-site")
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36")
.add("sec-ch-ua", "\"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\"")
.add("sec-ch-ua-mobile", "?0")
.add("sec-ch-ua-platform", "\"Windows\"")
.build()
UqloadExtractor(client).videoFromUrl(if (link.contains(".html")) link else "$link.html", headers, "Uqload").let { videoList.addAll(it) }
} catch (_: Exception) {}
}
if (link.contains("dood")) {
try {
DoodExtractor(client).videoFromUrl(link)?.let { videoList.add(it) }
} catch (_: Exception) {}
}
if (link.contains("streamlare")) {
try {
StreamlareExtractor(client).videosFromUrl(link)?.let { videoList.add(it) }
} catch (_: Exception) {}
} }
} }
return videoList return videoList
} }
private fun urlResolve(url: String) = if (url.startsWith("//")) "https:$url" else url
override fun videoListSelector() = throw Exception("not used") override fun videoListSelector() = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used") override fun videoUrlParse(document: Document) = throw Exception("not used")
@ -104,7 +242,7 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "720p") val quality = preferences.getString("preferred_quality", "Voex")
if (quality != null) { if (quality != null) {
val newList = mutableListOf<Video>() val newList = mutableListOf<Video>()
var preferred = 0 var preferred = 0
@ -123,60 +261,66 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return when { return when {
query.isNotBlank() -> GET("$baseUrl/?name=$query&op=categories_all&page=$page") query.isNotBlank() -> GET("$baseUrl/search/$query/page/$page/")
else -> popularAnimeRequest(page) else -> popularAnimeRequest(page)
} }
} }
override fun searchAnimeFromElement(element: Element): SAnime { override fun searchAnimeParse(response: Response): AnimesPage {
return popularAnimeFromElement(element) val document = response.asJsoup()
val animeList = mutableListOf<SAnime>()
val hasNextPage = document.select(".pagination .current ~ a").any()
document.select(".block-post").map { element ->
if (element.selectFirst("a")?.attr("href")?.contains("/series/") == true) {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").attr("href"))
anime.title = element.select("a .title").text()
anime.thumbnail_url = element.select("a img").attr("data-img")
anime.description = ""
animeList.add(anime)
}
}
return AnimesPage(animeList, hasNextPage)
} }
override fun searchAnimeFromElement(element: Element): SAnime = throw Exception("not used")
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() override fun searchAnimeNextPageSelector(): String = throw Exception("not used")
override fun searchAnimeSelector(): String = throw Exception("not used")
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime { override fun animeDetailsParse(document: Document): SAnime {
val descriptionElement = document.selectFirst("#inwg")!!.text()
.substringAfter("Notifications:")
.substringBefore("Capitulo")
.substringBefore("Capítulo")
val anime = SAnime.create() val anime = SAnime.create()
val title = document.selectFirst("#inwg h3 span.first-word")!!.text() val title = document.selectFirst("[itemprop=\"name\"]")?.text() ?: ""
val idx = idxDescription(descriptionElement, title) val description = document.selectFirst(".postDesc p:nth-child(2)")?.text() ?: ""
val description = descriptionElement.substring(0, idx).trim() val genres = document.select("ul.postlist li:nth-child(1) span a").joinToString { it.text() }
anime.title = title.trim() anime.title = title
anime.description = description.ifEmpty { title } anime.description = description.ifEmpty { title }
anime.genre = "novela" anime.genre = genres
anime.status = SAnime.UNKNOWN anime.status = SAnime.UNKNOWN
return anime return anime
} }
private fun String.removeNonSpacingMarks() = Normalizer.normalize(this, Normalizer.Form.NFD).replace("\\p{Mn}+".toRegex(), "")
private fun idxDescription(text: String, title: String): Int {
val descriptionLowercase = text.lowercase().removeNonSpacingMarks()
val titleLowercase = title.lowercase().removeNonSpacingMarks()
val idx = descriptionLowercase.lastIndexOf(titleLowercase)
return if (idx == -1) descriptionLowercase.length - 1 else idx
}
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element) override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/browse?order=added&page=$page") override fun latestUpdatesRequest(page: Int) = popularAnimeRequest(page)
override fun latestUpdatesSelector() = popularAnimeSelector() override fun latestUpdatesSelector() = popularAnimeSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru
"Fembed:1080p", "Fembed:720p", "Fembed:480p", "Fembed:360p", "Fembed:240p", "Fembed:144p", // Fembed
"StreamSB:1080p", "StreamSB:720p", "StreamSB:480p", "StreamSB:360p", "StreamSB:240p", "StreamSB:144p", // StreamSB
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare
"StreamTape", "Voex", "DoodStream", "YourUpload", "MixDrop", "Vidmoly", "Uqload",
)
val videoQualityPref = ListPreference(screen.context).apply { val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality" key = "preferred_quality"
title = "Preferred quality" title = "Preferred quality"
entries = arrayOf("1080p", "720p", "480p") entries = qualities
entryValues = arrayOf("1080p", "720p", "480p") entryValues = qualities
setDefaultValue("720p") setDefaultValue("Voex")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -189,3 +333,45 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
screen.addPreference(videoQualityPref) screen.addPreference(videoQualityPref)
} }
} }
class VidmolyExtractor(private val client: OkHttpClient) {
private val REGEX_PLAYLIST = Regex("file:\"(\\S+?)\"")
fun getVideoList(url: String, lang: String): List<Video> {
val body = client.newCall(GET(url)).execute()
.use { it.body.string() }
val playlistUrl = REGEX_PLAYLIST.find(body)!!.groupValues.get(1)
val headers = Headers.headersOf("Referer", "https://vidmoly.to")
val playlistData = client.newCall(GET(playlistUrl, headers)).execute()
.use { it.body.string() }
val separator = "#EXT-X-STREAM-INF:"
return playlistData.substringAfter(separator).split(separator).map {
val quality = it.substringAfter("RESOLUTION=")
.substringAfter("x")
.substringBefore(",") + "p"
val videoUrl = it.substringAfter("\n").substringBefore("\n")
Video(videoUrl, "$lang - $quality", videoUrl, headers)
}
}
}
class UqloadExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, headers: Headers, quality: String): List<Video> {
val videoList = mutableListOf<Video>()
return try {
val document = client.newCall(GET(url)).execute().asJsoup()
document.select("script").map {
if (it.data().contains("var player = new")) {
val basicUrl =
it.data().substringAfter("sources: [\"").substringBefore("\"],")
videoList.add(Video(basicUrl, quality, basicUrl, headers = headers))
}
}
videoList
} catch (e: Exception) {
videoList
}
}
}

View File

@ -0,0 +1,55 @@
package eu.kanade.tachiyomi.animeextension.es.ennovelas.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
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
import uy.kohesive.injekt.injectLazy
class StreamlareExtractor(private val client: OkHttpClient) {
private val json: Json by injectLazy()
fun videosFromUrl(url: String): Video? {
val id = url.substringAfter("/e/").substringBefore("?poster")
val videoUrlResponse =
client.newCall(POST("https://slwatch.co/api/video/stream/get?id=$id")).execute()
.asJsoup()
json.decodeFromString<JsonObject>(
videoUrlResponse.select("body").text(),
)["result"]?.jsonObject?.forEach { quality ->
if (quality.toString().contains("file=\"")) {
val videoUrl = quality.toString().substringAfter("file=\"").substringBefore("\"").trim()
val type = if (videoUrl.contains(".m3u8")) "HSL" else "MP4"
val headers = Headers.Builder()
.add("authority", videoUrl.substringBefore("/hls").substringBefore("/mp4"))
.add("origin", "https://slwatch.co")
.add("referer", "https://slwatch.co/e/" + url.substringAfter("/e/"))
.add(
"sec-ch-ua",
"\"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"108\", \"Google Chrome\";v=\"108\"",
)
.add("sec-ch-ua-mobile", "?0")
.add("sec-ch-ua-platform", "\"Windows\"")
.add("sec-fetch-dest", "empty")
.add("sec-fetch-mode", "cors")
.add("sec-fetch-site", "cross-site")
.add(
"user-agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/),108.0.0.0 Safari/537.36",
)
.add("Accept-Encoding", "gzip, deflate, br")
.add("accept", "*/*")
.add(
"accept-language",
"es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7,zh-TW;q=0.6,zh-CN;q=0.5,zh;q=0.4",
)
.build()
return Video(videoUrl, "Streamlare:$type", videoUrl, headers = headers)
}
}
return null
}
}