mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
sora: fix Jeniusplay
This commit is contained in:
parent
3b1d788587
commit
e8661648da
9 changed files with 224 additions and 342 deletions
|
@ -1,5 +1,5 @@
|
||||||
// use an integer for version numbers
|
// use an integer for version numbers
|
||||||
version = 12
|
version = 13
|
||||||
|
|
||||||
|
|
||||||
cloudstream {
|
cloudstream {
|
||||||
|
|
|
@ -13,13 +13,12 @@ import org.jsoup.nodes.Element
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
class IdlixProvider : MainAPI() {
|
class IdlixProvider : MainAPI() {
|
||||||
override var mainUrl = "https://tv.idlixprime.com"
|
override var mainUrl = "https://tv.idlixplus.net"
|
||||||
private var directUrl = mainUrl
|
private var directUrl = mainUrl
|
||||||
override var name = "Idlix"
|
override var name = "Idlix"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override var lang = "id"
|
override var lang = "id"
|
||||||
override val hasDownloadSupport = true
|
override val hasDownloadSupport = true
|
||||||
private val session = Session(Requests().baseClient)
|
|
||||||
override val supportedTypes = setOf(
|
override val supportedTypes = setOf(
|
||||||
TvType.Movie,
|
TvType.Movie,
|
||||||
TvType.TvSeries,
|
TvType.TvSeries,
|
||||||
|
@ -51,9 +50,9 @@ class IdlixProvider : MainAPI() {
|
||||||
val url = request.data.split("?")
|
val url = request.data.split("?")
|
||||||
val nonPaged = request.name == "Featured" && page <= 1
|
val nonPaged = request.name == "Featured" && page <= 1
|
||||||
val req = if (nonPaged) {
|
val req = if (nonPaged) {
|
||||||
session.get(request.data)
|
app.get(request.data)
|
||||||
} else {
|
} else {
|
||||||
session.get("${url.first()}$page/?${url.lastOrNull()}")
|
app.get("${url.first()}$page/?${url.lastOrNull()}")
|
||||||
}
|
}
|
||||||
mainUrl = getBaseUrl(req.url)
|
mainUrl = getBaseUrl(req.url)
|
||||||
val document = req.document
|
val document = req.document
|
||||||
|
@ -98,7 +97,7 @@ class IdlixProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(query: String): List<SearchResponse> {
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
val req = session.get("$mainUrl/search/$query")
|
val req = app.get("$mainUrl/search/$query")
|
||||||
mainUrl = getBaseUrl(req.url)
|
mainUrl = getBaseUrl(req.url)
|
||||||
val document = req.document
|
val document = req.document
|
||||||
return document.select("div.result-item").map {
|
return document.select("div.result-item").map {
|
||||||
|
@ -113,7 +112,7 @@ class IdlixProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun load(url: String): LoadResponse {
|
override suspend fun load(url: String): LoadResponse {
|
||||||
val request = session.get(url)
|
val request = app.get(url)
|
||||||
directUrl = getBaseUrl(request.url)
|
directUrl = getBaseUrl(request.url)
|
||||||
val document = request.document
|
val document = request.document
|
||||||
val title =
|
val title =
|
||||||
|
@ -193,7 +192,7 @@ class IdlixProvider : MainAPI() {
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
|
||||||
val document = session.get(data).document
|
val document = app.get(data).document
|
||||||
val id = document.select("meta#dooplay-ajax-counter").attr("data-postid")
|
val id = document.select("meta#dooplay-ajax-counter").attr("data-postid")
|
||||||
val type = if (data.contains("/movie/")) "movie" else "tv"
|
val type = if (data.contains("/movie/")) "movie" else "tv"
|
||||||
|
|
||||||
|
@ -201,22 +200,18 @@ class IdlixProvider : MainAPI() {
|
||||||
it.attr("data-nume")
|
it.attr("data-nume")
|
||||||
}.apmap { nume ->
|
}.apmap { nume ->
|
||||||
safeApiCall {
|
safeApiCall {
|
||||||
var source = session.post(
|
val source = app.get(
|
||||||
url = "$directUrl/wp-admin/admin-ajax.php",
|
url = "$directUrl/wp-json/dooplayer/v2/$id/$type/$nume",
|
||||||
data = mapOf(
|
|
||||||
"action" to "doo_player_ajax",
|
|
||||||
"post" to id,
|
|
||||||
"nume" to nume,
|
|
||||||
"type" to type
|
|
||||||
),
|
|
||||||
headers = mapOf("X-Requested-With" to "XMLHttpRequest"),
|
headers = mapOf("X-Requested-With" to "XMLHttpRequest"),
|
||||||
referer = data
|
referer = data
|
||||||
).let { tryParseJson<ResponseHash>(it.text) }?.embed_url ?: return@safeApiCall
|
).let { tryParseJson<ResponseHash>(it.text) } ?: return@safeApiCall
|
||||||
|
|
||||||
if (source.startsWith("https://uservideo.xyz")) {
|
var decrypted = AesHelper.cryptoAESHandler(source.embed_url,source.key.toByteArray(), false)?.fixBloat() ?: return@safeApiCall
|
||||||
source = app.get(source).document.select("iframe").attr("src")
|
|
||||||
|
if (decrypted.startsWith("https://uservideo.xyz")) {
|
||||||
|
decrypted = app.get(decrypted).document.select("iframe").attr("src")
|
||||||
}
|
}
|
||||||
loadExtractor(source, directUrl, subtitleCallback, callback)
|
loadExtractor(decrypted, "$directUrl/", subtitleCallback, callback)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,9 +219,15 @@ class IdlixProvider : MainAPI() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.fixBloat() : String {
|
||||||
|
return this.replace("\"", "").replace("\\", "")
|
||||||
|
}
|
||||||
|
|
||||||
data class ResponseHash(
|
data class ResponseHash(
|
||||||
@JsonProperty("embed_url") val embed_url: String,
|
@JsonProperty("embed_url") val embed_url: String,
|
||||||
|
@JsonProperty("key") val key: String,
|
||||||
@JsonProperty("type") val type: String?,
|
@JsonProperty("type") val type: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
95
IdlixProvider/src/main/kotlin/com/hexated/Utils.kt
Normal file
95
IdlixProvider/src/main/kotlin/com/hexated/Utils.kt
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||||
|
import com.lagradost.cloudstream3.base64DecodeArray
|
||||||
|
import com.lagradost.cloudstream3.base64Encode
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
|
import java.security.DigestException
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
object AesHelper {
|
||||||
|
|
||||||
|
fun cryptoAESHandler(
|
||||||
|
data: String,
|
||||||
|
pass: ByteArray,
|
||||||
|
encrypt: Boolean = true,
|
||||||
|
padding: String = "AES/CBC/PKCS5PADDING",
|
||||||
|
): String? {
|
||||||
|
val parse = AppUtils.tryParseJson<AesData>(data) ?: return null
|
||||||
|
val (key, iv) = generateKeyAndIv(pass, parse.s.hexToByteArray()) ?: throw ErrorLoadingException("failed to generate key")
|
||||||
|
val cipher = Cipher.getInstance(padding)
|
||||||
|
return if (!encrypt) {
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||||
|
String(cipher.doFinal(base64DecodeArray(parse.ct)))
|
||||||
|
} else {
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||||
|
base64Encode(cipher.doFinal(parse.ct.toByteArray()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/41434590/8166854
|
||||||
|
private fun generateKeyAndIv(
|
||||||
|
password: ByteArray,
|
||||||
|
salt: ByteArray,
|
||||||
|
hashAlgorithm: String = "MD5",
|
||||||
|
keyLength: Int = 32,
|
||||||
|
ivLength: Int = 16,
|
||||||
|
iterations: Int = 1
|
||||||
|
): List<ByteArray>? {
|
||||||
|
|
||||||
|
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||||
|
val digestLength = md.digestLength
|
||||||
|
val targetKeySize = keyLength + ivLength
|
||||||
|
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
|
||||||
|
val generatedData = ByteArray(requiredLength)
|
||||||
|
var generatedLength = 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
md.reset()
|
||||||
|
|
||||||
|
while (generatedLength < targetKeySize) {
|
||||||
|
if (generatedLength > 0)
|
||||||
|
md.update(
|
||||||
|
generatedData,
|
||||||
|
generatedLength - digestLength,
|
||||||
|
digestLength
|
||||||
|
)
|
||||||
|
|
||||||
|
md.update(password)
|
||||||
|
md.update(salt, 0, 8)
|
||||||
|
md.digest(generatedData, generatedLength, digestLength)
|
||||||
|
|
||||||
|
for (i in 1 until iterations) {
|
||||||
|
md.update(generatedData, generatedLength, digestLength)
|
||||||
|
md.digest(generatedData, generatedLength, digestLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedLength += digestLength
|
||||||
|
}
|
||||||
|
return listOf(
|
||||||
|
generatedData.copyOfRange(0, keyLength),
|
||||||
|
generatedData.copyOfRange(keyLength, targetKeySize)
|
||||||
|
)
|
||||||
|
} catch (e: DigestException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.hexToByteArray(): ByteArray {
|
||||||
|
check(length % 2 == 0) { "Must have an even length" }
|
||||||
|
return chunked(2)
|
||||||
|
.map { it.toInt(16).toByte() }
|
||||||
|
.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class AesData(
|
||||||
|
@JsonProperty("ct") val ct: String,
|
||||||
|
@JsonProperty("iv") val iv: String,
|
||||||
|
@JsonProperty("s") val s: String
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import org.jetbrains.kotlin.konan.properties.Properties
|
import org.jetbrains.kotlin.konan.properties.Properties
|
||||||
|
|
||||||
// use an integer for version numbers
|
// use an integer for version numbers
|
||||||
version = 159
|
version = 160
|
||||||
|
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.hexated
|
package com.hexated
|
||||||
|
|
||||||
|
import com.hexated.AesHelper.cryptoAESHandler
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
|
@ -414,7 +415,7 @@ object SoraExtractor : SoraStream() {
|
||||||
} else {
|
} else {
|
||||||
"$idlixAPI/episode/$fixTitle-season-$season-episode-$episode"
|
"$idlixAPI/episode/$fixTitle-season-$season-episode-$episode"
|
||||||
}
|
}
|
||||||
invokeWpmovies(url, subtitleCallback, callback)
|
invokeWpmovies(url, subtitleCallback, callback, encrypt = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun invokeMultimovies(
|
suspend fun invokeMultimovies(
|
||||||
|
@ -455,8 +456,13 @@ object SoraExtractor : SoraStream() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit,
|
callback: (ExtractorLink) -> Unit,
|
||||||
fixIframe: Boolean = false,
|
fixIframe: Boolean = false,
|
||||||
|
encrypt: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val res = session.get(url ?: return)
|
fun String.fixBloat() : String {
|
||||||
|
return this.replace("\"", "").replace("\\", "")
|
||||||
|
}
|
||||||
|
val res = app.get(url ?: return)
|
||||||
|
val headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||||
val referer = getBaseUrl(res.url)
|
val referer = getBaseUrl(res.url)
|
||||||
val document = res.document
|
val document = res.document
|
||||||
document.select("ul#playeroptionsul > li").map {
|
document.select("ul#playeroptionsul > li").map {
|
||||||
|
@ -466,13 +472,21 @@ object SoraExtractor : SoraStream() {
|
||||||
it.attr("data-type")
|
it.attr("data-type")
|
||||||
)
|
)
|
||||||
}.apmap { (id, nume, type) ->
|
}.apmap { (id, nume, type) ->
|
||||||
val json = session.post(
|
val json = if(encrypt) app.get(
|
||||||
|
url = "$referer/wp-json/dooplayer/v2/$id/$type/$nume",
|
||||||
|
headers = headers,
|
||||||
|
referer = url
|
||||||
|
) else app.post(
|
||||||
url = "$referer/wp-admin/admin-ajax.php", data = mapOf(
|
url = "$referer/wp-admin/admin-ajax.php", data = mapOf(
|
||||||
"action" to "doo_player_ajax", "post" to id, "nume" to nume, "type" to type
|
"action" to "doo_player_ajax", "post" to id, "nume" to nume, "type" to type
|
||||||
), headers = mapOf("X-Requested-With" to "XMLHttpRequest"), referer = url
|
), headers = headers, referer = url
|
||||||
)
|
)
|
||||||
val source = tryParseJson<ResponseHash>(json.text)?.embed_url?.let {
|
val source = tryParseJson<ResponseHash>(json.text)?.let {
|
||||||
if (fixIframe) Jsoup.parse(it).select("IFRAME").attr("SRC") else it
|
when {
|
||||||
|
encrypt -> cryptoAESHandler(it.embed_url,it.key.toByteArray(), false)?.fixBloat()
|
||||||
|
fixIframe -> Jsoup.parse(it.embed_url).select("IFRAME").attr("SRC")
|
||||||
|
else -> it.embed_url
|
||||||
|
}
|
||||||
} ?: return@apmap
|
} ?: return@apmap
|
||||||
if (!source.contains("youtube")) {
|
if (!source.contains("youtube")) {
|
||||||
loadExtractor(source, "$referer/", subtitleCallback, callback)
|
loadExtractor(source, "$referer/", subtitleCallback, callback)
|
||||||
|
@ -2540,80 +2554,6 @@ 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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun invokeCryMovies(
|
suspend fun invokeCryMovies(
|
||||||
imdbId: String? = null,
|
imdbId: String? = null,
|
||||||
title: String? = null,
|
title: String? = null,
|
||||||
|
|
|
@ -65,6 +65,7 @@ data class HdMovieBoxIframe(
|
||||||
|
|
||||||
data class ResponseHash(
|
data class ResponseHash(
|
||||||
@JsonProperty("embed_url") val embed_url: String,
|
@JsonProperty("embed_url") val embed_url: String,
|
||||||
|
@JsonProperty("key") val key: String,
|
||||||
@JsonProperty("type") val type: String?,
|
@JsonProperty("type") val type: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -206,25 +207,6 @@ data class WatchOnlineResponse(
|
||||||
@JsonProperty("subtitles") val subtitles: Any? = null,
|
@JsonProperty("subtitles") val subtitles: Any? = 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,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class CryMoviesProxyHeaders(
|
data class CryMoviesProxyHeaders(
|
||||||
@JsonProperty("request") val request: Map<String, String>?,
|
@JsonProperty("request") val request: Map<String, String>?,
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,7 +34,6 @@ import com.hexated.SoraExtractor.invokeMoviezAdd
|
||||||
import com.hexated.SoraExtractor.invokeNavy
|
import com.hexated.SoraExtractor.invokeNavy
|
||||||
import com.hexated.SoraExtractor.invokeNinetv
|
import com.hexated.SoraExtractor.invokeNinetv
|
||||||
import com.hexated.SoraExtractor.invokeNowTv
|
import com.hexated.SoraExtractor.invokeNowTv
|
||||||
import com.hexated.SoraExtractor.invokePutlocker
|
|
||||||
import com.hexated.SoraExtractor.invokeRStream
|
import com.hexated.SoraExtractor.invokeRStream
|
||||||
import com.hexated.SoraExtractor.invokeRidomovies
|
import com.hexated.SoraExtractor.invokeRidomovies
|
||||||
import com.hexated.SoraExtractor.invokeShinobiMovies
|
import com.hexated.SoraExtractor.invokeShinobiMovies
|
||||||
|
@ -55,7 +54,6 @@ import com.hexated.SoraExtractor.invokeWatchsomuch
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
|
||||||
import com.lagradost.cloudstream3.extractors.VidSrcExtractor
|
import com.lagradost.cloudstream3.extractors.VidSrcExtractor
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
@ -94,7 +92,7 @@ open class SoraStream : TmdbProvider() {
|
||||||
const val hdMovieBoxAPI = "https://hdmoviebox.net"
|
const val hdMovieBoxAPI = "https://hdmoviebox.net"
|
||||||
const val dreamfilmAPI = "https://dreamfilmsw.net"
|
const val dreamfilmAPI = "https://dreamfilmsw.net"
|
||||||
const val series9API = "https://series9.cx"
|
const val series9API = "https://series9.cx"
|
||||||
const val idlixAPI = "https://tv.idlixprime.com"
|
const val idlixAPI = "https://tv.idlixplus.net"
|
||||||
const val noverseAPI = "https://www.nollyverse.com"
|
const val noverseAPI = "https://www.nollyverse.com"
|
||||||
const val filmxyAPI = "https://www.filmxy.vip"
|
const val filmxyAPI = "https://www.filmxy.vip"
|
||||||
const val kimcartoonAPI = "https://kimcartoon.li"
|
const val kimcartoonAPI = "https://kimcartoon.li"
|
||||||
|
@ -102,7 +100,7 @@ open class SoraStream : TmdbProvider() {
|
||||||
const val crunchyrollAPI = "https://beta-api.crunchyroll.com"
|
const val crunchyrollAPI = "https://beta-api.crunchyroll.com"
|
||||||
const val kissKhAPI = "https://kisskh.co"
|
const val kissKhAPI = "https://kisskh.co"
|
||||||
const val lingAPI = "https://ling-online.net"
|
const val lingAPI = "https://ling-online.net"
|
||||||
const val uhdmoviesAPI = "https://uhdmovies.actor"
|
const val uhdmoviesAPI = "https://uhdmovies.wiki"
|
||||||
const val fwatayakoAPI = "https://5100.svetacdn.in"
|
const val fwatayakoAPI = "https://5100.svetacdn.in"
|
||||||
const val gMoviesAPI = "https://gdrivemovies.xyz"
|
const val gMoviesAPI = "https://gdrivemovies.xyz"
|
||||||
const val fdMoviesAPI = "https://freedrivemovie.lol"
|
const val fdMoviesAPI = "https://freedrivemovie.lol"
|
||||||
|
@ -118,7 +116,6 @@ open class SoraStream : TmdbProvider() {
|
||||||
const val ask4MoviesAPI = "https://ask4movie.nl"
|
const val ask4MoviesAPI = "https://ask4movie.nl"
|
||||||
const val watchOnlineAPI = "https://watchonline.ag"
|
const val watchOnlineAPI = "https://watchonline.ag"
|
||||||
const val nineTvAPI = "https://moviesapi.club"
|
const val nineTvAPI = "https://moviesapi.club"
|
||||||
const val putlockerAPI = "https://ww7.putlocker.vip"
|
|
||||||
const val fmoviesAPI = "https://fmovies.to"
|
const val fmoviesAPI = "https://fmovies.to"
|
||||||
const val nowTvAPI = "https://myfilestorage.xyz"
|
const val nowTvAPI = "https://myfilestorage.xyz"
|
||||||
const val gokuAPI = "https://goku.sx"
|
const val gokuAPI = "https://goku.sx"
|
||||||
|
@ -545,9 +542,6 @@ 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)
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,7 +21,6 @@ import com.hexated.SoraExtractor.invokeMovieHab
|
||||||
import com.hexated.SoraExtractor.invokeNavy
|
import com.hexated.SoraExtractor.invokeNavy
|
||||||
import com.hexated.SoraExtractor.invokeNinetv
|
import com.hexated.SoraExtractor.invokeNinetv
|
||||||
import com.hexated.SoraExtractor.invokeNowTv
|
import com.hexated.SoraExtractor.invokeNowTv
|
||||||
import com.hexated.SoraExtractor.invokePutlocker
|
|
||||||
import com.hexated.SoraExtractor.invokeRStream
|
import com.hexated.SoraExtractor.invokeRStream
|
||||||
import com.hexated.SoraExtractor.invokeRidomovies
|
import com.hexated.SoraExtractor.invokeRidomovies
|
||||||
import com.hexated.SoraExtractor.invokeSeries9
|
import com.hexated.SoraExtractor.invokeSeries9
|
||||||
|
@ -55,15 +54,6 @@ 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,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.hexated
|
package com.hexated
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.hexated.DumpUtils.queryApi
|
import com.hexated.DumpUtils.queryApi
|
||||||
import com.hexated.SoraStream.Companion.anilistAPI
|
import com.hexated.SoraStream.Companion.anilistAPI
|
||||||
import com.hexated.SoraStream.Companion.base64DecodeAPI
|
import com.hexated.SoraStream.Companion.base64DecodeAPI
|
||||||
|
@ -10,7 +11,6 @@ import com.hexated.SoraStream.Companion.filmxyAPI
|
||||||
import com.hexated.SoraStream.Companion.fmoviesAPI
|
import com.hexated.SoraStream.Companion.fmoviesAPI
|
||||||
import com.hexated.SoraStream.Companion.gdbot
|
import com.hexated.SoraStream.Companion.gdbot
|
||||||
import com.hexated.SoraStream.Companion.malsyncAPI
|
import com.hexated.SoraStream.Companion.malsyncAPI
|
||||||
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.watchOnlineAPI
|
import com.hexated.SoraStream.Companion.watchOnlineAPI
|
||||||
|
@ -41,11 +41,9 @@ import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=")
|
val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=")
|
||||||
const val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
const val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
const val otakuzBaseUrl = "https://otakuz.live/"
|
|
||||||
val encodedIndex = arrayOf(
|
val encodedIndex = arrayOf(
|
||||||
"GamMovies",
|
"GamMovies",
|
||||||
"JSMovies",
|
"JSMovies",
|
||||||
|
@ -1055,52 +1053,6 @@ suspend fun getCrunchyrollIdFromMalSync(aniId: String?): String? {
|
||||||
?: regex.find("$crunchyroll")?.groupValues?.getOrNull(1)
|
?: regex.find("$crunchyroll")?.groupValues?.getOrNull(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun convertTmdbToAnimeId(
|
suspend fun convertTmdbToAnimeId(
|
||||||
title: String?,
|
title: String?,
|
||||||
date: String?,
|
date: String?,
|
||||||
|
@ -1655,161 +1607,6 @@ private enum class Symbol(val decimalValue: Int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 DumpUtils {
|
object DumpUtils {
|
||||||
|
|
||||||
private val deviceId = getDeviceId()
|
private val deviceId = getDeviceId()
|
||||||
|
@ -1926,4 +1723,87 @@ object RSAEncryptionHelper {
|
||||||
exception.printStackTrace()
|
exception.printStackTrace()
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object AesHelper {
|
||||||
|
|
||||||
|
fun cryptoAESHandler(
|
||||||
|
data: String,
|
||||||
|
pass: ByteArray,
|
||||||
|
encrypt: Boolean = true,
|
||||||
|
padding: String = "AES/CBC/PKCS5PADDING",
|
||||||
|
): String? {
|
||||||
|
val parse = AppUtils.tryParseJson<AesData>(data) ?: return null
|
||||||
|
val (key, iv) = generateKeyAndIv(pass, parse.s.hexToByteArray()) ?: throw ErrorLoadingException("failed to generate key")
|
||||||
|
val cipher = Cipher.getInstance(padding)
|
||||||
|
return if (!encrypt) {
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||||
|
String(cipher.doFinal(base64DecodeArray(parse.ct)))
|
||||||
|
} else {
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||||
|
base64Encode(cipher.doFinal(parse.ct.toByteArray()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/41434590/8166854
|
||||||
|
private fun generateKeyAndIv(
|
||||||
|
password: ByteArray,
|
||||||
|
salt: ByteArray,
|
||||||
|
hashAlgorithm: String = "MD5",
|
||||||
|
keyLength: Int = 32,
|
||||||
|
ivLength: Int = 16,
|
||||||
|
iterations: Int = 1
|
||||||
|
): List<ByteArray>? {
|
||||||
|
|
||||||
|
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||||
|
val digestLength = md.digestLength
|
||||||
|
val targetKeySize = keyLength + ivLength
|
||||||
|
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
|
||||||
|
val generatedData = ByteArray(requiredLength)
|
||||||
|
var generatedLength = 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
md.reset()
|
||||||
|
|
||||||
|
while (generatedLength < targetKeySize) {
|
||||||
|
if (generatedLength > 0)
|
||||||
|
md.update(
|
||||||
|
generatedData,
|
||||||
|
generatedLength - digestLength,
|
||||||
|
digestLength
|
||||||
|
)
|
||||||
|
|
||||||
|
md.update(password)
|
||||||
|
md.update(salt, 0, 8)
|
||||||
|
md.digest(generatedData, generatedLength, digestLength)
|
||||||
|
|
||||||
|
for (i in 1 until iterations) {
|
||||||
|
md.update(generatedData, generatedLength, digestLength)
|
||||||
|
md.digest(generatedData, generatedLength, digestLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedLength += digestLength
|
||||||
|
}
|
||||||
|
return listOf(
|
||||||
|
generatedData.copyOfRange(0, keyLength),
|
||||||
|
generatedData.copyOfRange(keyLength, targetKeySize)
|
||||||
|
)
|
||||||
|
} catch (e: DigestException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.hexToByteArray(): ByteArray {
|
||||||
|
check(length % 2 == 0) { "Must have an even length" }
|
||||||
|
return chunked(2)
|
||||||
|
.map { it.toInt(16).toByte() }
|
||||||
|
.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class AesData(
|
||||||
|
@JsonProperty("ct") val ct: String,
|
||||||
|
@JsonProperty("iv") val iv: String,
|
||||||
|
@JsonProperty("s") val s: String
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue