forked from recloudstream/cloudstream
New Providers [Vietnamese][Ukrainian][Russian] (#1266)
* Add files via upload * Add files via upload * Update MainAPI.kt * - Add NontonAnimeID and Kuramanime - refactoring code * add GomunimeProvider, add some source, fix minor code * add sources * add KuronimeProvider, add sources * small fix, (ready to merge..) * add indonesia provider, add extractor, add source, small fix * small fix (ready to merge) * fix * fix layarkaca/gomunime/kuronime * fix (ready to merge) * add new indonesian providers * small fix * add multiplex provider * update providers * add trailer to Providers * add indexsubtitle * small fix * small fix (ready to merge) * clean * fix search * improve search and load * small fix * left * left too * idlix domain fix * fix AnimeIndo * small cleaning * fix(from feedback) & update Kuronime * small fix * fix * fix extractor * fix Kuronime source * add HDrezka/Phimmoichill/Uakino * fixHDrezka qualty * try to fix Phimmoichill * fix NontonAnimeID/Oploverz/Kuramanime & add extractor * small fix * enable indexsubtitle * fixes HDrezka sources * fixes rebahin * fixes Idlix domain * small fix Co-authored-by: Osten <11805592+LagradOst@users.noreply.github.com>
This commit is contained in:
parent
524d0717e2
commit
b970ca0668
12 changed files with 978 additions and 142 deletions
|
@ -101,6 +101,10 @@ object APIHolder {
|
||||||
IdlixProvider(),
|
IdlixProvider(),
|
||||||
MultiplexProvider(),
|
MultiplexProvider(),
|
||||||
VidSrcProvider(),
|
VidSrcProvider(),
|
||||||
|
UakinoProvider(),
|
||||||
|
PhimmoichillProvider(),
|
||||||
|
HDrezkaProvider(),
|
||||||
|
|
||||||
|
|
||||||
// Metadata providers
|
// Metadata providers
|
||||||
//TmdbProvider(),
|
//TmdbProvider(),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.animeproviders
|
package com.lagradost.cloudstream3.animeproviders
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||||
import com.lagradost.cloudstream3.network.DdosGuardKiller
|
import com.lagradost.cloudstream3.network.DdosGuardKiller
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
|
@ -42,30 +43,22 @@ class KuramanimeProvider : MainAPI() {
|
||||||
|
|
||||||
val homePageList = ArrayList<HomePageList>()
|
val homePageList = ArrayList<HomePageList>()
|
||||||
|
|
||||||
document.select("div[class*=__product]").forEach { block ->
|
document.select("div.trending__product").forEach { block ->
|
||||||
val header = block.select(".section-title > h4").text()
|
val header = block.selectFirst("h4")?.text().toString()
|
||||||
val animes = block.select("div.col-lg-4.col-md-6.col-sm-6").mapNotNull {
|
val animes = block.select("div.col-lg-4.col-md-6.col-sm-6").map {
|
||||||
it.toSearchResult()
|
it.toSearchResult()
|
||||||
}
|
}
|
||||||
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
||||||
}
|
}
|
||||||
|
|
||||||
document.select("#topAnimesSection").forEach { block ->
|
document.select("div.product__sidebar__view").forEach { block ->
|
||||||
val header = block.previousElementSibling()!!.select("h5").text().trim()
|
val header = block.selectFirst("h5")?.text().toString()
|
||||||
val animes = block.select("a").mapNotNull {
|
val animes = block.select("div.product__sidebar__comment__item").map {
|
||||||
it.toSearchResultView()
|
it.toSearchResultView()
|
||||||
}
|
}
|
||||||
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
||||||
}
|
}
|
||||||
|
|
||||||
document.select("#latestCommentSection").forEach { block ->
|
|
||||||
val header = block.previousElementSibling()!!.select("h5").text().trim()
|
|
||||||
val animes = block.select(".product__sidebar__comment__item").mapNotNull {
|
|
||||||
it.toSearchResultComment()
|
|
||||||
}
|
|
||||||
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
|
||||||
}
|
|
||||||
|
|
||||||
return HomePageResponse(homePageList)
|
return HomePageResponse(homePageList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,41 +70,34 @@ class KuramanimeProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Element.toSearchResult(): SearchResponse {
|
private fun Element.toSearchResult(): AnimeSearchResponse {
|
||||||
val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href")))
|
val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href")))
|
||||||
val title = this.select(".product__item__text > h5 > a").text()
|
val title = this.selectFirst("h5 a")?.text().toString()
|
||||||
val posterUrl = fixUrl(this.select(".product__item__pic.set-bg").attr("data-setbg"))
|
val posterUrl = fixUrl(this.select("div.product__item__pic.set-bg").attr("data-setbg"))
|
||||||
val type = getType(this.selectFirst(".product__item__text > ul > li")!!.text())
|
val episode = Regex("([0-9*])\\s?/").find(
|
||||||
|
this.select("div.ep span").text()
|
||||||
return newAnimeSearchResponse(title, href, type) {
|
)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||||
this.posterUrl = posterUrl
|
|
||||||
addDubStatus(dubExist = false, subExist = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Element.toSearchResultView(): SearchResponse {
|
|
||||||
val href = getProperAnimeLink(fixUrl(this.attr("href")))
|
|
||||||
val title = this.selectFirst("h5")!!.text().trim()
|
|
||||||
val posterUrl =
|
|
||||||
fixUrl(this.select(".product__sidebar__view__item.set-bg").attr("data-setbg"))
|
|
||||||
|
|
||||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||||
this.posterUrl = posterUrl
|
this.posterUrl = posterUrl
|
||||||
addDubStatus(dubExist = false, subExist = true)
|
addSub(episode)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Element.toSearchResultComment(): SearchResponse {
|
private fun Element.toSearchResultView(): AnimeSearchResponse {
|
||||||
val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href")))
|
val href = getProperAnimeLink(fixUrl(this.selectFirst("a")?.attr("href").toString()))
|
||||||
val title = this.selectFirst("h5")!!.text()
|
val title = this.selectFirst("h5")?.text()?.trim().toString()
|
||||||
val posterUrl = fixUrl(this.select("img").attr("src"))
|
val posterUrl = fixUrlNull(
|
||||||
val type = getType(this.selectFirst("ul > li")!!.text())
|
this.selectFirst("div.product__sidebar__comment__item__pic.set-bg")?.attr("data-setbg")
|
||||||
|
)
|
||||||
|
val episode =
|
||||||
|
this.selectFirst("h5")?.nextElementSibling()?.text()?.replace(Regex("[^0-9]"), "")
|
||||||
|
?.toIntOrNull()
|
||||||
|
|
||||||
return newAnimeSearchResponse(title, href, type) {
|
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||||
this.posterUrl = posterUrl
|
this.posterUrl = posterUrl
|
||||||
addDubStatus(dubExist = false, subExist = true)
|
addSub(episode)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -190,8 +176,9 @@ class KuramanimeProvider : MainAPI() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val servers = app.get(data, interceptor = DdosGuardKiller(true)).document
|
val servers = app.get(data).document
|
||||||
servers.select("video#player > source").map {
|
servers.select("video#player > source").map {
|
||||||
|
suspendSafeApiCall {
|
||||||
val url = it.attr("src")
|
val url = it.attr("src")
|
||||||
val quality = it.attr("size").toInt()
|
val quality = it.attr("size").toInt()
|
||||||
callback.invoke(
|
callback.invoke(
|
||||||
|
@ -204,6 +191,7 @@ class KuramanimeProvider : MainAPI() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,16 +68,26 @@ class NontonAnimeIDProvider : MainAPI() {
|
||||||
return if (uri.contains("/anime/")) {
|
return if (uri.contains("/anime/")) {
|
||||||
uri
|
uri
|
||||||
} else {
|
} else {
|
||||||
val name = Regex("$mainUrl/(.*)-episode.*").find(uri)?.groupValues?.get(1).toString()
|
var title = uri.substringAfter("$mainUrl/")
|
||||||
if (name.contains("movie")) {
|
val fixTitle = Regex("(.*)-episode.*").find(title)?.groupValues?.getOrNull(1).toString()
|
||||||
return "$mainUrl/anime/" + name.replace("-movie", "")
|
title = when {
|
||||||
} else {
|
title.contains("utawarerumono-season-3") -> fixTitle.replace(
|
||||||
if (name.contains("kokurasetai-season-3")) {
|
"season-3",
|
||||||
"$mainUrl/anime/${name.replace("season-3", "ultra-romantic")}"
|
"futari-no-hakuoro"
|
||||||
} else {
|
)
|
||||||
"$mainUrl/anime/$name"
|
title.contains("kingdom-season-4") -> fixTitle.replace("season-4", "4th-season")
|
||||||
}
|
title.contains("maou-sama-season-2") -> fixTitle.replace("season-2", "2")
|
||||||
|
title.contains("overlord-season-4") -> fixTitle.replace("season-4", "iv")
|
||||||
|
title.contains("kyoushitsu-e-season-2") -> fixTitle.replace(
|
||||||
|
"kyoushitsu-e-season-2",
|
||||||
|
"kyoushitsu-e-tv-2nd-season"
|
||||||
|
)
|
||||||
|
title.contains("season-2") -> fixTitle.replace("season-2", "2nd-season")
|
||||||
|
title.contains("season-3") -> fixTitle.replace("season-3", "3rd-season")
|
||||||
|
title.contains("movie") -> title.substringBefore("-movie")
|
||||||
|
else -> fixTitle
|
||||||
}
|
}
|
||||||
|
"$mainUrl/anime/$title"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ class OploverzProvider : MainAPI() {
|
||||||
)?.groupValues?.get(1).toString()
|
)?.groupValues?.get(1).toString()
|
||||||
(title.contains("-ova")) -> Regex("(.+)-ova").find(title)?.groupValues?.get(1)
|
(title.contains("-ova")) -> Regex("(.+)-ova").find(title)?.groupValues?.get(1)
|
||||||
.toString()
|
.toString()
|
||||||
|
(title.contains("-movie")) -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1).toString()
|
||||||
else -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1).toString()
|
else -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1).toString()
|
||||||
.replace(Regex("-\\d+"), "")
|
.replace(Regex("-\\d+"), "")
|
||||||
}
|
}
|
||||||
|
@ -136,9 +137,9 @@ class OploverzProvider : MainAPI() {
|
||||||
val typeCheck =
|
val typeCheck =
|
||||||
when (document.select(".info-content > .spe > span:nth-child(5), .info-content > .spe > span")
|
when (document.select(".info-content > .spe > span:nth-child(5), .info-content > .spe > span")
|
||||||
.text().trim()) {
|
.text().trim()) {
|
||||||
"TV" -> "TV"
|
"OVA" -> "OVA"
|
||||||
"Movie" -> "Movie"
|
"Movie" -> "Movie"
|
||||||
else -> "OVA"
|
else -> "TV"
|
||||||
}
|
}
|
||||||
val type = getType(typeCheck)
|
val type = getType(typeCheck)
|
||||||
val description = document.select(".entry-content > p").text().trim()
|
val description = document.select(".entry-content > p").text().trim()
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.lagradost.cloudstream3.extractors
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
|
||||||
|
class Linkbox : ExtractorApi() {
|
||||||
|
override val name = "Linkbox"
|
||||||
|
override val mainUrl = "https://www.linkbox.to"
|
||||||
|
override val requiresReferer = true
|
||||||
|
|
||||||
|
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||||
|
val id = url.substringAfter("id=")
|
||||||
|
val sources = mutableListOf<ExtractorLink>()
|
||||||
|
|
||||||
|
app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe<Responses>()?.data?.rList?.map { link ->
|
||||||
|
sources.add(
|
||||||
|
ExtractorLink(
|
||||||
|
name,
|
||||||
|
name,
|
||||||
|
link.url,
|
||||||
|
url,
|
||||||
|
getQualityFromName(link.resolution)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RList(
|
||||||
|
@JsonProperty("url") val url: String,
|
||||||
|
@JsonProperty("resolution") val resolution: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Data(
|
||||||
|
@JsonProperty("rList") val rList: List<RList>?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Responses(
|
||||||
|
@JsonProperty("data") val data: Data?,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,410 @@
|
||||||
|
package com.lagradost.cloudstream3.movieproviders
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class HDrezkaProvider : MainAPI() {
|
||||||
|
override var mainUrl = "https://rezka.ag"
|
||||||
|
override var name = "HDrezka"
|
||||||
|
override val hasMainPage = true
|
||||||
|
override var lang = "ru"
|
||||||
|
override val hasDownloadSupport = true
|
||||||
|
override val supportedTypes = setOf(
|
||||||
|
TvType.Movie,
|
||||||
|
TvType.TvSeries,
|
||||||
|
TvType.Anime,
|
||||||
|
TvType.AsianDrama
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getMainPage(): HomePageResponse {
|
||||||
|
|
||||||
|
val items = ArrayList<HomePageList>()
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
Pair("фильмы", "$mainUrl/films/?filter=watching"),
|
||||||
|
Pair("сериалы", "$mainUrl/series/?filter=watching"),
|
||||||
|
Pair("мультфильмы", "$mainUrl/cartoons/?filter=watching"),
|
||||||
|
Pair("аниме", "$mainUrl/animation/?filter=watching"),
|
||||||
|
).apmap { (header, url) ->
|
||||||
|
safeApiCall {
|
||||||
|
val home = app.get(url).document.select(
|
||||||
|
"div.b-content__inline_items div.b-content__inline_item"
|
||||||
|
).map {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
items.add(HomePageList(fixTitle(header), home))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (items.size <= 0) throw ErrorLoadingException()
|
||||||
|
return HomePageResponse(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Element.toSearchResult(): SearchResponse {
|
||||||
|
val title =
|
||||||
|
this.selectFirst("div.b-content__inline_item-link > a")?.text()?.trim().toString()
|
||||||
|
val href = this.selectFirst("a")?.attr("href").toString()
|
||||||
|
val posterUrl = this.select("img").attr("src")
|
||||||
|
val type = if (this.select("span.info").isNotEmpty()) TvType.TvSeries else TvType.Movie
|
||||||
|
return if (type == TvType.Movie) {
|
||||||
|
newMovieSearchResponse(title, href, TvType.Movie) {
|
||||||
|
this.posterUrl = posterUrl
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val episode =
|
||||||
|
this.select("span.info").text().substringAfter(",").replace(Regex("[^0-9]"), "")
|
||||||
|
.toIntOrNull()
|
||||||
|
newAnimeSearchResponse(title, href, TvType.TvSeries) {
|
||||||
|
this.posterUrl = posterUrl
|
||||||
|
addDubStatus(
|
||||||
|
dubExist = true,
|
||||||
|
dubEpisodes = episode,
|
||||||
|
subExist = true,
|
||||||
|
subEpisodes = episode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
|
val link = "$mainUrl/search/?do=search&subaction=search&q=$query"
|
||||||
|
val document = app.get(link).document
|
||||||
|
|
||||||
|
return document.select("div.b-content__inline_items div.b-content__inline_item").map {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(url: String): LoadResponse {
|
||||||
|
val document = app.get(url).document
|
||||||
|
|
||||||
|
val id = url.split("/").last().split("-").first()
|
||||||
|
val title = (document.selectFirst("div.b-post__origtitle")?.text()?.trim()
|
||||||
|
?: document.selectFirst("div.b-post__title h1")?.text()?.trim()).toString()
|
||||||
|
val poster = fixUrlNull(document.selectFirst("div.b-sidecover img")?.attr("src"))
|
||||||
|
val tags =
|
||||||
|
document.select("table.b-post__info > tbody > tr:nth-child(5) span[itemprop=genre]")
|
||||||
|
.map { it.text() }
|
||||||
|
val year = document.select("div.film-info > div:nth-child(2) a").text().toIntOrNull()
|
||||||
|
val tvType = if (document.select("div#simple-episodes-tabs")
|
||||||
|
.isNullOrEmpty()
|
||||||
|
) TvType.Movie else TvType.TvSeries
|
||||||
|
val description = document.selectFirst("div.b-post__description_text")?.text()?.trim()
|
||||||
|
val trailer = app.post(
|
||||||
|
"$mainUrl/engine/ajax/gettrailervideo.php",
|
||||||
|
data = mapOf("id" to id),
|
||||||
|
referer = url
|
||||||
|
).parsedSafe<Trailer>()?.code.let {
|
||||||
|
Jsoup.parse(it.toString()).select("iframe").attr("src")
|
||||||
|
}
|
||||||
|
val rating =
|
||||||
|
document.selectFirst("table.b-post__info > tbody > tr:nth-child(1) span.bold")?.text()
|
||||||
|
.toRatingInt()
|
||||||
|
val actors = document.select("table.b-post__info > tbody > tr:last-child span.item").map {
|
||||||
|
Actor(
|
||||||
|
it.selectFirst("span[itemprop=name]")?.text().toString(),
|
||||||
|
it.selectFirst("span[itemprop=actor]")?.attr("data-photo")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val recommendations = document.select("div.b-sidelist div.b-content__inline_item").map {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = HashMap<String, Any>()
|
||||||
|
val server = ArrayList<Map<String, String>>()
|
||||||
|
|
||||||
|
data["id"] = id
|
||||||
|
data["favs"] = document.selectFirst("input#ctrl_favs")?.attr("value").toString()
|
||||||
|
data["ref"] = url
|
||||||
|
|
||||||
|
return if (tvType == TvType.TvSeries) {
|
||||||
|
document.select("ul#translators-list li").map { res ->
|
||||||
|
server.add(
|
||||||
|
mapOf(
|
||||||
|
"translator_name" to res.text(),
|
||||||
|
"translator_id" to res.attr("data-translator_id"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val episodes = document.select("div#simple-episodes-tabs ul li").map {
|
||||||
|
val season = it.attr("data-season_id").toIntOrNull()
|
||||||
|
val episode = it.attr("data-episode_id").toIntOrNull()
|
||||||
|
val name = "Episode $episode"
|
||||||
|
|
||||||
|
data["season"] = "$season"
|
||||||
|
data["episode"] = "$episode"
|
||||||
|
data["server"] = server
|
||||||
|
data["action"] = "get_stream"
|
||||||
|
|
||||||
|
Episode(
|
||||||
|
data.toJson(),
|
||||||
|
name,
|
||||||
|
season,
|
||||||
|
episode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.year = year
|
||||||
|
this.plot = description
|
||||||
|
this.tags = tags
|
||||||
|
this.rating = rating
|
||||||
|
addActors(actors)
|
||||||
|
this.recommendations = recommendations
|
||||||
|
addTrailer(trailer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.select("ul#translators-list li").map { res ->
|
||||||
|
server.add(
|
||||||
|
mapOf(
|
||||||
|
"translator_name" to res.text(),
|
||||||
|
"translator_id" to res.attr("data-translator_id"),
|
||||||
|
"camrip" to res.attr("data-camrip"),
|
||||||
|
"ads" to res.attr("data-ads"),
|
||||||
|
"director" to res.attr("data-director")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data["server"] = server
|
||||||
|
data["action"] = "get_movie"
|
||||||
|
|
||||||
|
newMovieLoadResponse(title, url, TvType.Movie, data.toJson()) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.year = year
|
||||||
|
this.plot = description
|
||||||
|
this.tags = tags
|
||||||
|
this.rating = rating
|
||||||
|
addActors(actors)
|
||||||
|
this.recommendations = recommendations
|
||||||
|
addTrailer(trailer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decryptStreamUrl(data: String): String {
|
||||||
|
|
||||||
|
fun getTrash(arr: List<String>, item: Int): List<String> {
|
||||||
|
val trash = ArrayList<List<String>>()
|
||||||
|
for (i in 1..item) {
|
||||||
|
trash.add(arr)
|
||||||
|
}
|
||||||
|
return trash.reduce { acc, list ->
|
||||||
|
val temp = ArrayList<String>()
|
||||||
|
acc.forEach { ac ->
|
||||||
|
list.forEach { li ->
|
||||||
|
temp.add(ac.plus(li))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@reduce temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val trashList = listOf("@", "#", "!", "^", "$")
|
||||||
|
val trashSet = getTrash(trashList, 2) + getTrash(trashList, 3)
|
||||||
|
var trashString = data.replace("#h", "").split("//_//").joinToString("")
|
||||||
|
|
||||||
|
trashSet.forEach {
|
||||||
|
val temp = base64Encode(it.toByteArray())
|
||||||
|
trashString = trashString.replace(temp, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64Decode(trashString)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanCallback(
|
||||||
|
source: String,
|
||||||
|
url: String,
|
||||||
|
quality: String,
|
||||||
|
isM3u8: Boolean,
|
||||||
|
sourceCallback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
sourceCallback.invoke(
|
||||||
|
ExtractorLink(
|
||||||
|
source,
|
||||||
|
source,
|
||||||
|
url,
|
||||||
|
"$mainUrl/",
|
||||||
|
getQuality(quality),
|
||||||
|
isM3u8,
|
||||||
|
headers = mapOf(
|
||||||
|
"Origin" to mainUrl
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLanguage(str: String): String {
|
||||||
|
return when (str) {
|
||||||
|
"Русский" -> "Russian"
|
||||||
|
"Українська" -> "Ukrainian"
|
||||||
|
else -> str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getQuality(str: String): Int {
|
||||||
|
return when (str) {
|
||||||
|
"360p" -> Qualities.P240.value
|
||||||
|
"480p" -> Qualities.P360.value
|
||||||
|
"720p" -> Qualities.P480.value
|
||||||
|
"1080p" -> Qualities.P720.value
|
||||||
|
"1080p Ultra" -> Qualities.P1080.value
|
||||||
|
else -> getQualityFromName(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invokeSources(
|
||||||
|
source: String,
|
||||||
|
url: String,
|
||||||
|
subtitle: String,
|
||||||
|
subCallback: (SubtitleFile) -> Unit,
|
||||||
|
sourceCallback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
decryptStreamUrl(url).split(",").map { links ->
|
||||||
|
val quality =
|
||||||
|
Regex("\\[([0-9]*p.*?)]").find(links)?.groupValues?.getOrNull(1)
|
||||||
|
.toString().trim()
|
||||||
|
links.replace("[$quality]", "").split("or").map { it.trim() }
|
||||||
|
.map { link ->
|
||||||
|
|
||||||
|
if (link.endsWith(".m3u8")) {
|
||||||
|
cleanCallback(
|
||||||
|
"$source (Main)",
|
||||||
|
link,
|
||||||
|
quality,
|
||||||
|
true,
|
||||||
|
sourceCallback,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
cleanCallback(
|
||||||
|
"$source (Backup)",
|
||||||
|
link,
|
||||||
|
quality,
|
||||||
|
false,
|
||||||
|
sourceCallback,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitle.split(",").map { sub ->
|
||||||
|
val language =
|
||||||
|
Regex("\\[(.*)]").find(sub)?.groupValues?.getOrNull(1)
|
||||||
|
.toString()
|
||||||
|
val link = sub.replace("[$language]", "").trim()
|
||||||
|
subCallback.invoke(
|
||||||
|
SubtitleFile(
|
||||||
|
getLanguage(language),
|
||||||
|
link
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
|
tryParseJson<Data>(data)?.let { res ->
|
||||||
|
if (res.server?.isEmpty() == true) {
|
||||||
|
val document = app.get(res.ref ?: return@let).document
|
||||||
|
document.select("script").map { script ->
|
||||||
|
if (script.data().contains("sof.tv.initCDNMoviesEvents(")) {
|
||||||
|
val dataJson =
|
||||||
|
script.data().substringAfter("false, {").substringBefore("});")
|
||||||
|
tryParseJson<LocalSources>("{$dataJson}")?.let { source ->
|
||||||
|
invokeSources(
|
||||||
|
this.name,
|
||||||
|
source.streams,
|
||||||
|
source.subtitle.toString(),
|
||||||
|
subtitleCallback,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.server?.apmap { server ->
|
||||||
|
suspendSafeApiCall {
|
||||||
|
app.post(
|
||||||
|
url = "$mainUrl/ajax/get_cdn_series/?t=${Date().time}",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to res.id,
|
||||||
|
"translator_id" to server.translator_id,
|
||||||
|
"favs" to res.favs,
|
||||||
|
"is_camrip" to server.camrip,
|
||||||
|
"is_ads" to server.ads,
|
||||||
|
"is_director" to server.director,
|
||||||
|
"season" to res.season,
|
||||||
|
"episode" to res.episode,
|
||||||
|
"action" to res.action,
|
||||||
|
).filterValues { it != null }.mapValues { it.value as String },
|
||||||
|
referer = res.ref
|
||||||
|
).parsedSafe<Sources>()?.let { source ->
|
||||||
|
invokeSources(
|
||||||
|
server.translator_name.toString(),
|
||||||
|
source.url,
|
||||||
|
source.subtitle.toString(),
|
||||||
|
subtitleCallback,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LocalSources(
|
||||||
|
@JsonProperty("streams") val streams: String,
|
||||||
|
@JsonProperty("subtitle") val subtitle: Any?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Sources(
|
||||||
|
@JsonProperty("url") val url: String,
|
||||||
|
@JsonProperty("subtitle") val subtitle: Any?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Server(
|
||||||
|
@JsonProperty("translator_name") val translator_name: String?,
|
||||||
|
@JsonProperty("translator_id") val translator_id: String?,
|
||||||
|
@JsonProperty("camrip") val camrip: String?,
|
||||||
|
@JsonProperty("ads") val ads: String?,
|
||||||
|
@JsonProperty("director") val director: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Data(
|
||||||
|
@JsonProperty("id") val id: String?,
|
||||||
|
@JsonProperty("favs") val favs: String?,
|
||||||
|
@JsonProperty("server") val server: List<Server>?,
|
||||||
|
@JsonProperty("season") val season: String?,
|
||||||
|
@JsonProperty("episode") val episode: String?,
|
||||||
|
@JsonProperty("action") val action: String?,
|
||||||
|
@JsonProperty("ref") val ref: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Trailer(
|
||||||
|
@JsonProperty("success") val success: Boolean?,
|
||||||
|
@JsonProperty("code") val code: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import java.net.URI
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
class IdlixProvider : MainAPI() {
|
class IdlixProvider : MainAPI() {
|
||||||
override var mainUrl = "https://idlix.xn--6frz82g"
|
override var mainUrl = "https://94.103.82.88"
|
||||||
override var name = "Idlix"
|
override var name = "Idlix"
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
override var lang = "id"
|
override var lang = "id"
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
package com.lagradost.cloudstream3.movieproviders
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
class PhimmoichillProvider : MainAPI() {
|
||||||
|
override var mainUrl = "https://phimmoichill.net"
|
||||||
|
override var name = "Phimmoichill"
|
||||||
|
override val hasMainPage = true
|
||||||
|
override var lang = "vi"
|
||||||
|
override val hasDownloadSupport = true
|
||||||
|
override val supportedTypes = setOf(
|
||||||
|
TvType.Movie,
|
||||||
|
TvType.TvSeries,
|
||||||
|
TvType.Anime,
|
||||||
|
TvType.AsianDrama
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getMainPage(): HomePageResponse {
|
||||||
|
val document = app.get(mainUrl).document
|
||||||
|
|
||||||
|
val homePageList = ArrayList<HomePageList>()
|
||||||
|
|
||||||
|
document.select("div.container div.block").forEach { block ->
|
||||||
|
val header = fixTitle(block.selectFirst("h2")!!.text())
|
||||||
|
val items = block.select("li.item").mapNotNull {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
if (items.isNotEmpty()) homePageList.add(HomePageList(header, items))
|
||||||
|
}
|
||||||
|
|
||||||
|
return HomePageResponse(homePageList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decode(input: String): String? = URLDecoder.decode(input, "utf-8")
|
||||||
|
|
||||||
|
private fun Element.toSearchResult(): SearchResponse {
|
||||||
|
val title = this.selectFirst("p,h3")?.text()?.trim().toString()
|
||||||
|
val href = fixUrl(this.selectFirst("a")!!.attr("href"))
|
||||||
|
val posterUrl = decode(this.selectFirst("img")!!.attr("src").substringAfter("url="))
|
||||||
|
val temp = this.select("span.label").text()
|
||||||
|
return if (temp.contains(Regex("\\d"))) {
|
||||||
|
val episode = Regex("(\\((\\d+))|(\\s(\\d+))").find(temp)?.groupValues?.map { num ->
|
||||||
|
num.replace(Regex("\\(|\\s"), "")
|
||||||
|
}?.distinct()?.firstOrNull()?.toIntOrNull()
|
||||||
|
newAnimeSearchResponse(title, href, TvType.TvSeries) {
|
||||||
|
this.posterUrl = posterUrl
|
||||||
|
addSub(episode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val quality =
|
||||||
|
temp.replace(Regex("(-.*)|(\\|.*)|(?i)(VietSub.*)|(?i)(Thuyết.*)"), "").trim()
|
||||||
|
newMovieSearchResponse(title, href, TvType.Movie) {
|
||||||
|
this.posterUrl = posterUrl
|
||||||
|
addQuality(quality)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
|
val link = "$mainUrl/tim-kiem/$query"
|
||||||
|
val document = app.get(link).document
|
||||||
|
|
||||||
|
return document.select("ul.list-film li").map {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(url: String): LoadResponse {
|
||||||
|
val document = app.get(url).document
|
||||||
|
|
||||||
|
val title = document.selectFirst("h1[itemprop=name]")?.text()?.trim().toString()
|
||||||
|
val link = document.select("ul.list-button li:last-child a").attr("href")
|
||||||
|
val poster = document.selectFirst("div.image img[itemprop=image]")?.attr("src")
|
||||||
|
val tags = document.select("ul.entry-meta.block-film li:nth-child(4) a").map { it.text() }
|
||||||
|
val year = document.select("ul.entry-meta.block-film li:nth-child(2) a").text().trim()
|
||||||
|
.toIntOrNull()
|
||||||
|
val tvType = if (document.select("div.latest-episode").isNotEmpty()
|
||||||
|
) TvType.TvSeries else TvType.Movie
|
||||||
|
val description = document.select("div#film-content-wrapper").text().trim()
|
||||||
|
val trailer =
|
||||||
|
document.select("div#trailer script").last()?.data()?.substringAfter("file: \"")
|
||||||
|
?.substringBefore("\",")
|
||||||
|
val rating =
|
||||||
|
document.select("ul.entry-meta.block-film li:nth-child(7) span").text().toRatingInt()
|
||||||
|
val actors = document.select("ul.entry-meta.block-film li:last-child a").map { it.text() }
|
||||||
|
val recommendations = document.select("ul#list-film-realted li.item").map {
|
||||||
|
it.toSearchResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (tvType == TvType.TvSeries) {
|
||||||
|
val docEpisodes = app.get(link).document
|
||||||
|
val episodes = docEpisodes.select("ul#list_episodes > li").map {
|
||||||
|
val href = it.select("a").attr("href")
|
||||||
|
val episode =
|
||||||
|
it.select("a").text().replace(Regex("[^0-9]"), "").trim().toIntOrNull()
|
||||||
|
val name = "Episode $episode"
|
||||||
|
Episode(
|
||||||
|
data = href,
|
||||||
|
name = name,
|
||||||
|
episode = episode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.year = year
|
||||||
|
this.plot = description
|
||||||
|
this.tags = tags
|
||||||
|
this.rating = rating
|
||||||
|
addActors(actors)
|
||||||
|
this.recommendations = recommendations
|
||||||
|
addTrailer(trailer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newMovieLoadResponse(title, url, TvType.Movie, link) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.year = year
|
||||||
|
this.plot = description
|
||||||
|
this.tags = tags
|
||||||
|
this.rating = rating
|
||||||
|
addActors(actors)
|
||||||
|
this.recommendations = recommendations
|
||||||
|
addTrailer(trailer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
|
val document = app.get(data).document
|
||||||
|
|
||||||
|
val key = document.select("div#content script").mapNotNull { script ->
|
||||||
|
if (script.data().contains("filmInfo.episodeID =")) {
|
||||||
|
val id = script.data().substringAfter("filmInfo.episodeID = parseInt('")
|
||||||
|
.substringBefore("');")
|
||||||
|
app.post(
|
||||||
|
url = "$mainUrl/pmplayer.php",
|
||||||
|
data = mapOf("qcao" to id),
|
||||||
|
referer = data,
|
||||||
|
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||||
|
).text.substringAfterLast("iniPlayers(\"").substringBefore("\",")
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.first()
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
Pair("https://so-trym.topphimmoi.org/hlspm/$key", "PMFAST"),
|
||||||
|
Pair("https://dash.megacdn.xyz/hlspm/$key", "PMHLS"),
|
||||||
|
Pair("https://dash.megacdn.xyz/dast/$key/index.m3u8", "PMBK")
|
||||||
|
).apmap { (link, source) ->
|
||||||
|
safeApiCall {
|
||||||
|
if (source == "PMBK") {
|
||||||
|
callback.invoke(
|
||||||
|
ExtractorLink(
|
||||||
|
source,
|
||||||
|
source,
|
||||||
|
link,
|
||||||
|
referer = "$mainUrl/",
|
||||||
|
quality = Qualities.P1080.value,
|
||||||
|
isM3u8 = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val playList = app.get(link, referer = "$mainUrl/")
|
||||||
|
.parsedSafe<ResponseM3u>()?.main?.segments?.map { segment ->
|
||||||
|
PlayListItem(
|
||||||
|
segment.link,
|
||||||
|
(segment.du.toFloat() * 1_000_000).toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.invoke(
|
||||||
|
ExtractorLinkPlayList(
|
||||||
|
source,
|
||||||
|
source,
|
||||||
|
playList ?: return@safeApiCall,
|
||||||
|
referer = "$mainUrl/",
|
||||||
|
quality = Qualities.P1080.value,
|
||||||
|
headers = mapOf(
|
||||||
|
// "If-None-Match" to "*",
|
||||||
|
"Origin" to mainUrl,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Segment(
|
||||||
|
@JsonProperty("du") val du: String,
|
||||||
|
@JsonProperty("link") val link: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DataM3u(
|
||||||
|
@JsonProperty("segments") val segments: List<Segment>?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ResponseM3u(
|
||||||
|
@JsonProperty("2048p") val main: DataM3u?,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -81,7 +81,7 @@ class RebahinProvider : MainAPI() {
|
||||||
this.select("div.mli-eps > span").text().replace(Regex("[^0-9]"), "").toIntOrNull()
|
this.select("div.mli-eps > span").text().replace(Regex("[^0-9]"), "").toIntOrNull()
|
||||||
newAnimeSearchResponse(title, href, TvType.TvSeries) {
|
newAnimeSearchResponse(title, href, TvType.TvSeries) {
|
||||||
this.posterUrl = posterUrl
|
this.posterUrl = posterUrl
|
||||||
addDubStatus(dubExist = false, subExist = true, subEpisodes = episode)
|
addSub(episode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,19 +114,18 @@ class RebahinProvider : MainAPI() {
|
||||||
.ownText().replace(Regex("[^0-9]"), "").toIntOrNull()
|
.ownText().replace(Regex("[^0-9]"), "").toIntOrNull()
|
||||||
val actors = document.select("span[itemprop=actor] > a").map { it.select("span").text() }
|
val actors = document.select("span[itemprop=actor] > a").map { it.select("span").text() }
|
||||||
|
|
||||||
|
val baseLink = fixUrl(document.select("div#mv-info > a").attr("href").toString())
|
||||||
|
|
||||||
return if (tvType == TvType.TvSeries) {
|
return if (tvType == TvType.TvSeries) {
|
||||||
val baseLink = document.select("div#mv-info > a").attr("href")
|
|
||||||
val episodes = app.get(baseLink).document.select("div#list-eps > a").map {
|
val episodes = app.get(baseLink).document.select("div#list-eps > a").map {
|
||||||
val name = it.text().replace(Regex("Server\\s?\\d"), "").trim()
|
Pair(it.text(), it.attr("data-iframe"))
|
||||||
name
|
}.groupBy { it.first }.map { eps ->
|
||||||
}.distinct().map {
|
Episode(
|
||||||
val name = it
|
data = eps.value.map { fixUrl(base64Decode(it.second)) }.toString(),
|
||||||
val epNum = it.replace(Regex("[^0-9]"), "").toIntOrNull()
|
name = eps.key,
|
||||||
val link = "$baseLink?ep=$epNum"
|
episode = eps.key.filter { it.isDigit() }.toIntOrNull()
|
||||||
newEpisode(link) {
|
)
|
||||||
this.name = name
|
|
||||||
this.episode = epNum
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||||
this.posterUrl = poster
|
this.posterUrl = poster
|
||||||
|
@ -139,8 +138,12 @@ class RebahinProvider : MainAPI() {
|
||||||
addTrailer(trailer)
|
addTrailer(trailer)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val episodes = document.select("div#mv-info > a").attr("href")
|
val links =
|
||||||
newMovieLoadResponse(title, url, TvType.Movie, episodes) {
|
app.get(baseLink).document.select("div#server-list div.server-wrapper div[id*=episode]")
|
||||||
|
.map {
|
||||||
|
fixUrl(base64Decode(it.attr("data-iframe")))
|
||||||
|
}.toString()
|
||||||
|
newMovieLoadResponse(title, url, TvType.Movie, links) {
|
||||||
this.posterUrl = poster
|
this.posterUrl = poster
|
||||||
this.year = year
|
this.year = year
|
||||||
this.plot = description
|
this.plot = description
|
||||||
|
@ -153,18 +156,6 @@ class RebahinProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class ResponseLocal(
|
|
||||||
@JsonProperty("file") val file: String,
|
|
||||||
@JsonProperty("label") val label: String,
|
|
||||||
@JsonProperty("type") val type: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class Tracks(
|
|
||||||
@JsonProperty("file") val file: String,
|
|
||||||
@JsonProperty("label") val label: String?,
|
|
||||||
@JsonProperty("kind") val kind: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
private suspend fun invokeLokalSource(
|
private suspend fun invokeLokalSource(
|
||||||
url: String,
|
url: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -182,7 +173,8 @@ class RebahinProvider : MainAPI() {
|
||||||
document.select("script").map { script ->
|
document.select("script").map { script ->
|
||||||
if (script.data().contains("sources: [")) {
|
if (script.data().contains("sources: [")) {
|
||||||
val source = tryParseJson<ResponseLocal>(
|
val source = tryParseJson<ResponseLocal>(
|
||||||
script.data().substringAfter("sources: [").substringBefore("],"))
|
script.data().substringAfter("sources: [").substringBefore("],")
|
||||||
|
)
|
||||||
val m3uData = app.get(source!!.file, referer = ref).text
|
val m3uData = app.get(source!!.file, referer = ref).text
|
||||||
val quality = Regex("\\d{3,4}\\.m3u8").findAll(m3uData).map { it.value }.toList()
|
val quality = Regex("\\d{3,4}\\.m3u8").findAll(m3uData).map { it.value }.toList()
|
||||||
|
|
||||||
|
@ -213,28 +205,6 @@ class RebahinProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class Captions(
|
|
||||||
@JsonProperty("id") val id: String,
|
|
||||||
@JsonProperty("hash") val hash: String,
|
|
||||||
@JsonProperty("language") val language: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class Data(
|
|
||||||
@JsonProperty("file") val file: String,
|
|
||||||
@JsonProperty("label") val label: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class Player(
|
|
||||||
@JsonProperty("poster_file") val poster_file: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class ResponseKotakAjair(
|
|
||||||
@JsonProperty("success") val success: Boolean,
|
|
||||||
@JsonProperty("player") val player: Player,
|
|
||||||
@JsonProperty("data") val data: List<Data>?,
|
|
||||||
@JsonProperty("captions") val captions: List<Captions>?
|
|
||||||
)
|
|
||||||
|
|
||||||
private suspend fun invokeKotakAjairSource(
|
private suspend fun invokeKotakAjairSource(
|
||||||
url: String,
|
url: String,
|
||||||
subCallback: (SubtitleFile) -> Unit,
|
subCallback: (SubtitleFile) -> Unit,
|
||||||
|
@ -277,45 +247,26 @@ class RebahinProvider : MainAPI() {
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
|
||||||
val sources = if (data.contains("/play")) {
|
data.removeSurrounding("[", "]").split(",").map { it.trim() }.apmap { link ->
|
||||||
app.get(data).document.select(".server-wrapper").mapNotNull {
|
|
||||||
val iframeLink =
|
|
||||||
"${mainUrl}/iembed/?source=${it.selectFirst("div.server")?.attr("data-iframe")}"
|
|
||||||
app.get(iframeLink).document.select("iframe").attr("src")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val fixData = Regex("(.*?)\\?ep").find(data)?.groupValues?.get(1).toString()
|
|
||||||
val document = app.get(fixData).document
|
|
||||||
val ep = Regex("\\?ep=([0-9]+)").find(data)?.groupValues?.get(1).toString()
|
|
||||||
val title = document.selectFirst("div#list-eps > a")?.text()?.replace(Regex("[\\d]"), "")
|
|
||||||
?.trim()?.replace("Server", "")?.trim()
|
|
||||||
document.select("div#list-eps > a:matches(${title}\\s?${ep}$)").mapNotNull {
|
|
||||||
val iframeLink = "${mainUrl}/iembed/?source=${it.attr("data-iframe")}"
|
|
||||||
app.get(iframeLink).document.select("iframe")
|
|
||||||
.attr("src")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sources.apmap {
|
|
||||||
safeApiCall {
|
safeApiCall {
|
||||||
when {
|
when {
|
||||||
it.startsWith("http://172.96.161.72") -> invokeLokalSource(
|
link.startsWith("http://172.96.161.72") -> invokeLokalSource(
|
||||||
it,
|
link,
|
||||||
this.name,
|
this.name,
|
||||||
"http://172.96.161.72/",
|
"http://172.96.161.72/",
|
||||||
subtitleCallback,
|
subtitleCallback,
|
||||||
callback
|
callback
|
||||||
)
|
)
|
||||||
it.startsWith("https://kotakajair.xyz") -> invokeKotakAjairSource(
|
link.startsWith("https://kotakajair.xyz") -> invokeKotakAjairSource(
|
||||||
it,
|
link,
|
||||||
subtitleCallback,
|
subtitleCallback,
|
||||||
callback
|
callback
|
||||||
)
|
)
|
||||||
else -> {
|
else -> {
|
||||||
loadExtractor(it, data, callback)
|
loadExtractor(link, "$mainUrl/", callback)
|
||||||
if (it.startsWith("https://sbfull.com")) {
|
if (link.startsWith("https://sbfull.com")) {
|
||||||
val response = app.get(
|
val response = app.get(
|
||||||
it, interceptor = WebViewResolver(
|
link, interceptor = WebViewResolver(
|
||||||
Regex("""\.srt""")
|
Regex("""\.srt""")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -334,5 +285,39 @@ class RebahinProvider : MainAPI() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class ResponseLocal(
|
||||||
|
@JsonProperty("file") val file: String,
|
||||||
|
@JsonProperty("label") val label: String,
|
||||||
|
@JsonProperty("type") val type: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class Tracks(
|
||||||
|
@JsonProperty("file") val file: String,
|
||||||
|
@JsonProperty("label") val label: String?,
|
||||||
|
@JsonProperty("kind") val kind: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class Captions(
|
||||||
|
@JsonProperty("id") val id: String,
|
||||||
|
@JsonProperty("hash") val hash: String,
|
||||||
|
@JsonProperty("language") val language: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class Data(
|
||||||
|
@JsonProperty("file") val file: String,
|
||||||
|
@JsonProperty("label") val label: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class Player(
|
||||||
|
@JsonProperty("poster_file") val poster_file: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class ResponseKotakAjair(
|
||||||
|
@JsonProperty("success") val success: Boolean,
|
||||||
|
@JsonProperty("player") val player: Player,
|
||||||
|
@JsonProperty("data") val data: List<Data>?,
|
||||||
|
@JsonProperty("captions") val captions: List<Captions>?
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
package com.lagradost.cloudstream3.movieproviders
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class UakinoProvider : MainAPI() {
|
||||||
|
override var mainUrl = "https://uakino.club"
|
||||||
|
override var name = "Uakino"
|
||||||
|
override val hasMainPage = true
|
||||||
|
override var lang = "uk"
|
||||||
|
override val hasDownloadSupport = true
|
||||||
|
override val supportedTypes = setOf(
|
||||||
|
TvType.Movie,
|
||||||
|
TvType.TvSeries,
|
||||||
|
TvType.Anime
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getMainPage(): HomePageResponse {
|
||||||
|
val document = app.get(mainUrl).document
|
||||||
|
|
||||||
|
val homePageList = ArrayList<HomePageList>()
|
||||||
|
|
||||||
|
document.select("div.main-section-inner").forEach { block ->
|
||||||
|
val header = block.selectFirst("p.sidebar-title")?.text()?.trim().toString()
|
||||||
|
val items = block.select("div.owl-item, div.movie-item").map {
|
||||||
|
it.toSearchResponse()
|
||||||
|
}
|
||||||
|
if (items.isNotEmpty()) homePageList.add(HomePageList(header, items))
|
||||||
|
}
|
||||||
|
|
||||||
|
return HomePageResponse(homePageList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Element.toSearchResponse(): SearchResponse {
|
||||||
|
val title = this.selectFirst("a.movie-title")?.text()?.trim().toString()
|
||||||
|
val href = this.selectFirst("a.movie-title")?.attr("href").toString()
|
||||||
|
val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src"))
|
||||||
|
return newMovieSearchResponse(title, href, TvType.Movie) {
|
||||||
|
this.posterUrl = posterUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
|
val document = app.post(
|
||||||
|
url = mainUrl,
|
||||||
|
data = mapOf(
|
||||||
|
"do" to "search",
|
||||||
|
"subaction" to "search",
|
||||||
|
"story" to query.replace(" ", "+")
|
||||||
|
)
|
||||||
|
).document
|
||||||
|
return document.select("div.movie-item.short-item").map {
|
||||||
|
it.toSearchResponse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(url: String): LoadResponse {
|
||||||
|
val document = app.get(url).document
|
||||||
|
|
||||||
|
val title = document.selectFirst("h1 span.solototle")?.text()?.trim().toString()
|
||||||
|
val poster = fixUrl(document.selectFirst("div.film-poster img")?.attr("src").toString())
|
||||||
|
val tags = document.select("div.film-info > div:nth-child(4) a").map { it.text() }
|
||||||
|
val year = document.select("div.film-info > div:nth-child(2) a").text().toIntOrNull()
|
||||||
|
val tvType = if (url.contains(Regex("(/anime-series)|(/seriesss)|(/cartoonseries)"))) TvType.TvSeries else TvType.Movie
|
||||||
|
val description = document.selectFirst("div[itemprop=description]")?.text()?.trim()
|
||||||
|
val trailer = document.selectFirst("iframe#pre")?.attr("data-src")
|
||||||
|
val rating = document.selectFirst("div.film-info > div:nth-child(8) div.fi-desc")?.text()
|
||||||
|
?.substringBefore("/").toRatingInt()
|
||||||
|
val actors = document.select("div.film-info > div:nth-child(6) a").map { it.text() }
|
||||||
|
|
||||||
|
val recommendations = document.select("div#full-slides div.owl-item").map {
|
||||||
|
it.toSearchResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (tvType == TvType.TvSeries) {
|
||||||
|
val id = url.split("/").last().split("-").first()
|
||||||
|
val episodes = app.get("$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}")
|
||||||
|
.parsedSafe<Responses>()?.response.let {
|
||||||
|
Jsoup.parse(it.toString()).select("ul > li").mapNotNull { eps ->
|
||||||
|
val href = fixUrl(eps.attr("data-file"))
|
||||||
|
val name = eps.text().trim()
|
||||||
|
if (href.isNotEmpty()) {
|
||||||
|
Episode(
|
||||||
|
href,
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.year = year
|
||||||
|
this.plot = description
|
||||||
|
this.tags = tags
|
||||||
|
this.rating = rating
|
||||||
|
addActors(actors)
|
||||||
|
this.recommendations = recommendations
|
||||||
|
addTrailer(trailer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newMovieLoadResponse(title, url, TvType.Movie, url) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.year = year
|
||||||
|
this.plot = description
|
||||||
|
this.tags = tags
|
||||||
|
this.rating = rating
|
||||||
|
addActors(actors)
|
||||||
|
this.recommendations = recommendations
|
||||||
|
addTrailer(trailer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
|
val links = ArrayList<String>()
|
||||||
|
|
||||||
|
if (data.startsWith("https://ashdi.vip")) {
|
||||||
|
links.add(data)
|
||||||
|
} else {
|
||||||
|
val iframeUrl = app.get(data).document.selectFirst("iframe#pre")?.attr("src")
|
||||||
|
if (iframeUrl.isNullOrEmpty()) {
|
||||||
|
val id = data.split("/").last().split("-").first()
|
||||||
|
app.get("$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}")
|
||||||
|
.parsedSafe<Responses>()?.response.let {
|
||||||
|
Jsoup.parse(it.toString()).select("ul > li").mapNotNull { mirror ->
|
||||||
|
links.add(fixUrl(mirror.attr("data-file")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
links.add(iframeUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
links.apmap { link ->
|
||||||
|
safeApiCall {
|
||||||
|
app.get(link, referer = "$mainUrl/").document.select("script").map { script ->
|
||||||
|
if (script.data().contains("var player = new Playerjs({")) {
|
||||||
|
val m3uLink =
|
||||||
|
script.data().substringAfterLast("file:\"").substringBefore("\",")
|
||||||
|
M3u8Helper.generateM3u8(
|
||||||
|
source = this.name,
|
||||||
|
streamUrl = m3uLink,
|
||||||
|
referer = "https://ashdi.vip/"
|
||||||
|
).forEach(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Responses(
|
||||||
|
@JsonProperty("success") val success: Boolean?,
|
||||||
|
@JsonProperty("response") val response: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -11,8 +11,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
||||||
val malApi = MALApi(0)
|
val malApi = MALApi(0)
|
||||||
val aniListApi = AniListApi(0)
|
val aniListApi = AniListApi(0)
|
||||||
val openSubtitlesApi = OpenSubtitlesApi(0)
|
val openSubtitlesApi = OpenSubtitlesApi(0)
|
||||||
// Removed because of cloudflare
|
val indexSubtitlesApi = IndexSubtitleApi()
|
||||||
// val indexSubtitlesApi = IndexSubtitleApi()
|
|
||||||
val nginxApi = NginxApi(0)
|
val nginxApi = NginxApi(0)
|
||||||
|
|
||||||
// used to login via app intent
|
// used to login via app intent
|
||||||
|
@ -39,8 +38,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
||||||
val subtitleProviders
|
val subtitleProviders
|
||||||
get() = listOf(
|
get() = listOf(
|
||||||
openSubtitlesApi,
|
openSubtitlesApi,
|
||||||
// Removed because of cloudflare
|
indexSubtitlesApi
|
||||||
// indexSubtitlesApi
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const val appString = "cloudstreamapp"
|
const val appString = "cloudstreamapp"
|
||||||
|
|
|
@ -282,6 +282,8 @@ val extractorApis: Array<ExtractorApi> = arrayOf(
|
||||||
|
|
||||||
Filesim(),
|
Filesim(),
|
||||||
|
|
||||||
|
Linkbox(),
|
||||||
|
|
||||||
YoutubeExtractor(),
|
YoutubeExtractor(),
|
||||||
YoutubeShortLinkExtractor(),
|
YoutubeShortLinkExtractor(),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue