From cf4fe53ec21060fcf44da7707114874c9c0c18e3 Mon Sep 17 00:00:00 2001 From: hexated Date: Sun, 11 Sep 2022 20:00:33 +0700 Subject: [PATCH] added new providers --- GoodPorn/build.gradle.kts | 25 +++ GoodPorn/src/main/AndroidManifest.xml | 2 + .../src/main/kotlin/com/hexated/GoodPorn.kt | 107 ++++++++++ .../main/kotlin/com/hexated/GoodPornPlugin.kt | 13 ++ Hentaiheaven/build.gradle.kts | 25 +++ Hentaiheaven/src/main/AndroidManifest.xml | 2 + .../main/kotlin/com/hexated/Hentaiheaven.kt | 156 ++++++++++++++ .../kotlin/com/hexated/HentaiheavenPlugin.kt | 13 ++ IMoviehd/build.gradle.kts | 26 +++ IMoviehd/src/main/AndroidManifest.xml | 2 + .../src/main/kotlin/com/hexated/IMoviehd.kt | 169 +++++++++++++++ .../main/kotlin/com/hexated/IMoviehdPlugin.kt | 14 ++ Movierulzhd/build.gradle.kts | 26 +++ Movierulzhd/src/main/AndroidManifest.xml | 2 + .../main/kotlin/com/hexated/Movierulzhd.kt | 197 ++++++++++++++++++ .../kotlin/com/hexated/MovierulzhdPlugin.kt | 14 ++ build.gradle.kts | 20 +- 17 files changed, 802 insertions(+), 11 deletions(-) create mode 100644 GoodPorn/build.gradle.kts create mode 100644 GoodPorn/src/main/AndroidManifest.xml create mode 100644 GoodPorn/src/main/kotlin/com/hexated/GoodPorn.kt create mode 100644 GoodPorn/src/main/kotlin/com/hexated/GoodPornPlugin.kt create mode 100644 Hentaiheaven/build.gradle.kts create mode 100644 Hentaiheaven/src/main/AndroidManifest.xml create mode 100644 Hentaiheaven/src/main/kotlin/com/hexated/Hentaiheaven.kt create mode 100644 Hentaiheaven/src/main/kotlin/com/hexated/HentaiheavenPlugin.kt create mode 100644 IMoviehd/build.gradle.kts create mode 100644 IMoviehd/src/main/AndroidManifest.xml create mode 100644 IMoviehd/src/main/kotlin/com/hexated/IMoviehd.kt create mode 100644 IMoviehd/src/main/kotlin/com/hexated/IMoviehdPlugin.kt create mode 100644 Movierulzhd/build.gradle.kts create mode 100644 Movierulzhd/src/main/AndroidManifest.xml create mode 100644 Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt create mode 100644 Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt diff --git a/GoodPorn/build.gradle.kts b/GoodPorn/build.gradle.kts new file mode 100644 index 00000000..34398fc9 --- /dev/null +++ b/GoodPorn/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "en" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "NSFW", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=goodporn.to&sz=%size%" +} \ No newline at end of file diff --git a/GoodPorn/src/main/AndroidManifest.xml b/GoodPorn/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/GoodPorn/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/GoodPorn/src/main/kotlin/com/hexated/GoodPorn.kt b/GoodPorn/src/main/kotlin/com/hexated/GoodPorn.kt new file mode 100644 index 00000000..96c79de3 --- /dev/null +++ b/GoodPorn/src/main/kotlin/com/hexated/GoodPorn.kt @@ -0,0 +1,107 @@ +package com.hexated + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.utils.* +import org.jsoup.nodes.Element + +class GoodPorn : MainAPI() { + override var mainUrl = "https://goodporn.to" + override var name = "GoodPorn" + override val hasMainPage = true + override val hasDownloadSupport = true + override val supportedTypes = setOf(TvType.NSFW) + + override val mainPage = mainPageOf( + "$mainUrl/?mode=async&function=get_block&block_id=list_videos_most_recent_videos&sort_by=post_date&from=" to "New Videos", + "$mainUrl/?mode=async&function=get_block&block_id=list_videos_most_recent_videos&sort_by=video_viewed&from=" to "Most Viewed Videos", + "$mainUrl/?mode=async&function=get_block&block_id=list_videos_most_recent_videos&sort_by=rating&from=" to "Top Rated Videos ", + "$mainUrl/?mode=async&function=get_block&block_id=list_videos_most_recent_videos&sort_by=most_commented&from=" to "Most Commented Videos ", + "$mainUrl/?mode=async&function=get_block&block_id=list_videos_most_recent_videos&sort_by=duration&from=" to "Longest Videos", + "$mainUrl/?mode=async&function=get_block&block_id=list_videos_most_recent_videos&sort_by=most_favourited&from=" to "Most Favourited Videos ", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + val home = document.select("div#list_videos_most_recent_videos_items div.item").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse( + list = HomePageList( + name = request.name, + list = home, + isHorizontalImages = true + ), + hasNext = true + ) + } + + private fun Element.toSearchResult(): SearchResponse? { + val title = this.selectFirst("strong.title")?.text() ?: return null + val href = fixUrl(this.selectFirst("a")!!.attr("href")) + val posterUrl = fixUrlNull(this.select("div.img > img").attr("data-original")) + return newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + } + + } + + override suspend fun search(query: String): List { + val document = app.get("$mainUrl/search/$query").document + return document.select("div#list_videos_videos_list_search_result_items div.item") + .mapNotNull { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("div.headline > h1")?.text()?.trim().toString() + val poster = + fixUrlNull(document.selectFirst("meta[property=og:image]")?.attr("content").toString()) + val tags = document.select("div.info div:nth-child(5) > a").map { it.text() } + val description = document.select("div.info div:nth-child(2)").text().trim() + val actors = document.select("div.info div:nth-child(6) > a").map { it.text() } + val recommendations = + document.select("div.list_videos_related_videos_items div.item").mapNotNull { + it.toSearchResult() + } + + return newMovieLoadResponse(title, url, TvType.Movie, url) { + this.posterUrl = poster + this.plot = description + this.tags = tags + addActors(actors) + this.recommendations = recommendations + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = app.get(data).document + document.select("div.info div:last-child a").map { res -> + callback.invoke( + ExtractorLink( + this.name, + this.name, + res.attr("href"), + referer = data, + quality = Regex("([0-9]+p),").find(res.text())?.groupValues?.get(1) + .let { getQualityFromName(it) }, + headers = mapOf("Range" to "bytes=0-"), + ) + ) + } + + return true + } +} \ No newline at end of file diff --git a/GoodPorn/src/main/kotlin/com/hexated/GoodPornPlugin.kt b/GoodPorn/src/main/kotlin/com/hexated/GoodPornPlugin.kt new file mode 100644 index 00000000..cdb2d9fd --- /dev/null +++ b/GoodPorn/src/main/kotlin/com/hexated/GoodPornPlugin.kt @@ -0,0 +1,13 @@ +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class GoodPornPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(GoodPorn()) + } +} \ No newline at end of file diff --git a/Hentaiheaven/build.gradle.kts b/Hentaiheaven/build.gradle.kts new file mode 100644 index 00000000..e557b1e1 --- /dev/null +++ b/Hentaiheaven/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "en" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "NSFW", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=hentaihaven.xxx&sz=%size%" +} \ No newline at end of file diff --git a/Hentaiheaven/src/main/AndroidManifest.xml b/Hentaiheaven/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Hentaiheaven/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Hentaiheaven/src/main/kotlin/com/hexated/Hentaiheaven.kt b/Hentaiheaven/src/main/kotlin/com/hexated/Hentaiheaven.kt new file mode 100644 index 00000000..c728dcea --- /dev/null +++ b/Hentaiheaven/src/main/kotlin/com/hexated/Hentaiheaven.kt @@ -0,0 +1,156 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.* +import org.jsoup.nodes.Element + +class Hentaiheaven : MainAPI() { + override var mainUrl = "https://hentaihaven.xxx" + override var name = "Hentaiheaven" + override val hasMainPage = true + override var lang = "en" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.NSFW) + + override val mainPage = mainPageOf( + "?m_orderby=new-manga" to "New", + "?m_orderby=views" to "Most Views", + "?m_orderby=rating" to "Rating", + "?m_orderby=alphabet" to "A-Z", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get("$mainUrl/page/$page/${request.data}").document + val home = + document.select("div.page-listing-item div.col-6.col-md-zarat.badge-pos-1").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse(request.name, home) + } + + private fun Element.toSearchResult(): AnimeSearchResponse? { + val href = fixUrl(this.selectFirst("a")!!.attr("href")) + val title = + this.selectFirst("h3 a, h5 a")?.text()?.trim() ?: this.selectFirst("a")?.attr("title") + ?: return null + val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src")) + val episode = this.selectFirst("span.chapter.font-meta a")?.text()?.filter { it.isDigit() } + ?.toIntOrNull() + + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addSub(episode) + } + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query&post_type=wp-manga" + val document = app.get(link).document + + return document.select("div.c-tabs-item div.row.c-tabs-item__content").mapNotNull { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse? { + val document = app.get(url).document + + val title = document.selectFirst("div.post-title h1")?.text()?.trim() ?: return null + val poster = document.select("div.summary_image img").attr("src") + val tags = document.select("div.genres-content > a").map { it.text() } + + val description = document.select("div.description-summary p").text().trim() + val trailer = document.selectFirst("a.trailerbutton")?.attr("href") + + val episodes = document.select("div.listing-chapters_wrap ul li").mapNotNull { + val name = it.selectFirst("a")?.text() ?: return@mapNotNull null + val image = fixUrlNull(it.selectFirst("a img")?.attr("src")) + val link = fixUrlNull(it.selectFirst("a")?.attr("href")) ?: return@mapNotNull null + Episode(link, name, posterUrl = image) + }.reversed() + + val recommendations = + document.select("div.row div.col-6.col-md-zarat").mapNotNull { + it.toSearchResult() + } + + return newAnimeLoadResponse(title, url, TvType.Anime) { + engName = title + posterUrl = poster + addEpisodes(DubStatus.Subbed, episodes) + plot = description + this.tags = tags + this.recommendations = recommendations + addTrailer(trailer) + } + + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + app.get(data).document.select("div.player_logic_item iframe").attr("src").let { iframe -> + val document = app.get(iframe, referer = data).text + val en = Regex("var\\sen\\s=\\s'(\\S+)';").find(document)?.groupValues?.getOrNull(1) + val iv = Regex("var\\siv\\s=\\s'(\\S+)';").find(document)?.groupValues?.getOrNull(1) + + app.post( + "$mainUrl/wp-content/plugins/player-logic/api.php", + data = mapOf( + "action" to "zarat_get_data_player_ajax", + "a" to "$en", + "b" to "$iv" + ), + headers = mapOf("Sec-Fetch-Mode" to "cors") + ).parsedSafe()?.data?.sources?.map { res -> +// M3u8Helper.generateM3u8( +// this.name, +// res.src ?: return@map null, +// referer = "$mainUrl/", +// headers = mapOf( +// "Origin" to mainUrl, +// ) +// ).forEach(callback) + callback.invoke( + ExtractorLink( + this.name, + this.name, + res.src ?: return@map null, + referer = "$mainUrl/", + quality = Qualities.Unknown.value, + isM3u8 = true + ) + ) + } + } + + return true + } + + data class Response( + @JsonProperty("data") val data: Data? = null, + ) + + data class Data( + @JsonProperty("sources") val sources: ArrayList? = arrayListOf(), + ) + + data class Sources( + @JsonProperty("src") val src: String? = null, + @JsonProperty("type") val type: String? = null, + @JsonProperty("label") val label: String? = null, + ) + + +} \ No newline at end of file diff --git a/Hentaiheaven/src/main/kotlin/com/hexated/HentaiheavenPlugin.kt b/Hentaiheaven/src/main/kotlin/com/hexated/HentaiheavenPlugin.kt new file mode 100644 index 00000000..d51b5849 --- /dev/null +++ b/Hentaiheaven/src/main/kotlin/com/hexated/HentaiheavenPlugin.kt @@ -0,0 +1,13 @@ +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class HentaiheavenPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Hentaiheaven()) + } +} \ No newline at end of file diff --git a/IMoviehd/build.gradle.kts b/IMoviehd/build.gradle.kts new file mode 100644 index 00000000..54ae7e8d --- /dev/null +++ b/IMoviehd/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "th" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=www.i-moviehd.com&sz=%size%" +} \ No newline at end of file diff --git a/IMoviehd/src/main/AndroidManifest.xml b/IMoviehd/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/IMoviehd/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/IMoviehd/src/main/kotlin/com/hexated/IMoviehd.kt b/IMoviehd/src/main/kotlin/com/hexated/IMoviehd.kt new file mode 100644 index 00000000..314a4e33 --- /dev/null +++ b/IMoviehd/src/main/kotlin/com/hexated/IMoviehd.kt @@ -0,0 +1,169 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import org.jsoup.nodes.Element +import java.net.URI + +class IMoviehd : MainAPI() { + override var mainUrl = "https://www.i-moviehd.com" + override var name = "I-Moviehd" + override val hasMainPage = true + override var lang = "th" + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + + override val mainPage = mainPageOf( + "$mainUrl/page/" to "RECOMMENDATION", + "$mainUrl/category/series-ซีรี่ส์/page/" to "NEW SERIES", + "$mainUrl/top-movie/page/" to "TOP MOVIES IMDB", + "$mainUrl/top-series/page/" to "TOP SERIES IMDB", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + val home = document.select("div.item-wrap.clearfix div.item").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse(fixTitle(request.name), home) + } + + private fun getProperAnimeLink(uri: String): String { + return if (uri.substringAfter("$mainUrl/").contains("-ep-")) { + val title = uri.substringAfter("$mainUrl/").replace(Regex("-ep-[0-9]+"), "") + "$mainUrl/$title" + } else { + uri + } + } + + private fun Element.toSearchResult(): SearchResponse? { + val title = this.selectFirst("a")?.attr("title") ?: return null + val href = getProperAnimeLink(this.selectFirst("a")!!.attr("href")) + val posterUrl = fixUrlNull(this.select("img").attr("src")) + return newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + } + + } + + override suspend fun search(query: String): List { + val document = app.get("$mainUrl/?s=$query").document + + return document.select("div.item-wrap.clearfix div.item").mapNotNull { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse? { + val document = app.get(url).document + + val title = document.selectFirst("h1.entry-title")?.text() ?: return null + val poster = document.selectFirst("table#imdbinfo td img")?.attr("src") + val tags = document.select("span.categories > a").map { it.text() } + + val tvType = if (document.select("table#Sequel").isNullOrEmpty() + ) TvType.Movie else TvType.TvSeries + val description = document.select("div.entry-content.post_content p").text().trim() + val trailer = document.selectFirst("div#tabt iframe")?.attr("sub_src") + val rating = document.selectFirst("div.imdb-rating-content span")?.text()?.toRatingInt() + + val recommendations = document.select("div.item-wrap.clearfix div.item").mapNotNull { + it.toSearchResult() + } + + return if (tvType == TvType.TvSeries) { + val episodes = document.select("table#Sequel tbody tr").mapNotNull { + val href = fixUrl(it.selectFirst("a")?.attr("href") ?: return null) + val name = it.selectFirst("a")?.text()?.trim() ?: return null + Episode( + href, + name, + ) + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { + this.posterUrl = poster + this.plot = description + this.tags = tags + this.rating = rating + this.recommendations = recommendations + addTrailer(trailer) + } + } else { + newMovieLoadResponse(title, url, TvType.Movie, url) { + this.posterUrl = poster + this.plot = description + this.tags = tags + this.rating = rating + this.recommendations = recommendations + addTrailer(trailer) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = app.get(data).document + + document.select("script").find { it.data().contains("\$.getJSON(") }?.data() + ?.substringAfter("\$.getJSON( \"")?.substringBefore("\"+")?.let { iframe -> + val server = app.get("$iframe\\0&b=", referer = "$mainUrl/") + .parsedSafe()?.link + val id = app.post( + "https://vlp.77player.xyz/initPlayer/${server?.substringAfter("key=")}", + referer = server, + headers = mapOf("X-Requested-With" to "XMLHttpRequest") + ).parsedSafe()?.data?.substringAfter("id=") + +// M3u8Helper.generateM3u8( +// this.name, +// "https://xxx.77player.xyz/iosplaylist/$id/$id.m3u8", +// referer = "$mainUrl/", +// headers = mapOf( +//// "Origin" to "https://xxx.77player.xyz", +// ) +// ).forEach(callback) + + callback.invoke( + ExtractorLink( + this.name, + this.name, + "https://xxx.77player.xyz/iosplaylist/$id/$id.m3u8", + referer = "$mainUrl/", + quality = Qualities.Unknown.value, + isM3u8 = true + ) + ) + + } + + + return true + } + + data class Server( + @JsonProperty("ตัวเล่นไว") val link: String?, + ) + + data class Source( + @JsonProperty("data") val data: String?, + ) + + +} \ No newline at end of file diff --git a/IMoviehd/src/main/kotlin/com/hexated/IMoviehdPlugin.kt b/IMoviehd/src/main/kotlin/com/hexated/IMoviehdPlugin.kt new file mode 100644 index 00000000..a416a029 --- /dev/null +++ b/IMoviehd/src/main/kotlin/com/hexated/IMoviehdPlugin.kt @@ -0,0 +1,14 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class IMoviehdPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(IMoviehd()) + } +} \ No newline at end of file diff --git a/Movierulzhd/build.gradle.kts b/Movierulzhd/build.gradle.kts new file mode 100644 index 00000000..317cc75b --- /dev/null +++ b/Movierulzhd/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "hi" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=movierulzhd.run&sz=%size%" +} \ No newline at end of file diff --git a/Movierulzhd/src/main/AndroidManifest.xml b/Movierulzhd/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Movierulzhd/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt b/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt new file mode 100644 index 00000000..b4071a16 --- /dev/null +++ b/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt @@ -0,0 +1,197 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import org.jsoup.nodes.Element +import java.net.URI + +class Movierulzhd : MainAPI() { + override var mainUrl = "https://movierulzhd.run" + override var name = "Movierulzhd" + override val hasMainPage = true + override var lang = "hi" + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + + override val mainPage = mainPageOf( + "$mainUrl/trending/page/" to "Trending", + "$mainUrl/movies/page/" to "Movies", + "$mainUrl/tvshows/page/" to "TV Shows", + "$mainUrl/seasons/page/" to "Season", + "$mainUrl/episodes/page/" to "Episode", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + val home = document.select("div.items.normal article, div#archive-content article").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse(request.name, home) + } + + private fun getProperLink(uri: String): String { + return when { + uri.contains("/episode/") -> { + var title = uri.substringAfter("$mainUrl/episode/") + title = Regex("(.+?)-season").find(title)?.groupValues?.get(1).toString() + "$mainUrl/tvseries/$title" + } + uri.contains("/season/") -> { + var title = uri.substringAfter("$mainUrl/season/") + title = Regex("(.+?)-season").find(title)?.groupValues?.get(1).toString() + "$mainUrl/tvseries/$title" + } + else -> { + uri + } + } + } + + private fun Element.toSearchResult(): SearchResponse? { + val title = this.selectFirst("h3 > a")?.text() ?: return null + val href = fixUrl(this.selectFirst("h3 > a")!!.attr("href")) + val posterUrl = fixUrlNull(this.select("div.poster > img").attr("src")) + val quality = getQualityFromString(this.select("span.quality").text()) + return newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + this.quality = quality + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/search/$query" + val document = app.get(link).document + + return document.select("div.result-item").map { + val title = + it.selectFirst("div.title > a")!!.text().replace(Regex("\\(\\d{4}\\)"), "").trim() + val href = getProperLink(it.selectFirst("div.title > a")!!.attr("href")) + val posterUrl = it.selectFirst("img")!!.attr("src").toString() + newMovieSearchResponse(title, href, TvType.TvSeries) { + this.posterUrl = posterUrl + } + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = + document.selectFirst("div.data > h1")?.text()?.trim().toString() + val poster = document.select("div.poster > img").attr("src").toString() + val tags = document.select("div.sgeneros > a").map { it.text() } + + val year = Regex(",\\s?(\\d+)").find( + document.select("span.date").text().trim() + )?.groupValues?.get(1).toString().toIntOrNull() + val tvType = if (document.select("ul#section > li:nth-child(1)").text().contains("Episodes") + ) TvType.TvSeries else TvType.Movie + val description = document.select("div.wp-content > p").text().trim() + val trailer = document.selectFirst("div.embed iframe")?.attr("src") + val rating = + document.selectFirst("span.dt_rating_vgs")?.text()?.toRatingInt() + val actors = document.select("div.persons > div[itemprop=actor]").map { + Actor(it.select("meta[itemprop=name]").attr("content"), it.select("img").attr("src")) + } + + val recommendations = document.select("div.owl-item").map { + val recName = + it.selectFirst("a")!!.attr("href").toString().removeSuffix("/").split("/").last() + val recHref = it.selectFirst("a")!!.attr("href") + val recPosterUrl = it.selectFirst("img")?.attr("src").toString() + newTvSeriesSearchResponse(recName, recHref, TvType.TvSeries) { + this.posterUrl = recPosterUrl + } + } + + return if (tvType == TvType.TvSeries) { + val episodes = document.select("ul.episodios > li").map { + val href = it.select("a").attr("href") + val name = fixTitle(it.select("div.episodiotitle > a").text().trim()) + val image = it.select("div.imagen > img").attr("src") + val episode = it.select("div.numerando").text().replace(" ", "").split("-").last() + .toIntOrNull() + val season = it.select("div.numerando").text().replace(" ", "").split("-").first() + .toIntOrNull() + Episode( + href, + name, + season, + episode, + image + ) + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + addTrailer(trailer) + } + } else { + newMovieLoadResponse(title, url, TvType.Movie, url) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + addTrailer(trailer) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = app.get(data).document + val id = document.select("meta#dooplay-ajax-counter").attr("data-postid") + val type = if (data.contains("/movies/")) "movie" else "tv" + + document.select("ul#playeroptionsul > li").map { + it.attr("data-nume") + }.apmap { nume -> + safeApiCall { + val source = app.post( + url = "$mainUrl/wp-admin/admin-ajax.php", + data = mapOf( + "action" to "doo_player_ajax", + "post" to id, + "nume" to nume, + "type" to type + ) + ).parsed().embed_url + + loadExtractor(source, data, subtitleCallback, callback) + } + } + + return true + } + + data class ResponseHash( + @JsonProperty("embed_url") val embed_url: String, + @JsonProperty("type") val type: String?, + ) + +} \ No newline at end of file diff --git a/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt b/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt new file mode 100644 index 00000000..cd1615da --- /dev/null +++ b/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt @@ -0,0 +1,14 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class MovierulzhdPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Movierulzhd()) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7d6e19e0..ece36bb1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,11 +25,9 @@ allprojects { } } -fun Project.cloudstream(configuration: CloudstreamExtension.() -> Unit) = - extensions.getByName("cloudstream").configuration() +fun Project.cloudstream(configuration: CloudstreamExtension.() -> Unit) = extensions.getByName("cloudstream").configuration() -fun Project.android(configuration: BaseExtension.() -> Unit) = - extensions.getByName("android").configuration() +fun Project.android(configuration: BaseExtension.() -> Unit) = extensions.getByName("android").configuration() subprojects { apply(plugin = "com.android.library") @@ -38,7 +36,10 @@ subprojects { cloudstream { // when running through github workflow, GITHUB_REPOSITORY should contain current repository name - setRepo(System.getenv("GITHUB_REPOSITORY") ?: "user/repo") + setRepo(System.getenv("GITHUB_REPOSITORY") ?: "https://github.com/hexated/cloudstream-extensions-hexated") + + description = "Providers from all country" + authors = listOf("Hexated") } android { @@ -78,15 +79,12 @@ subprojects { // https://github.com/recloudstream/cloudstream/blob/master/app/build.gradle implementation(kotlin("stdlib")) // adds standard kotlin features, like listOf, mapOf etc implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") implementation("org.jsoup:jsoup:1.13.1") // html parser - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") // html parser - - //run JS - implementation("org.mozilla:rhino:1.7.14") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") + implementation("io.karn:khttp-android:0.1.2") } } task("clean") { delete(rootProject.buildDir) -} +} \ No newline at end of file