sora: added ninetv

This commit is contained in:
hexated 2023-04-08 21:14:42 +07:00
parent 67f87cd7a5
commit 3ca01b9b81
6 changed files with 164 additions and 1 deletions

View file

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

View file

@ -1,8 +1,19 @@
package com.hexated package com.hexated
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.extractors.Filesim import com.lagradost.cloudstream3.extractors.Filesim
import com.lagradost.cloudstream3.extractors.StreamSB import com.lagradost.cloudstream3.extractors.StreamSB
import com.lagradost.cloudstream3.extractors.XStreamCdn import com.lagradost.cloudstream3.extractors.XStreamCdn
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import javax.crypto.Cipher
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
class Sbnet : StreamSB() { class Sbnet : StreamSB() {
override var name = "Sbnet" override var name = "Sbnet"
@ -27,4 +38,121 @@ class Keephealth : StreamSB() {
class FileMoonIn : Filesim() { class FileMoonIn : Filesim() {
override val mainUrl = "https://filemoon.in" override val mainUrl = "https://filemoon.in"
override val name = "FileMoon" override val name = "FileMoon"
}
class Watchx : Chillx() {
override val name = "Watchx"
override val mainUrl = "https://watchx.top"
}
open class Chillx : ExtractorApi() {
override val name = "Chillx"
override val mainUrl = "https://chillx.top"
override val requiresReferer = true
companion object {
private const val KEY = "4VqE3#N7zt&HEP^a"
}
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val master = Regex("MasterJS\\s*=\\s*'([^']+)").find(
app.get(
url,
referer = referer
).text
)?.groupValues?.get(1)
val encData = AppUtils.tryParseJson<AESData>(base64Decode(master ?: return))
val decrypt = cryptoAESHandler(encData ?: return, KEY, false)
val source = Regex("""sources:\s*\[\{"file":"([^"]+)""").find(decrypt)?.groupValues?.get(1)
val tracks = Regex("""tracks:\s*\[(.+)]""").find(decrypt)?.groupValues?.get(1)
val headers = mapOf(
"Accept" to "*/*",
"Connection" to "keep-alive",
"Sec-Fetch-Dest" to "empty",
"Sec-Fetch-Mode" to "cors",
"Sec-Fetch-Site" to "cross-site",
"Origin" to mainUrl,
)
callback.invoke(
ExtractorLink(
name,
name,
source ?: return,
"$mainUrl/",
Qualities.P1080.value,
headers = headers,
isM3u8 = true
)
)
AppUtils.tryParseJson<List<Tracks>>("[$tracks]")
?.filter { it.kind == "captions" }?.map { track ->
subtitleCallback.invoke(
SubtitleFile(
track.label ?: "",
track.file ?: return@map null
)
)
}
}
private fun cryptoAESHandler(
data: AESData,
pass: String,
encrypt: Boolean = true
): String {
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512")
val spec = PBEKeySpec(
pass.toCharArray(),
data.salt?.hexToByteArray(),
data.iterations?.toIntOrNull() ?: 1,
256
)
val key = factory.generateSecret(spec)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
return if (!encrypt) {
cipher.init(
Cipher.DECRYPT_MODE,
SecretKeySpec(key.encoded, "AES"),
IvParameterSpec(data.iv?.hexToByteArray())
)
String(cipher.doFinal(base64DecodeArray(data.ciphertext.toString())))
} else {
cipher.init(
Cipher.ENCRYPT_MODE,
SecretKeySpec(key.encoded, "AES"),
IvParameterSpec(data.iv?.hexToByteArray())
)
base64Encode(cipher.doFinal(data.ciphertext?.toByteArray()))
}
}
private fun String.hexToByteArray(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
data class AESData(
@JsonProperty("ciphertext") val ciphertext: String? = null,
@JsonProperty("iv") val iv: String? = null,
@JsonProperty("salt") val salt: String? = null,
@JsonProperty("iterations") val iterations: String? = null,
)
data class Tracks(
@JsonProperty("file") val file: String? = null,
@JsonProperty("label") val label: String? = null,
@JsonProperty("kind") val kind: String? = null,
)
} }

View file

@ -2767,6 +2767,24 @@ object SoraExtractor : SoraStream() {
} }
} }
suspend fun invokeNinetv(
tmdbId: Int? = null,
season: Int? = null,
episode: Int? = null,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val url = if (season == null) {
"$nineTvAPI/movie/$tmdbId"
} else {
"$nineTvAPI/tv/$tmdbId-$season-$episode"
}
val iframe = app.get(url).document.selectFirst("iframe")?.attr("src") ?: return
loadExtractor(iframe, "$nineTvAPI/", subtitleCallback, callback)
}
} }

View file

@ -38,6 +38,7 @@ import com.hexated.SoraExtractor.invokeM4uhd
import com.hexated.SoraExtractor.invokeMovie123Net import com.hexated.SoraExtractor.invokeMovie123Net
import com.hexated.SoraExtractor.invokeMoviesbay import com.hexated.SoraExtractor.invokeMoviesbay
import com.hexated.SoraExtractor.invokeMoviezAdd import com.hexated.SoraExtractor.invokeMoviezAdd
import com.hexated.SoraExtractor.invokeNinetv
import com.hexated.SoraExtractor.invokePapaonMovies1 import com.hexated.SoraExtractor.invokePapaonMovies1
import com.hexated.SoraExtractor.invokePapaonMovies2 import com.hexated.SoraExtractor.invokePapaonMovies2
import com.hexated.SoraExtractor.invokeRStream import com.hexated.SoraExtractor.invokeRStream
@ -120,6 +121,7 @@ open class SoraStream : TmdbProvider() {
const val ask4MoviesAPI = "https://ask4movie.mx" const val ask4MoviesAPI = "https://ask4movie.mx"
const val biliBiliAPI = "https://api-vn.kaguya.app/server" const val biliBiliAPI = "https://api-vn.kaguya.app/server"
const val watchOnlineAPI = "https://watchonline.ag" const val watchOnlineAPI = "https://watchonline.ag"
const val nineTvAPI = "https://api.9animetv.live"
// INDEX SITE // INDEX SITE
const val baymoviesAPI = "https://opengatewayindex.pages.dev" // dead const val baymoviesAPI = "https://opengatewayindex.pages.dev" // dead
const val chillmovies0API = "https://chill.aicirou.workers.dev/0:" // dead const val chillmovies0API = "https://chill.aicirou.workers.dev/0:" // dead
@ -630,6 +632,9 @@ open class SoraStream : TmdbProvider() {
subtitleCallback subtitleCallback
) )
}, },
{
if (!res.isAnime) invokeNinetv(res.id, res.season, res.episode, subtitleCallback, callback)
},
{ {
if (!res.isAnime) invokeBlackmovies( if (!res.isAnime) invokeBlackmovies(
blackMoviesAPI, blackMoviesAPI,

View file

@ -17,6 +17,7 @@ import com.hexated.SoraExtractor.invokeLing
import com.hexated.SoraExtractor.invokeM4uhd import com.hexated.SoraExtractor.invokeM4uhd
import com.hexated.SoraExtractor.invokeMovie123Net import com.hexated.SoraExtractor.invokeMovie123Net
import com.hexated.SoraExtractor.invokeMovieHab import com.hexated.SoraExtractor.invokeMovieHab
import com.hexated.SoraExtractor.invokeNinetv
import com.hexated.SoraExtractor.invokeRStream import com.hexated.SoraExtractor.invokeRStream
import com.hexated.SoraExtractor.invokeSeries9 import com.hexated.SoraExtractor.invokeSeries9
import com.hexated.SoraExtractor.invokeSmashyStream import com.hexated.SoraExtractor.invokeSmashyStream
@ -63,6 +64,15 @@ class SoraStreamLite : SoraStream() {
callback callback
) )
}, },
{
if (!res.isAnime) invokeNinetv(
res.id,
res.season,
res.episode,
subtitleCallback,
callback
)
},
{ {
invokeTwoEmbed(res.id, res.season, res.episode, subtitleCallback, callback) invokeTwoEmbed(res.id, res.season, res.episode, subtitleCallback, callback)
}, },

View file

@ -16,5 +16,7 @@ class SoraStreamPlugin: Plugin() {
registerExtractorAPI(Keephealth()) registerExtractorAPI(Keephealth())
registerExtractorAPI(FileMoonIn()) registerExtractorAPI(FileMoonIn())
registerExtractorAPI(Sbnet()) registerExtractorAPI(Sbnet())
registerExtractorAPI(Chillx())
registerExtractorAPI(Watchx())
} }
} }