Merge branch 'master' into master

This commit is contained in:
IndusAryan 2023-09-10 00:38:41 +05:30 committed by GitHub
commit 013151104a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 872 additions and 783 deletions

View File

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

View File

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

View File

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

View File

@ -60,22 +60,22 @@ class AnimeIndoProvider : MainAPI() {
return if (uri.contains("/anime/")) {
uri
} else {
var title = uri.substringAfter("nonton/")
var title = uri.substringAfter("$mainUrl/")
title = when {
(title.contains("-episode")) && !(title.contains("-movie")) -> Regex("(.+)-episode").find(
title
)?.groupValues?.get(1).toString()
(title.contains("-movie")) -> Regex("(.+)-movie").find(title)?.groupValues?.get(
1
).toString()
(title.contains("-episode")) && !(title.contains("-movie")) -> title.substringBefore(
"-episode"
)
(title.contains("-movie")) -> title.substringBefore("-movie")
else -> title
}
"$mainUrl/anime/$title"
}
}
private fun Element.toSearchResult(): AnimeSearchResponse {
val title = this.selectFirst("div.titlex, h2.entry-title, h4")?.text()?.trim() ?: ""
val title = this.selectFirst("div.title, h2.entry-title, h4")?.text()?.trim() ?: ""
val href = getProperAnimeLink(this.selectFirst("a")!!.attr("href"))
val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src"))
val epNum = this.selectFirst("span.episode")?.ownText()?.replace(Regex("\\D"), "")?.trim()

View File

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

View File

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

View File

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

View File

@ -158,7 +158,7 @@ open class Aniworld : MainAPI() {
link.url,
link.referer,
link.quality,
link.isM3u8,
link.type,
link.headers,
link.extractorData
)

View File

@ -1,12 +1,12 @@
// use an integer for version numbers
version = 8
version = 11
cloudstream {
language = "id"
// All of these properties are optional, you can safely remove them
description = "Includes: DutaMovie, Ngefilm, Nodrakorid"
description = "Includes: DutaMovie, Ngefilm, Nodrakorid, Multiplex"
authors = listOf("Hexated")
/**

View File

@ -1,8 +1,11 @@
package com.hexated
import com.lagradost.cloudstream3.extractors.Filesim
import com.lagradost.cloudstream3.extractors.Gdriveplayer
import com.lagradost.cloudstream3.extractors.StreamSB
import com.lagradost.cloudstream3.extractors.*
class Doods : DoodLaExtractor() {
override var name = "Doods"
override var mainUrl = "https://doods.pro"
}
class Dutamovie21 : StreamSB() {
override var name = "Dutamovie21"

View File

@ -13,11 +13,13 @@ class GomovPlugin: Plugin() {
registerMainAPI(DutaMovie())
registerMainAPI(Ngefilm())
registerMainAPI(Nodrakorid())
registerMainAPI(Multiplex())
registerExtractorAPI(FilelionsTo())
registerExtractorAPI(Likessb())
registerExtractorAPI(DbGdriveplayer())
registerExtractorAPI(Dutamovie21())
registerExtractorAPI(Embedwish())
registerExtractorAPI(Doods())
registerExtractorAPI(Lylxan())
}
}

View File

@ -0,0 +1,15 @@
package com.hexated
import com.lagradost.cloudstream3.mainPageOf
class Multiplex : DutaMovie() {
override var mainUrl = "http://5.104.81.46"
override var name = "Multiplex"
override val mainPage = mainPageOf(
"country/usa/page/%d/" to "Movie",
"west-series/page/%d/" to "West Series",
"nonton-drama-korea/page/%d/" to "Drama Korea",
)
}

View File

@ -26,16 +26,30 @@ class Nodrakorid : DutaMovie() {
is TvSeriesLoadResponse -> {
val doc = app.get(url).document
this.comingSoon = false
this.episodes = doc.select("div.entry-content p:contains(Episode)").distinctBy {
it.text()
}.map { eps ->
val num = eps.text()
val endSibling = eps.nextElementSiblings().select("p:contains(Episode)").firstOrNull() ?: eps.nextElementSiblings().select("div.content-moviedata").firstOrNull()
val siblings = eps.nextElementSiblingsUntil(endSibling).map { ele ->
ele.ownText().filter { it.isDigit() }.toIntOrNull() to ele.select("a")
.map { it.attr("href") to it.text() }
}.filter { it.first != null }
Episode(siblings.toJson(), episode = Regex("Episode\\s?([0-9]+)").find(num)?.groupValues?.getOrNull(1)?.toIntOrNull())
this.episodes = when {
doc.select("div.vid-episodes a, div.gmr-listseries a").isNotEmpty() -> this.episodes
doc.select("div#download").isEmpty() -> {
doc.select("div.entry-content p:contains(Episode)").distinctBy {
it.text()
}.mapNotNull { eps ->
val endSibling = eps.nextElementSiblings().select("p:contains(Episode)").firstOrNull() ?: eps.nextElementSiblings().select("div.content-moviedata").firstOrNull()
val siblings = eps.nextElementSiblingsUntil(endSibling).map { ele ->
ele.ownText().filter { it.isDigit() }.toIntOrNull() to ele.select("a")
.map { it.attr("href") to it.text() }
}.filter { it.first != null }
Episode(siblings.toJson(), episode = eps.text().toEpisode())
}
}
else -> {
doc.select("div#download h3.title-download").mapNotNull { eps ->
val siblings = eps.nextElementSibling()?.select("li")?.map { ele ->
ele.text().filter { it.isDigit() }.toIntOrNull() to ele.select("a").map {
it.attr("href") to it.text().split(" ").first()
}
}?.filter { it.first != null }
Episode(siblings?.toJson() ?: return@mapNotNull null, episode = eps.text().toEpisode())
}
}
}
}
}
@ -83,6 +97,10 @@ class Nodrakorid : DutaMovie() {
}
}
private fun String.toEpisode() : Int? {
return Regex("(?i)Episode\\s?([0-9]+)").find(this)?.groupValues?.getOrNull(1)?.toIntOrNull()
}
private fun getBaseUrl(url: String): String {
return URI(url).let {
"${it.scheme}://${it.host}"
@ -103,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
)

View File

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

View File

@ -213,7 +213,7 @@ class Hdfilmcehennemi : MainAPI() {
link.url,
link.referer,
link.quality,
link.isM3u8,
link.type,
link.headers,
link.extractorData
)

View File

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

View File

@ -5,21 +5,24 @@ import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.network.CloudflareKiller
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.Session
import okhttp3.Interceptor
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import java.net.URI
class IdlixProvider : MainAPI() {
override var mainUrl = "https://tv.idlixprime.com"
override var mainUrl = "https://tv.idlixplus.net"
private var directUrl = mainUrl
override var name = "Idlix"
override val hasMainPage = true
override var lang = "id"
override val hasDownloadSupport = true
private val session = Session(Requests().baseClient)
private val cloudflareKiller by lazy { CloudflareKiller() }
private val interceptor by lazy { CloudflareInterceptor(cloudflareKiller) }
override val supportedTypes = setOf(
TvType.Movie,
TvType.TvSeries,
@ -27,6 +30,8 @@ class IdlixProvider : MainAPI() {
TvType.AsianDrama
)
private val 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"
override val mainPage = mainPageOf(
"$mainUrl/" to "Featured",
"$mainUrl/trending/page/?get=movies" to "Trending Movies",
@ -51,9 +56,9 @@ class IdlixProvider : MainAPI() {
val url = request.data.split("?")
val nonPaged = request.name == "Featured" && page <= 1
val req = if (nonPaged) {
session.get(request.data)
app.get(request.data, interceptor = interceptor)
} else {
session.get("${url.first()}$page/?${url.lastOrNull()}")
app.get("${url.first()}$page/?${url.lastOrNull()}", interceptor = interceptor)
}
mainUrl = getBaseUrl(req.url)
val document = req.document
@ -93,12 +98,13 @@ class IdlixProvider : MainAPI() {
return newMovieSearchResponse(title, href, TvType.Movie) {
this.posterUrl = posterUrl
this.quality = quality
posterHeaders = cloudflareKiller.getCookieHeaders(mainUrl).toMap()
}
}
override suspend fun search(query: String): List<SearchResponse> {
val req = session.get("$mainUrl/search/$query")
val req = app.get("$mainUrl/search/$query", interceptor = interceptor)
mainUrl = getBaseUrl(req.url)
val document = req.document
return document.select("div.result-item").map {
@ -108,12 +114,13 @@ class IdlixProvider : MainAPI() {
val posterUrl = it.selectFirst("img")!!.attr("src").toString()
newMovieSearchResponse(title, href, TvType.TvSeries) {
this.posterUrl = posterUrl
posterHeaders = cloudflareKiller.getCookieHeaders(mainUrl).toMap()
}
}
}
override suspend fun load(url: String): LoadResponse {
val request = session.get(url)
val request = app.get(url, interceptor = interceptor, referer = "$directUrl/")
directUrl = getBaseUrl(request.url)
val document = request.document
val title =
@ -142,6 +149,7 @@ class IdlixProvider : MainAPI() {
val recPosterUrl = it.selectFirst("img")?.attr("src").toString()
newTvSeriesSearchResponse(recName, recHref, TvType.TvSeries) {
this.posterUrl = recPosterUrl
posterHeaders = cloudflareKiller.getCookieHeaders(mainUrl).toMap()
}
}
@ -171,6 +179,7 @@ class IdlixProvider : MainAPI() {
addActors(actors)
this.recommendations = recommendations
addTrailer(trailer)
posterHeaders = cloudflareKiller.getCookieHeaders(mainUrl).toMap()
}
} else {
newMovieLoadResponse(title, url, TvType.Movie, url) {
@ -182,6 +191,7 @@ class IdlixProvider : MainAPI() {
addActors(actors)
this.recommendations = recommendations
addTrailer(trailer)
posterHeaders = cloudflareKiller.getCookieHeaders(mainUrl).toMap()
}
}
}
@ -193,7 +203,7 @@ class IdlixProvider : MainAPI() {
callback: (ExtractorLink) -> Unit
): Boolean {
val document = session.get(data).document
val document = app.get(data, interceptor = interceptor, referer = "$directUrl/").document
val id = document.select("meta#dooplay-ajax-counter").attr("data-postid")
val type = if (data.contains("/movie/")) "movie" else "tv"
@ -201,22 +211,20 @@ class IdlixProvider : MainAPI() {
it.attr("data-nume")
}.apmap { nume ->
safeApiCall {
var source = session.post(
url = "$directUrl/wp-admin/admin-ajax.php",
data = mapOf(
"action" to "doo_player_ajax",
"post" to id,
"nume" to nume,
"type" to type
),
headers = mapOf("X-Requested-With" to "XMLHttpRequest"),
referer = data
).let { tryParseJson<ResponseHash>(it.text) }?.embed_url ?: return@safeApiCall
val source = app.post(
url = "$directUrl/wp-admin/admin-ajax.php", data = mapOf(
"action" to "doo_player_ajax", "post" to id, "nume" to nume, "type" to type
), headers = mapOf("X-Requested-With" to "XMLHttpRequest"), referer = data, interceptor = interceptor
).let { tryParseJson<ResponseHash>(it.text) } ?: return@safeApiCall
if (source.startsWith("https://uservideo.xyz")) {
source = app.get(source).document.select("iframe").attr("src")
val password = if(source.key?.startsWith("\\x") == true) source.key else key
var decrypted = AesHelper.cryptoAESHandler(source.embed_url, password.toByteArray(), false)?.fixBloat() ?: return@safeApiCall
if (decrypted.startsWith("https://uservideo.xyz")) {
decrypted = app.get(decrypted).document.select("iframe").attr("src")
}
loadExtractor(source, directUrl, subtitleCallback, callback)
getUrl(decrypted, "$directUrl/", subtitleCallback, callback)
}
}
@ -224,9 +232,86 @@ class IdlixProvider : MainAPI() {
return true
}
class CloudflareInterceptor(private val cloudflareKiller: CloudflareKiller): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val doc = Jsoup.parse(response.peekBody(1024 * 1024).string())
if (doc.select("title").text() == "Just a moment...") {
return cloudflareKiller.intercept(chain)
}
return response
}
}
private fun String.fixBloat() : String {
return this.replace("\"", "").replace("\\", "")
}
private suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val document = app.get(url, referer = referer).document
val hash = url.split("/").last().substringAfter("data=")
val m3uLink = app.post(
url = "$mainUrl/player/index.php?data=$hash&do=getVideo",
data = mapOf("hash" to hash, "r" to "$referer"),
referer = referer,
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
).parsed<ResponseSource>().videoSource
M3u8Helper.generateM3u8(
this.name,
m3uLink,
"$referer",
).forEach(callback)
document.select("script").map { script ->
if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
val subData =
getAndUnpack(script.data()).substringAfter("\"tracks\":[").substringBefore("],")
tryParseJson<List<Tracks>>("[$subData]")?.map { subtitle ->
subtitleCallback.invoke(
SubtitleFile(
getLanguage(subtitle.label ?: ""),
subtitle.file
)
)
}
}
}
}
private fun getLanguage(str: String): String {
return when {
str.contains("indonesia", true) || str
.contains("bahasa", true) -> "Indonesian"
else -> str
}
}
data class ResponseSource(
@JsonProperty("hls") val hls: Boolean,
@JsonProperty("videoSource") val videoSource: String,
@JsonProperty("securedLink") val securedLink: String?,
)
data class Tracks(
@JsonProperty("kind") val kind: String?,
@JsonProperty("file") val file: String,
@JsonProperty("label") val label: String?,
)
data class ResponseHash(
@JsonProperty("embed_url") val embed_url: String,
@JsonProperty("key") val key: String?,
@JsonProperty("type") val type: String?,
)
}

View 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
)
}

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import org.jsoup.nodes.Element
import java.net.URLDecoder
class LayarKacaProvider : MainAPI() {
override var mainUrl = "https://tv1.lk21official.pro"
override var mainUrl = "https://tv3.lk21official.pro"
private var seriesUrl = "https://tv1.nontondrama.click"
override var name = "LayarKaca"
override val hasMainPage = true
@ -72,11 +72,11 @@ class LayarKacaProvider : MainAPI() {
}
override suspend fun search(query: String): List<SearchResponse> {
val document = app.get("$mainUrl/?s=$query").document
return document.select("div.search-item").map {
val title = it.selectFirst("h2 > a")!!.text().trim()
val href = fixUrl(it.selectFirst("a")!!.attr("href"))
val posterUrl = fixUrl(it.selectFirst("img.img-thumbnail")?.attr("src").toString())
val document = app.get("$mainUrl/search.php?s=$query").document
return document.select("div.search-item").mapNotNull {
val title = it.selectFirst("a")?.attr("title") ?: ""
val href = fixUrl(it.selectFirst("a")?.attr("href") ?: return@mapNotNull null)
val posterUrl = fixUrlNull(it.selectFirst("img.img-thumbnail")?.attr("src"))
newTvSeriesSearchResponse(title, href, TvType.TvSeries) {
this.posterUrl = posterUrl
}
@ -172,8 +172,6 @@ class LayarKacaProvider : MainAPI() {
return app.get(this, referer = "$seriesUrl/").document.select("div.embed iframe").attr("src")
}
private fun decode(input: String): String = URLDecoder.decode(input, "utf-8").replace(" ", "%20")
}
open class Emturbovid : ExtractorApi() {

View File

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

View File

@ -16,6 +16,11 @@ class Paistream : Streampai() {
override val mainUrl = "https://paistream.my.id"
}
class TvMinioppai : Streampai() {
override val name = "Minioppai"
override val mainUrl = "https://tv.minioppai.org"
}
open class Streampai : ExtractorApi() {
override val name = "Streampai"
override val mainUrl = "https://streampai.my.id"

View File

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

View File

@ -11,5 +11,6 @@ class MinioppaiPlugin: Plugin() {
registerMainAPI(Minioppai())
registerExtractorAPI(Streampai())
registerExtractorAPI(Paistream())
registerExtractorAPI(TvMinioppai())
}
}

View File

@ -11,7 +11,9 @@ import com.lagradost.cloudstream3.utils.loadExtractor
import org.jsoup.Jsoup
class Hdmovie2 : Movierulzhd() {
override var mainUrl = "https://hdmovie2.codes"
override var name = "Hdmovie2"
override val mainPage = mainPageOf(

View File

@ -11,7 +11,9 @@ import org.jsoup.nodes.Element
import java.net.URI
open class Movierulzhd : MainAPI() {
override var mainUrl = "https://movierulzvid.gold"
var directUrl = ""
override var name = "Movierulzhd"
override val hasMainPage = true

View File

@ -1,27 +0,0 @@
// use an integer for version numbers
version = 2
cloudstream {
language = "id"
// All of these properties are optional, you can safely remove them
// description = "Lorem Ipsum"
authors = listOf("Hexated")
/**
* Status int as the following:
* 0: Down
* 1: Ok
* 2: Slow
* 3: Beta only
* */
status = 1 // will be 3 if unspecified
tvTypes = listOf(
"AsianDrama",
"TvSeries",
"Movie",
)
iconUrl = "https://www.google.com/s2/favicons?domain=146.19.24.137&sz=%size%"
}

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.hexated"/>

View File

@ -1,188 +0,0 @@
package com.hexated
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
import org.jsoup.nodes.Element
class MultiplexProvider : MainAPI() {
override var mainUrl = "http://5.104.81.46"
override var name = "Multiplex"
override val hasMainPage = true
override var lang = "id"
override val hasDownloadSupport = true
override val supportedTypes = setOf(
TvType.Movie,
TvType.TvSeries,
TvType.AsianDrama
)
override val mainPage = mainPageOf(
"$mainUrl/genre/top-popular-movies/page/" to "Top Popolar Movies",
"$mainUrl/genre/series-ongoing/page/" to "Series Ongoing",
"$mainUrl/genre/series-barat/page/" to "Series Barat",
"$mainUrl/genre/series-korea/page/" to "Series Korea",
)
override suspend fun getMainPage(
page: Int,
request: MainPageRequest
): HomePageResponse {
val document = app.get(request.data + page).document
val home = document.select("article.item").mapNotNull {
it.toSearchResult()
}
return newHomePageResponse(request.name, home)
}
private fun Element.toSearchResult(): SearchResponse? {
val title = this.selectFirst("h2.entry-title > a")?.text()?.trim() ?: return null
val href = fixUrl(this.selectFirst("a")!!.attr("href"))
val posterUrl = fixUrlNull(this.selectFirst("a > img")?.attr("data-src"))
val quality = this.select("div.gmr-quality-item > a").text().trim()
return if (quality.isEmpty()) {
val episode = this.select("div.gmr-numbeps > span").text().toIntOrNull()
newAnimeSearchResponse(title, href, TvType.TvSeries) {
this.posterUrl = posterUrl
addSub(episode)
}
} else {
newMovieSearchResponse(title, href, TvType.Movie) {
this.posterUrl = posterUrl
addQuality(quality)
}
}
}
private fun Element.toBottomSearchResult(): SearchResponse? {
val title = this.selectFirst("a > span.idmuvi-rp-title")?.text()?.trim() ?: return null
val href = this.selectFirst("a")!!.attr("href")
val posterUrl = fixUrl(this.selectFirst("a > img")?.attr("data-src").toString())
return newMovieSearchResponse(title, href, TvType.Movie) {
this.posterUrl = posterUrl
}
}
override suspend fun search(query: String): List<SearchResponse> {
val link = "$mainUrl/?s=$query&post_type[]=post&post_type[]=tv"
val document = app.get(link).document
return document.select("article.item").mapNotNull {
it.toSearchResult()
}
}
override suspend fun load(url: String): LoadResponse {
val document = app.get(url).document
val title =
document.selectFirst("h1.entry-title")?.text()?.substringBefore("Season")?.trim()
.toString()
val poster =
fixUrl(document.selectFirst("figure.pull-left > img")?.attr("data-src").toString())
val tags = document.select("span.gmr-movie-genre:contains(Genre:) > a").map { it.text() }
val year =
document.select("span.gmr-movie-genre:contains(Year:) > a").text().trim().toIntOrNull()
val tvType = if (url.contains("/tv/")) TvType.TvSeries else TvType.Movie
val description = document.selectFirst("div[itemprop=description] > p")?.text()?.trim()
val trailer = document.selectFirst("ul.gmr-player-nav li a.gmr-trailer-popup")?.attr("href")
val rating =
document.selectFirst("div.gmr-meta-rating > span[itemprop=ratingValue]")?.text()
?.toRatingInt()
val actors = document.select("div.gmr-moviedata").last()?.select("span[itemprop=actors]")
?.map { it.select("a").text() }
val recommendations = document.select("div.idmuvi-rp ul li").mapNotNull {
it.toBottomSearchResult()
}
return if (tvType == TvType.TvSeries) {
val episodes = document.select("div.gmr-listseries > a").map {
val href = fixUrl(it.attr("href"))
val episode = it.text().split(" ").last().toIntOrNull()
val season = it.text().split(" ").first().substringAfter("S").toIntOrNull()
Episode(
href,
"Episode $episode",
season,
episode,
)
}
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
this.posterUrl = poster
this.year = year
this.plot = description
this.tags = tags
this.rating = rating
addActors(actors)
this.recommendations = recommendations
addTrailer(trailer)
}
} else {
newMovieLoadResponse(title, url, TvType.Movie, url) {
this.posterUrl = poster
this.year = year
this.plot = description
this.tags = tags
this.rating = rating
addActors(actors)
this.recommendations = recommendations
addTrailer(trailer)
}
}
}
private data class ResponseSource(
@JsonProperty("file") val file: String,
@JsonProperty("type") val type: String?,
@JsonProperty("label") val label: String?
)
override suspend fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val document = app.get(data).document
val id = document.selectFirst("div#muvipro_player_content_id")!!.attr("data-id")
val server = app.post(
"$mainUrl/wp-admin/admin-ajax.php",
data = mapOf("action" to "muvipro_player_content", "tab" to "player1", "post_id" to id)
).document.select("iframe").attr("src")
app.get(server, referer = "$mainUrl/").document.select("script").map { script ->
if (script.data().contains("var config = {")) {
val source = script.data().substringAfter("sources: [").substringBefore("],")
tryParseJson<List<ResponseSource>>("[$source]")?.map { m3u ->
val m3uData = app.get(m3u.file, referer = "https://gdriveplayer.link/").text
val quality =
Regex("\\d{3,4}\\.m3u8").findAll(m3uData).map { it.value }.toList()
quality.forEach {
callback.invoke(
ExtractorLink(
source = name,
name = name,
url = m3u.file.replace("video.m3u8", it),
referer = "https://gdriveplayer.link/",
quality = getQualityFromName("${it.replace(".m3u8", "")}p"),
isM3u8 = true
)
)
}
}
}
}
return true
}
}

View File

@ -1,14 +0,0 @@
package com.hexated
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
import com.lagradost.cloudstream3.plugins.Plugin
import android.content.Context
@CloudstreamPlugin
class MultiplexProviderPlugin: Plugin() {
override fun load(context: Context) {
// All providers should be added in this manner. Please don't edit the providers list directly.
registerMainAPI(MultiplexProvider())
}
}

View File

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

View File

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

View File

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

View File

@ -164,7 +164,7 @@ class Nimegami : MainAPI() {
link.url,
link.referer,
getQualityFromName(quality),
link.isM3u8,
link.type,
link.headers,
link.extractorData
)

View File

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

View File

@ -0,0 +1,45 @@
package com.hexated
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
open class Qiwi : ExtractorApi() {
override val name = "Qiwi"
override val mainUrl = "https://qiwi.gg"
override val requiresReferer = true
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val document = app.get(url, referer = referer).document
val title = document.select("title").text()
val source = document.select("video source").attr("src")
callback.invoke(
ExtractorLink(
this.name,
this.name,
source,
"$mainUrl/",
getIndexQuality(title),
headers = mapOf(
"Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
"Range" to "bytes=0-",
"Sec-Fetch-Dest" to "video",
"Sec-Fetch-Mode" to "no-cors",
)
)
)
}
private fun getIndexQuality(str: String): Int {
return Regex("(\\d{3,4})[pP]").find(str)?.groupValues?.getOrNull(1)?.toIntOrNull()
?: Qualities.Unknown.value
}
}

View File

@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.*
import org.jsoup.nodes.Element
class OploverzProvider : MainAPI() {
override var mainUrl = "https://oploverz.team"
override var mainUrl = "https://oploverz.red"
override var name = "Oploverz"
override val hasMainPage = true
override var lang = "id"
@ -195,7 +195,7 @@ class OploverzProvider : MainAPI() {
link.url,
link.referer,
name.fixQuality(),
link.isM3u8,
link.type,
link.headers,
link.extractorData
)

View File

@ -10,5 +10,6 @@ class OploverzProviderPlugin: Plugin() {
override fun load(context: Context) {
// All providers should be added in this manner. Please don't edit the providers list directly.
registerMainAPI(OploverzProvider())
registerExtractorAPI(Qiwi())
}
}

View File

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

View File

@ -236,7 +236,7 @@ class OtakudesuProvider : MainAPI() {
link.url,
link.referer,
quality,
link.isM3u8,
link.type,
link.headers,
link.extractorData
)

View File

@ -9,7 +9,7 @@ import org.jsoup.nodes.Element
import java.net.URLDecoder
class PhimmoichillProvider : MainAPI() {
override var mainUrl = "https://phimmoichilld.net"
override var mainUrl = "https://phimmoichillg.net"
override var name = "Phimmoichill"
override val hasMainPage = true
override var lang = "vi"

View File

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

View File

@ -203,7 +203,7 @@ class Samehadaku : MainAPI() {
link.url,
link.referer,
name.fixQuality(),
link.isM3u8,
link.type,
link.headers,
link.extractorData
)

View File

@ -1,7 +1,7 @@
import org.jetbrains.kotlin.konan.properties.Properties
// use an integer for version numbers
version = 159
version = 164
android {
defaultConfig {

View 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"
}

View File

@ -9,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
@ -126,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
)
@ -272,7 +273,7 @@ object SoraExtractor : SoraStream() {
video.url,
video.referer,
Qualities.P1080.value,
video.isM3u8,
video.type,
video.headers,
video.extractorData
)
@ -414,7 +415,7 @@ object SoraExtractor : SoraStream() {
} else {
"$idlixAPI/episode/$fixTitle-season-$season-episode-$episode"
}
invokeWpmovies(url, subtitleCallback, callback)
invokeWpmovies(url, subtitleCallback, callback, encrypt = true)
}
suspend fun invokeMultimovies(
@ -455,8 +456,14 @@ object SoraExtractor : SoraStream() {
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit,
fixIframe: Boolean = false,
encrypt: Boolean = false,
key: String? = null,
) {
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 document = res.document
document.select("ul#playeroptionsul > li").map {
@ -466,13 +473,17 @@ object SoraExtractor : SoraStream() {
it.attr("data-type")
)
}.apmap { (id, nume, type) ->
val json = session.post(
val json = app.post(
url = "$referer/wp-admin/admin-ajax.php", data = mapOf(
"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 {
if (fixIframe) Jsoup.parse(it).select("IFRAME").attr("SRC") else it
val source = tryParseJson<ResponseHash>(json.text)?.let {
when {
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
}
} ?: return@apmap
if (!source.contains("youtube")) {
loadExtractor(source, "$referer/", subtitleCallback, callback)
@ -700,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
)
@ -1028,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
)
@ -1506,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"),
@ -1527,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 =
@ -1547,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",
@ -1561,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"
),
@ -1583,7 +1598,7 @@ object SoraExtractor : SoraStream() {
),
).document.select("iframe").attr("src")
loadExtractor(iframe, m4uhdAPI, subtitleCallback, callback)
loadExtractor(iframe, referer, subtitleCallback, callback)
}
}
@ -2540,80 +2555,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(
imdbId: String? = null,
title: String? = null,
@ -2884,36 +2825,84 @@ object SoraExtractor : SoraStream() {
}
suspend fun invokeSusflix(
tmdbId: Int? = null,
season: Int? = null,
episode: Int? = null,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit,
) {
val url = if(season == null) {
"$susflixAPI/view/movie/$tmdbId"
} else {
"$susflixAPI/view/tv/$tmdbId/$season/$episode"
}
val res = app.get(url,cookies = mapOf(
"session" to "eyJfZnJlc2giOmZhbHNlLCJwaG9uZV9udW1iZXIiOiJzdXNoZXg5OCJ9.ZO6CsA.XUs6Y5gna8ExAUX55-myMi1QpYU"
)).text.substringAfter("response = {").substringBefore("};").replace("\'", "\"")
val sources = tryParseJson<SusflixSources>("{$res}")
sources?.qualities?.map { source ->
callback.invoke(
ExtractorLink(
"Susflix",
"Susflix",
source.path ?: return@map,
"$susflixAPI/",
getQualityFromName(source.quality)
)
)
}
sources?.srtfiles?.map { sub ->
subtitleCallback.invoke(
SubtitleFile(
sub.caption ?: return@map,
sub.url ?: return@map,
)
)
}
}
suspend fun invokeJump1(
tmdbId: Int? = null,
tvdbId: Int? = null,
title: String? = null,
year: Int? = null,
season: Int? = null,
episode: Int? = null,
callback: (ExtractorLink) -> Unit,
) {
val referer = "https://jump1.net/"
val res = if(season == null) {
val body = """{"filters":[{"type":"slug","args":{"slugs":["${title.createSlug()}-$year"]}}],"sort":"addedRecent","skip":0,"limit":100}""".toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull())
app.post("$jump1API/api/movies", requestBody = body, referer = referer)
} else {
app.get("$jump1API/api/shows/$tvdbId/seasons", referer = referer)
}.text
val source = if(season == null) {
tryParseJson<Jump1Movies>(res)?.movies?.find { it.id == tmdbId }?.videoId
} else {
val jumpSeason = tryParseJson<ArrayList<Jump1Season>>(res)?.find { it.seasonNumber == season }?.id
val seasonRes = app.get("$jump1API/api/shows/seasons/${jumpSeason ?: return}/episodes", referer = referer)
tryParseJson<ArrayList<Jump1Episodes>>(seasonRes.text)?.find { it.episodeNumber == episode }?.videoId
}
callback.invoke(
ExtractorLink(
"Jump1",
"Jump1",
"$jump1API/hls/${source ?: return}/master.m3u8?ts=${APIHolder.unixTimeMS}",
referer,
Qualities.P1080.value,
true
)
)
}
}
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"
}

View File

@ -65,7 +65,8 @@ data class HdMovieBoxIframe(
data class ResponseHash(
@JsonProperty("embed_url") val embed_url: String,
@JsonProperty("type") val type: String?,
@JsonProperty("key") val key: String? = null,
@JsonProperty("type") val type: String? = null,
)
data class KisskhSources(
@ -87,11 +88,41 @@ data class KisskhDetail(
@JsonProperty("episodes") val episodes: ArrayList<KisskhEpisodes>? = arrayListOf(),
)
data class SusflixSrtfiles(
@JsonProperty("caption") val caption: String? = null,
@JsonProperty("url") val url: String? = null,
)
data class SusflixQualities(
@JsonProperty("path") val path: String? = null,
@JsonProperty("quality") val quality: String? = null,
)
data class SusflixSources(
@JsonProperty("Qualities") val qualities: ArrayList<SusflixQualities>? = arrayListOf(),
@JsonProperty("Srtfiles") val srtfiles: ArrayList<SusflixSrtfiles>? = arrayListOf(),
)
data class KisskhResults(
@JsonProperty("id") val id: Int?,
@JsonProperty("title") val title: String?,
)
data class Jump1Episodes(
@JsonProperty("id") val id: Any? = null,
@JsonProperty("episodeNumber") val episodeNumber: Int? = null,
@JsonProperty("videoId") val videoId: String? = null,
)
data class Jump1Season(
@JsonProperty("seasonNumber") val seasonNumber: Int? = null,
@JsonProperty("id") val id: String? = null,
)
data class Jump1Movies(
@JsonProperty("movies") val movies: ArrayList<Jump1Episodes>? = arrayListOf(),
)
data class EpisodesFwatayako(
@JsonProperty("id") val id: String? = null,
@JsonProperty("file") val file: String? = null,
@ -206,25 +237,6 @@ data class WatchOnlineResponse(
@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(
@JsonProperty("request") val request: Map<String, String>?,
)

View File

@ -34,7 +34,6 @@ import com.hexated.SoraExtractor.invokeMoviezAdd
import com.hexated.SoraExtractor.invokeNavy
import com.hexated.SoraExtractor.invokeNinetv
import com.hexated.SoraExtractor.invokeNowTv
import com.hexated.SoraExtractor.invokePutlocker
import com.hexated.SoraExtractor.invokeRStream
import com.hexated.SoraExtractor.invokeRidomovies
import com.hexated.SoraExtractor.invokeShinobiMovies
@ -42,11 +41,13 @@ import com.hexated.SoraExtractor.invokeSmashyStream
import com.hexated.SoraExtractor.invokeDumpStream
import com.hexated.SoraExtractor.invokeEmovies
import com.hexated.SoraExtractor.invokeFourCartoon
import com.hexated.SoraExtractor.invokeJump1
import com.hexated.SoraExtractor.invokeMoment
import com.hexated.SoraExtractor.invokeMultimovies
import com.hexated.SoraExtractor.invokeNetmovies
import com.hexated.SoraExtractor.invokePobmovies
import com.hexated.SoraExtractor.invokePrimewire
import com.hexated.SoraExtractor.invokeSusflix
import com.hexated.SoraExtractor.invokeTvMovies
import com.hexated.SoraExtractor.invokeUhdmovies
import com.hexated.SoraExtractor.invokeVidsrcto
@ -55,7 +56,6 @@ import com.hexated.SoraExtractor.invokeWatchsomuch
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
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.toJson
import com.lagradost.cloudstream3.utils.ExtractorLink
@ -94,7 +94,7 @@ open class SoraStream : TmdbProvider() {
const val hdMovieBoxAPI = "https://hdmoviebox.net"
const val dreamfilmAPI = "https://dreamfilmsw.net"
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 filmxyAPI = "https://www.filmxy.vip"
const val kimcartoonAPI = "https://kimcartoon.li"
@ -102,7 +102,7 @@ open class SoraStream : TmdbProvider() {
const val crunchyrollAPI = "https://beta-api.crunchyroll.com"
const val kissKhAPI = "https://kisskh.co"
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 gMoviesAPI = "https://gdrivemovies.xyz"
const val fdMoviesAPI = "https://freedrivemovie.lol"
@ -118,7 +118,6 @@ open class SoraStream : TmdbProvider() {
const val ask4MoviesAPI = "https://ask4movie.nl"
const val watchOnlineAPI = "https://watchonline.ag"
const val nineTvAPI = "https://moviesapi.club"
const val putlockerAPI = "https://ww7.putlocker.vip"
const val fmoviesAPI = "https://fmovies.to"
const val nowTvAPI = "https://myfilestorage.xyz"
const val gokuAPI = "https://goku.sx"
@ -127,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"
@ -135,6 +134,8 @@ open class SoraStream : TmdbProvider() {
const val vidsrctoAPI = "https://vidsrc.to"
const val dramadayAPI = "https://dramaday.me"
const val animetoshoAPI = "https://animetosho.org"
const val susflixAPI = "https://susflix.tv"
const val jump1API = "https://ca.jump1.net"
// INDEX SITE
const val dahmerMoviesAPI = "https://edytjedhgmdhm.abfhaqrhbnf.workers.dev"
@ -279,6 +280,7 @@ open class SoraStream : TmdbProvider() {
LinkData(
data.id,
res.external_ids?.imdb_id,
res.external_ids?.tvdb_id,
data.type,
eps.seasonNumber,
eps.episodeNumber,
@ -332,6 +334,7 @@ open class SoraStream : TmdbProvider() {
LinkData(
data.id,
res.external_ids?.imdb_id,
res.external_ids?.tvdb_id,
data.type,
title = title,
year = year,
@ -536,7 +539,7 @@ open class SoraStream : TmdbProvider() {
)
},
{
invokeM4uhd(
if(!res.isAnime) invokeM4uhd(
res.title,
res.year,
res.season,
@ -545,9 +548,6 @@ open class SoraStream : TmdbProvider() {
callback
)
},
{
invokePutlocker(res.title, res.year, res.season, res.episode, callback)
},
{
invokeTvMovies(res.title, res.season, res.episode, callback)
},
@ -746,6 +746,20 @@ open class SoraStream : TmdbProvider() {
{
if(!res.isAnime) invoke2embed(res.imdbId,res.season,res.episode,callback)
},
// {
// invokeSusflix(res.id,res.season,res.episode,subtitleCallback,callback)
// },
{
if(!res.isAnime) invokeJump1(
res.id,
res.tvdbId,
res.title,
res.year,
res.season,
res.episode,
callback
)
},
)
return true
@ -754,6 +768,7 @@ open class SoraStream : TmdbProvider() {
data class LinkData(
val id: Int? = null,
val imdbId: String? = null,
val tvdbId: Int? = null,
val type: String? = null,
val season: Int? = null,
val episode: Int? = null,
@ -858,7 +873,7 @@ open class SoraStream : TmdbProvider() {
data class ExternalIds(
@JsonProperty("imdb_id") val imdb_id: String? = null,
@JsonProperty("tvdb_id") val tvdb_id: String? = null,
@JsonProperty("tvdb_id") val tvdb_id: Int? = null,
)
data class Credits(

View File

@ -21,7 +21,6 @@ import com.hexated.SoraExtractor.invokeMovieHab
import com.hexated.SoraExtractor.invokeNavy
import com.hexated.SoraExtractor.invokeNinetv
import com.hexated.SoraExtractor.invokeNowTv
import com.hexated.SoraExtractor.invokePutlocker
import com.hexated.SoraExtractor.invokeRStream
import com.hexated.SoraExtractor.invokeRidomovies
import com.hexated.SoraExtractor.invokeSeries9
@ -29,10 +28,12 @@ import com.hexated.SoraExtractor.invokeSmashyStream
import com.hexated.SoraExtractor.invokeDumpStream
import com.hexated.SoraExtractor.invokeEmovies
import com.hexated.SoraExtractor.invokeFourCartoon
import com.hexated.SoraExtractor.invokeJump1
import com.hexated.SoraExtractor.invokeMoment
import com.hexated.SoraExtractor.invokeMultimovies
import com.hexated.SoraExtractor.invokeNetmovies
import com.hexated.SoraExtractor.invokePrimewire
import com.hexated.SoraExtractor.invokeSusflix
import com.hexated.SoraExtractor.invokeVidSrc
import com.hexated.SoraExtractor.invokeVidsrcto
import com.hexated.SoraExtractor.invokeWatchOnline
@ -56,14 +57,17 @@ class SoraStreamLite : SoraStream() {
argamap(
{
invokePutlocker(
res.title,
res.year,
res.season,
res.episode,
callback
)
if(!res.isAnime) invokeJump1(res.id,res.tvdbId,res.title,res.year,res.season,res.episode,callback)
},
// {
// invokeSusflix(
// res.id,
// res.season,
// res.episode,
// subtitleCallback,
// callback
// )
// },
{
invokeWatchsomuch(
res.imdbId,
@ -253,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,

View File

@ -17,5 +17,6 @@ class SoraStreamPlugin: Plugin() {
registerExtractorAPI(Yipsu())
registerExtractorAPI(Mwish())
registerExtractorAPI(TravelR())
registerExtractorAPI(Playm4u())
}
}

View File

@ -10,7 +10,6 @@ import com.hexated.SoraStream.Companion.filmxyAPI
import com.hexated.SoraStream.Companion.fmoviesAPI
import com.hexated.SoraStream.Companion.gdbot
import com.hexated.SoraStream.Companion.malsyncAPI
import com.hexated.SoraStream.Companion.putlockerAPI
import com.hexated.SoraStream.Companion.smashyStreamAPI
import com.hexated.SoraStream.Companion.tvMoviesAPI
import com.hexated.SoraStream.Companion.watchOnlineAPI
@ -45,7 +44,6 @@ import kotlin.math.min
val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=")
const val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
const val otakuzBaseUrl = "https://otakuz.live/"
val encodedIndex = arrayOf(
"GamMovies",
"JSMovies",
@ -1055,52 +1053,6 @@ suspend fun getCrunchyrollIdFromMalSync(aniId: String?): String? {
?: 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(
title: String?,
date: String?,
@ -1191,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
)
@ -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 {
private val deviceId = getDeviceId()
@ -1926,4 +1723,127 @@ object RSAEncryptionHelper {
exception.printStackTrace()
null
}
}
// 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 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)
}
/**
* 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)
}
}
}

View File

@ -10,7 +10,7 @@ import org.jsoup.nodes.Element
import java.net.URI
open class YomoviesProvider : MainAPI() {
override var mainUrl = "https://yomovies.ltd"
override var mainUrl = "https://yomovies.fan"
private var directUrl = ""
override var name = "Yomovies"
override val hasMainPage = true

View File

@ -2,7 +2,7 @@ rootProject.name = "CloudstreamPlugins"
// This file sets what projects are included. All new projects should get automatically included unless specified in "disabled" variable.
val disabled = listOf<String>("Animixplay")
val disabled = listOf<String>("Animixplay","Kickassanime")
File(rootDir, ".").eachDir { dir ->
if (!disabled.contains(dir.name) && File(dir, "build.gradle.kts").exists()) {