Merge branch 'recloudstream:master' into master

This commit is contained in:
KillerDogeEmpire 2022-12-26 13:46:49 -08:00 committed by GitHub
commit 0be3872afa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 313 additions and 118 deletions

View File

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 3
version = 4
cloudstream {

View File

@ -3,7 +3,9 @@ package com.lagradost
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.ui.settings.SettingsProviders
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
@ -54,9 +56,14 @@ class AllAnimeProvider : MainAPI() {
@JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes?,
@JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AvailableEpisodesDetail?,
@JsonProperty("studios") val studios: List<String>?,
@JsonProperty("genres") val genres: List<String>?,
@JsonProperty("averageScore") val averageScore: Int?,
@JsonProperty("description") val description: String?,
@JsonProperty("status") val status: String?,
)
@JsonProperty("banner") val banner : String?,
@JsonProperty("episodeDuration") val episodeDuration : Int?,
@JsonProperty("prevideos") val prevideos : List<String> = emptyList(),
)
private data class AvailableEpisodes(
@JsonProperty("sub") val sub: Int,
@ -104,56 +111,70 @@ class AllAnimeProvider : MainAPI() {
@JsonProperty("__typename") val _typename: String? = null
)
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
val items = ArrayList<HomePageList>()
val urls = listOf(
// Pair(
// "Top Anime",
// """$mainUrl/graphql?variables={"type":"anime","size":30,"dateRange":30}&extensions={"persistedQuery":{"version":1,"sha256Hash":"276d52ba09ca48ce2b8beb3affb26d9d673b22f9d1fd4892aaa39524128bc745"}}"""
// ),
// "countryOrigin":"JP" for Japanese only
Pair(
"Recently updated",
"""$mainUrl/graphql?variables={"search":{"allowAdult":false,"allowUnknown":false},"limit":30,"page":1,"translationType":"dub","countryOrigin":"ALL"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"d2670e3e27ee109630991152c8484fce5ff5e280c523378001f9a23dc1839068"}}"""
),
private val popularTitle = "Popular"
private val recentTitle = "Recently updated"
override val mainPage = listOf(
MainPageData(
recentTitle,
"""$mainUrl/allanimeapi?variables={"search":{"sortBy":"Recent","allowAdult":${settingsForProvider.enableAdult},"allowUnknown":false},"limit":26,"page":%d,"translationType":"sub","countryOrigin":"ALL"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"9c7a8bc1e095a34f2972699e8105f7aaf9082c6e1ccd56eab99c2f1a971152c6"}}"""
),
MainPageData(
popularTitle,
"""$mainUrl/allanimeapi?variables={"type":"anime","size":30,"dateRange":1,"page":%d,"allowAdult":${settingsForProvider.enableAdult},"allowUnknown":false}&extensions={"persistedQuery":{"version":1,"sha256Hash":"6f6fe5663e3e9ea60bdfa693f878499badab83e7f18b56acdba5f8e8662002aa"}}"""
)
)
val random =
"""$mainUrl/graphql?variables={"format":"anime"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"21ac672633498a3698e8f6a93ce6c2b3722b29a216dcca93363bf012c360cd54"}}"""
val ranlink = app.get(random).text
val jsonran = parseJson<RandomMain>(ranlink)
val ranhome = jsonran.data?.queryRandomRecommendation?.map {
newAnimeSearchResponse(it.name!!, "$mainUrl/anime/${it.Id}", fix = false) {
this.posterUrl = it.thumbnail
this.otherName = it.nativeName
}
}
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
val url = request.data.format(page)
val test = app.get(url).text
items.add(HomePageList("Random", ranhome!!))
val home = when (request.name) {
recentTitle -> {
val json = parseJson<AllAnimeQuery>(test)
val results = json.data.shows.edges.filter {
// filtering in case there is an anime with 0 episodes available on the site.
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
}
urls.apmap { (HomeName, url) ->
val test = app.get(url).text
val json = parseJson<AllAnimeQuery>(test)
val home = ArrayList<SearchResponse>()
val results = json.data.shows.edges.filter {
// filtering in case there is an anime with 0 episodes available on the site.
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
}
results.map {
home.add(
results.map {
newAnimeSearchResponse(it.name, "$mainUrl/anime/${it.Id}", fix = false) {
this.posterUrl = it.thumbnail
this.year = it.airedStart?.year
this.otherName = it.englishName
addDub(it.availableEpisodes?.dub)
addSub(it.availableEpisodes?.sub)
})
}
}
}
items.add(HomePageList(HomeName, home))
popularTitle -> {
val json = parseJson<PopularQuery>(test)
val results = json.data?.queryPopular?.recommendations?.filter {
// filtering in case there is an anime with 0 episodes available on the site.
!(it.anyCard?.availableEpisodes?.raw == 0 && it.anyCard.availableEpisodes.sub == 0 && it.anyCard.availableEpisodes.dub == 0)
}
results?.mapNotNull {
newAnimeSearchResponse(
it.anyCard?.name ?: return@mapNotNull null,
"$mainUrl/anime/${it.anyCard.Id ?: it.pageStatus?.Id}",
fix = false
) {
this.posterUrl = it.anyCard.thumbnail
this.otherName = it.anyCard.englishName
addDub(it.anyCard.availableEpisodes?.dub)
addSub(it.anyCard.availableEpisodes?.sub)
}
} ?: emptyList()
}
else -> emptyList()
}
if (items.size <= 0) throw ErrorLoadingException()
return HomePageResponse(items)
return HomePageResponse(
listOf(
HomePageList(request.name, home)
), hasNext = home.isNotEmpty()
)
}
override suspend fun search(query: String): List<SearchResponse> {
@ -210,6 +231,7 @@ class AllAnimeProvider : MainAPI() {
rhino.evaluateString(scope, js, "JavaScript", 1, null)
val jsEval = scope.get("returnValue", scope) ?: return null
val showData = parseJson<Edges>(jsEval as String)
val title = showData.name
@ -241,7 +263,7 @@ class AllAnimeProvider : MainAPI() {
Pair(Actor(name, img), role)
}
// bruh, they use graphql
// bruh, they use graphql and bruh it is fucked
//val recommendations = soup.select("#suggesction > div > div.p > .swipercard")?.mapNotNull {
// val recTitle = it?.selectFirst(".showname > a") ?: return@mapNotNull null
// val recName = recTitle.text() ?: return@mapNotNull null
@ -252,7 +274,12 @@ class AllAnimeProvider : MainAPI() {
return newAnimeLoadResponse(title, url, TvType.Anime) {
posterUrl = poster
backgroundPosterUrl = showData.banner
rating = showData.averageScore?.times(100)
tags = showData.genres
year = showData.airedStart?.year
duration = showData.episodeDuration?.div(60_000)
addTrailer(showData.prevideos.filter { it.isNotBlank() }.map { "https://www.youtube.com/watch?v=$it" })
addEpisodes(DubStatus.Subbed, episodes.first)
addEpisodes(DubStatus.Dubbed, episodes.second)

View File

@ -0,0 +1,68 @@
package com.lagradost
import com.fasterxml.jackson.annotation.JsonProperty
data class PopularQuery(
@JsonProperty("data") val data: Data? = Data()
)
data class AvailableEpisodes(
@JsonProperty("sub") val sub: Int? = null,
@JsonProperty("dub") val dub: Int? = null,
@JsonProperty("raw") val raw: Int? = null
)
data class Sub(
@JsonProperty("hour") val hour: Int? = null,
@JsonProperty("minute") val minute: Int? = null,
@JsonProperty("year") val year: Int? = null,
@JsonProperty("month") val month: Int? = null,
@JsonProperty("date") val date: Int? = null
)
data class LastEpisodeDate(
@JsonProperty("dub") val dub: Sub? = Sub(),
@JsonProperty("sub") val sub: Sub? = Sub(),
@JsonProperty("raw") val raw: Sub? = Sub()
)
data class AnyCard(
@JsonProperty("_id") val Id: String? = null,
@JsonProperty("name") val name: String? = null,
@JsonProperty("englishName") val englishName: String? = null,
@JsonProperty("nativeName") val nativeName: String? = null,
@JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes? = AvailableEpisodes(),
@JsonProperty("score") val score: Double? = null,
@JsonProperty("lastEpisodeDate") val lastEpisodeDate: LastEpisodeDate? = LastEpisodeDate(),
@JsonProperty("thumbnail") val thumbnail: String? = null,
@JsonProperty("lastChapterDate") val lastChapterDate: String? = null,
@JsonProperty("availableChapters") val availableChapters: String? = null,
@JsonProperty("__typename") val _typename: String? = null
)
data class PageStatus(
@JsonProperty("_id") val Id: String? = null,
@JsonProperty("views") val views: String? = null,
@JsonProperty("showId") val showId: String? = null,
@JsonProperty("rangeViews") val rangeViews: String? = null,
@JsonProperty("isManga") val isManga: Boolean? = null,
@JsonProperty("__typename") val _typename: String? = null
)
data class Recommendations(
@JsonProperty("anyCard") val anyCard: AnyCard? = AnyCard(),
@JsonProperty("pageStatus") val pageStatus: PageStatus? = PageStatus(),
@JsonProperty("__typename") val _typename: String? = null
)
data class QueryPopular(
@JsonProperty("total") val total: Int? = null,
@JsonProperty("recommendations") val recommendations: ArrayList<Recommendations> = arrayListOf(),
@JsonProperty("__typename") val _typename: String? = null
)
data class Data(
@JsonProperty("queryPopular") val queryPopular: QueryPopular? = QueryPopular()
)

View File

@ -16,7 +16,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
status = 1 // will be 3 if unspecified
status = 0 // will be 3 if unspecified
tvTypes = listOf("AnimeMovie", "Anime", "OVA")
iconUrl = "https://www.google.com/s2/favicons?domain=crunchyroll.com&sz=%size%"
}
}

View File

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 9
version = 11
cloudstream {
@ -25,4 +25,4 @@ cloudstream {
)
iconUrl = "https://www.google.com/s2/favicons?domain=www.2embed.to&sz=%size%"
}
}

View File

@ -458,11 +458,8 @@ open class SflixProvider : MainAPI() {
}
suspend fun getKey(): String? {
data class KeyObject(
@JsonProperty("key") val key: String? = null
)
return app.get("https://raw.githubusercontent.com/BlipBlob/blabflow/main/keys.json")
.parsed<KeyObject>().key
return app.get("https://raw.githubusercontent.com/consumet/rapidclown/rabbitstream/key.txt")
.text
}
/**

View File

@ -1,6 +1,7 @@
package com.lagradost
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.SflixProvider.Companion.extractRabbitStream
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
@ -345,21 +346,21 @@ class ZoroProvider : MainAPI() {
val extractorLink = app.get(
link,
).parsed<RapidCloudResponse>().link
// val hasLoadedExtractorLink =
val hasLoadedExtractorLink =
loadExtractor(extractorLink, "https://rapid-cloud.ru/", subtitleCallback, callback)
// if (!hasLoadedExtractorLink) {
// extractRabbitStream(
// extractorLink,
// subtitleCallback,
// // Blacklist VidCloud for now
// { videoLink -> if (!videoLink.url.contains("betterstream")) callback(videoLink) },
//// false,
//// extractorData,
//// decryptKey = getKey()
// ) { sourceName ->
// sourceName + " - ${it.first}"
// }
// }
if (!hasLoadedExtractorLink) {
extractRabbitStream(
extractorLink,
subtitleCallback,
// Blacklist VidCloud for now
{ videoLink -> if (!videoLink.url.contains("betterstream")) callback(videoLink) },
false,
null,
decryptKey = getKey()
) { sourceName ->
sourceName + " - ${it.first}"
}
}
}
return true

View File

@ -13,6 +13,8 @@ import com.lagradost.cloudstream3.utils.loadExtractor
import org.json.JSONObject
import java.net.URLEncoder
private const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
class StremioProvider : MainAPI() {
override var mainUrl = "https://stremio.github.io/stremio-static-addon-example"
override var name = "Stremio example"
@ -53,7 +55,6 @@ class StremioProvider : MainAPI() {
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
Log.i("Stremio", data)
val res = tryParseJson<StreamsResponse>(app.get(data).text) ?: return false
res.streams.forEach { stream ->
stream.runCallback(subtitleCallback, callback)
@ -164,7 +165,9 @@ class StremioProvider : MainAPI() {
val url: String?,
val ytId: String?,
val externalUrl: String?,
val behaviorHints: JSONObject?
val behaviorHints: JSONObject?,
val infoHash: String?,
val sources: List<String> = emptyList()
) {
suspend fun runCallback(subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
if (url != null) {
@ -205,6 +208,33 @@ class StremioProvider : MainAPI() {
if (externalUrl != null) {
loadExtractor(externalUrl, subtitleCallback, callback)
}
if (infoHash != null) {
val resp = app.get(TRACKER_LIST_URL).text
val otherTrackers = resp
.split("\n")
.filterIndexed{i, s -> i%2==0}
.filter{s -> !s.isNullOrEmpty()}
.map{it -> "&tr=$it"}
.joinToString("")
val sourceTrackers = sources
.filter{it->it.startsWith("tracker:")}
.map{it->it.removePrefix("tracker:")}
.filter{s -> !s.isNullOrEmpty()}
.map{it -> "&tr=$it"}
.joinToString("")
val magnet = "magnet:?xt=urn:btih:${infoHash}${sourceTrackers}${otherTrackers}"
callback.invoke(
ExtractorLink(
name ?: "",
title ?: name ?: "",
magnet,
"",
Qualities.Unknown.value
)
)
}
}
}

View File

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 4
version = 7
cloudstream {

View File

@ -17,11 +17,14 @@ 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
class SuperStream : MainAPI() {
private val timeout = 120L
override var name = "SuperStream"
override val hasMainPage = true
override val hasChromecastSupport = true
@ -78,7 +81,7 @@ class SuperStream : MainAPI() {
length++
}
cipher.init(
1,
ENCRYPT_MODE,
SecretKeySpec(bArr, ALGORITHM),
IvParameterSpec(iv.toByteArray())
)
@ -90,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() }
}
@ -155,7 +183,7 @@ class SuperStream : MainAPI() {
}
}
private suspend fun queryApi(query: String): NiceResponse {
private suspend fun queryApi(query: String, useAlternativeApi: Boolean): NiceResponse {
val encryptedQuery = CipherUtils.encrypt(query, key, iv)!!
val appKeyHash = CipherUtils.md5(appKey)!!
val newBody =
@ -172,16 +200,20 @@ 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"
)
return app.post(apiUrl, headers = headers, data = data)
val url = if (useAlternativeApi) secondApiUrl else apiUrl
return app.post(url, headers = headers, data = data, timeout = timeout)
}
private suspend inline fun <reified T : Any> queryApiParsed(query: String): T {
return queryApi(query).parsed()
private suspend inline fun <reified T : Any> queryApiParsed(
query: String,
useAlternativeApi: Boolean = true
): T {
return queryApi(query, useAlternativeApi).parsed()
}
private fun getExpiryDate(): Long {
@ -219,21 +251,31 @@ class SuperStream : MainAPI() {
// Free Tibet, The Tienanmen Square protests of 1989
private val iv = base64Decode("d0VpcGhUbiE=")
private val key = base64Decode("MTIzZDZjZWRmNjI2ZHk1NDIzM2FhMXc2")
private val ip = base64Decode("aHR0cHM6Ly8xNTIuMzIuMTQ5LjE2MA==")
private val apiUrl =
"$ip${base64Decode("L2FwaS9hcGlfY2xpZW50L2luZGV4Lw==")}"
// Another url because the first one sucks at searching
// This one was revealed to me in a dream
private val secondApiUrl =
base64Decode("aHR0cHM6Ly9tYnBhcGkuc2hlZ3UubmV0L2FwaS9hcGlfY2xpZW50L2luZGV4Lw==")
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<DataJSON>(
"""{"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<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 {
var name = it.name
if (name.isNullOrEmpty()) name = "Featured"
@ -270,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
) {
@ -289,8 +334,8 @@ 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"}"""
val searchResponse = parseJson<MainData>(queryApi(apiQuery).text).data.mapNotNull {
"""{"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 {
it.toSearchResponse(this)
}
return searchResponse
@ -471,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<MovieDataProp>(apiQuery)).data
?: throw RuntimeException("API error")
@ -498,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<SeriesDataProp>(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<SeriesSeasonProp>(seasonQuery)).data
}.flatten()
@ -646,6 +691,7 @@ class SuperStream : MainAPI() {
val parsed = parseJson<LinkData>(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 {
@ -654,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<LinkDataProp>(query)
val linkData = queryApiParsed<LinkDataProp>(query, false)
linkData.data?.list?.forEach {
callback.invoke(it.toExtractorLink() ?: return@forEach)
}

View File

@ -0,0 +1,36 @@
package com.lagradost
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import kotlinx.coroutines.delay
import org.json.JSONArray
import org.json.JSONObject
import android.util.Log
public object CaptchaSolver {
suspend fun predictFace(url: String): String? {
val img = "data:image/jpeg;base64," + base64Encode(app.get(url).body.bytes())
val reqData = HFRequest(listOf(img)).toJson()
val res = app.post("https://yuqi-gender-classifier.hf.space/api/queue/push/", json = reqData).text
val request = tryParseJson<JSONObject>(res)
for (i in 1..5) {
delay(500L)
val document = app.post("https://yuqi-gender-classifier.hf.space/api/queue/status/", json=request?.toJson()).text
val status = tryParseJson<JSONObject>(document)
if (status?.get("status") != "COMPLETE") continue
return (((status.get("data") as? JSONObject?)
?.get("data") as? JSONArray?)
?.get(0) as? JSONObject?)
?.get("label") as String?
}
return null
}
private data class HFRequest(
val data: List<String>,
val action: String = "predict",
val fn_index: Int = 0,
val session_hash: String = "aaaaaaaaaaa"
)
}

View File

@ -6,9 +6,8 @@ import com.lagradost.cloudstream3.metaproviders.TmdbProvider
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor
import kotlinx.coroutines.delay
import org.json.JSONArray
import org.json.JSONObject
import okhttp3.FormBody
import android.util.Log
class SuperembedProvider : TmdbProvider() {
override var mainUrl = "https://seapi.link"
@ -54,40 +53,31 @@ class SuperembedProvider : TmdbProvider() {
val url: String
) {
suspend fun getIframeContents(): String? {
val document = app.get(url)
var document = app.get(url)
for (i in 1..5) {
if ("captcha-message" in document.text) {
val soup = document.document
val prompt = soup.selectFirst(".captcha-message")?.text() ?: continue
val captchaId = soup.selectFirst("input[name=\"captcha_id\"]")?.attr("value") ?: continue
val promptGender = if ("female" in prompt) "female" else "male"
val checkboxes = soup.select(".captcha-checkbox").mapNotNull { it ->
val img = it.selectFirst("img")?.attr("src") ?: return@mapNotNull null
val gender = CaptchaSolver.predictFace("https://streamembed.net${img}") ?: return@mapNotNull null
if (gender != promptGender) return@mapNotNull null
return@mapNotNull it.selectFirst("input")?.attr("value")
}
val formData = FormBody.Builder().apply {
add("captcha_id", captchaId)
checkboxes.forEach { check ->
add("captcha_answer[]", check)
}
}.build()
document = app.post(url, requestBody=formData)
} else { break }
}
val regex = "<iframe[^+]+\\+(?:window\\.)?atob\\(['\"]([-A-Za-z0-9+/=]+)".toRegex()
val encoded = regex.find(document.text)?.groupValues?.get(1) ?: return null
return base64Decode(encoded)
}
}
/*
private object CaptchaSolver {
private enum class Gender { Female, Male }
private suspend fun predictFace(url: String): Gender? {
val img = "data:image/jpeg;base64," + base64Encode(app.get(url).body.bytes())
val res = app.post("https://hf.space/embed/njgroene/age-gender-profilepic/api/queue/push/ HTTP/1.1", json = HFRequest(
listOf(img))).text
val request = tryParseJson<JSONObject>(res)
for (i in 1..5) {
delay(500L)
val document = app.post("https://hf.space/embed/njgroene/age-gender-profilepic/api/queue/status/", json=request).text
val status = tryParseJson<JSONObject>(document)
if (status?.get("status") != "COMPLETE") continue
val pred = (((status.get("data") as? JSONObject?)
?.get("data") as? JSONArray?)
?.get(0) as? String?) ?: return null
return if ("Male" in pred) Gender.Male
else if ("Female" in pred) Gender.Female
else null
}
}
private data class HFRequest(
val data: List<String>,
val action: String = "predict",
val fn_index: Int = 0,
val session_hash: String = "aaaaaaaaaaa"
)
}*/
}

View File

@ -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")