cloudstream/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt

532 lines
12 KiB
Kotlin

package com.lagradost.cloudstream3.utils
import android.net.Uri
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.extractors.*
import kotlinx.coroutines.delay
import org.jsoup.Jsoup
import kotlin.collections.MutableList
/**
* For use in the ConcatenatingMediaSource.
* If features are missing (headers), please report and we can add it.
* @param durationUs use Long.toUs() for easier input
* */
data class PlayListItem(
val url: String,
val durationUs: Long,
)
/**
* Converts Seconds to MicroSeconds, multiplication by 1_000_000
* */
fun Long.toUs(): Long {
return this * 1_000_000
}
/**
* If your site has an unorthodox m3u8-like system where there are multiple smaller videos concatenated
* use this.
* */
data class ExtractorLinkPlayList(
override val source: String,
override val name: String,
val playlist: List<PlayListItem>,
override val referer: String,
override val quality: Int,
override val isM3u8: Boolean = false,
override val headers: Map<String, String> = mapOf(),
/** Used for getExtractorVerifierJob() */
override val extractorData: String? = null,
) : ExtractorLink(
source,
name,
// Blank as un-used
"",
referer,
quality,
isM3u8,
headers,
extractorData
)
open class ExtractorLink constructor(
open val source: String,
open val name: String,
override val url: String,
override val referer: String,
open val quality: Int,
open val isM3u8: Boolean = false,
override val headers: Map<String, String> = mapOf(),
/** Used for getExtractorVerifierJob() */
open val extractorData: String? = null,
open val isDash: Boolean = false,
) : VideoDownloadManager.IDownloadableMinimum {
/**
* Old constructor without isDash, allows for backwards compatibility with extensions.
* Should be removed after all extensions have updated their cloudstream.jar
**/
constructor(
source: String,
name: String,
url: String,
referer: String,
quality: Int,
isM3u8: Boolean = false,
headers: Map<String, String> = mapOf(),
/** Used for getExtractorVerifierJob() */
extractorData: String? = null
) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false)
override fun toString(): String {
return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)"
}
}
data class ExtractorUri(
val uri: Uri,
val name: String,
val basePath: String? = null,
val relativePath: String? = null,
val displayName: String? = null,
val id: Int? = null,
val parentId: Int? = null,
val episode: Int? = null,
val season: Int? = null,
val headerName: String? = null,
val tvType: TvType? = null,
)
data class ExtractorSubtitleLink(
val name: String,
override val url: String,
override val referer: String,
override val headers: Map<String, String> = mapOf()
) : VideoDownloadManager.IDownloadableMinimum
/**
* Removes https:// and www.
* To match urls regardless of schema, perhaps Uri() can be used?
*/
val schemaStripRegex = Regex("""^(https:|)//(www\.|)""")
enum class Qualities(var value: Int) {
Unknown(400),
P144(144), // 144p
P240(240), // 240p
P360(360), // 360p
P480(480), // 480p
P720(720), // 720p
P1080(1080), // 1080p
P1440(1440), // 1440p
P2160(2160); // 4k or 2160p
companion object {
fun getStringByInt(qual: Int?): String {
return when (qual) {
0 -> "Auto"
Unknown.value -> ""
P2160.value -> "4K"
null -> ""
else -> "${qual}p"
}
}
}
}
fun getQualityFromName(qualityName: String?): Int {
if (qualityName == null)
return Qualities.Unknown.value
val match = qualityName.lowercase().replace("p", "").trim()
return when (match) {
"4k" -> Qualities.P2160
else -> null
}?.value ?: match.toIntOrNull() ?: Qualities.Unknown.value
}
private val packedRegex = Regex("""eval\(function\(p,a,c,k,e,.*\)\)""")
fun getPacked(string: String): String? {
return packedRegex.find(string)?.value
}
fun getAndUnpack(string: String): String {
val packedText = getPacked(string)
return JsUnpacker(packedText).unpack() ?: string
}
suspend fun unshortenLinkSafe(url: String): String {
return try {
if (ShortLink.isShortLink(url))
ShortLink.unshorten(url)
else url
} catch (e: Exception) {
logError(e)
url
}
}
suspend fun loadExtractor(
url: String,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
return loadExtractor(
url = url,
referer = null,
subtitleCallback = subtitleCallback,
callback = callback
)
}
/**
* Tries to load the appropriate extractor based on link, returns true if any extractor is loaded.
* */
suspend fun loadExtractor(
url: String,
referer: String? = null,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val currentUrl = unshortenLinkSafe(url)
val compareUrl = currentUrl.lowercase().replace(schemaStripRegex, "")
for (extractor in extractorApis) {
if (compareUrl.startsWith(extractor.mainUrl.replace(schemaStripRegex, ""))) {
extractor.getSafeUrl(currentUrl, referer, subtitleCallback, callback)
return true
}
}
return false
}
val extractorApis: MutableList<ExtractorApi> = arrayListOf(
//AllProvider(),
WcoStream(),
Vidstreamz(),
Vizcloud(),
Vizcloud2(),
VizcloudOnline(),
VizcloudXyz(),
VizcloudLive(),
VizcloudInfo(),
MwvnVizcloudInfo(),
VizcloudDigital(),
VizcloudCloud(),
VizcloudSite(),
VideoVard(),
VideovardSX(),
Mp4Upload(),
StreamTape(),
StreamTapeNet(),
ShaveTape(),
//mixdrop extractors
MixDropBz(),
MixDropCh(),
MixDropTo(),
MixDrop(),
Mcloud(),
XStreamCdn(),
StreamSB(),
Vidgomunimesb(),
StreamSB1(),
StreamSB2(),
StreamSB3(),
StreamSB4(),
StreamSB5(),
StreamSB6(),
StreamSB7(),
StreamSB8(),
StreamSB9(),
StreamSB10(),
StreamSB11(),
SBfull(),
// Streamhub(), cause Streamhub2() works
Streamhub2(),
Ssbstream(),
Sbthe(),
Vidgomunime(),
Sbflix(),
Streamsss(),
Sbspeed(),
Fastream(),
FEmbed(),
FeHD(),
Fplayer(),
DBfilm(),
Luxubu(),
LayarKaca(),
Rasacintaku(),
FEnet(),
Kotakajair(),
Cdnplayer(),
// WatchSB(), 'cause StreamSB.kt works
Uqload(),
Uqload1(),
Uqload2(),
Evoload(),
Evoload1(),
UpstreamExtractor(),
Tomatomatela(),
TomatomatelalClub(),
Cinestart(),
OkRu(),
OkRuHttps(),
Okrulink(),
Sendvid(),
// dood extractors
DoodCxExtractor(),
DoodPmExtractor(),
DoodToExtractor(),
DoodSoExtractor(),
DoodLaExtractor(),
DoodWsExtractor(),
DoodShExtractor(),
DoodWatchExtractor(),
DoodWfExtractor(),
DoodYtExtractor(),
AsianLoad(),
// GenericM3U8(),
Jawcloud(),
Zplayer(),
ZplayerV2(),
Upstream(),
Maxstream(),
Tantifilm(),
Userload(),
Supervideo(),
GuardareStream(),
CineGrabber(),
Vanfem(),
// StreamSB.kt works
// SBPlay(),
// SBPlay1(),
// SBPlay2(),
PlayerVoxzer(),
BullStream(),
GMPlayer(),
Blogger(),
Solidfiles(),
YourUpload(),
Hxfile(),
KotakAnimeid(),
Neonime8n(),
Neonime7n(),
Yufiles(),
Aico(),
JWPlayer(),
Meownime(),
DesuArcg(),
DesuOdchan(),
DesuOdvip(),
DesuDrive(),
Chillx(),
Watchx(),
Bestx(),
Keephealth(),
Sbnet(),
Sbasian(),
Sblongvu(),
Fembed9hd(),
StreamM4u(),
Krakenfiles(),
Gofile(),
Vicloud(),
Uservideo(),
Movhide(),
StreamhideCom(),
FileMoonIn(),
Moviesm4u(),
Filesim(),
FileMoon(),
FileMoonSx(),
Vido(),
Linkbox(),
Acefile(),
SpeedoStream(),
SpeedoStream1(),
Zorofile(),
Embedgram(),
Mvidoo(),
Streamplay(),
Vidmoly(),
Vidmolyme(),
Voe(),
Tubeless(),
Moviehab(),
MoviehabNet(),
Jeniusplay(),
Gdriveplayerapi(),
Gdriveplayerapp(),
Gdriveplayerfun(),
Gdriveplayerio(),
Gdriveplayerme(),
Gdriveplayerbiz(),
Gdriveplayerorg(),
Gdriveplayerus(),
Gdriveplayerco(),
Gdriveplayer(),
DatabaseGdrive(),
DatabaseGdrive2(),
YoutubeExtractor(),
YoutubeShortLinkExtractor(),
YoutubeMobileExtractor(),
YoutubeNoCookieExtractor(),
Streamlare(),
VidSrcExtractor(),
VidSrcExtractor2(),
PlayLtXyz(),
AStreamHub(),
Cda(),
Dailymotion(),
ByteShare(),
Ztreamhub()
)
fun getExtractorApiFromName(name: String): ExtractorApi {
for (api in extractorApis) {
if (api.name == name) return api
}
return extractorApis[0]
}
fun requireReferer(name: String): Boolean {
return getExtractorApiFromName(name).requiresReferer
}
fun httpsify(url: String): String {
return if (url.startsWith("//")) "https:$url" else url
}
suspend fun getPostForm(requestUrl: String, html: String): String? {
val document = Jsoup.parse(html)
val inputs = document.select("Form > input")
if (inputs.size < 4) return null
var op: String? = null
var id: String? = null
var mode: String? = null
var hash: String? = null
for (input in inputs) {
val value = input.attr("value") ?: continue
when (input.attr("name")) {
"op" -> op = value
"id" -> id = value
"mode" -> mode = value
"hash" -> hash = value
else -> Unit
}
}
if (op == null || id == null || mode == null || hash == null) {
return null
}
delay(5000) // ye this is needed, wont work with 0 delay
return app.post(
requestUrl,
headers = mapOf(
"content-type" to "application/x-www-form-urlencoded",
"referer" to requestUrl,
"user-agent" to USER_AGENT,
"accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
),
data = mapOf("op" to op, "id" to id, "mode" to mode, "hash" to hash)
).text
}
fun ExtractorApi.fixUrl(url: String): String {
if (url.startsWith("http") ||
// Do not fix JSON objects when passed as urls.
url.startsWith("{\"")
) {
return url
}
if (url.isEmpty()) {
return ""
}
val startsWithNoHttp = url.startsWith("//")
if (startsWithNoHttp) {
return "https:$url"
} else {
if (url.startsWith('/')) {
return mainUrl + url
}
return "$mainUrl/$url"
}
}
abstract class ExtractorApi {
abstract val name: String
abstract val mainUrl: String
abstract val requiresReferer: Boolean
/** Determines which plugin a given extractor is from */
var sourcePlugin: String? = null
//suspend fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? {
// return suspendSafeApiCall { getUrl(url, referer) }
//}
// this is the new extractorapi, override to add subtitles and stuff
open suspend fun getUrl(
url: String,
referer: String? = null,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
getUrl(url, referer)?.forEach(callback)
}
suspend fun getSafeUrl(
url: String,
referer: String? = null,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
try {
getUrl(url, referer, subtitleCallback, callback)
} catch (e: Exception) {
logError(e)
}
}
/**
* Will throw errors, use getSafeUrl if you don't want to handle the exception yourself
*/
open suspend fun getUrl(url: String, referer: String? = null): List<ExtractorLink>? {
return emptyList()
}
open fun getExtractorUrl(id: String): String {
return id
}
}