From ed8164ca40e6a33f8c95d88fc487851bdcf0d433 Mon Sep 17 00:00:00 2001 From: Hexated <37908684+hexated@users.noreply.github.com> Date: Thu, 7 Jul 2022 01:15:48 +0700 Subject: [PATCH] NEW!! Providers (Indonesian) (#1151) * 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 Co-authored-by: Osten <11805592+LagradOst@users.noreply.github.com> --- .../com/lagradost/cloudstream3/MainAPI.kt | 11 +- .../animeproviders/AnimeIndoProvider.kt | 193 +++++++++ .../animeproviders/GomunimeProvider.kt | 7 +- .../animeproviders/KuronimeProvider.kt | 41 +- .../animeproviders/NeonimeProvider.kt | 58 +-- .../animeproviders/NontonAnimeIDProvider.kt | 16 +- .../animeproviders/OploverzProvider.kt | 26 +- .../animeproviders/OtakudesuProvider.kt | 199 +++++++++ .../cloudstream3/extractors/Filesim.kt | 38 ++ .../cloudstream3/extractors/Hxfile.kt | 23 +- .../cloudstream3/extractors/JWPlayer.kt | 81 ++++ .../cloudstream3/extractors/YourUpload.kt | 47 +++ .../movieproviders/DramaidProvider.kt | 2 +- .../movieproviders/IdlixProvider.kt | 378 ++++++++++++++++++ .../movieproviders/LayarKaca21Provider.kt | 27 +- .../movieproviders/MultiplexProvider.kt | 206 ++++++++++ .../movieproviders/RebahinProvider.kt | 22 +- .../providers/IndexSubtitleApi.kt | 252 ++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 12 + 19 files changed, 1532 insertions(+), 107 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeIndoProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/animeproviders/OtakudesuProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/JWPlayer.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/IdlixProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/movieproviders/MultiplexProvider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index e7adfb7b..e2e052f6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -97,7 +97,9 @@ object APIHolder { RebahinProvider(), LayarKacaProvider(), HDTodayProvider(), - OpenVidsProvider(), + OpenVidsProvider(), + IdlixProvider(), + MultiplexProvider(), // Metadata providers //TmdbProvider(), @@ -132,6 +134,8 @@ object APIHolder { GomunimeProvider(), NontonAnimeIDProvider(), KuronimeProvider(), + OtakudesuProvider(), + AnimeIndoProvider(), //MultiAnimeProvider(), NginxProvider(), OlgplyProvider(), @@ -543,6 +547,11 @@ fun capitalizeStringNullable(str: String?): String? { } } +fun fixTitle(str: String): String { + return str.split(" ").joinToString(" ") { it.lowercase() + .replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it } } +} + /** https://www.imdb.com/title/tt2861424/ -> tt2861424 */ fun imdbUrlToId(url: String): String? { return Regex("/title/(tt[0-9]*)").find(url)?.groupValues?.get(1) diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeIndoProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeIndoProvider.kt new file mode 100644 index 00000000..38d2efb0 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeIndoProvider.kt @@ -0,0 +1,193 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.APIHolder.getCaptchaToken +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import com.lagradost.nicehttp.NiceResponse +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import java.util.ArrayList + +class AnimeIndoProvider : MainAPI() { + override var mainUrl = "https://animeindo.sbs" + override var name = "AnimeIndo" + override val hasMainPage = true + override var lang = "id" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA") || t.contains("Special")) TvType.OVA + else if (t.contains("Movie")) TvType.AnimeMovie + else TvType.Anime + } + + fun getStatus(t: String): ShowStatus { + return when (t) { + "Finished Airing" -> ShowStatus.Completed + "Currently Airing" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + + private suspend fun request(url: String): NiceResponse { + val req = app.get( + url, + cookies = mapOf("recaptcha_cookie" to "#Asia/Jakarta#-420#win32#Windows#0,false,false#Google Inc. (Intel)~ANGLE (Intel, Intel(R) HD Graphics 400 Direct3D11 vs_5_0 ps_5_0)") + ) + if (req.isSuccessful) { + return req + } else { + val document = app.get(url).document + val captchaKey = + document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") + .attr("src").substringAfter("render=").substringBefore("&") + val token = getCaptchaToken(url, captchaKey) + return app.post( + url, + data = mapOf( + "action" to "recaptcha_for_all", + "token" to "$token", + "sitekey" to captchaKey + ) + ) + } + } + } + + override suspend fun getMainPage(): HomePageResponse { + val document = request(mainUrl).document + + val homePageList = ArrayList() + + document.select("div.widget_senction").forEach { block -> + val header = block.selectFirst("div.widget-title > h3")!!.text().trim() + val items = block.select("div.post-show > article").map { + it.toSearchResult() + } + if (items.isNotEmpty()) homePageList.add(HomePageList(header, items)) + } + + return HomePageResponse(homePageList) + } + + private fun getProperAnimeLink(uri: String): String { + return if (uri.contains("/anime/")) { + uri + } else { + var title = uri.substringAfter("$mainUrl/") + title = when { + (title.contains("-episode")) && !(title.contains("-movie")) -> Regex("(.+)-episode").find( + title + )?.groupValues?.get(1).toString() + (title.contains("-movie")) -> Regex("(.+)-movie").find(title)?.groupValues?.get( + 1 + ).toString() + else -> title + } + "$mainUrl/anime/$title" + } + } + + private fun Element.toSearchResult(): AnimeSearchResponse { + val title = this.selectFirst("div.title")!!.text().trim() + val href = getProperAnimeLink(this.selectFirst("a")!!.attr("href")) + val posterUrl = this.select("img[itemprop=image]").attr("src").toString() + val type = getType(this.select("div.type").text().trim()) + val epNum = + this.selectFirst("span.episode")?.ownText()?.replace(Regex("[^0-9]"), "")?.trim() + ?.toIntOrNull() + return newAnimeSearchResponse(title, href, type) { + this.posterUrl = posterUrl + addSub(epNum) + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query" + val document = request(link).document + + return document.select(".site-main.relat > article").map { + val title = it.selectFirst("div.title > h2")!!.ownText().trim() + val href = it.selectFirst("a")!!.attr("href") + val posterUrl = it.selectFirst("img")!!.attr("src").toString() + val type = getType(it.select("div.type").text().trim()) + newAnimeSearchResponse(title, href, type) { + this.posterUrl = posterUrl + } + } + } + + override suspend fun load(url: String): LoadResponse { + val document = request(url).document + + val title = document.selectFirst("h1.entry-title")?.text().toString().trim() + val poster = document.selectFirst("div.thumb > img[itemprop=image]")?.attr("src") + val tags = document.select("div.genxed > a").map { it.text() } + val type = getType( + document.selectFirst("div.info-content > div.spe > span:nth-child(6)")?.ownText() + .toString() + ) + val year = Regex("\\d, ([0-9]*)").find( + document.select("div.info-content > div.spe > span:nth-child(9) > time").text() + )?.groupValues?.get(1)?.toIntOrNull() + val status = getStatus( + document.selectFirst("div.info-content > div.spe > span:nth-child(1)")!!.ownText() + .trim() + ) + val description = document.select("div[itemprop=description] > p").text() + val trailer = document.selectFirst("div.player-embed iframe")?.attr("src") + val episodes = document.select("div.lstepsiode.listeps ul li").mapNotNull { + val header = it.selectFirst("span.lchx > a") ?: return@mapNotNull null + val name = header.text().trim() + val episode = header.text().trim().replace("Episode", "").trim().toIntOrNull() + val link = fixUrl(header.attr("href")) + Episode(link, name = name, episode = episode) + }.reversed() + + return newAnimeLoadResponse(title, url, type) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + this.tags = tags + addTrailer(trailer) + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = request(data).document + document.select("div.itemleft > .mirror > option").mapNotNull { + fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("src")) + }.apmap { + if (it.startsWith("https://uservideo.xyz")) { + app.get(it, referer = "$mainUrl/").document.select("iframe").attr("src") + } else { + it + } + }.apmap { + loadExtractor(it, data, callback) + } + + return true + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt index 08b935f7..06e1c60a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.animeproviders import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.AppUtils.parseJson @@ -14,7 +15,6 @@ import org.jsoup.Jsoup class GomunimeProvider : MainAPI() { override var mainUrl = "https://185.231.223.76" override var name = "Gomunime" - override val hasQuickSearch = false override val hasMainPage = true override var lang = "id" override val hasDownloadSupport = true @@ -75,7 +75,7 @@ class GomunimeProvider : MainAPI() { .toIntOrNull() newAnimeSearchResponse(title, href, type) { this.posterUrl = posterUrl - addDubStatus(dubExist = false, subExist = true, subEpisodes = epNum) + addSub(epNum) } } items.add(HomePageList(name, home)) @@ -139,7 +139,7 @@ class GomunimeProvider : MainAPI() { )?.groupValues?.get(1)?.toIntOrNull() val status = getStatus(document.selectFirst(".spe > span")!!.ownText()) val description = document.select("div[itemprop = description] > p").text() - + val trailer = document.selectFirst("div.embed-responsive noscript iframe")?.attr("src") val episodes = parseJson>( Regex("var episodelist = (\\[.*])").find( document.select(".bixbox.bxcl.epcheck > script").toString().trim() @@ -158,6 +158,7 @@ class GomunimeProvider : MainAPI() { showStatus = status plot = description this.tags = tags + addTrailer(trailer) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt index 5eec3a07..138ba021 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt @@ -10,7 +10,7 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Element class KuronimeProvider : MainAPI() { - override var mainUrl = "https://185.231.223.254" + override var mainUrl = "https://45.12.2.2" override var name = "Kuronime" override val hasQuickSearch = false override val hasMainPage = true @@ -46,7 +46,7 @@ class KuronimeProvider : MainAPI() { document.select(".bixbox").forEach { block -> val header = block.select(".releases > h3").text().trim() - val animes = block.select("article").mapNotNull { + val animes = block.select("article").map { it.toSearchResult() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) @@ -74,15 +74,15 @@ class KuronimeProvider : MainAPI() { } } - private fun Element.toSearchResult(): SearchResponse { - val href = getProperAnimeLink(fixUrl(this.select("a").attr("href"))) + private fun Element.toSearchResult(): AnimeSearchResponse { + val href = getProperAnimeLink(fixUrlNull(this.selectFirst("a")?.attr("href")).toString()) val title = this.select(".bsuxtt, .tt > h4").text().trim() - val posterUrl = fixUrl(this.select("img").attr("src")) + val posterUrl = fixUrlNull(this.selectFirst("div.view,div.bt")?.nextElementSibling()?.select("img")?.attr("data-src")) val epNum = this.select(".ep").text().replace(Regex("[^0-9]"), "").trim().toIntOrNull() - - return newAnimeSearchResponse(title, href, TvType.Anime) { + val tvType = getType(this.selectFirst(".bt > span")?.text().toString()) + return newAnimeSearchResponse(title, href, tvType) { this.posterUrl = posterUrl - addDubStatus(dubExist = false, subExist = true, subEpisodes = epNum) + addSub(epNum) } } @@ -91,16 +91,8 @@ class KuronimeProvider : MainAPI() { val link = "$mainUrl/?s=$query" val document = app.get(link).document - return document.select("article.bs").mapNotNull { - val title = it.selectFirst(".tt > h4")!!.text().trim() - val poster = it.select("img").attr("src") - val tvType = getType(it.selectFirst(".bt > span")?.text().toString()) - val href = getProperAnimeLink(fixUrl(it.selectFirst("a")!!.attr("href"))) - - newAnimeSearchResponse(title, href, tvType) { - this.posterUrl = poster - addDubStatus(dubExist = false, subExist = true) - } + return document.select("article.bs").map { + it.toSearchResult() } } @@ -108,14 +100,12 @@ class KuronimeProvider : MainAPI() { val document = app.get(url).document val title = document.selectFirst(".entry-title")?.text().toString().trim() - val poster = document.select("div[itemprop=image]").joinToString { - it.select("img").attr("src") - } + val poster = document.selectFirst("div.l[itemprop=image] > img")?.attr("data-src") val tags = document.select(".infodetail > ul > li:nth-child(2) > a").map { it.text() } val type = getType( document.selectFirst(".infodetail > ul > li:nth-child(7)")?.ownText()?.trim().toString() ) - val trailer = document.select("iframe.entered.lazyloaded").attr("src") + val trailer = document.selectFirst("div.tply iframe")?.attr("data-lazy-src") val year = Regex("\\d, ([0-9]*)").find( document.select(".infodetail > ul > li:nth-child(5)").text() )?.groupValues?.get(1)?.toIntOrNull() @@ -126,9 +116,10 @@ class KuronimeProvider : MainAPI() { val description = document.select("span.const > p").text() val episodes = document.select("div.bixbox.bxcl > ul > li").map { - val name = it.selectFirst("a")?.text()?.trim()?.replace("Episode", title) + val name = it.selectFirst("a")?.text()?.trim() + val episode = it.selectFirst("a")?.text()?.trim()?.replace("Episode", "")?.trim()?.toIntOrNull() val link = it.selectFirst("a")!!.attr("href") - Episode(link, name) + Episode(link, name = name, episode = episode) }.reversed() return newAnimeLoadResponse(title, url, type) { @@ -147,7 +138,7 @@ class KuronimeProvider : MainAPI() { url: String, sourceCallback: (ExtractorLink) -> Unit ) { - val doc = app.get(url).document + val doc = app.get(url, referer = "${mainUrl}/").document doc.select("script").map { script -> if (script.data().contains("function jalankan_jwp() {")) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt index 0d9574e5..fa5c1216 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.animeproviders import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.nodes.Element @@ -43,10 +44,10 @@ class NeonimeProvider : MainAPI() { val document = app.get(mainUrl).document val homePageList = ArrayList() - - document.select("div.item_1.items").forEach { block -> - val header = block.previousElementSibling()?.select("h1")!!.text() - val animes = block.select("div.item").mapNotNull { + + document.select("div.item_1.items,div#slid01").forEach { block -> + val header = block.previousElementSibling()?.select("h1")?.text() ?: block.selectFirst("h3")?.text().toString() + val animes = block.select("div.item").map { it.toSearchResult() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) @@ -58,26 +59,36 @@ class NeonimeProvider : MainAPI() { private fun getProperAnimeLink(uri: String): String { return when { uri.contains("/episode") -> { - val href = "$mainUrl/tvshows/" + Regex("episode/(.*)-\\d{1,2}x\\d+").find(uri)?.groupValues?.get(1).toString() - when { - !href.contains("-subtitle-indonesia") -> "$href-subtitle-indonesia" - href.contains("-special") -> href.replace(Regex("-x\\d+"), "") - else -> href + val title = uri.substringAfter("$mainUrl/episode/").let { tt -> + val fixTitle = Regex("(.*)-\\d{1,2}x\\d+").find(tt)?.groupValues?.getOrNull(1).toString() + when { + !tt.contains("-season") && !tt.contains(Regex("-1x\\d+")) && !tt.contains("one-piece") -> "$fixTitle-season-${Regex("-(\\d{1,2})x\\d+").find(tt)?.groupValues?.getOrNull(1).toString()}" + tt.contains("-special") -> fixTitle.replace(Regex("-x\\d+"), "") + !fixTitle.contains("-subtitle-indonesia") -> "$fixTitle-subtitle-indonesia" + else -> fixTitle + } } + +// title = when { +// title.contains("youkoso-jitsuryoku") && !title.contains("-season") -> title.replace("-e-", "-e-tv-") +// else -> title +// } + + "$mainUrl/tvshows/$title" } else -> uri } } - private fun Element.toSearchResult(): SearchResponse { + private fun Element.toSearchResult(): AnimeSearchResponse { val href = getProperAnimeLink(fixUrl(this.select("a").attr("href"))) - val title = this.select("span.tt.title-episode,h2.title-episode-movie").text() + val title = this.select("span.tt.title-episode,h2.title-episode-movie,span.ttps").text() val posterUrl = fixUrl(this.select("img").attr("data-src")) val epNum = this.select(".fixyear > h2.text-center").text().replace(Regex("[^0-9]"), "").trim().toIntOrNull() return newAnimeSearchResponse(title, href, TvType.Anime) { this.posterUrl = posterUrl - addDubStatus(dubExist = false, subExist = true, subEpisodes = epNum) + addSub(epNum) } } @@ -96,7 +107,7 @@ class NeonimeProvider : MainAPI() { newAnimeSearchResponse(title, href, tvType) { this.posterUrl = poster - addDubStatus(dubExist = false, subExist = true, subEpisodes = episodes) + addSub(episodes) } } } @@ -112,19 +123,16 @@ class NeonimeProvider : MainAPI() { val mYear = document.selectFirst("a[href*=release-year]")!!.text().toIntOrNull() val mDescription = document.select("div[itemprop = description]").text().trim() val mRating = document.select("span[itemprop = ratingValue]").text().toIntOrNull() + val mTrailer = document.selectFirst("div.youtube_id iframe")?.attr("data-wpfc-original-src")?.substringAfterLast("html#")?.let{ "https://www.youtube.com/embed/$it"} - return MovieLoadResponse( - name = mTitle, - url = url, - this.name, - type = TvType.Movie, - dataUrl = url, - posterUrl = mPoster, - year = mYear, - plot = mDescription, - rating = mRating, + return newMovieLoadResponse(name = mTitle, url = url, type = TvType.Movie, dataUrl = url) { + posterUrl = mPoster + year = mYear + plot = mDescription + rating = mRating tags = mTags - ) + addTrailer(mTrailer) + } } else { val title = document.select("h1[itemprop = name]").text().trim() @@ -133,6 +141,7 @@ class NeonimeProvider : MainAPI() { val year = document.select("#info a[href*=\"-year/\"]").text().toIntOrNull() val status = getStatus(document.select("div.metadatac > span").last()!!.text().trim()) val description = document.select("div[itemprop = description] > p").text().trim() + val trailer = document.selectFirst("div.youtube_id_tv iframe")?.attr("data-wpfc-original-src")?.substringAfterLast("html#")?.let{ "https://www.youtube.com/embed/$it"} val episodes = document.select("ul.episodios > li").mapNotNull { val name = it.selectFirst(".episodiotitle > a")!!.ownText().trim() @@ -148,6 +157,7 @@ class NeonimeProvider : MainAPI() { showStatus = status plot = description this.tags = tags + addTrailer(trailer) } } } 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 5998f2a7..94a82436 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt @@ -4,10 +4,8 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import org.jsoup.Jsoup import org.jsoup.nodes.Element -import java.net.URI import java.util.ArrayList class NontonAnimeIDProvider : MainAPI() { @@ -49,15 +47,15 @@ class NontonAnimeIDProvider : MainAPI() { document.select("section#postbaru").forEach { block -> val header = block.selectFirst("h2")!!.text().trim() - val animes = block.select("article.animeseries").mapNotNull { + val animes = block.select("article.animeseries").map { it.toSearchResult() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) } - document.select("aside#sidebar_right > div.side:nth-child(2)").forEach { block -> + document.select("aside#sidebar_right > div:nth-child(4)").forEach { block -> val header = block.selectFirst("h3")!!.ownText().trim() - val animes = block.select("li.fullwdth").mapNotNull { + val animes = block.select("li.fullwdth").map { it.toSearchResultPopular() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) @@ -83,7 +81,7 @@ class NontonAnimeIDProvider : MainAPI() { } } - private fun Element.toSearchResult(): SearchResponse { + private fun Element.toSearchResult(): AnimeSearchResponse { val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href"))) val title = this.selectFirst("h3.title")!!.text() val posterUrl = fixUrl(this.select("img").attr("data-src")) @@ -95,7 +93,7 @@ class NontonAnimeIDProvider : MainAPI() { } - private fun Element.toSearchResultPopular(): SearchResponse { + private fun Element.toSearchResultPopular(): AnimeSearchResponse { val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href"))) val title = this.select("h4").text().trim() val posterUrl = fixUrl(this.select("img").attr("data-src")) @@ -149,7 +147,7 @@ class NontonAnimeIDProvider : MainAPI() { val type = getType(document.select("span.typeseries").text().trim()) val rating = document.select("span.nilaiseries").text().trim().toIntOrNull() val description = document.select(".entry-content.seriesdesc > p").text().trim() - val trailer = document.select("a.ytp-impression-link").attr("href") + val trailer = document.selectFirst("iframe#traileryt")?.attr("data-src") val episodes = if (document.select("button.buttfilter").isNotEmpty()) { val id = document.select("input[name=series_id]").attr("value") @@ -238,7 +236,7 @@ class NontonAnimeIDProvider : MainAPI() { } sources.apmap { - loadExtractor(it, data, callback) + loadExtractor(it, "$mainUrl/", callback) } return true 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 9c01934d..7668b151 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt @@ -1,17 +1,18 @@ package com.lagradost.cloudstream3.animeproviders +import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.loadExtractor +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.* import org.jsoup.Jsoup import org.jsoup.nodes.Element import java.util.ArrayList + class OploverzProvider : MainAPI() { - override var mainUrl = "https://oploverz.asia" + override var mainUrl = "https://65.108.132.145" override var name = "Oploverz" - override val hasQuickSearch = false override val hasMainPage = true override var lang = "id" override val hasDownloadSupport = true @@ -47,7 +48,7 @@ class OploverzProvider : MainAPI() { document.select(".bixbox.bbnofrm").forEach { block -> val header = block.selectFirst("h3")!!.text().trim() - val animes = block.select("article[itemscope=itemscope]").mapNotNull { + val animes = block.select("article[itemscope=itemscope]").map { it.toSearchResult() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) @@ -86,7 +87,7 @@ class OploverzProvider : MainAPI() { } - private fun Element.toSearchResult(): SearchResponse { + private fun Element.toSearchResult(): AnimeSearchResponse { val href = getProperAnimeLink(this.selectFirst("a.tip")!!.attr("href")) val title = this.selectFirst("h2[itemprop=headline]")!!.text().trim() val posterUrl = fixUrl(this.selectFirst("img")!!.attr("src")) @@ -97,7 +98,7 @@ class OploverzProvider : MainAPI() { return newAnimeSearchResponse(title, href, type) { this.posterUrl = posterUrl - addDubStatus(dubExist = false, subExist = true, subEpisodes = epNum) + addSub(epNum) } } @@ -133,15 +134,15 @@ class OploverzProvider : MainAPI() { .text().trim().replace("Status: ", "") ) val typeCheck = - when { - document.select(".info-content > .spe > span:nth-child(5), .info-content > .spe > span") - .text().trim().contains("TV") -> "TV" - document.select(".info-content > .spe > span:nth-child(5), .info-content > .spe > span") - .text().trim().contains("TV") -> "Movie" + when (document.select(".info-content > .spe > span:nth-child(5), .info-content > .spe > span") + .text().trim()) { + "TV" -> "TV" + "Movie" -> "Movie" else -> "OVA" } val type = getType(typeCheck) val description = document.select(".entry-content > p").text().trim() + val trailer = document.selectFirst("a.trailerbutton")?.attr("href") val episodes = document.select(".eplister > ul > li").map { val name = it.select(".epl-title").text().trim() @@ -171,6 +172,7 @@ class OploverzProvider : MainAPI() { plot = description this.tags = tags this.recommendations = recommendations + addTrailer(trailer) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OtakudesuProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OtakudesuProvider.kt new file mode 100644 index 00000000..4bd02bf8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OtakudesuProvider.kt @@ -0,0 +1,199 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import java.util.ArrayList + +class OtakudesuProvider : MainAPI() { + override var mainUrl = "https://otakudesu.watch" + override var name = "Otakudesu" + override val hasMainPage = true + override var lang = "id" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA") || t.contains("Special")) TvType.OVA + else if (t.contains("Movie")) TvType.AnimeMovie + else TvType.Anime + } + + fun getStatus(t: String): ShowStatus { + return when (t) { + "Completed" -> ShowStatus.Completed + "Ongoing" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + override suspend fun getMainPage(): HomePageResponse { + val document = app.get(mainUrl).document + + val homePageList = ArrayList() + + document.select("div.rseries").forEach { block -> + val header = block.selectFirst("div.rvad > h1")!!.text().trim() + val items = block.select("div.venz > ul > li").map { + it.toSearchResult() + } + if (items.isNotEmpty()) homePageList.add(HomePageList(header, items)) + } + + return HomePageResponse(homePageList) + } + + private fun Element.toSearchResult(): AnimeSearchResponse { + val title = this.selectFirst("h2.jdlflm")!!.text().trim() + val href = this.selectFirst("a")!!.attr("href") + val posterUrl = this.select("div.thumbz > img").attr("src").toString() + val epNum = this.selectFirst("div.epz")?.ownText()?.replace(Regex("[^0-9]"), "")?.trim() + ?.toIntOrNull() + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addSub(epNum) + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query&post_type=anime" + val document = app.get(link).document + + return document.select("ul.chivsrc > li").map { + val title = it.selectFirst("h2 > a")!!.ownText().trim() + val href = it.selectFirst("h2 > a")!!.attr("href") + val posterUrl = it.selectFirst("img")!!.attr("src").toString() + newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + } + } + } + + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("div.infozingle > p:nth-child(1) > span")?.ownText() + ?.replace(":", "")?.trim().toString() + val poster = document.selectFirst("div.fotoanime > img")?.attr("src") + val tags = document.select("div.infozingle > p:nth-child(11) > span > a").map { it.text() } + val type = getType( + document.selectFirst("div.infozingle > p:nth-child(5) > span")?.ownText() + ?.replace(":", "")?.trim().toString() + ) + val year = Regex("\\d, ([0-9]*)").find( + document.select("div.infozingle > p:nth-child(9) > span").text() + )?.groupValues?.get(1)?.toIntOrNull() + val status = getStatus( + document.selectFirst("div.infozingle > p:nth-child(6) > span")!!.ownText() + .replace(":", "") + .trim() + ) + val description = document.select("div.sinopc > p").text() + + val episodes = document.select("div.episodelist")[1].select("ul > li").mapNotNull { + val name = it.selectFirst("a")!!.text().trim() + val link = fixUrl(it.selectFirst("a")!!.attr("href")) + Episode(link, name) + }.reversed() + + val recommendations = + document.select("div.isi-recommend-anime-series > div.isi-konten").map { + val recName = it.selectFirst("span.judul-anime > a")!!.text() + val recHref = it.selectFirst("a")!!.attr("href") + val recPosterUrl = it.selectFirst("a > img")?.attr("src").toString() + newAnimeSearchResponse(recName, recHref, TvType.Anime) { + this.posterUrl = recPosterUrl + } + } + + return newAnimeLoadResponse(title, url, type) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + this.tags = tags + this.recommendations = recommendations + } + } + + + data class ResponseSources( + @JsonProperty("id") val id: String, + @JsonProperty("i") val i: String, + @JsonProperty("q") val q: String, + ) + + data class ResponseData( + @JsonProperty("data") val data: String + ) + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = app.get(data).document + val scriptData = document.select("script").last()?.data() + val token = scriptData?.substringAfter("{action:\"")?.substringBefore("\"}").toString() + + val nonce = app.post("$mainUrl/wp-admin/admin-ajax.php", data = mapOf("action" to token)) + .parsed().data + val action = scriptData?.substringAfter(",action:\"")?.substringBefore("\"}").toString() + + val mirrorData = document.select("div.mirrorstream > ul > li").mapNotNull { + base64Decode(it.select("a").attr("data-content")) + }.toString() + + tryParseJson>(mirrorData)?.apmap { res -> + val id = res.id + val i = res.i + val q = res.q + + var sources = Jsoup.parse( + base64Decode( + app.post( + "${mainUrl}/wp-admin/admin-ajax.php", data = mapOf( + "id" to id, + "i" to i, + "q" to q, + "nonce" to nonce, + "action" to action + ) + ).parsed().data + ) + ).select("iframe").attr("src") + + if (sources.startsWith("https://desustream.me")) { + if (!sources.contains("/arcg/") && !sources.contains("/odchan/") && !sources.contains("/desudrive/")) { + sources = app.get(sources).document.select("iframe").attr("src") + } + if (sources.startsWith("https://yourupload.com")) { + sources = sources.replace("//", "//www.") + } + } + + loadExtractor(sources, data, callback) + + } + + return true + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt new file mode 100644 index 00000000..5c8af1c5 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt @@ -0,0 +1,38 @@ +package com.lagradost.cloudstream3.extractors + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson + +class Filesim : ExtractorApi() { + override val name = "Filesim" + override val mainUrl = "https://files.im" + override val requiresReferer = false + + override suspend fun getUrl(url: String, referer: String?): List { + val sources = mutableListOf() + with(app.get(url).document) { + this.select("script").map { script -> + if (script.data().contains("eval(function(p,a,c,k,e,d)")) { + val data = getAndUnpack(script.data()).substringAfter("sources:[").substringBefore("]") + tryParseJson>("[$data]")?.map { + M3u8Helper.generateM3u8( + name, + it.file, + "$mainUrl/", + ).forEach { m3uData -> sources.add(m3uData) } + } + } + } + } + return sources + } + + private data class ResponseSource( + @JsonProperty("file") val file: String, + @JsonProperty("type") val type: String?, + @JsonProperty("label") val label: String? + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Hxfile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Hxfile.kt index 1bf370cf..f5dde774 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Hxfile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Hxfile.kt @@ -20,13 +20,18 @@ class Neonime8n : Hxfile() { class KotakAnimeid : Hxfile() { override val name = "KotakAnimeid" override val mainUrl = "https://kotakanimeid.com" + override val requiresReferer = true } -private data class ResponseSource( - @JsonProperty("file") val file: String, - @JsonProperty("type") val type: String?, - @JsonProperty("label") val label: String? -) +class Yufiles : Hxfile() { + override val name = "Yufiles" + override val mainUrl = "https://yufiles.com" +} + +class Aico : Hxfile() { + override val name = "Aico" + override val mainUrl = "https://aico.pw" +} open class Hxfile : ExtractorApi() { override val name = "Hxfile" @@ -36,7 +41,7 @@ open class Hxfile : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { val sources = mutableListOf() - val document = app.get(url, allowRedirects = redirect).document + val document = app.get(url, allowRedirects = redirect, referer = referer).document with(document) { this.select("script").map { script -> if (script.data().contains("eval(function(p,a,c,k,e,d)")) { @@ -86,4 +91,10 @@ open class Hxfile : ExtractorApi() { return sources } + private data class ResponseSource( + @JsonProperty("file") val file: String, + @JsonProperty("type") val type: String?, + @JsonProperty("label") val label: String? + ) + } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/JWPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/JWPlayer.kt new file mode 100644 index 00000000..6e6f6516 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/JWPlayer.kt @@ -0,0 +1,81 @@ +package com.lagradost.cloudstream3.extractors + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.getQualityFromName + +class Meownime : JWPlayer() { + override val name = "Meownime" + override val mainUrl = "https://meownime.ltd" +} + +class DesuOdchan : JWPlayer() { + override val name = "DesuOdchan" + override val mainUrl = "https://desustream.me/odchan/" +} + +class DesuArcg : JWPlayer() { + override val name = "DesuArcg" + override val mainUrl = "https://desustream.me/arcg/" +} + +class DesuDrive : JWPlayer() { + override val name = "DesuDrive" + override val mainUrl = "https://desustream.me/desudrive/" +} + +class DesuOdvip : JWPlayer() { + override val name = "DesuOdvip" + override val mainUrl = "https://desustream.me/odvip/" +} + +open class JWPlayer : ExtractorApi() { + override val name = "JWPlayer" + override val mainUrl = "https://www.jwplayer.com" + override val requiresReferer = false + + override suspend fun getUrl(url: String, referer: String?): List? { + val sources = mutableListOf() + with(app.get(url).document) { + val data = this.select("script").mapNotNull { script -> + if (script.data().contains("sources: [")) { + script.data().substringAfter("sources: [") + .substringBefore("],").replace("'", "\"") + } else if (script.data().contains("otakudesu('")) { + script.data().substringAfter("otakudesu('") + .substringBefore("');") + } else { + null + } + } + + tryParseJson>("$data")?.map { + sources.add( + ExtractorLink( + name, + name, + it.file, + referer = url, + quality = getQualityFromName( + Regex("(\\d{3,4}p)").find(it.file)?.groupValues?.get( + 1 + ) + ) + ) + ) + } + } + return sources + } + + private data class ResponseSource( + @JsonProperty("file") val file: String, + @JsonProperty("type") val type: String?, + @JsonProperty("label") val label: String? + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt new file mode 100644 index 00000000..3c564f67 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt @@ -0,0 +1,47 @@ +package com.lagradost.cloudstream3.extractors + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName + +class YourUpload: ExtractorApi() { + override val name = "Yourupload" + override val mainUrl = "https://www.yourupload.com" + override val requiresReferer = false + + override suspend fun getUrl(url: String, referer: String?): List { + val sources = mutableListOf() + with(app.get(url).document) { + val quality = Regex("\\d{3,4}p").find(this.select("title").text())?.groupValues?.get(0) + this.select("script").map { script -> + if (script.data().contains("var jwplayerOptions = {")) { + val data = + script.data().substringAfter("var jwplayerOptions = {").substringBefore(",\n") + val link = tryParseJson( + "{${ + data.replace("file", "\"file\"").replace("'", "\"") + }}" + ) + sources.add( + ExtractorLink( + source = name, + name = name, + url = link!!.file, + referer = url, + quality = getQualityFromName(quality) + ) + ) + } + } + } + return sources + } + + private data class ResponseSource( + @JsonProperty("file") val file: String, + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt index 77945fa7..b8796f29 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt @@ -180,7 +180,7 @@ class DramaidProvider : MainAPI() { } tryParseJson(trackers)?.let { - subCallback( + subCallback.invoke( SubtitleFile( if (it.label.contains("Indonesia")) "${it.label}n" else it.label, it.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 new file mode 100644 index 00000000..6d37e994 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/IdlixProvider.kt @@ -0,0 +1,378 @@ +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 com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import org.jsoup.nodes.Element +import java.net.URI +import java.util.ArrayList + +class IdlixProvider : MainAPI() { + override var mainUrl = "https://idlix.xn--6frz82g" + override var name = "Idlix" + override val hasMainPage = true + override var lang = "id" + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + + override suspend fun getMainPage(): HomePageResponse { + val document = app.get(mainUrl).document + + val homePageList = ArrayList() + + document.select("div.items").forEach { block -> + val header = + fixTitle(block.previousElementSibling()?.previousElementSibling()?.select("header > h2") + ?.text()!!.trim()) + val items = block.select("article.item").mapNotNull { + it.toSearchResult() + } + if (items.isNotEmpty()) homePageList.add(HomePageList(header, items)) + } + + return HomePageResponse(homePageList) + } + + private fun getProperLink(uri: String): String { + return when { + uri.contains("/episode/") -> { + var title = uri.substringAfter("$mainUrl/episode/") + title = Regex("(.+?)-season").find(title)?.groupValues?.get(1).toString() + "$mainUrl/tvseries/$title" + } + uri.contains("/season/") -> { + var title = uri.substringAfter("$mainUrl/season/") + title = Regex("(.+?)-season").find(title)?.groupValues?.get(1).toString() + "$mainUrl/tvseries/$title" + } + else -> { + uri + } + } + } + + private fun Element.toSearchResult(): SearchResponse { + val title = this.selectFirst("h3 > a")!!.text().replace(Regex("\\(\\d{4}\\)"), "").trim() + val href = getProperLink(this.selectFirst("h3 > a")!!.attr("href")) + val posterUrl = this.select("div.poster > img").attr("src").toString() + val quality = getQualityFromString(this.select("span.quality").text()) + return newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + this.quality = quality + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/search/$query" + val document = app.get(link).document + + return document.select("div.result-item").map { + val title = it.selectFirst("div.title > a")!!.text().replace(Regex("\\(\\d{4}\\)"), "").trim() + val href = getProperLink(it.selectFirst("div.title > a")!!.attr("href")) + val posterUrl = it.selectFirst("img")!!.attr("src").toString() + newMovieSearchResponse(title, href, TvType.TvSeries) { + this.posterUrl = posterUrl + } + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("div.data > h1")?.text()?.replace(Regex("\\(\\d{4}\\)"), "")?.trim().toString() + val poster = document.select("div.poster > img").attr("src").toString() + val tags = document.select("div.sgeneros > a").map { it.text() } + + val year = Regex(",\\s?(\\d+)").find( + document.select("span.date").text().trim() + )?.groupValues?.get(1).toString().toIntOrNull() + val tvType = if (document.select("ul#section > li:nth-child(1)").text().contains("Episodes") + ) TvType.TvSeries else TvType.Movie + val description = document.select("div.wp-content > p").text().trim() + val trailer = document.selectFirst("div.embed iframe")?.attr("src") + val rating = + document.selectFirst("span.dt_rating_vgs")?.text()?.toRatingInt() + val actors = document.select("div.persons > div[itemprop=actor]").map { + Actor(it.select("meta[itemprop=name]").attr("content"), it.select("img").attr("src")) + } + + val recommendations = document.select("div.owl-item").map { + val recName = + it.selectFirst("a")!!.attr("href").toString().removeSuffix("/").split("/").last() + val recHref = it.selectFirst("a")!!.attr("href") + val recPosterUrl = it.selectFirst("img")?.attr("src").toString() + newTvSeriesSearchResponse(recName, recHref, TvType.TvSeries) { + this.posterUrl = recPosterUrl + } + } + + return if (tvType == TvType.TvSeries) { + val episodes = document.select("ul.episodios > li").map { + val href = it.select("a").attr("href") + val name = fixTitle(it.select("div.episodiotitle > a").text().trim()) + val image = it.select("div.imagen > img").attr("src") + val episode = it.select("div.numerando").text().replace(" ", "").split("-").last() + .toIntOrNull() + val season = it.select("div.numerando").text().replace(" ", "").split("-").first() + .toIntOrNull() + Episode( + href, + name, + season, + episode, + image + ) + } + 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) + } + } + } + + private fun getLanguage(str: String): String { + return when { + str.lowercase().contains("indonesia") || str.lowercase().contains("bahasa") -> "Indonesian" + else -> str + } + } + + data class ResponseHash( + @JsonProperty("embed_url") val embed_url: String, + @JsonProperty("type") val type: String?, + ) + + data class ResponseSource( + @JsonProperty("hls") val hls: Boolean, + @JsonProperty("videoSource") val videoSource: String, + @JsonProperty("securedLink") val securedLink: String?, + ) + + data class Tracks( + @JsonProperty("kind") val kind: String?, + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String?, + ) + + private suspend fun invokeLokalSource( + url: String, + subCallback: (SubtitleFile) -> Unit, + sourceCallback: (ExtractorLink) -> Unit + ) { + val document = app.get(url, referer = "$mainUrl/").document + val hash = url.split("/").last().substringAfter("data=") + + val m3uLink = app.post( + url = "https://jeniusplay.com/player/index.php?data=$hash&do=getVideo", + data = mapOf("hash" to hash, "r" to "$mainUrl/"), + referer = url, + headers = mapOf("X-Requested-With" to "XMLHttpRequest") + ).parsed().videoSource + + M3u8Helper.generateM3u8( + this.name, + m3uLink, + url, + ).forEach(sourceCallback) + + + document.select("script").map { script -> + if (script.data().contains("eval(function(p,a,c,k,e,d)")) { + val subData = getAndUnpack(script.data()).substringAfter("\"tracks\":[").substringBefore("],") + tryParseJson>("[$subData]")?.map { subtitle -> + subCallback.invoke( + SubtitleFile( + getLanguage(subtitle.label!!), + subtitle.file + ) + ) + } + } + } + } + + data class ResponseLaviolaSource( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String?, + ) + + private suspend fun invokeLaviolaSource( + url: String, + subCallback: (SubtitleFile) -> Unit, + sourceCallback: (ExtractorLink) -> Unit + ) { + val document = app.get(url, referer = "$mainUrl/").document + val baseName = "Laviola" + val baseUrl = "https://laviola.live/" + document.select("script").map { script -> + if (script.data().contains("var config = {")) { + val data = script.data().substringAfter("sources: [").substringBefore("],") + tryParseJson>("[$data]")?.map { m3u -> + val m3uData = app.get(m3u.file, referer = baseUrl).text + val quality = + Regex("\\d{3,4}\\.m3u8").findAll(m3uData).map { it.value }.toList() + quality.forEach { + sourceCallback.invoke( + ExtractorLink( + source = baseName, + name = baseName, + url = m3u.file.replace("video.m3u8", it), + referer = baseUrl, + quality = getQualityFromName("${it.replace(".m3u8", "")}p"), + isM3u8 = true + ) + ) + } + } + + val subData = script.data().substringAfter("tracks: [").substringBefore("],") + tryParseJson>("[$subData]")?.map { subtitle -> + subCallback.invoke( + SubtitleFile( + getLanguage(subtitle.label!!), + (if (subtitle.kind!!.contains("captions")) subtitle.file else null)!! + ) + ) + } + } + } + } + + 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 ResponseCdn( + @JsonProperty("success") val success: Boolean, + @JsonProperty("player") val player: Player, + @JsonProperty("data") val data: List?, + @JsonProperty("captions") val captions: List? + ) + + private suspend fun invokeCdnSource( + url: String, + subCallback: (SubtitleFile) -> Unit, + sourceCallback: (ExtractorLink) -> Unit + ) { + val domainUrl = "https://cdnplayer.online" + val id = url.trimEnd('/').split("/").last() + val sources = app.post( + url = "$domainUrl/api/source/$id", + data = mapOf("r" to mainUrl, "d" to URI(url).host) + ).parsed() + + sources.data?.map { + sourceCallback.invoke( + ExtractorLink( + name, + "Cdnplayer", + fixUrl(it.file), + referer = url, + quality = getQualityFromName(it.label) + ) + ) + } + val userData = sources.player.poster_file.split("/")[2] + sources.captions?.map { subtitle -> + subCallback.invoke( + SubtitleFile( + getLanguage(subtitle.language), + "$domainUrl/asset/userdata/$userData/caption/${subtitle.hash}/${subtitle.id}.srt" + ) + ) + } + + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = app.get(data).document + val id = document.select("meta#dooplay-ajax-counter").attr("data-postid") + val type = if (data.contains("/movie/")) "movie" else "tv" + + document.select("ul#playeroptionsul > li").map { + it.attr("data-nume") + }.apmap { nume -> + safeApiCall { + var source = app.post( + url = "$mainUrl/wp-admin/admin-ajax.php", + data = mapOf( + "action" to "doo_player_ajax", + "post" to id, + "nume" to nume, + "type" to type + ) + ).parsed().embed_url + + when { + source.startsWith("https://jeniusplay.com") -> invokeLokalSource( + source, + subtitleCallback, + callback + ) + source.startsWith("https://laviola.live") -> invokeLaviolaSource( + source, + subtitleCallback, + callback + ) + source.startsWith("https://cdnplayer.online") -> invokeCdnSource( + source, + subtitleCallback, + callback + ) + else -> { + if (source.startsWith("https://uservideo.xyz")) { + source = app.get(source).document.select("iframe").attr("src") + } + loadExtractor(source, data, callback) + } + } + } + } + + return true + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LayarKaca21Provider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LayarKaca21Provider.kt index 393bae12..fa3c2c00 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LayarKaca21Provider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LayarKaca21Provider.kt @@ -1,6 +1,8 @@ package com.lagradost.cloudstream3.movieproviders import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.Jsoup @@ -25,7 +27,7 @@ class LayarKacaProvider : MainAPI() { val homePageList = ArrayList() document.select("section.hot-block,section#newseries").forEach { block -> - val header = block.select("footer.load-more > a").text().trim() + val header = fixTitle(block.select("footer.load-more > a").text().trim()) val items = block.select("div.slider-item").mapNotNull { it.toTopSearchResult() } @@ -33,7 +35,7 @@ class LayarKacaProvider : MainAPI() { } document.select("div#newest").forEach { block -> - val header = block.select(".header > h2 > a").text() + val header = fixTitle(block.select(".header > h2 > a").text()) val items = block.select("div.item").mapNotNull { it.toMainSearchResult() } @@ -42,7 +44,7 @@ class LayarKacaProvider : MainAPI() { document.select("section#recomendation,section#populer,section#seriespopuler") .forEach { block -> - val header = block.select(".header > h2 > a").text() + val header = fixTitle(block.select(".header > h2 > a").text()) val items = block.select("div.item").mapNotNull { it.toBottomSearchResult() } @@ -60,7 +62,7 @@ class LayarKacaProvider : MainAPI() { if (this.select("div.quality-top").isNotEmpty()) TvType.Movie else TvType.TvSeries return if (type == TvType.Movie) { val quality = getQualityFromString(this.select("div.quality-top").text().trim()) - return newMovieSearchResponse(title, href, TvType.Movie) { + newMovieSearchResponse(title, href, TvType.Movie) { this.posterUrl = posterUrl this.quality = quality } @@ -68,7 +70,7 @@ class LayarKacaProvider : MainAPI() { val episode = this.select("div.last-episode > span").text().toIntOrNull() newAnimeSearchResponse(title, href, TvType.TvSeries) { this.posterUrl = posterUrl - addDubStatus(dubExist = false, subExist = true, subEpisodes = episode) + addSub(episode) } } @@ -133,15 +135,10 @@ class LayarKacaProvider : MainAPI() { .isNotEmpty() ) TvType.TvSeries else TvType.Movie val description = document.select("div.content > blockquote").text().trim() + val trailer = document.selectFirst("div.action-player li > a.fancybox")?.attr("href") val rating = document.selectFirst("div.content > div:nth-child(6) > h3")?.text()?.toRatingInt() - val actors = document.select("div.col-xs-9.content > div:nth-child(3) > h3 > a").map { - ActorData( - Actor( - it.text() - ) - ) - } + val actors = document.select("div.col-xs-9.content > div:nth-child(3) > h3 > a").map { it.text() } val recommendations = document.select("div.row.item-media").map { val recName = it.selectFirst("h3")?.text()?.trim().toString() @@ -171,8 +168,9 @@ class LayarKacaProvider : MainAPI() { this.plot = description this.tags = tags this.rating = rating - this.actors = actors + addActors(actors) this.recommendations = recommendations + addTrailer(trailer) } } else { @@ -182,8 +180,9 @@ class LayarKacaProvider : MainAPI() { this.plot = description this.tags = tags this.rating = rating - this.actors = actors + addActors(actors) this.recommendations = recommendations + addTrailer(trailer) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MultiplexProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MultiplexProvider.kt new file mode 100644 index 00000000..fe013f5e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MultiplexProvider.kt @@ -0,0 +1,206 @@ +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.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName +import org.jsoup.nodes.Element +import java.util.ArrayList + +class MultiplexProvider : MainAPI() { + override var mainUrl = "https://146.19.24.137" + override var name = "Multiplex" + override val hasMainPage = true + override var lang = "id" + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + TvType.AsianDrama + ) + + override suspend fun getMainPage(): HomePageResponse { + val document = app.get(mainUrl).document + + val homePageList = ArrayList() + + document.select("div.col-md-12 > div.home-widget").forEach { block -> + val header = fixTitle(block.select("h3.homemodule-title").text()) + val items = block.select("div.col-md-125").mapNotNull { + it.toSearchResult() + } + if (items.isNotEmpty()) homePageList.add(HomePageList(header, items)) + } + + document.select("div.container.gmr-maincontent") + .forEach { block -> + val header = fixTitle(block.select("h3.homemodule-title").text()) + val items = block.select("article.item").mapNotNull { + it.toSearchResult() + } + if (items.isNotEmpty()) homePageList.add(HomePageList(header, items)) + } + + document.select("div#idmuvi-rp-2").forEach { block -> + val header = fixTitle(block.selectFirst("h3.widget-title")?.ownText()!!.trim()) + val items = block.select("div.idmuvi-rp ul li").mapNotNull { + it.toBottomSearchResult() + } + if (items.isNotEmpty()) homePageList.add(HomePageList(header, items)) + } + + return HomePageResponse(homePageList) + } + + private fun Element.toSearchResult(): SearchResponse { + val title = this.selectFirst("h2.entry-title > a")!!.text().trim() + val href = this.selectFirst("a")!!.attr("href") + val posterUrl = fixUrl(this.selectFirst("a > img")?.attr("data-src").toString()) + val quality = getQualityFromString(this.select("div.gmr-quality-item > a").text().trim()) + return if (quality == null) { + val episode = this.select("div.gmr-numbeps > span").text().toIntOrNull() + newAnimeSearchResponse(title, href, TvType.TvSeries) { + this.posterUrl = posterUrl + addSub(episode) + } + } else { + newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + this.quality = quality + } + } + + } + + private fun Element.toBottomSearchResult(): SearchResponse { + val title = this.selectFirst("a > span.idmuvi-rp-title")!!.text().trim() + val href = this.selectFirst("a")!!.attr("href") + val posterUrl = fixUrl(this.selectFirst("a > img")?.attr("data-src").toString()) + return newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query&post_type[]=post&post_type[]=tv" + val document = app.get(link).document + + return document.select("div#gmr-main-load > article.item").map { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = + document.selectFirst("h1.entry-title")?.text()?.substringBefore("Season")?.trim() + .toString() + val poster = + fixUrl(document.selectFirst("figure.pull-left > img")?.attr("data-src").toString()) + val tags = document.select("span.gmr-movie-genre:contains(Genre:) > a").map { it.text() } + + val year = + document.select("span.gmr-movie-genre:contains(Year:) > a").text().trim().toIntOrNull() + val tvType = if (url.contains("/tv/")) TvType.TvSeries else TvType.Movie + val description = document.selectFirst("div[itemprop=description] > p")?.text()?.trim() + val trailer = document.selectFirst("ul.gmr-player-nav li a.gmr-trailer-popup")?.attr("href") + val rating = + document.selectFirst("div.gmr-meta-rating > span[itemprop=ratingValue]")?.text() + ?.toRatingInt() + val actors = document.select("div.gmr-moviedata").last()?.select("span[itemprop=actors]")?.map { it.select("a").text() } + + val recommendations = document.select("div.idmuvi-rp ul li").map { + it.toBottomSearchResult() + } + + return if (tvType == TvType.TvSeries) { + val episodes = document.select("div.gmr-listseries > a").map { + val href = fixUrl(it.attr("href")) + val episode = it.text().split(" ").last().toIntOrNull() + val season = it.text().split(" ").first().substringAfter("S").toIntOrNull() + Episode( + href, + "Episode $episode", + 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 { + 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) + } + } + } + + private data class ResponseSource( + @JsonProperty("file") val file: String, + @JsonProperty("type") val type: String?, + @JsonProperty("label") val label: String? + ) + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = app.get(data).document + + val id = document.selectFirst("div#muvipro_player_content_id")!!.attr("data-id") + val server = app.post( + "$mainUrl/wp-admin/admin-ajax.php", + data = mapOf("action" to "muvipro_player_content", "tab" to "player1", "post_id" to id) + ).document.select("iframe").attr("src") + + app.get(server, referer = "$mainUrl/").document.select("script").map { script -> + if (script.data().contains("var config = {")) { + val source = script.data().substringAfter("sources: [").substringBefore("],") + tryParseJson>("[$source]")?.map { m3u -> + val m3uData = app.get(m3u.file, referer = "https://gdriveplayer.link/").text + val quality = + Regex("\\d{3,4}\\.m3u8").findAll(m3uData).map { it.value }.toList() + quality.forEach { + callback.invoke( + ExtractorLink( + source = name, + name = name, + url = m3u.file.replace("video.m3u8", it), + referer = "https://gdriveplayer.link/", + quality = getQualityFromName("${it.replace(".m3u8", "")}p"), + isM3u8 = true + ) + ) + } + } + } + } + + return true + + } + + +} \ 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 3a004b6b..196a7798 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt @@ -2,6 +2,8 @@ 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.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.network.WebViewResolver @@ -106,16 +108,11 @@ class RebahinProvider : MainAPI() { )?.groupValues?.get(1).toString().toIntOrNull() val tvType = if (url.contains("/series/")) TvType.TvSeries else TvType.Movie val description = document.select("span[itemprop=reviewBody] > p").text().trim() + val trailer = fixUrlNull(document.selectFirst("div.modal-body-trailer iframe")?.attr("src")) val rating = document.selectFirst("span[itemprop=ratingValue]")?.text()?.toRatingInt() val duration = document.selectFirst(".mvici-right > p:nth-child(1)")!! .ownText().replace(Regex("[^0-9]"), "").toIntOrNull() - val actors = document.select("span[itemprop=actor] > a").map { - ActorData( - Actor( - it.select("span").text() - ) - ) - } + val actors = document.select("span[itemprop=actor] > a").map { it.select("span").text() } return if (tvType == TvType.TvSeries) { val baseLink = document.select("div#mv-info > a").attr("href") @@ -124,7 +121,6 @@ class RebahinProvider : MainAPI() { name }.distinct().map { val name = it -// val epNum = Regex("[^r|R]\\s(\\d+)").find(it)?.groupValues?.get(1)?.toIntOrNull() val epNum = it.replace(Regex("[^0-9]"), "").toIntOrNull() val link = "$baseLink?ep=$epNum" newEpisode(link) { @@ -139,7 +135,8 @@ class RebahinProvider : MainAPI() { this.tags = tags this.rating = rating this.duration = duration - this.actors = actors + addActors(actors) + addTrailer(trailer) } } else { val episodes = document.select("div#mv-info > a").attr("href") @@ -150,7 +147,8 @@ class RebahinProvider : MainAPI() { this.tags = tags this.rating = rating this.duration = duration - this.actors = actors + addActors(actors) + addTrailer(trailer) } } } @@ -204,7 +202,7 @@ class RebahinProvider : MainAPI() { val trackJson = script.data().substringAfter("tracks: [").substringBefore("],") val track = tryParseJson>("[$trackJson]") track?.map { - subCallback( + subCallback.invoke( SubtitleFile( "Indonesian", (if (it.file.contains(".srt")) it.file else null)!! @@ -262,7 +260,7 @@ class RebahinProvider : MainAPI() { } val userData = sources.player.poster_file.split("/")[2] sources.captions?.map { - subCallback( + subCallback.invoke( SubtitleFile( if (it.language.lowercase().contains("eng")) it.language else "Indonesian", "$domainUrl/asset/userdata/$userData/caption/${it.hash}/${it.id}.srt" diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt new file mode 100644 index 00000000..edbfc53b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt @@ -0,0 +1,252 @@ +package com.lagradost.cloudstream3.syncproviders.providers + +import android.util.Log +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.subtitles.AbstractSubProvider +import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities +import com.lagradost.cloudstream3.syncproviders.AuthAPI +import com.lagradost.cloudstream3.utils.SubtitleHelper + +class IndexSubtitleApi : AuthAPI, AbstractSubProvider { + override val name = "IndexSubtitle" + override val idPrefix = "indexsubtitle" + override val requiresLogin = false + override val icon: Nothing? = null + override val createAccountUrl: Nothing? = null + + override fun loginInfo(): Nothing? = null + + override fun logOut() {} + + companion object { + const val host = "https://indexsubtitle.com" + const val TAG = "INDEXSUBS" + } + + private fun fixUrl(url: String): String { + if (url.startsWith("http")) { + return url + } + if (url.isEmpty()) { + return "" + } + + val startsWithNoHttp = url.startsWith("//") + if (startsWithNoHttp) { + return "https:$url" + } else { + if (url.startsWith('/')) { + return host + url + } + return "$host/$url" + } + } + + private fun getOrdinal(num: Int?) : String? { + return when (num) { + 1 -> "First" + 2 -> "Second" + 3 -> "Third" + 4 -> "Fourth" + 5 -> "Fifth" + 6 -> "Sixth" + 7 -> "Seventh" + 8 -> "Eighth" + 9 -> "Ninth" + 10 -> "Tenth" + 11 -> "Eleventh" + 12 -> "Twelfth" + 13 -> "Thirteenth" + 14 -> "Fourteenth" + 15 -> "Fifteenth" + 16 -> "Sixteenth" + 17 -> "Seventeenth" + 18 -> "Eighteenth" + 19 -> "Nineteenth" + 20 -> "Twentieth" + 21 -> "Twenty-First" + 22 -> "Twenty-Second" + 23 -> "Twenty-Third" + 24 -> "Twenty-Fourth" + 25 -> "Twenty-Fifth" + 26 -> "Twenty-Sixth" + 27 -> "Twenty-Seventh" + 28 -> "Twenty-Eighth" + 29 -> "Twenty-Ninth" + 30 -> "Thirtieth" + 31 -> "Thirty-First" + 32 -> "Thirty-Second" + 33 -> "Thirty-Third" + 34 -> "Thirty-Fourth" + 35 -> "Thirty-Fifth" + else -> null + } + } + + private fun isRightEps(text: String, seasonNum: Int?, epNum: Int?) : Boolean { + val FILTER_EPS_REGEX = Regex("(?i)((Chapter\\s?0?${epNum})|((Season)?\\s?0?${seasonNum}?\\s?(Episode)\\s?0?${epNum}[^0-9]))|(?i)((S?0?${seasonNum}?E0?${epNum}[^0-9])|(0?${seasonNum}[a-z]0?${epNum}[^0-9]))") + return text.contains(FILTER_EPS_REGEX) + } + + private fun haveEps(text: String) : Boolean { + val HAVE_EPS_REGEX = Regex("(?i)((Chapter\\s?0?\\d)|((Season)?\\s?0?\\d?\\s?(Episode)\\s?0?\\d))|(?i)((S?0?\\d?E0?\\d)|(0?\\d[a-z]0?\\d))") + return text.contains(HAVE_EPS_REGEX) + } + + override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List { + val imdbId = query.imdb ?: 0 + val lang = query.lang + val queryLang = SubtitleHelper.fromTwoLettersToLanguage(lang.toString()) + val queryText = query.query + val epNum = query.epNumber ?: 0 + val seasonNum = query.seasonNumber ?: 0 + val yearNum = query.year ?: 0 + + val urlItems = ArrayList() + + fun cleanResources(results: MutableList, name: String, link: String) { + results.add( + AbstractSubtitleEntities.SubtitleEntity( + idPrefix = idPrefix, + name = name, + lang = queryLang.toString(), + data = link, + type = if (seasonNum > 0) TvType.TvSeries else TvType.Movie, + epNumber = epNum, + seasonNumber = seasonNum, + year = yearNum + ) + ) + } + + val document = app.get("$host/?search=$queryText").document + + document.select("div.my-3.p-3 div.media").map { block -> + if (seasonNum > 0) { + val name = block.select("strong.text-primary").text().trim() + val season = getOrdinal(seasonNum) + if ((block.selectFirst("a")?.attr("href") + ?.contains( + "$season", + ignoreCase = true + )!! || name.contains( + "$season", + ignoreCase = true + )) && name.contains(queryText, ignoreCase = true) + ) { + block.select("div.media").mapNotNull { + urlItems.add( + fixUrl( + it.selectFirst("a")!!.attr("href") + ) + ) + } + } + } else { + if (block.selectFirst("strong")!!.text().trim() + .matches(Regex("(?i)^$queryText\$")) + ) { + if (block.select("span[title=Release]").isNullOrEmpty()) { + block.select("div.media").mapNotNull { + val urlItem = fixUrl( + it.selectFirst("a")!!.attr("href") + ) + val itemDoc = app.get(urlItem).document + val id = imdbUrlToIdNullable(itemDoc.selectFirst("div.d-flex span.badge.badge-primary")?.parent() + ?.attr("href"))?.toLongOrNull() + val year = itemDoc.selectFirst("div.d-flex span.badge.badge-success") + ?.ownText() + ?.trim().toString() + Log.i(TAG, "id => $id \nyear => $year||$yearNum") + if (imdbId > 0) { + if (id == imdbId) { + urlItems.add(urlItem) + } + } else { + if (year.contains("$yearNum")) { + urlItems.add(urlItem) + } + } + } + } else { + if (block.select("span[title=Release]").text().trim() + .contains("$yearNum") + ) { + block.select("div.media").mapNotNull { + urlItems.add( + fixUrl( + it.selectFirst("a")!!.attr("href") + ) + ) + } + } + } + } + } + } + Log.i(TAG, "urlItems => $urlItems") + val results = mutableListOf() + + urlItems.forEach { url -> + val request = app.get(url) + if (request.isSuccessful) { + request.document.select("div.my-3.p-3 div.media").map { block -> + if (block.select("span.d-block span[data-original-title=Language]").text().trim() + .contains("$queryLang") + ) { + var name = block.select("strong.text-primary").text().trim() + val link = fixUrl(block.selectFirst("a")!!.attr("href")) + if(seasonNum > 0) { + when { + isRightEps(name, seasonNum, epNum) -> { + cleanResources(results, name, link) + } + !(haveEps(name)) -> { + name = "$name (S${seasonNum}:E${epNum})" + cleanResources(results, name, link) + } + } + } else { + cleanResources(results, name, link) + } + } + } + } + } + return results + } + + override suspend fun load(data: AbstractSubtitleEntities.SubtitleEntity): String? { + val seasonNum = data.seasonNumber + val epNum = data.epNumber + + val req = app.get(data.data) + + if(req.isSuccessful) { + val document = req.document + val link = if (document.select("div.my-3.p-3 div.media").size == 1) { + fixUrl( + document.selectFirst("div.my-3.p-3 div.media a")!!.attr("href") + ) + } else { + document.select("div.my-3.p-3 div.media").mapNotNull { block -> + val name = block.selectFirst("strong.d-block.text-primary")?.text()?.trim().toString() + if (seasonNum!! > 0) { + if (isRightEps(name, seasonNum, epNum)) { + fixUrl(block.selectFirst("a")!!.attr("href")) + } else { + null + } + } else { + fixUrl(block.selectFirst("a")!!.attr("href")) + } + }.first() + } + return link + } + + return null + + } + +} \ No newline at end of file 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 e2b76354..aaeaabdf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -216,11 +216,23 @@ val extractorApis: Array = arrayOf( Blogger(), Solidfiles(), + YourUpload(), Hxfile(), KotakAnimeid(), Neonime8n(), Neonime7n(), + Yufiles(), + Aico(), + + JWPlayer(), + Meownime(), + DesuArcg(), + DesuOdchan(), + DesuOdvip(), + DesuDrive(), + + Filesim(), YoutubeExtractor(), YoutubeShortLinkExtractor(),