diff --git a/Anizm/build.gradle.kts b/Anizm/build.gradle.kts new file mode 100644 index 0000000..6c69805 --- /dev/null +++ b/Anizm/build.gradle.kts @@ -0,0 +1,27 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "tr" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AnimeMovie", + "Anime", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=anizm.net&sz=%size%" +} \ No newline at end of file diff --git a/Anizm/src/main/AndroidManifest.xml b/Anizm/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/Anizm/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Anizm/src/main/kotlin/com/lagradost/Anizm.kt b/Anizm/src/main/kotlin/com/lagradost/Anizm.kt new file mode 100644 index 0000000..eefabe2 --- /dev/null +++ b/Anizm/src/main/kotlin/com/lagradost/Anizm.kt @@ -0,0 +1,195 @@ +package com.lagradost + +import android.util.Log +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.* +import org.jsoup.Jsoup +import org.jsoup.nodes.Element + + +class Anizm : MainAPI() { + override var mainUrl = "https://anizm.net" + override var name = "Anizm" + override val hasMainPage = true + override var lang = "tr" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + private const val mainServer = "https://anizmplayer.com" + } + + override val mainPage = mainPageOf( + "$mainUrl/anime-izle?sayfa=" to "Son Eklenen Animeler", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + val home = document.select("div.restrictedWidth div#episodesMiddle").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse(request.name, home) + } + + private fun getProperAnimeLink(uri: String): String { + return if (uri.contains("-bolum")) { + "$mainUrl/${uri.substringAfter("$mainUrl/").replace(Regex("-[0-9]+-bolum.*"), "")}" + } else { + uri + } + } + + private fun Element.toSearchResult(): AnimeSearchResponse? { + val href = getProperAnimeLink(this.selectFirst("a")!!.attr("href")) + val title = this.selectFirst("div.title, h5.animeTitle a")?.text() ?: return null + val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src")) + val episode = this.selectFirst("div.truncateText")?.text()?.let { + Regex("([0-9]+).\\s?Bölüm").find(it)?.groupValues?.getOrNull(1)?.toIntOrNull() + } + + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addSub(episode) + } + } + + override suspend fun search(query: String): List { + val document = app.get( + "$mainUrl/fullViewSearch?search=$query&skip=0", + headers = mapOf("X-Requested-With" to "XMLHttpRequest") + ).document + + return document.select("div.searchResultItem").mapNotNull { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("h2.anizm_pageTitle a")!!.text().trim() + val type = + if (document.select("div.ui.grid div.four.wide").size == 1) TvType.Movie else TvType.Anime + val trailer = document.select("div.yt-hd-thumbnail-inner-container iframe").attr("src") + val episodes = document.select("div.ui.grid div.four.wide").map { + val name = it.select("div.episodeBlock").text() + val link = fixUrl(it.selectFirst("a")?.attr("href").toString()) + Episode(link, name) + } + return newAnimeLoadResponse(title, url, type) { + posterUrl = fixUrlNull(document.selectFirst("div.infoPosterImg > img")?.attr("src")) + this.year = document.select("div.infoSta ul li:first-child").text().trim().toIntOrNull() + addEpisodes(DubStatus.Subbed, episodes) + plot = document.select("div.infoDesc").text().trim() + this.tags = document.select("span.dataValue span.ui.label").map { it.text() } + addTrailer(trailer) + } + } + + private suspend fun invokeLokalSource( + url: String, + translator: String, + sourceCallback: (ExtractorLink) -> Unit + ) { + app.get(url, referer = "$mainUrl/").document.select("script").find { script -> + script.data().contains("eval(function(p,a,c,k,e,d)") + }?.let { + val key = getAndUnpack(it.data()).substringAfter("FirePlayer(\"").substringBefore("\",") + val referer = "$mainServer/video/$key" + val link = "$mainServer/player/index.php?data=$key&do=getVideo" + Log.i("hexated", link) + app.post( + link, + data = mapOf("hash" to key, "r" to "$mainUrl/"), + referer = referer, + headers = mapOf( + "Accept" to "*/*", + "Origin" to mainServer, + "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe()?.videoSource?.let { m3uLink -> + M3u8Helper.generateM3u8( + "${this.name} ($translator)", + m3uLink, + referer + ).forEach(sourceCallback) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val document = app.get(data).document + document.select("div.episodeTranslators div#fansec").map { + Pair(it.select("a").attr("translator"), it.select("div.title").text()) + }.apmap { (url, translator) -> + safeApiCall { + app.get( + url, + referer = data, + headers = mapOf( + "Accept" to "application/json, text/javascript, */*; q=0.01", + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe()?.data?.let { + Jsoup.parse(it).select("a").apmap { video -> + app.get( + video.attr("video"), + referer = data, + headers = mapOf( + "Accept" to "application/json, text/javascript, */*; q=0.01", + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe()?.player?.let { iframe -> + Jsoup.parse(iframe).select("iframe").attr("src").let { link -> + when { + link.startsWith(mainServer) -> { + invokeLokalSource(link, translator, callback) + } + else -> { + loadExtractor( + fixUrl(link), + "$mainUrl/", + subtitleCallback, + callback + ) + } + } + } + } + } + } + } + } + return true + } + + data class Source( + @JsonProperty("videoSource") val videoSource: String?, + ) + + data class Videos( + @JsonProperty("player") val player: String?, + ) + + data class Translators( + @JsonProperty("data") val data: String?, + ) + +} \ No newline at end of file diff --git a/Anizm/src/main/kotlin/com/lagradost/AnizmPlugin.kt b/Anizm/src/main/kotlin/com/lagradost/AnizmPlugin.kt new file mode 100644 index 0000000..8080247 --- /dev/null +++ b/Anizm/src/main/kotlin/com/lagradost/AnizmPlugin.kt @@ -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 AnizmPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Anizm()) + } +} \ No newline at end of file diff --git a/DramaidProvider/build.gradle.kts b/DramaidProvider/build.gradle.kts index 3fc2c3a..c217391 100644 --- a/DramaidProvider/build.gradle.kts +++ b/DramaidProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { @@ -7,7 +7,7 @@ cloudstream { // All of these properties are optional, you can safely remove them // description = "Lorem Ipsum" - // authors = listOf("Cloudburst") + authors = listOf("Hexated") /** * Status int as the following: @@ -22,5 +22,5 @@ cloudstream { "Movie", ) - iconUrl = "https://www.google.com/s2/favicons?domain=185.224.83.103&sz=%size%" + iconUrl = "https://www.google.com/s2/favicons?domain=dramaid.asia&sz=%size%" } \ No newline at end of file diff --git a/DramaidProvider/src/main/kotlin/com/lagradost/DramaidProvider.kt b/DramaidProvider/src/main/kotlin/com/lagradost/DramaidProvider.kt index 318a5ca..8dcf2b7 100644 --- a/DramaidProvider/src/main/kotlin/com/lagradost/DramaidProvider.kt +++ b/DramaidProvider/src/main/kotlin/com/lagradost/DramaidProvider.kt @@ -10,7 +10,7 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Element class DramaidProvider : MainAPI() { - override var mainUrl = "https://185.224.83.103" + override var mainUrl = "https://dramaid.asia" override var name = "DramaId" override val hasQuickSearch = false override val hasMainPage = true diff --git a/FilmanProvider/build.gradle.kts b/FilmanProvider/build.gradle.kts index 53dbbaf..f24d517 100644 --- a/FilmanProvider/build.gradle.kts +++ b/FilmanProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { @@ -23,4 +23,4 @@ cloudstream { ) iconUrl = "https://www.google.com/s2/favicons?domain=filman.cc&sz=%size%" -} \ No newline at end of file +} diff --git a/FilmanProvider/src/main/kotlin/com/lagradost/FilmanProvider.kt b/FilmanProvider/src/main/kotlin/com/lagradost/FilmanProvider.kt index 1ecec45..4c60ace 100644 --- a/FilmanProvider/src/main/kotlin/com/lagradost/FilmanProvider.kt +++ b/FilmanProvider/src/main/kotlin/com/lagradost/FilmanProvider.kt @@ -137,8 +137,13 @@ class FilmanProvider : MainAPI() { document?.select(".link-to-video")?.apmap { item -> val decoded = base64Decode(item.select("a").attr("data-iframe")) + val videoType = item.parent()?.select("td:nth-child(2)")?.text() val link = tryParseJson(decoded)?.src ?: return@apmap - loadExtractor(link, subtitleCallback, callback) + loadExtractor(link, subtitleCallback) { extractedLink -> + run { + callback(ExtractorLink(extractedLink.source, extractedLink.name + " " + videoType, extractedLink.url, extractedLink.referer, extractedLink.quality, extractedLink.isM3u8, extractedLink.headers, extractedLink.extractorData)) + } + } } return true } diff --git a/FrenchStreamProvider/build.gradle.kts b/FrenchStreamProvider/build.gradle.kts index 83658ab..b86bc10 100644 --- a/FrenchStreamProvider/build.gradle.kts +++ b/FrenchStreamProvider/build.gradle.kts @@ -6,8 +6,8 @@ cloudstream { language = "fr" // All of these properties are optional, you can safely remove them - // description = "Lorem Ipsum" - // authors = listOf("Cloudburst") + description = "FRENCH STREAM en plus d'être un site efficace et plaisant dispose d'un contenu visuel diversifié" + authors = listOf("Sarlay", "Eddy976") /** * Status int as the following: @@ -18,10 +18,9 @@ cloudstream { * */ status = 1 // will be 3 if unspecified tvTypes = listOf( - "AnimeMovie", "TvSeries", "Movie", ) - iconUrl = "https://www.google.com/s2/favicons?domain=french-stream.re&sz=%size%" + iconUrl = "https://www.google.com/s2/favicons?domain=french-stream.ac&sz=%size%" } \ No newline at end of file diff --git a/FrenchStreamProvider/src/main/kotlin/com/lagradost/FrenchStreamProvider.kt b/FrenchStreamProvider/src/main/kotlin/com/lagradost/FrenchStreamProvider.kt index 4d6720c..05aef23 100644 --- a/FrenchStreamProvider/src/main/kotlin/com/lagradost/FrenchStreamProvider.kt +++ b/FrenchStreamProvider/src/main/kotlin/com/lagradost/FrenchStreamProvider.kt @@ -1,54 +1,31 @@ package com.lagradost + import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.LoadResponse.Companion.addRating import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.extractorApis +import org.jsoup.nodes.Element class FrenchStreamProvider : MainAPI() { - override var mainUrl = "https://french-stream.re" - override var name = "French Stream" + override var mainUrl = "https://french-stream.cx" //re ou ac ou city + override var name = "FrenchStream" override val hasQuickSearch = false override val hasMainPage = true override var lang = "fr" - override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie) - + override val supportedTypes = setOf(TvType.Movie, TvType.TvSeries) override suspend fun search(query: String): List { - val link = "$mainUrl/?do=search&subaction=search&story=$query" - val soup = app.post(link).document + val link = "$mainUrl/?do=search&subaction=search&story=$query" // search' + val document = + app.post(link).document // app.get() permet de télécharger la page html avec une requete HTTP (get) + val results = document.select("div#dle-content > > div.short") - return soup.select("div.short-in.nl").map { li -> - val href = fixUrl(li.selectFirst("a.short-poster")!!.attr("href")) - val poster = li.selectFirst("img")?.attr("src") - val title = li.selectFirst("> a.short-poster")!!.text().toString().replace(". ", "") - val year = li.selectFirst(".date")?.text()?.split("-")?.get(0)?.toIntOrNull() - if (title.contains( - "saison", - ignoreCase = true - ) - ) { // if saison in title ==> it's a TV serie - TvSeriesSearchResponse( - title, - href, - this.name, - TvType.TvSeries, - poster, - year, - (title.split("Eps ", " ")[1]).split(" ")[0].toIntOrNull() - ) - } else { // it's a movie - MovieSearchResponse( - title, - href, - this.name, - TvType.Movie, - poster, - year, - ) + val allresultshome = + results.apmap { article -> // avec mapnotnull si un élément est null, il sera automatiquement enlevé de la liste + article.toSearchResponse() } - } + return allresultshome } override suspend fun load(url: String): LoadResponse { @@ -58,45 +35,45 @@ class FrenchStreamProvider : MainAPI() { val isMovie = !title.contains("saison", ignoreCase = true) val description = soup.selectFirst("div.fdesc")!!.text().toString() - .split("streaming", ignoreCase = true)[1].replace(" : ", "") - var poster = fixUrlNull(soup.selectFirst("div.fposter > img")?.attr("src")) + .split("streaming", ignoreCase = true)[1].replace(":", "") + var poster = soup.selectFirst("div.fposter > img")?.attr("src") val listEpisode = soup.select("div.elink") + val tags = soup.select("ul.flist-col > li").getOrNull(1) + //val rating = soup.select("span[id^=vote-num-id]")?.getOrNull(1)?.text()?.toInt() if (isMovie) { - val tags = soup.select("ul.flist-col > li").getOrNull(1) + val yearRegex = Regex("""ate de sortie\: (\d*)""") + val year = yearRegex.find(soup.text())?.groupValues?.get(1) val tagsList = tags?.select("a") ?.mapNotNull { // all the tags like action, thriller ...; unused variable it?.text() } return newMovieLoadResponse(title, url, TvType.Movie, url) { this.posterUrl = poster - addRating(soup.select("div.fr-count > div").text()) - this.year = soup.select("ul.flist-col > li").getOrNull(2)?.text()?.toIntOrNull() + this.year = year?.toIntOrNull() this.tags = tagsList this.plot = description - addTrailer(soup.selectFirst("div.fleft > span > a")?.attr("href")) + //this.rating = rating + addTrailer(soup.selectFirst("button#myBtn > a")?.attr("href")) } } else // a tv serie { - //println(listEpisode) - //println("listeEpisode:") + val episodeList = if (" 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" + "Vostfr" } - "Episode " + epNum?.toString() + " en " + type + "Episode " + type } else { a.text() } @@ -112,17 +89,24 @@ class FrenchStreamProvider : MainAPI() { null // episode date ) } - return TvSeriesLoadResponse( + + // val tagsList = tags?.text()?.replace("Genre :","") + val yearRegex = Regex("""Titre .* \/ (\d*)""") + val year = yearRegex.find(soup.text())?.groupValues?.get(1) + return newTvSeriesLoadResponse( title, url, - this.name, TvType.TvSeries, episodes, - poster, - null, - description, - ShowStatus.Ongoing, - ) + ) { + this.posterUrl = poster + this.plot = description + this.year = year?.toInt() + //this.rating = rating + //this.showStatus = ShowStatus.Ongoing + //this.tags = tagsList + addTrailer(soup.selectFirst("button#myBtn > a")?.attr("href")) + } } } @@ -219,10 +203,27 @@ class FrenchStreamProvider : MainAPI() { servers.apmap { for (extractor in extractorApis) { - if (it.first.contains(extractor.name, ignoreCase = true)) { - // val name = it.first - // print("true for $name") - extractor.getSafeUrl(it.second, it.second, subtitleCallback, callback) + var playerName = it.first + + if (playerName.contains("Stream.B")) { + playerName = it.first.replace("Stream.B", "StreamSB") + } + if (it.second.contains("streamlare")) { + playerName = "Streamlare" + } + if (playerName.contains(extractor.name, ignoreCase = true)) { + val header = app.get( + "https" + it.second.split("https").get(1), + allowRedirects = false + ).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 } } @@ -232,42 +233,71 @@ class FrenchStreamProvider : MainAPI() { } - override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse? { - val document = app.get(mainUrl).document - val docs = document.select("div.sect") - val returnList = docs.mapNotNull { - val epList = it.selectFirst("> div.sect-c.floats.clearfix") ?: return@mapNotNull null - val title = - it.selectFirst("> div.sect-t.fx-row.icon-r > div.st-left > a.st-capt")!!.text() - val list = epList.select("> div.short") - val isMovieType = title.contains("Films") // if truen type is Movie - val currentList = list.map { head -> - val hrefItem = head.selectFirst("> div.short-in.nl > a") - val href = fixUrl(hrefItem!!.attr("href")) - val img = hrefItem.selectFirst("> img") - val posterUrl = img!!.attr("src") - val name = img.attr("> div.short-title").toString() - return@map if (isMovieType) MovieSearchResponse( - name, - href, - this.name, - TvType.Movie, - posterUrl, - null - ) else TvSeriesSearchResponse( - name, - href, - this.name, - TvType.TvSeries, - posterUrl, - null, null - ) - } - if (currentList.isNotEmpty()) { - HomePageList(title, currentList) - } else null + private fun Element.toSearchResponse(): SearchResponse { + + val posterUrl = fixUrl(select("a.short-poster > img").attr("src")) + val qualityExtracted = select("span.film-ripz > a").text() + val type = select("span.mli-eps").text() + val title = select("div.short-title").text() + val link = select("a.short-poster").attr("href").replace("wvw.", "") //wvw is an issue + var quality = when (!qualityExtracted.isNullOrBlank()) { + qualityExtracted.contains("HDLight") -> getQualityFromString("HD") + qualityExtracted.contains("Bdrip") -> getQualityFromString("BlueRay") + qualityExtracted.contains("DVD") -> getQualityFromString("DVD") + qualityExtracted.contains("CAM") -> getQualityFromString("Cam") + + else -> null + } + + if (type.contains("Eps", false)) { + return MovieSearchResponse( + name = title, + url = link, + apiName = title, + type = TvType.Movie, + posterUrl = posterUrl, + quality = quality + + ) + + + } else // an Serie + { + + return TvSeriesSearchResponse( + name = title, + url = link, + apiName = title, + type = TvType.TvSeries, + posterUrl = posterUrl, + quality = quality, + // + ) + } - if (returnList.isEmpty()) return null - return HomePageResponse(returnList) } + + override val mainPage = mainPageOf( + Pair("$mainUrl/xfsearch/version-film/page/", "Derniers films"), + Pair("$mainUrl/xfsearch/version-serie/page/", "Derniers séries"), + Pair("$mainUrl/film/arts-martiaux/page/", "Films za m'ringué (Arts martiaux)"), + Pair("$mainUrl/film/action/page/", "Films Actions"), + Pair("$mainUrl/film/romance/page/", "Films za malomo (Romance)"), + Pair("$mainUrl/serie/aventure-serie/page/", "Série aventure"), + Pair("$mainUrl/film/documentaire/page/", "Documentaire") + + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val url = request.data + page + val document = app.get(url).document + val movies = document.select("div#dle-content > div.short") + + val home = + movies.map { article -> // avec mapnotnull si un élément est null, il sera automatiquement enlevé de la liste + article.toSearchResponse() + } + return newHomePageResponse(request.name, home) + } + } diff --git a/FrenchStreamProvider/src/main/kotlin/com/lagradost/FrenchStreamProviderPlugin.kt b/FrenchStreamProvider/src/main/kotlin/com/lagradost/FrenchStreamProviderPlugin.kt index f111fc9..ac71bb4 100644 --- a/FrenchStreamProvider/src/main/kotlin/com/lagradost/FrenchStreamProviderPlugin.kt +++ b/FrenchStreamProvider/src/main/kotlin/com/lagradost/FrenchStreamProviderPlugin.kt @@ -10,5 +10,6 @@ class FrenchStreamProviderPlugin: Plugin() { override fun load(context: Context) { // All providers should be added in this manner. Please don't edit the providers list directly. registerMainAPI(FrenchStreamProvider()) + registerExtractorAPI(VidoExtractor()) } } \ No newline at end of file diff --git a/FrenchStreamProvider/src/main/kotlin/com/lagradost/VidoExtractor.kt b/FrenchStreamProvider/src/main/kotlin/com/lagradost/VidoExtractor.kt new file mode 100644 index 0000000..32f5517 --- /dev/null +++ b/FrenchStreamProvider/src/main/kotlin/com/lagradost/VidoExtractor.kt @@ -0,0 +1,47 @@ +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? { + 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 + } +} \ No newline at end of file diff --git a/HDMovie5/build.gradle.kts b/HDMovie5/build.gradle.kts index 7b872b7..13af911 100644 --- a/HDMovie5/build.gradle.kts +++ b/HDMovie5/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 2 +version = 3 cloudstream { @@ -22,5 +22,5 @@ cloudstream { "Movie", ) - iconUrl = "https://www.google.com/s2/favicons?domain=hdmovie2.plus&sz=%size%" + iconUrl = "https://www.google.com/s2/favicons?domain=hdmovie2.run&sz=%size%" } \ No newline at end of file diff --git a/HDMovie5/src/main/kotlin/com/lagradost/HDMovie5.kt b/HDMovie5/src/main/kotlin/com/lagradost/HDMovie5.kt index e6e3929..591db74 100644 --- a/HDMovie5/src/main/kotlin/com/lagradost/HDMovie5.kt +++ b/HDMovie5/src/main/kotlin/com/lagradost/HDMovie5.kt @@ -9,7 +9,7 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Element class HDMovie5 : MainAPI() { - override var mainUrl = "https://hdmovie2.plus" + override var mainUrl = "https://hdmovie2.rip" override var name = "HDMovie" override var lang = "hi" diff --git a/Hdfilmcehennemi/build.gradle.kts b/Hdfilmcehennemi/build.gradle.kts new file mode 100644 index 0000000..c10c4e9 --- /dev/null +++ b/Hdfilmcehennemi/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "tr" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=hdfilmcehennemi.live&sz=%size%" +} \ No newline at end of file diff --git a/Hdfilmcehennemi/src/main/AndroidManifest.xml b/Hdfilmcehennemi/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/Hdfilmcehennemi/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Hdfilmcehennemi/src/main/kotlin/com/lagradost/Hdfilmcehennemi.kt b/Hdfilmcehennemi/src/main/kotlin/com/lagradost/Hdfilmcehennemi.kt new file mode 100644 index 0000000..2ff8264 --- /dev/null +++ b/Hdfilmcehennemi/src/main/kotlin/com/lagradost/Hdfilmcehennemi.kt @@ -0,0 +1,205 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.* +import org.jsoup.nodes.Element + +class Hdfilmcehennemi : MainAPI() { + override var mainUrl = "https://www.hdfilmcehennemi.live" + override var name = "hdfilmcehennemi" + override val hasMainPage = true + override var lang = "tr" + override val hasQuickSearch = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + + override val mainPage = mainPageOf( + "$mainUrl/category/tavsiye-filmler-izle1/page/" to "Tavsiye Filmler Kategorisi", + "$mainUrl/yabancidizi/page/" to "Son Eklenen Yabancı Diziler", + "$mainUrl/imdb-7-puan-uzeri-filmler/page/" to "Imdb 7+ Filmler", + "$mainUrl/en-cok-yorumlananlar/page/" to "En Çok Yorumlananlar", + "$mainUrl/en-cok-begenilen-filmleri-izle/page/" to "En Çok Beğenilenler", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + val home = document.select("div.card-body div.row div.col-6.col-sm-3.poster-container") + .mapNotNull { + it.toSearchResult() + } + return newHomePageResponse(request.name, home) + } + + private fun Element.toSearchResult(): SearchResponse? { + val title = this.selectFirst("a")?.text() ?: return null + val href = fixUrlNull(this.selectFirst("a")?.attr("href")) ?: return null + val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("data-src")) + return newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + } + + } + + private fun Media.toSearchResponse(): SearchResponse? { + return newMovieSearchResponse( + title ?: return null, + "$mainUrl/$slugPrefix$slug", + TvType.TvSeries, + ) { + this.posterUrl = "$mainUrl/uploads/poster/$poster" + } + } + + override suspend fun quickSearch(query: String): List = search(query) + + override suspend fun search(query: String): List { + return app.post( + "$mainUrl/search/", + data = mapOf("query" to query), + referer = "$mainUrl/", + headers = mapOf( + "Accept" to "application/json, text/javascript, */*; q=0.01", + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe()?.result?.mapNotNull { media -> + media.toSearchResponse() + } ?: throw ErrorLoadingException("Invalid Json reponse") + } + + override suspend fun load(url: String): LoadResponse? { + val document = app.get(url).document + + val title = document.selectFirst("div.card-header > h1, div.card-header > h2")?.text() + ?: return null + val poster = fixUrlNull(document.selectFirst("img.img-fluid")?.attr("src")) + val tags = document.select("div.mb-0.lh-lg div:nth-child(5) a").map { it.text() } + val year = + document.selectFirst("div.mb-0.lh-lg div:nth-child(4) a")?.text()?.trim()?.toIntOrNull() + val tvType = if (document.select("nav#seasonsTabs").isNullOrEmpty() + ) TvType.Movie else TvType.TvSeries + val description = document.selectFirst("article.text-white > p")?.text()?.trim() + val rating = document.selectFirst("div.rating-votes div.rate span")?.text()?.toRatingInt() + val actors = document.select("div.mb-0.lh-lg div:last-child a.chip").map { + Actor(it.text(), it.select("img").attr("src")) + } + val recommendations = + document.select("div.swiper-wrapper div.poster.poster-pop").mapNotNull { + val recName = it.selectFirst("h2.title")?.text() ?: return@mapNotNull null + val recHref = + fixUrlNull(it.selectFirst("a")?.attr("href")) ?: return@mapNotNull null + val recPosterUrl = fixUrlNull(it.selectFirst("img")?.attr("data-src")) + newTvSeriesSearchResponse(recName, recHref, TvType.TvSeries) { + this.posterUrl = recPosterUrl + } + } + + return if (tvType == TvType.TvSeries) { + val trailer = + document.selectFirst("button.btn.btn-fragman.btn-danger")?.attr("data-trailer") + ?.let { + "https://www.youtube.com/embed/$it" + } + val episodes = document.select("div#seasonsTabs-tabContent div.card-list-item").map { + val href = it.select("a").attr("href") + val name = it.select("h3").text().trim() + val episode = it.select("h3").text().let { num -> + Regex("Sezon\\s?([0-9]+).").find(num)?.groupValues?.getOrNull(1)?.toIntOrNull() + } + val season = it.parents()[1].attr("id").substringAfter("-").toIntOrNull() + Episode( + href, + name, + season, + episode, + ) + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + addTrailer(trailer) + } + } else { + val trailer = + document.selectFirst("nav.nav.card-nav.nav-slider a[data-bs-toggle=\"modal\"]") + ?.attr("data-trailer")?.let { + "https://www.youtube.com/embed/$it" + } + newMovieLoadResponse(title, url, TvType.Movie, url) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + addTrailer(trailer) + } + } + } + + private suspend fun invokeLocalSource( + source: String, + url: String, + sourceCallback: (ExtractorLink) -> Unit + ) { + val m3uLink = + app.get(url, referer = "$mainUrl/").document.select("script") + .find { + it.data().contains("var sources = [];") || it.data() + .contains("playerInstance =") + }?.data() + ?.substringAfter("[{file:\"")?.substringBefore("\"}]") ?: return + + M3u8Helper.generateM3u8( + source, + m3uLink, + if (url.startsWith(mainUrl)) "$mainUrl/" else "https://vidmoly.to/" + ).forEach(sourceCallback) + + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("nav.nav.card-nav.nav-slider a.nav-link").map { + Pair(it.attr("href"), it.text()) + }.apmap { (url, source) -> + safeApiCall { + app.get(url).document.select("div.card-video > iframe").attr("data-src") + .let { link -> + invokeLocalSource(source, link, callback) + } + } + } + return true + } + + data class Result( + @JsonProperty("result") val result: ArrayList? = arrayListOf(), + ) + + data class Media( + @JsonProperty("title") val title: String? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("slug") val slug: String? = null, + @JsonProperty("slug_prefix") val slugPrefix: String? = null, + ) +} \ No newline at end of file diff --git a/Hdfilmcehennemi/src/main/kotlin/com/lagradost/HdfilmcehennemiPlugin.kt b/Hdfilmcehennemi/src/main/kotlin/com/lagradost/HdfilmcehennemiPlugin.kt new file mode 100644 index 0000000..bdee03b --- /dev/null +++ b/Hdfilmcehennemi/src/main/kotlin/com/lagradost/HdfilmcehennemiPlugin.kt @@ -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 HdfilmcehennemiPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Hdfilmcehennemi()) + } +} \ No newline at end of file diff --git a/KuramanimeProvider/build.gradle.kts b/KuramanimeProvider/build.gradle.kts index ff8359b..4c82120 100644 --- a/KuramanimeProvider/build.gradle.kts +++ b/KuramanimeProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { @@ -7,7 +7,7 @@ cloudstream { // All of these properties are optional, you can safely remove them // description = "Lorem Ipsum" - // authors = listOf("Cloudburst") + authors = listOf("Hexated") /** * Status int as the following: diff --git a/KuramanimeProvider/src/main/kotlin/com/lagradost/KuramanimeProvider.kt b/KuramanimeProvider/src/main/kotlin/com/lagradost/KuramanimeProvider.kt index 48da3b3..f3fc00e 100644 --- a/KuramanimeProvider/src/main/kotlin/com/lagradost/KuramanimeProvider.kt +++ b/KuramanimeProvider/src/main/kotlin/com/lagradost/KuramanimeProvider.kt @@ -21,12 +21,6 @@ class KuramanimeProvider : MainAPI() { ) companion object { - fun getType(t: String): TvType { - return if (t.contains("OVA") || t.contains("Special")) TvType.OVA - else if (t.contains("Movie")) TvType.AnimeMovie - else TvType.Anime - } - fun getStatus(t: String): ShowStatus { return when (t) { "Selesai Tayang" -> ShowStatus.Completed @@ -80,20 +74,11 @@ class KuramanimeProvider : MainAPI() { } override suspend fun search(query: String): List { - val link = "$mainUrl/anime?search=$query&order_by=oldest" + val link = "$mainUrl/anime?search=$query&order_by=latest" val document = app.get(link).document - return document.select(".product__item").mapNotNull { - val title = it.selectFirst("div.product__item__text > h5")!!.text().trim() - val poster = it.selectFirst("a > div")!!.attr("data-setbg") - val tvType = - getType(it.selectFirst(".product__item__text > ul > li")!!.text().toString()) - val href = fixUrl(it.selectFirst("a")!!.attr("href")) - - newAnimeSearchResponse(title, href, tvType) { - this.posterUrl = poster - addDubStatus(dubExist = false, subExist = true) - } + return document.select("div#animeList div.col-lg-4.col-md-6.col-sm-6").mapNotNull { + it.toSearchResult() } } @@ -164,7 +149,10 @@ class KuramanimeProvider : MainAPI() { name, url, referer = "$mainUrl/", - quality = quality + quality = quality, + headers = mapOf( + "Range" to "bytes=0-" + ) ) ) } diff --git a/KuronimeProvider/build.gradle.kts b/KuronimeProvider/build.gradle.kts index f4ad752..51f4655 100644 --- a/KuronimeProvider/build.gradle.kts +++ b/KuronimeProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { @@ -7,7 +7,7 @@ cloudstream { // All of these properties are optional, you can safely remove them // description = "Lorem Ipsum" - // authors = listOf("Cloudburst") + authors = listOf("Hexated") /** * Status int as the following: diff --git a/KuronimeProvider/src/main/kotlin/com/lagradost/KuronimeProvider.kt b/KuronimeProvider/src/main/kotlin/com/lagradost/KuronimeProvider.kt index 5f68ec6..9b22dee 100644 --- a/KuronimeProvider/src/main/kotlin/com/lagradost/KuronimeProvider.kt +++ b/KuronimeProvider/src/main/kotlin/com/lagradost/KuronimeProvider.kt @@ -111,7 +111,7 @@ class KuronimeProvider : MainAPI() { val type = getType( document.selectFirst(".infodetail > ul > li:nth-child(7)")?.ownText()?.trim().toString() ) - val trailer = document.selectFirst("div.tply iframe")?.attr("data-lazy-src") + val trailer = document.selectFirst("div.tply iframe")?.attr("data-src") val year = Regex("\\d, ([0-9]*)").find( document.select(".infodetail > ul > li:nth-child(5)").text() )?.groupValues?.get(1)?.toIntOrNull() diff --git a/LayarKacaProvider/build.gradle.kts b/LayarKacaProvider/build.gradle.kts index aaf7e01..27c96ea 100644 --- a/LayarKacaProvider/build.gradle.kts +++ b/LayarKacaProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { @@ -7,7 +7,7 @@ cloudstream { // All of these properties are optional, you can safely remove them // description = "Lorem Ipsum" - // authors = listOf("Cloudburst") + authors = listOf("Hexated") /** * Status int as the following: @@ -23,5 +23,5 @@ cloudstream { "Movie", ) - - } \ No newline at end of file + iconUrl = "https://www.google.com/s2/favicons?domain=lk21.homes&sz=%size%" +} \ No newline at end of file diff --git a/LayarKacaProvider/src/main/kotlin/com/lagradost/LayarKacaProvider.kt b/LayarKacaProvider/src/main/kotlin/com/lagradost/LayarKacaProvider.kt index cd31c85..2d72c22 100644 --- a/LayarKacaProvider/src/main/kotlin/com/lagradost/LayarKacaProvider.kt +++ b/LayarKacaProvider/src/main/kotlin/com/lagradost/LayarKacaProvider.kt @@ -9,7 +9,7 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Element class LayarKacaProvider : MainAPI() { - override var mainUrl = "https://lk21.xn--6frz82g" + override var mainUrl = "https://lk21.homes" override var name = "LayarKaca" override val hasMainPage = true override var lang = "id" diff --git a/NekosamaProvider/build.gradle.kts b/NekosamaProvider/build.gradle.kts new file mode 100644 index 0000000..1e0cc3f --- /dev/null +++ b/NekosamaProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "fr" + // All of these properties are optional, you can safely remove them + + description = " Ce site fait son entrée dans la catégorie des meilleurs sites animes Français. Il est très fiable car quasiment tous ses liens vidéos marchent. Il propose des animes en « VF » version française et en « VOSTFR » version originale Sous-titrée en Français." + authors = listOf("Eddy") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "AnimeMovie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=neko-sama.fr&sz=%size%" +} \ No newline at end of file diff --git a/NekosamaProvider/src/main/AndroidManifest.xml b/NekosamaProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/NekosamaProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/NekosamaProvider/src/main/kotlin/com/lagradost/NekosamaProvider.kt b/NekosamaProvider/src/main/kotlin/com/lagradost/NekosamaProvider.kt new file mode 100644 index 0000000..8905e3e --- /dev/null +++ b/NekosamaProvider/src/main/kotlin/com/lagradost/NekosamaProvider.kt @@ -0,0 +1,443 @@ +package com.lagradost + + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.toJson + +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import org.jsoup.nodes.Element + +import me.xdrop.fuzzywuzzy.FuzzySearch +import java.util.* +import kotlin.collections.ArrayList + +class NekosamaProvider : MainAPI() { + override var mainUrl = "https://neko-sama.fr" + override var name = "Neko-sama" + 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.Anime, TvType.AnimeMovie, TvType.OVA) // animes, animesfilms + + private val nCharQuery = 10 // take the lenght of the query + nCharQuery + private val resultsSearchNbr = 50 // take only n results from search function + + + data class EpisodeData( + @JsonProperty("id") val id: Int, + @JsonProperty("title") val title: String?, + @JsonProperty("title_english") val title_english: String?, + @JsonProperty("title_romanji") val title_romanji: String?, + @JsonProperty("title_french") val title_french: String?, + @JsonProperty("others") val others: String?, + @JsonProperty("type") val type: String?, + @JsonProperty("status") val status: String?, + @JsonProperty("popularity") val popularity: Int?, + @JsonProperty("url") val url: String, + @JsonProperty("genre") val genre: Genre?, + @JsonProperty("url_image") val url_image: String?, + @JsonProperty("score") val score: String?, + @JsonProperty("start_date_year") val start_date_year: String?, + @JsonProperty("nb_eps") val nb_eps: String?, + + ) + + data class Genre( + @JsonProperty("0") val action: String?, + @JsonProperty("1") val adventure: String?, + @JsonProperty("2") val drama: String?, + @JsonProperty("3") val fantasy: String?, + @JsonProperty("4") val military: String?, + @JsonProperty("5") val shounen: String?, + ) + + // Looking for the best title matching from parsed Episode data + private fun EpisodeData.titleObtainedBysortByQuery(query: String?): String? { + + if (query == null) { + // No shorting so return the first title + var title = this.title + + return title + } else { + + + val titles = listOf(title, title_french, title_english, title_romanji).filterNotNull() + // Sorted by the best title matching + val titlesSorted = titles.sortedBy { it -> + -FuzzySearch.ratio( + it?.take(query.length + nCharQuery), + query + ) + } + return titlesSorted.elementAt(0) + + + } + } + + private fun List.sortByQuery(query: String?): List { + return if (query == null) { + // Return list to base state if no query + this.sortedBy { it.title } + } else { + + this.sortedBy { + val bestTitleMatching = it.titleObtainedBysortByQuery(query) + -FuzzySearch.ratio( + bestTitleMatching?.take(query.length + nCharQuery) ?: bestTitleMatching, + query + ) + } + } + } + + /** This function is done because there is two database (vf and vostfr). So it allows to sort the combined database **/ + private fun List.sortByname(query: String?): List { + return if (query == null) { + // Return list to base state if no query + this.sortedBy { it.name } + } else { + + this.sortedBy { + val name = it.name + -FuzzySearch.ratio(name.take(query.length + nCharQuery), query) + } + } + } + + /** + 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 { + + var listofResults = ArrayList() + + listOf( + "$mainUrl/animes-search-vf.json" to "(VF) ", + "$mainUrl/animes-search-vostfr.json" to "(Vostfr) " + ).apmap {(url, version) -> + val dubStatus = when (!version.isNullOrBlank()) { + version.contains("VF") -> DubStatus.Dubbed + version.contains("Vostfr") -> DubStatus.Subbed + else -> null + } + val reponse = app.get(url).text + val ParsedData = tryParseJson>(reponse) + + ParsedData?.sortByQuery(query)?.take(resultsSearchNbr)?.forEach { it -> + val type = it.type + val mediaPoster = it.url_image + val href = fixUrl(it.url) + val bestTitleMatching = it.titleObtainedBysortByQuery(query) + val title = version + bestTitleMatching + + when (type) { + "m0v1e", "special" -> ( + listofResults.add(newMovieSearchResponse( // réponse du film qui sera ajoutée à la liste apmap qui sera ensuite return + title, + href, + TvType.AnimeMovie, + false + ) { + this.posterUrl = mediaPoster + } + )) + null, "tv", "ova", "" -> ( + listofResults.add(newAnimeSearchResponse( + title, + href, + TvType.Anime, + false + ) { + this.posterUrl = mediaPoster + this.dubStatus = EnumSet.of(dubStatus) + } + + )) + else -> { + + throw ErrorLoadingException("invalid media type") // le type n'est pas reconnu ==> affiche une erreur + } + } + } ?: throw ErrorLoadingException("ParsedData failed") + } + return listofResults.sortByname(query) + .take(resultsSearchNbr) // Do that to short the vf and vostfr anime together + + } + + /** + * 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. + */ + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document // + // url est le lien retourné par la fonction search (la variable href) ou la fonction getMainPage + + val episodes = ArrayList() + var mediaType = TvType.Anime + val script = + document.select("div#main > script:first-of-type") + + val srcAllInfoEpisode = + Regex("""min\"\,\"([^\}]*)\}""") + val results = srcAllInfoEpisode.findAll(script.toString()) + //srcAllInfoEpisode.find(script.toString())?.groupValues?.get(1)? + ////////////////////////////////////// + var title = "" //document.select("div.offset-md-4 >:not(small)").text() + var dataUrl = "" + var link_video = "" + ///////////////////////////////////// + results.forEach { infoEpisode -> + val episodeScript = infoEpisode.groupValues[1] + val srcScriptEpisode = + Regex("""episode\"\:\"Ep\. ([0-9]*)\"""") + val episodeNum = srcScriptEpisode.find(episodeScript)?.groupValues?.get(1)?.toInt() + val srcScriptTitle = Regex("""title\"\:\"([^\"]*)\"\,\"url\"\:\"\\\/anime""") + var titleE = srcScriptTitle.find(episodeScript)?.groupValues?.get(1) + if (titleE != null) title = titleE + val srcScriptlink = + Regex("""\"url\"\:\"([^\"]*)\"""") // remove\ + val link = srcScriptlink.find(episodeScript)?.groupValues?.get(1) + + if (link != null) link_video = fixUrl(link.replace("\\", "")) + + val srcScriptposter = + Regex("""\"url_image\"\:\"([^\"]*)\"""") // remove\ + val poster = srcScriptposter.find(episodeScript)?.groupValues?.get(1) + var link_poster = "" + if (poster != null) link_poster = poster.replace("\\", "") + dataUrl = link_video + + + episodes.add( + Episode( + link_video, + episode = episodeNum, + name = title, + posterUrl = link_poster + + ) + ) + + } + val regexYear = Regex("""Diffusion [a-zA-Z]* (\d*)""") + val infosList = + document.selectFirst("div#anime-info-list")?.text() + val isinfosList = !infosList.isNullOrBlank() + var year:Int?=null + if (isinfosList) { + if (infosList!!.contains("movie")) mediaType = TvType.AnimeMovie + year =regexYear.find(infosList)!!.groupValues.get(1).toInt() + } + + val description = document.selectFirst("div.synopsis > p")?.text() + val poster = document.select("div.cover > img").attr("src") + + if (mediaType == TvType.AnimeMovie) { + return newMovieLoadResponse( + title, + url, + mediaType, + dataUrl + ) { // retourne les informations du film + this.posterUrl = poster + this.plot = description + this.year = year + } + } else // an anime + { + val status = when (isinfosList) { + infosList!!.contains("En cours") -> ShowStatus.Ongoing // En cours + infosList!!.contains("Terminé") -> ShowStatus.Completed + else -> null + } + return newAnimeLoadResponse( + title, + url, + mediaType, + ) { + this.posterUrl = poster + this.plot = description + addEpisodes( + DubStatus.Dubbed, + episodes + ) + this.showStatus = status + this.year = year + + } + } + } + + + /** 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 url = data + val document = app.get(url).document + val script = document.select("""[type^="text"]""")[1] + val srcAllvideolinks = + Regex("""\'(https:\/\/[^']*)""") + + val results = srcAllvideolinks.findAll(script.toString()) + + results.forEach { infoEpisode -> + + var playerUrl = infoEpisode.groupValues[1] + + if (!playerUrl.isNullOrBlank()) + loadExtractor( + httpsify(playerUrl), + playerUrl, + subtitleCallback + ) { link -> + callback.invoke( + ExtractorLink( + link.source, + link.name + "", + link.url, + link.referer, + getQualityFromName("HD"), + link.isM3u8, + link.headers, + link.extractorData + ) + ) + } + } + + return true + } + + private fun Element.toSearchResponse(): SearchResponse { + val poster = select("div.cover > a > div.ma-lazy-wrapper") + var posterUrl = poster.select("img:last-child").attr("src") + if (posterUrl == "#") posterUrl = poster.select("img:last-child").attr("data-src") + val type = select("div.info > p.year").text() + val title = select("div.info > a.title > div.limit").text() + val link = fixUrl(select("div.cover > a").attr("href")) + if (type.contains("Film")) { + return newMovieSearchResponse( + title, + link, + TvType.AnimeMovie, + false, + ) { + this.posterUrl = posterUrl + } + + } else // an Anime + { + return newAnimeSearchResponse( + title, + link, + TvType.Anime, + false, + ) { + this.posterUrl = posterUrl + } + } + } + + data class LastEpisodeData( + @JsonProperty("time") val time: String?, + @JsonProperty("timestamp") val timestamp: Int?, + @JsonProperty("episode") val episode: String?, + @JsonProperty("icons") val icons: String?, + @JsonProperty("title") val title: String?, + @JsonProperty("lang") val lang: String?, + @JsonProperty("url") val url: String?, + @JsonProperty("anime_url") val anime_url: String?, + @JsonProperty("url_image") val url_image: String?, + @JsonProperty("url_bg") val url_bg: String, + ) + + private fun LastEpisodeData.tomainHome(): SearchResponse { + + var posterUrl = this.url_image?.replace("""\""", "") + val link = this.anime_url?.replace("""\""", "")?.let { fixUrl(it) } + ?: throw error("Error parsing") + val title = this.title ?: throw error("Error parsing") + val type = this.episode ?: "" + var lang = this.lang + val dubStatus = if (lang?.contains("vf") == true) { + DubStatus.Dubbed + } else { + DubStatus.Subbed + } + + if (type.contains("Ep")) { + return newAnimeSearchResponse( + title.take(15).replace("\n", "") + "\n" + type.replace("Ep", "Episode"), + link, + TvType.Anime, + false, + ) { + this.posterUrl = posterUrl + this.dubStatus = EnumSet.of(dubStatus) + + } + + } else // a movie + { + return newMovieSearchResponse( + title, + link, + TvType.AnimeMovie, + false, + ) { + this.posterUrl = posterUrl + } + } + } + + override val mainPage = mainPageOf( + Pair("$mainUrl", "Nouveaux épisodes"), + Pair("$mainUrl/anime-vf/", "Animes et Films en version français"), + Pair("$mainUrl/anime/", "Animes et Films sous-titrés en français"), + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val categoryName = request.name + var cssSelector = "" + if (categoryName.contains("Nouveaux") && page <= 1) { + cssSelector = "div#main >script"//"div.js-last-episode-container > div.col-lg-3" + } + val url: String + url = if (page == 1) { + request.data + } else { + request.data + page + } + val document = app.get(url).document + + val regexLastEpisode = Regex("""lastEpisodes = (.*)\;""") + val home = when (!categoryName.isNullOrBlank()) { + request.name.contains("Animes") -> document.select("div#regular-list-animes > div.anime") + .mapNotNull { article -> article.toSearchResponse() } + else -> + tryParseJson>( + document.selectFirst( + cssSelector + )?.let { + regexLastEpisode.find( + it.toString() + )?.groupValues?.get(1) + } + )!!.map { episode -> episode.tomainHome() } + } + return newHomePageResponse(request.name, home) + } +} \ No newline at end of file diff --git a/NekosamaProvider/src/main/kotlin/com/lagradost/NekosamaProviderPlugin.kt b/NekosamaProvider/src/main/kotlin/com/lagradost/NekosamaProviderPlugin.kt new file mode 100644 index 0000000..a94d27a --- /dev/null +++ b/NekosamaProvider/src/main/kotlin/com/lagradost/NekosamaProviderPlugin.kt @@ -0,0 +1,17 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class NekosamaPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(NekosamaProvider()) + registerExtractorAPI(PstreamExtractor()) + + + } +} \ No newline at end of file diff --git a/NekosamaProvider/src/main/kotlin/com/lagradost/PstreamExtractor.kt b/NekosamaProvider/src/main/kotlin/com/lagradost/PstreamExtractor.kt new file mode 100644 index 0000000..ad93b03 --- /dev/null +++ b/NekosamaProvider/src/main/kotlin/com/lagradost/PstreamExtractor.kt @@ -0,0 +1,53 @@ +package com.lagradost + +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.app + +import okio.ByteString.Companion.decodeBase64 + +open class PstreamExtractor : ExtractorApi() { + override val name: String = "Pstream" + override val mainUrl: String = "https://www.pstream.net" + override val requiresReferer = true + + override suspend fun getUrl(url: String, referer: String?): List? { + val refer = url + val headers = mapOf( + "Accept" to "*/*", + "Accept-Language" to "en-US,en;q=0.5", + ) + val document = app.get(url, headers = headers).document + + val scriptsourceUrl = + document.select("""script[src^="https://www.pstream.net/u/player-script?"]""") + .attr("src")//** Get the url where the scritp function is **/ + + val Scripdocument = + app.get(scriptsourceUrl, headers = headers).document//** Open the scritp function **/ + + val base64CodeRegex = + Regex("""e\.parseJSON\(atob\(t\)\.slice\(2\)\)\}\(\"(.*)\=\="\)\,n\=\"""") //** Search the code64 **/ + val code64 = base64CodeRegex.find(Scripdocument.toString())?.groupValues?.get(1) + + val decoded = code64?.decodeBase64()?.utf8() //** decode the code64 **/ + + val regexLink = Regex("""\"(https:\\\/\\\/[^"]*)""") //** Extract the m3u8 link **/ + val m3u8found = regexLink.find(decoded.toString())?.groupValues?.get(1) + var m3u8 = m3u8found.toString().replace("""\""", "") + + return listOf( + ExtractorLink( + name, + name, + m3u8, + refer, // voir si site demande le referer à mettre ici + Qualities.Unknown.value, + true, + headers = headers + + ) + ) + + } +} + diff --git a/NontonAnimeIDProvider/build.gradle.kts b/NontonAnimeIDProvider/build.gradle.kts index 233b380..3407d44 100644 --- a/NontonAnimeIDProvider/build.gradle.kts +++ b/NontonAnimeIDProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 3 cloudstream { @@ -7,7 +7,7 @@ cloudstream { // All of these properties are optional, you can safely remove them // description = "Lorem Ipsum" - // authors = listOf("Cloudburst") + authors = listOf("Hexated") /** * Status int as the following: diff --git a/NontonAnimeIDProvider/src/main/kotlin/com/lagradost/NontonAnimeIDProvider.kt b/NontonAnimeIDProvider/src/main/kotlin/com/lagradost/NontonAnimeIDProvider.kt index bc75ac2..a6310bb 100644 --- a/NontonAnimeIDProvider/src/main/kotlin/com/lagradost/NontonAnimeIDProvider.kt +++ b/NontonAnimeIDProvider/src/main/kotlin/com/lagradost/NontonAnimeIDProvider.kt @@ -47,15 +47,15 @@ class NontonAnimeIDProvider : MainAPI() { document.select("section#postbaru").forEach { block -> val header = block.selectFirst("h2")!!.text().trim() - val animes = block.select("article.animeseries").map { + val animes = block.select("article.animeseries").mapNotNull { it.toSearchResult() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) } - document.select("aside#sidebar_right > div:nth-child(4)").forEach { block -> + document.select("aside#sidebar_right > div.side").forEach { block -> val header = block.selectFirst("h3")!!.ownText().trim() - val animes = block.select("li.fullwdth").map { + val animes = block.select("ul li.fullwdth").mapNotNull { it.toSearchResultPopular() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) @@ -91,9 +91,9 @@ class NontonAnimeIDProvider : MainAPI() { } } - private fun Element.toSearchResult(): AnimeSearchResponse { + private fun Element.toSearchResult(): AnimeSearchResponse? { val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href"))) - val title = this.selectFirst("h3.title")!!.text() + val title = this.selectFirst("h3.title")?.text() ?: return null val posterUrl = fixUrl(this.select("img").attr("data-src")) return newAnimeSearchResponse(title, href, TvType.Anime) { @@ -103,9 +103,9 @@ class NontonAnimeIDProvider : MainAPI() { } - private fun Element.toSearchResultPopular(): AnimeSearchResponse { + private fun Element.toSearchResultPopular(): AnimeSearchResponse? { val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href"))) - val title = this.select("h4").text().trim() + val title = this.selectFirst("h4")?.text()?.trim() ?: return null val posterUrl = fixUrl(this.select("img").attr("data-src")) return newAnimeSearchResponse(title, href, TvType.Anime) { @@ -157,7 +157,7 @@ class NontonAnimeIDProvider : MainAPI() { val type = getType(document.select("span.typeseries").text().trim()) val rating = document.select("span.nilaiseries").text().trim().toIntOrNull() val description = document.select(".entry-content.seriesdesc > p").text().trim() - val trailer = document.selectFirst("iframe#traileryt")?.attr("data-src") + val trailer = document.selectFirst("a.trailerbutton")?.attr("href") val episodes = if (document.select("button.buttfilter").isNotEmpty()) { val id = document.select("input[name=series_id]").attr("value") diff --git a/PhimmoichillProvider/build.gradle.kts b/PhimmoichillProvider/build.gradle.kts index 84621af..4eee292 100644 --- a/PhimmoichillProvider/build.gradle.kts +++ b/PhimmoichillProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { @@ -7,7 +7,7 @@ cloudstream { // All of these properties are optional, you can safely remove them // description = "Lorem Ipsum" - // authors = listOf("Cloudburst") + authors = listOf("Hexated") /** * Status int as the following: diff --git a/PhimmoichillProvider/src/main/kotlin/com/lagradost/PhimmoichillProvider.kt b/PhimmoichillProvider/src/main/kotlin/com/lagradost/PhimmoichillProvider.kt index 1820ec8..a08b1b2 100644 --- a/PhimmoichillProvider/src/main/kotlin/com/lagradost/PhimmoichillProvider.kt +++ b/PhimmoichillProvider/src/main/kotlin/com/lagradost/PhimmoichillProvider.kt @@ -1,6 +1,5 @@ package com.lagradost -import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer @@ -8,7 +7,6 @@ import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.* import org.jsoup.nodes.Element import java.net.URLDecoder -import java.util.ArrayList class PhimmoichillProvider : MainAPI() { override var mainUrl = "https://phimmoichill.net" @@ -41,7 +39,14 @@ class PhimmoichillProvider : MainAPI() { val home = document.select("li.item").mapNotNull { it.toSearchResult() } - return newHomePageResponse(request.name, home) + return newHomePageResponse( + list = HomePageList( + name = request.name, + list = home, + isHorizontalImages = true + ), + hasNext = true + ) } private fun decode(input: String): String? = URLDecoder.decode(input, "utf-8") @@ -96,19 +101,11 @@ class PhimmoichillProvider : MainAPI() { val rating = document.select("ul.entry-meta.block-film li:nth-child(7) span").text().toRatingInt() val actors = document.select("ul.entry-meta.block-film li:last-child a").map { it.text() } - val recommendations = document.select("ul#list-film-realted li.item").mapNotNull { - val titleHeader = it.select("p") ?: return@mapNotNull null - val recUrl = titleHeader.attr("href") ?: return@mapNotNull null - val recTitle = titleHeader.text() ?: return@mapNotNull null - val poster = it.select("img").attr("src") - MovieSearchResponse( - recTitle, - recUrl, - this.name, - TvType.Movie, - poster - ) + val recommendations = document.select("ul#list-film-realted li.item").map { + it.toSearchResult().apply { + this.posterUrl = decode(it.selectFirst("img")!!.attr("data-src").substringAfter("url=")) } + } return if (tvType == TvType.TvSeries) { val docEpisodes = app.get(link).document @@ -155,81 +152,41 @@ class PhimmoichillProvider : MainAPI() { ): Boolean { val document = app.get(data).document - val key = document.select("div#content script").mapNotNull { script -> - if (script.data().contains("filmInfo.episodeID =")) { - val id = script.data().substringAfter("filmInfo.episodeID = parseInt('") - .substringBefore("');") + val key = document.select("div#content script") + .find { it.data().contains("filmInfo.episodeID =") }?.data()?.let { script -> + val id = script.substringAfter("filmInfo.episodeID = parseInt('") app.post( // Not mainUrl url = "https://phimmoichills.net/pmplayer.php", - data = mapOf("qcao" to id), + data = mapOf("qcao" to id, "sv" to "0"), referer = data, headers = mapOf( "X-Requested-With" to "XMLHttpRequest", "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8" ) - ).text.also { println("HERERERR $it") }.substringAfterLast("iniPlayers(\"").substringBefore("\",") - } else { - null + ).text.substringAfterLast("iniPlayers(\"") + .substringBefore("\",") } - }.first() listOf( - Pair("https://so-trym.topphimmoi.org/hlspm/$key", "PMFAST"), - Pair("https://dash.megacdn.xyz/hlspm/$key", "PMHLS"), + Pair("https://so-trym.topphimmoi.org/raw/$key/index.m3u8", "PMFAST"), + Pair("https://dash.megacdn.xyz/raw/$key/index.m3u8", "PMHLS"), Pair("https://dash.megacdn.xyz/dast/$key/index.m3u8", "PMBK") ).apmap { (link, source) -> safeApiCall { - if (source == "PMBK") { - callback.invoke( - ExtractorLink( - source, - source, - link, - referer = "$mainUrl/", - quality = Qualities.P1080.value, - isM3u8 = true - ) + callback.invoke( + ExtractorLink( + source, + source, + link, + referer = "$mainUrl/", + quality = Qualities.P1080.value, + isM3u8 = true, ) - } else { - val playList = app.get(link, referer = "$mainUrl/") - .parsedSafe()?.main?.segments?.map { segment -> - PlayListItem( - segment.link, - (segment.du.toFloat() * 1_000_000).toLong() - ) - } - - callback.invoke( - ExtractorLinkPlayList( - source, - source, - playList ?: return@safeApiCall, - referer = "$mainUrl/", - quality = Qualities.P1080.value, - headers = mapOf( -// "If-None-Match" to "*", - "Origin" to mainUrl, - ) - ) - ) - } + ) } } return true } - data class Segment( - @JsonProperty("du") val du: String, - @JsonProperty("link") val link: String, - ) - - data class DataM3u( - @JsonProperty("segments") val segments: List?, - ) - - data class ResponseM3u( - @JsonProperty("2048p") val main: DataM3u?, - ) - } diff --git a/README.md b/README.md index a377ccc..f207cae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cloudstream Non-English Plugin Repository +# Cloudstream Non-English Plugin Repository All available repositories: https://recloudstream.github.io/repos/ diff --git a/StreamingcommunityProvider/src/main/kotlin/com/lagradost/StreamingcommunityProvider.kt b/StreamingcommunityProvider/src/main/kotlin/com/lagradost/StreamingcommunityProvider.kt index e1c0f6a..b3cc074 100644 --- a/StreamingcommunityProvider/src/main/kotlin/com/lagradost/StreamingcommunityProvider.kt +++ b/StreamingcommunityProvider/src/main/kotlin/com/lagradost/StreamingcommunityProvider.kt @@ -128,7 +128,7 @@ data class TrailerElement( class StreamingcommunityProvider : MainAPI() { override var lang = "it" - override var mainUrl = "https://streamingcommunity.agency" + override var mainUrl = "https://streamingcommunity.tech" override var name = "Streamingcommunity" override val hasMainPage = true override val hasChromecastSupport = true diff --git a/UseeTv/build.gradle.kts b/UseeTv/build.gradle.kts index 31e67a1..9045169 100644 --- a/UseeTv/build.gradle.kts +++ b/UseeTv/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { diff --git a/UseeTv/src/main/kotlin/com/lagradost/UseeTv.kt b/UseeTv/src/main/kotlin/com/lagradost/UseeTv.kt index cbb7af9..6d7908a 100644 --- a/UseeTv/src/main/kotlin/com/lagradost/UseeTv.kt +++ b/UseeTv/src/main/kotlin/com/lagradost/UseeTv.kt @@ -32,7 +32,7 @@ class UseeTv : MainAPI() { }.mapNotNull { it.toSearchResult() } - HomePageList(name, home) + HomePageList(name, home, true) }.filter { it.list.isNotEmpty() } return HomePageResponse(home) diff --git a/VizjerProvider/build.gradle.kts b/VizjerProvider/build.gradle.kts index db8c650..0f5ce8a 100644 --- a/VizjerProvider/build.gradle.kts +++ b/VizjerProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 3 +version = 4 cloudstream { diff --git a/VizjerProvider/src/main/kotlin/com/lagradost/VizjerProvider.kt b/VizjerProvider/src/main/kotlin/com/lagradost/VizjerProvider.kt index 9cbb90a..2c9ef0f 100644 --- a/VizjerProvider/src/main/kotlin/com/lagradost/VizjerProvider.kt +++ b/VizjerProvider/src/main/kotlin/com/lagradost/VizjerProvider.kt @@ -5,11 +5,12 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor +import com.lagradost.cloudstream3.network.CloudflareKiller import org.jsoup.Jsoup import org.jsoup.select.Elements class VizjerProvider : MainAPI() { - override var mainUrl = "http://93.185.166.160" + override var mainUrl = "https://vizjer.pl" override var name = "Vizjer.pl" override var lang = "pl" override val hasMainPage = true @@ -19,8 +20,10 @@ class VizjerProvider : MainAPI() { TvType.Movie ) + private val interceptor = CloudflareKiller() + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { - val document = app.get(mainUrl).document + val document = app.get(mainUrl, interceptor = interceptor).document val lists = document.select(".item-list") val categories = ArrayList() for (l in lists) { @@ -37,7 +40,9 @@ class VizjerProvider : MainAPI() { this.name, TvType.Movie, properUrl(poster)!!, - year + year, + null, + posterHeaders = interceptor.getCookieHeaders(mainUrl).toMap() ) } categories.add(HomePageList(title, items)) @@ -47,7 +52,7 @@ class VizjerProvider : MainAPI() { override suspend fun search(query: String): List { val url = "$mainUrl/wyszukaj?phrase=$query" - val document = app.get(url).document + val document = app.get(url, interceptor = interceptor).document val lists = document.select("#advanced-search > div") val movies = lists[1].select("div:not(.clearfix)") val series = lists[3].select("div:not(.clearfix)") @@ -66,10 +71,10 @@ class VizjerProvider : MainAPI() { type, properUrl(img)!!, null, - null + posterHeaders = interceptor.getCookieHeaders(url).toMap() ) } else { - MovieSearchResponse(name, properUrl(href)!!, this.name, type, properUrl(img)!!, null) + MovieSearchResponse(name, properUrl(href)!!, this.name, type, properUrl(img)!!, null, posterHeaders = interceptor.getCookieHeaders(url).toMap()) } } } @@ -77,7 +82,7 @@ class VizjerProvider : MainAPI() { } override suspend fun load(url: String): LoadResponse { - val document = app.get(url).document + val document = app.get(url, interceptor = interceptor).document val documentTitle = document.select("title").text().trim() if (documentTitle.startsWith("Logowanie")) { diff --git a/VostfreeProvider/build.gradle.kts b/VostfreeProvider/build.gradle.kts new file mode 100644 index 0000000..9e936a5 --- /dev/null +++ b/VostfreeProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "fr" + // All of these properties are optional, you can safely remove them + + description = " Ce site est certainement l’un des meilleurs sites permettant de regarder des animes en ligne et gratuitement. Il vous propose la version « VF » version française et la « VOSTFR » version originale Sous-titrée en Français." + authors = listOf("Eddy") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "AnimeMovie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=vostfree.cx&sz=%size%" +} \ No newline at end of file diff --git a/VostfreeProvider/src/main/AndroidManifest.xml b/VostfreeProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/VostfreeProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/VostfreeProvider/src/main/kotlin/com/lagradost/MytvExtractor.kt b/VostfreeProvider/src/main/kotlin/com/lagradost/MytvExtractor.kt new file mode 100644 index 0000000..66c5d21 --- /dev/null +++ b/VostfreeProvider/src/main/kotlin/com/lagradost/MytvExtractor.kt @@ -0,0 +1,42 @@ +package com.lagradost +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.app +import org.jsoup.Jsoup + + +open class MytvExtractor : ExtractorApi() { + override val name: String = "Mytv" + override val mainUrl: String = "https://www.myvi.tv/" + private val srcRegex = + Regex("""PlayerLoader\.CreatePlayer\(\"v\=(.*)\\u0026tp""") // would be possible to use the parse and find src attribute + override val requiresReferer = false + + + override suspend fun getUrl(url: String, referer: String?): List? { + val cleaned_url = url + val html = app.get(cleaned_url) + with(html) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" + srcRegex.find(this.text)?.groupValues?.get(1)?.let { link -> + var lien = link + lien = lien.replace("%2f", "/").replace("%3a", ":").replace("%3f", "?") + .replace("%3d", "=").replace("%26", "&") + + //val html = app.get(url).text + //val document = Jsoup.parse(html) + //val link1 = document.select("script") + return listOf( + ExtractorLink( + name, + name, + lien, + cleaned_url, // voir si site demande le referer à mettre ici + Qualities.Unknown.value, + ) + ) + } + } + + return null + + } +} diff --git a/VostfreeProvider/src/main/kotlin/com/lagradost/SibnetExtractor.kt b/VostfreeProvider/src/main/kotlin/com/lagradost/SibnetExtractor.kt new file mode 100644 index 0000000..46bdccb --- /dev/null +++ b/VostfreeProvider/src/main/kotlin/com/lagradost/SibnetExtractor.kt @@ -0,0 +1,35 @@ + +package com.lagradost +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.app +import org.jsoup.Jsoup + + +open class SibnetExtractor : ExtractorApi() { + override val name: String = "Sibnet" + override val mainUrl: String = "https://video.sibnet.ru" + private val srcRegex = + Regex("""player\.src\(\[\{src: \"(.*?)\"""") // would be possible to use the parse and find src attribute + override val requiresReferer = true + + + override suspend fun getUrl(url: String, referer: String?): List? { + val cleaned_url = url + val html = app.get(cleaned_url) + with(html) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" + srcRegex.find(this.text)?.groupValues?.get(1)?.let { link -> + return listOf( + ExtractorLink( + name, + name, + mainUrl + link, + cleaned_url, // voir si site demande le referer à mettre ici + Qualities.Unknown.value, + ) + ) + } + } + + return null + } +} diff --git a/VostfreeProvider/src/main/kotlin/com/lagradost/VostfreeProvider.kt b/VostfreeProvider/src/main/kotlin/com/lagradost/VostfreeProvider.kt new file mode 100644 index 0000000..b41e261 --- /dev/null +++ b/VostfreeProvider/src/main/kotlin/com/lagradost/VostfreeProvider.kt @@ -0,0 +1,368 @@ +package com.lagradost + + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import org.jsoup.nodes.Element +import java.util.* +import kotlin.collections.ArrayList + + +class VostfreeProvider : MainAPI() { + // VostFreeProvider() est ajouté à la liste allProviders dans MainAPI.kt + override var mainUrl = "https://vostfree.cx" + override var name = "Vostfree" + 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.Anime, TvType.AnimeMovie, TvType.OVA) // animes, animesfilms + // 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 { + val link = + "$mainUrl/index.php?do=search&subaction=search&story=$query&submit=Submit+Query" // L'url pour chercher un anime de dragon sera donc: 'https://vostfree.cx/index.php?story=dragon&do=search&subaction=search' + var mediaType = TvType.Anime + val document = + app.post(link).document // app.get() permet de télécharger la page html avec une requete HTTP (get) + return document.select("div.search-result") // on séléctione tous les éléments 'enfant' du type articles + .mapNotNull { div -> // map crée une liste des éléments (ici newMovieSearchResponse et newAnimeSearchResponse) + val type = + div?.selectFirst("div.genre") + ?.text() // replace enlève tous les '\t' et '\n' du titre + val mediaPoster = + div?.selectFirst("span.image > img")?.attr("src") + ?.let { fixUrl(it) } // récupère le texte de l'attribut src de l'élément + val href = div?.selectFirst("div.info > div.title > a")?.attr("href") + ?: throw ErrorLoadingException("invalid link") // renvoie une erreur si il n'y a pas de lien vers le média + val title = div.selectFirst("> div.info > div.title > a")?.text().toString() + val version = div.selectFirst("> div.info > ul > li")?.text().toString() + if (type == "OAV") mediaType = TvType.OVA + when (type) { + "FILM" -> ( + newMovieSearchResponse( // réponse du film qui sera ajoutée à la liste map qui sera ensuite return + title, + href, + TvType.AnimeMovie, + false + ) { + this.posterUrl = mediaPoster + // this.rating = rating + } + ) + null, "OAV" -> ( + newAnimeSearchResponse( + title, + href, + mediaType, + false + ) { + this.posterUrl = mediaPoster + this.dubStatus = + if (version.contains("VF")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of( + DubStatus.Subbed + ) + // this.rating = rating + } + + + ) + else -> { + throw ErrorLoadingException("invalid media type") // le type n'est pas reconnu ==> affiche une erreur + } + } + } + } + + /** + * 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 { + val document = app.get(url).document // récupere le texte sur la page (requète http) + // url est le lien retourné par la fonction search (la variable href) ou la fonction getMainPage + var mediaType = TvType.Anime + val episodes = ArrayList() + val urlSaison = ArrayList() + val meta = + document.selectFirst("div#dle-content > div.watch-top > div.image-bg > div.image-bg-content > div.slide-block ") + val description = meta?.select("div.slide-middle > div.slide-desc")?.first() + ?.text() // first() selectione le premier élément de la liste + var title = meta?.select("div.slide-middle > h1")?.text() + ?: "Invalid title" + title = title.replace("Saison", "").replace("saison", "").replace("SAISON", "") + .replace("Season", "").replace("season", "").replace("SEASON", "") + val poster = fixUrl( + meta?.select(" div.slide-poster > img") + ?.attr("src")!! + )// récupere le texte de l'attribut 'data-src' + var year = document.select("div.slide-info > p > b > a")?.text()?.toInt() + + urlSaison.add(url) + + + var seasonNumber: Int? = null + val otherSaisonFound = document.select("div.new_player_series_count > a") + otherSaisonFound.forEach { + urlSaison.add(it.attr("href")) + } + + urlSaison.apmap { urlseason -> + val document = + app.get(urlseason).document // récupere le texte sur la page (requète http) + + val meta = + document.selectFirst("div#dle-content > div.watch-top > div.image-bg > div.image-bg-content > div.slide-block ") + val poster_saison = mainUrl + meta?.select(" div.slide-poster > img") + ?.attr("src") + val seasontext = meta?.select("ul.slide-top > li:last-child > b:last-child")?.text() + var indication: String? = null + + if (!seasontext.isNullOrBlank() && !seasontext.contains("""([a-zA-Z])""".toRegex())) { + seasonNumber = seasontext.toInt() + + if (seasonNumber!! < 1) { // seem a an OVA has 0 as season number + seasonNumber = 1000 + indication = "Vous regardez un OVA" + } + } + + document.select(" select.new_player_selector > option").forEach { + val typeOftheAnime = it.text() + + if (typeOftheAnime != "Film") { + mediaType = TvType.Anime + val link = + EpisodeData( + urlseason, + typeOftheAnime.replace("Episode ", ""), + ).toJson() + episodes.add( + Episode( + link, + episode = typeOftheAnime.replace("Episode ", "").toInt(), + season = seasonNumber, + name = typeOftheAnime, + description = indication, + posterUrl = poster_saison + ) + ) + } else { + + mediaType = TvType.AnimeMovie + } + } + } + + if (mediaType == TvType.AnimeMovie) { + return newMovieLoadResponse( + title, + url, + mediaType, + url + ) { // retourne les informations du film + this.posterUrl = poster + this.plot = description + this.year = year + } + } else // an anime + { + return newAnimeLoadResponse( + title, + url, + mediaType, + ) { + this.posterUrl = poster + this.plot = description + this.year = year + addEpisodes( + if (title.contains("VF")) DubStatus.Dubbed else DubStatus.Subbed, + episodes + ) + + } + } + } + + + // 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 parsedInfo = tryParseJson(data) + val url = parsedInfo?.url ?: data + + val noMovie = "1" + val numeroEpisode = parsedInfo?.episodeNumber + ?: noMovie // if is not a movie then take the episode number else for movie it is 1 + + val document = app.get(url).document + document.select("div.new_player_bottom") + .forEach { player_bottom -> // séléctione tous les players + + // supprimer les zéro de 0015 pour obtenir l'episode 15 + var index = numeroEpisode.indexOf('0') + var numero = numeroEpisode + while (index == 0) { + numero = numeroEpisode.drop(1) + index = numero.indexOf('0') + } + + val cssQuery = " div#buttons_$numero" // numero épisode + val buttonsNepisode = player_bottom?.select(cssQuery) + ?: throw ErrorLoadingException("Non player") //séléctione tous les players pour l'episode NoEpisode + buttonsNepisode.select("> div").apmap { + val player = it.attr("id") + .toString() //prend tous les players resultat : "player_2140" et "player_6521" + val playerName = it.select("div#$player") + .text() // prend le nom du player ex : "Uqload" et "Sibnet" + val codePlayload = + document.selectFirst("div#content_$player")?.text() + .toString() // result : "325544" ou "https:..." + var playerUrl = when (playerName) { + "VIP", "Upvid", "Dstream", "Streamsb", "Vudeo", "NinjaS", "Upstream" -> codePlayload // case https + "Uqload" -> "https://uqload.com/embed-$codePlayload.html" + "Mytv" -> "https://www.myvi.tv/embed/$codePlayload" + "Sibnet" -> "https://video.sibnet.ru/shell.php?videoid=$codePlayload" + "Stream" -> "https://myvi.ru/player/embed/html/$codePlayload" + else -> return@apmap + } + + + 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 + "", + link.url, + link.referer, + getQualityFromName("HD"), + link.isM3u8, + link.headers, + link.extractorData + ) + ) + } + // } + + } + + } + return true + } + + private fun Element.toSearchResponse(): SearchResponse { + val poster = select("span.image") + val posterUrl = fixUrl(poster.select("> img").attr("src")) + val subdub = select("div.quality").text() + val genre = select("div.genre").text() + val title = select("div.info > div.title").text() + val link = select("div.play > a").attr("href") + if (genre == "FILM") { + return newMovieSearchResponse( + title, + link, + TvType.AnimeMovie, + false, + ) { + this.posterUrl = posterUrl +//this.quality = quality + } + + } else // an Anime + { + return newAnimeSearchResponse( + title, + link, + TvType.Anime, + false, + ) { + this.posterUrl = posterUrl + this.dubStatus = + if (subdub == "VF") EnumSet.of(DubStatus.Dubbed) else EnumSet.of(DubStatus.Subbed) + } + } + } + + private fun Element.toSearchResponse1(): SearchResponse { + val poster = select("span.image") + val posterUrl = fixUrl(poster.select("> img").attr("src")) + val subdub = select("div.quality").text() + //val genre = select("div.info > ul.additional > li").text() + val title = select("div.info > div.title").text() + val link = select(" div.info > div.title > a").attr("href") + + return newAnimeSearchResponse( + title, + link, + TvType.Anime, + false, + ) { + this.posterUrl = posterUrl + this.dubStatus = + if (subdub == "VF") EnumSet.of(DubStatus.Dubbed) else EnumSet.of(DubStatus.Subbed) + } + + } + + override val mainPage = mainPageOf( + Pair("$mainUrl/last-episode.html/page/", "Nouveaux épisodes en Vostfr"), + Pair( + "$mainUrl/animes-vostfr-recement-ajoutees.html/page/", + "Animes Vostfr récemment ajoutés" + ), + Pair("$mainUrl/last-episode-vf.html/page/", "Nouveaux épisodes en français"), + Pair("$mainUrl/last-anime-vf.html/page/", "Animes VF récemment ajoutés"), + Pair("$mainUrl/animes-vf/page/", "Animes en version français"), + Pair("$mainUrl/animes-vostfr/page/", "Animes sous-titrés en français"), + Pair("$mainUrl/films-vf-vostfr/page/", "Films en Fr et Vostfr") + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val categoryName = request.name + var cssSelector = "" + if (categoryName.contains("récemment")) { + cssSelector = "div#content > div.movie-poster" + } else { + cssSelector = "div#content > div#dle-content > div.movie-poster" + } + val url = request.data + page + val document = app.get(url).document + + val home = + when (!categoryName.isNullOrBlank()) { + request.name.contains("Nouveaux") -> document.select("div#content > div.last-episode") + .mapNotNull { article -> article.toSearchResponse1() } + else -> + document.select(cssSelector) + .mapNotNull { article -> // avec mapnotnull si un élément est null, il sera automatiquement enlevé de la liste + article.toSearchResponse() + } + } + return newHomePageResponse(request.name, home) + } + + +} \ No newline at end of file diff --git a/VostfreeProvider/src/main/kotlin/com/lagradost/VostfreeProviderPlugin.kt b/VostfreeProvider/src/main/kotlin/com/lagradost/VostfreeProviderPlugin.kt new file mode 100644 index 0000000..8083b33 --- /dev/null +++ b/VostfreeProvider/src/main/kotlin/com/lagradost/VostfreeProviderPlugin.kt @@ -0,0 +1,18 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class VostfreePlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(VostfreeProvider()) + registerExtractorAPI(VudeoExtractor()) + registerExtractorAPI(SibnetExtractor()) + registerExtractorAPI(MytvExtractor()) + + } +} \ No newline at end of file diff --git a/VostfreeProvider/src/main/kotlin/com/lagradost/VudeoExtractor.kt b/VostfreeProvider/src/main/kotlin/com/lagradost/VudeoExtractor.kt new file mode 100644 index 0000000..a0d4e54 --- /dev/null +++ b/VostfreeProvider/src/main/kotlin/com/lagradost/VudeoExtractor.kt @@ -0,0 +1,34 @@ + +package com.lagradost +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.app + + +open class VudeoExtractor : ExtractorApi() { + override val name: String = "Vudeo" + override val mainUrl: String = "https://vudeo.io/" + private val srcRegex = + Regex("""sources\: \[\"(.*)\"""") // would be possible to use the parse and find src attribute + override val requiresReferer = false + + + override suspend fun getUrl(url: String, referer: String?): List? { + val cleaned_url = url + with(app.get(cleaned_url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" + srcRegex.find(this.text)?.groupValues?.get(1)?.let { link -> + return listOf( + ExtractorLink( + name, + name, + link, + cleaned_url, // voir si site demande le referer à mettre ici + Qualities.Unknown.value, + ) + ) + } + } + return null + } +} + + diff --git a/WebFlix/build.gradle.kts b/WebFlix/build.gradle.kts new file mode 100644 index 0000000..80a21f7 --- /dev/null +++ b/WebFlix/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 3 + +cloudstream { + // All of these properties are optional, you can safely remove them + + description = "Adds multiple sites using WebFlix. This includes sites in English, Polish, Portuguese and Arabic" + authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Movies", + "TvSeries", + "Live" + ) + + iconUrl = "https://raw.githubusercontent.com/recloudstream/cloudstream-extensions-multilingual/master/WebFlix/icon.png" +} diff --git a/WebFlix/icon.png b/WebFlix/icon.png new file mode 100644 index 0000000..0bd09f7 Binary files /dev/null and b/WebFlix/icon.png differ diff --git a/WebFlix/src/main/AndroidManifest.xml b/WebFlix/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/WebFlix/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/WebFlix/src/main/kotlin/com/lagradost/WebFlixProvider.kt b/WebFlix/src/main/kotlin/com/lagradost/WebFlixProvider.kt new file mode 100644 index 0000000..79530bd --- /dev/null +++ b/WebFlix/src/main/kotlin/com/lagradost/WebFlixProvider.kt @@ -0,0 +1,226 @@ +package com.lagradost + +import android.util.Base64 +import android.util.Log +import com.lagradost.WebFlixProvider.Companion.toHomePageList +import com.lagradost.cloudstream3.* +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.Qualities +import com.lagradost.cloudstream3.utils.loadExtractor +import java.net.URLEncoder + + +class WebFlixProvider(override var lang: String, override var mainUrl: String, override var name: String, override val supportedTypes: Set) : MainAPI() { + val magicPath = base64Decode("NEY1QTlDM0Q5QTg2RkE1NEVBQ0VEREQ2MzUxODUvZDUwNmFiZmQtOWZlMi00YjcxLWI5NzktZmVmZjIxYmNhZDEzLw==") + override val hasMainPage = true + override val hasChromecastSupport = true + + override suspend fun getMainPage( + page: Int, + request : MainPageRequest + ): HomePageResponse? { + val res = tryParseJson(app.get("$mainUrl/api/first/$magicPath").text) ?: return null + return HomePageResponse( + res.getHomePageLists(this), + false + ) + } + + override suspend fun search(query: String): List? { + val res = tryParseJson(app.get("$mainUrl/api/search/${query.encodeUri()}/$magicPath").text) ?: return null + return res.posters.map { it.toSearchResponse(this) } + } + + override suspend fun load(url: String): LoadResponse? { + val data = tryParseJson(app.get(url).text) ?: return null + return data.toLoadResponse(this) + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val sources = tryParseJson>(data) ?: return false + sources.forEach { + it.load(subtitleCallback, callback) + } + return true + } + + private data class ApiSearchResponse( + val posters: List + ) + + private data class HomeResponse( + val genres: List = emptyList(), + val channels: List = emptyList(), + // val slides: List = emptyList() + ) { + fun getHomePageLists(provider: WebFlixProvider): List { + val lists = mutableListOf() + if (channels.isNotEmpty()) { + channels.forEach { + if (it.type == null) it.type = "channel" + } + lists.add(channels.toHomePageList("Channels", provider)) + } + //if (slides.isNotEmpty()) lists.add(slides.toHomePageList("Slides", provider)) + lists.addAll(genres.map { it.toHomePageList(provider) }) + return lists + } + } + private data class HomeReponseGenre( + val title: String, + val posters: List + ) { + fun toHomePageList(provider: WebFlixProvider) = posters.toHomePageList(title, provider) + } + + data class Source( + val title: String?, + val url: String?, + val quality: String?, + ) { + suspend fun load(subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { + if (url == null) return; + when (url.split(".").last()) { + "mp4", "m3u8", "mov" -> callback.invoke( + ExtractorLink( + quality ?: "", + title ?: "", + url, + "", + Qualities.Unknown.value, + isM3u8 = (url.endsWith("m3u8")) + )) + else -> loadExtractor(url, subtitleCallback, callback) + } + } + } + + private data class ApiEpisode( + val id: Int, + val title: String?, + val description: String?, + val sources: List = emptyList() + ) { + fun toEpisode(season: Int, episode: Int) = Episode( + sources.toJson(), + title, + season, + episode, + null, + null, + description + ) + } + + private data class ApiSeason( + val title: String?, + val episodes: List = emptyList() + ) { + fun getEpisodes(season: Int): List = episodes.mapIndexed { idx, episode -> episode.toEpisode(season, idx + 1) } + } + + data class Entry( + val id: Int, + val title: String, + val label: String?, + val sublabel: String?, + val image: String?, + val description: String?, + var type: String?, + val year: String?, + val imdb: Double?, + val sources: List = emptyList() + ) { + fun getTvType() = when (type) { + "serie" -> TvType.TvSeries + "movie" -> TvType.Movie + "channel", "4" -> TvType.Live + else -> { + Log.d("WebFlix", "other: $type") + TvType.Others + } + } + + fun toSearchResponse(provider: WebFlixProvider): SearchResponse { + val entry = this + return when(getTvType()) { + TvType.Movie -> provider.newMovieSearchResponse( + title, + "${provider.mainUrl}/api/movie/by/$id/${provider.magicPath}", + getTvType(), + ) { + posterUrl = image + year = entry.year?.toIntOrNull() + } + TvType.TvSeries -> provider.newTvSeriesSearchResponse( + title, + "${provider.mainUrl}/api/movie/by/$id/${provider.magicPath}", + TvType.TvSeries + ) { + posterUrl = image + //year = entry.year?.toIntOrNull() + } + TvType.Live -> provider.newMovieSearchResponse( + title, + "${provider.mainUrl}/api/channel/by/$id/${provider.magicPath}", + getTvType(), + ) { + posterUrl = image + year = entry.year?.toIntOrNull() + } + else -> provider.newMovieSearchResponse( + title, + "${provider.mainUrl}/api/$type/by/$id/${provider.magicPath}", + getTvType(), + ) { + posterUrl = image + year = entry.year?.toIntOrNull() + } + } + } + + suspend fun toLoadResponse(provider: WebFlixProvider): LoadResponse? { + val entry = this + return when(getTvType()) { + TvType.TvSeries -> { + val res = tryParseJson>(app.get("${provider.mainUrl}/api/season/by/serie/${id}/${provider.magicPath}").text) ?: return null + provider.newTvSeriesLoadResponse( + title, + "", + TvType.TvSeries, + res.mapIndexed { idx, season -> season.getEpisodes(idx + 1) } + .flatten() + ) { + this.posterUrl = entry.image + this.year = entry.year?.toIntOrNull() + this.plot = description + this.rating = if (entry.imdb != null) (entry.imdb*10).toInt() else null + } + } + else -> provider.newMovieLoadResponse( + title, + "", + getTvType(), + sources.toJson() + ) { + this.posterUrl = entry.image + this.year = entry.year?.toIntOrNull() + this.plot = description + this.rating = if (entry.imdb != null) (entry.imdb*10).toInt() else null + } + } + } + } + + companion object { + fun String.encodeUri() = URLEncoder.encode(this, "utf8") + fun List.toHomePageList(name: String, provider: WebFlixProvider) = HomePageList(name, this.map { it.toSearchResponse(provider) }) + } +} \ No newline at end of file diff --git a/WebFlix/src/main/kotlin/com/lagradost/WebFlixProviderPlugin.kt b/WebFlix/src/main/kotlin/com/lagradost/WebFlixProviderPlugin.kt new file mode 100644 index 0000000..2c33933 --- /dev/null +++ b/WebFlix/src/main/kotlin/com/lagradost/WebFlixProviderPlugin.kt @@ -0,0 +1,20 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context +import com.lagradost.cloudstream3.TvType + +@CloudstreamPlugin +class WebFlixProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(WebFlixProvider("en", "https://dhfilmtv.com", "DHFilmTv", setOf(TvType.Movie, TvType.TvSeries))) + registerMainAPI(WebFlixProvider("pl", "https://app.vodi.cc", "Vodi.cc", setOf(TvType.Movie, TvType.TvSeries))) + registerMainAPI(WebFlixProvider("fr", "http://www.vanflix.cm", "Vanflix", setOf(TvType.Movie, TvType.TvSeries, TvType.Live))) + registerMainAPI(WebFlixProvider("pt-pt", "https://www.brflix.xyz", "BrFlix", setOf(TvType.Movie, TvType.TvSeries, TvType.Live))) + registerMainAPI(WebFlixProvider("ar", "https://ifilm.live", "ifilm.live", setOf(TvType.Movie, TvType.TvSeries))) + registerMainAPI(WebFlixProvider("en", "https://karmadarna.com", "KarMaDarNa", setOf(TvType.NSFW))) + } +} \ No newline at end of file diff --git a/WiflixProvider/build.gradle.kts b/WiflixProvider/build.gradle.kts new file mode 100644 index 0000000..096b9ae --- /dev/null +++ b/WiflixProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "fr" + // All of these properties are optional, you can safely remove them + + description = "WIFLIX, le site grâce auquel vous allez pouvoir regarder vos films et séries préférées" + authors = listOf("Eddy") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=wiflix.zone&sz=%size%" +} \ No newline at end of file diff --git a/WiflixProvider/src/main/AndroidManifest.xml b/WiflixProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/WiflixProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/WiflixProvider/src/main/kotlin/com/lagradost/DoodStreamExtractor.kt b/WiflixProvider/src/main/kotlin/com/lagradost/DoodStreamExtractor.kt new file mode 100644 index 0000000..06b0277 --- /dev/null +++ b/WiflixProvider/src/main/kotlin/com/lagradost/DoodStreamExtractor.kt @@ -0,0 +1,35 @@ +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.getQualityFromName + + + +open class DoodStreamExtractor : ExtractorApi() { + override var name = "DoodStream" + override var mainUrl = "https://doodstream.com" + override val requiresReferer = false + + override fun getExtractorUrl(id: String): String { + return "$mainUrl/d/$id" + } + + override suspend fun getUrl(url: String, referer: String?): List? { + val response0 = app.get(url).text // html of DoodStream page to look for /pass_md5/... + val md5 =mainUrl+(Regex("/pass_md5/[^']*").find(response0)?.value ?: return null) // get https://dood.ws/pass_md5/... + val trueUrl = app.get(md5, referer = url).text + "zUEJeL3mUN?token=" + md5.substringAfterLast("/") //direct link to extract (zUEJeL3mUN is random) + val quality = Regex("\\d{3,4}p").find(response0.substringAfter("").substringBefore(""))?.groupValues?.get(0) + return listOf( + ExtractorLink( + trueUrl, + this.name, + trueUrl, + mainUrl, + getQualityFromName(quality), + false + ) + ) // links are valid in 8h + + } +} \ No newline at end of file diff --git a/WiflixProvider/src/main/kotlin/com/lagradost/StreamSBPlusExtractor.kt b/WiflixProvider/src/main/kotlin/com/lagradost/StreamSBPlusExtractor.kt new file mode 100644 index 0000000..6db5221 --- /dev/null +++ b/WiflixProvider/src/main/kotlin/com/lagradost/StreamSBPlusExtractor.kt @@ -0,0 +1,81 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper + + +// This is a modified version of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/genoanime/src/eu/kanade/tachiyomi/animeextension/en/genoanime/extractors/StreamSBExtractor.kt +// The following code is under the Apache License 2.0 https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE +open class StreamSBPlusExtractor : ExtractorApi() { + override var name = "StreamSB" + override var mainUrl = "https://sbspeed.com" + override val requiresReferer = false + + private val hexArray = "0123456789ABCDEF".toCharArray() + + private fun bytesToHex(bytes: ByteArray): String { + val hexChars = CharArray(bytes.size * 2) + for (j in bytes.indices) { + val v = bytes[j].toInt() and 0xFF + + hexChars[j * 2] = hexArray[v ushr 4] + hexChars[j * 2 + 1] = hexArray[v and 0x0F] + } + return String(hexChars) + } + + data class Subs ( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String, + ) + + data class StreamData ( + @JsonProperty("file") val file: String, + @JsonProperty("cdn_img") val cdnImg: String, + @JsonProperty("hash") val hash: String, + @JsonProperty("subs") val subs: List?, + @JsonProperty("length") val length: String, + @JsonProperty("id") val id: String, + @JsonProperty("title") val title: String, + @JsonProperty("backup") val backup: String, + ) + + data class Main ( + @JsonProperty("stream_data") val streamData: StreamData, + @JsonProperty("status_code") val statusCode: Int, + ) + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val regexID = + Regex("(embed-[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+|/e/[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + val id = regexID.findAll(url).map { + it.value.replace(Regex("(embed-|/e/)"), "") + }.first() +// val master = "$mainUrl/sources48/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362" + val master = "$mainUrl/sources48/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/" + val headers = mapOf( + "watchsb" to "sbstream", + ) + val mapped = app.get( + master.lowercase(), + headers = headers, + referer = url, + ).parsedSafe
() + // val urlmain = mapped.streamData.file.substringBefore("/hls/") + M3u8Helper.generateM3u8( + name, + mapped?.streamData?.file ?: return, + url, + headers = headers + ).forEach(callback) + } +} \ No newline at end of file diff --git a/WiflixProvider/src/main/kotlin/com/lagradost/WiflixProvider.kt b/WiflixProvider/src/main/kotlin/com/lagradost/WiflixProvider.kt new file mode 100644 index 0000000..77a55d7 --- /dev/null +++ b/WiflixProvider/src/main/kotlin/com/lagradost/WiflixProvider.kt @@ -0,0 +1,307 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import org.jsoup.nodes.Element +import org.jsoup.select.Elements +import kotlin.collections.ArrayList + +class WiflixProvider : MainAPI() { + + + override var mainUrl = "https://wiflix.zone" + override var name = "Wiflix" + 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, TvType.TvSeries) // series, films + // 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 { + val link = + "$mainUrl/index.php?do=search&subaction=search&search_start=0&full_search=1&result_from=1&story=$query&titleonly=3&searchuser=&replyless=0&replylimit=0&searchdate=0&beforeafter=after&sortby=date&resorder=desc&showposts=0&catlist%5B%5D=0" // search' + val document = + app.post(link).document // app.get() permet de télécharger la page html avec une requete HTTP (get) + val results = document.select("div#dle-content > div.clearfix") + + val allresultshome = + results.mapNotNull { article -> // avec mapnotnull si un élément est null, il sera automatiquement enlevé de la liste + article.toSearchResponse() + } + return allresultshome + } + + /** + * 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, + ) + + private fun Elements.takeEpisode(url: String, duborSub: String?): ArrayList { + + val episodes = ArrayList() + this.select("ul.eplist > li").forEach { + + val strEpisode = it.text() + val strEpisodeN = strEpisode.replace("Episode ", "") + val link = + EpisodeData( + url, + strEpisodeN, + ).toJson() + + + episodes.add( + Episode( + link, + name = duborSub, + episode = strEpisodeN.toInt(), + ) + ) + } + + return episodes + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document // + // url est le lien retourné par la fonction search (la variable href) ou la fonction getMainPage + + var episodes = ArrayList() + var mediaType: TvType + val episodeFrfound = + document.select("div.blocfr") + + val episodeVostfrfound = + document.select("div.blocvostfr") + val title = + document.select("h1[itemprop]").text() + val posterUrl = + document.select("img#posterimg").attr("src") + val yearRegex = Regex("""ate de sortie\: (\d*)""") + val year = yearRegex.find(document.text())?.groupValues?.get(1) + + + val tags = document.select("[itemprop=genre] > a") + .map { it.text() } // séléctione tous les tags et les ajoutes à une liste + + if (episodeFrfound.text().contains("Episode")) { + mediaType = TvType.TvSeries + val duborSub = "Episode en VF" + episodes = episodeFrfound.takeEpisode(url, duborSub) + } else if (episodeVostfrfound.text().contains("Episode")) { + mediaType = TvType.TvSeries + val duborSub = "Episode sous-titré" + episodes = episodeVostfrfound.takeEpisode(url, duborSub) + } else { + + mediaType = TvType.Movie + } + /////////////////////////////////////////// + /////////////////////////////////////////// + var type_rec: TvType + val recommendations = + document.select("div.clearfixme > div > div")?.mapNotNull { element -> + val recTitle = + element.select("a").text() ?: return@mapNotNull null + val image = element.select("a >img")?.attr("src") + val recUrl = element.select("a").attr("href") + type_rec = TvType.TvSeries + if (recUrl.contains("film")) type_rec = TvType.Movie + + if (type_rec == TvType.TvSeries) { + TvSeriesSearchResponse( + recTitle, + recUrl, + this.name, + TvType.TvSeries, + image?.let { fixUrl(it) }, + + ) + } else + MovieSearchResponse( + recTitle, + recUrl, + this.name, + TvType.Movie, + image?.let { fixUrl(it) }, + + ) + + } + + var comingSoon = url.contains("films-prochainement") + + + if (mediaType == TvType.Movie) { + val description = document.selectFirst("div.screenshots-full")?.text() + ?.replace("(.* .ynopsis)".toRegex(), "") + return newMovieLoadResponse( + name = title, + url = url, + type = TvType.Movie, + dataUrl = url + + ) { + this.posterUrl = fixUrl(posterUrl) + this.plot = description + this.recommendations = recommendations + this.year = year?.toIntOrNull() + this.comingSoon = comingSoon + this.tags = tags + } + } else { + val description = document.selectFirst("span[itemprop=description]")?.text() + return newTvSeriesLoadResponse( + title, + url, + mediaType, + episodes + ) { + this.posterUrl = fixUrl(posterUrl) + this.plot = description + this.recommendations = recommendations + this.year = year?.toIntOrNull() + this.comingSoon = comingSoon + this.tags = tags + + } + } + } + + + // 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 parsedInfo = + tryParseJson(data) + val url = parsedInfo?.url ?: data + + val numeroEpisode = parsedInfo?.episodeNumber ?: null + + val document = app.get(url).document + val episodeFrfound = + document.select("div.blocfr") + val episodeVostfrfound = + document.select("div.blocvostfr") + + val cssCodeForPlayer = if (episodeFrfound.text().contains("Episode")) { + "div.ep${numeroEpisode}vf > a" + } else if (episodeVostfrfound.text().contains("Episode")) { + "div.ep${numeroEpisode}vs > a" + } else { + "div.linkstab > a" + } + + + document.select("$cssCodeForPlayer").apmap { player -> // séléctione tous les players + var playerUrl = "https"+player.attr("href").replace("(.*)https".toRegex(), "") + if (!playerUrl.isNullOrBlank()) + if (playerUrl.contains("dood")) { + playerUrl = playerUrl.replace("doodstream.com", "dood.wf") + } + loadExtractor( + httpsify(playerUrl), + playerUrl, + subtitleCallback + ) { link -> + callback.invoke( + ExtractorLink( // ici je modifie le callback pour ajouter des informations, normalement ce n'est pas nécessaire + link.source, + link.name + "", + link.url, + link.referer, + getQualityFromName("HD"), + link.isM3u8, + link.headers, + link.extractorData + ) + ) + } + } + + + return true + } + + private fun Element.toSearchResponse(): SearchResponse { + + val posterUrl = fixUrl(select("div.img-box > img").attr("src")) + val qualityExtracted = select("div.nbloc1-2 >span").text() + val type = select("div.nbloc3").text() + val title = select("a.nowrap").text() + val link = select("a.nowrap").attr("href") + var quality = when (!qualityExtracted.isNullOrBlank()) { + qualityExtracted.contains("HDLight") -> getQualityFromString("HD") + qualityExtracted.contains("Bdrip") -> getQualityFromString("BlueRay") + qualityExtracted.contains("DVD") -> getQualityFromString("DVD") + qualityExtracted.contains("CAM") -> getQualityFromString("Cam") + + else -> null + } + if (type.contains("Film")) { + return MovieSearchResponse( + name = title, + url = link, + apiName = title, + type = TvType.Movie, + posterUrl = posterUrl, + quality = quality + + ) + + + } else // an Serie + { + + return TvSeriesSearchResponse( + name = title, + url = link, + apiName = title, + type = TvType.TvSeries, + posterUrl = posterUrl, + quality = quality, + // + ) + + } + } + + override val mainPage = mainPageOf( + Pair("$mainUrl/films-prochainement/page/", "Film Prochainement en Streaming"), + Pair("$mainUrl/film-en-streaming/page/", "Top Films cette année"), + Pair("$mainUrl/serie-en-streaming/page/", "Top Séries cette année"), + Pair("$mainUrl/saison-complete/page/", "Les saisons complètes"), + Pair("$mainUrl/film-ancien/page/", "Film zahalé (ancien)") + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val url = request.data + page + val document = app.get(url).document + val movies = document.select("div#dle-content > div.clearfix") + + val home = + movies.mapNotNull { article -> // avec mapnotnull si un élément est null, il sera automatiquement enlevé de la liste + article.toSearchResponse() + } + return newHomePageResponse(request.name, home) + } + +} diff --git a/WiflixProvider/src/main/kotlin/com/lagradost/WiflixProviderPlugin.kt b/WiflixProvider/src/main/kotlin/com/lagradost/WiflixProviderPlugin.kt new file mode 100644 index 0000000..7e90a52 --- /dev/null +++ b/WiflixProvider/src/main/kotlin/com/lagradost/WiflixProviderPlugin.kt @@ -0,0 +1,18 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class WiflixPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(WiflixProvider()) + registerExtractorAPI(DoodStreamExtractor()) + registerExtractorAPI(StreamSBPlusExtractor()) + + + } +} \ No newline at end of file diff --git a/XcineProvider/build.gradle.kts b/XcineProvider/build.gradle.kts index baa891d..3b64aed 100644 --- a/XcineProvider/build.gradle.kts +++ b/XcineProvider/build.gradle.kts @@ -16,7 +16,7 @@ cloudstream { * 2: Slow * 3: Beta only * */ - status = 1 // will be 3 if unspecified + status = 0 // will be 3 if unspecified tvTypes = listOf( "AnimeMovie", "Anime", @@ -25,4 +25,4 @@ cloudstream { ) iconUrl = "https://www.google.com/s2/favicons?domain=xcine.me&sz=%size%" -} \ No newline at end of file +} diff --git a/YomoviesProvider/src/main/kotlin/com/lagradost/YomoviesProvider.kt b/YomoviesProvider/src/main/kotlin/com/lagradost/YomoviesProvider.kt index e206af8..212b5e5 100644 --- a/YomoviesProvider/src/main/kotlin/com/lagradost/YomoviesProvider.kt +++ b/YomoviesProvider/src/main/kotlin/com/lagradost/YomoviesProvider.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.nodes.Element class YomoviesProvider : MainAPI() { - override var mainUrl = "https://yomovies.cloud" + override var mainUrl = "https://yomovies.homes" override var name = "Yomovies" override val hasMainPage = true override var lang = "hi" diff --git a/build.gradle.kts b/build.gradle.kts index 7d6e19e..122837f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -84,6 +84,8 @@ subprojects { //run JS implementation("org.mozilla:rhino:1.7.14") + // Library/extensions searching with Levenshtein distance + implementation ("me.xdrop:fuzzywuzzy:1.4.0") } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 72ef97b..bfedf27 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,7 +5,34 @@ rootProject.name = "CloudstreamPlugins" // Plugins are included like this val disabled = listOf( - "EgyBestProvider", "FaselHDProvider", "AkwamProvider", "MyCimaProvider" + "EgyBestProvider", + "FaselHDProvider", + "AkwamProvider", + "MyCimaProvider", + "AnimeIndoProvider", + "AnimeSailProvider", + "Anizm", + "DramaidProvider", + "DubokuProvider", + "Gomunimeis", + "GomunimeProvider", + "Hdfilmcehennemi", + "HDrezkaProvider", + "IdlixProvider", + "KuramanimeProvider", + "KuronimeProvider", + "LayarKacaProvider", + "MultiplexProvider", + "NeonimeProvider", + "NontonAnimeIDProvider", + "OploverzProvider", + "OtakudesuProvider", + "PhimmoichillProvider", + "RebahinProvider", + "TocanimeProvider", + "UakinoProvider", + "UseeTv", + "YomoviesProvider" ) File(rootDir, ".").eachDir { dir ->