forked from recloudstream/cloudstream
		
	New source with content in Latin Spanish (#369)
* Pelisplus spanish provider Co-authored-by: LagradOst <balt.758@gmail.com>
This commit is contained in:
		
							parent
							
								
									8c6e5a63d5
								
							
						
					
					
						commit
						bd91cfb5fc
					
				
					 11 changed files with 446 additions and 6 deletions
				
			
		|  | @ -75,4 +75,5 @@ It merely scrapes 3rd-party websites that are publicly accessable via any regula | ||||||
| - [pinoymoviepedia.ru](https://pinoymoviepedia.ru)  | - [pinoymoviepedia.ru](https://pinoymoviepedia.ru)  | ||||||
| - [pinoy-hd.xyz](https://www.pinoy-hd.xyz)  | - [pinoy-hd.xyz](https://www.pinoy-hd.xyz)  | ||||||
| - [asiaflix.app](https://asiaflix.app)  | - [asiaflix.app](https://asiaflix.app)  | ||||||
|  | - [pelisplus.icu](https://pelisplus.icu)  | ||||||
| <!--SITE LIST END--> | <!--SITE LIST END--> | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ object APIHolder { | ||||||
|     private const val defProvider = 0 |     private const val defProvider = 0 | ||||||
| 
 | 
 | ||||||
|     val apis = arrayListOf( |     val apis = arrayListOf( | ||||||
|  |         PelisplusProvider(), | ||||||
|         GogoanimeProvider(), |         GogoanimeProvider(), | ||||||
|         AllAnimeProvider(), |         AllAnimeProvider(), | ||||||
|         //ShiroProvider(), // v2 fucked me |         //ShiroProvider(), // v2 fucked me | ||||||
|  |  | ||||||
|  | @ -14,6 +14,11 @@ class DoodSoExtractor : DoodLaExtractor() { | ||||||
|     override val mainUrl = "https://dood.so" |     override val mainUrl = "https://dood.so" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | class DoodWsExtractor : DoodLaExtractor() { | ||||||
|  |     override val mainUrl = "https://dood.ws" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| open class DoodLaExtractor : ExtractorApi() { | open class DoodLaExtractor : ExtractorApi() { | ||||||
|     override val name = "DoodStream" |     override val name = "DoodStream" | ||||||
|     override val mainUrl = "https://dood.la" |     override val mainUrl = "https://dood.la" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | package com.lagradost.cloudstream3.extractors | ||||||
|  | 
 | ||||||
|  | import android.util.Log | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
|  | import com.fasterxml.jackson.module.kotlin.readValue | ||||||
|  | import com.lagradost.cloudstream3.network.Session | ||||||
|  | import com.lagradost.cloudstream3.utils.* | ||||||
|  | import com.lagradost.cloudstream3.mapper | ||||||
|  | 
 | ||||||
|  | class FeHD: XStreamCdn() { | ||||||
|  |     override val name: String = "FeHD" | ||||||
|  |     override val mainUrl: String = "https://fembed-hd.com" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | package com.lagradost.cloudstream3.extractors | ||||||
|  | 
 | ||||||
|  | import android.util.Log | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
|  | import com.fasterxml.jackson.module.kotlin.readValue | ||||||
|  | import com.lagradost.cloudstream3.network.Session | ||||||
|  | import com.lagradost.cloudstream3.utils.* | ||||||
|  | import com.lagradost.cloudstream3.mapper | ||||||
|  | 
 | ||||||
|  | class Fplayer: XStreamCdn() { | ||||||
|  |     override val name: String = "Fplayer" | ||||||
|  |     override val mainUrl: String = "https://fplayer.info" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,98 @@ | ||||||
|  | package com.lagradost.cloudstream3.extractors | ||||||
|  | 
 | ||||||
|  | import com.lagradost.cloudstream3.app | ||||||
|  | import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | ||||||
|  | import com.lagradost.cloudstream3.pmap | ||||||
|  | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
|  | import com.lagradost.cloudstream3.utils.extractorApis | ||||||
|  | import com.lagradost.cloudstream3.utils.getQualityFromName | ||||||
|  | import com.lagradost.cloudstream3.utils.loadExtractor | ||||||
|  | import org.jsoup.Jsoup | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * overrideMainUrl is necessary for for other vidstream clones like vidembed.cc | ||||||
|  |  * If they diverge it'd be better to make them separate. | ||||||
|  |  * */ | ||||||
|  | class Pelisplus(val mainUrl: String) { | ||||||
|  |     val name: String = "Vidstream" | ||||||
|  | 
 | ||||||
|  |     private fun getExtractorUrl(id: String): String { | ||||||
|  |         return "$mainUrl/play?id=$id" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun getDownloadUrl(id: String): String { | ||||||
|  |         return "$mainUrl/download?id=$id" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private val normalApis = arrayListOf(MultiQuality()) | ||||||
|  | 
 | ||||||
|  |     // https://gogo-stream.com/streaming.php?id=MTE3NDg5 | ||||||
|  |     fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean { | ||||||
|  |         try { | ||||||
|  |             normalApis.pmap { api -> | ||||||
|  |                 val url = api.getExtractorUrl(id) | ||||||
|  |                 val source = api.getSafeUrl(url) | ||||||
|  |                 source?.forEach { callback.invoke(it) } | ||||||
|  |             } | ||||||
|  |             val extractorUrl = getExtractorUrl(id) | ||||||
|  | 
 | ||||||
|  |             /** Stolen from GogoanimeProvider.kt extractor */ | ||||||
|  |             normalSafeApiCall { | ||||||
|  |                 val link = getDownloadUrl(id) | ||||||
|  |                 println("Generated vidstream download link: $link") | ||||||
|  |                 val page = app.get(link, referer = extractorUrl) | ||||||
|  | 
 | ||||||
|  |                 val pageDoc = Jsoup.parse(page.text) | ||||||
|  |                 val qualityRegex = Regex("(\\d+)P") | ||||||
|  | 
 | ||||||
|  |                 //a[download] | ||||||
|  |                 pageDoc.select(".dowload > a")?.pmap { element -> | ||||||
|  |                     val href = element.attr("href") ?: return@pmap | ||||||
|  |                     val qual = if (element.text() | ||||||
|  |                             .contains("HDP") | ||||||
|  |                     ) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString() | ||||||
|  | 
 | ||||||
|  |                     if (!loadExtractor(href, link, callback)) { | ||||||
|  |                         callback.invoke( | ||||||
|  |                             ExtractorLink( | ||||||
|  |                                 this.name, | ||||||
|  |                                 if (qual == "null") this.name else "${this.name} - " + qual + "p", | ||||||
|  |                                 href, | ||||||
|  |                                 page.url, | ||||||
|  |                                 getQualityFromName(qual), | ||||||
|  |                                 element.attr("href").contains(".m3u8") | ||||||
|  |                             ) | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             with(app.get(extractorUrl)) { | ||||||
|  |                 val document = Jsoup.parse(this.text) | ||||||
|  |                 val primaryLinks = document.select("ul.list-server-items > li.linkserver") | ||||||
|  |                 //val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||||
|  | 
 | ||||||
|  |                 // All vidstream links passed to extractors | ||||||
|  |                 primaryLinks.distinctBy { it.attr("data-video") }.forEach { element -> | ||||||
|  |                     val link = element.attr("data-video") | ||||||
|  |                     //val name = element.text() | ||||||
|  | 
 | ||||||
|  |                     // Matches vidstream links with extractors | ||||||
|  |                     extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api -> | ||||||
|  |                         if (link.startsWith(api.mainUrl)) { | ||||||
|  |                             val extractedLinks = api.getSafeUrl(link, extractorUrl) | ||||||
|  |                             if (extractedLinks?.isNotEmpty() == true) { | ||||||
|  |                                 extractedLinks.forEach { | ||||||
|  |                                     callback.invoke(it) | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return true | ||||||
|  |             } | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             return false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -9,6 +9,10 @@ import com.lagradost.cloudstream3.utils.Qualities | ||||||
| import com.lagradost.cloudstream3.utils.getPostForm | import com.lagradost.cloudstream3.utils.getPostForm | ||||||
| import org.jsoup.Jsoup | import org.jsoup.Jsoup | ||||||
| 
 | 
 | ||||||
|  | class SBPlay1 : SBPlay() { | ||||||
|  |     override val mainUrl = "https://sbplay1.com" | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class SBPlay2 : SBPlay() { | class SBPlay2 : SBPlay() { | ||||||
|     override val mainUrl = "https://sbplay2.com" |     override val mainUrl = "https://sbplay2.com" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | import com.lagradost.cloudstream3.utils.M3u8Helper | ||||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | import com.lagradost.cloudstream3.utils.getQualityFromName | ||||||
| 
 | 
 | ||||||
| class WatchSB : ExtractorApi() { | open class WatchSB : ExtractorApi() { | ||||||
|     override val name: String |     override val name: String | ||||||
|         get() = "WatchSB" |         get() = "WatchSB" | ||||||
|     override val mainUrl: String |     override val mainUrl: String | ||||||
|  | @ -22,7 +22,7 @@ class WatchSB : ExtractorApi() { | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         val extractedLinksList = M3u8Helper().m3u8Generation( |         return M3u8Helper().m3u8Generation( | ||||||
|             M3u8Helper.M3u8Stream( |             M3u8Helper.M3u8Stream( | ||||||
|                 response.url, |                 response.url, | ||||||
|                 headers = response.headers.toMap() |                 headers = response.headers.toMap() | ||||||
|  | @ -39,7 +39,5 @@ class WatchSB : ExtractorApi() { | ||||||
|                     true |                     true | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|         return extractedLinksList |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | package com.lagradost.cloudstream3.movieproviders | ||||||
|  | 
 | ||||||
|  | import com.lagradost.cloudstream3.TvType | ||||||
|  | 
 | ||||||
|  | /** Needs to inherit from MainAPI() to | ||||||
|  |  * make the app know what functions to call | ||||||
|  |  */  | ||||||
|  | class PelisplusProvider : PelisplusProviderTemplate() { | ||||||
|  |     // mainUrl is good to have as a holder for the url to make future changes easier. | ||||||
|  |     override val mainUrl: String | ||||||
|  |         get() = "https://pelisplus.icu" | ||||||
|  | 
 | ||||||
|  |     // name is for how the provider will be named which is visible in the UI, no real rules for this. | ||||||
|  |     override val name: String | ||||||
|  |         get() = "Pelisplus" | ||||||
|  | 
 | ||||||
|  |     override val homePageUrlList: List<String> = listOf( | ||||||
|  |         mainUrl, | ||||||
|  |         "$mainUrl/movies", | ||||||
|  |         "$mainUrl/series", | ||||||
|  |         "$mainUrl/new-season", | ||||||
|  |         "$mainUrl/popular" | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     // This is just extra metadata about what type of movies the provider has. | ||||||
|  |     // Needed for search functionality. | ||||||
|  |     override val supportedTypes: Set<TvType> | ||||||
|  |         get() = setOf(TvType.TvSeries, TvType.Movie) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,274 @@ | ||||||
|  | package com.lagradost.cloudstream3.movieproviders | ||||||
|  | 
 | ||||||
|  | import com.lagradost.cloudstream3.* | ||||||
|  | import com.lagradost.cloudstream3.extractors.Pelisplus | ||||||
|  | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
|  | import com.lagradost.cloudstream3.utils.getQualityFromName | ||||||
|  | import org.jsoup.Jsoup | ||||||
|  | import java.net.URI | ||||||
|  | 
 | ||||||
|  | /** Needs to inherit from MainAPI() to | ||||||
|  |  * make the app know what functions to call | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | open class PelisplusProviderTemplate : MainAPI() { | ||||||
|  |     override val lang = "es" | ||||||
|  |     open val homePageUrlList = listOf<String>() | ||||||
|  |     open val pelisplusExtractorUrl: String? = null | ||||||
|  | 
 | ||||||
|  | //    // mainUrl is good to have as a holder for the url to make future changes easier. | ||||||
|  | //    override val mainUrl: String | ||||||
|  | //        get() = "https://vidembed.cc" | ||||||
|  | // | ||||||
|  | //    // name is for how the provider will be named which is visible in the UI, no real rules for this. | ||||||
|  | //    override val name: String | ||||||
|  | //        get() = "VidEmbed" | ||||||
|  | 
 | ||||||
|  |     // hasQuickSearch defines if quickSearch() should be called, this is only when typing the searchbar | ||||||
|  |     // gives results on the site instead of bringing you to another page. | ||||||
|  |     // if hasQuickSearch is true and quickSearch() hasn't been overridden you will get errors. | ||||||
|  |     // VidEmbed actually has quick search on their site, but the function wasn't implemented. | ||||||
|  |     override val hasQuickSearch = false | ||||||
|  | 
 | ||||||
|  |     // If getMainPage() is functional, used to display the homepage in app, an optional, but highly encouraged endevour. | ||||||
|  |     override val hasMainPage = true | ||||||
|  | 
 | ||||||
|  |     // Searching returns a SearchResponse, which can be one of the following: AnimeSearchResponse, MovieSearchResponse, TorrentSearchResponse, TvSeriesSearchResponse | ||||||
|  |     // Each of the classes requires some different data, but always has some critical things like name, poster and url. | ||||||
|  |     override fun search(query: String): ArrayList<SearchResponse> { | ||||||
|  |         // Simply looking at devtools network is enough to spot a request like: | ||||||
|  |         // https://vidembed.cc/search.html?keyword=neverland where neverland is the query, can be written as below. | ||||||
|  |         val link = "$mainUrl/search.html?keyword=$query" | ||||||
|  |         val html = app.get(link).text | ||||||
|  |         val soup = Jsoup.parse(html) | ||||||
|  | 
 | ||||||
|  |         return ArrayList(soup.select(".listing.items > .video-block").map { li -> | ||||||
|  |             // Selects the href in <a href="..."> | ||||||
|  |             val href = fixUrl(li.selectFirst("a").attr("href")) | ||||||
|  |             val poster = fixUrl(li.selectFirst("img").attr("src")) | ||||||
|  | 
 | ||||||
|  |             // .text() selects all the text in the element, be careful about doing this while too high up in the html hierarchy | ||||||
|  |             val title = li.selectFirst(".name").text() | ||||||
|  |             // Use get(0) and toIntOrNull() to prevent any possible crashes, [0] or toInt() will error the search on unexpected values. | ||||||
|  |             val year = li.selectFirst(".date")?.text()?.split("-")?.get(0)?.toIntOrNull() | ||||||
|  | 
 | ||||||
|  |             TvSeriesSearchResponse( | ||||||
|  |                 // .trim() removes unwanted spaces in the start and end. | ||||||
|  |                 if (!title.contains("Episode")) title else title.split("Episode")[0].trim(), | ||||||
|  |                 href, | ||||||
|  |                 this.name, | ||||||
|  |                 TvType.TvSeries, | ||||||
|  |                 poster, year, | ||||||
|  |                 // You can't get the episodes from the search bar. | ||||||
|  |                 null | ||||||
|  |             ) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     // Load, like the name suggests loads the info page, where all the episodes and data usually is. | ||||||
|  |     // Like search you should return either of: AnimeLoadResponse, MovieLoadResponse, TorrentLoadResponse, TvSeriesLoadResponse. | ||||||
|  |     override fun load(url: String): LoadResponse? { | ||||||
|  |         // Gets the url returned from searching. | ||||||
|  |         val html = app.get(url).text | ||||||
|  |         val soup = Jsoup.parse(html) | ||||||
|  | 
 | ||||||
|  |         var title = soup.selectFirst("h1,h2,h3").text() | ||||||
|  |         title = if (!title.contains("Episode")) title else title.split("Episode")[0].trim() | ||||||
|  | 
 | ||||||
|  |         val description = soup.selectFirst(".post-entry")?.text()?.trim() | ||||||
|  |         var poster: String? = null | ||||||
|  | 
 | ||||||
|  |         val episodes = soup.select(".listing.items.lists > .video-block").map { li -> | ||||||
|  |             val epTitle = if (li.selectFirst(".name") != null) | ||||||
|  |                 if (li.selectFirst(".name").text().contains("Episode")) | ||||||
|  |                     "Episode " + li.selectFirst(".name").text().split("Episode")[1].trim() | ||||||
|  |                 else | ||||||
|  |                     li.selectFirst(".name").text() | ||||||
|  |             else "" | ||||||
|  |             val epThumb = fixUrl(li.selectFirst("img").attr("src")) | ||||||
|  |             val epDate = li.selectFirst(".meta > .date").text() | ||||||
|  | 
 | ||||||
|  |             if (poster == null) { | ||||||
|  |                 poster = li.selectFirst("img")?.attr("onerror")?.replace("//img", "https://img")?.split("=")?.get(1) | ||||||
|  |                     ?.replace(Regex("[';]"), "") | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             val epNum = Regex("""Episode (\d+)""").find(epTitle)?.destructured?.component1()?.toIntOrNull() | ||||||
|  | 
 | ||||||
|  |             TvSeriesEpisode( | ||||||
|  |                 epTitle, | ||||||
|  |                 null, | ||||||
|  |                 epNum, | ||||||
|  |                 fixUrl(li.selectFirst("a").attr("href")), | ||||||
|  |                 epThumb, | ||||||
|  |                 epDate | ||||||
|  |             ) | ||||||
|  |         }.reversed() | ||||||
|  | 
 | ||||||
|  |         val year = episodes.first().date?.split("-")?.get(0)?.toIntOrNull() | ||||||
|  | 
 | ||||||
|  |         // Make sure to get the type right to display the correct UI. | ||||||
|  |         val tvType = if (episodes.size == 1 && episodes[0].name == title) TvType.Movie else TvType.TvSeries | ||||||
|  | 
 | ||||||
|  |         return when (tvType) { | ||||||
|  |             TvType.TvSeries -> { | ||||||
|  |                 TvSeriesLoadResponse( | ||||||
|  |                     title, | ||||||
|  |                     url, | ||||||
|  |                     this.name, | ||||||
|  |                     tvType, | ||||||
|  |                     episodes, | ||||||
|  |                     poster, | ||||||
|  |                     year, | ||||||
|  |                     description, | ||||||
|  |                     ShowStatus.Ongoing, | ||||||
|  |                     null, | ||||||
|  |                     null | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             TvType.Movie -> { | ||||||
|  |                 MovieLoadResponse( | ||||||
|  |                     title, | ||||||
|  |                     url, | ||||||
|  |                     this.name, | ||||||
|  |                     tvType, | ||||||
|  |                     episodes[0].data, | ||||||
|  |                     poster, | ||||||
|  |                     year, | ||||||
|  |                     description, | ||||||
|  |                     null, | ||||||
|  |                     null | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             else -> null | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // This loads the homepage, which is basically a collection of search results with labels. | ||||||
|  |     // Optional function, but make sure to enable hasMainPage if you program this. | ||||||
|  |     override fun getMainPage(): HomePageResponse { | ||||||
|  |         val urls = homePageUrlList | ||||||
|  |         val homePageList = ArrayList<HomePageList>() | ||||||
|  |         // .pmap {} is used to fetch the different pages in parallel | ||||||
|  |         urls.pmap { url -> | ||||||
|  |             val response = app.get(url, timeout = 20).text | ||||||
|  |             val document = Jsoup.parse(response) | ||||||
|  |             document.select("div.main-inner")?.forEach { inner -> | ||||||
|  |                 // Always trim your text unless you want the risk of spaces at the start or end. | ||||||
|  |                 val title = inner.select(".widget-title").text().trim() | ||||||
|  |                 val elements = inner.select(".video-block").map { | ||||||
|  |                     val link = fixUrl(it.select("a").attr("href")) | ||||||
|  |                     val image = it.select(".picture > img").attr("src").replace("//img", "https://img") | ||||||
|  |                     val name = it.select("div.name").text().trim().replace(Regex("""[Ee]pisode \d+"""), "") | ||||||
|  |                     val isSeries = (name.contains("Season") || name.contains("Episode")) | ||||||
|  | 
 | ||||||
|  |                     if (isSeries) { | ||||||
|  |                         TvSeriesSearchResponse( | ||||||
|  |                             name, | ||||||
|  |                             link, | ||||||
|  |                             this.name, | ||||||
|  |                             TvType.TvSeries, | ||||||
|  |                             image, | ||||||
|  |                             null, | ||||||
|  |                             null, | ||||||
|  |                         ) | ||||||
|  |                     } else { | ||||||
|  |                         MovieSearchResponse( | ||||||
|  |                             name, | ||||||
|  |                             link, | ||||||
|  |                             this.name, | ||||||
|  |                             TvType.Movie, | ||||||
|  |                             image, | ||||||
|  |                             null, | ||||||
|  |                             null, | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 homePageList.add( | ||||||
|  |                     HomePageList( | ||||||
|  |                         title, elements | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |         return HomePageResponse(homePageList) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // loadLinks gets the raw .mp4 or .m3u8 urls from the data parameter in the episodes class generated in load() | ||||||
|  |     // See TvSeriesEpisode(...) in this provider. | ||||||
|  |     // The data are usually links, but can be any other string to help aid loading the links. | ||||||
|  |     override fun loadLinks( | ||||||
|  |         data: String, | ||||||
|  |         isCasting: Boolean, | ||||||
|  |         // These callbacks are functions you should call when you get a link to a subtitle file or media file. | ||||||
|  |         subtitleCallback: (SubtitleFile) -> Unit, | ||||||
|  |         callback: (ExtractorLink) -> Unit | ||||||
|  |     ): Boolean { | ||||||
|  |         // "?: return" is a very useful statement which returns if the iframe link isn't found. | ||||||
|  |         val iframeLink = Jsoup.parse(app.get(data).text).selectFirst(".tab-video")?.attr("data-video") ?: return false | ||||||
|  | 
 | ||||||
|  |         // In this case the video player is a vidstream clone and can be handled by the vidstream extractor. | ||||||
|  |         // This case is a both unorthodox and you normally do not call extractors as they detect the url returned and does the rest. | ||||||
|  |         val vidstreamObject = Pelisplus(pelisplusExtractorUrl ?: mainUrl) | ||||||
|  |         // https://vidembed.cc/streaming.php?id=MzUwNTY2&... -> MzUwNTY2 | ||||||
|  |         val id = Regex("""id=([^?]*)""").find(iframeLink)?.groupValues?.get(1) | ||||||
|  | 
 | ||||||
|  |         if (id != null) { | ||||||
|  |             vidstreamObject.getUrl(id, isCasting, callback) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val html = app.get(fixUrl(iframeLink)).text | ||||||
|  |         val soup = Jsoup.parse(html) | ||||||
|  | 
 | ||||||
|  |         val servers = soup.select(".list-server-items > .linkserver").mapNotNull { li -> | ||||||
|  |             if (!li?.attr("data-video").isNullOrEmpty()) { | ||||||
|  |                 Pair(li.text(), fixUrl(li.attr("data-video"))) | ||||||
|  |             } else { | ||||||
|  |                 null | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         servers.forEach { | ||||||
|  |             // When checking strings make sure to make them lowercase and trimmed because edgecases like "beta server " wouldn't work otherwise. | ||||||
|  |             if (it.first.trim().equals("beta server", ignoreCase = true)) { | ||||||
|  |                 // Group 1: link, Group 2: Label | ||||||
|  |                 // Regex can be used to effectively parse small amounts of json without bothering with writing a json class. | ||||||
|  |                 val sourceRegex = Regex("""sources:[\W\w]*?file:\s*["'](.*?)["'][\W\w]*?label:\s*["'](.*?)["']""") | ||||||
|  |                 val trackRegex = Regex("""tracks:[\W\w]*?file:\s*["'](.*?)["'][\W\w]*?label:\s*["'](.*?)["']""") | ||||||
|  | 
 | ||||||
|  |                 // Having a referer is often required. It's a basic security check most providers have. | ||||||
|  |                 // Try to replicate what your browser does. | ||||||
|  |                 val serverHtml = app.get(it.second, headers = mapOf("referer" to iframeLink)).text | ||||||
|  |                 sourceRegex.findAll(serverHtml).forEach { match -> | ||||||
|  |                     callback.invoke( | ||||||
|  |                         ExtractorLink( | ||||||
|  |                             this.name, | ||||||
|  |                             match.groupValues.getOrNull(2)?.let { "${this.name} $it" } ?: this.name, | ||||||
|  |                             match.groupValues[1], | ||||||
|  |                             it.second, | ||||||
|  |                             // Useful function to turn something like "1080p" to an app quality. | ||||||
|  |                             getQualityFromName(match.groupValues.getOrNull(2) ?: ""), | ||||||
|  |                             // Kinda risky | ||||||
|  |                             // isM3u8 makes the player pick the correct extractor for the source. | ||||||
|  |                             // If isM3u8 is wrong the player will error on that source. | ||||||
|  |                             URI(match.groupValues[1]).path.endsWith(".m3u8"), | ||||||
|  |                         ) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |                 trackRegex.findAll(serverHtml).forEach { match -> | ||||||
|  |                     subtitleCallback.invoke( | ||||||
|  |                         SubtitleFile( | ||||||
|  |                             match.groupValues.getOrNull(2) ?: "Unknown", | ||||||
|  |                             match.groupValues[1] | ||||||
|  |                         ) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -81,6 +81,8 @@ val extractorApis: Array<ExtractorApi> = arrayOf( | ||||||
|     Streamhub(), |     Streamhub(), | ||||||
| 
 | 
 | ||||||
|     FEmbed(), |     FEmbed(), | ||||||
|  |     FeHD(), | ||||||
|  |     Fplayer(), | ||||||
|     WatchSB(), |     WatchSB(), | ||||||
|     Uqload(), |     Uqload(), | ||||||
|     Evoload(), |     Evoload(), | ||||||
|  | @ -91,10 +93,12 @@ val extractorApis: Array<ExtractorApi> = arrayOf( | ||||||
|     DoodToExtractor(), |     DoodToExtractor(), | ||||||
|     DoodSoExtractor(), |     DoodSoExtractor(), | ||||||
|     DoodLaExtractor(), |     DoodLaExtractor(), | ||||||
|  |     DoodWsExtractor(), | ||||||
| 
 | 
 | ||||||
|     AsianLoad(), |     AsianLoad(), | ||||||
| 
 | 
 | ||||||
|     SBPlay(), |     SBPlay(), | ||||||
|  |     SBPlay1(), | ||||||
|     SBPlay2(), |     SBPlay2(), | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue