Merge branch 'recloudstream:master' into master
This commit is contained in:
commit
0be3872afa
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
)
|
|
@ -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%"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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%"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 4
|
||||
version = 7
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
}
|
|
@ -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"
|
||||
)
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue