forked from recloudstream/cloudstream
Added SflixProvider.kt
This commit is contained in:
parent
d86a80ae14
commit
b2798d6862
2 changed files with 346 additions and 0 deletions
|
@ -45,6 +45,8 @@ object APIHolder {
|
|||
VidEmbedProvider(),
|
||||
VfFilmProvider(),
|
||||
AsianLoadProvider(),
|
||||
|
||||
SflixProvider(),
|
||||
)
|
||||
|
||||
val restrictedApis = arrayListOf(
|
||||
|
|
|
@ -0,0 +1,344 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import android.net.Uri
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.network.WebViewResolver
|
||||
import com.lagradost.cloudstream3.network.get
|
||||
import com.lagradost.cloudstream3.network.text
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URI
|
||||
|
||||
class SflixProvider : MainAPI() {
|
||||
override val mainUrl: String
|
||||
get() = "https://sflix.to"
|
||||
override val name: String
|
||||
get() = "Sflix"
|
||||
|
||||
override val hasQuickSearch: Boolean
|
||||
get() = false
|
||||
|
||||
override val hasMainPage: Boolean
|
||||
get() = true
|
||||
|
||||
override val hasChromecastSupport: Boolean
|
||||
get() = true
|
||||
|
||||
override val hasDownloadSupport: Boolean
|
||||
get() = false
|
||||
|
||||
override val supportedTypes: Set<TvType>
|
||||
get() = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
)
|
||||
|
||||
private fun Element.toSearchResult(): SearchResponse {
|
||||
val img = this.select("img")
|
||||
val title = img.attr("title")
|
||||
val posterUrl = img.attr("data-src")
|
||||
val href = fixUrl(this.select("a").attr("href"))
|
||||
val isMovie = href.contains("/movie/")
|
||||
return if (isMovie) {
|
||||
MovieSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this@SflixProvider.name,
|
||||
TvType.Movie,
|
||||
posterUrl,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this@SflixProvider.name,
|
||||
TvType.Movie,
|
||||
posterUrl,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMainPage(): HomePageResponse? {
|
||||
val html = get("$mainUrl/home").text
|
||||
val document = Jsoup.parse(html)
|
||||
|
||||
val all = ArrayList<HomePageList>()
|
||||
|
||||
val map = mapOf(
|
||||
"Trending Movies" to "div#trending-movies",
|
||||
"Trending TV Shows" to "div#trending-tv",
|
||||
)
|
||||
map.forEach {
|
||||
all.add(HomePageList(
|
||||
it.key,
|
||||
document.select(it.value).select("div.film-poster").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
document.select("section.block_area.block_area_home.section-id-02").forEach {
|
||||
val title = it.select("h2.cat-heading").text().trim()
|
||||
val elements = it.select("div.film-poster").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
all.add(HomePageList(title, elements))
|
||||
}
|
||||
|
||||
return HomePageResponse(all)
|
||||
}
|
||||
|
||||
override val vpnStatus: VPNStatus
|
||||
get() = VPNStatus.None
|
||||
|
||||
override fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/search/$query"
|
||||
val html = get(url).text
|
||||
val document = Jsoup.parse(html)
|
||||
|
||||
return document.select("div.flw-item").map {
|
||||
val title = it.select("h2.film-name").text()
|
||||
val href = fixUrl(it.select("a").attr("href"))
|
||||
val year = it.select("span.fdi-item").text().toIntOrNull()
|
||||
val image = it.select("img").attr("data-src")
|
||||
val isMovie = href.contains("/movie/")
|
||||
|
||||
if (isMovie) {
|
||||
MovieSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
image,
|
||||
year
|
||||
)
|
||||
} else {
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
image,
|
||||
year,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun load(url: String): LoadResponse? {
|
||||
val html = get(url).text
|
||||
val document = Jsoup.parse(html)
|
||||
|
||||
val details = document.select("div.detail_page-watch")
|
||||
val img = details.select("img.film-poster-img")
|
||||
val posterUrl = img.attr("src")
|
||||
val title = img.attr("title")
|
||||
val year = Regex("""[Rr]eleased:\s*(\d{4})""").find(
|
||||
document.select("div.elements").text()
|
||||
)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val duration = Regex("""[Dd]uration:\s*(\d*)""").find(
|
||||
document.select("div.elements").text()
|
||||
)?.groupValues?.get(1)?.trim()?.plus(" min")
|
||||
|
||||
val plot = details.select("div.description").text().replace("Overview:", "").trim()
|
||||
|
||||
|
||||
val isMovie = url.contains("/movie/")
|
||||
|
||||
|
||||
// https://sflix.to/movie/free-never-say-never-again-hd-18317 -> 18317
|
||||
val idRegex = Regex(""".*-(\d+)""")
|
||||
val dataId = details.attr("data-id")
|
||||
val id = if (dataId.isNullOrEmpty())
|
||||
idRegex.find(url)?.groupValues?.get(1) ?: throw RuntimeException("Unable to get id from '$url'")
|
||||
else dataId
|
||||
|
||||
if (isMovie) {
|
||||
// Movies
|
||||
val episodesUrl = "$mainUrl/ajax/movie/episodes/$id"
|
||||
val episodes = get(episodesUrl).text
|
||||
|
||||
// Supported streams, they're identical
|
||||
val sourceId = Jsoup.parse(episodes).select("a").firstOrNull {
|
||||
it.select("span").text().trim().equals("RapidStream", ignoreCase = true)
|
||||
|| it.select("span").text().trim().equals("Vidcloud", ignoreCase = true)
|
||||
}?.attr("data-id")
|
||||
|
||||
val webViewUrl = "$url${sourceId?.let { ".$it" } ?: ""}".replace("/movie/", "/watch-movie/")
|
||||
|
||||
return MovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
webViewUrl,
|
||||
posterUrl,
|
||||
year,
|
||||
plot,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
duration,
|
||||
null,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
val seasonsHtml = get("$mainUrl/ajax/v2/tv/seasons/$id").text
|
||||
val seasonsDocument = Jsoup.parse(seasonsHtml)
|
||||
val episodes = arrayListOf<TvSeriesEpisode>()
|
||||
|
||||
seasonsDocument.select("div.dropdown-menu.dropdown-menu-model > a").forEachIndexed { season, it ->
|
||||
val seasonId = it.attr("data-id")
|
||||
if (seasonId.isNullOrBlank()) return@forEachIndexed
|
||||
|
||||
val seasonHtml = get("$mainUrl/ajax/v2/season/episodes/$seasonId").text
|
||||
val seasonDocument = Jsoup.parse(seasonHtml)
|
||||
seasonDocument.select("div.flw-item.film_single-item.episode-item.eps-item")
|
||||
.forEachIndexed { i, it ->
|
||||
val episodeImg = it.select("img")
|
||||
val episodeTitle = episodeImg.attr("title")
|
||||
val episodePosterUrl = episodeImg.attr("src")
|
||||
val episodeData = it.attr("data-id")
|
||||
|
||||
// val episodeNum =
|
||||
// Regex("""\d+""").find(it.select("div.episode-number").text())?.groupValues?.get(1)
|
||||
// ?.toIntOrNull()
|
||||
|
||||
episodes.add(
|
||||
TvSeriesEpisode(
|
||||
episodeTitle,
|
||||
season + 1,
|
||||
null,
|
||||
"$url:::$episodeData",
|
||||
fixUrl(episodePosterUrl)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
return TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
episodes,
|
||||
posterUrl,
|
||||
year,
|
||||
plot,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
duration,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Tracks(
|
||||
@JsonProperty("file") val file: String?,
|
||||
@JsonProperty("label") val label: String?,
|
||||
@JsonProperty("kind") val kind: String?
|
||||
)
|
||||
|
||||
data class Sources1(
|
||||
@JsonProperty("file") val file: String?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("label") val label: String?
|
||||
)
|
||||
|
||||
data class SourceObject(
|
||||
@JsonProperty("sources_1") val sources1: List<Sources1?>?,
|
||||
@JsonProperty("sources_2") val sources2: List<Sources1?>?,
|
||||
@JsonProperty("tracks") val tracks: List<Tracks?>?
|
||||
)
|
||||
|
||||
private fun Sources1.toExtractorLink(): ExtractorLink? {
|
||||
return this.file?.let {
|
||||
ExtractorLink(
|
||||
this@SflixProvider.name,
|
||||
this.label?.let { "${this@SflixProvider.name} - $it" } ?: this@SflixProvider.name,
|
||||
it,
|
||||
this@SflixProvider.mainUrl,
|
||||
getQualityFromName(this.label ?: ""),
|
||||
URI(this.file).path.endsWith(".m3u8") || this.label.equals("hls", ignoreCase = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Tracks.toSubtitleFile(): SubtitleFile? {
|
||||
return this.file?.let {
|
||||
SubtitleFile(
|
||||
this.label ?: "Unknown",
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
// To transfer url:::id
|
||||
val split = data.split(":::")
|
||||
// Only used for tv series
|
||||
val url = if (split.size == 2) {
|
||||
val episodesUrl = "$mainUrl/ajax/v2/episode/servers/${split[1]}"
|
||||
val episodes = get(episodesUrl).text
|
||||
|
||||
// Supported streams, they're identical
|
||||
val sourceId = Jsoup.parse(episodes).select("a").firstOrNull {
|
||||
it.select("span").text().trim().equals("RapidStream", ignoreCase = true)
|
||||
|| it.select("span").text().trim().equals("Vidcloud", ignoreCase = true)
|
||||
}?.attr("data-id")
|
||||
|
||||
"${split[0]}${sourceId?.let { ".$it" } ?: ""}".replace("/tv/", "/watch-tv/")
|
||||
} else {
|
||||
data
|
||||
}
|
||||
|
||||
val sources = get(
|
||||
url,
|
||||
interceptor = WebViewResolver(
|
||||
Regex("""/getSources""")
|
||||
)
|
||||
).text
|
||||
|
||||
val mapped = mapper.readValue<SourceObject>(sources)
|
||||
|
||||
mapped.sources1?.forEach {
|
||||
it?.toExtractorLink()?.let { extractorLink ->
|
||||
callback.invoke(
|
||||
extractorLink
|
||||
)
|
||||
}
|
||||
}
|
||||
mapped.sources2?.forEach {
|
||||
it?.toExtractorLink()?.let { extractorLink ->
|
||||
callback.invoke(
|
||||
extractorLink
|
||||
)
|
||||
}
|
||||
}
|
||||
mapped.tracks?.forEach {
|
||||
it?.toSubtitleFile()?.let { subtitleFile ->
|
||||
subtitleCallback.invoke(subtitleFile)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue