sora: added new sources

This commit is contained in:
hexated 2023-04-12 00:00:54 +07:00
parent 08d11e47f5
commit d123ad253b
5 changed files with 204 additions and 14 deletions

View file

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

View file

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

View file

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

View file

@ -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,

View file

@ -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 {