From 4cfb7c38c04e65b35b60e783476bf5d1e9e5e447 Mon Sep 17 00:00:00 2001 From: LagradOst Date: Sat, 26 Jun 2021 16:44:53 +0200 Subject: [PATCH] TrailersToProvider --- .../com/lagradost/cloudstream3/MainAPI.kt | 49 +++-- .../movieproviders/LookMovieProvider.kt | 25 +-- .../movieproviders/MeloMovieProvider.kt | 6 +- .../movieproviders/TrailersToProvider.kt | 176 ++++++++++++++++++ .../cloudstream3/ui/result/ResultFragment.kt | 29 +-- .../utils/extractors/M3u8Manifest.kt | 16 ++ 6 files changed, 255 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/extractors/M3u8Manifest.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 1282625f..9ff3050b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -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? { + open fun quickSearch(query: String): ArrayList? { 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? + 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? = null, + override val tags: ArrayList? = null, val synonyms: ArrayList? = 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? = 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? = null, + override val duration: String? = null, + override val trailerUrl: String? = null, ) : LoadResponse \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LookMovieProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LookMovieProvider.kt index 1dd55aa1..f4ed554d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LookMovieProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LookMovieProvider.kt @@ -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? { + override fun quickSearch(query: String): ArrayList { val movieUrl = "$mainUrl/api/v1/movies/search/?q=$query" val movieResponse = khttp.get(movieUrl) val movies = mapper.readValue(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(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 } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt index 9be28748..45c0dfba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt @@ -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() val seasons = document.select("div.accordion__card") @@ -154,7 +154,7 @@ class MeloMovieProvider : MainAPI() { year, plot, null, - imdbId) + imdbUrl) } return null } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt new file mode 100644 index 00000000..e4b979f1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt @@ -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 { + 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() + 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 { + 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() + 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 = " 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? = 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 0667a4af..bda55be9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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(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(R.id.result_tag_card) - btt.text = tag - - result_tag.addView(viewBtt, index) - } - } } else -> result_title.text = d.name } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/M3u8Manifest.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/M3u8Manifest.kt new file mode 100644 index 00000000..13547bf6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/M3u8Manifest.kt @@ -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> { + val data: ArrayList> = 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 + } +} \ No newline at end of file