Merge pull request #119 from Blatzar/master

Added AsianLoadProvider.kt and made vidstream a template
This commit is contained in:
Osten 2021-10-03 22:34:55 +02:00 committed by GitHub
commit 54139ab99c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 401 additions and 288 deletions

View file

@ -43,7 +43,8 @@ object APIHolder {
AllMoviesForYouProvider(), AllMoviesForYouProvider(),
VidEmbedProvider(), VidEmbedProvider(),
VfFilmProvider() VfFilmProvider(),
AsianLoadProvider(),
) )
val restrictedApis = arrayListOf( val restrictedApis = arrayListOf(

View file

@ -0,0 +1,60 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.network.get
import com.lagradost.cloudstream3.network.text
import com.lagradost.cloudstream3.network.url
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 AsianLoad : ExtractorApi() {
override val name: String
get() = "AsianLoad"
override val mainUrl: String
get() = "https://asianload1.com"
override val requiresReferer: Boolean
get() = true
private val urlRegex = Regex("""(.*?)([^/]+$)""")
private val m3u8UrlRegex = Regex("""RESOLUTION=\d*x(\d*).*\n(.*\.m3u8)""")
private val sourceRegex = Regex("""sources:[\W\w]*?file:\s*?["'](.*?)["']""")
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
with(get(url, referer = referer)) {
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(get(extractedUrl, referer = this.url)) {
m3u8UrlRegex.findAll(this.text).forEach { match ->
extractedLinksList.add(
ExtractorLink(
name,
"$name ${match.groupValues[1]}p",
urlRegex.find(this.url)!!.groupValues[1] + match.groupValues[2],
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

@ -6,11 +6,13 @@ import com.lagradost.cloudstream3.network.url
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName
import java.net.URI
class MultiQuality : ExtractorApi() { class MultiQuality : ExtractorApi() {
override val name: String = "MultiQuality" override val name: String = "MultiQuality"
override val mainUrl: String = "https://gogo-play.net" override val mainUrl: String = "https://gogo-play.net"
private val sourceRegex = Regex("""file:\s*'(.*?)',label:\s*'(.*?)'""") private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""")
private val m3u8Regex = Regex(""".*?(\d*).m3u8""") private val m3u8Regex = Regex(""".*?(\d*).m3u8""")
private val urlRegex = Regex("""(.*?)([^/]+$)""") private val urlRegex = Regex("""(.*?)([^/]+$)""")
override val requiresReferer = false override val requiresReferer = false
@ -19,23 +21,13 @@ class MultiQuality : ExtractorApi() {
return "$mainUrl/loadserver.php?id=$id" return "$mainUrl/loadserver.php?id=$id"
} }
private fun getQuality(string: String): Int {
return when (string) {
"360" -> Qualities.P480.value
"480" -> Qualities.P480.value
"720" -> Qualities.P720.value
"1080" -> Qualities.P1080.value
else -> Qualities.Unknown.value
}
}
override fun getUrl(url: String, referer: String?): List<ExtractorLink> { override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
with(get(url)) { with(get(url)) {
sourceRegex.findAll(this.text).forEach { sourceMatch -> sourceRegex.findAll(this.text).forEach { sourceMatch ->
val extractedUrl = sourceMatch.groupValues[1] val extractedUrl = sourceMatch.groupValues[1]
// Trusting this isn't mp4, may fuck up stuff // Trusting this isn't mp4, may fuck up stuff
if (extractedUrl.endsWith(".m3u8")) { if (URI(extractedUrl).path.endsWith(".m3u8")) {
with(get(extractedUrl)) { with(get(extractedUrl)) {
m3u8Regex.findAll(this.text).forEach { match -> m3u8Regex.findAll(this.text).forEach { match ->
extractedLinksList.add( extractedLinksList.add(
@ -44,7 +36,7 @@ class MultiQuality : ExtractorApi() {
"$name ${match.groupValues[1]}p", "$name ${match.groupValues[1]}p",
urlRegex.find(this.url)!!.groupValues[1] + match.groupValues[0], urlRegex.find(this.url)!!.groupValues[1] + match.groupValues[0],
url, url,
getQuality(match.groupValues[1]), getQualityFromName(match.groupValues[1]),
isM3u8 = true isM3u8 = true
) )
) )

View file

@ -6,6 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.JsUnpacker import com.lagradost.cloudstream3.utils.JsUnpacker
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import java.net.URI
class Streamhub : ExtractorApi() { class Streamhub : ExtractorApi() {
override val mainUrl: String override val mainUrl: String
@ -31,7 +32,7 @@ class Streamhub : ExtractorApi() {
link, link,
referer ?: "", referer ?: "",
Qualities.Unknown.value, Qualities.Unknown.value,
link.endsWith(".m3u8") URI(link).path.endsWith(".m3u8")
) )
) )
} }

View file

@ -14,9 +14,8 @@ import org.jsoup.Jsoup
* overrideMainUrl is necessary for for other vidstream clones like vidembed.cc * overrideMainUrl is necessary for for other vidstream clones like vidembed.cc
* If they diverge it'd be better to make them separate. * If they diverge it'd be better to make them separate.
* */ * */
class Vidstream(overrideMainUrl: String? = null) { class Vidstream(val mainUrl: String) {
val name: String = "Vidstream" val name: String = "Vidstream"
private val mainUrl: String = overrideMainUrl ?: "https://gogo-stream.com"
private fun getExtractorUrl(id: String): String { private fun getExtractorUrl(id: String): String {
return "$mainUrl/streaming.php?id=$id" return "$mainUrl/streaming.php?id=$id"
@ -41,7 +40,9 @@ class Vidstream(overrideMainUrl: String? = null) {
/** Stolen from GogoanimeProvider.kt extractor */ /** Stolen from GogoanimeProvider.kt extractor */
normalSafeApiCall { normalSafeApiCall {
val link = getDownloadUrl(id) val link = getDownloadUrl(id)
val page = get(link, headers = mapOf("Referer" to extractorUrl)) println("Generated vidstream download link: $link")
val page = get(link, referer = extractorUrl)
val pageDoc = Jsoup.parse(page.text) val pageDoc = Jsoup.parse(page.text)
val qualityRegex = Regex("(\\d+)P") val qualityRegex = Regex("(\\d+)P")

View file

@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.network.text
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import java.net.URI
class AsiaFlixProvider : MainAPI() { class AsiaFlixProvider : MainAPI() {
override val mainUrl: String override val mainUrl: String
@ -162,7 +163,7 @@ class AsiaFlixProvider : MainAPI() {
it, it,
"https://asianload1.com/", /** <------ This provider should be added instead */ "https://asianload1.com/", /** <------ This provider should be added instead */
getQualityFromName(it), getQualityFromName(it),
it.endsWith(".m3u8") URI(it).path.endsWith(".m3u8")
) )
) )
} }

View file

@ -0,0 +1,26 @@
package com.lagradost.cloudstream3.movieproviders
import com.lagradost.cloudstream3.TvType
/** Needs to inherit from MainAPI() to
* make the app know what functions to call
*/
class AsianLoadProvider : VidstreamProviderTemplate() {
override val name: String
get() = "AsianLoad"
override val mainUrl: String
get() = "https://asianload.cc"
override val homePageUrlList = listOf(
mainUrl,
"$mainUrl/recently-added-raw",
"$mainUrl/movies",
"$mainUrl/kshow",
"$mainUrl/popular",
"$mainUrl/ongoing-series"
)
override val supportedTypes: Set<TvType>
get() = setOf(TvType.TvSeries, TvType.Movie)
}

View file

@ -13,7 +13,7 @@ import kotlin.collections.ArrayList
/** Needs to inherit from MainAPI() to /** Needs to inherit from MainAPI() to
* make the app know what functions to call * make the app know what functions to call
*/ */
class VidEmbedProvider : MainAPI() { class VidEmbedProvider : VidstreamProviderTemplate() {
// mainUrl is good to have as a holder for the url to make future changes easier. // mainUrl is good to have as a holder for the url to make future changes easier.
override val mainUrl: String override val mainUrl: String
get() = "https://vidembed.cc" get() = "https://vidembed.cc"
@ -22,274 +22,16 @@ class VidEmbedProvider : MainAPI() {
override val name: String override val name: String
get() = "VidEmbed" get() = "VidEmbed"
// hasQuickSearch defines if quickSearch() should be called, this is only when typing the searchbar override val homePageUrlList: List<String> = listOf(
// gives results on the site instead of bringing you to another page. mainUrl,
// if hasQuickSearch is true and quickSearch() hasn't been overridden you will get errors. "$mainUrl/movies",
// VidEmbed actually has quick search on their site, but the function wasn't implemented. "$mainUrl/series",
override val hasQuickSearch: Boolean "$mainUrl/recommended-series",
get() = false "$mainUrl/cinema-movies"
)
// If getMainPage() is functional, used to display the homepage in app, an optional, but highly encouraged endevour.
override val hasMainPage: Boolean
get() = true
// Sometimes on sites the urls can be something like "/movie.html" which translates to "*full site url*/movie.html" in the browser
private fun fixUrl(url: String): String {
return if (url.startsWith("//")) {
"https:$url"
} else if (url.startsWith("/")) {
"$mainUrl$url"
} else {
url
}
}
// This is just extra metadata about what type of movies the provider has. // This is just extra metadata about what type of movies the provider has.
// Needed for search functionality. // Needed for search functionality.
override val supportedTypes: Set<TvType> override val supportedTypes: Set<TvType>
get() = setOf(TvType.Anime, TvType.AnimeMovie, TvType.TvSeries, TvType.Movie) get() = setOf(TvType.TvSeries, TvType.Movie)
// 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 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 = 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 fun load(url: String): LoadResponse? {
// Gets the url returned from searching.
val html = 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
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()
TvSeriesEpisode(
epTitle,
null,
epNum,
fixUrl(li.selectFirst("a").attr("href")),
epThumb,
epDate
)
}.reversed()
val year = episodes.first().date?.split("-")?.get(0)?.toIntOrNull()
// 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 fun getMainPage(): HomePageResponse {
val urls = listOf(
mainUrl,
"$mainUrl/movies",
"$mainUrl/series",
"$mainUrl/recommended-series",
"$mainUrl/cinema-movies"
)
val homePageList = ArrayList<HomePageList>()
// .pmap {} is used to fetch the different pages in parallel
urls.pmap { url ->
val response = get(url, timeout = 20).text
val document = Jsoup.parse(response)
document.select("div.main-inner")?.forEach {
// Always trim your text unless you want the risk of spaces at the start or end.
val title = it.select(".widget-title").text().trim()
val elements = it.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()
val isSeries = (name.contains("Season") || name.contains("Episode"))
if (isSeries) {
TvSeriesSearchResponse(
name,
link,
this.name,
TvType.TvSeries,
image,
null,
null,
)
} else {
MovieSearchResponse(
name,
link,
this.name,
TvType.Movie,
image,
null,
null,
)
}
}
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 TvSeriesEpisode(...) in this provider.
// The data are usually links, but can be any other string to help aid loading the links.
override 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(get(data).text).selectFirst("iframe")?.attr("src") ?: return false
// 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("https://vidembed.cc")
// https://vidembed.cc/streaming.php?id=MzUwNTY2&... -> MzUwNTY2
val id = Regex("""id=([^&]*)""").find(iframeLink)?.groupValues?.get(1)
if (id != null) {
vidstreamObject.getUrl(id, isCasting, callback)
}
val html = 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.forEach {
// When checking strings make sure to make them lowercase and trimmed because edgecases like "beta server " wouldn't work otherwise.
if (it.first.toLowerCase(Locale.ROOT).trim() == "beta server") {
// 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 = 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.
match.groupValues[1].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,286 @@
package com.lagradost.cloudstream3.movieproviders
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.extractors.Vidstream
import com.lagradost.cloudstream3.network.get
import com.lagradost.cloudstream3.network.text
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
// // 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: Boolean
get() = false
// If getMainPage() is functional, used to display the homepage in app, an optional, but highly encouraged endevour.
override val hasMainPage: Boolean
get() = true
// Sometimes on sites the urls can be something like "/movie.html" which translates to "*full site url*/movie.html" in the browser
private fun fixUrl(url: String): String {
return if (url.startsWith("//")) {
"https:$url"
} else if (url.startsWith("/")) {
"$mainUrl$url"
} else {
url
}
}
// 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 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 = 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 fun load(url: String): LoadResponse? {
// Gets the url returned from searching.
val html = 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
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()
TvSeriesEpisode(
epTitle,
null,
epNum,
fixUrl(li.selectFirst("a").attr("href")),
epThumb,
epDate
)
}.reversed()
val year = episodes.first().date?.split("-")?.get(0)?.toIntOrNull()
// 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 fun getMainPage(): HomePageResponse {
val urls = homePageUrlList
val homePageList = ArrayList<HomePageList>()
// .pmap {} is used to fetch the different pages in parallel
urls.pmap { url ->
val response = get(url, timeout = 20).text
val document = Jsoup.parse(response)
document.select("div.main-inner")?.forEach {
// Always trim your text unless you want the risk of spaces at the start or end.
val title = it.select(".widget-title").text().trim()
val elements = it.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) {
TvSeriesSearchResponse(
name,
link,
this.name,
TvType.TvSeries,
image,
null,
null,
)
} else {
MovieSearchResponse(
name,
link,
this.name,
TvType.Movie,
image,
null,
null,
)
}
}
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 TvSeriesEpisode(...) in this provider.
// The data are usually links, but can be any other string to help aid loading the links.
override 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(get(data).text).selectFirst("iframe")?.attr("src") ?: return false
// 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, callback)
}
val html = 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.forEach {
// 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 = 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

@ -407,7 +407,7 @@ class HomeFragment : Fragment() {
reloadStored() reloadStored()
val apiName = context?.getKey<String>(HOMEPAGE_API) val apiName = context?.getKey<String>(HOMEPAGE_API)
if(homeViewModel.apiName.value != apiName) { if(homeViewModel.apiName.value != apiName) {
println("COUGHT HOME : " + homeViewModel.apiName.value + " AT " + apiName) println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
homeViewModel.loadAndCancel(apiName) homeViewModel.loadAndCancel(apiName)
} }
} }

View file

@ -82,6 +82,8 @@ val extractorApis: Array<ExtractorApi> = arrayOf(
DoodToExtractor(), DoodToExtractor(),
DoodSoExtractor(), DoodSoExtractor(),
DoodLaExtractor(), DoodLaExtractor(),
AsianLoad()
) )
fun getExtractorApiFromName(name: String): ExtractorApi { fun getExtractorApiFromName(name: String): ExtractorApi {

View file

@ -36,6 +36,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.* import java.io.*
import java.lang.Thread.sleep import java.lang.Thread.sleep
import java.net.URI
import java.net.URL import java.net.URL
import java.net.URLConnection import java.net.URLConnection
import java.util.* import java.util.*
@ -1110,7 +1111,7 @@ object VideoDownloadManager {
): Int { ): Int {
val name = sanitizeFilename(ep.name ?: "${context.getString(R.string.episode)} ${ep.episode}") val name = sanitizeFilename(ep.name ?: "${context.getString(R.string.episode)} ${ep.episode}")
if (link.isM3u8 || link.url.endsWith(".m3u8")) { if (link.isM3u8 || URI(link.url).path.endsWith(".m3u8")) {
val startIndex = if (tryResume) { val startIndex = if (tryResume) {
context.getKey<DownloadedFileInfo>( context.getKey<DownloadedFileInfo>(
KEY_DOWNLOAD_INFO, KEY_DOWNLOAD_INFO,