From b970ca06680fa8ca928a1a695d5754e1ad8b9107 Mon Sep 17 00:00:00 2001 From: Hexated <37908684+hexated@users.noreply.github.com> Date: Sat, 16 Jul 2022 22:10:11 +0700 Subject: [PATCH] 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> --- .../com/lagradost/cloudstream3/MainAPI.kt | 4 + .../animeproviders/KuramanimeProvider.kt | 88 ++-- .../animeproviders/NontonAnimeIDProvider.kt | 28 +- .../animeproviders/OploverzProvider.kt | 5 +- .../cloudstream3/extractors/Linkbox.kt | 46 ++ .../movieproviders/HDrezkaProvider.kt | 410 ++++++++++++++++++ .../movieproviders/IdlixProvider.kt | 2 +- .../movieproviders/PhimmoichillProvider.kt | 217 +++++++++ .../movieproviders/RebahinProvider.kt | 137 +++--- .../movieproviders/UakinoProvider.kt | 175 ++++++++ .../syncproviders/AccountManager.kt | 6 +- .../cloudstream3/utils/ExtractorApi.kt | 2 + 12 files changed, 978 insertions(+), 142 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDrezkaProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/PhimmoichillProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/UakinoProvider.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 63d81f8c..b6b9b6ec 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -101,6 +101,10 @@ object APIHolder { IdlixProvider(), MultiplexProvider(), VidSrcProvider(), + UakinoProvider(), + PhimmoichillProvider(), + HDrezkaProvider(), + // Metadata providers //TmdbProvider(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt index ce560949..a6b3fb27 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.animeproviders import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.network.DdosGuardKiller import com.lagradost.cloudstream3.utils.ExtractorLink import org.jsoup.Jsoup @@ -42,30 +43,22 @@ class KuramanimeProvider : MainAPI() { val homePageList = ArrayList() - document.select("div[class*=__product]").forEach { block -> - val header = block.select(".section-title > h4").text() - val animes = block.select("div.col-lg-4.col-md-6.col-sm-6").mapNotNull { + document.select("div.trending__product").forEach { block -> + val header = block.selectFirst("h4")?.text().toString() + val animes = block.select("div.col-lg-4.col-md-6.col-sm-6").map { it.toSearchResult() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) } - document.select("#topAnimesSection").forEach { block -> - val header = block.previousElementSibling()!!.select("h5").text().trim() - val animes = block.select("a").mapNotNull { + document.select("div.product__sidebar__view").forEach { block -> + val header = block.selectFirst("h5")?.text().toString() + val animes = block.select("div.product__sidebar__comment__item").map { it.toSearchResultView() } 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) } @@ -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 title = this.select(".product__item__text > h5 > a").text() - val posterUrl = fixUrl(this.select(".product__item__pic.set-bg").attr("data-setbg")) - val type = getType(this.selectFirst(".product__item__text > ul > li")!!.text()) - - return newAnimeSearchResponse(title, href, type) { - 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")) + val title = this.selectFirst("h5 a")?.text().toString() + val posterUrl = fixUrl(this.select("div.product__item__pic.set-bg").attr("data-setbg")) + val episode = Regex("([0-9*])\\s?/").find( + this.select("div.ep span").text() + )?.groupValues?.getOrNull(1)?.toIntOrNull() return newAnimeSearchResponse(title, href, TvType.Anime) { this.posterUrl = posterUrl - addDubStatus(dubExist = false, subExist = true) + addSub(episode) } } - private fun Element.toSearchResultComment(): SearchResponse { - val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href"))) - val title = this.selectFirst("h5")!!.text() - val posterUrl = fixUrl(this.select("img").attr("src")) - val type = getType(this.selectFirst("ul > li")!!.text()) + private fun Element.toSearchResultView(): AnimeSearchResponse { + val href = getProperAnimeLink(fixUrl(this.selectFirst("a")?.attr("href").toString())) + val title = this.selectFirst("h5")?.text()?.trim().toString() + val posterUrl = fixUrlNull( + 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 - addDubStatus(dubExist = false, subExist = true) + addSub(episode) } } @@ -190,19 +176,21 @@ class KuramanimeProvider : MainAPI() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ): Boolean { - val servers = app.get(data, interceptor = DdosGuardKiller(true)).document + val servers = app.get(data).document servers.select("video#player > source").map { - val url = it.attr("src") - val quality = it.attr("size").toInt() - callback.invoke( - ExtractorLink( - name, - name, - url, - referer = "$mainUrl/", - quality = quality + suspendSafeApiCall { + val url = it.attr("src") + val quality = it.attr("size").toInt() + callback.invoke( + ExtractorLink( + name, + name, + url, + referer = "$mainUrl/", + quality = quality + ) ) - ) + } } return true diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt index 94a82436..6dec98f0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt @@ -68,16 +68,26 @@ class NontonAnimeIDProvider : MainAPI() { return if (uri.contains("/anime/")) { uri } else { - val name = Regex("$mainUrl/(.*)-episode.*").find(uri)?.groupValues?.get(1).toString() - if (name.contains("movie")) { - return "$mainUrl/anime/" + name.replace("-movie", "") - } else { - if (name.contains("kokurasetai-season-3")) { - "$mainUrl/anime/${name.replace("season-3", "ultra-romantic")}" - } else { - "$mainUrl/anime/$name" - } + var title = uri.substringAfter("$mainUrl/") + val fixTitle = Regex("(.*)-episode.*").find(title)?.groupValues?.getOrNull(1).toString() + title = when { + title.contains("utawarerumono-season-3") -> fixTitle.replace( + "season-3", + "futari-no-hakuoro" + ) + 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" } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt index 7668b151..f29d3685 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt @@ -69,6 +69,7 @@ class OploverzProvider : MainAPI() { )?.groupValues?.get(1).toString() (title.contains("-ova")) -> Regex("(.+)-ova").find(title)?.groupValues?.get(1) .toString() + (title.contains("-movie")) -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1).toString() else -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1).toString() .replace(Regex("-\\d+"), "") } @@ -136,9 +137,9 @@ class OploverzProvider : MainAPI() { val typeCheck = when (document.select(".info-content > .spe > span:nth-child(5), .info-content > .spe > span") .text().trim()) { - "TV" -> "TV" + "OVA" -> "OVA" "Movie" -> "Movie" - else -> "OVA" + else -> "TV" } val type = getType(typeCheck) val description = document.select(".entry-content > p").text().trim() diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt new file mode 100644 index 00000000..52fc5532 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt @@ -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 { + val id = url.substringAfter("id=") + val sources = mutableListOf() + + app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe()?.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?, + ) + + data class Responses( + @JsonProperty("data") val data: Data?, + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDrezkaProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDrezkaProvider.kt new file mode 100644 index 00000000..a039cb77 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDrezkaProvider.kt @@ -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() + + 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 { + 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()?.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() + val server = ArrayList>() + + 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, item: Int): List { + val trash = ArrayList>() + for (i in 1..item) { + trash.add(arr) + } + return trash.reduce { acc, list -> + val temp = ArrayList() + 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)?.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("{$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()?.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?, + @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?, + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/IdlixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/IdlixProvider.kt index 6d37e994..dec6bf17 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/IdlixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/IdlixProvider.kt @@ -12,7 +12,7 @@ import java.net.URI import java.util.ArrayList class IdlixProvider : MainAPI() { - override var mainUrl = "https://idlix.xn--6frz82g" + override var mainUrl = "https://94.103.82.88" override var name = "Idlix" override val hasMainPage = true override var lang = "id" diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PhimmoichillProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PhimmoichillProvider.kt new file mode 100644 index 00000000..38862076 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PhimmoichillProvider.kt @@ -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() + + 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 { + 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()?.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?, + ) + + data class ResponseM3u( + @JsonProperty("2048p") val main: DataM3u?, + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt index 196a7798..84dae89c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt @@ -81,7 +81,7 @@ class RebahinProvider : MainAPI() { this.select("div.mli-eps > span").text().replace(Regex("[^0-9]"), "").toIntOrNull() newAnimeSearchResponse(title, href, TvType.TvSeries) { 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() 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) { - val baseLink = document.select("div#mv-info > a").attr("href") val episodes = app.get(baseLink).document.select("div#list-eps > a").map { - val name = it.text().replace(Regex("Server\\s?\\d"), "").trim() - name - }.distinct().map { - val name = it - val epNum = it.replace(Regex("[^0-9]"), "").toIntOrNull() - val link = "$baseLink?ep=$epNum" - newEpisode(link) { - this.name = name - this.episode = epNum - } + Pair(it.text(), it.attr("data-iframe")) + }.groupBy { it.first }.map { eps -> + Episode( + data = eps.value.map { fixUrl(base64Decode(it.second)) }.toString(), + name = eps.key, + episode = eps.key.filter { it.isDigit() }.toIntOrNull() + ) + } newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { this.posterUrl = poster @@ -139,8 +138,12 @@ class RebahinProvider : MainAPI() { addTrailer(trailer) } } else { - val episodes = document.select("div#mv-info > a").attr("href") - newMovieLoadResponse(title, url, TvType.Movie, episodes) { + val links = + 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.year = year 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( url: String, name: String, @@ -182,7 +173,8 @@ class RebahinProvider : MainAPI() { document.select("script").map { script -> if (script.data().contains("sources: [")) { val source = tryParseJson( - script.data().substringAfter("sources: [").substringBefore("],")) + script.data().substringAfter("sources: [").substringBefore("],") + ) val m3uData = app.get(source!!.file, referer = ref).text 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?, - @JsonProperty("captions") val captions: List? - ) - private suspend fun invokeKotakAjairSource( url: String, subCallback: (SubtitleFile) -> Unit, @@ -277,45 +247,26 @@ class RebahinProvider : MainAPI() { callback: (ExtractorLink) -> Unit ): Boolean { - val sources = if (data.contains("/play")) { - 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 { + data.removeSurrounding("[", "]").split(",").map { it.trim() }.apmap { link -> safeApiCall { when { - it.startsWith("http://172.96.161.72") -> invokeLokalSource( - it, + link.startsWith("http://172.96.161.72") -> invokeLokalSource( + link, this.name, "http://172.96.161.72/", subtitleCallback, callback ) - it.startsWith("https://kotakajair.xyz") -> invokeKotakAjairSource( - it, + link.startsWith("https://kotakajair.xyz") -> invokeKotakAjairSource( + link, subtitleCallback, callback ) else -> { - loadExtractor(it, data, callback) - if (it.startsWith("https://sbfull.com")) { + loadExtractor(link, "$mainUrl/", callback) + if (link.startsWith("https://sbfull.com")) { val response = app.get( - it, interceptor = WebViewResolver( + link, interceptor = WebViewResolver( Regex("""\.srt""") ) ) @@ -334,5 +285,39 @@ class RebahinProvider : MainAPI() { 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?, + @JsonProperty("captions") val captions: List? + ) + } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/UakinoProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/UakinoProvider.kt new file mode 100644 index 00000000..eb48b8d8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/UakinoProvider.kt @@ -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() + + 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 { + 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()?.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() + + 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()?.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, + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt index fd68f940..d6540cce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt @@ -11,8 +11,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { val malApi = MALApi(0) val aniListApi = AniListApi(0) val openSubtitlesApi = OpenSubtitlesApi(0) - // Removed because of cloudflare -// val indexSubtitlesApi = IndexSubtitleApi() + val indexSubtitlesApi = IndexSubtitleApi() val nginxApi = NginxApi(0) // used to login via app intent @@ -39,8 +38,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { val subtitleProviders get() = listOf( openSubtitlesApi, - // Removed because of cloudflare -// indexSubtitlesApi + indexSubtitlesApi ) const val appString = "cloudstreamapp" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 58f5d411..b89ca004 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -282,6 +282,8 @@ val extractorApis: Array = arrayOf( Filesim(), + Linkbox(), + YoutubeExtractor(), YoutubeShortLinkExtractor(), )