mirror of
https://github.com/recloudstream/cloudstream-extensions.git
synced 2024-08-15 03:03:54 +00:00
Switched more api calls to lessen the strain on the link generating server
This commit is contained in:
parent
039b1d98b1
commit
f491cd5d27
3 changed files with 53 additions and 16 deletions
|
@ -1,5 +1,5 @@
|
||||||
// use an integer for version numbers
|
// use an integer for version numbers
|
||||||
version = 6
|
version = 7
|
||||||
|
|
||||||
|
|
||||||
cloudstream {
|
cloudstream {
|
||||||
|
|
|
@ -17,6 +17,8 @@ import java.nio.charset.StandardCharsets
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import javax.crypto.Cipher
|
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.IvParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
@ -79,7 +81,7 @@ class SuperStream : MainAPI() {
|
||||||
length++
|
length++
|
||||||
}
|
}
|
||||||
cipher.init(
|
cipher.init(
|
||||||
1,
|
ENCRYPT_MODE,
|
||||||
SecretKeySpec(bArr, ALGORITHM),
|
SecretKeySpec(bArr, ALGORITHM),
|
||||||
IvParameterSpec(iv.toByteArray())
|
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? {
|
fun md5(str: String): String? {
|
||||||
return MD5Util.md5(str)?.let { HexDump.toHexString(it).lowercase() }
|
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 encryptedQuery = CipherUtils.encrypt(query, key, iv)!!
|
||||||
val appKeyHash = CipherUtils.md5(appKey)!!
|
val appKeyHash = CipherUtils.md5(appKey)!!
|
||||||
val newBody =
|
val newBody =
|
||||||
|
@ -173,7 +200,7 @@ class SuperStream : MainAPI() {
|
||||||
"data" to base64Body,
|
"data" to base64Body,
|
||||||
"appid" to "27",
|
"appid" to "27",
|
||||||
"platform" to "android",
|
"platform" to "android",
|
||||||
"version" to "129",
|
"version" to appVersionCode,
|
||||||
// Probably best to randomize this
|
// Probably best to randomize this
|
||||||
"medium" to "Website&token$token"
|
"medium" to "Website&token$token"
|
||||||
)
|
)
|
||||||
|
@ -182,7 +209,10 @@ class SuperStream : MainAPI() {
|
||||||
return app.post(url, headers = headers, data = data, timeout = timeout)
|
return app.post(url, headers = headers, data = data, timeout = timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend inline fun <reified T : Any> queryApiParsed(query: String, useAlternativeApi: Boolean = false): T {
|
private suspend inline fun <reified T : Any> queryApiParsed(
|
||||||
|
query: String,
|
||||||
|
useAlternativeApi: Boolean = true
|
||||||
|
): T {
|
||||||
return queryApi(query, useAlternativeApi).parsed()
|
return queryApi(query, useAlternativeApi).parsed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,16 +263,19 @@ class SuperStream : MainAPI() {
|
||||||
|
|
||||||
private val appKey = base64Decode("bW92aWVib3g=")
|
private val appKey = base64Decode("bW92aWVib3g=")
|
||||||
private val appId = base64Decode("Y29tLnRkby5zaG93Ym94")
|
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 {
|
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||||
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
||||||
val json = queryApi(
|
val data = queryApiParsed<DataJSON>(
|
||||||
"""{"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"}
|
"""{"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()
|
""".trimIndent()
|
||||||
).text
|
)
|
||||||
|
|
||||||
// Cut off the first row (featured)
|
// Cut off the first row (featured)
|
||||||
val pages = parseJson<DataJSON>(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 {
|
.mapNotNull {
|
||||||
var name = it.name
|
var name = it.name
|
||||||
if (name.isNullOrEmpty()) name = "Featured"
|
if (name.isNullOrEmpty()) name = "Featured"
|
||||||
|
@ -279,7 +312,10 @@ class SuperStream : MainAPI() {
|
||||||
fun toSearchResponse(api: MainAPI): MovieSearchResponse? {
|
fun toSearchResponse(api: MainAPI): MovieSearchResponse? {
|
||||||
return api.newMovieSearchResponse(
|
return api.newMovieSearchResponse(
|
||||||
this.title ?: "",
|
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(),
|
ResponseTypes.getResponseType(this.boxType).toTvType(),
|
||||||
false
|
false
|
||||||
) {
|
) {
|
||||||
|
@ -298,7 +334,7 @@ class SuperStream : MainAPI() {
|
||||||
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
||||||
val apiQuery =
|
val apiQuery =
|
||||||
// Originally 8 pagelimit
|
// 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<MainData>(apiQuery, true).data.mapNotNull {
|
val searchResponse = queryApiParsed<MainData>(apiQuery, true).data.mapNotNull {
|
||||||
it.toSearchResponse(this)
|
it.toSearchResponse(this)
|
||||||
}
|
}
|
||||||
|
@ -480,7 +516,7 @@ class SuperStream : MainAPI() {
|
||||||
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
||||||
if (isMovie) { // 1 = Movie
|
if (isMovie) { // 1 = Movie
|
||||||
val apiQuery =
|
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<MovieDataProp>(apiQuery)).data
|
val data = (queryApiParsed<MovieDataProp>(apiQuery)).data
|
||||||
?: throw RuntimeException("API error")
|
?: throw RuntimeException("API error")
|
||||||
|
|
||||||
|
@ -507,13 +543,13 @@ class SuperStream : MainAPI() {
|
||||||
}
|
}
|
||||||
} else { // 2 Series
|
} else { // 2 Series
|
||||||
val apiQuery =
|
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<SeriesDataProp>(apiQuery)).data
|
val data = (queryApiParsed<SeriesDataProp>(apiQuery)).data
|
||||||
?: throw RuntimeException("API error")
|
?: throw RuntimeException("API error")
|
||||||
|
|
||||||
val episodes = data.season.mapNotNull {
|
val episodes = data.season.mapNotNull {
|
||||||
val seasonQuery =
|
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<SeriesSeasonProp>(seasonQuery)).data
|
(queryApiParsed<SeriesSeasonProp>(seasonQuery)).data
|
||||||
}.flatten()
|
}.flatten()
|
||||||
|
|
||||||
|
@ -655,6 +691,7 @@ class SuperStream : MainAPI() {
|
||||||
val parsed = parseJson<LinkData>(data)
|
val parsed = parseJson<LinkData>(data)
|
||||||
|
|
||||||
// No childmode when getting links
|
// No childmode when getting links
|
||||||
|
// New api does not return video links :(
|
||||||
val query = if (parsed.type == ResponseTypes.Movies.value) {
|
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":""}"""
|
"""{"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 {
|
} 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":""}"""
|
"""{"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<LinkDataProp>(query)
|
val linkData = queryApiParsed<LinkDataProp>(query, false)
|
||||||
linkData.data?.list?.forEach {
|
linkData.data?.list?.forEach {
|
||||||
callback.invoke(it.toExtractorLink() ?: return@forEach)
|
callback.invoke(it.toExtractorLink() ?: return@forEach)
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ subprojects {
|
||||||
implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library
|
implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1")
|
||||||
implementation("org.jsoup:jsoup:1.13.1") // html parser
|
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
|
//run JS
|
||||||
implementation("org.mozilla:rhino:1.7.14")
|
implementation("org.mozilla:rhino:1.7.14")
|
||||||
|
|
Loading…
Reference in a new issue