mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
sora: added new sources
This commit is contained in:
parent
08d11e47f5
commit
d123ad253b
5 changed files with 204 additions and 14 deletions
|
@ -1,5 +1,5 @@
|
||||||
// use an integer for version numbers
|
// use an integer for version numbers
|
||||||
version = 114
|
version = 115
|
||||||
|
|
||||||
|
|
||||||
cloudstream {
|
cloudstream {
|
||||||
|
|
|
@ -7,9 +7,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
import com.lagradost.nicehttp.Requests
|
import com.lagradost.nicehttp.Requests
|
||||||
import com.lagradost.nicehttp.Session
|
import com.lagradost.nicehttp.Session
|
||||||
import com.hexated.RabbitStream.extractRabbitStream
|
import com.hexated.RabbitStream.extractRabbitStream
|
||||||
import com.lagradost.cloudstream3.extractors.Filesim
|
|
||||||
import com.lagradost.cloudstream3.extractors.StreamSB
|
|
||||||
import com.lagradost.cloudstream3.extractors.XStreamCdn
|
|
||||||
import com.lagradost.cloudstream3.network.CloudflareKiller
|
import com.lagradost.cloudstream3.network.CloudflareKiller
|
||||||
import com.lagradost.nicehttp.RequestBodyTypes
|
import com.lagradost.nicehttp.RequestBodyTypes
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -660,7 +657,7 @@ object SoraExtractor : SoraStream() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit,
|
callback: (ExtractorLink) -> Unit,
|
||||||
) {
|
) {
|
||||||
val (id, type) = getSoraIdAndType(title, year, season) ?: return invokeJustchill(
|
val (id, type) = getSoraIdAndType(title, year, season) ?: return invokeSoraBackup(
|
||||||
title,
|
title,
|
||||||
year,
|
year,
|
||||||
season,
|
season,
|
||||||
|
@ -698,7 +695,7 @@ object SoraExtractor : SoraStream() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun invokeJustchill(
|
private suspend fun invokeSoraBackup(
|
||||||
title: String? = null,
|
title: String? = null,
|
||||||
year: Int? = null,
|
year: Int? = null,
|
||||||
season: Int? = null,
|
season: Int? = null,
|
||||||
|
@ -707,7 +704,7 @@ object SoraExtractor : SoraStream() {
|
||||||
callback: (ExtractorLink) -> Unit,
|
callback: (ExtractorLink) -> Unit,
|
||||||
) {
|
) {
|
||||||
val results =
|
val results =
|
||||||
app.get("$chillAPI/api/search?keyword=$title").parsedSafe<ChillSearch>()?.data?.results
|
app.get("$soraBackupAPI/api/search?keyword=$title").parsedSafe<ChillSearch>()?.data?.results
|
||||||
val media = if (results?.size == 1) {
|
val media = if (results?.size == 1) {
|
||||||
results.firstOrNull()
|
results.firstOrNull()
|
||||||
} else {
|
} else {
|
||||||
|
@ -732,17 +729,17 @@ object SoraExtractor : SoraStream() {
|
||||||
}
|
}
|
||||||
} ?: return
|
} ?: return
|
||||||
|
|
||||||
val episodeId = app.get("$chillAPI/api/detail?id=${media.id}&category=${media.domainType}").parsedSafe<Load>()?.data?.episodeVo?.find {
|
val episodeId = app.get("$soraBackupAPI/api/detail?id=${media.id}&category=${media.domainType}").parsedSafe<Load>()?.data?.episodeVo?.find {
|
||||||
it.seriesNo == (episode ?: 0)
|
it.seriesNo == (episode ?: 0)
|
||||||
}?.id ?: return
|
}?.id ?: return
|
||||||
|
|
||||||
val sources = app.get("$chillAPI/api/episode?id=${media.id}&category=${media.domainType}&episode=$episodeId").parsedSafe<ChillSources>()?.data
|
val sources = app.get("$soraBackupAPI/api/episode?id=${media.id}&category=${media.domainType}&episode=$episodeId").parsedSafe<ChillSources>()?.data
|
||||||
|
|
||||||
sources?.qualities?.map { source ->
|
sources?.qualities?.map { source ->
|
||||||
callback.invoke(
|
callback.invoke(
|
||||||
ExtractorLink(
|
ExtractorLink(
|
||||||
"ChillMovie",
|
this.name,
|
||||||
"ChillMovie",
|
this.name,
|
||||||
source.url ?: return@map null,
|
source.url ?: return@map null,
|
||||||
"",
|
"",
|
||||||
source.quality ?: Qualities.Unknown.value,
|
source.quality ?: Qualities.Unknown.value,
|
||||||
|
@ -2785,6 +2782,80 @@ object SoraExtractor : SoraStream() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun invokePutlocker(
|
||||||
|
title: String? = null,
|
||||||
|
year: Int? = null,
|
||||||
|
season: Int? = null,
|
||||||
|
episode: Int? = null,
|
||||||
|
callback: (ExtractorLink) -> Unit,
|
||||||
|
) {
|
||||||
|
val query = if (season == null) {
|
||||||
|
title
|
||||||
|
} else {
|
||||||
|
"$title - season $season"
|
||||||
|
}
|
||||||
|
|
||||||
|
val res = app.get("$putlockerAPI/movie/search/$query").document
|
||||||
|
val scripData = res.select("div.movies-list div.ml-item").map {
|
||||||
|
it.selectFirst("h2")?.text() to it.selectFirst("a")?.attr("href")
|
||||||
|
}
|
||||||
|
val script = if (scripData.size == 1) {
|
||||||
|
scripData.first()
|
||||||
|
} else {
|
||||||
|
scripData.find {
|
||||||
|
if (season == null) {
|
||||||
|
it.first.equals(title, true) || (it.first?.contains(
|
||||||
|
"$title", true
|
||||||
|
) == true && it.first?.contains("$year") == true)
|
||||||
|
} else {
|
||||||
|
it.first?.contains("$title", true) == true && it.first?.contains(
|
||||||
|
"Season $season", true
|
||||||
|
) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val id = fixUrl(script?.second ?: return).split("-").lastOrNull()?.removeSuffix("/")
|
||||||
|
val iframe = app.get("$putlockerAPI/ajax/movie_episodes/$id")
|
||||||
|
.parsedSafe<PutlockerEpisodes>()?.html?.let { Jsoup.parse(it) }?.let { server ->
|
||||||
|
if (season == null) {
|
||||||
|
server.select("div.les-content a").map {
|
||||||
|
it.attr("data-id") to it.attr("data-server")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
server.select("div.les-content a").map { it }
|
||||||
|
.filter { it.text().contains("Episode $episode", true) }.map {
|
||||||
|
it.attr("data-id") to it.attr("data-server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe?.apmap {
|
||||||
|
delay(3000)
|
||||||
|
val embedUrl = app.get("$putlockerAPI/ajax/movie_embed/${it.first}")
|
||||||
|
.parsedSafe<PutlockerEmbed>()?.src ?: return@apmap null
|
||||||
|
val sources = extractPutlockerSources(embedUrl)?.parsedSafe<PutlockerResponses>()
|
||||||
|
|
||||||
|
argamap(
|
||||||
|
{
|
||||||
|
sources?.callback(embedUrl, "Server ${it.second}", callback)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
if (!sources?.backupLink.isNullOrBlank()) {
|
||||||
|
extractPutlockerSources(sources?.backupLink)?.parsedSafe<PutlockerResponses>()
|
||||||
|
?.callback(
|
||||||
|
embedUrl, "Backup ${it.second}", callback
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return@argamap
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3177,3 +3248,22 @@ data class ChillData(
|
||||||
data class ChillSearch(
|
data class ChillSearch(
|
||||||
@JsonProperty("data") val data: ChillData? = null,
|
@JsonProperty("data") val data: ChillData? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class PutlockerEpisodes(
|
||||||
|
@JsonProperty("html") val html: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PutlockerEmbed(
|
||||||
|
@JsonProperty("src") val src: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PutlockerSources(
|
||||||
|
@JsonProperty("file") val file: String,
|
||||||
|
@JsonProperty("label") val label: String? = null,
|
||||||
|
@JsonProperty("type") val type: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PutlockerResponses(
|
||||||
|
@JsonProperty("sources") val sources: ArrayList<PutlockerSources>? = arrayListOf(),
|
||||||
|
@JsonProperty("backupLink") val backupLink: String? = null,
|
||||||
|
)
|
|
@ -41,6 +41,7 @@ import com.hexated.SoraExtractor.invokeMoviezAdd
|
||||||
import com.hexated.SoraExtractor.invokeNinetv
|
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.invokePutlocker
|
||||||
import com.hexated.SoraExtractor.invokeRStream
|
import com.hexated.SoraExtractor.invokeRStream
|
||||||
import com.hexated.SoraExtractor.invokeRinzrymovies
|
import com.hexated.SoraExtractor.invokeRinzrymovies
|
||||||
import com.hexated.SoraExtractor.invokeRubyMovies
|
import com.hexated.SoraExtractor.invokeRubyMovies
|
||||||
|
@ -122,6 +123,7 @@ open class SoraStream : TmdbProvider() {
|
||||||
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"
|
const val nineTvAPI = "https://api.9animetv.live"
|
||||||
|
const val putlockerAPI = "https://ww7.putlocker.vip"
|
||||||
// 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
|
||||||
|
@ -538,6 +540,9 @@ open class SoraStream : TmdbProvider() {
|
||||||
callback
|
callback
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
invokePutlocker(res.title, res.year, res.season, res.episode, callback)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
invokeTvMovies(res.title, res.season, res.episode, callback)
|
invokeTvMovies(res.title, res.season, res.episode, callback)
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,7 @@ 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.invokeNinetv
|
||||||
|
import com.hexated.SoraExtractor.invokePutlocker
|
||||||
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
|
||||||
|
@ -46,6 +47,15 @@ class SoraStreamLite : SoraStream() {
|
||||||
val res = AppUtils.parseJson<LinkData>(data)
|
val res = AppUtils.parseJson<LinkData>(data)
|
||||||
|
|
||||||
argamap(
|
argamap(
|
||||||
|
{
|
||||||
|
invokePutlocker(
|
||||||
|
res.title,
|
||||||
|
res.year,
|
||||||
|
res.season,
|
||||||
|
res.episode,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
invokeWatchsomuch(
|
invokeWatchsomuch(
|
||||||
res.imdbId,
|
res.imdbId,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.hexated.SoraStream.Companion.baymoviesAPI
|
||||||
import com.hexated.SoraStream.Companion.consumetCrunchyrollAPI
|
import com.hexated.SoraStream.Companion.consumetCrunchyrollAPI
|
||||||
import com.hexated.SoraStream.Companion.filmxyAPI
|
import com.hexated.SoraStream.Companion.filmxyAPI
|
||||||
import com.hexated.SoraStream.Companion.gdbot
|
import com.hexated.SoraStream.Companion.gdbot
|
||||||
|
import com.hexated.SoraStream.Companion.putlockerAPI
|
||||||
import com.hexated.SoraStream.Companion.smashyStreamAPI
|
import com.hexated.SoraStream.Companion.smashyStreamAPI
|
||||||
import com.hexated.SoraStream.Companion.tvMoviesAPI
|
import com.hexated.SoraStream.Companion.tvMoviesAPI
|
||||||
import com.hexated.SoraStream.Companion.twoEmbedAPI
|
import com.hexated.SoraStream.Companion.twoEmbedAPI
|
||||||
|
@ -14,13 +15,11 @@ import com.hexated.SoraStream.Companion.watchOnlineAPI
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
|
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
|
||||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||||
import com.lagradost.cloudstream3.network.WebViewResolver
|
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.nicehttp.NiceResponse
|
import com.lagradost.nicehttp.NiceResponse
|
||||||
import com.lagradost.nicehttp.RequestBodyTypes
|
import com.lagradost.nicehttp.RequestBodyTypes
|
||||||
import com.lagradost.nicehttp.requestCreator
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
|
@ -42,7 +41,7 @@ import kotlin.collections.ArrayList
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
val soraAPI = base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=")
|
val soraAPI = base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=")
|
||||||
val chillAPI = base64DecodeAPI("dg==LnQ=bGw=aGk=dGM=dXM=Lmo=b2s=a2w=bG8=Ly8=czo=dHA=aHQ=")
|
val soraBackupAPI = base64DecodeAPI("dg==LnQ=bGw=aGk=dGM=dXM=Lmo=b2s=a2w=bG8=Ly8=czo=dHA=aHQ=")
|
||||||
|
|
||||||
val soraHeaders = mapOf(
|
val soraHeaders = mapOf(
|
||||||
"lang" to "en",
|
"lang" to "en",
|
||||||
|
@ -826,6 +825,61 @@ fun Map<String, List<CrunchyrollEpisodes>>?.matchingEpisode(
|
||||||
}?.firstOrNull()
|
}?.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun extractPutlockerSources(url: String?): NiceResponse? {
|
||||||
|
val embedHost = url?.substringBefore("/embed-player")
|
||||||
|
val player = app.get(
|
||||||
|
url ?: return null,
|
||||||
|
referer = "${putlockerAPI}/"
|
||||||
|
).document.select("div#player")
|
||||||
|
|
||||||
|
val text = "\"${player.attr("data-id")}\""
|
||||||
|
val password = player.attr("data-hash")
|
||||||
|
val cipher = CryptoAES.plEncrypt(password, text)
|
||||||
|
|
||||||
|
return app.get(
|
||||||
|
"$embedHost/ajax/getSources/", params = mapOf(
|
||||||
|
"id" to cipher.cipherText,
|
||||||
|
"h" to cipher.password,
|
||||||
|
"a" to cipher.iv,
|
||||||
|
"t" to cipher.salt,
|
||||||
|
), referer = url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun PutlockerResponses?.callback(
|
||||||
|
referer: String,
|
||||||
|
server: String,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
val ref = getBaseUrl(referer)
|
||||||
|
this?.sources?.map { source ->
|
||||||
|
val request = app.get(source.file, referer = ref)
|
||||||
|
callback.invoke(
|
||||||
|
ExtractorLink(
|
||||||
|
"Putlocker [$server]",
|
||||||
|
"Putlocker [$server]",
|
||||||
|
if (!request.isSuccessful) return@map null else source.file,
|
||||||
|
ref,
|
||||||
|
if (source.file.contains("m3u8")) getPutlockerQuality(request.text) else source.label?.replace(
|
||||||
|
Regex("[Pp]"),
|
||||||
|
""
|
||||||
|
)?.trim()?.toIntOrNull()
|
||||||
|
?: Qualities.P720.value,
|
||||||
|
source.file.contains("m3u8")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPutlockerQuality(quality: String): Int {
|
||||||
|
return when {
|
||||||
|
quality.contains("NAME=\"1080p\"") || quality.contains("RESOLUTION=1920x1080") -> Qualities.P1080.value
|
||||||
|
quality.contains("NAME=\"720p\"") || quality.contains("RESOLUTION=1280x720")-> Qualities.P720.value
|
||||||
|
else -> Qualities.P480.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getEpisodeSlug(
|
fun getEpisodeSlug(
|
||||||
season: Int? = null,
|
season: Int? = null,
|
||||||
episode: Int? = null,
|
episode: Int? = null,
|
||||||
|
@ -1195,6 +1249,25 @@ object CryptoAES {
|
||||||
return String(bEncode)
|
return String(bEncode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun plEncrypt(password: String, plainText: String): EncryptResult {
|
||||||
|
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())
|
||||||
|
val bEncode = Base64.encode(cipherText, Base64.NO_WRAP)
|
||||||
|
return EncryptResult(
|
||||||
|
String(bEncode).toHex(),
|
||||||
|
password.toHex(),
|
||||||
|
saltBytes.toHex(),
|
||||||
|
iv.toHex()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt
|
* Decrypt
|
||||||
* Thanks Artjom B. for this: http://stackoverflow.com/a/29152379/4405051
|
* Thanks Artjom B. for this: http://stackoverflow.com/a/29152379/4405051
|
||||||
|
@ -1272,6 +1345,18 @@ object CryptoAES {
|
||||||
SecureRandom().nextBytes(this)
|
SecureRandom().nextBytes(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
|
||||||
|
|
||||||
|
private fun String.toHex(): String = toByteArray().toHex()
|
||||||
|
|
||||||
|
data class EncryptResult(
|
||||||
|
val cipherText: String,
|
||||||
|
val password: String,
|
||||||
|
val salt: String,
|
||||||
|
val iv: String
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object RabbitStream {
|
object RabbitStream {
|
||||||
|
|
Loading…
Reference in a new issue