From afeb0785a45c5a3d787637ef10de14780f0da16e Mon Sep 17 00:00:00 2001 From: Blatzar Date: Sat, 25 Sep 2021 12:07:40 +0200 Subject: [PATCH] Added VidEmbed with the help of Arjix :) --- .../com/lagradost/cloudstream3/MainAPI.kt | 19 +- .../cloudstream3/extractors/Shiro.kt | 34 --- .../cloudstream3/extractors/Vidstream.kt | 49 +++- .../cloudstream3/extractors/XStreamCdn.kt | 1 + .../movieproviders/VidEmbedProvider.kt | 243 ++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 5 +- .../cloudstream3/utils/JsUnpacker.kt | 4 +- 7 files changed, 299 insertions(+), 56 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/Shiro.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/VidEmbedProvider.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 7468736c..b2360ded 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -1,6 +1,5 @@ package com.lagradost.cloudstream3 -import android.app.Activity import android.content.Context import androidx.preference.PreferenceManager import com.fasterxml.jackson.databind.DeserializationFeature @@ -10,11 +9,10 @@ import com.lagradost.cloudstream3.animeproviders.* import com.lagradost.cloudstream3.movieproviders.* import com.lagradost.cloudstream3.utils.ExtractorLink import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.HashSet const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + //val baseHeader = mapOf("User-Agent" to USER_AGENT) val mapper = JsonMapper.builder().addModule(KotlinModule()) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!! @@ -44,7 +42,8 @@ object APIHolder { WatchCartoonOnlineProvider(), AllMoviesForYouProvider(), AsiaFlixProvider(), - ThenosProvider() + ThenosProvider(), + VidEmbedProvider() ) val restrictedApis = arrayListOf( @@ -87,11 +86,11 @@ object APIHolder { val list = HashSet() for (name in set) { val api = getApiFromNameNull(name) ?: continue - if(activeLangs.contains(api.lang) ) { + if (activeLangs.contains(api.lang)) { list.add(name) } } - if(list.isEmpty()) return hashSet + if (list.isEmpty()) return hashSet return list } @@ -119,7 +118,7 @@ object APIHolder { hashSet.toMutableSet() ) - if(list.isNullOrEmpty()) return hashSet + if (list.isNullOrEmpty()) return hashSet return list.toHashSet() } @@ -132,11 +131,11 @@ object APIHolder { hashSet.map { it.name }.toMutableSet() ) - if(list.isNullOrEmpty()) return hashSet + if (list.isNullOrEmpty()) return hashSet val names = TvType.values().map { it.name }.toHashSet() val realSet = list.filter { names.contains(it) }.map { TvType.valueOf(it) }.toHashSet() - if(realSet.isEmpty()) return hashSet + if (realSet.isEmpty()) return hashSet return realSet } @@ -380,7 +379,7 @@ data class AnimeEpisode( val date: String? = null, val rating: Int? = null, val descript: String? = null, - val episode : Int? = null, + val episode: Int? = null, ) data class TorrentLoadResponse( diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Shiro.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Shiro.kt deleted file mode 100644 index a343fd2b..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Shiro.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities -import org.jsoup.Jsoup - -class Shiro : ExtractorApi() { - override val name: String = "Shiro" - override val mainUrl: String = "https://cherry.subsplea.se" - override val requiresReferer = false - - override fun getExtractorUrl(id: String): String { - return "$mainUrl/$id" - } - - override fun getUrl(url: String, referer: String?): List? { - val headers = mapOf("Referer" to "https://shiro.is/") - val res = khttp.get(url, headers = headers).text - Jsoup.parse(res).select("source").firstOrNull()?.attr("src")?.replace("&", "?")?.let { - return listOf( - ExtractorLink( - name, - name, - it.replace(" ", "%20"), - "https://cherry.subsplea.se/", - // UHD to give top priority - Qualities.P2160.value - ) - ) - } - return null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt index ad71ffc5..b61705db 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt @@ -1,19 +1,29 @@ package com.lagradost.cloudstream3.extractors +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.pmap import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.extractorApis +import com.lagradost.cloudstream3.utils.getQualityFromName import org.jsoup.Jsoup -class Vidstream { +/** + * overrideMainUrl is necessary for for other vidstream clones like vidembed.cc + * If they diverge it'd be better to make them separate. + * */ +class Vidstream(overrideMainUrl: String? = null) { val name: String = "Vidstream" - private val mainUrl: String = "https://gogo-stream.com" + private val mainUrl: String = overrideMainUrl ?: "https://gogo-stream.com" private fun getExtractorUrl(id: String): String { return "$mainUrl/streaming.php?id=$id" } - private val normalApis = arrayListOf(Shiro(), MultiQuality()) + private fun getDownloadUrl(id: String): String { + return "$mainUrl/download?id=$id" + } + + private val normalApis = arrayListOf(MultiQuality()) // https://gogo-stream.com/streaming.php?id=MTE3NDg5 fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean { @@ -23,22 +33,47 @@ class Vidstream { val source = api.getSafeUrl(url) source?.forEach { callback.invoke(it) } } + val extractorUrl = getExtractorUrl(id) - val url = getExtractorUrl(id) - with(khttp.get(url)) { + /** Stolen from GogoanimeProvider.kt extractor */ + normalSafeApiCall { + val link = getDownloadUrl(id) + val page = khttp.get(link, headers = mapOf("Referer" to extractorUrl)) + val pageDoc = Jsoup.parse(page.text) + val qualityRegex = Regex("(\\d+)P") + + pageDoc.select(".dowload > a[download]").forEach { + val qual = if (it.text() + .contains("HDP") + ) "1080" else qualityRegex.find(it.text())?.destructured?.component1().toString() + + callback.invoke( + ExtractorLink( + this.name, + if (qual == "null") this.name else "${this.name} - " + qual + "p", + it.attr("href"), + page.url, + getQualityFromName(qual), + it.attr("href").contains(".m3u8") + ) + ) + } + } + + with(khttp.get(extractorUrl)) { val document = Jsoup.parse(this.text) val primaryLinks = document.select("ul.list-server-items > li.linkserver") //val extractedLinksList: MutableList = mutableListOf() // All vidstream links passed to extractors - primaryLinks.forEach { element -> + primaryLinks.distinctBy { it.attr("data-video") }.forEach { element -> val link = element.attr("data-video") //val name = element.text() // Matches vidstream links with extractors extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api -> if (link.startsWith(api.mainUrl)) { - val extractedLinks = api.getSafeUrl(link, url) + val extractedLinks = api.getSafeUrl(link, extractorUrl) if (extractedLinks?.isNotEmpty() == true) { extractedLinks.forEach { callback.invoke(it) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt index f92fb3ba..a2545a1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt @@ -45,6 +45,7 @@ class XStreamCdn : ExtractorApi() { val newUrl = url.replace("$mainUrl/v/", "$mainUrl/api/source/") val extractedLinksList: MutableList = mutableListOf() with(khttp.post(newUrl, headers = headers)) { + if (this.text == """{"success":false,"data":"Video not found or has been removed"}""") return listOf() mapper.readValue(this.text)?.let { if (it.success && it.data != null) { it.data.forEach { data -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VidEmbedProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VidEmbedProvider.kt new file mode 100644 index 00000000..05f1c0d3 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VidEmbedProvider.kt @@ -0,0 +1,243 @@ +package com.lagradost.cloudstream3.movieproviders + +import org.jsoup.Jsoup +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.extractors.Vidstream +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName +import java.util.* +import kotlin.collections.ArrayList + + +class VidEmbedProvider : MainAPI() { + override val mainUrl: String + get() = "https://vidembed.cc" + override val name: String + get() = "VidEmbed" + override val hasQuickSearch: Boolean + get() = false + override val hasMainPage: Boolean + get() = true + + private fun fixUrl(url: String): String { + return if (url.startsWith("//")) { + "https:$url" + } else if (url.startsWith("/")) { + "$mainUrl$url" + } else { + url + } + } + + override val supportedTypes: Set + get() = setOf(TvType.Anime, TvType.AnimeMovie, TvType.TvSeries, TvType.Movie) + + override fun search(query: String): ArrayList { + val link = "$mainUrl/search.html?keyword=$query" + val html = khttp.get(link).text + val soup = Jsoup.parse(html) + + return ArrayList(soup.select(".listing.items > .video-block").map { li -> + val href = fixUrl(li.selectFirst("a").attr("href")) + val poster = li.selectFirst("img")?.attr("src") + val title = li.selectFirst(".name").text() + val year = li.selectFirst(".date")?.text()?.split("-")?.get(0)?.toIntOrNull() + + TvSeriesSearchResponse( + if (!title.contains("Episode")) title else title.split("Episode")[0].trim(), + href, + this.name, + TvType.TvSeries, + poster, year, + null + ) + }) + } + + override fun load(url: String): LoadResponse? { + val html = khttp.get(url).text + val soup = Jsoup.parse(html) + + var title = soup.selectFirst("h1,h2,h3").text() + title = if (!title.contains("Episode")) title else title.split("Episode")[0].trim() + + val description = soup.selectFirst(".post-entry")?.text()?.trim() + var poster: String? = null + + val episodes = soup.select(".listing.items.lists > .video-block").withIndex().map { (index, li) -> + val epTitle = if (li.selectFirst(".name") != null) + if (li.selectFirst(".name").text().contains("Episode")) + "Episode " + li.selectFirst(".name").text().split("Episode")[1].trim() + else + li.selectFirst(".name").text() + else "" + val epThumb = li.selectFirst("img")?.attr("src") + val epDate = li.selectFirst(".meta > .date").text() + + if (poster == null) { + poster = li.selectFirst("img")?.attr("onerror")?.split("=")?.get(1)?.replace(Regex("[';]"), "") + } + + val epNum = Regex("""Episode (\d+)""").find(epTitle)?.destructured?.component1()?.toIntOrNull() + + TvSeriesEpisode( + epTitle, + null, + epNum, + fixUrl(li.selectFirst("a").attr("href")), + epThumb, + epDate + ) + }.reversed() + val year = if (episodes.isNotEmpty()) episodes.first().date?.split("-")?.get(0)?.toIntOrNull() else null + + val tvType = if (episodes.size == 1 && episodes[0].name == title) TvType.Movie else TvType.TvSeries + + return when (tvType) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + poster, + year, + description, + ShowStatus.Ongoing, + null, + null + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + episodes[0].data, + poster, + year, + description, + null, + null + ) + } + else -> null + } + } + + override fun getMainPage(): HomePageResponse? { + val urls = listOf( + mainUrl, + "$mainUrl/movies", + "$mainUrl/series", + "$mainUrl/recommended-series", + "$mainUrl/cinema-movies" + ) + val homePageList = ArrayList() + urls.pmap { url -> + val response = khttp.get(url, timeout = 20.0) + val document = Jsoup.parse(response.text) + document.select("div.main-inner")?.forEach { + val title = it.select(".widget-title").text().trim() + val elements = it.select(".video-block").map { + val link = fixUrl(it.select("a").attr("href")) + val image = it.select(".picture > img").attr("src") + val name = it.select("div.name").text().trim() + val isSeries = (name.contains("Season") || name.contains("Episode")) + + if (isSeries) { + TvSeriesSearchResponse( + name, + link, + this.name, + TvType.TvSeries, + image, + null, + null, + ) + } else { + MovieSearchResponse( + name, + link, + this.name, + TvType.Movie, + image, + null, + null, + ) + } + } + + homePageList.add( + HomePageList( + title, elements + ) + ) + + } + + } + return HomePageResponse(homePageList) + } + + override fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val iframeLink = Jsoup.parse(khttp.get(data).text).selectFirst("iframe")?.attr("src") ?: return false + val vidstreamObject = Vidstream("https://vidembed.cc") + // https://vidembed.cc/streaming.php?id=MzUwNTY2&... -> MzUwNTY2 + val id = Regex("""id=([^&]*)""").find(iframeLink)?.groupValues?.get(1) + + if (id != null) { + vidstreamObject.getUrl(id, isCasting, callback) + } + + val html = khttp.get(fixUrl(iframeLink)).text + val soup = Jsoup.parse(html) + + val servers = soup.select(".list-server-items > .linkserver").mapNotNull { li -> + if (!li?.attr("data-video").isNullOrEmpty()) { + Pair(li.text(), fixUrl(li.attr("data-video"))) + } else { + null + } + } + servers.forEach { + if (it.first.toLowerCase(Locale.ROOT).trim() == "beta server") { + // Group 1: link, Group 2: Label + val sourceRegex = Regex("""sources:[\W\w]*?file:\s*["'](.*?)["'][\W\w]*?label:\s*["'](.*?)["']""") + val trackRegex = Regex("""tracks:[\W\w]*?file:\s*["'](.*?)["'][\W\w]*?label:\s*["'](.*?)["']""") + + val html = khttp.get(it.second, headers = mapOf("referer" to iframeLink)).text + sourceRegex.findAll(html).forEach { match -> + callback.invoke( + ExtractorLink( + this.name, + match.groupValues.getOrNull(2)?.let { "${this.name} $it" } ?: this.name, + match.groupValues[1], + it.second, + getQualityFromName(match.groupValues.getOrNull(2) ?: ""), + // Kinda risky + match.groupValues[1].endsWith(".m3u8"), + ) + ) + } + trackRegex.findAll(html).forEach { match -> + subtitleCallback.invoke( + SubtitleFile( + match.groupValues.getOrNull(2) ?: "Unknown", + match.groupValues[1] + ) + ) + } + } + } + + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 93a16a5e..81eebab1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -49,9 +49,9 @@ fun getPacked(string: String): String? { return packedRegex.find(string)?.value } -fun getAndUnpack(string: String): String? { +fun getAndUnpack(string: String): String { val packedText = getPacked(string) - return JsUnpacker(packedText).unpack() + return JsUnpacker(packedText).unpack() ?: string } fun loadExtractor(url: String, referer: String?, callback: (ExtractorLink) -> Unit) { @@ -65,7 +65,6 @@ fun loadExtractor(url: String, referer: String?, callback: (ExtractorLink) -> Un val extractorApis: Array = arrayOf( //AllProvider(), - Shiro(), WcoStream(), Mp4Upload(), StreamTape(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/JsUnpacker.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/JsUnpacker.kt index a082af68..e49067fa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/JsUnpacker.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/JsUnpacker.kt @@ -32,7 +32,7 @@ class JsUnpacker(packedJS: String?) { val js = packedJS try { var p = - Pattern.compile("""}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", Pattern.DOTALL) + Pattern.compile("""\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", Pattern.DOTALL) var m = p.matcher(js) if (m.find() && m.groupCount() == 4) { val payload = m.group(1).replace("\\'", "'") @@ -72,7 +72,7 @@ class JsUnpacker(packedJS: String?) { return decoded.toString() } } catch (e: Exception) { - logError(e) +// logError(e) } return null }