mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	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…
	
	Add table
		Add a link
		
	
		Reference in a new issue