diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 8aff2b49..214324aa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -76,6 +76,7 @@ object APIHolder { TwoEmbedProvider(), DramaSeeProvider(), WatchAsianProvider(), + DramaidProvider(), KdramaHoodProvider(), AkwamProvider(), MyCimaProvider(), @@ -111,6 +112,12 @@ object APIHolder { DubbedAnimeProvider(), MonoschinosProvider(), KawaiifuProvider(), // disabled due to cloudflare + NeonimeProvider(), + KuramanimeProvider(), + OploverzProvider(), + GomunimeProvider(), + NontonAnimeIDProvider(), + KuronimeProvider(), //MultiAnimeProvider(), NginxProvider(), ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt new file mode 100644 index 00000000..98691fc2 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt @@ -0,0 +1,247 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import java.util.* +import com.fasterxml.jackson.module.kotlin.readValue +import com.lagradost.cloudstream3.movieproviders.SflixProvider +import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.extractRabbitStream +import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.toExtractorLink +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.nicehttp.Requests.Companion.await +import kotlinx.coroutines.runBlocking +import okhttp3.Interceptor +import java.net.URI + +class GomunimeProvider : MainAPI() { + override var mainUrl = "https://185.231.223.76" + override var name = "Gomunime" + override val hasQuickSearch = false + override val hasMainPage = true + override val 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 + } + } + } + + private data class Response( + @JsonProperty("status") val status: Boolean, + @JsonProperty("html") val html: String + ) + + override suspend fun getMainPage(): HomePageResponse { + val urls = listOf( + Pair("e", "Episode Baru"), + Pair("c", "Completed"), + Pair("la", "Live Action"), + Pair("t", "Trending"), + ) + + val items = ArrayList() + + for ((payload, name) in urls) { + try { + val home = Jsoup.parse( + parseJson( + app.post( + url = "$mainUrl/wp-admin/admin-ajax.php/wp-admin/admin-ajax.php", + headers = mapOf("Referer" to mainUrl), + data = mapOf("action" to "home_ajax", "fungsi" to payload, "pag" to "1") + ).text + ).html + ).select("li").map { + val title = it.selectFirst("a.name")!!.text().trim() + val href = getProperAnimeLink(it.selectFirst("a")!!.attr("href")) + val posterUrl = it.selectFirst("img")!!.attr("src") + val type = getType(it.selectFirst(".taglist > span")!!.text().trim()) + val epNum = it.select(".tag.ep").text().replace(Regex("[^0-9]"), "").trim() + .toIntOrNull() + newAnimeSearchResponse(title, href, type) { + this.posterUrl = posterUrl + addDubStatus(dubExist = false, subExist = true, subEpisodes = epNum) + } + } + items.add(HomePageList(name, home)) + } catch (e: Exception) { + logError(e) + } + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + private fun getProperAnimeLink(uri: String): String { + return if (uri.contains("-episode")) { + val href = + "$mainUrl/anime/" + Regex("\\w\\d/(.*)-episode.*").find(uri)?.groupValues?.get(1) + .toString() + when { + href.contains("pokemon") -> href.replace(Regex("-[0-9]+"), "") + else -> href + } + } else { + uri + } + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query" + val document = app.get(link).document + + return document.select(".anime-list > li").map { + val title = it.selectFirst("a.name")!!.text() + val poster = it.selectFirst("img")!!.attr("src") + val tvType = getType(it.selectFirst(".taglist > span")?.text().toString()) + val href = fixUrl(it.selectFirst("a.name")!!.attr("href")) + + newAnimeSearchResponse(title, href, tvType) { + this.posterUrl = poster + addDubStatus(dubExist = false, subExist = true) + } + } + } + + private data class EpisodeElement( + @JsonProperty("data-index") val dataIndex: Long?, + @JsonProperty("ep-num") val epNum: String?, + @JsonProperty("ep-title") val epTitle: String?, + @JsonProperty("ep-link") val epLink: String, + @JsonProperty("ep-date") val epDate: String? + ) + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst(".entry-title")?.text().toString() + val poster = document.selectFirst(".thumbposter > img")?.attr("data-lazy-src") + val tags = document.select(".genxed > a").map { it.text() } + + val year = Regex("\\d, ([0-9]*)").find( + document.select("time[itemprop = datePublished]").text() + )?.groupValues?.get(1)?.toIntOrNull() + val status = getStatus(document.selectFirst(".spe > span")!!.ownText()) + val description = document.select("div[itemprop = description] > p").text() + + val episodes = parseJson>( + Regex("var episodelist = (\\[.*])").find( + document.select(".bixbox.bxcl.epcheck > script").toString().trim() + )?.groupValues?.get(1).toString().replace(Regex("""\\"""), "").trim() + ).map { + val name = it.epTitle + val link = it.epLink + Episode(link, name) + }.reversed() + + return newAnimeLoadResponse(title, url, TvType.Anime) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + this.tags = tags + } + } + + private fun invokeSource( + source: String, + sourceCallback: (ExtractorLink) -> Unit + ) { + M3u8Helper.generateM3u8( + source = this.name, + streamUrl = source, + referer = "$mainUrl/", + name = this.name, + ).forEach(sourceCallback) + } + + data class MobiSource( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String, + @JsonProperty("type") val type: 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("aside.sidebar > script").dataNodes().toString() + val key = scriptData.substringAfter("var a_ray = '").substringBefore("';") + val title = scriptData.substringAfter("var judul_postingan = \"").substringBefore("\";") + + val sources: List> = app.post( + url = "https://path.gomuni.me/app/vapi.php", + data = mapOf("data" to key, "judul" to title, "func" to "mirror") + ).document.select("div.gomunime-server-mirror").map { + Pair( + it.attr("data-vhash"), + it.attr("data-type") + ) + } + + sources.apmap { + safeApiCall { + when { + it.second.contains("frame") -> { + loadExtractor(it.first, data, callback) + } + it.second.contains("hls") -> { + app.post( + url = "https://path.gomuni.me/app/vapi.php", + data = mapOf("fid" to it.first, "func" to "hls") + ).text.let { link -> + invokeSource(link, callback) + } + } + else -> { + app.post( + url = "https://path.gomuni.me/app/vapi.php", + data = mapOf("data" to it.first, "func" to "blogs") + ).parsed>().map { + callback( + ExtractorLink( + source = name, + name = "Mobi SD", + url = it.file, + referer = "$mainUrl/", + quality = Qualities.P360.value + ) + ) + } + } + } + } + } + + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt new file mode 100644 index 00000000..e41e9119 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt @@ -0,0 +1,211 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.network.DdosGuardKiller +import com.lagradost.cloudstream3.utils.ExtractorLink +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import java.util.* + +class KuramanimeProvider : MainAPI() { + override var mainUrl = "https://kuramanime.com" + override var name = "Kuramanime" + override val hasQuickSearch = false + override val hasMainPage = true + override val 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) { + "Selesai Tayang" -> ShowStatus.Completed + "Sedang Tayang" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + override suspend fun getMainPage(): HomePageResponse { + val document = app.get(mainUrl).document + + 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 { + 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 { + 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) + } + + private fun getProperAnimeLink(uri: String): String { + return if (uri.contains("/episode")) { + Regex("(.*)/episode/.+").find(uri)?.groupValues?.get(1).toString() + "/" + } else { + uri + } + } + + private fun Element.toSearchResult(): SearchResponse { + 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")) + + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addDubStatus(dubExist = false, subExist = true) + } + + } + + 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()) + + return newAnimeSearchResponse(title, href, type) { + this.posterUrl = posterUrl + addDubStatus(dubExist = false, subExist = true) + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/anime?search=$query&order_by=oldest" + val document = app.get(link).document + + return document.select(".product__item").mapNotNull { + val title = it.selectFirst("div.product__item__text > h5")!!.text().trim() + val poster = it.selectFirst("a > div")!!.attr("data-setbg") + val tvType = + getType(it.selectFirst(".product__item__text > ul > li")!!.text().toString()) + val href = fixUrl(it.selectFirst("a")!!.attr("href")) + + newAnimeSearchResponse(title, href, tvType) { + this.posterUrl = poster + addDubStatus(dubExist = false, subExist = true) + } + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst(".anime__details__title > h3")!!.text().trim() + val poster = document.selectFirst(".anime__details__pic")?.attr("data-setbg") + val tags = + document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)") + .text().trim().replace("Genre: ", "").split(", ") + + val year = Regex("[^0-9]").replace( + document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(5)") + .text().trim().replace("Musim: ", ""), "" + ).toIntOrNull() + val status = getStatus( + document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(3)") + .text().trim().replace("Status: ", "") + ) + val description = document.select(".anime__details__text > p").text().trim() + + val episodes = + Jsoup.parse(document.select("#episodeLists").attr("data-content")).select("a").map { + val name = it.text().trim() + val link = it.attr("href") + Episode(link, name) + } + + val recommendations = document.select("div#randomList > a").mapNotNull { + val epHref = it.attr("href") + val epTitle = it.select("h5.sidebar-title-h5.px-2.py-2").text() + val epPoster = it.select(".product__sidebar__view__item.set-bg").attr("data-setbg") + + newAnimeSearchResponse(epTitle, epHref, TvType.Anime) { + this.posterUrl = epPoster + addDubStatus(dubExist = false, subExist = true) + } + } + + return newAnimeLoadResponse(title, url, TvType.Anime) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + this.tags = tags + this.recommendations = recommendations + } + + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val servers = app.get(data, interceptor = DdosGuardKiller(true)).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 + ) + ) + } + + return true + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt new file mode 100644 index 00000000..bf59fd34 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt @@ -0,0 +1,195 @@ +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.safeApiCall +import com.lagradost.cloudstream3.utils.* +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import java.util.ArrayList + +class KuronimeProvider : MainAPI() { + override var mainUrl = "https://185.231.223.254" + override var name = "Kuronime" + override val hasQuickSearch = false + override val hasMainPage = true + override val 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(".bixbox").forEach { block -> + val header = block.select(".releases > h3").text().trim() + val animes = block.select("article").mapNotNull { + it.toSearchResult() + } + if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) + } + + 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("nonton-(.+)-episode").find(title)?.groupValues?.get(1).toString() + (title.contains("-movie")) -> Regex("nonton-(.+)-movie").find(title)?.groupValues?.get(1).toString() + else -> title + } + + "$mainUrl/anime/$title" + } + } + + private fun Element.toSearchResult(): SearchResponse { + val href = getProperAnimeLink(fixUrl(this.select("a").attr("href"))) + val title = this.select(".bsuxtt, .tt > h4").text().trim() + val posterUrl = fixUrl(this.select("img").attr("src")) + val epNum = this.select(".ep").text().replace(Regex("[^0-9]"), "").trim().toIntOrNull() + + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addDubStatus(dubExist = false, subExist = true, subEpisodes = epNum) + } + + } + + override suspend fun search(query: String): List { + 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) + } + } + } + + override suspend fun load(url: String): LoadResponse { + 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 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 year = Regex("\\d, ([0-9]*)").find( + document.select(".infodetail > ul > li:nth-child(5)").text() + )?.groupValues?.get(1)?.toIntOrNull() + val status = getStatus(document.selectFirst(".infodetail > ul > li:nth-child(3)")!!.ownText().replace(Regex("\\W"), "")) + 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 link = it.selectFirst("a")!!.attr("href") + Episode(link, name) + }.reversed() + + return newAnimeLoadResponse(title, url, type) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + addTrailer(trailer) + this.tags = tags + trailers = listOf(trailer) + } + } + + private suspend fun invokeKuroSource( + url: String, + sourceCallback: (ExtractorLink) -> Unit + ) { + val doc = app.get(url).document + + doc.select("script").map { script -> + if (script.data().contains("function jalankan_jwp() {")) { + val data = script.data() + val doma = data.substringAfter("var doma = \"").substringBefore("\";") + val token = data.substringAfter("var token = \"").substringBefore("\";") + val pat = data.substringAfter("var pat = \"").substringBefore("\";") + val link = "$doma$token$pat/index.m3u8" + + sourceCallback.invoke( + ExtractorLink( + this.name, + this.name, + link, + referer = "https://animeku.org/", + quality = Qualities.Unknown.value, + isM3u8 = link.contains(".m3u8") + ) + ) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val document = app.get(data).document + val iframeLink = document.select(".mobius > .mirror > option").mapNotNull { + fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("src")) + } + + iframeLink.map { + it.replace("https://ok.ru", "http://ok.ru") + }.apmap { + safeApiCall { + when { + it.contains("hxfile.co") -> invokeLocalSource( + it, + this.name, + sourceCallback = callback + ) +// it.contains("animeku.org") -> invokeKuroSource(it, callback) + else -> loadExtractor(it, mainUrl, callback) + } + } + } + return true + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt new file mode 100644 index 00000000..426826be --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt @@ -0,0 +1,190 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.nodes.Element +import java.util.* + +class NeonimeProvider : MainAPI() { + override var mainUrl = "https://neonime.watch" + override var name = "Neonime" + override val hasQuickSearch = false + override val hasMainPage = true + override val 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) { + "Ended" -> ShowStatus.Completed + "OnGoing" -> ShowStatus.Ongoing + "In Production" -> ShowStatus.Ongoing + "Returning Series" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + override suspend fun getMainPage(): HomePageResponse { + 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 { + it.toSearchResult() + } + if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) + } + + return HomePageResponse(homePageList) + } + + 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 + } + } + else -> uri + } + } + + private fun Element.toSearchResult(): SearchResponse { + val href = getProperAnimeLink(fixUrl(this.select("a").attr("href"))) + val title = this.select("span.tt.title-episode,h2.title-episode-movie").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) + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query" + val document = app.get(link).document + + return document.select("div.item.episode-home").mapNotNull { + val title = it.selectFirst("div.judul-anime > span")!!.text() + val poster = it.select("img").attr("data-src") + val episodes = it.selectFirst("div.fixyear > h2.text-center")!! + .text().replace(Regex("[^0-9]"), "").trim().toIntOrNull() + val tvType = getType(it.selectFirst("span.calidad2.episode")?.text().toString()) + val href = getProperAnimeLink(fixUrl(it.selectFirst("a")!!.attr("href"))) + + newAnimeSearchResponse(title, href, tvType) { + this.posterUrl = poster + addDubStatus(dubExist = false, subExist = true, subEpisodes = episodes) + } + } + } + + data class NeonimeSyncData( + @JsonProperty("mal_id") val malId: String?, + @JsonProperty("anilist_id") val aniListId: String?, + ) + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + if (url.contains("movie") || url.contains("live-action")) { + val mTitle = document.selectFirst(".sbox > .data > h1[itemprop = name]")?.text().toString().trim() + val mPoster = + document.selectFirst(".sbox > .imagen > .fix > img[itemprop = image]")?.attr("data-src") + val mTags = document.select("p.meta_dd > a").map { it.text() } + 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() + + return MovieLoadResponse( + name = mTitle, + url = url, + this.name, + type = TvType.Movie, + dataUrl = url, + posterUrl = mPoster, + year = mYear, + plot = mDescription, + rating = mRating, + tags = mTags + ) + } + else { + val title = document.select("h1[itemprop = name]").text().trim() + val poster = document.selectFirst(".imagen > img")?.attr("data-src") + val tags = document.select("#info a[href*=\"-genre/\"]").map { it.text() } + 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 episodes = document.select("ul.episodios > li").mapNotNull { + val name = it.selectFirst(".episodiotitle > a")!!.ownText().trim() + val link = fixUrl(it.selectFirst(".episodiotitle > a")!!.attr("href")) + Episode(link, name) + }.reversed() + + return newAnimeLoadResponse(title, url, TvType.Anime) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + this.tags = tags + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val source = if(data.contains("movie") || data.contains("live-action")) { + app.get(data).document.select("#player2-1 > div[id*=div]").mapNotNull { + fixUrl(it.select("iframe").attr("data-src")) + } + } else { + app.get(data).document.select(".player2 > .embed2 > div[id*=player]").mapNotNull { + fixUrl(it.select("iframe").attr("data-src")) + } + } + + source.map { + it.replace("https://ok.ru", "http://ok.ru") + }.apmap { + when { + it.contains("blogger.com") -> invokeBloggerSource(it, callback) + it.contains("7njctn.neonime.watch") || it.contains("8njctn.neonime.net") -> invokeLocalSource(it, mainUrl, redirect = false, callback) + else -> loadExtractor(it, data, callback) + } + } + + return true + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt new file mode 100644 index 00000000..0b4d6318 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt @@ -0,0 +1,347 @@ +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.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() { + override var mainUrl = "https://75.119.159.228" + override var name = "NontonAnimeID" + override val hasQuickSearch = false + override val hasMainPage = true + override val lang = "id" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + fun getType(t: String): TvType { + return when { + t.contains("TV") -> TvType.Anime + t.contains("Movie") -> TvType.AnimeMovie + else -> TvType.OVA + } + } + + fun getStatus(t: String): ShowStatus { + return when (t) { + "Finished Airing" -> ShowStatus.Completed + "Currently Airing" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + override suspend fun getMainPage(): HomePageResponse { + val document = app.get(mainUrl).document + + val homePageList = ArrayList() + + document.select("section#postbaru").forEach { block -> + val header = block.selectFirst("h2")!!.text().trim() + val animes = block.select("article.animeseries").mapNotNull { + it.toSearchResult() + } + if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) + } + + document.select("aside#sidebar_right > div.side:nth-child(2)").forEach { block -> + val header = block.selectFirst("h3")!!.ownText().trim() + val animes = block.select("li.fullwdth").mapNotNull { + it.toSearchResultPopular() + } + if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) + } + + return HomePageResponse(homePageList) + } + + private fun getProperAnimeLink(uri: String): String { + 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 { + "$mainUrl/anime/$name" + } + } + } + + private fun Element.toSearchResult(): SearchResponse { + 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")) + + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addDubStatus(dubExist = false, subExist = true) + } + + } + + private fun Element.toSearchResultPopular(): SearchResponse { + 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")) + + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addDubStatus(dubExist = false, subExist = true) + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query" + val document = app.get(link).document + + return document.select(".result > ul > li").mapNotNull { + val title = it.selectFirst("h2")!!.text().trim() + val poster = it.selectFirst("img")!!.attr("src") + val tvType = getType( + it.selectFirst(".boxinfores > span.typeseries")!!.text().toString() + ) + val href = fixUrl(it.selectFirst("a")!!.attr("href")) + + newAnimeSearchResponse(title, href, tvType) { + this.posterUrl = poster + addDubStatus(dubExist = false, subExist = true) + } + } + } + + private data class EpResponse( + @JsonProperty("posts") val posts: String?, + @JsonProperty("max_page") val max_page: Int?, + @JsonProperty("found_posts") val found_posts: Int?, + @JsonProperty("content") val content: String + ) + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("h1.entry-title.cs")!!.text().trim() + val poster = document.selectFirst(".poster > img")?.attr("data-src") + val tags = document.select(".tagline > a").map { it.text() } + + val year = Regex("\\d, ([0-9]*)").find( + document.select(".bottomtitle > span:nth-child(5)").text() + )?.groupValues?.get(1)?.toIntOrNull() + val status = getStatus( + document.select("span.statusseries").text().trim() + ) + 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 episodes = if (document.select("button.buttfilter").isNotEmpty()) { + val id = document.select("input[name=series_id]").attr("value") + val numEp = + document.selectFirst(".latestepisode > a")?.text()?.replace(Regex("[^0-9]"), "") + .toString() + Jsoup.parse( + app.post( + url = "$mainUrl/wp-admin/admin-ajax.php", + data = mapOf( + "misha_number_of_results" to numEp, + "misha_order_by" to "date-DESC", + "action" to "mishafilter", + "series_id" to id + ) + ).parsed().content + ).select("li").map { + val engName = + document.selectFirst("div.bottomtitle:nth-child(4) > span:nth-child(1)") + ?.ownText() + val name = it.selectFirst("span.t1")!!.text().trim().replace("Episode", "$engName") + val link = it.selectFirst("a")!!.attr("href") + Episode(link, name) + }.reversed() + } else { + document.select("ul.misha_posts_wrap2 > li").map { + val name = it.select("span.t1").text().trim() + val link = it.select("a").attr("href") + Episode(link, name) + }.reversed() + } + + + val recommendations = document.select(".result > li").mapNotNull { + val epHref = it.selectFirst("a")!!.attr("href") + val epTitle = it.selectFirst("h3")!!.text() + val epPoster = it.select(".top > img").attr("data-src") + + newAnimeSearchResponse(epTitle, epHref, TvType.Anime) { + this.posterUrl = epPoster + addDubStatus(dubExist = false, subExist = true) + } + } + + return newAnimeLoadResponse(title, url, type) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + this.rating = rating + plot = description + addTrailer(trailer) + this.tags = tags + this.recommendations = recommendations + } + + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = app.get(data).document + val sources = ArrayList() + + document.select(".container1 > ul > li").apmap { + val dataPost = it.attr("data-post") + val dataNume = it.attr("data-nume") + val dataType = it.attr("data-type") + + val iframe = app.post( + url = "$mainUrl/wp-admin/admin-ajax.php", + data = mapOf( + "action" to "player_ajax", + "post" to dataPost, + "nume" to dataNume, + "type" to dataType + ) + ).document.select("iframe").attr("src") + + sources.add(fixUrl(iframe)) + } + + sources.map { + it.replace("https://ok.ru", "http://ok.ru") + }.apmap { + when { + it.contains("blogger.com") -> invokeBloggerSource(it, callback) + it.contains("kotakanimeid.com") -> invokeLocalSource( + it, + this.name, + sourceCallback = callback + ) + else -> loadExtractor(it, data, callback) + } + } + + return true + } +} + +// re-use as extractorApis + +suspend fun invokeBloggerSource( + url: String, + sourceCallback: (ExtractorLink) -> Unit +) { + val doc = app.get(url).document + val sourceName = Regex("[^w{3}]\\.?(.+)\\.").find(URI(url).host)?.groupValues?.get(1).toString() + .replace(Regex("\\w+\\."), "").replaceFirstChar { it.uppercase() } + + val server = + doc.selectFirst("script")?.data()!!.substringAfter("\"streams\":[").substringBefore("]") + tryParseJson>("[$server]")?.map { + sourceCallback.invoke( + ExtractorLink( + sourceName, + sourceName, + it.play_url, + referer = "https://www.youtube.com/", + quality = when (it.format_id) { + 18 -> 360 + 22 -> 720 + else -> Qualities.Unknown.value + } + ) + ) + } +} + +data class BloggerSource( + @JsonProperty("play_url") val play_url: String, + @JsonProperty("format_id") val format_id: Int +) + +suspend fun invokeLocalSource( + source: String, + ref: String, + redirect: Boolean = true, + sourceCallback: (ExtractorLink) -> Unit +) { + val doc = app.get(source, allowRedirects = redirect).document + val sourceName = + Regex("[^w{3}]\\.?(.+)\\.").find(URI(source).host)?.groupValues?.get(1).toString() + .replace(Regex("\\w+\\."), "").replaceFirstChar { it.uppercase() } + + doc.select("script").map { script -> + if (script.data().contains("eval(function(p,a,c,k,e,d)")) { + val data = getAndUnpack(script.data()) + val server = data.substringAfter("sources:[").substringBefore("]") + tryParseJson>("[$server]")?.map { + sourceCallback.invoke( + ExtractorLink( + sourceName, + sourceName, + it.file, + referer = ref, + quality = when { + source.contains("hxfile.co") -> getQualityFromName( + Regex("\\d\\.(.*?).mp4").find( + doc.select("title").text() + )?.groupValues?.get(1).toString() + ) + else -> getQualityFromName(it.label) + } + + ) + ) + } + } else { + if (script.data().contains("\"sources\":[")) { + val server = script.data().substringAfter("\"sources\":[").substringBefore("]") + tryParseJson>("[$server]")?.map { + sourceCallback.invoke( + ExtractorLink( + sourceName, + sourceName, + it.file, + referer = ref, + quality = getQualityFromName(it.label) + ) + ) + } + } else { + // skip for now + } + } + } +} + +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/animeproviders/OploverzProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt new file mode 100644 index 00000000..ed064940 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt @@ -0,0 +1,206 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +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 OploverzProvider : MainAPI() { + override var mainUrl = "https://oploverz.asia" + override var name = "Oploverz" + override val hasQuickSearch = false + override val hasMainPage = true + override val lang = "id" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + fun getType(t: String): TvType { + return when { + t.contains("TV") -> TvType.Anime + t.contains("Movie") -> TvType.AnimeMovie + else -> TvType.OVA + } + } + + 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(".bixbox.bbnofrm").forEach { block -> + val header = block.selectFirst("h3")!!.text().trim() + val animes = block.select("article[itemscope=itemscope]").mapNotNull { + it.toSearchResult() + } + if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) + } + + 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("-ova")) -> Regex("(.+)-episode").find( + title + )?.groupValues?.get(1).toString() + (title.contains("-ova")) -> Regex("(.+)-ova").find(title)?.groupValues?.get(1) + .toString() + else -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1).toString() + .replace(Regex("-\\d+"), "") + } + + when { + title.contains("overlord") -> { + title = title.replace("s", "season-") + } + title.contains("kaguya-sama") -> { + title = title.replace("s3", "ultra-romantic") + } + } + + "$mainUrl/anime/$title" + } + + } + + private fun Element.toSearchResult(): SearchResponse { + 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")) + val type = getType(this.selectFirst(".eggtype, .typez")!!.text().trim()) + val epNum = + this.selectFirst(".eggepisode, span.epx")!!.text().replace(Regex("[^0-9]"), "").trim() + .toIntOrNull() + + return newAnimeSearchResponse(title, href, type) { + this.posterUrl = posterUrl + addDubStatus(dubExist = false, subExist = true, subEpisodes = epNum) + } + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query" + val document = app.get(link).document + + return document.select("article[itemscope=itemscope]").map { + val title = it.selectFirst(".tt")!!.ownText().trim() + val poster = it.selectFirst("img")!!.attr("src") + val tvType = getType(it.selectFirst(".typez")?.text().toString()) + val href = fixUrl(it.selectFirst("a.tip")!!.attr("href")) + + newAnimeSearchResponse(title, href, tvType) { + this.posterUrl = poster + addDubStatus(dubExist = false, subExist = true) + } + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("h1.entry-title")!!.text().trim() + val poster = document.select(".thumb > img").attr("src") + val tags = document.select(".genxed > a").map { it.text() } + + val year = Regex("\\d, ([0-9]*)").find( + document.selectFirst(".info-content > .spe > span > time")!!.text().trim() + )?.groupValues?.get(1).toString().toIntOrNull() + val status = getStatus( + document.select(".info-content > .spe > span:nth-child(1)") + .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" + else -> "OVA" + } + val type = getType(typeCheck) + val description = document.select(".entry-content > p").text().trim() + + val episodes = document.select(".eplister > ul > li").map { + val name = it.select(".epl-title").text().trim() + val link = fixUrl(it.select("a").attr("href")) + Episode(link, name) + }.reversed() + + val recommendations = + document.select(".listupd > article[itemscope=itemscope]").mapNotNull { rec -> + val epTitle = rec.selectFirst(".tt")!!.ownText().trim() + val epPoster = rec.selectFirst("img")!!.attr("src") + val epType = getType(rec.selectFirst(".typez")?.text().toString()) + val epHref = fixUrl(rec.selectFirst("a.tip")!!.attr("href")) + + newAnimeSearchResponse(epTitle, epHref, epType) { + this.posterUrl = epPoster + addDubStatus(dubExist = false, subExist = true) + } + } + + 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 Source( + @JsonProperty("play_url") val play_url: String, + @JsonProperty("format_id") val format_id: Int + ) + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val document = app.get(data).document + val iframeLink = document.select(".mobius > .mirror > option").mapNotNull { + fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("src")) + } + + iframeLink.map { + it.replace("https://ok.ru", "http://ok.ru") + }.apmap { + when { + it.contains("blogger.com") -> invokeBloggerSource(it, callback) + else -> loadExtractor(it, data, callback) + } + } + + return true + } + +} \ 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 new file mode 100644 index 00000000..dc4bc9e6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt @@ -0,0 +1,217 @@ +package com.lagradost.cloudstream3.movieproviders + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.animeproviders.invokeBloggerSource +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import java.util.ArrayList + +class DramaidProvider : MainAPI() { + override var mainUrl = "https://185.224.83.103" + override var name = "DramaId" + override val hasQuickSearch = false + override val hasMainPage = true + override val lang = "id" + override val hasDownloadSupport = true + override val hasChromecastSupport = false + override val supportedTypes = setOf(TvType.AsianDrama) + + companion object { + 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(".bixbox").forEach { block -> + val header = block.selectFirst(".releases > h3")!!.text().trim() + val dramas = block.select("article[itemscope=itemscope]").mapNotNull { + it.toSearchResult() + } + if (dramas.isNotEmpty()) homePageList.add(HomePageList(header, dramas)) + } + + return HomePageResponse(homePageList) + } + + private fun getProperDramaLink(uri: String): String { + return if (uri.contains("/series/")) { + uri + } else { + "$mainUrl/series/" + Regex("$mainUrl/(.+)-ep.+").find(uri)?.groupValues?.get(1) + .toString() + } + } + + private fun Element.toSearchResult(): SearchResponse { + val href = getProperDramaLink(this.selectFirst("a.tip")!!.attr("href")) + val title = this.selectFirst("h2[itemprop=headline]")!!.text().trim() + val posterUrl = this.selectFirst(".limit > noscript > img")!!.attr("src") + + return newTvSeriesSearchResponse(title, href, TvType.AsianDrama) { + this.posterUrl = posterUrl + } + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query" + val document = app.get(link).document + + return document.select("article[itemscope=itemscope]").map { + val title = it.selectFirst("h2[itemprop=headline]")!!.text().trim() + val poster = it.selectFirst(".limit > noscript > img")!!.attr("src") + val href = it.selectFirst("a.tip")!!.attr("href") + + newTvSeriesSearchResponse(title, href, TvType.AsianDrama) { + this.posterUrl = poster + } + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("h1.entry-title")!!.text().trim() + val poster = document.select(".thumb > noscript > img").attr("src") + val tags = document.select(".genxed > a").map { it.text() } + + val year = Regex("\\d, ([0-9]*)").find( + document.selectFirst(".info-content > .spe > span > time")!!.text().trim() + )?.groupValues?.get(1).toString().toIntOrNull() + val status = getStatus( + document.select(".info-content > .spe > span:nth-child(1)") + .text().trim().replace("Status: ", "") + ) + val description = document.select(".entry-content > p").text().trim() + + val episodes = document.select(".eplister > ul > li").map { + val name = it.selectFirst("a > .epl-title")!!.text().trim() + val link = it.select("a").attr("href") + val epNum = it.selectFirst("a > .epl-num")!!.text().trim().toIntOrNull() + newEpisode(link) { + this.name = name + this.episode = epNum + } + }.reversed() + + val recommendations = + document.select(".listupd > article[itemscope=itemscope]").map { rec -> + val epTitle = rec.selectFirst("h2[itemprop=headline]")!!.text().trim() + val epPoster = rec.selectFirst(".limit > noscript > img")!!.attr("src") + val epHref = fixUrl(rec.selectFirst("a.tip")!!.attr("href")) + + newTvSeriesSearchResponse(epTitle, epHref, TvType.AsianDrama) { + this.posterUrl = epPoster + } + } + + if (episodes.size == 1) { + return newMovieLoadResponse(title, url, TvType.Movie, episodes[0].data) { + posterUrl = poster + this.year = year + plot = description + this.tags = tags + this.recommendations = recommendations + } + } else { + return newTvSeriesLoadResponse(title, url, TvType.AsianDrama, episodes = episodes) { + posterUrl = poster + this.year = year + showStatus = status + plot = description + this.tags = tags + this.recommendations = recommendations + } + } + + } + + private data class Sources( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String, + @JsonProperty("type") val type: String, + @JsonProperty("default") val default: Boolean? + ) + + private data class Tracks( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String, + @JsonProperty("kind") val type: String, + @JsonProperty("default") val default: Boolean? + ) + + private suspend fun invokeDriveSource( + url: String, + name: String, + subCallback: (SubtitleFile) -> Unit, + sourceCallback: (ExtractorLink) -> Unit + ) { + val server = app.get(url).document.selectFirst(".picasa")?.nextElementSibling()?.data() + + val source = "[${server!!.substringAfter("sources: [").substringBefore("],")}]".trimIndent() + val trackers = server.substringAfter("tracks:[").substringBefore("],") + .replace("//language", "") + .replace("file", "\"file\"") + .replace("label", "\"label\"") + .replace("kind", "\"kind\"").trimIndent() + + tryParseJson>(source)?.map { + sourceCallback( + ExtractorLink( + name, + "Drive", + fixUrl(it.file), + referer = "https://motonews.club/", + quality = getQualityFromName(it.label) + ) + ) + } + + tryParseJson(trackers)?.let { + subCallback( + SubtitleFile( + if (it.label.contains("Indonesia")) "${it.label}n" else it.label, + it.file + ) + ) + } + + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val document = app.get(data).document + val iframeLink = document.select(".mobius > .mirror > option").mapNotNull { + fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("src")) + } + + iframeLink.map { + it.replace("https://ndrama.xyz", "https://www.fembed.com") + }.apmap { + when { + it.contains("motonews.club") -> invokeDriveSource(it, this.name, subtitleCallback, callback) + else -> loadExtractor(it, data, callback) + } + } + + return true + } + +}