mirror of
https://github.com/recloudstream/cloudstream-extensions-multilingual.git
synced 2024-08-15 03:15:14 +00:00
MesFilms French Provider (#15)
* Added MesFilmsProvider * Added MesFilmsProvider * fixed additional info bs * finally fixed mesfilms after 30 days lol * Fixed French Stream Providers * Update FrenchStreamProviderPlugin.kt * Added Vido Provider to base cloudstream * Fixed issues (finally lol) Removed tv shows from search * Update MesFilmsProvider.kt --------- Co-authored-by: sarlay
This commit is contained in:
parent
55b4086082
commit
7e4e4e57ae
7 changed files with 467 additions and 157 deletions
|
@ -1,20 +1,25 @@
|
||||||
package com.lagradost
|
package com.lagradost
|
||||||
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.extractorApis
|
import com.lagradost.cloudstream3.utils.extractorApis
|
||||||
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
|
||||||
class FrenchStreamProvider : MainAPI() {
|
class FrenchStreamProvider : MainAPI() {
|
||||||
override var mainUrl = "https://french-stream.cx" //re ou ac ou city
|
override var mainUrl = "https://streem.re" //re ou ac ou city
|
||||||
override var name = "FrenchStream"
|
override var name = "FrenchStream"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override var lang = "fr"
|
override var lang = "fr"
|
||||||
override val supportedTypes = setOf(TvType.Movie, TvType.TvSeries)
|
override val supportedTypes = setOf(TvType.Movie, TvType.TvSeries)
|
||||||
|
|
||||||
override suspend fun search(query: String): List<SearchResponse> {
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
val link = "$mainUrl/?do=search&subaction=search&story=$query" // search'
|
val link = "$mainUrl/?do=search&subaction=search&story=$query" // search'
|
||||||
val document =
|
val document =
|
||||||
|
@ -22,21 +27,60 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
val results = document.select("div#dle-content > > div.short")
|
val results = document.select("div#dle-content > > div.short")
|
||||||
|
|
||||||
val allresultshome =
|
val allresultshome =
|
||||||
results.apmap { article -> // avec mapnotnull si un élément est null, il sera automatiquement enlevé de la liste
|
results.mapNotNull { article -> // avec mapnotnull si un élément est null, il sera automatiquement enlevé de la liste
|
||||||
article.toSearchResponse()
|
article.toSearchResponse()
|
||||||
}
|
}
|
||||||
return allresultshome
|
return allresultshome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Element.takeEpisode(
|
||||||
|
url: String,
|
||||||
|
): List<Episode> {
|
||||||
|
return this.select("a").map { a ->
|
||||||
|
val epNum =
|
||||||
|
Regex("""pisode[\s]+(\d+)""").find(a.text().lowercase())?.groupValues?.get(1)
|
||||||
|
?.toIntOrNull()
|
||||||
|
val epTitle = if (a.text().contains("Episode")) {
|
||||||
|
val type = if ("honey" in a.attr("id")) {
|
||||||
|
"VF"
|
||||||
|
} else {
|
||||||
|
"Vostfr"
|
||||||
|
}
|
||||||
|
"Episode $type"
|
||||||
|
} else {
|
||||||
|
a.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
Episode(
|
||||||
|
loadLinkData(
|
||||||
|
fixUrl(url),
|
||||||
|
epTitle.contains("Vostfr"),
|
||||||
|
epNum,
|
||||||
|
).toJson(),
|
||||||
|
epTitle,
|
||||||
|
null,
|
||||||
|
epNum,
|
||||||
|
a.selectFirst("div.fposter > img")?.attr("src"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class loadLinkData(
|
||||||
|
val embedUrl: String,
|
||||||
|
val isVostFr: Boolean? = null,
|
||||||
|
val episodenumber: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun load(url: String): LoadResponse {
|
override suspend fun load(url: String): LoadResponse {
|
||||||
val soup = app.get(url).document
|
val soup = app.get(url).document
|
||||||
|
var subEpisodes = listOf<Episode>()
|
||||||
|
var dubEpisodes = listOf<Episode>()
|
||||||
val title = soup.selectFirst("h1#s-title")!!.text().toString()
|
val title = soup.selectFirst("h1#s-title")!!.text().toString()
|
||||||
val isMovie = !title.contains("saison", ignoreCase = true)
|
val isMovie = !url.contains("/serie/", ignoreCase = true)
|
||||||
val description =
|
val description =
|
||||||
soup.selectFirst("div.fdesc")!!.text().toString()
|
soup.selectFirst("div.fdesc")!!.text().toString()
|
||||||
.split("streaming", ignoreCase = true)[1].replace(":", "")
|
.split("streaming", ignoreCase = true)[1].replace(":", "")
|
||||||
var poster = soup.selectFirst("div.fposter > img")?.attr("src")
|
val poster = soup.selectFirst("div.fposter > img")?.attr("src")
|
||||||
val listEpisode = soup.select("div.elink")
|
val listEpisode = soup.select("div.elink")
|
||||||
val tags = soup.select("ul.flist-col > li").getOrNull(1)
|
val tags = soup.select("ul.flist-col > li").getOrNull(1)
|
||||||
//val rating = soup.select("span[id^=vote-num-id]")?.getOrNull(1)?.text()?.toInt()
|
//val rating = soup.select("span[id^=vote-num-id]")?.getOrNull(1)?.text()?.toInt()
|
||||||
|
@ -48,7 +92,7 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
?.mapNotNull { // all the tags like action, thriller ...; unused variable
|
?.mapNotNull { // all the tags like action, thriller ...; unused variable
|
||||||
it?.text()
|
it?.text()
|
||||||
}
|
}
|
||||||
return newMovieLoadResponse(title, url, TvType.Movie, url) {
|
return newMovieLoadResponse(title, url, TvType.Movie, loadLinkData(url)) {
|
||||||
this.posterUrl = poster
|
this.posterUrl = poster
|
||||||
this.year = year?.toIntOrNull()
|
this.year = year?.toIntOrNull()
|
||||||
this.tags = tagsList
|
this.tags = tagsList
|
||||||
|
@ -56,56 +100,26 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
//this.rating = rating
|
//this.rating = rating
|
||||||
addTrailer(soup.selectFirst("button#myBtn > a")?.attr("href"))
|
addTrailer(soup.selectFirst("button#myBtn > a")?.attr("href"))
|
||||||
}
|
}
|
||||||
} else // a tv serie
|
} else {
|
||||||
{
|
if ("<a" in listEpisode[1].toString()) { // check if VF is empty
|
||||||
|
subEpisodes = listEpisode[1].takeEpisode(url)// return vostfr
|
||||||
val episodeList = if ("<a" !in (listEpisode[0]).toString()) { // check if VF is empty
|
|
||||||
listEpisode[1] // no vf, return vostfr
|
|
||||||
} else {
|
|
||||||
listEpisode[0] // no vostfr, return vf
|
|
||||||
}
|
}
|
||||||
|
if ("<a" in listEpisode[0].toString()) {
|
||||||
val episodes = episodeList.select("a").map { a ->
|
dubEpisodes = listEpisode[0].takeEpisode(url)// return vf
|
||||||
val epNum = a.text().split("Episode")[1].trim().toIntOrNull()
|
|
||||||
val epTitle = if (a.text().contains("Episode")) {
|
|
||||||
val type = if ("honey" in a.attr("id")) {
|
|
||||||
"VF"
|
|
||||||
} else {
|
|
||||||
"Vostfr"
|
|
||||||
}
|
|
||||||
"Episode " + type
|
|
||||||
} else {
|
|
||||||
a.text()
|
|
||||||
}
|
|
||||||
if (poster == null) {
|
|
||||||
poster = a.selectFirst("div.fposter > img")?.attr("src")
|
|
||||||
}
|
|
||||||
Episode(
|
|
||||||
fixUrl(url).plus("-episodenumber:$epNum"),
|
|
||||||
epTitle,
|
|
||||||
null,
|
|
||||||
epNum,
|
|
||||||
null, // episode Thumbnail
|
|
||||||
null // episode date
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// val tagsList = tags?.text()?.replace("Genre :","")
|
|
||||||
val yearRegex = Regex("""Titre .* \/ (\d*)""")
|
val yearRegex = Regex("""Titre .* \/ (\d*)""")
|
||||||
val year = yearRegex.find(soup.text())?.groupValues?.get(1)
|
val year = yearRegex.find(soup.text())?.groupValues?.get(1)
|
||||||
return newTvSeriesLoadResponse(
|
return newAnimeLoadResponse(
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
TvType.TvSeries,
|
TvType.TvSeries,
|
||||||
episodes,
|
|
||||||
) {
|
) {
|
||||||
this.posterUrl = poster
|
this.posterUrl = poster
|
||||||
this.plot = description
|
this.plot = description
|
||||||
this.year = year?.toInt()
|
this.year = year?.toInt()
|
||||||
//this.rating = rating
|
|
||||||
//this.showStatus = ShowStatus.Ongoing
|
|
||||||
//this.tags = tagsList
|
|
||||||
addTrailer(soup.selectFirst("button#myBtn > a")?.attr("href"))
|
addTrailer(soup.selectFirst("button#myBtn > a")?.attr("href"))
|
||||||
|
if (subEpisodes.isNotEmpty()) addEpisodes(DubStatus.Subbed, subEpisodes)
|
||||||
|
if (dubEpisodes.isNotEmpty()) addEpisodes(DubStatus.Dubbed, dubEpisodes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,23 +140,26 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadLinks(
|
|
||||||
|
override suspend fun loadLinks( // TODO FIX *Garbage* data transmission betwenn function
|
||||||
data: String,
|
data: String,
|
||||||
isCasting: Boolean,
|
isCasting: Boolean,
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit,
|
callback: (ExtractorLink) -> Unit,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
val parsedData = tryParseJson<loadLinkData>(data)
|
||||||
|
val url = parsedData?.embedUrl ?: return false
|
||||||
val servers =
|
val servers =
|
||||||
if (data.contains("-episodenumber:"))// It's a serie:
|
if (parsedData.episodenumber != null)// It's a serie:
|
||||||
{
|
{
|
||||||
val split =
|
val isvostfr = parsedData.isVostFr == true
|
||||||
data.split("-episodenumber:") // the data contains the url and the wanted episode number (a temporary dirty fix that will last forever)
|
|
||||||
val url = split[0]
|
|
||||||
val wantedEpisode =
|
val wantedEpisode =
|
||||||
if (split[1] == "2") { // the episode number 2 has id of ABCDE, don't ask any question
|
if (parsedData.episodenumber.toString() == "2") { // the episode number 2 has id of ABCDE, don't ask any question
|
||||||
"ABCDE"
|
"ABCDE"
|
||||||
} else {
|
} else {
|
||||||
"episode" + split[1]
|
"episode" + parsedData.episodenumber.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -160,7 +177,7 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
// val litext = li.text()
|
// val litext = li.text()
|
||||||
if (serverUrl.isNotBlank()) {
|
if (serverUrl.isNotBlank()) {
|
||||||
if (li.text().replace(" ", "").replace(" ", "").isNotBlank()) {
|
if (li.text().replace(" ", "").replace(" ", "").isNotBlank()) {
|
||||||
Pair(li.text().replace(" ", ""), "vf" + fixUrl(serverUrl))
|
Pair(li.text().replace(" ", ""), fixUrl(serverUrl))
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -169,14 +186,14 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val translated = translate(split[1], serversvf.isNotEmpty())
|
val translated = translate(parsedData.episodenumber.toString(), serversvf.isNotEmpty())
|
||||||
val serversvo = // Original version servers
|
val serversvo = // Original version servers
|
||||||
soup.select("div#$translated > div.selink > ul.btnss $div> li")
|
soup.select("div#$translated > div.selink > ul.btnss $div> li")
|
||||||
.mapNotNull { li ->
|
.mapNotNull { li ->
|
||||||
val serverUrl = fixUrlNull(li.selectFirst("a")?.attr("href"))
|
val serverUrl = fixUrlNull(li.selectFirst("a")?.attr("href"))
|
||||||
if (!serverUrl.isNullOrEmpty()) {
|
if (!serverUrl.isNullOrEmpty()) {
|
||||||
if (li.text().replace(" ", "").isNotBlank()) {
|
if (li.text().replace(" ", "").isNotBlank()) {
|
||||||
Pair(li.text().replace(" ", ""), "vo" + fixUrl(serverUrl))
|
Pair(li.text().replace(" ", ""), fixUrl(serverUrl))
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -184,10 +201,14 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serversvf + serversvo
|
if (isvostfr) {
|
||||||
|
serversvo
|
||||||
|
} else {
|
||||||
|
serversvf
|
||||||
|
}
|
||||||
} else { // it's a movie
|
} else { // it's a movie
|
||||||
val movieServers =
|
val movieServers =
|
||||||
app.get(fixUrl(data)).document.select("nav#primary_nav_wrap > ul > li > ul > li > a")
|
app.get(fixUrl(url)).document.select("nav#primary_nav_wrap > ul > li > ul > li > a")
|
||||||
.mapNotNull { a ->
|
.mapNotNull { a ->
|
||||||
val serverurl = fixUrlNull(a.attr("href")) ?: return@mapNotNull null
|
val serverurl = fixUrlNull(a.attr("href")) ?: return@mapNotNull null
|
||||||
val parent = a.parents()[2]
|
val parent = a.parents()[2]
|
||||||
|
@ -200,33 +221,19 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
movieServers
|
movieServers
|
||||||
}
|
}
|
||||||
|
|
||||||
servers.apmap {
|
servers.apmap {
|
||||||
for (extractor in extractorApis) {
|
val urlplayer = it.second
|
||||||
var playerName = it.first
|
|
||||||
|
|
||||||
if (playerName.contains("Stream.B")) {
|
val playerUrl = if (urlplayer.contains("opsktp.com") || urlplayer.contains("flixeo.xyz")) {
|
||||||
playerName = it.first.replace("Stream.B", "StreamSB")
|
val header = app.get(
|
||||||
}
|
"https" + it.second.split("https")[1],
|
||||||
if (it.second.contains("streamlare")) {
|
allowRedirects = false
|
||||||
playerName = "Streamlare"
|
).headers
|
||||||
}
|
header["location"].toString()
|
||||||
if (playerName.contains(extractor.name, ignoreCase = true)) {
|
} else {
|
||||||
val header = app.get(
|
urlplayer
|
||||||
"https" + it.second.split("https").get(1),
|
}.replace("https://doodstream.com", "https://dood.yt")
|
||||||
allowRedirects = false
|
loadExtractor(playerUrl, mainUrl, subtitleCallback, callback)
|
||||||
).headers
|
|
||||||
val urlplayer = it.second
|
|
||||||
var playerUrl = when (!urlplayer.isNullOrEmpty()) {
|
|
||||||
urlplayer.contains("opsktp.com") -> header.get("location")
|
|
||||||
.toString() // case where there is redirection to opsktp
|
|
||||||
|
|
||||||
else -> it.second
|
|
||||||
}
|
|
||||||
extractor.getSafeUrl(playerUrl, playerUrl, subtitleCallback, callback)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -237,19 +244,20 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
|
|
||||||
val posterUrl = fixUrl(select("a.short-poster > img").attr("src"))
|
val posterUrl = fixUrl(select("a.short-poster > img").attr("src"))
|
||||||
val qualityExtracted = select("span.film-ripz > a").text()
|
val qualityExtracted = select("span.film-ripz > a").text()
|
||||||
val type = select("span.mli-eps").text()
|
val type = select("span.mli-eps").text().lowercase()
|
||||||
val title = select("div.short-title").text()
|
val title = select("div.short-title").text()
|
||||||
val link = select("a.short-poster").attr("href").replace("wvw.", "") //wvw is an issue
|
val link = select("a.short-poster").attr("href").replace("wvw.", "") //wvw is an issue
|
||||||
var quality = when (!qualityExtracted.isNullOrBlank()) {
|
val quality = getQualityFromString(
|
||||||
qualityExtracted.contains("HDLight") -> getQualityFromString("HD")
|
when (!qualityExtracted.isNullOrBlank()) {
|
||||||
qualityExtracted.contains("Bdrip") -> getQualityFromString("BlueRay")
|
qualityExtracted.contains("HDLight") -> "HD"
|
||||||
qualityExtracted.contains("DVD") -> getQualityFromString("DVD")
|
qualityExtracted.contains("Bdrip") -> "BlueRay"
|
||||||
qualityExtracted.contains("CAM") -> getQualityFromString("Cam")
|
qualityExtracted.contains("DVD") -> "DVD"
|
||||||
|
qualityExtracted.contains("CAM") -> "Cam"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
else -> null
|
if (!type.contains("eps")) {
|
||||||
}
|
|
||||||
|
|
||||||
if (type.contains("Eps", false)) {
|
|
||||||
return MovieSearchResponse(
|
return MovieSearchResponse(
|
||||||
name = title,
|
name = title,
|
||||||
url = link,
|
url = link,
|
||||||
|
@ -261,35 +269,43 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
} else // an Serie
|
} else // a Serie
|
||||||
{
|
{
|
||||||
|
return newAnimeSearchResponse(
|
||||||
return TvSeriesSearchResponse(
|
|
||||||
name = title,
|
name = title,
|
||||||
url = link,
|
url = link,
|
||||||
apiName = title,
|
|
||||||
type = TvType.TvSeries,
|
type = TvType.TvSeries,
|
||||||
posterUrl = posterUrl,
|
|
||||||
quality = quality,
|
) {
|
||||||
//
|
this.posterUrl = posterUrl
|
||||||
)
|
addDubStatus(
|
||||||
|
isDub = select("span.film-verz").text().uppercase().contains("VF"),
|
||||||
|
episodes = select("span.mli-eps>i").text().toIntOrNull()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class mediaData(
|
||||||
|
@JsonProperty("title") var title: String,
|
||||||
|
@JsonProperty("url") val url: String,
|
||||||
|
)
|
||||||
|
|
||||||
override val mainPage = mainPageOf(
|
override val mainPage = mainPageOf(
|
||||||
Pair("$mainUrl/xfsearch/version-film/page/", "Derniers films"),
|
Pair("/xfsearch/version-film/page/", "Derniers Films"),
|
||||||
Pair("$mainUrl/xfsearch/version-serie/page/", "Derniers séries"),
|
Pair("/xfsearch/version-serie/page/", "Dernieres Séries"),
|
||||||
Pair("$mainUrl/film/arts-martiaux/page/", "Films za m'ringué (Arts martiaux)"),
|
Pair("/film/arts-martiaux/page/", "Films Arts martiaux"),
|
||||||
Pair("$mainUrl/film/action/page/", "Films Actions"),
|
Pair("/film/action/page/", "Films Action"),
|
||||||
Pair("$mainUrl/film/romance/page/", "Films za malomo (Romance)"),
|
Pair("/film/romance/page/", "Films Romance"),
|
||||||
Pair("$mainUrl/serie/aventure-serie/page/", "Série aventure"),
|
Pair("/serie/aventure-serie/page/", "Séries aventure"),
|
||||||
Pair("$mainUrl/film/documentaire/page/", "Documentaire")
|
Pair("/film/documentaire/page/", "Documentaires")
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||||
val url = request.data + page
|
val url = mainUrl + request.data + page
|
||||||
val document = app.get(url).document
|
val document = app.get(url).document
|
||||||
val movies = document.select("div#dle-content > div.short")
|
val movies = document.select("div#dle-content > div.short")
|
||||||
|
|
||||||
|
@ -299,5 +315,5 @@ class FrenchStreamProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
return newHomePageResponse(request.name, home)
|
return newHomePageResponse(request.name, home)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,5 @@ class FrenchStreamProviderPlugin: Plugin() {
|
||||||
override fun load(context: Context) {
|
override fun load(context: Context) {
|
||||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||||
registerMainAPI(FrenchStreamProvider())
|
registerMainAPI(FrenchStreamProvider())
|
||||||
registerExtractorAPI(VidoExtractor())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
package com.lagradost
|
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.app
|
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
|
||||||
import com.lagradost.cloudstream3.utils.getAndUnpack
|
|
||||||
|
|
||||||
class VidoExtractor : ExtractorApi() {
|
|
||||||
override var name = "Vido"
|
|
||||||
override var mainUrl = "https://vido.lol"
|
|
||||||
private val srcRegex = Regex("""layer\(\{sources\:\["(.*)"\]""")
|
|
||||||
override val requiresReferer = true
|
|
||||||
|
|
||||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
|
||||||
val methode = if (url.contains("embed")) {
|
|
||||||
app.get(url) // french stream
|
|
||||||
} else {
|
|
||||||
val code = url.substringAfterLast("/")
|
|
||||||
val data = mapOf(
|
|
||||||
"op" to "embed",
|
|
||||||
"file_code" to code,
|
|
||||||
"&auto" to "1"
|
|
||||||
|
|
||||||
)
|
|
||||||
app.post("https://vido.lol/dl", referer = url, data = data) // wiflix
|
|
||||||
}
|
|
||||||
with(methode) {
|
|
||||||
getAndUnpack(this.text).let { unpackedText ->
|
|
||||||
//val quality = unpackedText.lowercase().substringAfter(" height=").substringBefore(" ").toIntOrNull()
|
|
||||||
srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link ->
|
|
||||||
return listOf(
|
|
||||||
ExtractorLink(
|
|
||||||
name,
|
|
||||||
name,
|
|
||||||
link,
|
|
||||||
url,
|
|
||||||
Qualities.Unknown.value,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
25
MesFilmsProvider/build.gradle.kts
Normal file
25
MesFilmsProvider/build.gradle.kts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// use an integer for version numbers
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
|
||||||
|
cloudstream {
|
||||||
|
language = "fr"
|
||||||
|
// All of these properties are optional, you can safely remove them
|
||||||
|
|
||||||
|
// description = "Lorem Ipsum"
|
||||||
|
authors = listOf("Sarlay")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status int as the following:
|
||||||
|
* 0: Down
|
||||||
|
* 1: Ok
|
||||||
|
* 2: Slow
|
||||||
|
* 3: Beta only
|
||||||
|
* */
|
||||||
|
status = 1 // will be 3 if unspecified
|
||||||
|
tvTypes = listOf(
|
||||||
|
"Movie",
|
||||||
|
)
|
||||||
|
|
||||||
|
iconUrl = "https://www.google.com/s2/favicons?domain=mesfilms.lol&sz=%size%"
|
||||||
|
}
|
2
MesFilmsProvider/src/main/AndroidManifest.xml
Normal file
2
MesFilmsProvider/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="com.lagradost"/>
|
|
@ -0,0 +1,301 @@
|
||||||
|
package com.lagradost
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addRating
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider
|
||||||
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
|
||||||
|
|
||||||
|
class MesFilmsProvider : MainAPI() {
|
||||||
|
override var mainUrl = "https://mesfilms.lol"
|
||||||
|
override var name = "Mes Films"
|
||||||
|
override val hasQuickSearch = false // recherche rapide (optionel, pas vraimet utile)
|
||||||
|
override val hasMainPage = true // page d'accueil (optionel mais encoragé)
|
||||||
|
override var lang = "fr" // fournisseur est en francais
|
||||||
|
override val supportedTypes = setOf(TvType.Movie) // ici on ne supporte que les films
|
||||||
|
// override val supportedTypes = setOf(TvType.Movie, TvType.TvSeries) // films et series
|
||||||
|
// liste des types: https://recloudstream.github.io/dokka/app/com.lagradost.cloudstream3/-tv-type/index.html
|
||||||
|
|
||||||
|
/**
|
||||||
|
Cherche le site pour un titre spécifique
|
||||||
|
|
||||||
|
La recherche retourne une SearchResponse, qui peut être des classes suivants: AnimeSearchResponse, MovieSearchResponse, TorrentSearchResponse, TvSeriesSearchResponse
|
||||||
|
Chaque classes nécessite des données différentes, mais a en commun le nom, le poster et l'url
|
||||||
|
**/
|
||||||
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
|
val link = "$mainUrl/?s=$query"
|
||||||
|
val document = app.get(link).document // on convertit le html en un document
|
||||||
|
return document.select("div.search-page > div.result-item > article") // on séléctione tous les éléments 'enfant' du type articles
|
||||||
|
.filter { article -> // on filtre la liste obtenue de tous les articles
|
||||||
|
val type = article?.selectFirst("> div.image > div.thumbnail > a > span")?.text()
|
||||||
|
?.replace("\t", "")?.replace("\n", "")
|
||||||
|
|
||||||
|
type != "Épisode" // enlève tous les éléments qui sont des épisodes de série
|
||||||
|
}.mapNotNull { div -> // apmap crée une liste des éléments (ici newMovieSearchResponse et newTvSeriesSearchResponse)
|
||||||
|
val posterContainer = div.selectFirst("> div.image > div.thumbnail > a") // selectione le premier élément correspondant à ces critères
|
||||||
|
val type = posterContainer?.selectFirst("> span")?.text()?.replace("\t", "")?.replace("\n", "")
|
||||||
|
// replace enlève tous les '\t' et '\n' du titre
|
||||||
|
val mediaPoster = posterContainer?.selectFirst("> img")?.attr("src") // récupère le texte de l'attribut src de l'élément
|
||||||
|
|
||||||
|
val href = posterContainer?.attr("href") ?: return@mapNotNull null // renvoie une erreur si il n'y a pas de lien vers le média
|
||||||
|
// val test1 = stringVariable ?: "valeur par défault"
|
||||||
|
// val test2 = stringVariable ?: doSomething() // si stringVariable est null, on appelle la fonction doSomething
|
||||||
|
// '?:' est appelé Elvis Operator, si la variable stringVariable est null, alors on utilise la "valeur par défault"
|
||||||
|
// https://stackoverflow.com/questions/48253107/what-does-do-in-kotlin-elvis-operator
|
||||||
|
// val details = div.select("> div.details > div.meta")
|
||||||
|
//val rating = details.select("> span.rating").text()
|
||||||
|
// val year = details.select("> span.year").text().toIntOrNull()
|
||||||
|
|
||||||
|
val title = div.selectFirst("> div.details > div.title > a")?.text().toString()
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
"FILM" -> (
|
||||||
|
newMovieSearchResponse( // réponse du film qui sera ajoutée à la liste map qui sera ensuite return
|
||||||
|
title,
|
||||||
|
href,
|
||||||
|
TvType.Movie,
|
||||||
|
false
|
||||||
|
) {
|
||||||
|
this.posterUrl = mediaPoster
|
||||||
|
// this.year = year
|
||||||
|
// this.rating = rating
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else -> {
|
||||||
|
return@mapNotNull null // le type n'est pas reconnu ==> enlever
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class EmbedUrlClass(
|
||||||
|
@JsonProperty("embed_url") val url: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* charge la page d'informations, il ya toutes les donées, les épisodes, le résumé etc ...
|
||||||
|
* Il faut retourner soit: AnimeLoadResponse, MovieLoadResponse, TorrentLoadResponse, TvSeriesLoadResponse.
|
||||||
|
*/
|
||||||
|
|
||||||
|
data class EpisodeData(
|
||||||
|
@JsonProperty("url") val url: String,
|
||||||
|
@JsonProperty("episodeNumber") val episodeNumber: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun load(url: String): LoadResponse {
|
||||||
|
// url est le lien retourné par la fonction search (la variable href) ou la fonction getMainPage
|
||||||
|
val document = app.get(url).document // convertit en document
|
||||||
|
|
||||||
|
val meta = document.selectFirst("div.sheader")
|
||||||
|
val poster = meta?.select("div.poster > img")?.attr("data-src") // récupere le texte de l'attribut 'data-src'
|
||||||
|
|
||||||
|
val title = meta?.select("div.data > h1")?.text() ?: throw ErrorLoadingException("Invalid title")
|
||||||
|
|
||||||
|
val data = meta.select("div.data")
|
||||||
|
val extra = data.select("div.extra")
|
||||||
|
|
||||||
|
val description = extra.select("span.tagline").first()?.text() // first() selectione le premier élément de la liste
|
||||||
|
|
||||||
|
val rating1 = document.select("div.custom_fields > span.valor > b#repimdb > strong").text()
|
||||||
|
val rating2 = document.select("div.custom_fields > span.valor > strong").text()
|
||||||
|
|
||||||
|
val rating = when {
|
||||||
|
!rating1.isNullOrBlank() -> rating1
|
||||||
|
!rating2.isNullOrBlank() -> rating2
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val date = extra.select("span.date").first()?.text()?.takeLast(4) // prends les 4 dernier chiffres
|
||||||
|
|
||||||
|
val tags = data.select("div.sgeneros > a").apmap {it.text()} // séléctione tous les tags et les ajoutes à une liste
|
||||||
|
|
||||||
|
val postId = document.select("#report-video-button-field > input[name=postID]").first()?.attr("value") // élémennt spécifique à ce site
|
||||||
|
|
||||||
|
val mediaType = if(url.contains("/film/")) {
|
||||||
|
"movie"
|
||||||
|
} else {
|
||||||
|
"TV"
|
||||||
|
}
|
||||||
|
// un api est disponible sur ce site pour récupérer le trailer (lien vers youtube)
|
||||||
|
val trailerUrl = postId?.let{
|
||||||
|
try {
|
||||||
|
val payloadRequest = mapOf(
|
||||||
|
"action" to "doo_player_ajax",
|
||||||
|
"post" to postId,
|
||||||
|
"nume" to "trailer",
|
||||||
|
"type" to mediaType
|
||||||
|
) // on crée les donées de la requetes ici
|
||||||
|
val getTrailer =
|
||||||
|
app.post(
|
||||||
|
"$mainUrl/wp-admin/admin-ajax.php",
|
||||||
|
headers = mapOf("Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8"),
|
||||||
|
data = payloadRequest
|
||||||
|
).text
|
||||||
|
parseJson<EmbedUrlClass>(getTrailer).url
|
||||||
|
// parseJson lit le contenu de la réponse (la variable getTrailer) et cherche la valeur d'embed_url dans cette réponse
|
||||||
|
|
||||||
|
} catch (e: Exception){
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (mediaType == "movie") {
|
||||||
|
return newMovieLoadResponse(
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
TvType.Movie,
|
||||||
|
url
|
||||||
|
) { // retourne les informations du film
|
||||||
|
this.posterUrl = poster
|
||||||
|
addRating(rating)
|
||||||
|
this.year = date?.toIntOrNull()
|
||||||
|
this.tags = tags
|
||||||
|
this.plot = description
|
||||||
|
trailerUrl?.let{addTrailer(trailerUrl)}
|
||||||
|
}
|
||||||
|
} else // a tv serie
|
||||||
|
{
|
||||||
|
throw ErrorLoadingException("Nothing besides movies are implemented for this provider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// récupere les liens .mp4 ou m3u8 directement à partir du paramètre data généré avec la fonction load()
|
||||||
|
override suspend fun loadLinks(
|
||||||
|
data: String, // fournit par load()
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit,
|
||||||
|
): Boolean {
|
||||||
|
val document = app.get(data).document
|
||||||
|
|
||||||
|
document.select("ul#playeroptionsul > li:not(#player-option-trailer)").apmap { li -> // séléctione tous les players sauf celui avec l'id player-option-trailer
|
||||||
|
// https://jsoup.org/cookbook/extracting-data/selector-syntax
|
||||||
|
val quality = li.selectFirst("span.title")?.text()
|
||||||
|
val server = li.selectFirst("> span.server")?.text()
|
||||||
|
val languageInfo =
|
||||||
|
|
||||||
|
li.selectFirst("span.flag > img")?.attr("data-src")
|
||||||
|
?.substringAfterLast("/") // séléctione la partie de la chaine de caractère apr_s le dernier /
|
||||||
|
?.replace(".png", "") ?: ""
|
||||||
|
|
||||||
|
val postId = li.attr("data-post")
|
||||||
|
|
||||||
|
val indexOfPlayer = li.attr("data-nume")
|
||||||
|
|
||||||
|
// un api est disponible sur le site pour récupéré les liens vers des embed (iframe) type uqload
|
||||||
|
val payloadRequest = mapOf(
|
||||||
|
"action" to "doo_player_ajax",
|
||||||
|
"post" to postId,
|
||||||
|
"nume" to indexOfPlayer,
|
||||||
|
"type" to "movie"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val getPlayerEmbed =
|
||||||
|
app.post(
|
||||||
|
"$mainUrl/wp-admin/admin-ajax.php",
|
||||||
|
headers = mapOf("Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8"),
|
||||||
|
data = payloadRequest
|
||||||
|
).text
|
||||||
|
val playerUrl = parseJson<EmbedUrlClass>(getPlayerEmbed).url // récupère l'url de l'embed en lisant le json
|
||||||
|
|
||||||
|
if (playerUrl != null)
|
||||||
|
loadExtractor(
|
||||||
|
httpsify(playerUrl),
|
||||||
|
playerUrl,
|
||||||
|
subtitleCallback
|
||||||
|
) { link -> // charge un extracteur d'extraire le lien direct .mp4
|
||||||
|
callback.invoke(ExtractorLink( // ici je modifie le callback pour ajouter des informations, normalement ce n'est pas nécessaire
|
||||||
|
link.source,
|
||||||
|
link.name + " $languageInfo",
|
||||||
|
link.url,
|
||||||
|
link.referer,
|
||||||
|
getQualityFromName(quality),
|
||||||
|
link.isM3u8,
|
||||||
|
link.headers,
|
||||||
|
link.extractorData
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override val mainPage = mainPageOf(
|
||||||
|
Pair("$mainUrl/film/", "Récemment ajouté"),
|
||||||
|
Pair("$mainUrl/tendance/?get=movies", "Tendance"),
|
||||||
|
Pair("$mainUrl/evaluations/?get=movies", "Les plus notés"),
|
||||||
|
Pair("$mainUrl/films-classiques/", "Quelques classiques"),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getMainPage(
|
||||||
|
page: Int,
|
||||||
|
request: MainPageRequest
|
||||||
|
): HomePageResponse {
|
||||||
|
val document = app.get(request.data).document
|
||||||
|
val movies = document.select("div.items > article.movies")
|
||||||
|
val categoryTitle = request.name
|
||||||
|
val returnList = movies.mapNotNull { article ->
|
||||||
|
// ici si un élément est null, il sera automatiquement enlevé de la liste
|
||||||
|
val poster = article.select("div.poster")
|
||||||
|
val posterUrl = poster.select("> img").attr("data-src")
|
||||||
|
val qualityExtracted = poster.select("> div.mepo > span.quality").text().uppercase()
|
||||||
|
|
||||||
|
val quality = getQualityFromString(
|
||||||
|
when (!qualityExtracted.isNullOrBlank()) {
|
||||||
|
qualityExtracted.contains("WEBRIP") -> {
|
||||||
|
"WebRip"
|
||||||
|
}
|
||||||
|
qualityExtracted.contains("HDRIP") -> {
|
||||||
|
"HD"
|
||||||
|
}
|
||||||
|
qualityExtracted.contains("HDLIGHT") -> {
|
||||||
|
"HD"
|
||||||
|
}
|
||||||
|
qualityExtracted.contains("BDRIP") -> {
|
||||||
|
"BlueRay"
|
||||||
|
}
|
||||||
|
qualityExtracted.contains("DVD") -> {
|
||||||
|
"DVD"
|
||||||
|
}
|
||||||
|
qualityExtracted.contains("CAM") -> {
|
||||||
|
"Cam"
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val data = article.select("div.data")
|
||||||
|
val title = data.select("> h3 > a").text()
|
||||||
|
val link = data.select("> h3 > a").attr("href")
|
||||||
|
newMovieSearchResponse(
|
||||||
|
title,
|
||||||
|
link,
|
||||||
|
TvType.Movie,
|
||||||
|
false,
|
||||||
|
) {
|
||||||
|
this.posterUrl = posterUrl
|
||||||
|
this.quality = quality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (returnList.isEmpty()) throw ErrorLoadingException("No movies")
|
||||||
|
return HomePageResponse(
|
||||||
|
listOf(
|
||||||
|
HomePageList(categoryTitle, returnList)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
package com.lagradost
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||||
|
import com.lagradost.cloudstream3.plugins.Plugin
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
@CloudstreamPlugin
|
||||||
|
class MesFilmsProviderPlugin: Plugin() {
|
||||||
|
override fun load(context: Context) {
|
||||||
|
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||||
|
registerMainAPI(MesFilmsProvider())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue