forked from recloudstream/cloudstream
		
	Added 3 Indonesian Provider (#1045)
* 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..)
This commit is contained in:
		
							parent
							
								
									f3e73b1e3c
								
							
						
					
					
						commit
						7cdb902f80
					
				
					 8 changed files with 1620 additions and 0 deletions
				
			
		|  | @ -76,6 +76,7 @@ object APIHolder { | ||||||
|             TwoEmbedProvider(), |             TwoEmbedProvider(), | ||||||
|             DramaSeeProvider(), |             DramaSeeProvider(), | ||||||
|             WatchAsianProvider(), |             WatchAsianProvider(), | ||||||
|  | 	        DramaidProvider(), | ||||||
|             KdramaHoodProvider(), |             KdramaHoodProvider(), | ||||||
|             AkwamProvider(), |             AkwamProvider(), | ||||||
|             MyCimaProvider(), |             MyCimaProvider(), | ||||||
|  | @ -111,6 +112,12 @@ object APIHolder { | ||||||
|             DubbedAnimeProvider(), |             DubbedAnimeProvider(), | ||||||
|             MonoschinosProvider(), |             MonoschinosProvider(), | ||||||
|             KawaiifuProvider(), // disabled due to cloudflare |             KawaiifuProvider(), // disabled due to cloudflare | ||||||
|  | 	        NeonimeProvider(), | ||||||
|  |             KuramanimeProvider(), | ||||||
|  |             OploverzProvider(), | ||||||
|  |             GomunimeProvider(), | ||||||
|  |             NontonAnimeIDProvider(), | ||||||
|  |             KuronimeProvider(), | ||||||
|             //MultiAnimeProvider(), |             //MultiAnimeProvider(), | ||||||
| 	        NginxProvider(), | 	        NginxProvider(), | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -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<HomePageList>() | ||||||
|  | 
 | ||||||
|  |         for ((payload, name) in urls) { | ||||||
|  |             try { | ||||||
|  |                 val home = Jsoup.parse( | ||||||
|  |                     parseJson<Response>( | ||||||
|  |                         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<SearchResponse> { | ||||||
|  |         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<List<EpisodeElement>>( | ||||||
|  |             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<Pair<String, String>> = 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<List<MobiSource>>().map { | ||||||
|  |                             callback( | ||||||
|  |                                 ExtractorLink( | ||||||
|  |                                     source = name, | ||||||
|  |                                     name = "Mobi SD", | ||||||
|  |                                     url = it.file, | ||||||
|  |                                     referer = "$mainUrl/", | ||||||
|  |                                     quality = Qualities.P360.value | ||||||
|  |                                 ) | ||||||
|  |                             ) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -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<HomePageList>() | ||||||
|  | 
 | ||||||
|  |         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<SearchResponse> { | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -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<HomePageList>() | ||||||
|  | 
 | ||||||
|  |         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<SearchResponse> { | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -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<HomePageList>() | ||||||
|  | 
 | ||||||
|  |         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<SearchResponse> { | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -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<HomePageList>() | ||||||
|  | 
 | ||||||
|  |         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<SearchResponse> { | ||||||
|  |         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<EpResponse>().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<String>() | ||||||
|  | 
 | ||||||
|  |         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<List<BloggerSource>>("[$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<List<ResponseSource>>("[$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<List<ResponseSource>>("[$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? | ||||||
|  | ) | ||||||
|  | @ -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<HomePageList>() | ||||||
|  | 
 | ||||||
|  |         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<SearchResponse> { | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -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<HomePageList>() | ||||||
|  | 
 | ||||||
|  |         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<SearchResponse> { | ||||||
|  |         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<List<Sources>>(source)?.map { | ||||||
|  |             sourceCallback( | ||||||
|  |                 ExtractorLink( | ||||||
|  |                     name, | ||||||
|  |                     "Drive", | ||||||
|  |                     fixUrl(it.file), | ||||||
|  |                     referer = "https://motonews.club/", | ||||||
|  |                     quality = getQualityFromName(it.label) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         tryParseJson<Tracks>(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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue