2021-05-19 18:44:02 +00:00
|
|
|
package com.lagradost.cloudstream3.utils
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
import android.net.Uri
|
|
|
|
import com.lagradost.cloudstream3.TvType
|
2021-09-30 15:51:34 +00:00
|
|
|
import com.lagradost.cloudstream3.USER_AGENT
|
2021-12-05 16:22:30 +00:00
|
|
|
import com.lagradost.cloudstream3.app
|
2021-07-30 01:14:53 +00:00
|
|
|
import com.lagradost.cloudstream3.extractors.*
|
2022-07-26 02:31:58 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.logError
|
2022-01-29 21:25:12 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
|
|
|
import kotlinx.coroutines.delay
|
2021-09-30 15:51:34 +00:00
|
|
|
import org.jsoup.Jsoup
|
2021-05-19 18:44:02 +00:00
|
|
|
|
2022-07-10 02:08:11 +00:00
|
|
|
/**
|
|
|
|
* 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,
|
2022-07-16 19:35:58 +00:00
|
|
|
) : ExtractorLink(
|
2022-07-10 02:08:11 +00:00
|
|
|
source,
|
|
|
|
name,
|
|
|
|
// Blank as un-used
|
|
|
|
"",
|
|
|
|
referer,
|
|
|
|
quality,
|
|
|
|
isM3u8,
|
|
|
|
headers,
|
|
|
|
extractorData
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
open class ExtractorLink(
|
|
|
|
open val source: String,
|
|
|
|
open val name: String,
|
2021-08-22 17:14:48 +00:00
|
|
|
override val url: String,
|
|
|
|
override val referer: String,
|
2022-07-10 02:08:11 +00:00
|
|
|
open val quality: Int,
|
|
|
|
open val isM3u8: Boolean = false,
|
2022-02-11 09:17:04 +00:00
|
|
|
override val headers: Map<String, String> = mapOf(),
|
|
|
|
/** Used for getExtractorVerifierJob() */
|
2022-07-10 02:08:11 +00:00
|
|
|
open val extractorData: String? = null,
|
2022-07-11 20:42:48 +00:00
|
|
|
) : VideoDownloadManager.IDownloadableMinimum {
|
|
|
|
override fun toString(): String {
|
|
|
|
return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)"
|
|
|
|
}
|
|
|
|
}
|
2021-05-19 18:44:02 +00:00
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
data class ExtractorUri(
|
2022-04-18 18:49:17 +00:00
|
|
|
val uri: Uri,
|
|
|
|
val name: String,
|
2022-01-07 19:27:25 +00:00
|
|
|
|
|
|
|
val basePath: String? = null,
|
|
|
|
val relativePath: String? = null,
|
|
|
|
val displayName: String? = null,
|
|
|
|
|
2022-04-18 18:49:17 +00:00
|
|
|
val id: Int? = null,
|
|
|
|
val parentId: Int? = null,
|
|
|
|
val episode: Int? = null,
|
|
|
|
val season: Int? = null,
|
|
|
|
val headerName: String? = null,
|
2022-01-07 19:27:25 +00:00
|
|
|
val tvType: TvType? = null,
|
|
|
|
)
|
|
|
|
|
2021-08-22 17:14:48 +00:00
|
|
|
data class ExtractorSubtitleLink(
|
|
|
|
val name: String,
|
|
|
|
override val url: String,
|
|
|
|
override val referer: String,
|
2021-09-11 18:44:37 +00:00
|
|
|
override val headers: Map<String, String> = mapOf()
|
2021-08-22 17:14:48 +00:00
|
|
|
) : VideoDownloadManager.IDownloadableMinimum
|
2021-05-28 13:38:06 +00:00
|
|
|
|
2022-07-16 19:35:58 +00:00
|
|
|
/**
|
|
|
|
* Removes https:// and www.
|
|
|
|
* To match urls regardless of schema, perhaps Uri() can be used?
|
|
|
|
*/
|
|
|
|
val schemaStripRegex = Regex("""^(https:|)//(www\.|)""")
|
|
|
|
|
2021-05-19 18:44:02 +00:00
|
|
|
enum class Qualities(var value: Int) {
|
2022-04-18 18:49:17 +00:00
|
|
|
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
|
2022-04-15 13:06:43 +00:00
|
|
|
|
|
|
|
companion object {
|
2022-04-18 18:49:17 +00:00
|
|
|
fun getStringByInt(qual: Int?): String {
|
2022-04-15 13:06:43 +00:00
|
|
|
return when (qual) {
|
2022-04-18 22:00:49 +00:00
|
|
|
0 -> "Auto"
|
2022-04-18 18:49:17 +00:00
|
|
|
Unknown.value -> ""
|
|
|
|
P2160.value -> "4K"
|
2022-05-30 12:13:11 +00:00
|
|
|
null -> ""
|
2022-04-18 18:49:17 +00:00
|
|
|
else -> "${qual}p"
|
2022-04-15 13:06:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-19 18:44:02 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 13:06:43 +00:00
|
|
|
fun getQualityFromName(qualityName: String?): Int {
|
2022-04-18 18:49:17 +00:00
|
|
|
if (qualityName == null)
|
2022-04-15 13:06:43 +00:00
|
|
|
return Qualities.Unknown.value
|
2022-04-18 18:49:17 +00:00
|
|
|
|
|
|
|
val match = qualityName.lowercase().replace("p", "").trim()
|
|
|
|
return when (match) {
|
2021-08-24 21:06:40 +00:00
|
|
|
"4k" -> Qualities.P2160
|
2022-04-18 18:49:17 +00:00
|
|
|
else -> null
|
|
|
|
}?.value ?: match.toIntOrNull() ?: Qualities.Unknown.value
|
2021-06-16 22:31:41 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 18:44:02 +00:00
|
|
|
private val packedRegex = Regex("""eval\(function\(p,a,c,k,e,.*\)\)""")
|
|
|
|
fun getPacked(string: String): String? {
|
|
|
|
return packedRegex.find(string)?.value
|
|
|
|
}
|
|
|
|
|
2021-09-25 10:07:40 +00:00
|
|
|
fun getAndUnpack(string: String): String {
|
2021-05-19 18:44:02 +00:00
|
|
|
val packedText = getPacked(string)
|
2021-09-25 10:07:40 +00:00
|
|
|
return JsUnpacker(packedText).unpack() ?: string
|
2021-05-19 18:44:02 +00:00
|
|
|
}
|
|
|
|
|
2022-07-26 02:31:58 +00:00
|
|
|
suspend fun unshortenLinkSafe(url : String) : String {
|
|
|
|
return try {
|
|
|
|
if (ShortLink.isShortLink(url))
|
|
|
|
ShortLink.unshorten(url)
|
|
|
|
else url
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
url
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-12 01:49:41 +00:00
|
|
|
/**
|
|
|
|
* Tries to load the appropriate extractor based on link, returns true if any extractor is loaded.
|
|
|
|
* */
|
2022-04-18 18:49:17 +00:00
|
|
|
suspend fun loadExtractor(
|
|
|
|
url: String,
|
|
|
|
referer: String? = null,
|
|
|
|
callback: (ExtractorLink) -> Unit
|
|
|
|
): Boolean {
|
2022-07-26 02:31:58 +00:00
|
|
|
val currentUrl = unshortenLinkSafe(url)
|
|
|
|
|
2021-08-25 22:16:20 +00:00
|
|
|
for (extractor in extractorApis) {
|
2022-07-26 02:31:58 +00:00
|
|
|
if (currentUrl.replace(schemaStripRegex, "")
|
2022-07-16 19:35:58 +00:00
|
|
|
.startsWith(extractor.mainUrl.replace(schemaStripRegex, ""))
|
|
|
|
) {
|
2022-07-26 02:31:58 +00:00
|
|
|
extractor.getSafeUrl(currentUrl, referer)?.forEach(callback)
|
2021-12-12 01:49:41 +00:00
|
|
|
return true
|
2021-08-25 22:16:20 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-26 02:31:58 +00:00
|
|
|
|
2021-12-12 01:49:41 +00:00
|
|
|
return false
|
2021-08-25 22:16:20 +00:00
|
|
|
}
|
|
|
|
|
2022-06-17 17:25:41 +00:00
|
|
|
suspend fun loadExtractor(
|
|
|
|
url: String,
|
|
|
|
referer: String? = null,
|
|
|
|
): List<ExtractorLink> {
|
2022-07-26 02:31:58 +00:00
|
|
|
val currentUrl = unshortenLinkSafe(url)
|
|
|
|
|
2022-06-17 17:25:41 +00:00
|
|
|
for (extractor in extractorApis) {
|
2022-07-26 02:31:58 +00:00
|
|
|
if (currentUrl.startsWith(extractor.mainUrl)) {
|
|
|
|
return extractor.getSafeUrl(currentUrl, referer) ?: emptyList()
|
2022-06-17 17:25:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return emptyList()
|
|
|
|
}
|
|
|
|
|
2021-05-20 15:22:28 +00:00
|
|
|
val extractorApis: Array<ExtractorApi> = arrayOf(
|
2021-05-19 18:44:02 +00:00
|
|
|
//AllProvider(),
|
2021-07-23 11:59:41 +00:00
|
|
|
WcoStream(),
|
2022-02-26 23:56:20 +00:00
|
|
|
Vidstreamz(),
|
2022-02-27 18:29:27 +00:00
|
|
|
Vizcloud(),
|
2022-03-05 12:19:50 +00:00
|
|
|
Vizcloud2(),
|
2022-03-21 18:19:29 +00:00
|
|
|
VizcloudOnline(),
|
2022-03-22 09:57:08 +00:00
|
|
|
VizcloudXyz(),
|
2022-04-07 15:54:20 +00:00
|
|
|
VizcloudLive(),
|
2022-04-10 23:06:17 +00:00
|
|
|
VizcloudInfo(),
|
2022-04-12 16:20:16 +00:00
|
|
|
MwvnVizcloudInfo(),
|
2022-04-18 17:52:40 +00:00
|
|
|
VizcloudDigital(),
|
2022-05-24 19:22:44 +00:00
|
|
|
VizcloudCloud(),
|
2022-05-30 16:39:26 +00:00
|
|
|
VideoVard(),
|
|
|
|
VideovardSX(),
|
2021-05-19 18:44:02 +00:00
|
|
|
Mp4Upload(),
|
|
|
|
StreamTape(),
|
2022-05-17 16:57:27 +00:00
|
|
|
|
|
|
|
//mixdrop extractors
|
|
|
|
MixDropBz(),
|
|
|
|
MixDropCh(),
|
2022-06-20 14:29:20 +00:00
|
|
|
MixDropTo(),
|
|
|
|
|
2021-05-19 18:44:02 +00:00
|
|
|
MixDrop(),
|
2022-05-17 16:57:27 +00:00
|
|
|
|
2022-02-10 09:27:33 +00:00
|
|
|
Mcloud(),
|
2021-07-23 16:41:37 +00:00
|
|
|
XStreamCdn(),
|
2022-05-17 16:57:27 +00:00
|
|
|
|
2021-07-23 16:41:37 +00:00
|
|
|
StreamSB(),
|
2022-02-20 16:25:44 +00:00
|
|
|
StreamSB1(),
|
|
|
|
StreamSB2(),
|
|
|
|
StreamSB3(),
|
|
|
|
StreamSB4(),
|
|
|
|
StreamSB5(),
|
|
|
|
StreamSB6(),
|
|
|
|
StreamSB7(),
|
|
|
|
StreamSB8(),
|
|
|
|
StreamSB9(),
|
2022-03-16 20:11:14 +00:00
|
|
|
StreamSB10(),
|
2022-05-23 10:55:13 +00:00
|
|
|
SBfull(),
|
2022-04-18 18:49:17 +00:00
|
|
|
// Streamhub(), cause Streamhub2() works
|
2022-03-12 13:02:30 +00:00
|
|
|
Streamhub2(),
|
2021-12-25 18:54:44 +00:00
|
|
|
|
2021-12-09 08:57:41 +00:00
|
|
|
FEmbed(),
|
2021-12-27 17:41:55 +00:00
|
|
|
FeHD(),
|
|
|
|
Fplayer(),
|
2022-05-23 10:55:13 +00:00
|
|
|
DBfilm(),
|
2022-07-19 20:48:48 +00:00
|
|
|
Luxubu(),
|
2022-05-23 10:55:13 +00:00
|
|
|
LayarKaca(),
|
2022-04-18 18:49:17 +00:00
|
|
|
// WatchSB(), 'cause StreamSB.kt works
|
2021-12-27 01:15:49 +00:00
|
|
|
Uqload(),
|
2022-01-16 17:43:44 +00:00
|
|
|
Uqload1(),
|
2021-12-27 01:15:49 +00:00
|
|
|
Evoload(),
|
2022-01-24 21:15:40 +00:00
|
|
|
Evoload1(),
|
2021-12-21 23:11:13 +00:00
|
|
|
VoeExtractor(),
|
2022-04-18 18:49:17 +00:00
|
|
|
// UpstreamExtractor(), GenericM3U8.kt works
|
2021-08-29 13:10:36 +00:00
|
|
|
|
2022-02-05 13:32:02 +00:00
|
|
|
Tomatomatela(),
|
|
|
|
Cinestart(),
|
|
|
|
OkRu(),
|
2022-05-17 16:57:27 +00:00
|
|
|
OkRuHttps(),
|
2022-02-05 13:32:02 +00:00
|
|
|
|
2021-08-29 13:10:36 +00:00
|
|
|
// dood extractors
|
2022-05-17 16:57:27 +00:00
|
|
|
DoodCxExtractor(),
|
|
|
|
DoodPmExtractor(),
|
2021-08-29 11:45:58 +00:00
|
|
|
DoodToExtractor(),
|
2021-08-29 13:10:36 +00:00
|
|
|
DoodSoExtractor(),
|
|
|
|
DoodLaExtractor(),
|
2021-12-27 17:41:55 +00:00
|
|
|
DoodWsExtractor(),
|
2022-06-02 09:04:29 +00:00
|
|
|
DoodShExtractor(),
|
2022-06-20 14:29:20 +00:00
|
|
|
DoodWatchExtractor(),
|
2021-10-03 20:23:06 +00:00
|
|
|
|
2021-12-25 18:54:44 +00:00
|
|
|
AsianLoad(),
|
|
|
|
|
2022-04-18 18:49:17 +00:00
|
|
|
// GenericM3U8(),
|
2022-02-20 16:25:44 +00:00
|
|
|
Jawcloud(),
|
2022-03-04 12:47:36 +00:00
|
|
|
Zplayer(),
|
|
|
|
ZplayerV2(),
|
|
|
|
Upstream(),
|
|
|
|
|
2022-06-02 09:04:29 +00:00
|
|
|
Maxstream(),
|
|
|
|
Tantifilm(),
|
|
|
|
Userload(),
|
|
|
|
Supervideo(),
|
2022-06-02 20:16:35 +00:00
|
|
|
GuardareStream(),
|
2022-02-20 16:25:44 +00:00
|
|
|
|
2022-04-18 18:49:17 +00:00
|
|
|
// StreamSB.kt works
|
|
|
|
// SBPlay(),
|
|
|
|
// SBPlay1(),
|
|
|
|
// SBPlay2(),
|
2022-02-20 16:25:44 +00:00
|
|
|
|
2022-03-13 06:44:02 +00:00
|
|
|
PlayerVoxzer(),
|
2022-05-17 16:57:27 +00:00
|
|
|
|
|
|
|
BullStream(),
|
2022-05-23 10:55:13 +00:00
|
|
|
GMPlayer(),
|
|
|
|
|
|
|
|
Blogger(),
|
2022-05-26 23:35:37 +00:00
|
|
|
Solidfiles(),
|
2022-07-06 18:15:48 +00:00
|
|
|
YourUpload(),
|
2022-05-23 10:55:13 +00:00
|
|
|
|
|
|
|
Hxfile(),
|
|
|
|
KotakAnimeid(),
|
|
|
|
Neonime8n(),
|
|
|
|
Neonime7n(),
|
2022-07-06 18:15:48 +00:00
|
|
|
Yufiles(),
|
|
|
|
Aico(),
|
|
|
|
|
|
|
|
JWPlayer(),
|
|
|
|
Meownime(),
|
|
|
|
DesuArcg(),
|
|
|
|
DesuOdchan(),
|
|
|
|
DesuOdvip(),
|
|
|
|
DesuDrive(),
|
|
|
|
|
|
|
|
Filesim(),
|
2022-07-16 15:10:11 +00:00
|
|
|
Linkbox(),
|
2022-07-26 01:43:25 +00:00
|
|
|
Acefile(),
|
|
|
|
SpeedoStream(),
|
2022-07-16 15:10:11 +00:00
|
|
|
|
2022-06-17 17:25:41 +00:00
|
|
|
YoutubeExtractor(),
|
2022-06-17 20:43:42 +00:00
|
|
|
YoutubeShortLinkExtractor(),
|
2022-07-17 20:08:40 +00:00
|
|
|
Streamlare()
|
2021-05-19 18:44:02 +00:00
|
|
|
)
|
|
|
|
|
2021-05-20 15:22:28 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-05-19 18:44:02 +00:00
|
|
|
fun httpsify(url: String): String {
|
|
|
|
return if (url.startsWith("//")) "https:$url" else url
|
|
|
|
}
|
|
|
|
|
2022-04-18 18:49:17 +00:00
|
|
|
suspend fun getPostForm(requestUrl: String, html: String): String? {
|
2021-09-30 15:51:34 +00:00
|
|
|
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
|
2022-01-07 19:27:25 +00:00
|
|
|
else -> Unit
|
2021-09-30 15:51:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (op == null || id == null || mode == null || hash == null) {
|
|
|
|
return null
|
|
|
|
}
|
2022-01-29 21:25:12 +00:00
|
|
|
delay(5000) // ye this is needed, wont work with 0 delay
|
2021-09-30 15:51:34 +00:00
|
|
|
|
2022-04-18 18:49:17 +00:00
|
|
|
return app.post(
|
2021-09-30 15:51:34 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-05-19 18:44:02 +00:00
|
|
|
abstract class ExtractorApi {
|
|
|
|
abstract val name: String
|
|
|
|
abstract val mainUrl: String
|
|
|
|
abstract val requiresReferer: Boolean
|
|
|
|
|
2022-01-29 21:25:12 +00:00
|
|
|
suspend fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? {
|
|
|
|
return suspendSafeApiCall { getUrl(url, referer) }
|
2021-07-23 16:41:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Will throw errors, use getSafeUrl if you don't want to handle the exception yourself
|
|
|
|
*/
|
2022-01-29 21:25:12 +00:00
|
|
|
abstract suspend fun getUrl(url: String, referer: String? = null): List<ExtractorLink>?
|
2021-05-19 18:44:02 +00:00
|
|
|
|
2021-05-20 15:22:28 +00:00
|
|
|
open fun getExtractorUrl(id: String): String {
|
2021-05-19 18:44:02 +00:00
|
|
|
return id
|
|
|
|
}
|
2021-07-23 11:59:41 +00:00
|
|
|
}
|