[Sora] added Baymovies

This commit is contained in:
hexated 2023-01-29 10:29:15 +07:00
parent 1e8dfcc789
commit 93cf4840a1
4 changed files with 347 additions and 9 deletions

View 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)
}
}
}

View File

@ -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,
)
@ -2300,4 +2437,45 @@ data class Smashy1Tracks(
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,
)

View File

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

View File

@ -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]"), "-")
}