Merge branch 'master' into master
This commit is contained in:
commit
013151104a
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 1
|
||||
version = 2
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -150,8 +150,8 @@ class Animasu : MainAPI() {
|
|||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
if(!link.isM3u8) getIndexQuality(quality) else link.quality,
|
||||
link.isM3u8,
|
||||
if(link.type != ExtractorLinkType.M3U8) getIndexQuality(quality) else link.quality,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 12
|
||||
version = 13
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 8
|
||||
version = 9
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.hexated
|
|||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import com.lagradost.nicehttp.NiceResponse
|
||||
|
@ -144,7 +145,7 @@ class AnimeSailProvider : MainAPI() {
|
|||
Jsoup.parse(base64Decode(it.attr("data-em"))).select("iframe").attr("src")
|
||||
?: throw ErrorLoadingException("No iframe found")
|
||||
)
|
||||
|
||||
val quality = getIndexQuality(it.text())
|
||||
when {
|
||||
iframe.startsWith("$mainUrl/utils/player/arch/") || iframe.startsWith(
|
||||
"$mainUrl/utils/player/race/"
|
||||
|
@ -156,15 +157,13 @@ class AnimeSailProvider : MainAPI() {
|
|||
iframe.contains("/race/") -> "Race"
|
||||
else -> this.name
|
||||
}
|
||||
val quality =
|
||||
Regex("\\.(\\d{3,4})\\.").find(link)?.groupValues?.get(1)
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
source = source,
|
||||
name = source,
|
||||
url = link,
|
||||
referer = mainUrl,
|
||||
quality = quality?.toIntOrNull() ?: Qualities.Unknown.value
|
||||
quality = quality
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -175,16 +174,16 @@ class AnimeSailProvider : MainAPI() {
|
|||
val link = "https://rasa-cintaku-semakin-berantai.xyz/v/${
|
||||
iframe.substringAfter("id=").substringBefore("&token")
|
||||
}"
|
||||
loadExtractor(link, mainUrl, subtitleCallback, callback)
|
||||
loadFixedExtractor(link, quality, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
iframe.startsWith("$mainUrl/utils/player/framezilla/") || iframe.startsWith("https://uservideo.xyz") -> {
|
||||
request(iframe, ref = data).document.select("iframe").attr("src")
|
||||
.let { link ->
|
||||
loadExtractor(fixUrl(link), mainUrl, subtitleCallback, callback)
|
||||
loadFixedExtractor(fixUrl(link), quality, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
loadExtractor(iframe, mainUrl, subtitleCallback, callback)
|
||||
loadFixedExtractor(iframe, quality, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -193,4 +192,32 @@ class AnimeSailProvider : MainAPI() {
|
|||
return true
|
||||
}
|
||||
|
||||
private suspend fun loadFixedExtractor(
|
||||
url: String,
|
||||
quality: Int?,
|
||||
referer: String? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
loadExtractor(url, referer, subtitleCallback) { link ->
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
link.name,
|
||||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
if(link.type == ExtractorLinkType.M3U8) link.quality else quality ?: Qualities.Unknown.value,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIndexQuality(str: String): Int {
|
||||
return Regex("(\\d{3,4})[pP]").find(str)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
?: Qualities.Unknown.value
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 5
|
||||
version = 6
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -158,7 +158,7 @@ open class Aniworld : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
link.quality,
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,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")
|
||||
|
||||
/**
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
)
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 10
|
||||
version = 11
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -213,7 +213,7 @@ class Hdfilmcehennemi : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
link.quality,
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 12
|
||||
version = 15
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -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?,
|
||||
)
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package com.hexated
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||
import com.lagradost.cloudstream3.base64DecodeArray
|
||||
import com.lagradost.cloudstream3.base64Encode
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import java.security.DigestException
|
||||
import java.security.MessageDigest
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
object AesHelper {
|
||||
|
||||
fun cryptoAESHandler(
|
||||
data: String,
|
||||
pass: ByteArray,
|
||||
encrypt: Boolean = true,
|
||||
padding: String = "AES/CBC/PKCS5PADDING",
|
||||
): String? {
|
||||
val parse = AppUtils.tryParseJson<AesData>(data) ?: return null
|
||||
val (key, iv) = generateKeyAndIv(pass, parse.s.hexToByteArray()) ?: throw ErrorLoadingException("failed to generate key")
|
||||
val cipher = Cipher.getInstance(padding)
|
||||
return if (!encrypt) {
|
||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||
String(cipher.doFinal(base64DecodeArray(parse.ct)))
|
||||
} else {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||
base64Encode(cipher.doFinal(parse.ct.toByteArray()))
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/41434590/8166854
|
||||
private fun generateKeyAndIv(
|
||||
password: ByteArray,
|
||||
salt: ByteArray,
|
||||
hashAlgorithm: String = "MD5",
|
||||
keyLength: Int = 32,
|
||||
ivLength: Int = 16,
|
||||
iterations: Int = 1
|
||||
): List<ByteArray>? {
|
||||
|
||||
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||
val digestLength = md.digestLength
|
||||
val targetKeySize = keyLength + ivLength
|
||||
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
|
||||
val generatedData = ByteArray(requiredLength)
|
||||
var generatedLength = 0
|
||||
|
||||
try {
|
||||
md.reset()
|
||||
|
||||
while (generatedLength < targetKeySize) {
|
||||
if (generatedLength > 0)
|
||||
md.update(
|
||||
generatedData,
|
||||
generatedLength - digestLength,
|
||||
digestLength
|
||||
)
|
||||
|
||||
md.update(password)
|
||||
md.update(salt, 0, 8)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
|
||||
for (i in 1 until iterations) {
|
||||
md.update(generatedData, generatedLength, digestLength)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
}
|
||||
|
||||
generatedLength += digestLength
|
||||
}
|
||||
return listOf(
|
||||
generatedData.copyOfRange(0, keyLength),
|
||||
generatedData.copyOfRange(keyLength, targetKeySize)
|
||||
)
|
||||
} catch (e: DigestException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.hexToByteArray(): ByteArray {
|
||||
check(length % 2 == 0) { "Must have an even length" }
|
||||
return chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
private data class AesData(
|
||||
@JsonProperty("ct") val ct: String,
|
||||
@JsonProperty("iv") val iv: String,
|
||||
@JsonProperty("s") val s: String
|
||||
)
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 14
|
||||
version = 15
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.hexated
|
|||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.extractors.helper.AesHelper
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
@ -10,12 +11,7 @@ import com.lagradost.cloudstream3.utils.getQualityFromName
|
|||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URI
|
||||
import java.security.DigestException
|
||||
import java.security.MessageDigest
|
||||
import java.util.ArrayList
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class KuronimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://45.12.2.26"
|
||||
|
@ -186,10 +182,11 @@ class KuronimeProvider : MainAPI() {
|
|||
|
||||
argamap(
|
||||
{
|
||||
val decrypt = cryptoAES(
|
||||
servers?.src ?: return@argamap,
|
||||
val decrypt = AesHelper.cryptoAESHandler(
|
||||
base64Decode(servers?.src ?: return@argamap),
|
||||
KEY.toByteArray(),
|
||||
false
|
||||
false,
|
||||
"AES/CBC/NoPadding"
|
||||
)
|
||||
val source =
|
||||
tryParseJson<Sources>(decrypt?.toJsonFormat())?.src?.replace("\\", "")
|
||||
|
@ -206,10 +203,11 @@ class KuronimeProvider : MainAPI() {
|
|||
)
|
||||
},
|
||||
{
|
||||
val decrypt = cryptoAES(
|
||||
servers?.mirror ?: return@argamap,
|
||||
val decrypt = AesHelper.cryptoAESHandler(
|
||||
base64Decode(servers?.mirror ?: return@argamap),
|
||||
KEY.toByteArray(),
|
||||
false
|
||||
false,
|
||||
"AES/CBC/NoPadding"
|
||||
)
|
||||
tryParseJson<Mirrors>(decrypt)?.embed?.map { embed ->
|
||||
embed.value.apmap {
|
||||
|
@ -249,7 +247,7 @@ class KuronimeProvider : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
getQualityFromName(quality),
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
@ -263,86 +261,6 @@ class KuronimeProvider : MainAPI() {
|
|||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/41434590/8166854
|
||||
private fun generateKeyAndIv(
|
||||
password: ByteArray,
|
||||
salt: ByteArray,
|
||||
hashAlgorithm: String = "MD5",
|
||||
keyLength: Int = 32,
|
||||
ivLength: Int = 16,
|
||||
iterations: Int = 1
|
||||
): List<ByteArray>? {
|
||||
|
||||
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||
val digestLength = md.digestLength
|
||||
val targetKeySize = keyLength + ivLength
|
||||
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
|
||||
val generatedData = ByteArray(requiredLength)
|
||||
var generatedLength = 0
|
||||
|
||||
try {
|
||||
md.reset()
|
||||
|
||||
while (generatedLength < targetKeySize) {
|
||||
if (generatedLength > 0)
|
||||
md.update(
|
||||
generatedData,
|
||||
generatedLength - digestLength,
|
||||
digestLength
|
||||
)
|
||||
|
||||
md.update(password)
|
||||
md.update(salt, 0, 8)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
|
||||
for (i in 1 until iterations) {
|
||||
md.update(generatedData, generatedLength, digestLength)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
}
|
||||
|
||||
generatedLength += digestLength
|
||||
}
|
||||
return listOf(
|
||||
generatedData.copyOfRange(0, keyLength),
|
||||
generatedData.copyOfRange(keyLength, targetKeySize)
|
||||
)
|
||||
} catch (e: DigestException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.decodeHex(): ByteArray {
|
||||
check(length % 2 == 0) { "Must have an even length" }
|
||||
return chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
private fun cryptoAES(
|
||||
data: String,
|
||||
pass: ByteArray,
|
||||
encrypt: Boolean = true
|
||||
): String? {
|
||||
val json = tryParseJson<AesData>(base64Decode(data))
|
||||
?: throw ErrorLoadingException("No Data Found")
|
||||
val (key, iv) = generateKeyAndIv(pass, json.s.decodeHex()) ?: return null
|
||||
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
|
||||
return if (!encrypt) {
|
||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||
String(cipher.doFinal(base64DecodeArray(json.ct)))
|
||||
} else {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||
base64Encode(cipher.doFinal(json.ct.toByteArray()))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
data class AesData(
|
||||
@JsonProperty("ct") val ct: String,
|
||||
@JsonProperty("iv") val iv: String,
|
||||
@JsonProperty("s") val s: String
|
||||
)
|
||||
|
||||
data class Mirrors(
|
||||
@JsonProperty("embed") val embed: Map<String, Map<String, String>> = emptyMap(),
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 15
|
||||
version = 16
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 6
|
||||
version = 8
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -11,5 +11,6 @@ class MinioppaiPlugin: Plugin() {
|
|||
registerMainAPI(Minioppai())
|
||||
registerExtractorAPI(Streampai())
|
||||
registerExtractorAPI(Paistream())
|
||||
registerExtractorAPI(TvMinioppai())
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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%"
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.hexated"/>
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 5
|
||||
version = 6
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -170,8 +170,8 @@ class Nekopoi : MainAPI() {
|
|||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
if (link.isM3u8) link.quality else it.first,
|
||||
link.isM3u8,
|
||||
if (link.type == ExtractorLinkType.M3U8) link.quality else it.first,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -164,7 +164,7 @@ class Nimegami : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
getQualityFromName(quality),
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 21
|
||||
version = 23
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 13
|
||||
version = 14
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -236,7 +236,7 @@ class OtakudesuProvider : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
quality,
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 13
|
||||
version = 14
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -203,7 +203,7 @@ class Samehadaku : MainAPI() {
|
|||
link.url,
|
||||
link.referer,
|
||||
name.fixQuality(),
|
||||
link.isM3u8,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
// use an integer for version numbers
|
||||
version = 159
|
||||
version = 164
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
@ -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>?,
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -17,5 +17,6 @@ class SoraStreamPlugin: Plugin() {
|
|||
registerExtractorAPI(Yipsu())
|
||||
registerExtractorAPI(Mwish())
|
||||
registerExtractorAPI(TravelR())
|
||||
registerExtractorAPI(Playm4u())
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in New Issue