mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
[Sora] added Baymovies
This commit is contained in:
parent
1e8dfcc789
commit
93cf4840a1
4 changed files with 347 additions and 9 deletions
133
SoraStream/src/main/kotlin/com/hexated/CryptoAES.kt
Normal file
133
SoraStream/src/main/kotlin/com/hexated/CryptoAES.kt
Normal file
|
@ -0,0 +1,133 @@
|
|||
package com.hexated
|
||||
|
||||
import android.util.Base64
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.math.min
|
||||
|
||||
// code found on https://stackoverflow.com/a/63701411
|
||||
|
||||
/**
|
||||
* Conforming with CryptoJS AES method
|
||||
*/
|
||||
// see https://gist.github.com/thackerronak/554c985c3001b16810af5fc0eb5c358f
|
||||
@Suppress("unused", "FunctionName", "SameParameterValue")
|
||||
object CryptoAES {
|
||||
|
||||
private const val KEY_SIZE = 256
|
||||
private const val IV_SIZE = 128
|
||||
private const val HASH_CIPHER = "AES/CBC/PKCS5Padding"
|
||||
private const val AES = "AES"
|
||||
private const val KDF_DIGEST = "MD5"
|
||||
|
||||
// Seriously crypto-js, what's wrong with you?
|
||||
private const val APPEND = "Salted__"
|
||||
|
||||
/**
|
||||
* Encrypt
|
||||
* @param password passphrase
|
||||
* @param plainText plain string
|
||||
*/
|
||||
fun encrypt(password: String, plainText: String): String {
|
||||
val saltBytes = generateSalt(8)
|
||||
val key = ByteArray(KEY_SIZE / 8)
|
||||
val iv = ByteArray(IV_SIZE / 8)
|
||||
EvpKDF(password.toByteArray(), KEY_SIZE, IV_SIZE, saltBytes, key, iv)
|
||||
val keyS = SecretKeySpec(key, AES)
|
||||
val cipher = Cipher.getInstance(HASH_CIPHER)
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyS, ivSpec)
|
||||
val cipherText = cipher.doFinal(plainText.toByteArray())
|
||||
// Thanks kientux for this: https://gist.github.com/kientux/bb48259c6f2133e628ad
|
||||
// Create CryptoJS-like encrypted!
|
||||
val sBytes = APPEND.toByteArray()
|
||||
val b = ByteArray(sBytes.size + saltBytes.size + cipherText.size)
|
||||
System.arraycopy(sBytes, 0, b, 0, sBytes.size)
|
||||
System.arraycopy(saltBytes, 0, b, sBytes.size, saltBytes.size)
|
||||
System.arraycopy(cipherText, 0, b, sBytes.size + saltBytes.size, cipherText.size)
|
||||
val bEncode = Base64.encode(b, Base64.NO_WRAP)
|
||||
return String(bEncode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt
|
||||
* Thanks Artjom B. for this: http://stackoverflow.com/a/29152379/4405051
|
||||
* @param password passphrase
|
||||
* @param cipherText encrypted string
|
||||
*/
|
||||
fun decrypt(password: String, cipherText: String): String {
|
||||
val ctBytes = Base64.decode(cipherText.toByteArray(), Base64.NO_WRAP)
|
||||
val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16)
|
||||
val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size)
|
||||
val key = ByteArray(KEY_SIZE / 8)
|
||||
val iv = ByteArray(IV_SIZE / 8)
|
||||
EvpKDF(password.toByteArray(), KEY_SIZE, IV_SIZE, saltBytes, key, iv)
|
||||
val cipher = Cipher.getInstance(HASH_CIPHER)
|
||||
val keyS = SecretKeySpec(key, AES)
|
||||
cipher.init(Cipher.DECRYPT_MODE, keyS, IvParameterSpec(iv))
|
||||
val plainText = cipher.doFinal(cipherTextBytes)
|
||||
return String(plainText)
|
||||
}
|
||||
|
||||
private fun EvpKDF(
|
||||
password: ByteArray,
|
||||
keySize: Int,
|
||||
ivSize: Int,
|
||||
salt: ByteArray,
|
||||
resultKey: ByteArray,
|
||||
resultIv: ByteArray
|
||||
): ByteArray {
|
||||
return EvpKDF(password, keySize, ivSize, salt, 1, KDF_DIGEST, resultKey, resultIv)
|
||||
}
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
private fun EvpKDF(
|
||||
password: ByteArray,
|
||||
keySize: Int,
|
||||
ivSize: Int,
|
||||
salt: ByteArray,
|
||||
iterations: Int,
|
||||
hashAlgorithm: String,
|
||||
resultKey: ByteArray,
|
||||
resultIv: ByteArray
|
||||
): ByteArray {
|
||||
val keySize = keySize / 32
|
||||
val ivSize = ivSize / 32
|
||||
val targetKeySize = keySize + ivSize
|
||||
val derivedBytes = ByteArray(targetKeySize * 4)
|
||||
var numberOfDerivedWords = 0
|
||||
var block: ByteArray? = null
|
||||
val hash = MessageDigest.getInstance(hashAlgorithm)
|
||||
while (numberOfDerivedWords < targetKeySize) {
|
||||
if (block != null) {
|
||||
hash.update(block)
|
||||
}
|
||||
hash.update(password)
|
||||
block = hash.digest(salt)
|
||||
hash.reset()
|
||||
// Iterations
|
||||
for (i in 1 until iterations) {
|
||||
block = hash.digest(block!!)
|
||||
hash.reset()
|
||||
}
|
||||
System.arraycopy(
|
||||
block!!, 0, derivedBytes, numberOfDerivedWords * 4,
|
||||
min(block.size, (targetKeySize - numberOfDerivedWords) * 4)
|
||||
)
|
||||
numberOfDerivedWords += block.size / 4
|
||||
}
|
||||
System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4)
|
||||
System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4)
|
||||
return derivedBytes // key + iv
|
||||
}
|
||||
|
||||
private fun generateSalt(length: Int): ByteArray {
|
||||
return ByteArray(length).apply {
|
||||
SecureRandom().nextBytes(this)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.hexated
|
||||
|
||||
import android.util.Log
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
@ -14,6 +15,7 @@ import com.lagradost.nicehttp.RequestBodyTypes
|
|||
import kotlinx.coroutines.delay
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.ByteString.Companion.encode
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
val session = Session(Requests().baseClient)
|
||||
|
@ -2022,6 +2024,135 @@ object SoraExtractor : SoraStream() {
|
|||
}
|
||||
}
|
||||
|
||||
//TODO only subs
|
||||
suspend fun invokeWatchsomuch(
|
||||
imdbId: String? = null,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
) {
|
||||
val watchSomuchAPI = "https://watchsomuch.tv"
|
||||
val id = imdbId?.removePrefix("tt")
|
||||
val epsId = app.post(
|
||||
"$watchSomuchAPI/Watch/ajMovieTorrents.aspx",
|
||||
data = mapOf(
|
||||
"index" to "0",
|
||||
"mid" to "$id",
|
||||
"wsk" to "f6ea6cde-e42b-4c26-98d3-b4fe48cdd4fb",
|
||||
"lid" to "",
|
||||
"liu" to ""
|
||||
), headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
).parsedSafe<WatchsomuchResponses>()?.movie?.torrents?.let { eps ->
|
||||
if (season == null) {
|
||||
eps.firstOrNull()?.id
|
||||
} else {
|
||||
eps.find { it.episode == episode && it.season == season }?.id
|
||||
}
|
||||
} ?: return
|
||||
|
||||
val subUrl = if (season == null) {
|
||||
"$watchSomuchAPI/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part="
|
||||
} else {
|
||||
"$watchSomuchAPI/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part=S0${season}E0${episode}"
|
||||
}
|
||||
|
||||
app.get(subUrl)
|
||||
.parsedSafe<WatchsomuchSubResponses>()?.subtitles
|
||||
?.filter { it.url?.startsWith("https") == true }
|
||||
?.map { sub ->
|
||||
Log.i("hexated", "${sub.label} => ${sub.url}")
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
sub.label ?: "",
|
||||
sub.url ?: return@map null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
suspend fun invokeBaymovies(
|
||||
title: String? = null,
|
||||
year: Int? = null,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
) {
|
||||
val key = base64DecodeAPI("ZW0=c3Q=c3k=b28=YWQ=Ymg=")
|
||||
val headers = mapOf(
|
||||
"Referer" to "$baymovies/",
|
||||
"Origin" to baymovies,
|
||||
"cf_cache_token" to "UKsVpQqBMxB56gBfhYKbfCVkRIXMh42pk6G4DdkXXoVh7j4BjV"
|
||||
)
|
||||
|
||||
val titleSlug = title.fixTitle()?.replace("-", ".") ?: return
|
||||
val (episodeSlug, seasonSlug) = if (season == null) {
|
||||
listOf("", "")
|
||||
} else {
|
||||
listOf(
|
||||
if (episode!! < 10) "0$episode" else episode,
|
||||
if (season < 10) "0$season" else season
|
||||
)
|
||||
}
|
||||
|
||||
val query = if (season == null) {
|
||||
"$title $year"
|
||||
} else {
|
||||
"$title S${episodeSlug}E${seasonSlug}"
|
||||
}
|
||||
|
||||
val media =
|
||||
app.get("$baymoviesAPI//0:search?q=$query&page_token=&page_index=0", headers = headers)
|
||||
.parsedSafe<BaymoviesSearch>()?.data?.files?.filter { media ->
|
||||
(if (season == null) {
|
||||
media.name?.contains("$year") == true
|
||||
} else {
|
||||
media.name?.contains(Regex("(?i)S${episodeSlug}E${seasonSlug}")) == true
|
||||
}) && media.name?.contains(
|
||||
"720p",
|
||||
true
|
||||
) == false && (media.mimeType == "video/x-matroska" || media.mimeType == "video/mp4") && (media.name.contains(
|
||||
titleSlug,
|
||||
true
|
||||
) || media.name.contains(title ?: return, true))
|
||||
}?.distinctBy { it.name } ?: return
|
||||
|
||||
media.apmap { file ->
|
||||
val expiry = (System.currentTimeMillis() + 345600000).toString()
|
||||
val hmacSign = "${file.id}@$expiry".encode()
|
||||
.hmacSha256(key.encode()).base64().replace("+", "-")
|
||||
val encryptedId =
|
||||
base64Encode(CryptoAES.encrypt(key, file.id ?: return@apmap null).toByteArray())
|
||||
val encryptedExpiry = base64Encode(CryptoAES.encrypt(key, expiry).toByteArray())
|
||||
val worker = getConfig().workers.randomOrNull() ?: return@apmap null
|
||||
|
||||
val link = "https://api.$worker.workers.dev/download.aspx?file=$encryptedId&expiry=$encryptedExpiry&mac=$hmacSign"
|
||||
val size = file.size?.toDouble() ?: return@apmap null
|
||||
val sizeFile = "%.2f GB".format(bytesToGigaBytes(size))
|
||||
val tags = Regex("\\d{3,4}[pP]\\.?(.*?)\\.(mkv|mp4)").find(
|
||||
file.name ?: return@apmap null
|
||||
)?.groupValues?.getOrNull(1)?.replace(".", " ")?.trim()
|
||||
?: ""
|
||||
val quality =
|
||||
Regex("(\\d{3,4})[pP]").find(file.name)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
?: Qualities.Unknown.value
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
"Baymovies $tags [$sizeFile]",
|
||||
"Baymovies $tags [$sizeFile]",
|
||||
link,
|
||||
"",
|
||||
quality,
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StreamM4u : XStreamCdn() {
|
||||
|
@ -2041,6 +2172,12 @@ data class FDMovieIFrame(
|
|||
val type: String,
|
||||
)
|
||||
|
||||
data class BaymoviesConfig(
|
||||
val country: String,
|
||||
val downloadTime: String,
|
||||
val workers: List<String>
|
||||
)
|
||||
|
||||
data class Movie123Media(
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
)
|
||||
|
@ -2301,3 +2438,44 @@ data class Smashy1Source(
|
|||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("tracks") val tracks: ArrayList<Smashy1Tracks>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class WatchsomuchTorrents(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("movieId") val movieId: Int? = null,
|
||||
@JsonProperty("season") val season: Int? = null,
|
||||
@JsonProperty("episode") val episode: Int? = null,
|
||||
)
|
||||
|
||||
data class WatchsomuchMovies(
|
||||
@JsonProperty("torrents") val torrents: ArrayList<WatchsomuchTorrents>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class WatchsomuchResponses(
|
||||
@JsonProperty("movie") val movie: WatchsomuchMovies? = null,
|
||||
)
|
||||
|
||||
data class WatchsomuchSubtitles(
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("label") val label: String? = null,
|
||||
)
|
||||
|
||||
data class WatchsomuchSubResponses(
|
||||
@JsonProperty("subtitles") val subtitles: ArrayList<WatchsomuchSubtitles>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class Baymovies(
|
||||
@JsonProperty("id") val id: String? = null,
|
||||
@JsonProperty("driveId") val driveId: String? = null,
|
||||
@JsonProperty("mimeType") val mimeType: String? = null,
|
||||
@JsonProperty("size") val size: String? = null,
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("modifiedTime") val modifiedTime: String? = null,
|
||||
)
|
||||
|
||||
data class BaymoviesData(
|
||||
@JsonProperty("files") val files: ArrayList<Baymovies>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class BaymoviesSearch(
|
||||
@JsonProperty("data") val data: BaymoviesData? = null,
|
||||
)
|
|
@ -3,6 +3,7 @@ package com.hexated
|
|||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.hexated.SoraExtractor.invoke123Movie
|
||||
import com.hexated.SoraExtractor.invokeAnimes
|
||||
import com.hexated.SoraExtractor.invokeBaymovies
|
||||
import com.hexated.SoraExtractor.invokeBollyMaza
|
||||
import com.hexated.SoraExtractor.invokeDbgo
|
||||
import com.hexated.SoraExtractor.invokeFilmxy
|
||||
|
@ -65,12 +66,11 @@ open class SoraStream : TmdbProvider() {
|
|||
const val consumetAnilistAPI = "https://api.consumet.org/meta/anilist"
|
||||
const val kamyrollAPI = "https://api.kamyroll.tech"
|
||||
|
||||
private val mainAPI =
|
||||
base64DecodeAPI("cHA=LmE=ZWw=cmM=dmU=aC4=dGM=d2E=eHA=Ly8=czo=dHA=aHQ=")
|
||||
|
||||
private val mainAPI = base64DecodeAPI("cHA=LmE=ZWw=cmM=dmU=aC4=dGM=d2E=eHA=Ly8=czo=dHA=aHQ=")
|
||||
var baymovies = base64DecodeAPI("Zw==b3I=dS4=LmU=ZXg=bmQ=emk=aS4=YXA=dXA=cm8=Y2c=bGk=dWI=eHA=ZGU=aW4=YXk=ZWI=dGg=Ly8=czo=dHA=aHQ=")
|
||||
// private var mainServerAPI = base64DecodeAPI("cA==YXA=bC4=Y2U=ZXI=LnY=aWU=b3Y=LW0=cmE=c28=Ly8=czo=dHA=aHQ=")
|
||||
var netMoviesAPI =
|
||||
base64DecodeAPI("aQ==YXA=cC8=YXA=bC4=Y2U=ZXI=LnY=bG0=Zmk=dC0=bmU=Ly8=czo=dHA=aHQ=")
|
||||
var netMoviesAPI = base64DecodeAPI("aQ==YXA=cC8=YXA=bC4=Y2U=ZXI=LnY=bG0=Zmk=dC0=bmU=Ly8=czo=dHA=aHQ=")
|
||||
|
||||
const val twoEmbedAPI = "https://www.2embed.to"
|
||||
const val vidSrcAPI = "https://v2.vidsrc.me"
|
||||
const val dbgoAPI = "https://dbgo.fun"
|
||||
|
@ -105,6 +105,7 @@ open class SoraStream : TmdbProvider() {
|
|||
const val animeKaizokuAPI = "https://animekaizoku.com"
|
||||
const val movie123NetAPI = "https://ww7.0123movie.net"
|
||||
const val smashyStreamAPI = "https://embed.smashystream.com"
|
||||
const val baymoviesAPI = "https://thebayindexpublicgroupapi.zindex.eu.org"
|
||||
|
||||
fun getType(t: String?): TvType {
|
||||
return when (t) {
|
||||
|
@ -529,6 +530,15 @@ open class SoraStream : TmdbProvider() {
|
|||
},
|
||||
{
|
||||
invokeSmashyStream(res.id, res.season, res.episode, subtitleCallback, callback)
|
||||
},
|
||||
{
|
||||
if(!res.isAnime) invokeBaymovies(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
callback
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package com.hexated
|
||||
|
||||
import com.hexated.SoraStream.Companion.baymovies
|
||||
import com.hexated.SoraStream.Companion.consumetCrunchyrollAPI
|
||||
import com.hexated.SoraStream.Companion.filmxyAPI
|
||||
import com.hexated.SoraStream.Companion.gdbot
|
||||
import com.hexated.SoraStream.Companion.kamyrollAPI
|
||||
import com.hexated.SoraStream.Companion.tvMoviesAPI
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.base64Decode
|
||||
import com.lagradost.cloudstream3.base64Encode
|
||||
import com.lagradost.cloudstream3.network.WebViewResolver
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
|
@ -600,6 +598,23 @@ fun List<HashMap<String, String>>?.matchingEpisode(episode: Int?): String? {
|
|||
}?.get("id")
|
||||
}
|
||||
|
||||
suspend fun getConfig(): BaymoviesConfig {
|
||||
val regex = """const country = "(.*?)";
|
||||
const downloadtime = "(.*?)";
|
||||
var arrayofworkers = (.*)""".toRegex()
|
||||
val js = app.get(
|
||||
"https://geolocation.zindex.eu.org/api.js",
|
||||
referer = "$baymovies/",
|
||||
).text
|
||||
val match = regex.find(js) ?: throw ErrorLoadingException()
|
||||
val country = match.groupValues[1]
|
||||
val downloadTime = match.groupValues[2]
|
||||
val workers = tryParseJson<List<String>>(match.groupValues[3])
|
||||
?: throw ErrorLoadingException()
|
||||
|
||||
return BaymoviesConfig(country, downloadTime, workers)
|
||||
}
|
||||
|
||||
fun String?.fixTitle(): String? {
|
||||
return this?.replace(Regex("[!%:'?,]|( &)"), "")?.replace(" ", "-")?.lowercase()
|
||||
?.replace("-–-", "-")
|
||||
|
@ -609,6 +624,8 @@ fun getLanguage(str: String): String {
|
|||
return if (str.contains("(in_ID)")) "Indonesian" else str
|
||||
}
|
||||
|
||||
fun bytesToGigaBytes( number: Double ): Double = number / 1024000000
|
||||
|
||||
fun getKisskhTitle(str: String?): String? {
|
||||
return str?.replace(Regex("[^a-zA-Z0-9]"), "-")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue