forked from recloudstream/cloudstream
		
	Merge pull request #123 from Blatzar/master
This commit is contained in:
		
						commit
						96b5c9658d
					
				
					 3 changed files with 347 additions and 0 deletions
				
			
		|  | @ -58,6 +58,7 @@ It merely scrapes 3rd-party websites that are publicly accessable via any regula | ||||||
| - [vidembed.cc](https://vidembed.cc)  | - [vidembed.cc](https://vidembed.cc)  | ||||||
| - [vf-film.org](https://vf-film.org)  | - [vf-film.org](https://vf-film.org)  | ||||||
| - [asianload.cc](https://asianload.cc)  | - [asianload.cc](https://asianload.cc)  | ||||||
|  | - [sflix.to](https://sflix.to)  | ||||||
| - [trailers.to](https://trailers.to)  | - [trailers.to](https://trailers.to)  | ||||||
| - [thenos.org](https://www.thenos.org)  | - [thenos.org](https://www.thenos.org)  | ||||||
| - [asiaflix.app](https://asiaflix.app)  | - [asiaflix.app](https://asiaflix.app)  | ||||||
|  |  | ||||||
|  | @ -45,6 +45,8 @@ object APIHolder { | ||||||
|         VidEmbedProvider(), |         VidEmbedProvider(), | ||||||
|         VfFilmProvider(), |         VfFilmProvider(), | ||||||
|         AsianLoadProvider(), |         AsianLoadProvider(), | ||||||
|  | 
 | ||||||
|  |         SflixProvider(), | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     val restrictedApis = arrayListOf( |     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.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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue