diff --git a/Animasu/build.gradle.kts b/Animasu/build.gradle.kts index 4b81579b..4c3d51d1 100644 --- a/Animasu/build.gradle.kts +++ b/Animasu/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { diff --git a/Animasu/src/main/kotlin/com/hexated/Animasu.kt b/Animasu/src/main/kotlin/com/hexated/Animasu.kt index e693a672..e6e38833 100644 --- a/Animasu/src/main/kotlin/com/hexated/Animasu.kt +++ b/Animasu/src/main/kotlin/com/hexated/Animasu.kt @@ -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 ) diff --git a/AnimeSailProvider/build.gradle.kts b/AnimeSailProvider/build.gradle.kts index d0914891..73173634 100644 --- a/AnimeSailProvider/build.gradle.kts +++ b/AnimeSailProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 8 +version = 9 cloudstream { diff --git a/AnimeSailProvider/src/main/kotlin/com/hexated/AnimeSailProvider.kt b/AnimeSailProvider/src/main/kotlin/com/hexated/AnimeSailProvider.kt index 45094af4..da62e664 100644 --- a/AnimeSailProvider/src/main/kotlin/com/hexated/AnimeSailProvider.kt +++ b/AnimeSailProvider/src/main/kotlin/com/hexated/AnimeSailProvider.kt @@ -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 + } + } \ No newline at end of file diff --git a/Aniworld/build.gradle.kts b/Aniworld/build.gradle.kts index d303a028..86c61272 100644 --- a/Aniworld/build.gradle.kts +++ b/Aniworld/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 5 +version = 6 cloudstream { diff --git a/Aniworld/src/main/kotlin/com/hexated/Aniworld.kt b/Aniworld/src/main/kotlin/com/hexated/Aniworld.kt index a5c6b08f..f55ac59e 100644 --- a/Aniworld/src/main/kotlin/com/hexated/Aniworld.kt +++ b/Aniworld/src/main/kotlin/com/hexated/Aniworld.kt @@ -158,7 +158,7 @@ open class Aniworld : MainAPI() { link.url, link.referer, link.quality, - link.isM3u8, + link.type, link.headers, link.extractorData ) diff --git a/Gomov/build.gradle.kts b/Gomov/build.gradle.kts index a811b0e8..cae88ee2 100644 --- a/Gomov/build.gradle.kts +++ b/Gomov/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 10 +version = 11 cloudstream { diff --git a/Gomov/src/main/kotlin/com/hexated/Nodrakorid.kt b/Gomov/src/main/kotlin/com/hexated/Nodrakorid.kt index 846c6ee0..afc8e4da 100644 --- a/Gomov/src/main/kotlin/com/hexated/Nodrakorid.kt +++ b/Gomov/src/main/kotlin/com/hexated/Nodrakorid.kt @@ -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 ) diff --git a/Hdfilmcehennemi/build.gradle.kts b/Hdfilmcehennemi/build.gradle.kts index 991be08d..3b75abc6 100644 --- a/Hdfilmcehennemi/build.gradle.kts +++ b/Hdfilmcehennemi/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 10 +version = 11 cloudstream { diff --git a/Hdfilmcehennemi/src/main/kotlin/com/hexated/Hdfilmcehennemi.kt b/Hdfilmcehennemi/src/main/kotlin/com/hexated/Hdfilmcehennemi.kt index 9e80daaa..cce3f58e 100644 --- a/Hdfilmcehennemi/src/main/kotlin/com/hexated/Hdfilmcehennemi.kt +++ b/Hdfilmcehennemi/src/main/kotlin/com/hexated/Hdfilmcehennemi.kt @@ -213,7 +213,7 @@ class Hdfilmcehennemi : MainAPI() { link.url, link.referer, link.quality, - link.isM3u8, + link.type, link.headers, link.extractorData ) diff --git a/KuronimeProvider/build.gradle.kts b/KuronimeProvider/build.gradle.kts index a4fc0461..74ca1975 100644 --- a/KuronimeProvider/build.gradle.kts +++ b/KuronimeProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 14 +version = 15 cloudstream { diff --git a/KuronimeProvider/src/main/kotlin/com/hexated/KuronimeProvider.kt b/KuronimeProvider/src/main/kotlin/com/hexated/KuronimeProvider.kt index 14a24c6c..9160beb0 100644 --- a/KuronimeProvider/src/main/kotlin/com/hexated/KuronimeProvider.kt +++ b/KuronimeProvider/src/main/kotlin/com/hexated/KuronimeProvider.kt @@ -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(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(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? { - - 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(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> = emptyMap(), ) diff --git a/Minioppai/build.gradle.kts b/Minioppai/build.gradle.kts index efcdc659..ee49ee06 100644 --- a/Minioppai/build.gradle.kts +++ b/Minioppai/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 7 +version = 8 cloudstream { diff --git a/Minioppai/src/main/kotlin/com/hexated/Extractors.kt b/Minioppai/src/main/kotlin/com/hexated/Extractors.kt index de2f8520..6a82e5d3 100644 --- a/Minioppai/src/main/kotlin/com/hexated/Extractors.kt +++ b/Minioppai/src/main/kotlin/com/hexated/Extractors.kt @@ -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" } diff --git a/Minioppai/src/main/kotlin/com/hexated/Minioppai.kt b/Minioppai/src/main/kotlin/com/hexated/Minioppai.kt index 0a263379..2dc220a1 100644 --- a/Minioppai/src/main/kotlin/com/hexated/Minioppai.kt +++ b/Minioppai/src/main/kotlin/com/hexated/Minioppai.kt @@ -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( diff --git a/Nekopoi/build.gradle.kts b/Nekopoi/build.gradle.kts index 0d3c4901..809c2b60 100644 --- a/Nekopoi/build.gradle.kts +++ b/Nekopoi/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 5 +version = 6 cloudstream { diff --git a/Nekopoi/src/main/kotlin/com/hexated/Nekopoi.kt b/Nekopoi/src/main/kotlin/com/hexated/Nekopoi.kt index c9a00826..124595b6 100644 --- a/Nekopoi/src/main/kotlin/com/hexated/Nekopoi.kt +++ b/Nekopoi/src/main/kotlin/com/hexated/Nekopoi.kt @@ -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 ) diff --git a/Nimegami/build.gradle.kts b/Nimegami/build.gradle.kts index f4617aee..afc730a5 100644 --- a/Nimegami/build.gradle.kts +++ b/Nimegami/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 3 +version = 4 cloudstream { diff --git a/Nimegami/src/main/kotlin/com/hexated/Nimegami.kt b/Nimegami/src/main/kotlin/com/hexated/Nimegami.kt index a2ed9aaf..3143bc15 100644 --- a/Nimegami/src/main/kotlin/com/hexated/Nimegami.kt +++ b/Nimegami/src/main/kotlin/com/hexated/Nimegami.kt @@ -164,7 +164,7 @@ class Nimegami : MainAPI() { link.url, link.referer, getQualityFromName(quality), - link.isM3u8, + link.type, link.headers, link.extractorData ) diff --git a/OploverzProvider/build.gradle.kts b/OploverzProvider/build.gradle.kts index 1f615de5..3b5d9597 100644 --- a/OploverzProvider/build.gradle.kts +++ b/OploverzProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 22 +version = 23 cloudstream { diff --git a/OploverzProvider/src/main/kotlin/com/hexated/OploverzProvider.kt b/OploverzProvider/src/main/kotlin/com/hexated/OploverzProvider.kt index 042e62f8..92547017 100644 --- a/OploverzProvider/src/main/kotlin/com/hexated/OploverzProvider.kt +++ b/OploverzProvider/src/main/kotlin/com/hexated/OploverzProvider.kt @@ -195,7 +195,7 @@ class OploverzProvider : MainAPI() { link.url, link.referer, name.fixQuality(), - link.isM3u8, + link.type, link.headers, link.extractorData ) diff --git a/OtakudesuProvider/build.gradle.kts b/OtakudesuProvider/build.gradle.kts index 2f4a7f48..39e00b68 100644 --- a/OtakudesuProvider/build.gradle.kts +++ b/OtakudesuProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 13 +version = 14 cloudstream { diff --git a/OtakudesuProvider/src/main/kotlin/com/hexated/OtakudesuProvider.kt b/OtakudesuProvider/src/main/kotlin/com/hexated/OtakudesuProvider.kt index 3bc73926..0a4b0dd3 100644 --- a/OtakudesuProvider/src/main/kotlin/com/hexated/OtakudesuProvider.kt +++ b/OtakudesuProvider/src/main/kotlin/com/hexated/OtakudesuProvider.kt @@ -236,7 +236,7 @@ class OtakudesuProvider : MainAPI() { link.url, link.referer, quality, - link.isM3u8, + link.type, link.headers, link.extractorData ) diff --git a/Samehadaku/build.gradle.kts b/Samehadaku/build.gradle.kts index c7f120a3..1c566566 100644 --- a/Samehadaku/build.gradle.kts +++ b/Samehadaku/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 13 +version = 14 cloudstream { diff --git a/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt b/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt index 78264113..a903d225 100644 --- a/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt +++ b/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt @@ -203,7 +203,7 @@ class Samehadaku : MainAPI() { link.url, link.referer, name.fixQuality(), - link.isM3u8, + link.type, link.headers, link.extractorData ) diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index a6a8ce22..10dc59bc 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.konan.properties.Properties // use an integer for version numbers -version = 163 +version = 164 android { defaultConfig { diff --git a/SoraStream/src/main/kotlin/com/hexated/Extractors.kt b/SoraStream/src/main/kotlin/com/hexated/Extractors.kt new file mode 100644 index 00000000..6def3d06 --- /dev/null +++ b/SoraStream/src/main/kotlin/com/hexated/Extractors.kt @@ -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() + + 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" +} \ No newline at end of file diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index 5efdec40..3f53a400 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -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(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" -} - diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index 46add238..ea77a419 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -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, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt index 2e7d99ab..2703133e 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt @@ -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, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt index 7bb7f262..3e0cda41 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt @@ -17,5 +17,6 @@ class SoraStreamPlugin: Plugin() { registerExtractorAPI(Yipsu()) registerExtractorAPI(Mwish()) registerExtractorAPI(TravelR()) + registerExtractorAPI(Playm4u()) } } \ No newline at end of file diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 3f640572..09f6f822 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -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(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? { + 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 - ) - } \ No newline at end of file