mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
TrailersToProvider
This commit is contained in:
parent
cea1839b5c
commit
4cfb7c38c0
6 changed files with 255 additions and 46 deletions
|
@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.animeproviders.ShiroProvider
|
|||
import com.lagradost.cloudstream3.movieproviders.HDMProvider
|
||||
import com.lagradost.cloudstream3.movieproviders.LookMovieProvider
|
||||
import com.lagradost.cloudstream3.movieproviders.MeloMovieProvider
|
||||
import com.lagradost.cloudstream3.movieproviders.TrailersToProvider
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
@ -33,6 +34,7 @@ object APIHolder {
|
|||
DubbedAnimeProvider(),
|
||||
HDMProvider(),
|
||||
LookMovieProvider(),
|
||||
TrailersToProvider(),
|
||||
)
|
||||
|
||||
fun getApiFromName(apiName: String?): MainAPI {
|
||||
|
@ -61,7 +63,7 @@ abstract class MainAPI {
|
|||
return null
|
||||
}
|
||||
|
||||
open fun quickSearch(query: String) : ArrayList<SearchResponse>? {
|
||||
open fun quickSearch(query: String): ArrayList<SearchResponse>? {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -75,23 +77,22 @@ abstract class MainAPI {
|
|||
}
|
||||
}
|
||||
|
||||
fun parseRating(ratingString : String?) : Int? {
|
||||
if(ratingString == null) return null
|
||||
fun parseRating(ratingString: String?): Int? {
|
||||
if (ratingString == null) return null
|
||||
val floatRating = ratingString.toFloatOrNull() ?: return null
|
||||
return (floatRating * 10).toInt()
|
||||
}
|
||||
|
||||
fun MainAPI.fixUrl(url: String): String {
|
||||
if(url.startsWith("http")) {
|
||||
if (url.startsWith("http")) {
|
||||
return url
|
||||
}
|
||||
|
||||
val startsWithNoHttp = url.startsWith("//")
|
||||
if(startsWithNoHttp) {
|
||||
if (startsWithNoHttp) {
|
||||
return "https:$url"
|
||||
}
|
||||
else {
|
||||
if(url.startsWith('/')) {
|
||||
} else {
|
||||
if (url.startsWith('/')) {
|
||||
return mainUrl + url
|
||||
}
|
||||
return "$mainUrl/$url"
|
||||
|
@ -176,7 +177,10 @@ interface LoadResponse {
|
|||
val posterUrl: String?
|
||||
val year: Int?
|
||||
val plot: String?
|
||||
val rating : Int? // 0-100
|
||||
val rating: Int? // 0-100
|
||||
val tags: ArrayList<String>?
|
||||
val duration: String?
|
||||
val trailerUrl: String?
|
||||
}
|
||||
|
||||
fun LoadResponse?.isEpisodeBased(): Boolean {
|
||||
|
@ -189,7 +193,7 @@ fun LoadResponse?.isAnimeBased(): Boolean {
|
|||
return (this.type == TvType.Anime || this.type == TvType.ONA) // && (this is AnimeLoadResponse)
|
||||
}
|
||||
|
||||
data class AnimeEpisode(val url: String, val name : String? = null)
|
||||
data class AnimeEpisode(val url: String, val name: String? = null)
|
||||
|
||||
data class AnimeLoadResponse(
|
||||
val engName: String?,
|
||||
|
@ -207,12 +211,14 @@ data class AnimeLoadResponse(
|
|||
val showStatus: ShowStatus?,
|
||||
|
||||
override val plot: String?,
|
||||
val tags: ArrayList<String>? = null,
|
||||
override val tags: ArrayList<String>? = null,
|
||||
val synonyms: ArrayList<String>? = null,
|
||||
|
||||
val malId: Int? = null,
|
||||
val anilistId: Int? = null,
|
||||
override val rating: Int? = null,
|
||||
override val duration: String? = null,
|
||||
override val trailerUrl: String? = null,
|
||||
) : LoadResponse
|
||||
|
||||
data class MovieLoadResponse(
|
||||
|
@ -226,11 +232,23 @@ data class MovieLoadResponse(
|
|||
override val year: Int?,
|
||||
override val plot: String?,
|
||||
|
||||
val imdbId: Int?,
|
||||
val imdbUrl: String?,
|
||||
override val rating: Int? = null,
|
||||
override val tags: ArrayList<String>? = null,
|
||||
override val duration: String? = null,
|
||||
override val trailerUrl: String? = null,
|
||||
) : LoadResponse
|
||||
|
||||
data class TvSeriesEpisode(val name: String?, val season: Int?, val episode: Int?, val data: String)
|
||||
data class TvSeriesEpisode(
|
||||
val name: String?,
|
||||
val season: Int?,
|
||||
val episode: Int?,
|
||||
val data: String,
|
||||
val posterUrl: String? = null,
|
||||
val date: String? = null,
|
||||
val rating: Int? = null,
|
||||
val descript: String? = null,
|
||||
)
|
||||
|
||||
data class TvSeriesLoadResponse(
|
||||
override val name: String,
|
||||
|
@ -244,6 +262,9 @@ data class TvSeriesLoadResponse(
|
|||
override val plot: String?,
|
||||
|
||||
val showStatus: ShowStatus?,
|
||||
val imdbId: Int?,
|
||||
val imdbUrl: String?,
|
||||
override val rating: Int? = null,
|
||||
override val tags: ArrayList<String>? = null,
|
||||
override val duration: String? = null,
|
||||
override val trailerUrl: String? = null,
|
||||
) : LoadResponse
|
|
@ -6,9 +6,11 @@ import com.lagradost.cloudstream3.*
|
|||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.extractors.M3u8Manifest
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
//BE AWARE THAT weboas.is is a clone of lookmovie
|
||||
class LookMovieProvider : MainAPI() {
|
||||
override val hasQuickSearch: Boolean
|
||||
get() = true
|
||||
|
@ -62,7 +64,7 @@ class LookMovieProvider : MainAPI() {
|
|||
@JsonProperty("season") var season: String,
|
||||
)
|
||||
|
||||
override fun quickSearch(query: String): ArrayList<SearchResponse>? {
|
||||
override fun quickSearch(query: String): ArrayList<SearchResponse> {
|
||||
val movieUrl = "$mainUrl/api/v1/movies/search/?q=$query"
|
||||
val movieResponse = khttp.get(movieUrl)
|
||||
val movies = mapper.readValue<LookMovieSearchResultRoot>(movieResponse.text).result
|
||||
|
@ -138,12 +140,13 @@ class LookMovieProvider : MainAPI() {
|
|||
|
||||
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
val response = khttp.get(data.replace("\$unixtime", unixTime.toString()))
|
||||
|
||||
"\"(.*?)\":\"(.*?)\"".toRegex().findAll(response.text).forEach {
|
||||
var quality = it.groupValues[1].replace("auto", "Auto")
|
||||
if (quality != "Auto") quality += "p"
|
||||
val url = it.groupValues[2]
|
||||
callback.invoke(ExtractorLink(this.name, "${this.name} - $quality", url, "", getQualityFromName(quality),true))
|
||||
M3u8Manifest.extractLinks(response.text).forEach {
|
||||
callback.invoke(ExtractorLink(this.name,
|
||||
"${this.name} - ${it.second}",
|
||||
fixUrl(it.first),
|
||||
"",
|
||||
getQualityFromName(it.second),
|
||||
true))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -174,9 +177,6 @@ class LookMovieProvider : MainAPI() {
|
|||
val root = mapper.readValue<LookMovieTokenRoot>(tokenResponse.text)
|
||||
val accessToken = root.data?.accessToken ?: return null
|
||||
|
||||
//https://lookmovie.io/api/v1/security/show-access?slug=9140554-loki-2021&token=&sk=null&step=1
|
||||
//https://lookmovie.io/api/v1/security/movie-access?id_movie=11582&token=1&sk=&step=1
|
||||
|
||||
if (isMovie) {
|
||||
return MovieLoadResponse(name,
|
||||
slug,
|
||||
|
@ -198,10 +198,6 @@ class LookMovieProvider : MainAPI() {
|
|||
return this.replace("$replace:", "\"$replace\":")
|
||||
}
|
||||
|
||||
//https://lookmovie.io/api/v1/security/show-access?slug=9140554-loki-2021&token=&sk=null&step=1
|
||||
//https://lookmovie.io/manifests/shows/json/TGv3dO0pcwomftMrywOnmw/1624571222/128848/master.m3u8
|
||||
//https://lookmovie.io/api/v1/shows/episode-subtitles/?id_episode=128848
|
||||
|
||||
val json = season
|
||||
.replace("\'", "\"")
|
||||
.fixSeasonJson("title")
|
||||
|
@ -230,6 +226,5 @@ class LookMovieProvider : MainAPI() {
|
|||
null,
|
||||
rating)
|
||||
}
|
||||
//watch-heading
|
||||
}
|
||||
}
|
|
@ -109,7 +109,7 @@ class MeloMovieProvider : MainAPI() {
|
|||
return src.toRegex().find(response)?.groups?.get(1)?.value ?: return null
|
||||
}
|
||||
|
||||
val imdbId = findUsingRegex("var imdb = \"(tt[0-9]*)\"")?.toIntOrNull()
|
||||
val imdbUrl = findUsingRegex("var imdb = \"(.*?)\"")
|
||||
val document = Jsoup.parse(response)
|
||||
val poster = document.selectFirst("img.img-fluid").attr("src")
|
||||
val type = findUsingRegex("var posttype = ([0-9]*)")?.toInt() ?: return null
|
||||
|
@ -128,7 +128,7 @@ class MeloMovieProvider : MainAPI() {
|
|||
poster,
|
||||
year,
|
||||
plot,
|
||||
imdbId)
|
||||
imdbUrl)
|
||||
} else if (type == 2) {
|
||||
val episodes = ArrayList<TvSeriesEpisode>()
|
||||
val seasons = document.select("div.accordion__card")
|
||||
|
@ -154,7 +154,7 @@ class MeloMovieProvider : MainAPI() {
|
|||
year,
|
||||
plot,
|
||||
null,
|
||||
imdbId)
|
||||
imdbUrl)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
// referer = https://trailers.to, USERAGENT ALSO REQUIRED
|
||||
class TrailersToProvider : MainAPI() {
|
||||
override val mainUrl: String
|
||||
get() = "https://trailers.to"
|
||||
override val name: String
|
||||
get() = "Trailers.to"
|
||||
|
||||
override val hasQuickSearch: Boolean
|
||||
get() = true
|
||||
|
||||
override fun quickSearch(query: String): ArrayList<SearchResponse> {
|
||||
val url = "https://trailers.to/en/quick-search?q=$query"
|
||||
val response = khttp.get(url)
|
||||
val document = Jsoup.parse(response.text)
|
||||
val items = document.select("div.group-post-minimal > a.post-minimal")
|
||||
if (items.isNullOrEmpty()) return ArrayList()
|
||||
|
||||
val returnValue = ArrayList<SearchResponse>()
|
||||
for (item in items) {
|
||||
val href = fixUrl(item.attr("href"))
|
||||
val poster = item.selectFirst("> div.post-minimal-media > img").attr("src")
|
||||
val header = item.selectFirst("> div.post-minimal-main")
|
||||
val name = header.selectFirst("> span.link-black").text()
|
||||
val info = header.select("> p")
|
||||
val year = info?.get(1)?.text()?.toIntOrNull()
|
||||
val isTvShow = href.contains("/tvshow/")
|
||||
|
||||
returnValue.add(
|
||||
if (isTvShow) {
|
||||
TvSeriesSearchResponse(name, href, href, this.name, TvType.TvSeries, poster, year, null)
|
||||
} else {
|
||||
MovieSearchResponse(name, href, href, this.name, TvType.Movie, poster, year)
|
||||
}
|
||||
)
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
|
||||
override fun search(query: String): ArrayList<SearchResponse> {
|
||||
val url = "https://trailers.to/en/popular/movies-tvshows-collections?q=$query"
|
||||
val response = khttp.get(url)
|
||||
val document = Jsoup.parse(response.text)
|
||||
val items = document.select("div.col-lg-8 > article.list-item")
|
||||
if (items.isNullOrEmpty()) return ArrayList()
|
||||
val returnValue = ArrayList<SearchResponse>()
|
||||
for (item in items) {
|
||||
val poster = item.selectFirst("> div.tour-modern-media > a.tour-modern-figure > img").attr("src")
|
||||
val infoDiv = item.selectFirst("> div.tour-modern-main")
|
||||
val nameHeader = infoDiv.select("> h5.tour-modern-title > a").last()
|
||||
val name = nameHeader.text()
|
||||
val href = fixUrl(nameHeader.attr("href"))
|
||||
val year = infoDiv.selectFirst("> div > span.small-text")?.text()?.takeLast(4)?.toIntOrNull()
|
||||
val isTvShow = href.contains("/tvshow/")
|
||||
|
||||
returnValue.add(
|
||||
if (isTvShow) {
|
||||
TvSeriesSearchResponse(name, href, href, this.name, TvType.TvSeries, poster, year, null)
|
||||
} else {
|
||||
MovieSearchResponse(name, href, href, this.name, TvType.Movie, poster, year)
|
||||
}
|
||||
)
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
|
||||
private fun loadLink(data: String, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
val response = khttp.get(data)
|
||||
val url = "<source src='(.*?)'".toRegex().find(response.text)?.groupValues?.get(1)
|
||||
if (url != null) {
|
||||
callback.invoke(ExtractorLink(this.name, this.name, url, mainUrl, Qualities.Unknown.value, false))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
if (isCasting) return false
|
||||
val isMovie = data.contains("/web-sources/")
|
||||
if (isMovie) {
|
||||
return loadLink(data, callback)
|
||||
} else if (data.contains("/episode/")) {
|
||||
val response = khttp.get(data)
|
||||
val document = Jsoup.parse(response.text)
|
||||
val subData = fixUrl(document.selectFirst("content").attr("data-url") ?: return false)
|
||||
if (subData.contains("/web-sources/")) {
|
||||
return loadLink(subData, callback)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun load(slug: String): LoadResponse? {
|
||||
val response = khttp.get(slug)
|
||||
val document = Jsoup.parse(response.text)
|
||||
val metaInfo = document.select("div.post-info-meta > ul.post-info-meta-list > li")
|
||||
val year = metaInfo?.get(0)?.selectFirst("> span.small-text")?.text()?.takeLast(4)?.toIntOrNull()
|
||||
val rating = parseRating(metaInfo?.get(1)?.selectFirst("> span.small-text")?.text()?.replace("/ 10", ""))
|
||||
val duration = metaInfo?.get(2)?.selectFirst("> span.small-text")?.text()
|
||||
val imdbUrl = metaInfo?.get(3)?.selectFirst("> a")?.attr("href")
|
||||
val trailer = metaInfo?.get(4)?.selectFirst("> a")?.attr("href")
|
||||
val poster = document.selectFirst("div.slider-image > a > img").attr("src")
|
||||
val descriptHeader = document.selectFirst("article.post-info")
|
||||
var title = document.selectFirst("h2.breadcrumbs-custom-title > a").text()
|
||||
title = title.substring(0, title.length - 6) // REMOVE YEAR
|
||||
|
||||
val descript = descriptHeader.select("> div > p").text()
|
||||
val table = descriptHeader.select("> table.post-info-table > tbody > tr > td")
|
||||
var generes: List<String>? = null
|
||||
for (i in 0 until table.size / 2) {
|
||||
val header = table[i * 2].text()
|
||||
val info = table[i * 2 + 1]
|
||||
when (header) {
|
||||
"Genre" -> generes = info.text().split(",")
|
||||
}
|
||||
}
|
||||
val tags = if (generes == null) null else ArrayList(generes)
|
||||
|
||||
val isTvShow = slug.contains("/tvshow/")
|
||||
if (isTvShow) {
|
||||
val episodes = document.select("article.tour-modern") ?: return null
|
||||
val parsedEpisodes = episodes.map { item ->
|
||||
val epPoster = item.selectFirst("> div.tour-modern-media > a.tour-modern-figure > img").attr("src")
|
||||
val main = item.selectFirst("> div.tour-modern-main")
|
||||
val titleHeader = main.selectFirst("> h5.tour-modern-title > a")
|
||||
val titleName = titleHeader.text()
|
||||
val href = fixUrl(titleHeader.attr("href"))
|
||||
val gValues = ".*?Season ([0-9]*).*Episode ([0-9]*): (.*)".toRegex().find(titleName)?.groupValues
|
||||
val season = gValues?.get(1)?.toIntOrNull()
|
||||
val episode = gValues?.get(2)?.toIntOrNull()
|
||||
val epName = gValues?.get(3)
|
||||
val infoHeaders = main.select("> div > span.small-text")
|
||||
val date = infoHeaders?.get(0)?.text()
|
||||
val ratingText = infoHeaders?.get(1)?.text()?.replace("/ 10", "")
|
||||
val epRating = if (ratingText == null) null else parseRating(ratingText)
|
||||
val epDescript = main.selectFirst("> p")?.text()
|
||||
TvSeriesEpisode(epName, season, episode, href, epPoster, date, epRating, epDescript)
|
||||
}
|
||||
return TvSeriesLoadResponse(title,
|
||||
slug,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
ArrayList(parsedEpisodes),
|
||||
poster,
|
||||
year,
|
||||
descript,
|
||||
null,
|
||||
imdbUrl,
|
||||
rating,
|
||||
tags,
|
||||
duration,
|
||||
trailer)
|
||||
} else {
|
||||
val data = fixUrl(document.selectFirst("content").attr("data-url") ?: return null)
|
||||
return MovieLoadResponse(title,
|
||||
slug,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
data,
|
||||
poster,
|
||||
year,
|
||||
descript,
|
||||
imdbUrl,
|
||||
rating,
|
||||
tags,
|
||||
duration,
|
||||
trailer)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -483,6 +483,21 @@ activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
|
|||
result_tag_holder.visibility = GONE
|
||||
result_status.visibility = GONE
|
||||
|
||||
val tags = d.tags
|
||||
if (tags == null) {
|
||||
result_tag_holder.visibility = GONE
|
||||
} else {
|
||||
result_tag_holder.visibility = VISIBLE
|
||||
|
||||
for ((index, tag) in tags.withIndex()) {
|
||||
val viewBtt = layoutInflater.inflate(R.layout.result_tag, null)
|
||||
val btt = viewBtt.findViewById<MaterialButton>(R.id.result_tag_card)
|
||||
btt.text = tag
|
||||
|
||||
result_tag.addView(viewBtt, index)
|
||||
}
|
||||
}
|
||||
|
||||
when (d.type) {
|
||||
TvType.Movie -> {
|
||||
result_play_movie.visibility = VISIBLE
|
||||
|
@ -525,20 +540,6 @@ activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
|
|||
val titleName = d.name
|
||||
result_title.text = titleName
|
||||
result_toolbar.title = titleName
|
||||
|
||||
if (d.tags == null) {
|
||||
result_tag_holder.visibility = GONE
|
||||
} else {
|
||||
result_tag_holder.visibility = VISIBLE
|
||||
|
||||
for ((index, tag) in d.tags.withIndex()) {
|
||||
val viewBtt = layoutInflater.inflate(R.layout.result_tag, null)
|
||||
val btt = viewBtt.findViewById<MaterialButton>(R.id.result_tag_card)
|
||||
btt.text = tag
|
||||
|
||||
result_tag.addView(viewBtt, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> result_title.text = d.name
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.lagradost.cloudstream3.utils.extractors
|
||||
|
||||
//{"auto":"/manifests/movies/15559/1624728920/qDwu5BOsfAwfTmnnjmkmXA/master.m3u8","1080p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/1080p/index.m3u8","720p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/720p/index.m3u8","360p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/360p/index.m3u8","480p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/480p/index.m3u8"}
|
||||
object M3u8Manifest {
|
||||
// URL = first, QUALITY = second
|
||||
fun extractLinks(m3u8Data: String): ArrayList<Pair<String, String>> {
|
||||
val data: ArrayList<Pair<String, String>> = ArrayList()
|
||||
"\"(.*?)\":\"(.*?)\"".toRegex().findAll(m3u8Data).forEach {
|
||||
var quality = it.groupValues[1].replace("auto", "Auto")
|
||||
if (quality != "Auto") quality += "p"
|
||||
val url = it.groupValues[2]
|
||||
data.add(Pair(url, quality))
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue