diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index fc68d53c..b136ae4d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -145,8 +145,8 @@ object APIHolder { NginxProvider(), OlgplyProvider(), AniflixProvider(), - - KimCartoonProvider() + KimCartoonProvider(), + XcineProvider() ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMovie5.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMovie5.kt index 531c0f4a..7308ce4c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMovie5.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMovie5.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.Jsoup class HDMovie5 : MainAPI() { - override var mainUrl = "https://Hdmovie2.biz" + override var mainUrl = "https://Hdmovie2.art" override var name = "HDMovie" override var lang = "hi" diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/XcineProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/XcineProvider.kt new file mode 100644 index 00000000..05fd843e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/XcineProvider.kt @@ -0,0 +1,260 @@ +package com.lagradost.cloudstream3.movieproviders + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import org.jsoup.nodes.Element +import org.mozilla.javascript.Context +import org.mozilla.javascript.Scriptable + +class XcineProvider : MainAPI() { + override var lang = "de" + override var mainUrl = "https://xcine.me" + override var name = "Xcine" + override val usesWebView = false + override val hasMainPage = true + override val supportedTypes = + setOf(TvType.TvSeries, TvType.Movie, TvType.Anime, TvType.AnimeMovie) + + var cookies: MutableMap = mutableMapOf() + + private fun Element.toSearchResponse(): SearchResponse? { + val url = this.attr("href") ?: return null + val poster = select("div.poster-film-small").attr("data-src") + + val title = this.attr("title") + val yearRegex = Regex("""\((\d{4})\)\s*stream$""") + val year = yearRegex.find(title)?.groupValues?.getOrNull(1)?.toIntOrNull() + val fixedTitle = title.replace(yearRegex, "") + + // If you need to differentiate use the url. + return MovieSearchResponse( + fixedTitle, + url, + this@XcineProvider.name, + TvType.TvSeries, + poster, + year, + null, + ) + } + + override suspend fun getMainPage(): HomePageResponse { + val document = app.get(mainUrl).document + val sections = document.select("div.group-film") + return HomePageResponse(sections.mapNotNull { section -> + val title = section.select("a.more").attr("title") + val searchResponses = + section.select("a.film-small").mapNotNull { it.toSearchResponse() } + if (searchResponses.isEmpty()) return@mapNotNull null + HomePageList(title, searchResponses) + }) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/search?key=$query" + val document = app.get(url).document + return document.select("a.film-small").mapNotNull { + it.toSearchResponse() + } + } + + private fun String.getIntFromText(): Int? { + return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() + } + + data class EpisodeData( + val episodeId: Int, + val showId: Int, + val episodeUrl: String, + val key: String, + val keyValue: String, + val cookies: Map + ) + + private fun Element.toEpisode( + showId: Int, + key: String, + keyValue: String, + cookies: Map + ): Episode? { + val id = this.attr("data-episode-id").toIntOrNull() ?: return null + val title = this.attr("title") + val titleRegex = Regex("""Staffel\s*(\d*).*folge\s*(\d*)""") + val url = this.attr("href") + val found = titleRegex.find(title) + + return newEpisode( + EpisodeData(id, showId, url, key, keyValue, cookies) + ) { + season = found?.groupValues?.getOrNull(1)?.toIntOrNull() + episode = found?.groupValues?.getOrNull(2)?.toIntOrNull() + } + } + + + override suspend fun load(url: String): LoadResponse { + val (document, text) = app.get(url).let { + it.document to it.text + } + + val title = document.select("h1.title-film-detail-1").text() + val posterRegex = Regex("""ImageObject.*url":"(.*?)"""") + val posterUrl = posterRegex.find(text)?.groupValues?.getOrNull(1)?.replace("\\", "") + + val information = document.select("ul.infomation-film > li").associate { + it.text().substringBefore(":") to it.select("span").text() + } + + val year = information["Erscheinungsjahr"]?.toIntOrNull() + val duration = information["Laufzeit"]?.getIntFromText() + val tags = information["Genre"]?.split(",") + + val recommendations = + document.select("a.film-small").mapNotNull { + it.toSearchResponse() + } + + val playUrl = document.select("a.play-film").attr("href") + val (isSeries, episodes) = if (playUrl.isNotEmpty()) { + val keyRegex = Regex("""loadStreamSV.*\n\s*(.*?)\s*:\s*(.*?)\s*\}""") + val (playDoc, playText) = app.get(playUrl).let { + val responseCookies = it.okhttpResponse.headers.filter { header -> + header.first.equals( + "set-cookie", + ignoreCase = true + ) + } + responseCookies.forEach { + val (cookieKey, cookieValue) = it.second.split("=") + cookies[cookieKey] = cookieValue.substringBefore(";") + } + it.document to it.text + } + + val rhino = Context.enter() + rhino.optimizationLevel = -1 + val scope: Scriptable = rhino.initSafeStandardObjects() + val decodeBase64 = "atob = function(s) {\n" + + " var e={},i,b=0,c,x,l=0,a,r='',w=String.fromCharCode,L=s.length;\n" + + " var A=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n" + + " for(i=0;i<64;i++){e[A.charAt(i)]=i;}\n" + + " for(x=0;x=8){((a=(b>>>(l-=8))&0xff)||(x<(L-2)))&&(r+=w(a));}\n" + + " }\n" + + " return r;\n" + + "};" + val doc = """ + var document = {}; + var window = { + document: document, + eval: eval, + atob: atob + }; + """.trimMargin() + val jsRegex = Regex("""@context.*\n([\W\w]*?) a").mapNotNull { + it.toEpisode(showId, key, keyValue, mapOf(cookieKey to cookieValue)) + } + isSeries to episodes + } else { + false to emptyList() + } + } else { + false to emptyList() + } + + return if (isSeries || episodes.size > 1) newTvSeriesLoadResponse( + title, + url, + TvType.TvSeries, + episodes + ) { + this.comingSoon = playUrl.isEmpty() + } else + newMovieLoadResponse( + title, + url, + TvType.Movie, + episodes.firstOrNull()?.data ?: "" + ) { + this.posterUrl = posterUrl + this.year = year + this.tags = tags + + if (duration != 0) + this.duration = duration + + this.recommendations = recommendations + this.comingSoon = playUrl.isEmpty() || episodes.isEmpty() + } + } + + + data class File( + val file: String? = null, + val type: String? = null, + val label: String? = null + ) + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val parsed = parseJson(data) + val url = "$mainUrl/movie/load-stream/${parsed.showId}/${parsed.episodeId}" + val response = app.post( + url, + referer = parsed.episodeUrl, + headers = mapOf( + "X-Requested-With" to "XMLHttpRequest", + ), + data = mapOf(parsed.key to parsed.keyValue), + cookies = parsed.cookies + cookies, + ).text + + val jsonRegex = Regex("""(vip_|)source.*?(\[.*);""") + val json = jsonRegex.findAll(response) + + val files = json.mapNotNull { + (it.groupValues.getOrNull(1) == "vip_") to ( + tryParseJson>(it.groupValues.getOrNull(2)) ?: return@mapNotNull null) + } + + files.forEach { (isVip, list) -> + list.forEach file@{ file -> + if (file.file == null) return@file + callback.invoke( + ExtractorLink( + this.name, + this.name + if (isVip) " VIP" else "", + file.file.replace("\\", ""), + this.mainUrl, + file.label?.getIntFromText() ?: Qualities.Unknown.value, + file.type?.contains("hls", ignoreCase = true) == true, + ) + ) + } + } + + return files.sumOf { it.second.size } > 0 + } +} diff --git a/docs/providers.json b/docs/providers.json index 589a3508..d2765660 100644 --- a/docs/providers.json +++ b/docs/providers.json @@ -156,7 +156,7 @@ "EgyBestProvider": { "language": "ar", "name": "EgyBest", - "status": 0, + "status": 1, "url": "https://www.egy.best" }, "ElifilmsProvider": {