mirror of
https://github.com/recloudstream/cloudstream-extensions.git
synced 2024-08-15 03:03:54 +00:00
Fix dependencies on other plugins causing errors
This commit is contained in:
parent
62f5d8258f
commit
b74b8614db
62 changed files with 94 additions and 488 deletions
22
VidstreamBundle/build.gradle.kts
Normal file
22
VidstreamBundle/build.gradle.kts
Normal file
|
@ -0,0 +1,22 @@
|
|||
// use an integer for version numbers
|
||||
version = 1
|
||||
|
||||
|
||||
cloudstream {
|
||||
// All of these properties are optional, you can safely remove them
|
||||
|
||||
description = "Includes many providers with the same layout as Vidstream"
|
||||
// authors = listOf("Cloudburst")
|
||||
|
||||
/**
|
||||
* Status int as the following:
|
||||
* 0: Down
|
||||
* 1: Ok
|
||||
* 2: Slow
|
||||
* 3: Beta only
|
||||
* */
|
||||
status = 1 // will be 3 if unspecified
|
||||
|
||||
// Set to true to get an 18+ symbol next to the plugin
|
||||
adult = false // will be false if unspecified
|
||||
}
|
2
VidstreamBundle/src/main/AndroidManifest.xml
Normal file
2
VidstreamBundle/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.lagradost"/>
|
|
@ -0,0 +1,32 @@
|
|||
package com.lagradost
|
||||
|
||||
import android.util.Log
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class AsianEmbedHelper {
|
||||
companion object {
|
||||
suspend fun getUrls(
|
||||
url: String,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
// Fetch links
|
||||
val doc = app.get(url).document
|
||||
val links = doc.select("div#list-server-more > ul > li.linkserver")
|
||||
if (!links.isNullOrEmpty()) {
|
||||
links.apmap {
|
||||
val datavid = it.attr("data-video") ?: ""
|
||||
//Log.i("AsianEmbed", "Result => (datavid) ${datavid}")
|
||||
if (datavid.isNotBlank()) {
|
||||
val res = loadExtractor(datavid, url, subtitleCallback, callback)
|
||||
Log.i("AsianEmbed", "Result => ($res) (datavid) $datavid")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
|
||||
/** Needs to inherit from MainAPI() to
|
||||
* make the app know what functions to call
|
||||
*/
|
||||
class AsianLoadProvider : VidstreamProviderTemplate() {
|
||||
override var name = "AsianLoad"
|
||||
override var mainUrl = "https://asianembed.io"
|
||||
override val homePageUrlList = listOf(
|
||||
mainUrl,
|
||||
"$mainUrl/recently-added-raw",
|
||||
"$mainUrl/movies",
|
||||
"$mainUrl/kshow",
|
||||
"$mainUrl/popular",
|
||||
"$mainUrl/ongoing-series"
|
||||
)
|
||||
|
||||
override val iv = "9262859232435825"
|
||||
override val secretKey = "93422192433952489752342908585752"
|
||||
override val secretDecryptKey = secretKey
|
||||
|
||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.Vidstream
|
||||
//import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider.Companion.extractVidstream
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class DramaSeeProvider : MainAPI() {
|
||||
override var mainUrl = "https://dramasee.net"
|
||||
override var name = "DramaSee"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = false
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val headers = mapOf("X-Requested-By" to mainUrl)
|
||||
val document = app.get(mainUrl, headers = headers).document
|
||||
val mainbody = document.getElementsByTag("body")
|
||||
|
||||
return HomePageResponse(
|
||||
mainbody.select("section.block_area.block_area_home")?.map { main ->
|
||||
val title = main.select("h2.cat-heading").text() ?: "Main"
|
||||
val inner = main.select("div.flw-item") ?: return@map null
|
||||
|
||||
HomePageList(
|
||||
title,
|
||||
inner.mapNotNull {
|
||||
val innerBody = it?.selectFirst("a")
|
||||
// Fetch details
|
||||
val link = fixUrlNull(innerBody?.attr("href")) ?: return@mapNotNull null
|
||||
val image = fixUrlNull(it.select("img").attr("data-src")) ?: ""
|
||||
val name = innerBody?.attr("title") ?: "<Untitled>"
|
||||
//Log.i(this.name, "Result => (innerBody, image) ${innerBody} / ${image}")
|
||||
MovieSearchResponse(
|
||||
name,
|
||||
link,
|
||||
this.name,
|
||||
TvType.AsianDrama,
|
||||
image,
|
||||
year = null,
|
||||
id = null,
|
||||
)
|
||||
}.distinctBy { c -> c.url })
|
||||
}?.filterNotNull() ?: listOf()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/search?q=$query"
|
||||
val document = app.get(url).document
|
||||
val posters = document.select("div.film-poster")
|
||||
|
||||
|
||||
return posters.mapNotNull {
|
||||
val innerA = it.select("a") ?: return@mapNotNull null
|
||||
val link = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null
|
||||
val title = innerA.attr("title") ?: return@mapNotNull null
|
||||
val year =
|
||||
Regex(""".*\((\d{4})\)""").find(title)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
val imgSrc = it.select("img")?.attr("data-src") ?: return@mapNotNull null
|
||||
val image = fixUrlNull(imgSrc)
|
||||
|
||||
MovieSearchResponse(
|
||||
name = title,
|
||||
url = link,
|
||||
apiName = this.name,
|
||||
type = TvType.Movie,
|
||||
posterUrl = image,
|
||||
year = year
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url).document
|
||||
val body = doc.getElementsByTag("body")
|
||||
val inner = body?.select("div.anis-content")
|
||||
|
||||
// Video details
|
||||
val poster = fixUrlNull(inner?.select("img.film-poster-img")?.attr("src")) ?: ""
|
||||
//Log.i(this.name, "Result => (imgLinkCode) ${imgLinkCode}")
|
||||
val title = inner?.select("h2.film-name.dynamic-name")?.text() ?: ""
|
||||
val year = if (title.length > 5) {
|
||||
title.substring(title.length - 5)
|
||||
.trim().trimEnd(')').toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
//Log.i(this.name, "Result => (year) ${title.substring(title.length - 5)}")
|
||||
val descript = body?.firstOrNull()?.select("div.film-description.m-hide")?.text()
|
||||
val tags = inner?.select("div.item.item-list > a")
|
||||
?.mapNotNull { it?.text()?.trim() ?: return@mapNotNull null }
|
||||
val recs = body.select("div.flw-item")?.mapNotNull {
|
||||
val a = it.select("a") ?: return@mapNotNull null
|
||||
val aUrl = fixUrlNull(a.attr("href")) ?: return@mapNotNull null
|
||||
val aImg = fixUrlNull(it.select("img")?.attr("data-src"))
|
||||
val aName = a.attr("title") ?: return@mapNotNull null
|
||||
val aYear = aName.trim().takeLast(5).removeSuffix(")").toIntOrNull()
|
||||
MovieSearchResponse(
|
||||
url = aUrl,
|
||||
name = aName,
|
||||
type = TvType.Movie,
|
||||
posterUrl = aImg,
|
||||
year = aYear,
|
||||
apiName = this.name
|
||||
)
|
||||
}
|
||||
|
||||
// Episodes Links
|
||||
val episodeUrl = body.select("a.btn.btn-radius.btn-primary.btn-play").attr("href")
|
||||
val episodeDoc = app.get(episodeUrl).document
|
||||
|
||||
|
||||
val episodeList = episodeDoc.select("div.ss-list.ss-list-min > a").mapNotNull { ep ->
|
||||
val episodeNumber = ep.attr("data-number").toIntOrNull()
|
||||
val epLink = fixUrlNull(ep.attr("href")) ?: return@mapNotNull null
|
||||
|
||||
// if (epLink.isNotBlank()) {
|
||||
// // Fetch video links
|
||||
// val epVidLinkEl = app.get(epLink, referer = mainUrl).document
|
||||
// val ajaxUrl = epVidLinkEl.select("div#js-player")?.attr("embed")
|
||||
// //Log.i(this.name, "Result => (ajaxUrl) ${ajaxUrl}")
|
||||
// if (!ajaxUrl.isNullOrEmpty()) {
|
||||
// val innerPage = app.get(fixUrl(ajaxUrl), referer = epLink).document
|
||||
// val listOfLinks = mutableListOf<String>()
|
||||
// innerPage.select("div.player.active > main > div")?.forEach { em ->
|
||||
// val href = fixUrlNull(em.attr("src")) ?: ""
|
||||
// if (href.isNotBlank()) {
|
||||
// listOfLinks.add(href)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //Log.i(this.name, "Result => (listOfLinks) ${listOfLinks.toJson()}")
|
||||
//
|
||||
// }
|
||||
// }
|
||||
Episode(
|
||||
name = null,
|
||||
season = null,
|
||||
episode = episodeNumber,
|
||||
data = epLink,
|
||||
posterUrl = null,
|
||||
date = null
|
||||
)
|
||||
}
|
||||
|
||||
//If there's only 1 episode, consider it a movie.
|
||||
if (episodeList.size == 1) {
|
||||
return MovieLoadResponse(
|
||||
name = title,
|
||||
url = url,
|
||||
apiName = this.name,
|
||||
type = TvType.Movie,
|
||||
dataUrl = episodeList.first().data,
|
||||
posterUrl = poster,
|
||||
year = year,
|
||||
plot = descript,
|
||||
recommendations = recs,
|
||||
tags = tags
|
||||
)
|
||||
}
|
||||
return TvSeriesLoadResponse(
|
||||
name = title,
|
||||
url = url,
|
||||
apiName = this.name,
|
||||
type = TvType.AsianDrama,
|
||||
episodes = episodeList,
|
||||
posterUrl = poster,
|
||||
year = year,
|
||||
plot = descript,
|
||||
recommendations = recs,
|
||||
tags = tags
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
println("DATATATAT $data")
|
||||
|
||||
val document = app.get(data).document
|
||||
val iframeUrl = document.select("iframe").attr("src")
|
||||
val iframe = app.get(iframeUrl)
|
||||
val iframeDoc = iframe.document
|
||||
|
||||
argamap({
|
||||
iframeDoc.select(".list-server-items > .linkserver")
|
||||
.forEach { element ->
|
||||
val status = element.attr("data-status") ?: return@forEach
|
||||
if (status != "1") return@forEach
|
||||
val extractorData = element.attr("data-video") ?: return@forEach
|
||||
loadExtractor(extractorData, iframe.url, subtitleCallback, callback)
|
||||
}
|
||||
}, {
|
||||
val iv = "9262859232435825"
|
||||
val secretKey = "93422192433952489752342908585752"
|
||||
val secretDecryptKey = "93422192433952489752342908585752"
|
||||
Vidstream.extractVidstream(
|
||||
iframe.url,
|
||||
this.name,
|
||||
callback,
|
||||
iv,
|
||||
secretKey,
|
||||
secretDecryptKey,
|
||||
isUsingAdaptiveKeys = false,
|
||||
isUsingAdaptiveData = true,
|
||||
iframeDocument = iframeDoc
|
||||
)
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class KdramaHoodProvider : MainAPI() {
|
||||
override var mainUrl = "https://kdramahood.com"
|
||||
override var name = "KDramaHood"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = false
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||
|
||||
private data class ResponseDatas(
|
||||
@JsonProperty("label") val label: String,
|
||||
@JsonProperty("file") val file: String
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val doc = app.get("$mainUrl/home2").document
|
||||
val home = ArrayList<HomePageList>()
|
||||
|
||||
// Hardcoded homepage cause of site implementation
|
||||
// Recently added
|
||||
val recentlyInner = doc.selectFirst("div.peliculas")
|
||||
val recentlyAddedTitle = recentlyInner!!.selectFirst("h1")?.text() ?: "Recently Added"
|
||||
val recentlyAdded = recentlyInner.select("div.item_2.items > div.fit.item").mapNotNull {
|
||||
val innerA = it.select("div.image > a") ?: return@mapNotNull null
|
||||
val link = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null
|
||||
val image = fixUrlNull(innerA.select("img").attr("src"))
|
||||
|
||||
val innerData = it.selectFirst("div.data")
|
||||
val title = innerData!!.selectFirst("h1")?.text() ?: return@mapNotNull null
|
||||
val year = try {
|
||||
val yearText = innerData.selectFirst("span.titulo_o")
|
||||
?.text()?.takeLast(11)?.trim()?.take(4) ?: ""
|
||||
//Log.i(this.name, "Result => (yearText) $yearText")
|
||||
val rex = Regex("\\((\\d+)")
|
||||
//Log.i(this.name, "Result => (rex value) ${rex.find(yearText)?.value}")
|
||||
rex.find(yearText)?.value?.toIntOrNull()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
MovieSearchResponse(
|
||||
name = title,
|
||||
url = link,
|
||||
apiName = this.name,
|
||||
type = TvType.TvSeries,
|
||||
posterUrl = image,
|
||||
year = year
|
||||
)
|
||||
}.distinctBy { it.url } ?: listOf()
|
||||
home.add(HomePageList(recentlyAddedTitle, recentlyAdded))
|
||||
return HomePageResponse(home.filter { it.list.isNotEmpty() })
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/?s=$query"
|
||||
val html = app.get(url).document
|
||||
val document = html.getElementsByTag("body")
|
||||
.select("div.item_1.items > div.item") ?: return listOf()
|
||||
|
||||
return document.mapNotNull {
|
||||
if (it == null) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
val innerA = it.selectFirst("div.boxinfo > a") ?: return@mapNotNull null
|
||||
val link = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null
|
||||
val title = innerA.select("span.tt")?.text() ?: return@mapNotNull null
|
||||
|
||||
val year = it.selectFirst("span.year")?.text()?.toIntOrNull()
|
||||
val image = fixUrlNull(it.selectFirst("div.image > img")?.attr("src"))
|
||||
|
||||
MovieSearchResponse(
|
||||
name = title,
|
||||
url = link,
|
||||
apiName = this.name,
|
||||
type = TvType.Movie,
|
||||
posterUrl = image,
|
||||
year = year
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url).document
|
||||
val inner = doc.selectFirst("div.central")
|
||||
|
||||
// Video details
|
||||
val title = inner?.selectFirst("h1")?.text() ?: ""
|
||||
val poster = fixUrlNull(doc.selectFirst("meta[property=og:image]")?.attr("content")) ?: ""
|
||||
//Log.i(this.name, "Result => (poster) ${poster}")
|
||||
val info = inner!!.selectFirst("div#info")
|
||||
val descript = inner.selectFirst("div.contenidotv > div > p")?.text()
|
||||
val year = try {
|
||||
val startLink = "https://kdramahood.com/drama-release-year/"
|
||||
var res: Int? = null
|
||||
info?.select("div.metadatac")?.forEach {
|
||||
if (res != null) {
|
||||
return@forEach
|
||||
}
|
||||
if (it == null) {
|
||||
return@forEach
|
||||
}
|
||||
val yearLink = it.select("a").attr("href") ?: return@forEach
|
||||
if (yearLink.startsWith(startLink)) {
|
||||
res = yearLink.substring(startLink.length).replace("/", "").toIntOrNull()
|
||||
}
|
||||
}
|
||||
res
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
val recs = doc.select("div.sidebartv > div.tvitemrel").mapNotNull {
|
||||
val a = it?.select("a") ?: return@mapNotNull null
|
||||
val aUrl = fixUrlNull(a.attr("href")) ?: return@mapNotNull null
|
||||
val aImg = a.select("img")
|
||||
val aCover = fixUrlNull(aImg.attr("src")) ?: fixUrlNull(aImg.attr("data-src"))
|
||||
val aNameYear = a.select("div.datatvrel") ?: return@mapNotNull null
|
||||
val aName = aNameYear.select("h4").text() ?: aImg.attr("alt") ?: return@mapNotNull null
|
||||
val aYear = aName.trim().takeLast(5).removeSuffix(")").toIntOrNull()
|
||||
MovieSearchResponse(
|
||||
url = aUrl,
|
||||
name = aName,
|
||||
type = TvType.Movie,
|
||||
posterUrl = aCover,
|
||||
year = aYear,
|
||||
apiName = this.name
|
||||
)
|
||||
}
|
||||
|
||||
// Episodes Links
|
||||
val episodeList = inner.select("ul.episodios > li")?.mapNotNull { ep ->
|
||||
//Log.i(this.name, "Result => (ep) ${ep}")
|
||||
val listOfLinks = mutableListOf<String>()
|
||||
val count = ep.select("div.numerando")?.text()?.toIntOrNull() ?: 0
|
||||
val innerA = ep.select("div.episodiotitle > a") ?: return@mapNotNull null
|
||||
//Log.i(this.name, "Result => (innerA) ${innerA}")
|
||||
val epLink = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null
|
||||
//Log.i(this.name, "Result => (epLink) ${epLink}")
|
||||
if (epLink.isNotBlank()) {
|
||||
// Fetch video links
|
||||
val epVidLinkEl = app.get(epLink, referer = mainUrl).document
|
||||
val epLinksContent = epVidLinkEl.selectFirst("div.player_nav > script")?.html()
|
||||
?.replace("ifr_target.src =", "<div>")
|
||||
?.replace("';", "</div>")
|
||||
//Log.i(this.name, "Result => (epLinksContent) $epLinksContent")
|
||||
if (!epLinksContent.isNullOrEmpty()) {
|
||||
//Log.i(this.name, "Result => (epLinksContent) ${Jsoup.parse(epLinksContent)?.select("div")}")
|
||||
Jsoup.parse(epLinksContent)?.select("div")?.forEach { em ->
|
||||
val href = em?.html()?.trim()?.removePrefix("'") ?: return@forEach
|
||||
//Log.i(this.name, "Result => (ep#$count link) $href")
|
||||
if (href.isNotBlank()) {
|
||||
listOfLinks.add(fixUrl(href))
|
||||
}
|
||||
}
|
||||
}
|
||||
//Fetch default source and subtitles
|
||||
epVidLinkEl.select("div.embed2")?.forEach { defsrc ->
|
||||
if (defsrc == null) {
|
||||
return@forEach
|
||||
}
|
||||
val scriptstring = defsrc.toString()
|
||||
if (scriptstring.contains("sources: [{")) {
|
||||
"(?<=playerInstance2.setup\\()([\\s\\S]*?)(?=\\);)".toRegex()
|
||||
.find(scriptstring)?.value?.let { itemjs ->
|
||||
listOfLinks.add("$mainUrl$itemjs")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Episode(
|
||||
name = null,
|
||||
season = null,
|
||||
episode = count,
|
||||
data = listOfLinks.distinct().toJson(),
|
||||
posterUrl = poster,
|
||||
date = null
|
||||
)
|
||||
}
|
||||
|
||||
//If there's only 1 episode, consider it a movie.
|
||||
if (episodeList?.size == 1) {
|
||||
return MovieLoadResponse(
|
||||
name = title,
|
||||
url = url,
|
||||
apiName = this.name,
|
||||
type = TvType.Movie,
|
||||
dataUrl = episodeList[0].data,
|
||||
posterUrl = poster,
|
||||
year = year,
|
||||
plot = descript,
|
||||
recommendations = recs
|
||||
)
|
||||
}
|
||||
return TvSeriesLoadResponse(
|
||||
name = title,
|
||||
url = url,
|
||||
apiName = this.name,
|
||||
type = TvType.AsianDrama,
|
||||
episodes = episodeList?.reversed() ?: emptyList(),
|
||||
posterUrl = poster,
|
||||
year = year,
|
||||
plot = descript,
|
||||
recommendations = recs
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
var count = 0
|
||||
parseJson<List<String>>(data).apmap { item ->
|
||||
if (item.isNotBlank()) {
|
||||
count++
|
||||
if (item.startsWith(mainUrl)) {
|
||||
val text = item.substring(mainUrl.length)
|
||||
//Log.i(this.name, "Result => (text) $text")
|
||||
//Find video files
|
||||
try {
|
||||
"(?<=sources: )([\\s\\S]*?)(?<=])".toRegex().find(text)?.value?.let { vid ->
|
||||
parseJson<List<ResponseDatas>>(vid).forEach { src ->
|
||||
//Log.i(this.name, "Result => (src) ${src.toJson()}")
|
||||
callback(
|
||||
ExtractorLink(
|
||||
name = name,
|
||||
url = src.file,
|
||||
quality = getQualityFromName(src.label),
|
||||
referer = mainUrl,
|
||||
source = name
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
//Find subtitles
|
||||
try {
|
||||
"(?<=tracks: )([\\s\\S]*?)(?<=])".toRegex().find(text)?.value?.let { sub ->
|
||||
val subtext = sub.replace("file:", "\"file\":")
|
||||
.replace("label:", "\"label\":")
|
||||
.replace("kind:", "\"kind\":")
|
||||
parseJson<List<ResponseDatas>>(subtext).forEach { src ->
|
||||
//Log.i(this.name, "Result => (sub) ${src.toJson()}")
|
||||
subtitleCallback(
|
||||
SubtitleFile(
|
||||
lang = src.label,
|
||||
url = src.file
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
|
||||
} else {
|
||||
val url = fixUrl(item.trim())
|
||||
//Log.i(this.name, "Result => (url) $url")
|
||||
when {
|
||||
url.startsWith("https://asianembed.io") -> {
|
||||
com.lagradost.AsianEmbedHelper.getUrls(url, subtitleCallback, callback)
|
||||
}
|
||||
url.startsWith("https://embedsito.com") -> {
|
||||
val extractor = com.lagradost.XStreamCdn()
|
||||
extractor.domainUrl = "embedsito.com"
|
||||
extractor.getUrl(url).forEach { link ->
|
||||
callback.invoke(link)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
loadExtractor(url, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import java.net.URI
|
||||
|
||||
class MultiQuality : ExtractorApi() {
|
||||
override var name = "MultiQuality"
|
||||
override var mainUrl = "https://gogo-play.net"
|
||||
private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""")
|
||||
private val m3u8Regex = Regex(""".*?(\d*).m3u8""")
|
||||
private val urlRegex = Regex("""(.*?)([^/]+$)""")
|
||||
override val requiresReferer = false
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/loadserver.php?id=$id"
|
||||
}
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
with(app.get(url)) {
|
||||
sourceRegex.findAll(this.text).forEach { sourceMatch ->
|
||||
val extractedUrl = sourceMatch.groupValues[1]
|
||||
// Trusting this isn't mp4, may fuck up stuff
|
||||
if (URI(extractedUrl).path.endsWith(".m3u8")) {
|
||||
with(app.get(extractedUrl)) {
|
||||
m3u8Regex.findAll(this.text).forEach { match ->
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name = name,
|
||||
urlRegex.find(this.url)!!.groupValues[1] + match.groupValues[0],
|
||||
url,
|
||||
getQualityFromName(match.groupValues[1]),
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
} else if (extractedUrl.endsWith(".mp4")) {
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
"$name ${sourceMatch.groupValues[2]}",
|
||||
extractedUrl,
|
||||
url.replace(" ", "%20"),
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.Vidstream
|
||||
import com.lagradost.cloudstream3.metaproviders.TmdbLink
|
||||
import com.lagradost.cloudstream3.metaproviders.TmdbProvider
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class OpenVidsProvider:TmdbProvider() {
|
||||
override val apiName = "OpenVids"
|
||||
override var name = "OpenVids"
|
||||
override var mainUrl = "https://openvids.io"
|
||||
override val useMetaLoadResponse = true
|
||||
override val instantLinkLoading = false
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
)
|
||||
|
||||
data class OpenvidsMain(
|
||||
@JsonProperty("ok" ) val ok : Boolean? = null,
|
||||
@JsonProperty("servers" ) val servers : OpenvidServers? = OpenvidServers()
|
||||
)
|
||||
|
||||
data class OpenvidServers (
|
||||
@JsonProperty("streamsb" ) val streamsb : OpenvidServersData? = OpenvidServersData(),
|
||||
@JsonProperty("voxzer" ) val voxzer : OpenvidServersData? = OpenvidServersData(),
|
||||
@JsonProperty("mixdrop" ) val mixdrop : OpenvidServersData? = OpenvidServersData(),
|
||||
@JsonProperty("doodstream" ) val doodstream : OpenvidServersData? = OpenvidServersData(),
|
||||
@JsonProperty("voe" ) val voe : OpenvidServersData? = OpenvidServersData(),
|
||||
@JsonProperty("vidcloud" ) val vidcloud : OpenvidServersData? = OpenvidServersData()
|
||||
)
|
||||
data class OpenvidServersData (
|
||||
@JsonProperty("code" ) val code : String? = null,
|
||||
@JsonProperty("updatedAt" ) val updatedAt : String? = null,
|
||||
@JsonProperty("encoded" ) val encoded : Boolean? = null
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val mappedData = parseJson<TmdbLink>(data)
|
||||
val (id, site) = if (mappedData.imdbID != null) listOf(
|
||||
mappedData.imdbID,
|
||||
"imdb"
|
||||
) else listOf(mappedData.tmdbID.toString(), "tmdb")
|
||||
val isMovie = mappedData.episode == null && mappedData.season == null
|
||||
val embedUrl = if (isMovie) {
|
||||
if(site == "imdb") "$mainUrl/movie/$id" else
|
||||
"$mainUrl/tmdb/movie/$id"
|
||||
} else {
|
||||
val suffix = "$id-${mappedData.season ?: 1}-${mappedData.episode ?: 1}"
|
||||
if (site == "imdb") "$mainUrl/episode/$suffix" else
|
||||
"$mainUrl/tmdb/episode/$suffix"
|
||||
}
|
||||
val zonedatetime = ZonedDateTime.now()
|
||||
val timeformated = DateTimeFormatter.ISO_INSTANT.format(zonedatetime)
|
||||
val headers = if (isMovie) {
|
||||
mapOf(
|
||||
"Host" to "openvids.io",
|
||||
"User-Agent" to USER_AGENT,
|
||||
"Accept" to "*/*",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"Referer" to embedUrl,
|
||||
"updatedAt" to timeformated,
|
||||
"title" to "${mappedData.movieName}",
|
||||
"year" to "2016",
|
||||
"DNT" to "1",
|
||||
"Alt-Used" to "openvids.io",
|
||||
"Connection" to "keep-alive",
|
||||
"Sec-Fetch-Dest" to "empty",
|
||||
"Sec-Fetch-Mode" to "cors",
|
||||
"Sec-Fetch-Site" to "same-origin",
|
||||
)
|
||||
} else {
|
||||
mapOf(
|
||||
"Host" to "openvids.io",
|
||||
"User-Agent" to USER_AGENT,
|
||||
"Accept" to "*/*",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"Referer" to embedUrl,
|
||||
"updatedAt" to timeformated,
|
||||
"title" to "${mappedData.movieName} - season 1",
|
||||
"year" to "2021",
|
||||
"e" to "${mappedData.episode}",
|
||||
"s" to "${mappedData.season}",
|
||||
"DNT" to "1",
|
||||
"Alt-Used" to "openvids.io",
|
||||
"Connection" to "keep-alive",
|
||||
"Sec-Fetch-Dest" to "empty",
|
||||
"Sec-Fetch-Mode" to "cors",
|
||||
"Sec-Fetch-Site" to "same-origin",
|
||||
)
|
||||
}
|
||||
val json = app.get("$mainUrl/api/servers.json?imdb=${mappedData.imdbID}", headers = headers).parsedSafe<OpenvidsMain>()
|
||||
|
||||
val listservers = listOf(
|
||||
"https://streamsb.net/e/" to json?.servers?.streamsb?.code,
|
||||
"https://player.voxzer.org/view/" to json?.servers?.voxzer?.code,
|
||||
"https://mixdrop.co/e/" to json?.servers?.mixdrop?.code,
|
||||
"https://dood.pm/e/" to json?.servers?.doodstream?.code,
|
||||
"https://voe.sx/e/" to json?.servers?.voe?.code,
|
||||
"https://membed.net/streaming.php?id=" to json?.servers?.vidcloud?.code
|
||||
).mapNotNull { (url, id) -> if(id==null) return@mapNotNull null else "$url$id" }
|
||||
|
||||
if (json?.ok != true) return false
|
||||
listservers.apmap { links ->
|
||||
if (links.contains("membed")) {
|
||||
val membed = VidEmbedProvider()
|
||||
Vidstream.extractVidstream(
|
||||
links,
|
||||
this.name,
|
||||
callback,
|
||||
membed.iv,
|
||||
membed.secretKey,
|
||||
membed.secretDecryptKey,
|
||||
membed.isUsingAdaptiveKeys,
|
||||
membed.isUsingAdaptiveData)
|
||||
} else
|
||||
loadExtractor(links, data, subtitleCallback, callback)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
|
||||
/** Needs to inherit from MainAPI() to
|
||||
* make the app know what functions to call
|
||||
*/
|
||||
class VidEmbedProvider : VidstreamProviderTemplate() {
|
||||
// mainUrl is good to have as a holder for the url to make future changes easier.
|
||||
override var mainUrl = "https://membed.net"
|
||||
|
||||
// name is for how the provider will be named which is visible in the UI, no real rules for this.
|
||||
override var name = "VidEmbed"
|
||||
|
||||
override val homePageUrlList: List<String> = listOf(
|
||||
mainUrl,
|
||||
"$mainUrl/movies",
|
||||
"$mainUrl/series",
|
||||
"$mainUrl/recommended-series",
|
||||
"$mainUrl/cinema-movies"
|
||||
)
|
||||
|
||||
override val iv = "9225679083961858"
|
||||
override val secretKey = "25742532592138496744665879883281"
|
||||
override val secretDecryptKey = secretKey
|
||||
|
||||
// This is just extra metadata about what type of movies the provider has.
|
||||
// Needed for search functionality.
|
||||
override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie)
|
||||
}
|
235
VidstreamBundle/src/main/kotlin/com/lagradost/Vidstream.kt
Normal file
235
VidstreamBundle/src/main/kotlin/com/lagradost/Vidstream.kt
Normal file
|
@ -0,0 +1,235 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.MultiQuality
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import java.net.URI
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/**
|
||||
* overrideMainUrl is necessary for for other vidstream clones like vidembed.cc
|
||||
* If they diverge it'd be better to make them separate.
|
||||
* */
|
||||
class Vidstream(val mainUrl: String) {
|
||||
val name: String = "Vidstream"
|
||||
|
||||
companion object {
|
||||
data class GogoSources(
|
||||
@JsonProperty("source") val source: List<GogoSource>?,
|
||||
@JsonProperty("sourceBk") val sourceBk: List<GogoSource>?,
|
||||
//val track: List<Any?>,
|
||||
//val advertising: List<Any?>,
|
||||
//val linkiframe: String
|
||||
)
|
||||
|
||||
data class GogoSource(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("default") val default: String? = null
|
||||
)
|
||||
|
||||
// https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt#L60
|
||||
// No Licence on the function
|
||||
private fun cryptoHandler(
|
||||
string: String,
|
||||
iv: String,
|
||||
secretKeyString: String,
|
||||
encrypt: Boolean = true
|
||||
): String {
|
||||
//println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string")
|
||||
val ivParameterSpec = IvParameterSpec(iv.toByteArray())
|
||||
val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES")
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
return if (!encrypt) {
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec)
|
||||
String(cipher.doFinal(base64DecodeArray(string)))
|
||||
} else {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
|
||||
base64Encode(cipher.doFinal(string.toByteArray()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iframeUrl something like https://gogoplay4.com/streaming.php?id=XXXXXX
|
||||
* @param mainApiName used for ExtractorLink names and source
|
||||
* @param iv secret iv from site, required non-null if isUsingAdaptiveKeys is off
|
||||
* @param secretKey secret key for decryption from site, required non-null if isUsingAdaptiveKeys is off
|
||||
* @param secretDecryptKey secret key to decrypt the response json, required non-null if isUsingAdaptiveKeys is off
|
||||
* @param isUsingAdaptiveKeys generates keys from IV and ID, see getKey()
|
||||
* @param isUsingAdaptiveData generate encrypt-ajax data based on $("script[data-name='episode']")[0].dataset.value
|
||||
* */
|
||||
suspend fun extractVidstream(
|
||||
iframeUrl: String,
|
||||
mainApiName: String,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
iv: String?,
|
||||
secretKey: String?,
|
||||
secretDecryptKey: String?,
|
||||
// This could be removed, but i prefer it verbose
|
||||
isUsingAdaptiveKeys: Boolean,
|
||||
isUsingAdaptiveData: Boolean,
|
||||
// If you don't want to re-fetch the document
|
||||
iframeDocument: Document? = null
|
||||
) = safeApiCall {
|
||||
// https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt
|
||||
// No Licence on the following code
|
||||
// Also modified of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/extractors/GogoCdnExtractor.kt
|
||||
// License on the code above https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE
|
||||
|
||||
if ((iv == null || secretKey == null || secretDecryptKey == null) && !isUsingAdaptiveKeys)
|
||||
return@safeApiCall
|
||||
|
||||
val id = Regex("id=([^&]+)").find(iframeUrl)!!.value.removePrefix("id=")
|
||||
|
||||
var document: Document? = iframeDocument
|
||||
val foundIv =
|
||||
iv ?: (document ?: app.get(iframeUrl).document.also { document = it })
|
||||
.select("""div.wrapper[class*=container]""")
|
||||
.attr("class").split("-").lastOrNull() ?: return@safeApiCall
|
||||
val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall
|
||||
val foundDecryptKey = secretDecryptKey ?: foundKey
|
||||
|
||||
val uri = URI(iframeUrl)
|
||||
val mainUrl = "https://" + uri.host
|
||||
|
||||
val encryptedId = cryptoHandler(id, foundIv, foundKey)
|
||||
val encryptRequestData = if (isUsingAdaptiveData) {
|
||||
// Only fetch the document if necessary
|
||||
val realDocument = document ?: app.get(iframeUrl).document
|
||||
val dataEncrypted =
|
||||
realDocument.select("script[data-name='episode']").attr("data-value")
|
||||
val headers = cryptoHandler(dataEncrypted, foundIv, foundKey, false)
|
||||
"id=$encryptedId&alias=$id&" + headers.substringAfter("&")
|
||||
} else {
|
||||
"id=$encryptedId&alias=$id"
|
||||
}
|
||||
|
||||
val jsonResponse =
|
||||
app.get(
|
||||
"$mainUrl/encrypt-ajax.php?$encryptRequestData",
|
||||
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
)
|
||||
val dataencrypted =
|
||||
jsonResponse.text.substringAfter("{\"data\":\"").substringBefore("\"}")
|
||||
val datadecrypted = cryptoHandler(dataencrypted, foundIv, foundDecryptKey, false)
|
||||
val sources = AppUtils.parseJson<GogoSources>(datadecrypted)
|
||||
|
||||
fun invokeGogoSource(
|
||||
source: GogoSource,
|
||||
sourceCallback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
sourceCallback.invoke(
|
||||
ExtractorLink(
|
||||
mainApiName,
|
||||
mainApiName,
|
||||
source.file,
|
||||
mainUrl,
|
||||
getQualityFromName(source.label),
|
||||
isM3u8 = source.type == "hls" || source.label?.contains(
|
||||
"auto",
|
||||
ignoreCase = true
|
||||
) == true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
sources.source?.forEach {
|
||||
invokeGogoSource(it, callback)
|
||||
}
|
||||
sources.sourceBk?.forEach {
|
||||
invokeGogoSource(it, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/streaming.php?id=$id"
|
||||
}
|
||||
|
||||
private fun getDownloadUrl(id: String): String {
|
||||
return "$mainUrl/download?id=$id"
|
||||
}
|
||||
|
||||
private val normalApis = arrayListOf(MultiQuality())
|
||||
|
||||
// https://gogo-stream.com/streaming.php?id=MTE3NDg5
|
||||
suspend fun getUrl(
|
||||
id: String,
|
||||
isCasting: Boolean = false,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
): Boolean {
|
||||
val extractorUrl = getExtractorUrl(id)
|
||||
argamap(
|
||||
{
|
||||
normalApis.apmap { api ->
|
||||
val url = api.getExtractorUrl(id)
|
||||
api.getSafeUrl(
|
||||
url,
|
||||
callback = callback,
|
||||
subtitleCallback = subtitleCallback
|
||||
)
|
||||
}
|
||||
}, {
|
||||
/** Stolen from GogoanimeProvider.kt extractor */
|
||||
val link = getDownloadUrl(id)
|
||||
println("Generated vidstream download link: $link")
|
||||
val page = app.get(link, referer = extractorUrl)
|
||||
|
||||
val pageDoc = Jsoup.parse(page.text)
|
||||
val qualityRegex = Regex("(\\d+)P")
|
||||
|
||||
//a[download]
|
||||
pageDoc.select(".dowload > a")?.apmap { element ->
|
||||
val href = element.attr("href") ?: return@apmap
|
||||
val qual = if (element.text()
|
||||
.contains("HDP")
|
||||
) "1080" else qualityRegex.find(element.text())?.destructured?.component1()
|
||||
.toString()
|
||||
|
||||
if (!loadExtractor(href, link, subtitleCallback, callback)) {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
name = this.name,
|
||||
href,
|
||||
page.url,
|
||||
getQualityFromName(qual),
|
||||
element.attr("href").contains(".m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
with(app.get(extractorUrl)) {
|
||||
val document = Jsoup.parse(this.text)
|
||||
val primaryLinks = document.select("ul.list-server-items > li.linkserver")
|
||||
//val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
|
||||
// All vidstream links passed to extractors
|
||||
primaryLinks.distinctBy { it.attr("data-video") }.forEach { element ->
|
||||
val link = element.attr("data-video")
|
||||
//val name = element.text()
|
||||
|
||||
// Matches vidstream links with extractors
|
||||
extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
|
||||
if (link.startsWith(api.mainUrl)) {
|
||||
api.getSafeUrl(link, extractorUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||
import com.lagradost.cloudstream3.plugins.Plugin
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.extractors.Vidstream
|
||||
|
||||
@CloudstreamPlugin
|
||||
class VidstreamBundlePlugin : Plugin() {
|
||||
override fun load(context: Context) {
|
||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerExtractorAPI(MultiQuality())
|
||||
registerExtractorAPI(XStreamCdn())
|
||||
registerExtractorAPI(LayarKaca())
|
||||
registerExtractorAPI(DBfilm())
|
||||
registerExtractorAPI(Luxubu())
|
||||
registerExtractorAPI(FEmbed())
|
||||
registerExtractorAPI(Fplayer())
|
||||
registerExtractorAPI(FeHD())
|
||||
registerMainAPI(VidEmbedProvider())
|
||||
registerMainAPI(OpenVidsProvider())
|
||||
registerMainAPI(KdramaHoodProvider())
|
||||
registerMainAPI(DramaSeeProvider())
|
||||
registerMainAPI(AsianLoadProvider())
|
||||
registerMainAPI(WatchAsianProvider())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.Vidstream
|
||||
//import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider.Companion.extractVidstream
|
||||
//import com.lagradost.cloudstream3.extractors.Vidstream
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import org.jsoup.Jsoup
|
||||
import java.net.URI
|
||||
|
||||
/** Needs to inherit from MainAPI() to
|
||||
* make the app know what functions to call
|
||||
*/
|
||||
open class VidstreamProviderTemplate : MainAPI() {
|
||||
open val homePageUrlList = listOf<String>()
|
||||
open val vidstreamExtractorUrl: String? = null
|
||||
|
||||
/**
|
||||
* Used to generate encrypted video links.
|
||||
* Try keys from other providers before cracking
|
||||
* one yourself.
|
||||
* */
|
||||
// Userscript to get the keys:
|
||||
|
||||
/*
|
||||
// ==UserScript==
|
||||
// @name Easy keys
|
||||
// @namespace Violentmonkey Scripts
|
||||
// @match https://*/streaming.php*
|
||||
// @grant none
|
||||
// @version 1.0
|
||||
// @author LagradOst
|
||||
// @description 4/16/2022, 2:05:31 PM
|
||||
// ==/UserScript==
|
||||
|
||||
let encrypt = CryptoJS.AES.encrypt;
|
||||
CryptoJS.AES.encrypt = (message, key, cfg) => {
|
||||
let realKey = CryptoJS.enc.Utf8.stringify(key);
|
||||
let realIv = CryptoJS.enc.Utf8.stringify(cfg.iv);
|
||||
|
||||
var result = encrypt(message, key, cfg);
|
||||
let realResult = CryptoJS.enc.Utf8.stringify(result);
|
||||
|
||||
popup = "Encrypt key: " + realKey + "\n\nIV: " + realIv + "\n\nMessage: " + message + "\n\nResult: " + realResult;
|
||||
alert(popup);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
let decrypt = CryptoJS.AES.decrypt;
|
||||
CryptoJS.AES.decrypt = (message, key, cfg) => {
|
||||
let realKey = CryptoJS.enc.Utf8.stringify(key);
|
||||
let realIv = CryptoJS.enc.Utf8.stringify(cfg.iv);
|
||||
|
||||
let result = decrypt(message, key, cfg);
|
||||
let realResult = CryptoJS.enc.Utf8.stringify(result);
|
||||
|
||||
popup = "Decrypt key: " + realKey + "\n\nIV: " + realIv + "\n\nMessage: " + message + "\n\nResult: " + realResult;
|
||||
alert(popup);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
*/
|
||||
*/
|
||||
|
||||
open val iv: String? = null
|
||||
open val secretKey: String? = null
|
||||
open val secretDecryptKey: String? = null
|
||||
|
||||
/** Generated the key from IV and ID */
|
||||
open val isUsingAdaptiveKeys: Boolean = false
|
||||
|
||||
/**
|
||||
* Generate data for the encrypt-ajax automatically (only on supported sites)
|
||||
* See $("script[data-name='episode']")[0].dataset.value
|
||||
* */
|
||||
open val isUsingAdaptiveData: Boolean = false
|
||||
|
||||
|
||||
// // mainUrl is good to have as a holder for the url to make future changes easier.
|
||||
// override val mainUrl: String
|
||||
// get() = "https://vidembed.cc"
|
||||
//
|
||||
// // name is for how the provider will be named which is visible in the UI, no real rules for this.
|
||||
// override val name: String
|
||||
// get() = "VidEmbed"
|
||||
|
||||
// hasQuickSearch defines if quickSearch() should be called, this is only when typing the searchbar
|
||||
// gives results on the site instead of bringing you to another page.
|
||||
// if hasQuickSearch is true and quickSearch() hasn't been overridden you will get errors.
|
||||
// VidEmbed actually has quick search on their site, but the function wasn't implemented.
|
||||
override val hasQuickSearch = false
|
||||
|
||||
// If getMainPage() is functional, used to display the homepage in app, an optional, but highly encouraged endevour.
|
||||
override val hasMainPage = true
|
||||
|
||||
// Searching returns a SearchResponse, which can be one of the following: AnimeSearchResponse, MovieSearchResponse, TorrentSearchResponse, TvSeriesSearchResponse
|
||||
// Each of the classes requires some different data, but always has some critical things like name, poster and url.
|
||||
override suspend fun search(query: String): ArrayList<SearchResponse> {
|
||||
// Simply looking at devtools network is enough to spot a request like:
|
||||
// https://vidembed.cc/search.html?keyword=neverland where neverland is the query, can be written as below.
|
||||
val link = "$mainUrl/search.html?keyword=$query"
|
||||
val html = app.get(link).text
|
||||
val soup = Jsoup.parse(html)
|
||||
|
||||
return ArrayList(soup.select(".listing.items > .video-block").map { li ->
|
||||
// Selects the href in <a href="...">
|
||||
val href = fixUrl(li.selectFirst("a")!!.attr("href"))
|
||||
val poster = li.selectFirst("img")?.attr("src")
|
||||
|
||||
// .text() selects all the text in the element, be careful about doing this while too high up in the html hierarchy
|
||||
val title = li.selectFirst(".name")!!.text()
|
||||
// Use get(0) and toIntOrNull() to prevent any possible crashes, [0] or toInt() will error the search on unexpected values.
|
||||
val year = li.selectFirst(".date")?.text()?.split("-")?.get(0)?.toIntOrNull()
|
||||
|
||||
TvSeriesSearchResponse(
|
||||
// .trim() removes unwanted spaces in the start and end.
|
||||
if (!title.contains("Episode")) title else title.split("Episode")[0].trim(),
|
||||
href,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
poster, year,
|
||||
// You can't get the episodes from the search bar.
|
||||
null
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Load, like the name suggests loads the info page, where all the episodes and data usually is.
|
||||
// Like search you should return either of: AnimeLoadResponse, MovieLoadResponse, TorrentLoadResponse, TvSeriesLoadResponse.
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
// Gets the url returned from searching.
|
||||
val html = app.get(url).text
|
||||
val soup = Jsoup.parse(html)
|
||||
|
||||
var title = soup.selectFirst("h1,h2,h3")!!.text()
|
||||
title = if (!title.contains("Episode")) title else title.split("Episode")[0].trim()
|
||||
|
||||
val description = soup.selectFirst(".post-entry")?.text()?.trim()
|
||||
var poster: String? = null
|
||||
var year: Int? = null
|
||||
|
||||
val episodes =
|
||||
soup.select(".listing.items.lists > .video-block").withIndex().map { (_, li) ->
|
||||
val epTitle = if (li.selectFirst(".name") != null)
|
||||
if (li.selectFirst(".name")!!.text().contains("Episode"))
|
||||
"Episode " + li.selectFirst(".name")!!.text().split("Episode")[1].trim()
|
||||
else
|
||||
li.selectFirst(".name")!!.text()
|
||||
else ""
|
||||
val epThumb = li.selectFirst("img")?.attr("src")
|
||||
val epDate = li.selectFirst(".meta > .date")!!.text()
|
||||
|
||||
if (poster == null) {
|
||||
poster = li.selectFirst("img")?.attr("onerror")?.split("=")?.get(1)
|
||||
?.replace(Regex("[';]"), "")
|
||||
}
|
||||
|
||||
val epNum = Regex("""Episode (\d+)""").find(epTitle)?.destructured?.component1()
|
||||
?.toIntOrNull()
|
||||
if (year == null) {
|
||||
year = epDate.split("-")[0].toIntOrNull()
|
||||
}
|
||||
newEpisode(li.selectFirst("a")!!.attr("href")) {
|
||||
this.episode = epNum
|
||||
this.posterUrl = epThumb
|
||||
addDate(epDate)
|
||||
}
|
||||
}.reversed()
|
||||
|
||||
// Make sure to get the type right to display the correct UI.
|
||||
val tvType =
|
||||
if (episodes.size == 1 && episodes[0].name == title) TvType.Movie else TvType.TvSeries
|
||||
|
||||
return when (tvType) {
|
||||
TvType.TvSeries -> {
|
||||
TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
tvType,
|
||||
episodes,
|
||||
poster,
|
||||
year,
|
||||
description,
|
||||
ShowStatus.Ongoing,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
TvType.Movie -> {
|
||||
MovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
tvType,
|
||||
episodes[0].data,
|
||||
poster,
|
||||
year,
|
||||
description,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
// This loads the homepage, which is basically a collection of search results with labels.
|
||||
// Optional function, but make sure to enable hasMainPage if you program this.
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val urls = homePageUrlList
|
||||
val homePageList = ArrayList<HomePageList>()
|
||||
// .pmap {} is used to fetch the different pages in parallel
|
||||
urls.apmap { url ->
|
||||
val response = app.get(url, timeout = 20).text
|
||||
val document = Jsoup.parse(response)
|
||||
document.select("div.main-inner").forEach { inner ->
|
||||
// Always trim your text unless you want the risk of spaces at the start or end.
|
||||
val title = inner.select(".widget-title").text().trim()
|
||||
val elements = inner.select(".video-block").map {
|
||||
val link = fixUrl(it.select("a").attr("href"))
|
||||
val image = it.select(".picture > img").attr("src")
|
||||
val name =
|
||||
it.select("div.name").text().trim().replace(Regex("""[Ee]pisode \d+"""), "")
|
||||
val isSeries = (name.contains("Season") || name.contains("Episode"))
|
||||
|
||||
if (isSeries) {
|
||||
newTvSeriesSearchResponse(name, link) {
|
||||
posterUrl = image
|
||||
}
|
||||
} else {
|
||||
newMovieSearchResponse(name, link) {
|
||||
posterUrl = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
homePageList.add(
|
||||
HomePageList(
|
||||
title, elements
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return HomePageResponse(homePageList)
|
||||
}
|
||||
|
||||
// loadLinks gets the raw .mp4 or .m3u8 urls from the data parameter in the episodes class generated in load()
|
||||
// See Episode(...) in this provider.
|
||||
// The data are usually links, but can be any other string to help aid loading the links.
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
// These callbacks are functions you should call when you get a link to a subtitle file or media file.
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
// "?: return" is a very useful statement which returns if the iframe link isn't found.
|
||||
val iframeLink =
|
||||
Jsoup.parse(app.get(data).text).selectFirst("iframe")?.attr("src") ?: return false
|
||||
|
||||
// extractVidstream(
|
||||
// iframeLink,
|
||||
// this.name,
|
||||
// callback,
|
||||
// iv,
|
||||
// secretKey,
|
||||
// secretDecryptKey,
|
||||
// isUsingAdaptiveKeys,
|
||||
// isUsingAdaptiveData
|
||||
// )
|
||||
// In this case the video player is a vidstream clone and can be handled by the vidstream extractor.
|
||||
// This case is a both unorthodox and you normally do not call extractors as they detect the url returned and does the rest.
|
||||
val vidstreamObject = Vidstream(vidstreamExtractorUrl ?: mainUrl)
|
||||
// https://vidembed.cc/streaming.php?id=MzUwNTY2&... -> MzUwNTY2
|
||||
val id = Regex("""id=([^&]*)""").find(iframeLink)?.groupValues?.get(1)
|
||||
|
||||
if (id != null) {
|
||||
vidstreamObject.getUrl(id, isCasting, subtitleCallback, callback)
|
||||
}
|
||||
|
||||
val html = app.get(fixUrl(iframeLink)).text
|
||||
val soup = Jsoup.parse(html)
|
||||
|
||||
val servers = soup.select(".list-server-items > .linkserver").mapNotNull { li ->
|
||||
if (!li?.attr("data-video").isNullOrEmpty()) {
|
||||
Pair(li.text(), fixUrl(li.attr("data-video")))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
servers.apmap {
|
||||
// When checking strings make sure to make them lowercase and trimmed because edgecases like "beta server " wouldn't work otherwise.
|
||||
if (it.first.trim().equals("beta server", ignoreCase = true)) {
|
||||
// Group 1: link, Group 2: Label
|
||||
// Regex can be used to effectively parse small amounts of json without bothering with writing a json class.
|
||||
val sourceRegex =
|
||||
Regex("""sources:[\W\w]*?file:\s*["'](.*?)["'][\W\w]*?label:\s*["'](.*?)["']""")
|
||||
val trackRegex =
|
||||
Regex("""tracks:[\W\w]*?file:\s*["'](.*?)["'][\W\w]*?label:\s*["'](.*?)["']""")
|
||||
|
||||
// Having a referer is often required. It's a basic security check most providers have.
|
||||
// Try to replicate what your browser does.
|
||||
val serverHtml = app.get(it.second, headers = mapOf("referer" to iframeLink)).text
|
||||
sourceRegex.findAll(serverHtml).forEach { match ->
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
match.groupValues.getOrNull(2)?.let { "${this.name} $it" } ?: this.name,
|
||||
match.groupValues[1],
|
||||
it.second,
|
||||
// Useful function to turn something like "1080p" to an app quality.
|
||||
getQualityFromName(match.groupValues.getOrNull(2) ?: ""),
|
||||
// Kinda risky
|
||||
// isM3u8 makes the player pick the correct extractor for the source.
|
||||
// If isM3u8 is wrong the player will error on that source.
|
||||
URI(match.groupValues[1]).path.endsWith(".m3u8"),
|
||||
)
|
||||
)
|
||||
}
|
||||
trackRegex.findAll(serverHtml).forEach { match ->
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
match.groupValues.getOrNull(2) ?: "Unknown",
|
||||
match.groupValues[1]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.Vidstream
|
||||
//import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider.Companion.extractVidstream
|
||||
//import com.lagradost.cloudstream3.extractors.XStreamCdn
|
||||
//import com.lagradost.cloudstream3.extractors.helper.AsianEmbedHelper
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class WatchAsianProvider : MainAPI() {
|
||||
override var mainUrl = "https://watchasian.cx"
|
||||
override var name = "WatchAsian"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = false
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val headers = mapOf("X-Requested-By" to mainUrl)
|
||||
val doc = app.get(mainUrl, headers = headers).document
|
||||
val rowPair = mutableListOf<Pair<String, String>>()
|
||||
doc.select("div.block-tab").forEach {
|
||||
it?.select("ul.tab > li")?.mapNotNull { row ->
|
||||
val link = row?.attr("data-tab") ?: return@mapNotNull null
|
||||
val title = row.text() ?: return@mapNotNull null
|
||||
Pair(title, link)
|
||||
}?.let { it1 ->
|
||||
rowPair.addAll(
|
||||
it1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return HomePageResponse(
|
||||
rowPair.mapNotNull { row ->
|
||||
val main = (doc.select("div.tab-content.${row.second}")
|
||||
?: doc.select("div.tab-content.${row.second}.selected"))
|
||||
?: return@mapNotNull null
|
||||
|
||||
val title = row.first
|
||||
val inner = main.select("li") ?: return@mapNotNull null
|
||||
|
||||
HomePageList(
|
||||
title,
|
||||
inner.map {
|
||||
// Get inner div from article
|
||||
val innerBody = it?.selectFirst("a")
|
||||
// Fetch details
|
||||
val link = fixUrlNull(innerBody?.attr("href")) ?: return@map null
|
||||
val image =
|
||||
fixUrlNull(innerBody?.select("img")?.attr("data-original")) ?: ""
|
||||
val name = (innerBody?.selectFirst("h3.title")?.text() ?: innerBody?.text())
|
||||
?: "<Untitled>"
|
||||
//Log.i(this.name, "Result => (innerBody, image) ${innerBody} / ${image}")
|
||||
MovieSearchResponse(
|
||||
name,
|
||||
link,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
image,
|
||||
year = null,
|
||||
id = null,
|
||||
)
|
||||
}.filterNotNull().distinctBy { c -> c.url })
|
||||
}.filter { a -> a.list.isNotEmpty() }
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/search?type=movies&keyword=$query"
|
||||
val document = app.get(url).document.getElementsByTag("body")
|
||||
.select("div.block.tab-container > div > ul > li") ?: return listOf()
|
||||
|
||||
return document.mapNotNull {
|
||||
val innerA = it?.selectFirst("a") ?: return@mapNotNull null
|
||||
val link = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null
|
||||
val title = it.select("h3.title").text() ?: return@mapNotNull null
|
||||
if (title.isEmpty()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
val year = null
|
||||
val imgsrc = innerA.select("img").attr("data-original") ?: return@mapNotNull null
|
||||
val image = fixUrlNull(imgsrc)
|
||||
//Log.i(this.name, "Result => (img movie) $title / $link")
|
||||
MovieSearchResponse(
|
||||
title,
|
||||
link,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
image,
|
||||
year
|
||||
)
|
||||
}.distinctBy { a -> a.url }
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val body = app.get(url).document
|
||||
// Declare vars
|
||||
val isDramaDetail = url.contains("/drama-detail/")
|
||||
var poster: String? = null
|
||||
var title = ""
|
||||
var descript: String? = null
|
||||
var year: Int? = null
|
||||
var tags: List<String>? = null
|
||||
if (isDramaDetail) {
|
||||
val main = body.select("div.details")
|
||||
val inner = main.select("div.info")
|
||||
// Video details
|
||||
poster = fixUrlNull(main.select("div.img > img").attr("src"))
|
||||
//Log.i(this.name, "Result => (imgLinkCode) ${imgLinkCode}")
|
||||
title = inner.select("h1").firstOrNull()?.text() ?: ""
|
||||
//Log.i(this.name, "Result => (year) ${title.substring(title.length - 5)}")
|
||||
descript = inner.text()
|
||||
|
||||
inner.select("p").forEach { p ->
|
||||
val caption =
|
||||
p?.selectFirst("span")?.text()?.trim()?.lowercase()?.removeSuffix(":")?.trim()
|
||||
?: return@forEach
|
||||
when (caption) {
|
||||
"genre" -> {
|
||||
tags = p.select("a").mapNotNull { it?.text()?.trim() }
|
||||
}
|
||||
"released" -> {
|
||||
year = p.select("a").text().trim()?.toIntOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
poster = body.select("meta[itemprop=\"image\"]")?.attr("content") ?: ""
|
||||
title = body.selectFirst("div.block.watch-drama")?.selectFirst("h1")
|
||||
?.text() ?: ""
|
||||
year = null
|
||||
descript = body.select("meta[name=\"description\"]")?.attr("content")
|
||||
}
|
||||
//Fallback year from title
|
||||
if (year == null) {
|
||||
year = if (title.length > 5) {
|
||||
title.replace(")", "").replace("(", "").substring(title.length - 5)
|
||||
.trim().trimEnd(')').toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// Episodes Links
|
||||
//Log.i(this.name, "Result => (all eps) ${body.select("ul.list-episode-item-2.all-episode > li")}")
|
||||
val episodeList = body.select("ul.list-episode-item-2.all-episode > li").mapNotNull { ep ->
|
||||
//Log.i(this.name, "Result => (epA) ${ep.select("a")}")
|
||||
val innerA = ep.select("a") ?: return@mapNotNull null
|
||||
//Log.i(this.name, "Result => (innerA) ${fixUrlNull(innerA.attr("href"))}")
|
||||
val epLink = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null
|
||||
|
||||
val regex = "(?<=episode-).*?(?=.html)".toRegex()
|
||||
val count = regex.find(epLink, mainUrl.length)?.value?.toIntOrNull() ?: 0
|
||||
//Log.i(this.name, "Result => $epLink (regexYear) ${count}")
|
||||
Episode(
|
||||
name = null,
|
||||
season = null,
|
||||
episode = count,
|
||||
data = epLink,
|
||||
posterUrl = poster,
|
||||
date = null
|
||||
)
|
||||
}
|
||||
//If there's only 1 episode, consider it a movie.
|
||||
if (episodeList.size == 1) {
|
||||
//Clean title
|
||||
title = title.trim().removeSuffix("Episode 1")
|
||||
val streamlink = getServerLinks(episodeList[0].data)
|
||||
//Log.i(this.name, "Result => (streamlink) $streamlink")
|
||||
return MovieLoadResponse(
|
||||
name = title,
|
||||
url = url,
|
||||
apiName = this.name,
|
||||
type = TvType.Movie,
|
||||
dataUrl = streamlink,
|
||||
posterUrl = poster,
|
||||
year = year,
|
||||
plot = descript,
|
||||
tags = tags
|
||||
)
|
||||
}
|
||||
return TvSeriesLoadResponse(
|
||||
name = title,
|
||||
url = url,
|
||||
apiName = this.name,
|
||||
type = TvType.AsianDrama,
|
||||
episodes = episodeList.reversed(),
|
||||
posterUrl = poster,
|
||||
year = year,
|
||||
plot = descript,
|
||||
tags = tags
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val links = if (data.startsWith(mainUrl)) {
|
||||
getServerLinks(data)
|
||||
} else {
|
||||
data
|
||||
}
|
||||
var count = 0
|
||||
parseJson<List<String>>(links).apmap { item ->
|
||||
count++
|
||||
val url = fixUrl(item.trim())
|
||||
//Log.i(this.name, "Result => (url) $url")
|
||||
when {
|
||||
url.startsWith("https://asianembed.io") || url.startsWith("https://asianload.io") -> {
|
||||
val iv = "9262859232435825"
|
||||
val secretKey = "93422192433952489752342908585752"
|
||||
Vidstream.extractVidstream(
|
||||
url, this.name, callback, iv, secretKey, secretKey,
|
||||
isUsingAdaptiveKeys = false,
|
||||
isUsingAdaptiveData = false
|
||||
)
|
||||
AsianEmbedHelper.getUrls(url, subtitleCallback, callback)
|
||||
}
|
||||
url.startsWith("https://embedsito.com") -> {
|
||||
val extractor = XStreamCdn()
|
||||
extractor.domainUrl = "embedsito.com"
|
||||
extractor.getSafeUrl(
|
||||
url,
|
||||
subtitleCallback = subtitleCallback,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
loadExtractor(url, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
|
||||
private suspend fun getServerLinks(url: String): String {
|
||||
val moviedoc = app.get(url, referer = mainUrl).document
|
||||
return moviedoc.select("div.anime_muti_link > ul > li")
|
||||
.mapNotNull {
|
||||
fixUrlNull(it?.attr("data-video")) ?: return@mapNotNull null
|
||||
}.toJson()
|
||||
}
|
||||
}
|
93
VidstreamBundle/src/main/kotlin/com/lagradost/XStreamCdn.kt
Normal file
93
VidstreamBundle/src/main/kotlin/com/lagradost/XStreamCdn.kt
Normal file
|
@ -0,0 +1,93 @@
|
|||
package com.lagradost
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
|
||||
class LayarKaca: XStreamCdn() {
|
||||
override val name: String = "LayarKaca-xxi"
|
||||
override val mainUrl: String = "https://layarkacaxxi.icu"
|
||||
}
|
||||
|
||||
class DBfilm: XStreamCdn() {
|
||||
override val name: String = "DBfilm"
|
||||
override val mainUrl: String = "https://dbfilm.bar"
|
||||
}
|
||||
|
||||
class Luxubu : XStreamCdn(){
|
||||
override val name: String = "FE"
|
||||
override val mainUrl: String = "https://www.luxubu.review"
|
||||
}
|
||||
|
||||
class FEmbed: XStreamCdn() {
|
||||
override val name: String = "FEmbed"
|
||||
override val mainUrl: String = "https://www.fembed.com"
|
||||
}
|
||||
|
||||
class Fplayer: XStreamCdn() {
|
||||
override val name: String = "Fplayer"
|
||||
override val mainUrl: String = "https://fplayer.info"
|
||||
}
|
||||
|
||||
class FeHD: XStreamCdn() {
|
||||
override val name: String = "FeHD"
|
||||
override val mainUrl: String = "https://fembed-hd.com"
|
||||
override var domainUrl: String = "fembed-hd.com"
|
||||
}
|
||||
|
||||
open class XStreamCdn : ExtractorApi() {
|
||||
override val name: String = "XStreamCdn"
|
||||
override val mainUrl: String = "https://embedsito.com"
|
||||
override val requiresReferer = false
|
||||
open var domainUrl: String = "embedsito.com"
|
||||
|
||||
private data class ResponseData(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String,
|
||||
//val type: String // Mp4
|
||||
)
|
||||
|
||||
private data class ResponseJson(
|
||||
@JsonProperty("success") val success: Boolean,
|
||||
@JsonProperty("data") val data: List<ResponseData>?
|
||||
)
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$domainUrl/api/source/$id"
|
||||
}
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val headers = mapOf(
|
||||
"Referer" to url,
|
||||
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0",
|
||||
)
|
||||
val id = url.trimEnd('/').split("/").last()
|
||||
val newUrl = "https://${domainUrl}/api/source/${id}"
|
||||
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
with(app.post(newUrl, headers = headers)) {
|
||||
if (this.code != 200) return listOf()
|
||||
val text = this.text
|
||||
if (text.isEmpty()) return listOf()
|
||||
if (text == """{"success":false,"data":"Video not found or has been removed"}""") return listOf()
|
||||
AppUtils.parseJson<ResponseJson?>(text)?.let {
|
||||
if (it.success && it.data != null) {
|
||||
it.data.forEach { data ->
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name = name,
|
||||
data.file,
|
||||
url,
|
||||
getQualityFromName(data.label),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue