forked from recloudstream/cloudstream
		
	
							parent
							
								
									38d6561799
								
							
						
					
					
						commit
						2edc4571a1
					
				
					 1 changed files with 81 additions and 86 deletions
				
			
		|  | @ -1,11 +1,8 @@ | ||||||
| package com.lagradost.cloudstream3.movieproviders | package com.lagradost.cloudstream3.movieproviders | ||||||
| 
 | 
 | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
| import com.lagradost.cloudstream3.extractors.Pelisplus | import com.lagradost.cloudstream3.utils.* | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink |  | ||||||
| import com.lagradost.cloudstream3.utils.getQualityFromName |  | ||||||
| import org.jsoup.Jsoup | import org.jsoup.Jsoup | ||||||
| import java.net.URI |  | ||||||
| 
 | 
 | ||||||
| /** Needs to inherit from MainAPI() to | /** Needs to inherit from MainAPI() to | ||||||
|  * make the app know what functions to call |  * make the app know what functions to call | ||||||
|  | @ -14,7 +11,6 @@ import java.net.URI | ||||||
| open class PelisplusProviderTemplate : MainAPI() { | open class PelisplusProviderTemplate : MainAPI() { | ||||||
|     override val lang = "es" |     override val lang = "es" | ||||||
|     open val homePageUrlList = listOf<String>() |     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. | //    // mainUrl is good to have as a holder for the url to make future changes easier. | ||||||
| //    override val mainUrl: String | //    override val mainUrl: String | ||||||
|  | @ -35,6 +31,7 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
| 
 | 
 | ||||||
|     // Searching returns a SearchResponse, which can be one of the following: AnimeSearchResponse, MovieSearchResponse, TorrentSearchResponse, TvSeriesSearchResponse |     // 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. |     // Each of the classes requires some different data, but always has some critical things like name, poster and url. | ||||||
|  | 
 | ||||||
|     override suspend fun search(query: String): ArrayList<SearchResponse> { |     override suspend fun search(query: String): ArrayList<SearchResponse> { | ||||||
|         // Simply looking at devtools network is enough to spot a request like: |         // 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. |         // https://vidembed.cc/search.html?keyword=neverland where neverland is the query, can be written as below. | ||||||
|  | @ -48,7 +45,7 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
|             val poster = fixUrl(li.selectFirst("img").attr("src")) |             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 |             // .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() |             val title = cleanName(li.selectFirst(".name").text()) | ||||||
|             // Use get(0) and toIntOrNull() to prevent any possible crashes, [0] or toInt() will error the search on unexpected values. |             // 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() |             val year = li.selectFirst(".date")?.text()?.split("-")?.get(0)?.toIntOrNull() | ||||||
| 
 | 
 | ||||||
|  | @ -73,33 +70,29 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
|         val html = app.get(url).text |         val html = app.get(url).text | ||||||
|         val soup = Jsoup.parse(html) |         val soup = Jsoup.parse(html) | ||||||
| 
 | 
 | ||||||
|         var title = soup.selectFirst("h1,h2,h3").text() |         val title = cleanName(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() |         val description = soup.selectFirst(".post-entry")?.text()?.trim() | ||||||
|         var poster: String? = null |         val poster = soup.selectFirst("head meta[property=og:image]").attr("content") | ||||||
| 
 | 
 | ||||||
|         val episodes = soup.select(".listing.items.lists > .video-block").map { li -> |         val episodes = soup.select(".listing.items.lists > .video-block").map { li -> | ||||||
|             val epTitle = if (li.selectFirst(".name") != null) |             val href = fixUrl(li.selectFirst("a").attr("href")) | ||||||
|                 if (li.selectFirst(".name").text().contains("Episode")) |             val regexseason = Regex("(-[Tt]emporada-(\\d+)-[Cc]apitulo-(\\d+))") | ||||||
|                     "Episode " + li.selectFirst(".name").text().split("Episode")[1].trim() |             val aaa = regexseason.find(href)?.destructured?.component1()?.replace(Regex("(-[Tt]emporada-|[Cc]apitulo-)"),"") | ||||||
|                 else |             val seasonid = aaa.let { str -> | ||||||
|                     li.selectFirst(".name").text() |                 str?.split("-")?.mapNotNull { subStr -> subStr.toIntOrNull() } | ||||||
|             else "" |             } | ||||||
|  |             val isValid = seasonid?.size == 2 | ||||||
|  |             val episode = if (isValid) seasonid?.getOrNull(1) else null | ||||||
|  |             val season = if (isValid) seasonid?.getOrNull(0) else null | ||||||
|             val epThumb = fixUrl(li.selectFirst("img").attr("src")) |             val epThumb = fixUrl(li.selectFirst("img").attr("src")) | ||||||
|             val epDate = li.selectFirst(".meta > .date").text() |             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( |             TvSeriesEpisode( | ||||||
|                 epTitle, |  | ||||||
|                 null, |                 null, | ||||||
|                 epNum, |                 season, | ||||||
|  |                 episode, | ||||||
|                 fixUrl(li.selectFirst("a").attr("href")), |                 fixUrl(li.selectFirst("a").attr("href")), | ||||||
|                 epThumb, |                 epThumb, | ||||||
|                 epDate |                 epDate | ||||||
|  | @ -119,10 +112,10 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
|                     this.name, |                     this.name, | ||||||
|                     tvType, |                     tvType, | ||||||
|                     episodes, |                     episodes, | ||||||
|                     poster, |                     fixUrl(poster), | ||||||
|                     year, |                     year, | ||||||
|                     description, |                     description, | ||||||
|                     ShowStatus.Ongoing, |                     null, | ||||||
|                     null, |                     null, | ||||||
|                     null |                     null | ||||||
|                 ) |                 ) | ||||||
|  | @ -134,7 +127,7 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
|                     this.name, |                     this.name, | ||||||
|                     tvType, |                     tvType, | ||||||
|                     episodes[0].data, |                     episodes[0].data, | ||||||
|                     poster, |                     fixUrl(poster), | ||||||
|                     year, |                     year, | ||||||
|                     description, |                     description, | ||||||
|                     null, |                     null, | ||||||
|  | @ -156,12 +149,12 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
|             val document = Jsoup.parse(response) |             val document = Jsoup.parse(response) | ||||||
|             document.select("div.main-inner")?.forEach { inner -> |             document.select("div.main-inner")?.forEach { inner -> | ||||||
|                 // Always trim your text unless you want the risk of spaces at the start or end. |                 // 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 title = cleanName(inner.select(".widget-title").text()) | ||||||
|                 val elements = inner.select(".video-block").map { |                 val elements = inner.select(".video-block").map { | ||||||
|                     val link = fixUrl(it.select("a").attr("href")) |                     val link = fixUrl(it.select("a").attr("href")) | ||||||
|                     val image = it.select(".picture > img").attr("src").replace("//img", "https://img") |                     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 name = cleanName(it.select("div.name").text()) | ||||||
|                     val isSeries = (name.contains("Season") || name.contains("Episode")) |                     val isSeries = (name.contains("Temporada") || name.contains("Capítulo")) | ||||||
| 
 | 
 | ||||||
|                     if (isSeries) { |                     if (isSeries) { | ||||||
|                         TvSeriesSearchResponse( |                         TvSeriesSearchResponse( | ||||||
|  | @ -198,6 +191,38 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
|         return HomePageResponse(homePageList) |         return HomePageResponse(homePageList) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     private fun cleanName(input: String): String = input.replace(Regex("([Tt]emporada (\\d+)|[Cc]apítulo (\\d+))|[Tt]emporada|[Cc]apítulo"),"").trim() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private suspend fun getPelisStream( | ||||||
|  |         link: String, | ||||||
|  |         callback: (ExtractorLink) -> Unit) : Boolean { | ||||||
|  |         val soup = app.get(link).text | ||||||
|  |         val m3u8regex = Regex("((https:|http:)\\/\\/.*m3u8.*expiry=(\\d+))") | ||||||
|  |         val m3u8 = m3u8regex.find(soup)?.value ?: return false | ||||||
|  |         M3u8Helper().m3u8Generation( | ||||||
|  |             M3u8Helper.M3u8Stream( | ||||||
|  |                 m3u8, | ||||||
|  |                 headers = mapOf("Referer" to mainUrl) | ||||||
|  |             ), true | ||||||
|  |         ) | ||||||
|  |             .map { stream -> | ||||||
|  |                 val qualityString = if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p" | ||||||
|  |                 callback( | ||||||
|  |                     ExtractorLink( | ||||||
|  |                         name, | ||||||
|  |                         "$name $qualityString", | ||||||
|  |                         stream.streamUrl, | ||||||
|  |                         mainUrl, | ||||||
|  |                         getQualityFromName(stream.quality.toString()), | ||||||
|  |                         true | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // loadLinks gets the raw .mp4 or .m3u8 urls from the data parameter in the episodes class generated in load() |     // loadLinks gets the raw .mp4 or .m3u8 urls from the data parameter in the episodes class generated in load() | ||||||
|     // See TvSeriesEpisode(...) in this provider. |     // See TvSeriesEpisode(...) in this provider. | ||||||
|     // The data are usually links, but can be any other string to help aid loading the links. |     // The data are usually links, but can be any other string to help aid loading the links. | ||||||
|  | @ -208,67 +233,37 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
|         subtitleCallback: (SubtitleFile) -> Unit, |         subtitleCallback: (SubtitleFile) -> Unit, | ||||||
|         callback: (ExtractorLink) -> Unit |         callback: (ExtractorLink) -> Unit | ||||||
|     ): Boolean { |     ): Boolean { | ||||||
|         // "?: return" is a very useful statement which returns if the iframe link isn't found. |         val doc = app.get(data).document | ||||||
|         val iframeLink = Jsoup.parse(app.get(data).text).selectFirst(".tab-video")?.attr("data-video") ?: return false |         val info = doc.select("div.tabs-video li").text() | ||||||
| 
 |         if (info.contains("Latino")) { | ||||||
|         // In this case the video player is a vidstream clone and can be handled by the vidstream extractor. |             doc.select(".server-item-1 li").apmap { | ||||||
|         // This case is a both unorthodox and you normally do not call extractors as they detect the url returned and does the rest. |                 val serverid = fixUrl(it.attr("data-video")).replace("streaming.php","play") | ||||||
|         val vidstreamObject = Pelisplus(pelisplusExtractorUrl ?: mainUrl) |                 loadExtractor(serverid, data, callback) | ||||||
|         // https://vidembed.cc/streaming.php?id=MzUwNTY2&... -> MzUwNTY2 |                 if (serverid.contains("pelisplus.icu")) { | ||||||
|         val id = Regex("""id=([^?]*)""").find(iframeLink)?.groupValues?.get(1) |                     getPelisStream(serverid, callback) | ||||||
| 
 |  | ||||||
|         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.apmap { |  | ||||||
|             // 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] |  | ||||||
|                         ) |  | ||||||
|                     ) |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (info.contains("Subtitulado")) { | ||||||
|  |             doc.select(".server-item-0 li").apmap { | ||||||
|  |                 val serverid = fixUrl(it.attr("data-video")).replace("streaming.php","play") | ||||||
|  |                 loadExtractor(serverid, data, callback) | ||||||
|  |                 if (serverid.contains("pelisplus.icu")) { | ||||||
|  |                     getPelisStream(serverid, callback) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (info.contains("Castellano")) { | ||||||
|  |             doc.select(".server-item-2 li").apmap { | ||||||
|  |                 val serverid = fixUrl(it.attr("data-video")).replace("streaming.php","play") | ||||||
|  |                 loadExtractor(serverid, data, callback) | ||||||
|  |                 if (serverid.contains("pelisplus.icu")) { | ||||||
|  |                     getPelisStream(serverid, callback) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         return true |         return true | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue