diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 00000000..e96534fb --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index ebec8317..001ee701 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -73,11 +73,6 @@ data class Link( val referer: String?, ) -interface LinkExtractor { - val linkStart: String // THIS IS USED TO AUTO-EXTRACT LINKS FROM URL - fun extract(link: String, referer: String): ArrayList -} - enum class ShowStatus { Completed, Ongoing, diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt new file mode 100644 index 00000000..be0e33e8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -0,0 +1,58 @@ +package com.lagradost.cloudstream3.utils + +import com.lagradost.cloudstream3.utils.extractors.MixDrop +import com.lagradost.cloudstream3.utils.extractors.Mp4Upload +import com.lagradost.cloudstream3.utils.extractors.Shiro +import com.lagradost.cloudstream3.cloudstream3.extractors.StreamTape +import com.lagradost.cloudstream3.utils.extractors.XStreamCdn + +data class ExtractorLink( + val name: String, + val url: String, + val referer: String, + val quality: Int, + val isM3u8: Boolean = false, +) + +enum class Qualities(var value: Int) { + Unknown(0), + SD(-1), // 360p - 480p + HD(1), // 720p + FullHd(2), // 1080p + UHD(3) // 4k +} + +private val packedRegex = Regex("""eval\(function\(p,a,c,k,e,.*\)\)""") +fun getPacked(string: String): String? { + return packedRegex.find(string)?.value +} + +fun getAndUnpack(string: String): String? { + val packedText = getPacked(string) + return JsUnpacker(packedText).unpack() +} + +val APIS: Array = arrayOf( + //AllProvider(), + Shiro(), + Mp4Upload(), + StreamTape(), + MixDrop(), + XStreamCdn() +) + +fun httpsify(url: String): String { + return if (url.startsWith("//")) "https:$url" else url +} + +abstract class ExtractorApi { + abstract val name: String + abstract val mainUrl: String + abstract val requiresReferer: Boolean + + abstract fun getUrl(url: String, referer: String? = null): List? + + open fun getExtractorUrl(id: String): String{ + return id + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/JsUnpacker.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/JsUnpacker.kt new file mode 100644 index 00000000..174f1971 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/JsUnpacker.kt @@ -0,0 +1,125 @@ +package com.lagradost.cloudstream3.utils + +import java.lang.Exception +import java.lang.StringBuilder +import java.util.HashMap +import java.util.regex.Pattern +import kotlin.math.pow + +// https://github.com/cylonu87/JsUnpacker +class JsUnpacker(packedJS: String?) { + private var packedJS: String? = null + + /** + * Detects whether the javascript is P.A.C.K.E.R. coded. + * + * @return true if it's P.A.C.K.E.R. coded. + */ + fun detect(): Boolean { + val js = packedJS!!.replace(" ", "") + val p = Pattern.compile("eval\\(function\\(p,a,c,k,e,[rd]") + val m = p.matcher(js) + return m.find() + } + + /** + * Unpack the javascript + * + * @return the javascript unpacked or null. + */ + fun unpack(): String? { + val js = packedJS + try { + var p = + 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("\\'", "'") + val radixStr = m.group(2) + val countStr = m.group(3) + val symtab = m.group(4).split("\\|".toRegex()).toTypedArray() + var radix = 36 + var count = 0 + try { + radix = radixStr.toInt() + } catch (e: Exception) { + } + try { + count = countStr.toInt() + } catch (e: Exception) { + } + if (symtab.size != count) { + throw Exception("Unknown p.a.c.k.e.r. encoding") + } + val unbase = Unbase(radix) + p = Pattern.compile("\\b\\w+\\b") + m = p.matcher(payload) + val decoded = StringBuilder(payload) + var replaceOffset = 0 + while (m.find()) { + val word = m.group(0) + val x = unbase.unbase(word) + var value: String? = null + if (x < symtab.size) { + value = symtab[x] + } + if (value != null && value.isNotEmpty()) { + decoded.replace(m.start() + replaceOffset, m.end() + replaceOffset, value) + replaceOffset += value.length - word.length + } + } + return decoded.toString() + } + } catch (e: Exception) { + e.printStackTrace() + } + return null + } + private inner class Unbase(private val radix: Int) { + private val ALPHABET_62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + private val ALPHABET_95 = + " !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + private var alphabet: String? = null + private var dictionary: HashMap? = null + fun unbase(str: String): Int { + var ret = 0 + if (alphabet == null) { + ret = str.toInt(radix) + } else { + val tmp = StringBuilder(str).reverse().toString() + for (i in tmp.indices) { + ret += (radix.toDouble().pow(i.toDouble()) * dictionary!![tmp.substring(i, i + 1)]!!).toInt() + } + } + return ret + } + init { + if (radix > 36) { + when { + radix < 62 -> { + alphabet = ALPHABET_62.substring(0, radix) + } + radix in 63..94 -> { + alphabet = ALPHABET_95.substring(0, radix) + } + radix == 62 -> { + alphabet = ALPHABET_62 + } + radix == 95 -> { + alphabet = ALPHABET_95 + } + } + dictionary = HashMap(95) + for (i in 0 until alphabet!!.length) { + dictionary!![alphabet!!.substring(i, i + 1)] = i + } + } + } + } + /** + * @param packedJS javascript P.A.C.K.E.R. coded. + */ + init { + this.packedJS = packedJS + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MixDrop.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MixDrop.kt new file mode 100644 index 00000000..9340e042 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MixDrop.kt @@ -0,0 +1,36 @@ +package com.lagradost.cloudstream3.utils.extractors + +import com.lagradost.cloudstream3.utils.* + +class MixDrop : ExtractorApi() { + override val name: String = "MixDrop" + override val mainUrl: String = "https://mixdrop.co" + private val srcRegex = Regex("""wurl.*?=.*?"(.*?)";""") + override val requiresReferer = true + + override fun getExtractorUrl(id: String): String { + return "$mainUrl/e/$id" + } + + override fun getUrl(url: String, referer: String?): List? { + try { + with(khttp.get(url)) { + getAndUnpack(this.text)?.let { unpackedText -> + srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> + return listOf( + ExtractorLink( + name, + httpsify(link), + url, + Qualities.Unknown.value, + ) + ) + } + } + } + return null + } catch (e: Exception) { + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Mp4Upload.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Mp4Upload.kt new file mode 100644 index 00000000..f7f7ae99 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Mp4Upload.kt @@ -0,0 +1,32 @@ +package com.lagradost.cloudstream3.utils.extractors + +import com.lagradost.cloudstream3.utils.* + +class Mp4Upload : ExtractorApi() { + override val name: String = "Mp4Upload" + override val mainUrl: String = "https://www.mp4upload.com" + private val srcRegex = Regex("""player\.src\("(.*?)"""") + override val requiresReferer = true + + override fun getUrl(url: String, referer: String?): List? { + try { + with(khttp.get(url)) { + getAndUnpack(this.text)?.let { unpackedText -> + srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> + return listOf( + ExtractorLink( + name, + link, + url, + Qualities.Unknown.value, + ) + ) + } + } + } + return null + } catch (e: Exception) { + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MultiQuality.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MultiQuality.kt new file mode 100644 index 00000000..93c2b603 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MultiQuality.kt @@ -0,0 +1,69 @@ +package com.lagradost.cloudstream3.utils.extractors + +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities + +class MultiQuality : ExtractorApi() { + override val name: String = "MultiQuality" + override val mainUrl: String = "https://gogo-play.net" + private val sourceRegex = Regex("""file:\s*'(.*?)',label:\s*'(.*?)'""") + private val m3u8Regex = Regex(""".*?(\d*).m3u8""") + private val urlRegex = Regex("""(.*?)([^/]+$)""") + override val requiresReferer = false + + override fun getExtractorUrl(id: String): String { + return "$mainUrl/loadserver.php?id=$id" + } + + private fun getQuality(string: String): Int { + return when (string) { + "360" -> Qualities.SD.value + "480" -> Qualities.SD.value + "720" -> Qualities.HD.value + "1080" -> Qualities.FullHd.value + else -> Qualities.Unknown.value + } + } + + override fun getUrl(url: String, referer: String?): List? { + try { + val extractedLinksList: MutableList = mutableListOf() + with(khttp.get(url)) { + sourceRegex.findAll(this.text).forEach { sourceMatch -> + val extractedUrl = sourceMatch.groupValues[1] + // Trusting this isn't mp4, may fuck up stuff + if (extractedUrl.endsWith(".m3u8")) { + with(khttp.get(extractedUrl)) { + m3u8Regex.findAll(this.text).forEach { match -> + extractedLinksList.add( + ExtractorLink( + "$name ${match.groupValues[1]}p", + urlRegex.find(this.url)!!.groupValues[1] + match.groupValues[0], + url, + getQuality(match.groupValues[1]), + isM3u8 = true + ) + ) + } + + } + } else if (extractedUrl.endsWith(".mp4")) { + extractedLinksList.add( + ExtractorLink( + "$name ${sourceMatch.groupValues[2]}", + extractedUrl, + url.replace(" ", "%20"), + Qualities.Unknown.value, + ) + ) + } + } + return extractedLinksList + } + } catch (e: Exception) { + + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Shiro.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Shiro.kt new file mode 100644 index 00000000..903e017f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Shiro.kt @@ -0,0 +1,33 @@ +package com.lagradost.cloudstream3.utils.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, + it.replace(" ", "%20"), + "https://cherry.subsplea.se/", + // UHD to give top priority + Qualities.UHD.value + ) + ) + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/StreamTape.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/StreamTape.kt new file mode 100644 index 00000000..a8c15955 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/StreamTape.kt @@ -0,0 +1,35 @@ +package com.lagradost.cloudstream3.cloudstream3.extractors + +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities + +class StreamTape : ExtractorApi() { + override val name: String = "StreamTape" + override val mainUrl: String = "https://streamtape.com" + override val requiresReferer = true + + // Because they add concatenation to fuck up scrapers + private val linkRegex = + Regex("""(i(|" \+ ')d(|" \+ ')=.*?&(|" \+ ')e(|" \+ ')x(|" \+ ')p(|" \+ ')i(|" \+ ')r(|" \+ ')e(|" \+ ')s(|" \+ ')=.*?&(|" \+ ')i(|" \+ ')p(|" \+ ')=.*?&(|" \+ ')t(|" \+ ')o(|" \+ ')k(|" \+ ')e(|" \+ ')n(|" \+ ')=.*)'""") + + override fun getUrl(url: String, referer: String?): List? { + try { + with(khttp.get(url)) { + linkRegex.find(this.text)?.let { + val extractedUrl = "https://streamtape.com/get_video?${it.groupValues[1]}".replace("""" + '""", "") + return listOf( + ExtractorLink( + name, + extractedUrl, + url, + Qualities.Unknown.value, + ) + ) + } + } + } catch (e: Exception) { + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Vidstream.kt new file mode 100644 index 00000000..2a3abebc --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Vidstream.kt @@ -0,0 +1,59 @@ +package com.lagradost.cloudstream3.utils.extractors + +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.APIS +import com.lagradost.cloudstream3.utils.extractors.MultiQuality +import com.lagradost.cloudstream3.utils.extractors.Shiro +import org.jsoup.Jsoup + +class Vidstream { + val name: String = "Vidstream" + private val mainUrl: String = "https://gogo-stream.com" + + private fun getExtractorUrl(id: String): String { + return "$mainUrl/streaming.php?id=$id" + } + + // https://gogo-stream.com/streaming.php?id=MTE3NDg5 + fun getUrl(id: String, isCasting: Boolean = false): List { + try { + val url = getExtractorUrl(id) + with(khttp.get(url)) { + val document = Jsoup.parse(this.text) + val primaryLinks = document.select("ul.list-server-items > li.linkserver") + val extractedLinksList: MutableList = mutableListOf() + + // --- Shiro --- + val shiroUrl = Shiro().getExtractorUrl(id) + val shiroSource = Shiro().getUrl(shiroUrl) + shiroSource?.forEach { extractedLinksList.add(it) } + // --- MultiQuality --- + val multiQualityUrl = MultiQuality().getExtractorUrl(id) + val multiQualitySource = MultiQuality().getUrl(multiQualityUrl) + multiQualitySource?.forEach { extractedLinksList.add(it) } + // -------------------- + + // All vidstream links passed to extractors + primaryLinks.forEach { element -> + val link = element.attr("data-video") + //val name = element.text() + + // Matches vidstream links with extractors + APIS.filter { !it.requiresReferer || !isCasting}.forEach { api -> + if (link.startsWith(api.mainUrl)) { + val extractedLinks = api.getUrl(link, url) + if (extractedLinks?.isNotEmpty() == true) { + extractedLinks.forEach { + extractedLinksList.add(it) + } + } + } + } + } + return extractedLinksList + } + } catch (e: Exception) { + return listOf() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/XStreamCdn.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/XStreamCdn.kt new file mode 100644 index 00000000..f0af4ce5 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/XStreamCdn.kt @@ -0,0 +1,70 @@ +package com.lagradost.cloudstream3.utils.extractors + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.module.kotlin.readValue +import com.lagradost.cloudstream3.mapper +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities + +class XStreamCdn : ExtractorApi() { + override val name: String = "XStreamCdn" + override val mainUrl: String = "https://fcdn.stream" + override val requiresReferer = true + + private data class ResponseData( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String, + //val type: String // Mp4 + ) + + private data class ResponseJson( + @JsonProperty("success") val success: Boolean, + @JsonProperty("data") val data: List? + ) + + override fun getExtractorUrl(id: String): String { + return "$mainUrl/api/source/$id" + } + + private fun getQuality(string: String): Int { + return when (string) { + "360p" -> Qualities.SD.value + "480p" -> Qualities.SD.value + "720p" -> Qualities.HD.value + "1080p" -> Qualities.FullHd.value + else -> Qualities.Unknown.value + } + } + + override fun getUrl(url: String, referer: String?): List? { + try { + val headers = mapOf( + "Referer" to url, + "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0", + ) + val newUrl = url.replace("$mainUrl/v/", "$mainUrl/api/source/") + val extractedLinksList: MutableList = mutableListOf() + with(khttp.post(newUrl, headers = headers)) { + mapper.readValue(this.text)?.let { + if (it.success && it.data != null) { + it.data.forEach { data -> + extractedLinksList.add( + ExtractorLink( + "$name ${data.label}", + data.file, + url, + getQuality(data.label), + ) + ) + } + } + } + } + return extractedLinksList + } catch (e: Exception) { + } + return null + } + +} \ No newline at end of file