Merge pull request #123 from Blatzar/master

This commit is contained in:
Arjix 2021-10-05 20:07:14 +03:00 committed by GitHub
commit 96b5c9658d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 347 additions and 0 deletions

View file

@ -58,6 +58,7 @@ It merely scrapes 3rd-party websites that are publicly accessable via any regula
- [vidembed.cc](https://vidembed.cc)
- [vf-film.org](https://vf-film.org)
- [asianload.cc](https://asianload.cc)
- [sflix.to](https://sflix.to)
- [trailers.to](https://trailers.to)
- [thenos.org](https://www.thenos.org)
- [asiaflix.app](https://asiaflix.app)

View file

@ -45,6 +45,8 @@ object APIHolder {
VidEmbedProvider(),
VfFilmProvider(),
AsianLoadProvider(),
SflixProvider(),
)
val restrictedApis = arrayListOf(

View file

@ -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.replace(" ", "-")}"
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
}
}