diff --git a/SuperStream/build.gradle.kts b/SuperStream/build.gradle.kts index dbd9613..2880eb4 100644 --- a/SuperStream/build.gradle.kts +++ b/SuperStream/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 6 +version = 7 cloudstream { diff --git a/SuperStream/src/main/kotlin/com/lagradost/SuperStream.kt b/SuperStream/src/main/kotlin/com/lagradost/SuperStream.kt index 0318bab..2a5b80f 100644 --- a/SuperStream/src/main/kotlin/com/lagradost/SuperStream.kt +++ b/SuperStream/src/main/kotlin/com/lagradost/SuperStream.kt @@ -17,6 +17,8 @@ import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.security.NoSuchAlgorithmException import javax.crypto.Cipher +import javax.crypto.Cipher.DECRYPT_MODE +import javax.crypto.Cipher.ENCRYPT_MODE import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec import kotlin.math.roundToInt @@ -79,7 +81,7 @@ class SuperStream : MainAPI() { length++ } cipher.init( - 1, + ENCRYPT_MODE, SecretKeySpec(bArr, ALGORITHM), IvParameterSpec(iv.toByteArray()) ) @@ -91,6 +93,31 @@ class SuperStream : MainAPI() { } } + // Useful for deobfuscation + fun decrypt(str: String, key: String, iv: String): String? { + return try { + val cipher: Cipher = Cipher.getInstance(TRANSFORMATION) + val bArr = ByteArray(24) + val bytes: ByteArray = key.toByteArray() + var length = if (bytes.size <= 24) bytes.size else 24 + System.arraycopy(bytes, 0, bArr, 0, length) + while (length < 24) { + bArr[length] = 0 + length++ + } + cipher.init( + DECRYPT_MODE, + SecretKeySpec(bArr, ALGORITHM), + IvParameterSpec(iv.toByteArray()) + ) + val inputStr = Base64.decode(str.toByteArray(), Base64.DEFAULT) + cipher.doFinal(inputStr).decodeToString() + } catch (e: Exception) { + e.printStackTrace() + null + } + } + fun md5(str: String): String? { return MD5Util.md5(str)?.let { HexDump.toHexString(it).lowercase() } } @@ -156,7 +183,7 @@ class SuperStream : MainAPI() { } } - private suspend fun queryApi(query: String, useAlternativeApi: Boolean = false): NiceResponse { + private suspend fun queryApi(query: String, useAlternativeApi: Boolean): NiceResponse { val encryptedQuery = CipherUtils.encrypt(query, key, iv)!! val appKeyHash = CipherUtils.md5(appKey)!! val newBody = @@ -173,7 +200,7 @@ class SuperStream : MainAPI() { "data" to base64Body, "appid" to "27", "platform" to "android", - "version" to "129", + "version" to appVersionCode, // Probably best to randomize this "medium" to "Website&token$token" ) @@ -182,7 +209,10 @@ class SuperStream : MainAPI() { return app.post(url, headers = headers, data = data, timeout = timeout) } - private suspend inline fun queryApiParsed(query: String, useAlternativeApi: Boolean = false): T { + private suspend inline fun queryApiParsed( + query: String, + useAlternativeApi: Boolean = true + ): T { return queryApi(query, useAlternativeApi).parsed() } @@ -233,16 +263,19 @@ class SuperStream : MainAPI() { private val appKey = base64Decode("bW92aWVib3g=") private val appId = base64Decode("Y29tLnRkby5zaG93Ym94") + private val appIdSecond = base64Decode("Y29tLm1vdmllYm94cHJvLmFuZHJvaWQ=") + private val appVersion = "14.7" + private val appVersionCode = "160" override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1 - val json = queryApi( - """{"childmode":"$hideNsfw","app_version":"11.5","appid":"$appId","module":"Home_list_type_v2","channel":"Website","page":"$page","lang":"en","type":"all","pagelimit":"10","expired_date":"${getExpiryDate()}","platform":"android"} + val data = queryApiParsed( + """{"childmode":"$hideNsfw","app_version":"$appVersion","appid":"$appIdSecond","module":"Home_list_type_v5","channel":"Website","page":"$page","lang":"en","type":"all","pagelimit":"10","expired_date":"${getExpiryDate()}","platform":"android"} """.trimIndent() - ).text + ) // Cut off the first row (featured) - val pages = parseJson(json).data.let { it.subList(minOf(it.size, 1), it.size) } + val pages = data.data.let { it.subList(minOf(it.size, 1), it.size) } .mapNotNull { var name = it.name if (name.isNullOrEmpty()) name = "Featured" @@ -279,7 +312,10 @@ class SuperStream : MainAPI() { fun toSearchResponse(api: MainAPI): MovieSearchResponse? { return api.newMovieSearchResponse( this.title ?: "", - LoadData(this.id ?: this.mid ?: return null, this.boxType ?: ResponseTypes.Movies.value).toJson(), + LoadData( + this.id ?: this.mid ?: return null, + this.boxType ?: ResponseTypes.Movies.value + ).toJson(), ResponseTypes.getResponseType(this.boxType).toTvType(), false ) { @@ -298,7 +334,7 @@ class SuperStream : MainAPI() { val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1 val apiQuery = // Originally 8 pagelimit - """{"childmode":"$hideNsfw","app_version":"11.5","appid":"$appId","module":"Search3","channel":"Website","page":"1","lang":"en","type":"all","keyword":"$query","pagelimit":"20","expired_date":"${getExpiryDate()}","platform":"android"}""" + """{"childmode":"$hideNsfw","app_version":"$appVersion","appid":"$appIdSecond","module":"Search3","channel":"Website","page":"1","lang":"en","type":"all","keyword":"$query","pagelimit":"20","expired_date":"${getExpiryDate()}","platform":"android"}""" val searchResponse = queryApiParsed(apiQuery, true).data.mapNotNull { it.toSearchResponse(this) } @@ -480,7 +516,7 @@ class SuperStream : MainAPI() { val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1 if (isMovie) { // 1 = Movie val apiQuery = - """{"childmode":"$hideNsfw","uid":"","app_version":"11.5","appid":"$appId","module":"Movie_detail","channel":"Website","mid":"${loadData.id}","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","oss":"","group":""}""" + """{"childmode":"$hideNsfw","uid":"","app_version":"$appVersion","appid":"$appIdSecond","module":"Movie_detail","channel":"Website","mid":"${loadData.id}","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","oss":"","group":""}""" val data = (queryApiParsed(apiQuery)).data ?: throw RuntimeException("API error") @@ -507,13 +543,13 @@ class SuperStream : MainAPI() { } } else { // 2 Series val apiQuery = - """{"childmode":"$hideNsfw","uid":"","app_version":"11.5","appid":"$appId","module":"TV_detail_1","display_all":"1","channel":"Website","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","tid":"${loadData.id}"}""" + """{"childmode":"$hideNsfw","uid":"","app_version":"$appVersion","appid":"$appIdSecond","module":"TV_detail_1","display_all":"1","channel":"Website","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","tid":"${loadData.id}"}""" val data = (queryApiParsed(apiQuery)).data ?: throw RuntimeException("API error") val episodes = data.season.mapNotNull { val seasonQuery = - """{"childmode":"$hideNsfw","app_version":"11.5","year":"0","appid":"$appId","module":"TV_episode","display_all":"1","channel":"Website","season":"$it","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","tid":"${loadData.id}"}""" + """{"childmode":"$hideNsfw","app_version":"$appVersion","year":"0","appid":"$appIdSecond","module":"TV_episode","display_all":"1","channel":"Website","season":"$it","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","tid":"${loadData.id}"}""" (queryApiParsed(seasonQuery)).data }.flatten() @@ -655,6 +691,7 @@ class SuperStream : MainAPI() { val parsed = parseJson(data) // No childmode when getting links + // New api does not return video links :( val query = if (parsed.type == ResponseTypes.Movies.value) { """{"childmode":"0","uid":"","app_version":"11.5","appid":"$appId","module":"Movie_downloadurl_v3","channel":"Website","mid":"${parsed.id}","lang":"","expired_date":"${getExpiryDate()}","platform":"android","oss":"1","group":""}""" } else { @@ -663,7 +700,7 @@ class SuperStream : MainAPI() { """{"childmode":"0","app_version":"11.5","module":"TV_downloadurl_v3","channel":"Website","episode":"$episode","expired_date":"${getExpiryDate()}","platform":"android","tid":"${parsed.id}","oss":"1","uid":"","appid":"$appId","season":"$season","lang":"en","group":""}""" } - val linkData = queryApiParsed(query) + val linkData = queryApiParsed(query, false) linkData.data?.list?.forEach { callback.invoke(it.toExtractorLink() ?: return@forEach) } diff --git a/build.gradle.kts b/build.gradle.kts index 7d6e19e..31f6e88 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,7 +80,7 @@ subprojects { implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") implementation("org.jsoup:jsoup:1.13.1") // html parser - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") // html parser + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") // delay() //run JS implementation("org.mozilla:rhino:1.7.14")