mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
update few providers
This commit is contained in:
parent
471a1703a3
commit
dd612e12bc
32 changed files with 370 additions and 248 deletions
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 1
|
||||
version = 2
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -150,8 +150,8 @@ class Animasu : MainAPI() {
|
|||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
if(!link.isM3u8) getIndexQuality(quality) else link.quality,
|
||||
link.isM3u8,
|
||||
if(link.type != ExtractorLinkType.M3U8) getIndexQuality(quality) else link.quality,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 8
|
||||
version = 9
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.hexated
|
|||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import com.lagradost.nicehttp.NiceResponse
|
||||
|
@ -144,7 +145,7 @@ class AnimeSailProvider : MainAPI() {
|
|||
Jsoup.parse(base64Decode(it.attr("data-em"))).select("iframe").attr("src")
|
||||
?: throw ErrorLoadingException("No iframe found")
|
||||
)
|
||||
|
||||
val quality = getIndexQuality(it.text())
|
||||
when {
|
||||
iframe.startsWith("$mainUrl/utils/player/arch/") || iframe.startsWith(
|
||||
"$mainUrl/utils/player/race/"
|
||||
|
@ -156,15 +157,13 @@ class AnimeSailProvider : MainAPI() {
|
|||
iframe.contains("/race/") -> "Race"
|
||||
else -> this.name
|
||||
}
|
||||
val quality =
|
||||
Regex("\\.(\\d{3,4})\\.").find(link)?.groupValues?.get(1)
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
source = source,
|
||||
name = source,
|
||||
url = link,
|
||||
referer = mainUrl,
|
||||
quality = quality?.toIntOrNull() ?: Qualities.Unknown.value
|
||||
quality = quality
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -175,16 +174,16 @@ class AnimeSailProvider : MainAPI() {
|
|||
val link = "https://rasa-cintaku-semakin-berantai.xyz/v/${
|
||||
iframe.substringAfter("id=").substringBefore("&token")
|
||||
}"
|
||||
loadExtractor(link, mainUrl, subtitleCallback, callback)
|
||||
loadFixedExtractor(link, quality, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
iframe.startsWith("$mainUrl/utils/player/framezilla/") || iframe.startsWith("https://uservideo.xyz") -> {
|
||||
request(iframe, ref = data).document.select("iframe").attr("src")
|
||||
.let { link ->
|
||||
loadExtractor(fixUrl(link), mainUrl, subtitleCallback, callback)
|
||||
loadFixedExtractor(fixUrl(link), quality, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
loadExtractor(iframe, mainUrl, subtitleCallback, callback)
|
||||
loadFixedExtractor(iframe, quality, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -193,4 +192,32 @@ class AnimeSailProvider : MainAPI() {
|
|||
return true
|
||||
}
|
||||
|
||||
private suspend fun loadFixedExtractor(
|
||||
url: String,
|
||||
quality: Int?,
|
||||
referer: String? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
loadExtractor(url, referer, subtitleCallback) { link ->
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
link.name,
|
||||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
if(link.type == ExtractorLinkType.M3U8) link.quality else quality ?: Qualities.Unknown.value,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIndexQuality(str: String): Int {
|
||||
return Regex("(\\d{3,4})[pP]").find(str)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
?: Qualities.Unknown.value
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 5
|
||||
version = 6
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -158,7 +158,7 @@ open class Aniworld : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
link.quality,
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 10
|
||||
version = 11
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -121,8 +121,8 @@ class Nodrakorid : DutaMovie() {
|
|||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
if(link.isM3u8) link.quality else quality ?: Qualities.Unknown.value,
|
||||
link.isM3u8,
|
||||
if(link.type == ExtractorLinkType.M3U8) link.quality else quality ?: Qualities.Unknown.value,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 10
|
||||
version = 11
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -213,7 +213,7 @@ class Hdfilmcehennemi : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
link.quality,
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 14
|
||||
version = 15
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.hexated
|
|||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.extractors.helper.AesHelper
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
@ -10,12 +11,7 @@ import com.lagradost.cloudstream3.utils.getQualityFromName
|
|||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URI
|
||||
import java.security.DigestException
|
||||
import java.security.MessageDigest
|
||||
import java.util.ArrayList
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class KuronimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://45.12.2.26"
|
||||
|
@ -186,10 +182,11 @@ class KuronimeProvider : MainAPI() {
|
|||
|
||||
argamap(
|
||||
{
|
||||
val decrypt = cryptoAES(
|
||||
servers?.src ?: return@argamap,
|
||||
val decrypt = AesHelper.cryptoAESHandler(
|
||||
base64Decode(servers?.src ?: return@argamap),
|
||||
KEY.toByteArray(),
|
||||
false
|
||||
false,
|
||||
"AES/CBC/NoPadding"
|
||||
)
|
||||
val source =
|
||||
tryParseJson<Sources>(decrypt?.toJsonFormat())?.src?.replace("\\", "")
|
||||
|
@ -206,10 +203,11 @@ class KuronimeProvider : MainAPI() {
|
|||
)
|
||||
},
|
||||
{
|
||||
val decrypt = cryptoAES(
|
||||
servers?.mirror ?: return@argamap,
|
||||
val decrypt = AesHelper.cryptoAESHandler(
|
||||
base64Decode(servers?.mirror ?: return@argamap),
|
||||
KEY.toByteArray(),
|
||||
false
|
||||
false,
|
||||
"AES/CBC/NoPadding"
|
||||
)
|
||||
tryParseJson<Mirrors>(decrypt)?.embed?.map { embed ->
|
||||
embed.value.apmap {
|
||||
|
@ -249,7 +247,7 @@ class KuronimeProvider : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
getQualityFromName(quality),
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
@ -263,86 +261,6 @@ class KuronimeProvider : MainAPI() {
|
|||
}
|
||||
}
|
||||
|
||||
// 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.decodeHex(): ByteArray {
|
||||
check(length % 2 == 0) { "Must have an even length" }
|
||||
return chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
private fun cryptoAES(
|
||||
data: String,
|
||||
pass: ByteArray,
|
||||
encrypt: Boolean = true
|
||||
): String? {
|
||||
val json = tryParseJson<AesData>(base64Decode(data))
|
||||
?: throw ErrorLoadingException("No Data Found")
|
||||
val (key, iv) = generateKeyAndIv(pass, json.s.decodeHex()) ?: return null
|
||||
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
|
||||
return if (!encrypt) {
|
||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||
String(cipher.doFinal(base64DecodeArray(json.ct)))
|
||||
} else {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||
base64Encode(cipher.doFinal(json.ct.toByteArray()))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
data class AesData(
|
||||
@JsonProperty("ct") val ct: String,
|
||||
@JsonProperty("iv") val iv: String,
|
||||
@JsonProperty("s") val s: String
|
||||
)
|
||||
|
||||
data class Mirrors(
|
||||
@JsonProperty("embed") val embed: Map<String, Map<String, String>> = emptyMap(),
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 7
|
||||
version = 8
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -17,7 +17,7 @@ class Paistream : Streampai() {
|
|||
}
|
||||
|
||||
class TvMinioppai : Streampai() {
|
||||
override val name = "Tv.Minioppai"
|
||||
override val name = "Minioppai"
|
||||
override val mainUrl = "https://tv.minioppai.org"
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class Minioppai : MainAPI() {
|
|||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/watch" to "New Episode",
|
||||
"$mainUrl/popular" to "Popular Hentai",
|
||||
"$mainUrl/populars" to "Popular Hentai",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 5
|
||||
version = 6
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -170,8 +170,8 @@ class Nekopoi : MainAPI() {
|
|||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
if (link.isM3u8) link.quality else it.first,
|
||||
link.isM3u8,
|
||||
if (link.type == ExtractorLinkType.M3U8) link.quality else it.first,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -164,7 +164,7 @@ class Nimegami : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
getQualityFromName(quality),
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 22
|
||||
version = 23
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -195,7 +195,7 @@ class OploverzProvider : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
name.fixQuality(),
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 13
|
||||
version = 14
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -236,7 +236,7 @@ class OtakudesuProvider : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
quality,
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 13
|
||||
version = 14
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -203,7 +203,7 @@ class Samehadaku : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
name.fixQuality(),
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
// use an integer for version numbers
|
||||
version = 163
|
||||
version = 164
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
|
|
162
SoraStream/src/main/kotlin/com/hexated/Extractors.kt
Normal file
162
SoraStream/src/main/kotlin/com/hexated/Extractors.kt
Normal file
|
@ -0,0 +1,162 @@
|
|||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.extractors.Filesim
|
||||
import com.lagradost.cloudstream3.extractors.GMPlayer
|
||||
import com.lagradost.cloudstream3.extractors.StreamSB
|
||||
import com.lagradost.cloudstream3.extractors.Voe
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
|
||||
open class Playm4u : ExtractorApi() {
|
||||
override val name = "Playm4u"
|
||||
override val mainUrl = "https://play9str.playm4u.xyz"
|
||||
override val requiresReferer = true
|
||||
private val password = "plhq@@@22"
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val document = app.get(url, referer = referer).document
|
||||
val script = document.selectFirst("script:containsData(idfile =)")?.data() ?: return
|
||||
val passScript = document.selectFirst("script:containsData(domain_ref =)")?.data() ?: return
|
||||
|
||||
val pass = passScript.substringAfter("CryptoJS.MD5('").substringBefore("')")
|
||||
val amount = passScript.substringAfter(".toString()), ").substringBefore("));").toInt()
|
||||
|
||||
val idFile = "idfile\\s*=\\s*[\"'](\\S+)[\"'];".findIn(script)
|
||||
val idUser = "idUser\\s*=\\s*[\"'](\\S+)[\"'];".findIn(script)
|
||||
val domainApi = "DOMAIN_API\\s*=\\s*[\"'](\\S+)[\"'];".findIn(script)
|
||||
val nameKeyV3 = "NameKeyV3\\s*=\\s*[\"'](\\S+)[\"'];".findIn(script)
|
||||
val dataEnc = caesarShift(
|
||||
mahoa(
|
||||
"Win32|$idUser|$idFile|$referer",
|
||||
md5(pass)
|
||||
), amount
|
||||
).toHex()
|
||||
|
||||
val captchaKey =
|
||||
document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]")
|
||||
.attr("src").substringAfter("render=")
|
||||
val token = getCaptchaToken(
|
||||
url,
|
||||
captchaKey,
|
||||
referer = referer
|
||||
)
|
||||
|
||||
val source = app.post(
|
||||
domainApi, data = mapOf(
|
||||
"namekey" to nameKeyV3,
|
||||
"token" to "$token",
|
||||
"referrer" to "$referer",
|
||||
"data" to "$dataEnc|${md5(dataEnc + password)}",
|
||||
), referer = "$mainUrl/"
|
||||
).parsedSafe<Source>()
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
source?.data ?: return,
|
||||
"$mainUrl/",
|
||||
Qualities.P1080.value,
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
source.sub?.substringBefore("|")?.toLanguage() ?: return,
|
||||
source.sub.substringAfter("|"),
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private fun caesarShift(str: String, amount: Int): String {
|
||||
var output = ""
|
||||
val adjustedAmount = if (amount < 0) amount + 26 else amount
|
||||
for (element in str) {
|
||||
var c = element
|
||||
if (c.isLetter()) {
|
||||
val code = c.code
|
||||
c = when (code) {
|
||||
in 65..90 -> ((code - 65 + adjustedAmount) % 26 + 65).toChar()
|
||||
in 97..122 -> ((code - 97 + adjustedAmount) % 26 + 97).toChar()
|
||||
else -> c
|
||||
}
|
||||
}
|
||||
output += c
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private fun mahoa(input: String, key: String): String {
|
||||
val a = CryptoJS.encrypt(key, input)
|
||||
return a.replace("U2FsdGVkX1", "")
|
||||
.replace("/", "|a")
|
||||
.replace("+", "|b")
|
||||
.replace("=", "|c")
|
||||
.replace("|", "-z")
|
||||
}
|
||||
|
||||
private fun md5(input: String): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0')
|
||||
}
|
||||
|
||||
private fun String.toHex(): String {
|
||||
return this.toByteArray().joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
private fun String.findIn(data: String): String {
|
||||
return this.toRegex().find(data)?.groupValues?.get(1) ?: ""
|
||||
}
|
||||
|
||||
private fun String.toLanguage() : String {
|
||||
return if(this == "EN") "English" else this
|
||||
}
|
||||
|
||||
data class Source(
|
||||
@JsonProperty("data") val data: String? = null,
|
||||
@JsonProperty("sub") val sub: String? = null,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
class TravelR : GMPlayer() {
|
||||
override val name = "TravelR"
|
||||
override val mainUrl = "https://travel-russia.xyz"
|
||||
}
|
||||
|
||||
class Mwish : Filesim() {
|
||||
override val name = "Mwish"
|
||||
override var mainUrl = "https://mwish.pro"
|
||||
}
|
||||
|
||||
class Animefever : Filesim() {
|
||||
override val name = "Animefever"
|
||||
override var mainUrl = "https://animefever.fun"
|
||||
}
|
||||
|
||||
class Multimovies : Filesim() {
|
||||
override val name = "Multimovies"
|
||||
override var mainUrl = "https://multimovies.cloud"
|
||||
}
|
||||
|
||||
class MultimoviesSB : StreamSB() {
|
||||
override var name = "Multimovies"
|
||||
override var mainUrl = "https://multimovies.website"
|
||||
}
|
||||
|
||||
class Yipsu : Voe() {
|
||||
override val name = "Yipsu"
|
||||
override var mainUrl = "https://yip.su"
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.hexated
|
||||
|
||||
import com.hexated.AesHelper.cryptoAESHandler
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
|
@ -10,6 +9,7 @@ import com.lagradost.cloudstream3.extractors.Filesim
|
|||
import com.lagradost.cloudstream3.extractors.GMPlayer
|
||||
import com.lagradost.cloudstream3.extractors.StreamSB
|
||||
import com.lagradost.cloudstream3.extractors.Voe
|
||||
import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler
|
||||
import com.lagradost.cloudstream3.extractors.helper.GogoHelper
|
||||
import com.lagradost.cloudstream3.network.CloudflareKiller
|
||||
import com.lagradost.nicehttp.RequestBodyTypes
|
||||
|
@ -127,7 +127,7 @@ object SoraExtractor : SoraStream() {
|
|||
link.url,
|
||||
link.referer,
|
||||
if (link.name == "VidSrc") Qualities.P1080.value else link.quality,
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
@ -273,7 +273,7 @@ object SoraExtractor : SoraStream() {
|
|||
video.url,
|
||||
video.referer,
|
||||
Qualities.P1080.value,
|
||||
video.isM3u8,
|
||||
video.type,
|
||||
video.headers,
|
||||
video.extractorData
|
||||
)
|
||||
|
@ -415,7 +415,7 @@ object SoraExtractor : SoraStream() {
|
|||
} else {
|
||||
"$idlixAPI/episode/$fixTitle-season-$season-episode-$episode"
|
||||
}
|
||||
invokeWpmovies(url, subtitleCallback, callback, encrypt = true, key = "\\x5a\\x6d\\x5a\\x6c\\x4e\\x7a\\x55\\x79\\x4d\\x54\\x56\\x6a\\x5a\\x47\\x52\\x69\\x5a\\x44\\x55\\x30\\x5a\\x6d\\x59\\x35\\x4f\\x57\\x45\\x33\\x4d\\x44\\x4a\\x69\\x4e\\x32\\x4a\\x6c\\x4f\\x54\\x42\\x6c\\x4e\\x7a\\x49\\x3d")
|
||||
invokeWpmovies(url, subtitleCallback, callback, encrypt = true)
|
||||
}
|
||||
|
||||
suspend fun invokeMultimovies(
|
||||
|
@ -480,7 +480,7 @@ object SoraExtractor : SoraStream() {
|
|||
)
|
||||
val source = tryParseJson<ResponseHash>(json.text)?.let {
|
||||
when {
|
||||
encrypt -> cryptoAESHandler(it.embed_url,(key ?: return@apmap).toByteArray(), false)?.fixBloat()
|
||||
encrypt -> cryptoAESHandler(it.embed_url,(it.key ?: return@apmap).toByteArray(), false)?.fixBloat()
|
||||
fixIframe -> Jsoup.parse(it.embed_url).select("IFRAME").attr("SRC")
|
||||
else -> it.embed_url
|
||||
}
|
||||
|
@ -711,10 +711,10 @@ object SoraExtractor : SoraStream() {
|
|||
link.url,
|
||||
link.referer,
|
||||
when {
|
||||
link.isM3u8 -> link.quality
|
||||
link.type == ExtractorLinkType.M3U8 -> link.quality
|
||||
else -> getQualityFromName(it.first)
|
||||
},
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
@ -1039,10 +1039,10 @@ object SoraExtractor : SoraStream() {
|
|||
link.url,
|
||||
link.referer,
|
||||
when {
|
||||
link.isM3u8 -> link.quality
|
||||
link.type == ExtractorLinkType.M3U8 -> link.quality
|
||||
else -> it.third
|
||||
},
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
@ -1517,7 +1517,9 @@ object SoraExtractor : SoraStream() {
|
|||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val res = app.get("$m4uhdAPI/search/${title.createSlug()}.html").document
|
||||
val req = app.get("$m4uhdAPI/search/${title.createSlug()}.html")
|
||||
val referer = getBaseUrl(req.url)
|
||||
val res = req.document
|
||||
val scriptData = res.select("div.row div.item").map {
|
||||
Triple(
|
||||
it.selectFirst("img.imagecover")?.attr("title"),
|
||||
|
@ -1538,7 +1540,7 @@ object SoraExtractor : SoraStream() {
|
|||
}
|
||||
}
|
||||
|
||||
val link = fixUrl(script?.third ?: return, m4uhdAPI)
|
||||
val link = fixUrl(script?.third ?: return, referer)
|
||||
val request = app.get(link)
|
||||
var cookiesSet = request.headers.filter { it.first == "set-cookie" }
|
||||
var xsrf =
|
||||
|
@ -1558,7 +1560,7 @@ object SoraExtractor : SoraStream() {
|
|||
?: return
|
||||
val idepisode = episodeData.select("button").attr("idepisode") ?: return
|
||||
val requestEmbed = app.post(
|
||||
"$m4uhdAPI/ajaxtv", data = mapOf(
|
||||
"$referer/ajaxtv", data = mapOf(
|
||||
"idepisode" to idepisode, "_token" to "$token"
|
||||
), referer = link, headers = mapOf(
|
||||
"X-Requested-With" to "XMLHttpRequest",
|
||||
|
@ -1572,14 +1574,16 @@ object SoraExtractor : SoraStream() {
|
|||
cookiesSet.find { it.second.contains("XSRF-TOKEN") }?.second?.substringAfter("XSRF-TOKEN=")
|
||||
?.substringBefore(";")
|
||||
session =
|
||||
cookiesSet.find { it.second.contains("laravel_session") }?.second?.substringAfter("laravel_session=")
|
||||
cookiesSet.find { it.second.contains("laravel_session") }?.second?.substringAfter(
|
||||
"laravel_session="
|
||||
)
|
||||
?.substringBefore(";")
|
||||
requestEmbed.document.select("div.le-server span").map { it.attr("data") }
|
||||
}
|
||||
|
||||
m4uData.apmap { data ->
|
||||
val iframe = app.post(
|
||||
"$m4uhdAPI/ajax",
|
||||
"$referer/ajax",
|
||||
data = mapOf(
|
||||
"m4u" to data, "_token" to "$token"
|
||||
),
|
||||
|
@ -1594,7 +1598,7 @@ object SoraExtractor : SoraStream() {
|
|||
),
|
||||
).document.select("iframe").attr("src")
|
||||
|
||||
loadExtractor(iframe, m4uhdAPI, subtitleCallback, callback)
|
||||
loadExtractor(iframe, referer, subtitleCallback, callback)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2902,33 +2906,3 @@ object SoraExtractor : SoraStream() {
|
|||
|
||||
}
|
||||
|
||||
class TravelR : GMPlayer() {
|
||||
override val name = "TravelR"
|
||||
override val mainUrl = "https://travel-russia.xyz"
|
||||
}
|
||||
|
||||
class Mwish : Filesim() {
|
||||
override val name = "Mwish"
|
||||
override var mainUrl = "https://mwish.pro"
|
||||
}
|
||||
|
||||
class Animefever : Filesim() {
|
||||
override val name = "Animefever"
|
||||
override var mainUrl = "https://animefever.fun"
|
||||
}
|
||||
|
||||
class Multimovies : Filesim() {
|
||||
override val name = "Multimovies"
|
||||
override var mainUrl = "https://multimovies.cloud"
|
||||
}
|
||||
|
||||
class MultimoviesSB : StreamSB() {
|
||||
override var name = "Multimovies"
|
||||
override var mainUrl = "https://multimovies.website"
|
||||
}
|
||||
|
||||
class Yipsu : Voe() {
|
||||
override val name = "Yipsu"
|
||||
override var mainUrl = "https://yip.su"
|
||||
}
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ open class SoraStream : TmdbProvider() {
|
|||
const val emoviesAPI = "https://emovies.si"
|
||||
const val pobmoviesAPI = "https://pobmovies.cam"
|
||||
const val fourCartoonAPI = "https://4cartoon.net"
|
||||
const val multimoviesAPI = "https://multimovies.xyz"
|
||||
const val multimoviesAPI = "https://multi-movies.xyz"
|
||||
const val netmoviesAPI = "https://netmovies.to"
|
||||
const val momentAPI = "https://moment-explanation-i-244.site"
|
||||
const val doomoviesAPI = "https://doomovies.net"
|
||||
|
@ -539,7 +539,7 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
invokeM4uhd(
|
||||
if(!res.isAnime) invokeM4uhd(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
|
|
|
@ -257,7 +257,7 @@ class SoraStreamLite : SoraStream() {
|
|||
invokeFwatayako(res.imdbId, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
invokeM4uhd(
|
||||
if(!res.isAnime) invokeM4uhd(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
|
|
|
@ -17,5 +17,6 @@ class SoraStreamPlugin: Plugin() {
|
|||
registerExtractorAPI(Yipsu())
|
||||
registerExtractorAPI(Mwish())
|
||||
registerExtractorAPI(TravelR())
|
||||
registerExtractorAPI(Playm4u())
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package com.hexated
|
||||
|
||||
import android.util.Base64
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.hexated.DumpUtils.queryApi
|
||||
import com.hexated.SoraStream.Companion.anilistAPI
|
||||
import com.hexated.SoraStream.Companion.base64DecodeAPI
|
||||
|
@ -41,6 +40,7 @@ import javax.crypto.Cipher
|
|||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.min
|
||||
|
||||
val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=")
|
||||
const val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
@ -1143,10 +1143,10 @@ suspend fun loadCustomExtractor(
|
|||
link.url,
|
||||
link.referer,
|
||||
when {
|
||||
link.isM3u8 -> link.quality
|
||||
link.type == ExtractorLinkType.M3U8 -> link.quality
|
||||
else -> quality ?: link.quality
|
||||
},
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
@ -1725,85 +1725,125 @@ object RSAEncryptionHelper {
|
|||
}
|
||||
}
|
||||
|
||||
object AesHelper {
|
||||
// code found on https://stackoverflow.com/a/63701411
|
||||
|
||||
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()))
|
||||
}
|
||||
/**
|
||||
* Conforming with CryptoJS AES method
|
||||
*/
|
||||
// see https://gist.github.com/thackerronak/554c985c3001b16810af5fc0eb5c358f
|
||||
@Suppress("unused", "FunctionName", "SameParameterValue")
|
||||
object CryptoJS {
|
||||
|
||||
private const val KEY_SIZE = 256
|
||||
private const val IV_SIZE = 128
|
||||
private const val HASH_CIPHER = "AES/CBC/PKCS7Padding"
|
||||
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)
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/41434590/8166854
|
||||
private fun generateKeyAndIv(
|
||||
/**
|
||||
* 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,
|
||||
hashAlgorithm: String = "MD5",
|
||||
keyLength: Int = 32,
|
||||
ivLength: Int = 16,
|
||||
iterations: Int = 1
|
||||
): List<ByteArray>? {
|
||||
resultKey: ByteArray,
|
||||
resultIv: ByteArray
|
||||
): ByteArray {
|
||||
return EvpKDF(password, keySize, ivSize, salt, 1, KDF_DIGEST, resultKey, resultIv)
|
||||
}
|
||||
|
||||
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
|
||||
@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)
|
||||
}
|
||||
return listOf(
|
||||
generatedData.copyOfRange(0, keyLength),
|
||||
generatedData.copyOfRange(keyLength, targetKeySize)
|
||||
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)
|
||||
)
|
||||
} catch (e: DigestException) {
|
||||
return null
|
||||
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 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