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