2021-06-24 17:45:59 +00:00
|
|
|
package com.lagradost.cloudstream3.movieproviders
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty
|
|
|
|
import com.fasterxml.jackson.module.kotlin.readValue
|
|
|
|
import com.lagradost.cloudstream3.*
|
2021-06-24 23:20:49 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.unixTime
|
2021-06-24 17:45:59 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
2021-07-30 01:14:53 +00:00
|
|
|
import com.lagradost.cloudstream3.extractors.M3u8Manifest
|
2021-06-24 23:20:49 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
2021-06-24 17:45:59 +00:00
|
|
|
import org.jsoup.Jsoup
|
|
|
|
|
2021-06-26 14:44:53 +00:00
|
|
|
//BE AWARE THAT weboas.is is a clone of lookmovie
|
2021-06-24 17:45:59 +00:00
|
|
|
class LookMovieProvider : MainAPI() {
|
|
|
|
override val hasQuickSearch: Boolean
|
|
|
|
get() = true
|
|
|
|
|
|
|
|
override val name: String
|
|
|
|
get() = "LookMovie"
|
|
|
|
|
|
|
|
override val mainUrl: String
|
|
|
|
get() = "https://lookmovie.io"
|
|
|
|
|
2021-08-14 17:31:27 +00:00
|
|
|
override val supportedTypes: Set<TvType>
|
|
|
|
get() = setOf(
|
|
|
|
TvType.Movie,
|
|
|
|
TvType.TvSeries,
|
|
|
|
)
|
|
|
|
|
2021-06-24 17:45:59 +00:00
|
|
|
data class LookMovieSearchResult(
|
|
|
|
@JsonProperty("backdrop") val backdrop: String?,
|
|
|
|
@JsonProperty("imdb_rating") val imdb_rating: String,
|
|
|
|
@JsonProperty("poster") val poster: String?,
|
|
|
|
@JsonProperty("slug") val slug: String,
|
|
|
|
@JsonProperty("title") val title: String,
|
|
|
|
@JsonProperty("year") val year: String?,
|
|
|
|
// @JsonProperty("flag_quality") val flag_quality: Int?,
|
|
|
|
)
|
|
|
|
|
2021-06-24 23:20:49 +00:00
|
|
|
data class LookMovieTokenRoot(
|
|
|
|
@JsonProperty("data") val data: LookMovieTokenResult?,
|
|
|
|
@JsonProperty("success") val success: Boolean,
|
|
|
|
)
|
|
|
|
|
|
|
|
data class LookMovieTokenResult(
|
|
|
|
@JsonProperty("accessToken") val accessToken: String,
|
|
|
|
@JsonProperty("subtitles") val subtitles: List<LookMovieTokenSubtitle>?,
|
|
|
|
)
|
|
|
|
|
|
|
|
data class LookMovieTokenSubtitle(
|
|
|
|
@JsonProperty("language") val language: String,
|
2021-07-01 20:11:33 +00:00
|
|
|
@JsonProperty("source") val source: String?,
|
2021-06-24 23:20:49 +00:00
|
|
|
//@JsonProperty("source_id") val source_id: String,
|
|
|
|
//@JsonProperty("kind") val kind: String,
|
|
|
|
//@JsonProperty("id") val id: String,
|
|
|
|
@JsonProperty("file") val file: String,
|
|
|
|
)
|
|
|
|
|
2021-06-24 17:45:59 +00:00
|
|
|
data class LookMovieSearchResultRoot(
|
|
|
|
// @JsonProperty("per_page") val per_page: Int?,
|
|
|
|
// @JsonProperty("total") val total: Int?,
|
|
|
|
@JsonProperty("result") val result: List<LookMovieSearchResult>?,
|
|
|
|
)
|
|
|
|
|
|
|
|
data class LookMovieEpisode(
|
|
|
|
@JsonProperty("title") var title: String,
|
|
|
|
@JsonProperty("index") var index: String,
|
|
|
|
@JsonProperty("episode") var episode: String,
|
|
|
|
@JsonProperty("id_episode") var idEpisode: Int,
|
|
|
|
@JsonProperty("season") var season: String,
|
|
|
|
)
|
|
|
|
|
2021-06-26 14:44:53 +00:00
|
|
|
override fun quickSearch(query: String): ArrayList<SearchResponse> {
|
2021-06-24 17:45:59 +00:00
|
|
|
val movieUrl = "$mainUrl/api/v1/movies/search/?q=$query"
|
|
|
|
val movieResponse = khttp.get(movieUrl)
|
|
|
|
val movies = mapper.readValue<LookMovieSearchResultRoot>(movieResponse.text).result
|
|
|
|
|
|
|
|
val showsUrl = "$mainUrl/api/v1/shows/search/?q=$query"
|
|
|
|
val showsResponse = khttp.get(showsUrl)
|
|
|
|
val shows = mapper.readValue<LookMovieSearchResultRoot>(showsResponse.text).result
|
|
|
|
|
|
|
|
val returnValue = ArrayList<SearchResponse>()
|
|
|
|
if (!movies.isNullOrEmpty()) {
|
|
|
|
for (m in movies) {
|
|
|
|
val url = "$mainUrl/movies/view/${m.slug}"
|
2021-07-01 20:11:33 +00:00
|
|
|
returnValue.add(
|
|
|
|
MovieSearchResponse(
|
|
|
|
m.title,
|
|
|
|
url,
|
|
|
|
this.name,
|
|
|
|
TvType.Movie,
|
|
|
|
m.poster ?: m.backdrop,
|
|
|
|
m.year?.toIntOrNull()
|
|
|
|
)
|
|
|
|
)
|
2021-06-24 17:45:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!shows.isNullOrEmpty()) {
|
|
|
|
for (s in shows) {
|
|
|
|
val url = "$mainUrl/shows/view/${s.slug}"
|
2021-07-01 20:11:33 +00:00
|
|
|
returnValue.add(
|
|
|
|
MovieSearchResponse(
|
|
|
|
s.title,
|
|
|
|
url,
|
|
|
|
this.name,
|
|
|
|
TvType.TvSeries,
|
|
|
|
s.poster ?: s.backdrop,
|
|
|
|
s.year?.toIntOrNull()
|
|
|
|
)
|
|
|
|
)
|
2021-06-24 17:45:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return returnValue
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun search(query: String): ArrayList<SearchResponse> {
|
|
|
|
fun search(query: String, isMovie: Boolean): ArrayList<SearchResponse> {
|
|
|
|
val url = "$mainUrl/${if (isMovie) "movies" else "shows"}/search/?q=$query"
|
|
|
|
val response = khttp.get(url)
|
|
|
|
val document = Jsoup.parse(response.text)
|
|
|
|
|
|
|
|
val items = document.select("div.flex-wrap-movielist > div.movie-item-style-1")
|
|
|
|
val returnValue = ArrayList<SearchResponse>()
|
|
|
|
items.forEach { item ->
|
|
|
|
val titleHolder = item.selectFirst("> div.mv-item-infor > h6 > a")
|
|
|
|
val href = fixUrl(titleHolder.attr("href"))
|
|
|
|
val name = titleHolder.text()
|
|
|
|
val posterHolder = item.selectFirst("> div.image__placeholder > a")
|
|
|
|
val poster = posterHolder.selectFirst("> img")?.attr("data-src")
|
|
|
|
val year = posterHolder.selectFirst("> p.year")?.text()?.toIntOrNull()
|
|
|
|
|
2021-07-01 20:11:33 +00:00
|
|
|
returnValue.add(
|
|
|
|
if (isMovie) {
|
|
|
|
MovieSearchResponse(
|
2021-07-30 14:09:57 +00:00
|
|
|
name, href, this.name, TvType.Movie, poster, year
|
2021-07-01 20:11:33 +00:00
|
|
|
)
|
|
|
|
} else
|
|
|
|
TvSeriesSearchResponse(
|
2021-07-30 14:09:57 +00:00
|
|
|
name, href, this.name, TvType.TvSeries, poster, year, null
|
2021-07-01 20:11:33 +00:00
|
|
|
)
|
2021-06-24 17:45:59 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
return returnValue
|
|
|
|
}
|
|
|
|
|
|
|
|
val movieList = search(query, true)
|
|
|
|
val seriesList = search(query, false)
|
|
|
|
movieList.addAll(seriesList)
|
|
|
|
return movieList
|
|
|
|
}
|
|
|
|
|
2021-07-01 20:11:33 +00:00
|
|
|
data class LookMovieLinkLoad(val url: String, val extraUrl: String, val isMovie: Boolean)
|
|
|
|
|
|
|
|
private fun addSubtitles(subs: List<LookMovieTokenSubtitle>?, subtitleCallback: (SubtitleFile) -> Unit) {
|
|
|
|
if (subs == null) return
|
|
|
|
subs.forEach {
|
2021-07-02 18:46:18 +00:00
|
|
|
if (it.file.endsWith(".vtt"))
|
2021-07-01 20:11:33 +00:00
|
|
|
subtitleCallback.invoke(SubtitleFile(it.language, fixUrl(it.file)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun loadCurrentLinks(url: String, callback: (ExtractorLink) -> Unit) {
|
|
|
|
val response = khttp.get(url.replace("\$unixtime", unixTime.toString()))
|
2021-06-26 14:44:53 +00:00
|
|
|
M3u8Manifest.extractLinks(response.text).forEach {
|
2021-07-01 20:11:33 +00:00
|
|
|
callback.invoke(
|
|
|
|
ExtractorLink(
|
|
|
|
this.name,
|
|
|
|
"${this.name} - ${it.second}",
|
|
|
|
fixUrl(it.first),
|
|
|
|
"",
|
|
|
|
getQualityFromName(it.second),
|
|
|
|
true
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun loadLinks(
|
|
|
|
data: String,
|
|
|
|
isCasting: Boolean,
|
|
|
|
subtitleCallback: (SubtitleFile) -> Unit,
|
|
|
|
callback: (ExtractorLink) -> Unit
|
|
|
|
): Boolean {
|
|
|
|
val localData: LookMovieLinkLoad = mapper.readValue(data)
|
|
|
|
|
|
|
|
if (localData.isMovie) {
|
|
|
|
val tokenResponse = khttp.get(localData.url)
|
|
|
|
val root = mapper.readValue<LookMovieTokenRoot>(tokenResponse.text)
|
|
|
|
val accessToken = root.data?.accessToken ?: return false
|
|
|
|
addSubtitles(root.data.subtitles, subtitleCallback)
|
|
|
|
loadCurrentLinks(localData.extraUrl.replace("\$accessToken", accessToken), callback)
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
loadCurrentLinks(localData.url, callback)
|
|
|
|
val subResponse = khttp.get(localData.extraUrl)
|
|
|
|
val subs = mapper.readValue<List<LookMovieTokenSubtitle>>(subResponse.text)
|
|
|
|
addSubtitles(subs, subtitleCallback)
|
2021-06-24 23:20:49 +00:00
|
|
|
}
|
|
|
|
return true
|
2021-06-24 17:45:59 +00:00
|
|
|
}
|
|
|
|
|
2021-07-30 14:09:57 +00:00
|
|
|
override fun load(url: String): LoadResponse? {
|
|
|
|
val response = khttp.get(url)
|
2021-06-24 17:45:59 +00:00
|
|
|
val document = Jsoup.parse(response.text)
|
2021-07-30 14:09:57 +00:00
|
|
|
val isMovie = url.contains("/movies/")
|
2021-06-24 17:45:59 +00:00
|
|
|
|
|
|
|
val watchHeader = document.selectFirst("div.watch-heading")
|
|
|
|
val nameHeader = watchHeader.selectFirst("> h1.bd-hd")
|
|
|
|
val year = nameHeader.selectFirst("> span")?.text()?.toIntOrNull()
|
2021-07-01 20:11:33 +00:00
|
|
|
val title = nameHeader.ownText()
|
2021-06-24 17:45:59 +00:00
|
|
|
val rating = parseRating(watchHeader.selectFirst("> div.movie-rate > div.rate > p > span").text())
|
2021-06-24 23:20:49 +00:00
|
|
|
val imgElement = document.selectFirst("div.movie-img > p.movie__poster")
|
|
|
|
val img = imgElement?.attr("style")
|
|
|
|
var poster = if (img.isNullOrEmpty()) null else "url\\((.*?)\\)".toRegex().find(img)?.groupValues?.get(1)
|
|
|
|
if (poster.isNullOrEmpty()) poster = imgElement?.attr("data-background-image")
|
2021-06-24 17:45:59 +00:00
|
|
|
val descript = document.selectFirst("p.description-short").text()
|
2021-06-24 23:20:49 +00:00
|
|
|
val id = "${if (isMovie) "id_movie" else "id_show"}:(.*?),".toRegex().find(response.text)?.groupValues?.get(1)
|
|
|
|
?.replace(" ", "")
|
2021-06-24 17:45:59 +00:00
|
|
|
?: return null
|
2021-07-30 14:09:57 +00:00
|
|
|
val realSlug = url.replace("$mainUrl/${if (isMovie) "movies" else "shows"}/view/", "")
|
2021-06-24 17:45:59 +00:00
|
|
|
val realUrl =
|
|
|
|
"$mainUrl/api/v1/security/${if (isMovie) "movie" else "show"}-access?${if (isMovie) "id_movie=$id" else "slug=$realSlug"}&token=1&sk=&step=1"
|
2021-06-24 23:20:49 +00:00
|
|
|
|
2021-06-24 17:45:59 +00:00
|
|
|
if (isMovie) {
|
2021-07-01 20:11:33 +00:00
|
|
|
val localData = mapper.writeValueAsString(
|
|
|
|
LookMovieLinkLoad(
|
|
|
|
realUrl,
|
|
|
|
"$mainUrl/manifests/movies/json/$id/\$unixtime/\$accessToken/master.m3u8",
|
|
|
|
true
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return MovieLoadResponse(
|
|
|
|
title,
|
2021-07-30 14:09:57 +00:00
|
|
|
url,
|
2021-06-24 23:20:49 +00:00
|
|
|
this.name,
|
|
|
|
TvType.Movie,
|
2021-07-01 20:11:33 +00:00
|
|
|
localData,
|
2021-06-24 23:20:49 +00:00
|
|
|
poster,
|
|
|
|
year,
|
|
|
|
descript,
|
|
|
|
null,
|
2021-07-01 20:11:33 +00:00
|
|
|
rating
|
|
|
|
)
|
2021-06-24 17:45:59 +00:00
|
|
|
} else {
|
2021-07-01 20:11:33 +00:00
|
|
|
val tokenResponse = khttp.get(realUrl)
|
|
|
|
val root = mapper.readValue<LookMovieTokenRoot>(tokenResponse.text)
|
|
|
|
val accessToken = root.data?.accessToken ?: return null
|
|
|
|
|
2021-06-24 17:45:59 +00:00
|
|
|
val window =
|
|
|
|
"window\\[\\'show_storage\\'\\] =((.|\\n)*?\\<)".toRegex().find(response.text)?.groupValues?.get(1)
|
|
|
|
?: return null
|
|
|
|
// val id = "id_show:(.*?),".toRegex().find(response.text)?.groupValues?.get(1) ?: return null
|
|
|
|
val season = "seasons:.*\\[((.|\\n)*?)]".toRegex().find(window)?.groupValues?.get(1) ?: return null
|
|
|
|
fun String.fixSeasonJson(replace: String): String {
|
|
|
|
return this.replace("$replace:", "\"$replace\":")
|
|
|
|
}
|
|
|
|
|
2021-06-24 23:20:49 +00:00
|
|
|
val json = season
|
2021-06-24 17:45:59 +00:00
|
|
|
.replace("\'", "\"")
|
|
|
|
.fixSeasonJson("title")
|
|
|
|
.fixSeasonJson("id_episode")
|
|
|
|
.fixSeasonJson("episode")
|
|
|
|
.fixSeasonJson("index")
|
|
|
|
.fixSeasonJson("season")
|
2021-06-24 23:20:49 +00:00
|
|
|
val realJson = "[" + json.substring(0, json.lastIndexOf(',')) + "]"
|
2021-06-24 17:45:59 +00:00
|
|
|
|
|
|
|
val episodes = mapper.readValue<List<LookMovieEpisode>>(realJson).map {
|
2021-07-01 20:11:33 +00:00
|
|
|
val localData = mapper.writeValueAsString(
|
|
|
|
LookMovieLinkLoad(
|
|
|
|
"$mainUrl/manifests/shows/json/$accessToken/\$unixtime/${it.idEpisode}/master.m3u8",
|
|
|
|
"https://lookmovie.io/api/v1/shows/episode-subtitles/?id_episode=${it.idEpisode}",
|
|
|
|
false
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
TvSeriesEpisode(
|
|
|
|
it.title,
|
2021-06-24 23:20:49 +00:00
|
|
|
it.season.toIntOrNull(),
|
|
|
|
it.episode.toIntOrNull(),
|
2021-07-01 20:11:33 +00:00
|
|
|
localData
|
|
|
|
)
|
2021-06-24 17:45:59 +00:00
|
|
|
}.toList()
|
|
|
|
|
2021-07-01 20:11:33 +00:00
|
|
|
return TvSeriesLoadResponse(
|
|
|
|
title,
|
2021-07-30 14:09:57 +00:00
|
|
|
url,
|
2021-06-24 17:45:59 +00:00
|
|
|
this.name,
|
|
|
|
TvType.TvSeries,
|
|
|
|
ArrayList(episodes),
|
|
|
|
poster,
|
|
|
|
year,
|
|
|
|
descript,
|
|
|
|
null,
|
|
|
|
null,
|
2021-07-01 20:11:33 +00:00
|
|
|
rating
|
|
|
|
)
|
2021-06-24 17:45:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|