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