Fix dependencies on other plugins causing errors

This commit is contained in:
Blatzar 2022-08-11 01:05:19 +02:00
parent 62f5d8258f
commit b74b8614db
62 changed files with 94 additions and 488 deletions

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

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