diff --git a/Example/src/main/kotlin/com/jacekun/Example.kt b/Example/src/main/kotlin/com/jacekun/Example.kt
deleted file mode 100644
index 1aad8d1..0000000
--- a/Example/src/main/kotlin/com/jacekun/Example.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.jacekun
-
-import com.lagradost.cloudstream3.MainAPI
-import com.lagradost.cloudstream3.TvType
-
-class Example : MainAPI() {
- private val DEV = "DevDebug"
- private val globaltvType = TvType.Movie
-}
\ No newline at end of file
diff --git a/FullPorner/build.gradle.kts b/FullPorner/build.gradle.kts
new file mode 100644
index 0000000..1dce6ab
--- /dev/null
+++ b/FullPorner/build.gradle.kts
@@ -0,0 +1,18 @@
+version = 4
+
+cloudstream {
+ authors = listOf("KillerDogeEmpire, Coxju")
+ language = "en"
+ description = "FullPorner is the best free full length porn video site. Choose from millions of hardcore videos that stream quickly and in high quality and only full length"
+
+ /**
+ * 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=fullporner.com&sz=%size%"
+}
diff --git a/Example/src/main/AndroidManifest.xml b/FullPorner/src/main/AndroidManifest.xml
similarity index 52%
rename from Example/src/main/AndroidManifest.xml
rename to FullPorner/src/main/AndroidManifest.xml
index 29aec9d..3f4d39b 100644
--- a/Example/src/main/AndroidManifest.xml
+++ b/FullPorner/src/main/AndroidManifest.xml
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/FullPorner/src/main/kotlin/com/KillerDogeEmpire/FullPorner.kt b/FullPorner/src/main/kotlin/com/KillerDogeEmpire/FullPorner.kt
new file mode 100644
index 0000000..071e9eb
--- /dev/null
+++ b/FullPorner/src/main/kotlin/com/KillerDogeEmpire/FullPorner.kt
@@ -0,0 +1,162 @@
+package com.KillerDogeEmpire
+
+import org.jsoup.nodes.Element
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.utils.*
+import com.lagradost.cloudstream3.network.WebViewResolver
+import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
+import org.jsoup.Jsoup
+
+class FullPorner : MainAPI() {
+ override var mainUrl = "https://fullporner.com"
+ override var name = "FullPorner"
+ override val hasMainPage = true
+ override var lang = "en"
+ override val hasQuickSearch = false
+ override val hasDownloadSupport = true
+ override val hasChromecastSupport = true
+ override val supportedTypes = setOf(TvType.NSFW)
+ override val vpnStatus = VPNStatus.MightBeNeeded
+
+ override val mainPage = mainPageOf(
+ "${mainUrl}/home/" to "Featured",
+ "${mainUrl}/category/amateur/" to "Amateur",
+ "${mainUrl}/category/teen/" to "Teen",
+ "${mainUrl}/category/cumshot/" to "CumShot",
+ "${mainUrl}/category/deepthroat/" to "DeepThroat",
+ "${mainUrl}/category/orgasm/" to "Orgasm",
+ "${mainUrl}/category/threesome/" to "ThreeSome",
+ "${mainUrl}/category/group-sex/" to "Group Sex",
+ )
+
+ override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
+ val document = app.get("${request.data}${page}").document
+ val home = document.select("div.video-block div.video-card").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("div.video-card div.video-card-body div.video-title a")?.text() ?: return null
+ val href = fixUrl(this.selectFirst("div.video-card div.video-card-body div.video-title a")!!.attr("href"))
+ val posterUrl = fixUrlNull(this.select("div.video-card div.video-card-image a img").attr("data-src"))
+
+ return newMovieSearchResponse(title, href, TvType.Movie) { this.posterUrl = posterUrl }
+ }
+
+ override suspend fun search(query: String): List {
+ val searchResponse = mutableListOf()
+
+ for (i in 1..15) {
+ val document = app.get("${mainUrl}/search?q=${query.replace(" ", "+")}&p=$i").document
+
+ val results = document.select("div.video-block div.video-card").mapNotNull { it.toSearchResult() }
+
+ searchResponse.addAll(results)
+
+ if (results.isEmpty()) break
+ }
+
+ return searchResponse
+ }
+
+ override suspend fun load(url: String): LoadResponse {
+ val document = app.get(url).document
+
+ val title = document.selectFirst("div.video-block div.single-video-left div.single-video-title h2")?.text()?.trim().toString()
+ val iframeUrl = fixUrlNull(document.selectFirst("div.video-block div.single-video-left div.single-video iframe")?.attr("src")) ?: ""
+
+ val poster: String?
+ val posterHeaders: Map
+ if (iframeUrl.contains("videoh")) {
+ val iframeDocument = app.get(iframeUrl, interceptor = WebViewResolver(Regex("""mydaddy"""))).document
+
+ val videoHtml = iframeDocument.selectXpath("//script[contains(text(),'poster')]").first()?.html()?.substringAfter("else{ \$(\"#jw\").html(\"")?.substringBefore("\");}if(hasAdblock)")?.replace("\\", "")
+ val video = Jsoup.parse(videoHtml.toString()).selectFirst("video")
+
+ poster = fixUrlNull(video?.attr("poster"))
+ posterHeaders = mapOf(Pair("referer", "https://mydaddy.cc/"))
+ } else {
+ val iframeDocument = app.get(iframeUrl).document
+ val videoDocument = Jsoup.parse("")
+
+ poster = fixUrlNull(videoDocument.selectFirst("video")?.attr("poster").toString())
+ posterHeaders = mapOf(Pair("referer", "https://xiaoshenke.net/"))
+ }
+
+ val tags = document.select("div.video-blockdiv.single-video-left div.single-video-title p.tag-link span a").map { it.text() }
+ val description = document.selectFirst("div.video-block div.single-video-left div.single-video-title h2")?.text()?.trim().toString()
+ val actors = document.select("div.video-block div.single-video-left div.single-video-info-content p a").map { it.text() }
+ val recommendations = document.select("div.video-block div.video-recommendation div.video-card").mapNotNull { it.toSearchResult() }
+
+ return newMovieLoadResponse(title, url, TvType.NSFW, url) {
+ this.posterUrl = poster
+ this.posterHeaders = posterHeaders
+ this.plot = description
+ this.tags = tags
+ this.recommendations = recommendations
+ addActors(actors)
+ }
+ }
+
+ override suspend fun loadLinks(data: String, isCasting: Boolean, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit): Boolean {
+ val document = app.get(data).document
+
+ val iframeUrl = fixUrlNull(document.selectFirst("div.video-block div.single-video-left div.single-video iframe")?.attr("src")) ?: ""
+
+ val extlinkList = mutableListOf()
+ if (iframeUrl.contains("videoh")) {
+ val iframeDocument = app.get(iframeUrl, interceptor = WebViewResolver(Regex("""mydaddy"""))).document
+ val videoDocument = Jsoup.parse("")
+
+ videoDocument.select("source").map { res ->
+ extlinkList.add(ExtractorLink(
+ name,
+ name,
+ fixUrl(res.attr("src")),
+ referer = data,
+ quality = Regex("(\\d+.)").find(res.attr("title"))?.groupValues?.get(1).let { getQualityFromName(it) }
+ ))
+ }
+ } else if (iframeUrl.contains("xiaoshenke")) {
+ val iframeDocument = app.get(iframeUrl).document
+ val videoID = Regex("""var id = \"(.+?)\"""").find(iframeDocument.html())?.groupValues?.get(1)
+
+ val pornTrexDocument = app.get("https://www.porntrex.com/embed/${videoID}").document
+ val video_url = fixUrlNull(Regex("""video_url: \'(.+?)\',""").find(pornTrexDocument.html())?.groupValues?.get(1))
+ if (video_url != null) {
+ extlinkList.add(ExtractorLink(
+ name,
+ name,
+ video_url,
+ referer = data,
+ quality = Qualities.Unknown.value
+ ))
+ }
+ } else {
+ val iframeDocument = app.get(iframeUrl).document
+ val videoDocument = Jsoup.parse("")
+
+ videoDocument.select("source").map { res ->
+ extlinkList.add(ExtractorLink(
+ this.name,
+ this.name,
+ fixUrl(res.attr("src")),
+ referer = mainUrl,
+ quality = Regex("(\\d+.)").find(res.attr("title"))?.groupValues?.get(1).let { getQualityFromName(it) }
+ ))
+ }
+ }
+
+ extlinkList.forEach(callback)
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/FullPorner/src/main/kotlin/com/KillerDogeEmpire/FullPornerProvider.kt b/FullPorner/src/main/kotlin/com/KillerDogeEmpire/FullPornerProvider.kt
new file mode 100644
index 0000000..5ff8087
--- /dev/null
+++ b/FullPorner/src/main/kotlin/com/KillerDogeEmpire/FullPornerProvider.kt
@@ -0,0 +1,12 @@
+package com.KillerDogeEmpire
+
+import android.content.Context
+import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
+import com.lagradost.cloudstream3.plugins.Plugin
+
+@CloudstreamPlugin
+class FullPornerProvider : Plugin() {
+ override fun load(context: Context) {
+ registerMainAPI(FullPorner())
+ }
+}
\ No newline at end of file
diff --git a/GoodPorn/build.gradle.kts b/GoodPorn/build.gradle.kts
new file mode 100644
index 0000000..fc3c883
--- /dev/null
+++ b/GoodPorn/build.gradle.kts
@@ -0,0 +1,28 @@
+// use an integer for version numbers
+version = 5
+
+
+cloudstream {
+ // All of these properties are optional, you can safely remove them
+
+ description = "GoodPorn"
+ authors = listOf(" KillerDogeEmpire, Stormunblessed, Jace, Hexated, Coxju")
+
+ /**
+ * Status int as the following:
+ * 0: Down
+ * 1: Ok
+ * 2: Slow
+ * 3: Beta only
+ * */
+ status = 1 // will be 3 if unspecified
+
+ // List of video source types. Users are able to filter for extensions in a given category.
+ // You can find a list of avaliable types here:
+ // https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html
+ tvTypes = listOf("NSFW")
+
+ iconUrl = "https://www.google.com/s2/favicons?domain=goodporn.to&sz=%size%"
+
+ language = "en"
+}
diff --git a/GoodPorn/src/main/AndroidManifest.xml b/GoodPorn/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0862a59
--- /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/KillerDogeEmpire/GoodPorn.kt b/GoodPorn/src/main/kotlin/com/KillerDogeEmpire/GoodPorn.kt
new file mode 100644
index 0000000..06e462d
--- /dev/null
+++ b/GoodPorn/src/main/kotlin/com/KillerDogeEmpire/GoodPorn.kt
@@ -0,0 +1,127 @@
+package com.KillerDogeEmpire
+
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.getQualityFromName
+import org.jsoup.nodes.Element
+import java.util.*
+
+class GoodPorn : MainAPI() {
+ override var mainUrl = "https://goodporn.to"
+ override var name = "GoodPorn"
+ override val hasMainPage = true
+ override val hasDownloadSupport = true
+ override val vpnStatus = VPNStatus.MightBeNeeded
+ 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/sites/fitness-rooms/?mode=async&function=get_block&block_id=list_videos_common_videos_list&sort_by=post_date&from=" to "Fitness Rooms",
+ "$mainUrl/sites/public-agent/?mode=async&function=get_block&block_id=list_videos_common_videos_list&sort_by=post_date&from=" to "Public Agent",
+ "$mainUrl/sites/massage-rooms/?mode=async&function=get_block&block_id=list_videos_common_videos_list&sort_by=post_date&from=" to "Massage Rooms",
+ "$mainUrl/sites/dane-jones/?mode=async&function=get_block&block_id=list_videos_common_videos_list&sort_by=post_date&from=" to "Dane Jones",
+ "$mainUrl/channels/brazzers/?mode=async&function=get_block&block_id=list_videos_common_videos_list&sort_by=post_date&from=" to "Brazzers",
+ "$mainUrl/channels/digitalplayground/?mode=async&function=get_block&block_id=list_videos_common_videos_list&sort_by=post_date&from=" to "Digital Playground",
+ "$mainUrl/channels/realitykings/?mode=async&function=get_block&block_id=list_videos_common_videos_list&sort_by=post_date&from=" to "Realitykings",
+ "$mainUrl/channels/babes-network/?mode=async&function=get_block&block_id=list_videos_common_videos_list&sort_by=post_date&from=" to "Babes Network",
+ )
+
+ 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, div#list_videos_common_videos_list_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 searchResponse = mutableListOf()
+ for (i in 1..15) {
+ val document = app.get(
+ "$mainUrl/search/nikki-benz/?mode=async&function=get_block&block_id=list_videos_videos_list_search_result&q=$query&category_ids=&sort_by=&from_videos=$i&from_albums=$i",
+ headers = mapOf("X-Requested-With" to "XMLHttpRequest")
+ ).document
+ val results =
+ document.select("div#list_videos_videos_list_search_result_items div.item")
+ .mapNotNull {
+ it.toSearchResult()
+ }
+ searchResponse.addAll(results)
+ if (results.isEmpty()) break
+ }
+ return searchResponse
+ }
+
+ 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.NSFW, 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
+ val extlinkList = mutableListOf()
+ document.select("div.info div:last-child a").map { res ->
+ extlinkList.add(
+ ExtractorLink(
+ this.name,
+ this.name,
+ res.attr("href")
+ .replace(Regex("\\?download\\S+.mp4&"), "?") + "&rnd=${Date().time}",
+ referer = data,
+ quality = Regex("(\\d+.),").find(res.text())?.groupValues?.get(1)
+ .let { getQualityFromName(it) },
+ headers = mapOf("Range" to "bytes=0-"),
+ )
+ )
+ }
+ extlinkList.forEach(callback)
+ return true
+ }
+
+}
\ No newline at end of file
diff --git a/GoodPorn/src/main/kotlin/com/KillerDogeEmpire/GoodPornProvider.kt b/GoodPorn/src/main/kotlin/com/KillerDogeEmpire/GoodPornProvider.kt
new file mode 100644
index 0000000..2622474
--- /dev/null
+++ b/GoodPorn/src/main/kotlin/com/KillerDogeEmpire/GoodPornProvider.kt
@@ -0,0 +1,14 @@
+package com.KillerDogeEmpire
+
+import android.content.Context
+import com.KillerDogeEmpire.GoodPorn
+import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
+import com.lagradost.cloudstream3.plugins.Plugin
+
+@CloudstreamPlugin
+class GoodPornProvider : 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/HentaiHaven/build.gradle.kts b/HentaiHaven/build.gradle.kts
index 9f1c8b3..7fa8fcf 100644
--- a/HentaiHaven/build.gradle.kts
+++ b/HentaiHaven/build.gradle.kts
@@ -1,12 +1,12 @@
// use an integer for version numbers
-version = 5
+version = 6
cloudstream {
// All of these properties are optional, you can safely remove them
description = ""
- authors = listOf("Jace")
+ authors = listOf("KillerDogeEmpire, Jace")
/**
* Status int as the following:
diff --git a/HentaiHaven/src/main/kotlin/com/jacekun/HentaiHaven.kt b/HentaiHaven/src/main/kotlin/com/jacekun/HentaiHaven.kt
index e88f9fe..748a4e2 100644
--- a/HentaiHaven/src/main/kotlin/com/jacekun/HentaiHaven.kt
+++ b/HentaiHaven/src/main/kotlin/com/jacekun/HentaiHaven.kt
@@ -1,101 +1,99 @@
package com.jacekun
-import android.util.Log
+
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.getQualityFromName
-import org.jsoup.select.Elements
+import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
+import com.lagradost.cloudstream3.utils.*
+import okhttp3.FormBody
+import org.jsoup.nodes.Element
+
class HentaiHaven : MainAPI() {
- private val globalTvType = TvType.NSFW
- override var name = "Hentai Haven"
override var mainUrl = "https://hentaihaven.xxx"
- override val supportedTypes = setOf(TvType.NSFW)
- override val hasDownloadSupport = false
- override val hasMainPage= true
- override val hasQuickSearch = false
+ 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 doc = app.get(mainUrl).document
- val all = ArrayList()
-
- doc.getElementsByTag("body").select("div.c-tabs-item")
- .select("div.vraven_home_slider").forEach { it2 ->
- // Fetch row title
- val title = it2?.select("div.home_slider_header")?.text() ?: "Unnamed Row"
- // Fetch list of items and map
- it2.select("div.page-content-listing div.item.vraven_item.badge-pos-1").let { inner ->
-
- all.add(
- HomePageList(
- name = title,
- list = inner.getResults(this.name),
- isHorizontalImages = false
- )
- )
- }
+ 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 HomePageResponse(all)
+ 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 searchUrl = "${mainUrl}/?s=${query}&post_type=wp-manga"
- return app.get(searchUrl).document
- .select("div.c-tabs-item div.row.c-tabs-item__content")
- .getResults(this.name)
+ val link = "$mainUrl/?s=$query&post_type=wp-manga"
+ val document = app.get(link).document
+
+ return document.select("div.c-tabs-item > div.c-tabs-item__content").mapNotNull {
+ it.toSearchResult()
+ }
}
- override suspend fun load(url: String): LoadResponse {
- //TODO: Load polishing
- val doc = app.get(url).document
- //Log.i(this.name, "Result => (url) ${url}")
- val poster = doc.select("meta[property=og:image]")
- .firstOrNull()?.attr("content")
- val title = doc.select("meta[name=title]")
- .firstOrNull()?.attr("content")
- ?.toString() ?: ""
- val descript = doc.select("div.description-summary").text()
+ override suspend fun load(url: String): LoadResponse? {
+ val document = app.get(url).document
- val body = doc.getElementsByTag("body")
- val episodes = body.select("div.page-content-listing.single-page")
- .first()?.select("li")
+ 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 year = episodes?.last()
- ?.selectFirst("span.chapter-release-date")
- ?.text()?.trim()?.takeLast(4)?.toIntOrNull()
+ val description = document.select("div.description-summary p").text().trim()
+ val trailer = document.selectFirst("a.trailerbutton")?.attr("href")
- val episodeList = episodes?.mapNotNull {
- val innerA = it?.selectFirst("a") ?: return@mapNotNull null
- val eplink = innerA.attr("href") ?: return@mapNotNull null
- val epCount = innerA.text().trim().filter { a -> a.isDigit() }.toIntOrNull()
- val imageEl = innerA.selectFirst("img")
- val epPoster = imageEl?.attr("src") ?: imageEl?.attr("data-src")
- Episode(
- name = innerA.text(),
- data = eplink,
- posterUrl = epPoster,
- episode = epCount,
- )
- } ?: listOf()
+ 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.NSFW) {
+ engName = title
+ posterUrl = poster
+ addEpisodes(DubStatus.Subbed, episodes)
+ plot = description
+ this.tags = tags
+ this.recommendations = recommendations
+ addTrailer(trailer)
+ }
- //Log.i(this.name, "Result => (id) ${id}")
- return AnimeLoadResponse(
- name = title,
- url = url,
- apiName = this.name,
- type = globalTvType,
- posterUrl = poster,
- year = year,
- plot = descript,
- episodes = mutableMapOf(
- Pair(DubStatus.Subbed, episodeList.reversed())
- )
- )
}
override suspend fun loadLinks(
@@ -105,120 +103,66 @@ class HentaiHaven : MainAPI() {
callback: (ExtractorLink) -> Unit
): Boolean {
- try {
- Log.i(name, "Loading iframe")
- val requestLink = "${mainUrl}/wp-content/plugins/player-logic/api.php"
- val action = "zarat_get_data_player_ajax"
- val reA = Regex("(?<=var en =)(.*?)(?=';)", setOf(RegexOption.DOT_MATCHES_ALL))
- val reB = Regex("(?<=var iv =)(.*?)(?=';)", setOf(RegexOption.DOT_MATCHES_ALL))
+ val doc = app.get(data).document
+ val meta = doc.selectFirst("meta[itemprop=thumbnailUrl]")?.attr("content")?.substringAfter("/hh/")?.substringBefore("/") ?: return false
+ doc.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.get(data).document.selectFirst("div.player_logic_item iframe")
- ?.attr("src")?.let { epLink ->
+ val body = FormBody.Builder()
+ .addEncoded("action", "zarat_get_data_player_ajax")
+ .addEncoded("a", "$en")
+ .addEncoded("b", "$iv")
+ .build()
- Log.i(name, "Loading ep link => $epLink")
- val scrAppGet = app.get(epLink, referer = data)
- val scrDoc = scrAppGet.document.getElementsByTag("script").toString()
- //Log.i(name, "Loading scrDoc => (${scrAppGet.code}) $scrDoc")
- if (scrDoc.isNotBlank()) {
- //en
- val a = reA.find(scrDoc)?.groupValues?.getOrNull(1)
- ?.trim()?.removePrefix("'") ?: ""
- //iv
- val b = reB.find(scrDoc)?.groupValues?.getOrNull(1)
- ?.trim()?.removePrefix("'") ?: ""
-
- Log.i(name, "a => $a")
- Log.i(name, "b => $b")
-
- val doc = app.post(
- url = requestLink,
- headers = mapOf(
-// Pair("mode", "cors"),
-// Pair("Content-Type", "multipart/form-data"),
-// Pair("Origin", mainUrl),
-// Pair("Host", mainUrl.split("//").last()),
- Pair("User-Agent", USER_AGENT),
- Pair("Sec-Fetch-Mode", "cors")
- ),
- data = mapOf(
- Pair("action", action),
- Pair("a", a),
- Pair("b", b)
- )
- )
- Log.i(name, "Response (${doc.code}) => ${doc.text}")
- //AppUtils.tryParseJson(doc.text)
- doc.parsedSafe()?.data?.sources?.map { m3src ->
- val m3srcFile = m3src.src ?: return@map null
- val label = m3src.label ?: ""
- Log.i(name, "M3u8 link: $m3srcFile")
- callback.invoke(
- ExtractorLink(
- name = "$name m3u8",
- source = "$name m3u8",
- url = m3srcFile,
- referer = "$mainUrl/",
- quality = getQualityFromName(label),
- isM3u8 = true
- )
- )
- }
- }
- }
- } catch (e: Exception) {
- Log.i(name, "Error => $e")
- logError(e)
- return false
+ 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"
+// ),
+ requestBody = body,
+// 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?.replace("/hh//", "/hh/$meta/") ?: return@map null,
+ referer = "",
+ quality = Qualities.Unknown.value,
+ isM3u8 = true
+ )
+ )
+ }
}
+
return true
}
- private fun Elements?.getResults(apiName: String): List {
- return this?.mapNotNull {
- val innerDiv = it.select("div").firstOrNull()
- val firstA = innerDiv?.selectFirst("a")
- val link = fixUrlNull(firstA?.attr("href")) ?: return@mapNotNull null
- val name = firstA?.attr("title") ?: ""
- val year = innerDiv?.selectFirst("span.c-new-tag")?.selectFirst("a")
- ?.attr("title")?.takeLast(4)?.toIntOrNull()
-
- val imageDiv = firstA?.selectFirst("img")
- var image = imageDiv?.attr("src")
- if (image.isNullOrBlank()) {
- image = imageDiv?.attr("data-src")
- }
-
- val latestEp = innerDiv?.selectFirst("div.list-chapter")
- ?.selectFirst("div.chapter-item")
- ?.selectFirst("a")
- ?.text()
- ?.filter { a -> a.isDigit() }
- ?.toIntOrNull() ?: 0
- val dubStatus = mutableMapOf(
- Pair(DubStatus.Subbed, latestEp)
- )
-
- AnimeSearchResponse(
- name = name,
- url = link,
- apiName = apiName,
- type = globalTvType,
- posterUrl = image,
- year = year,
- episodes = dubStatus
- )
- } ?: listOf()
- }
-
- private data class ResponseJson(
- @JsonProperty("data") val data: ResponseData?
+ data class Response(
+ @JsonProperty("data") val data: Data? = null,
)
- private data class ResponseData(
- @JsonProperty("sources") val sources: List? = listOf()
+
+ data class Data(
+ @JsonProperty("sources") val sources: ArrayList? = arrayListOf(),
)
- private data class ResponseSources(
- @JsonProperty("src") val src: String?,
- @JsonProperty("type") val type: String?,
- @JsonProperty("label") val label: String?
+
+ 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/HpJav/build.gradle.kts b/HpJav/build.gradle.kts
index c113cb4..53a2c6b 100644
--- a/HpJav/build.gradle.kts
+++ b/HpJav/build.gradle.kts
@@ -15,7 +15,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 3 // will be 3 if unspecified
+ status = 0 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
diff --git a/JavFreeProvider/build.gradle.kts b/JavFreeProvider/build.gradle.kts
index 6dd6026..ec05bb4 100644
--- a/JavFreeProvider/build.gradle.kts
+++ b/JavFreeProvider/build.gradle.kts
@@ -15,7 +15,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 1 // will be 3 if unspecified
+ status = 0 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
diff --git a/JavGuru/build.gradle.kts b/JavGuru/build.gradle.kts
index 9d51744..53e8da0 100644
--- a/JavGuru/build.gradle.kts
+++ b/JavGuru/build.gradle.kts
@@ -15,7 +15,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 3 // will be 3 if unspecified
+ status = 0 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
diff --git a/JavHD/build.gradle.kts b/JavHD/build.gradle.kts
index 2c0a3d0..aa4a24a 100644
--- a/JavHD/build.gradle.kts
+++ b/JavHD/build.gradle.kts
@@ -15,7 +15,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 1 // will be 3 if unspecified
+ status = 0 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
diff --git a/JavMost/build.gradle.kts b/JavMost/build.gradle.kts
index 08fb73c..5d248cc 100644
--- a/JavMost/build.gradle.kts
+++ b/JavMost/build.gradle.kts
@@ -15,7 +15,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 3 // will be 3 if unspecified
+ status = 0 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
diff --git a/JavSubProvider/build.gradle.kts b/JavSubProvider/build.gradle.kts
index b828aca..53ed9ee 100644
--- a/JavSubProvider/build.gradle.kts
+++ b/JavSubProvider/build.gradle.kts
@@ -15,7 +15,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 1 // will be 3 if unspecified
+ status = 0 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
diff --git a/JavTube/build.gradle.kts b/JavTube/build.gradle.kts
index 917a3e9..1b89210 100644
--- a/JavTube/build.gradle.kts
+++ b/JavTube/build.gradle.kts
@@ -15,7 +15,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 1 // will be 3 if unspecified
+ status = 0 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
diff --git a/NoodleMagazineProvider/build.gradle.kts b/NoodleMagazineProvider/build.gradle.kts
new file mode 100644
index 0000000..710e454
--- /dev/null
+++ b/NoodleMagazineProvider/build.gradle.kts
@@ -0,0 +1,24 @@
+version = 7
+
+
+cloudstream {
+ language = "en"
+ // All of these properties are optional, you can safely remove them
+
+ description = "type .nofap in discord - Full Length"
+ authors = listOf("KillerDogeEmpire, Coxju")
+
+ /**
+ * 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=noodlemagazine.com/&sz=%size%"
+}
diff --git a/NoodleMagazineProvider/src/main/AndroidManifest.xml b/NoodleMagazineProvider/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d2a6ed7
--- /dev/null
+++ b/NoodleMagazineProvider/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazinePlugin.kt b/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazinePlugin.kt
new file mode 100644
index 0000000..5cc0393
--- /dev/null
+++ b/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazinePlugin.kt
@@ -0,0 +1,13 @@
+package com.KillerDogeEmpire
+
+import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
+import com.lagradost.cloudstream3.plugins.Plugin
+import android.content.Context
+
+@CloudstreamPlugin
+class NoodleMagazinePlugin: Plugin() {
+ override fun load(context: Context) {
+ // All providers should be added in this manner. Please don't edit the providers list directly.
+ registerMainAPI(NoodleMagazineProvider())
+ }
+}
diff --git a/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazineProvider.kt b/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazineProvider.kt
new file mode 100644
index 0000000..d70231d
--- /dev/null
+++ b/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazineProvider.kt
@@ -0,0 +1,120 @@
+package com.KillerDogeEmpire
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.network.WebViewResolver
+import com.lagradost.cloudstream3.utils.*
+import org.jsoup.nodes.Element
+
+class NoodleMagazineProvider : MainAPI() { // all providers must be an instance of MainAPI
+ override var mainUrl = "https://noodlemagazine.com"
+ override var name = "Noodle Magazine"
+ override val hasMainPage = true
+ override var lang = "en"
+ override val hasDownloadSupport = true
+ override val supportedTypes = setOf(
+ TvType.NSFW
+ )
+
+ override val mainPage = mainPageOf(
+ "latest" to "Latest",
+ "onlyfans" to "Onlyfans",
+ "latina" to "Latina",
+ "blonde" to "Blonde",
+ "milf" to "MILF",
+ "jav" to "JAV",
+ "hentai" to "Hentai",
+ "lesbian" to "Lesbian",
+ )
+
+
+ override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
+ val curpage = page - 1
+ val link = "$mainUrl/video/${request.data}?p=$curpage"
+ val document = app.get(link).document
+ val home = document.select("div.item").mapNotNull {
+ it.toSearchResult()
+ }
+ return newHomePageResponse(request.name, home)
+ }
+
+ private fun Element.toSearchResult(): AnimeSearchResponse? {
+ val href = fixUrl(this.selectFirst("a")?.attr("href") ?: return null)
+ val title = this.selectFirst("a div.i_info div.title")?.text() ?: return null
+ val posterUrl = fixUrlNull(this.selectFirst("a div.i_img img")?.attr("data-src"))
+
+ return newAnimeSearchResponse(title, href, TvType.Anime) {
+ this.posterUrl = posterUrl
+ }
+ }
+
+ override suspend fun search(query: String): List {
+ val searchresult = mutableListOf()
+ (0..10).toList().apmap { page ->
+ val doc = app.get("$mainUrl/video/$query?p=$page").document
+ //return document.select("div.post-filter-image").mapNotNull {
+ doc.select("div.item").apmap { res ->
+ searchresult.add(res.toSearchResult()!!)
+ }
+
+ }
+
+ return searchresult
+ }
+
+ override suspend fun load(url: String): LoadResponse {
+ val document = app.get(url).document
+ val title = document.selectFirst("div.l_info h1")?.text()?.trim() ?: "null"
+ val poster =
+ document.selectFirst("""meta[property="og:image"]""")?.attr("content") ?: "null"
+
+ val recommendations = document.select("div.item").mapNotNull {
+ it.toSearchResult()
+ }
+
+ return newMovieLoadResponse(title, url, TvType.NSFW, url) {
+ this.posterUrl = poster
+ this.recommendations = recommendations
+ }
+
+ }
+
+
+ override suspend fun loadLinks(
+ data: String,
+ isCasting: Boolean,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit
+ ): Boolean {
+ val jason = app.get(
+ data, interceptor = WebViewResolver(Regex("""/playlist/"""))
+ ).parsed()
+ val extlinkList = mutableListOf()
+ jason.sources.map {
+ extlinkList.add(
+ ExtractorLink(
+ source = name,
+ name = name,
+ url = it.streamlink!!,
+ referer = "$mainUrl/",
+ quality = getQualityFromName(it.qualityfile)
+ )
+ )
+ }
+ extlinkList.forEach(callback)
+ return true
+ }
+
+ data class SusJSON(
+ @JsonProperty("image") val img: String? = null,
+ @JsonProperty("sources") val sources: ArrayList = arrayListOf()
+ )
+
+ data class Streams(
+ @JsonProperty("file") val streamlink: String? = null,//the link
+ @JsonProperty("label") val qualityfile: String? = null,//720 480 360 240
+ @JsonProperty("type") val type: String? = null,//mp4
+ )
+
+}
+
diff --git a/OpJav/build.gradle.kts b/OpJav/build.gradle.kts
index 67dd0a2..46cdcac 100644
--- a/OpJav/build.gradle.kts
+++ b/OpJav/build.gradle.kts
@@ -15,7 +15,7 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 2 // will be 3 if unspecified
+ status = 0 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
diff --git a/Pornhits/build.gradle.kts b/Pornhits/build.gradle.kts
new file mode 100644
index 0000000..5b5c045
--- /dev/null
+++ b/Pornhits/build.gradle.kts
@@ -0,0 +1,28 @@
+// use an integer for version numbers
+version = 5
+
+
+cloudstream {
+ // All of these properties are optional, you can safely remove them
+
+ description = "Pornhits"
+ authors = listOf("KillerDogeEmpire, Coxju")
+
+ /**
+ * Status int as the following:
+ * 0: Down
+ * 1: Ok
+ * 2: Slow
+ * 3: Beta only
+ * */
+ status = 1 // will be 3 if unspecified
+
+ // List of video source types. Users are able to filter for extensions in a given category.
+ // You can find a list of avaliable types here:
+ // https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html
+ tvTypes = listOf("NSFW")
+
+ iconUrl = "https://www.google.com/s2/favicons?domain=pornhits.com&sz=%size%"
+
+ language = "en"
+}
diff --git a/Pornhits/src/main/AndroidManifest.xml b/Pornhits/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0862a59
--- /dev/null
+++ b/Pornhits/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/Pornhits/src/main/kotlin/com/KillerDogeEmpire/Pornhits.kt b/Pornhits/src/main/kotlin/com/KillerDogeEmpire/Pornhits.kt
new file mode 100644
index 0000000..a8f34c9
--- /dev/null
+++ b/Pornhits/src/main/kotlin/com/KillerDogeEmpire/Pornhits.kt
@@ -0,0 +1,216 @@
+package com.KillerDogeEmpire
+
+import com.lagradost.cloudstream3.HomePageList
+import com.lagradost.cloudstream3.HomePageResponse
+import com.lagradost.cloudstream3.LoadResponse
+import com.lagradost.cloudstream3.MainAPI
+import com.lagradost.cloudstream3.MainPageRequest
+import com.lagradost.cloudstream3.SearchResponse
+import com.lagradost.cloudstream3.SubtitleFile
+import com.lagradost.cloudstream3.TvType
+import com.lagradost.cloudstream3.VPNStatus
+import com.lagradost.cloudstream3.app
+import com.lagradost.cloudstream3.fixUrl
+import com.lagradost.cloudstream3.fixUrlNull
+import com.lagradost.cloudstream3.mainPageOf
+import com.lagradost.cloudstream3.newHomePageResponse
+import com.lagradost.cloudstream3.newMovieLoadResponse
+import com.lagradost.cloudstream3.newMovieSearchResponse
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.Qualities
+import org.json.JSONObject
+import org.jsoup.nodes.Element
+
+class Pornhits : MainAPI() {
+ override var mainUrl = "https://www.pornhits.com"
+ override var name = "Pornhits"
+ override val hasMainPage = true
+ override val hasDownloadSupport = true
+ override val vpnStatus = VPNStatus.MightBeNeeded
+ override val supportedTypes = setOf(TvType.NSFW)
+
+ override val mainPage = mainPageOf(
+ "$mainUrl/videos.php?p=%d&s=l" to "Latest",
+ "$mainUrl/videos.php?p=%d&s=pd" to "Popular last day",
+ "$mainUrl/videos.php?p=%d&s=bd" to "Top Rated (day)",
+ "$mainUrl/videos.php?p=%d&s=pw" to "Popular last week",
+ "$mainUrl/videos.php?p=%d&s=bw" to "Top Rated (week)",
+ "$mainUrl/videos.php?p=%d&s=pm" to "Popular last month",
+ "$mainUrl/videos.php?p=%d&s=bm" to "Top Rated (month)",
+ )
+
+ override suspend fun getMainPage(
+ page: Int, request: MainPageRequest
+ ): HomePageResponse {
+ val document = app.get(request.data.format(page)).document
+ val home =
+ document.select("div.main-content section.main-container div.list-videos article.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("div.item-info h2.title")?.text() ?: return null
+ val href = fixUrl(this.selectFirst("a")!!.attr("href"))
+ val posterUrl = fixUrlNull(this.select("a div.img img").attr("data-original"))
+ return newMovieSearchResponse(title, href, TvType.Movie) {
+ this.posterUrl = posterUrl
+ }
+
+ }
+
+ override suspend fun search(query: String): List {
+ val searchResponse = mutableListOf()
+ for (i in 1..15) {
+ val document = app.get(
+ "$mainUrl/videos.php?p=${i}&q=${query.trim().replace(" ", "+")}"
+ ).document
+ val results =
+ document.select("div.main-content section.main-container div.list-videos article.item")
+ .mapNotNull {
+ it.toSearchResult()
+ }
+ searchResponse.addAll(results)
+ if (results.isEmpty()) break
+ }
+ return searchResponse
+ }
+
+ override suspend fun load(url: String): LoadResponse {
+ val document = app.get(url).document
+
+ val title =
+ document.selectFirst("section.video-holder div.video-info div.info-holder article#tab_video_info.tab-content div.headline h1")
+ ?.text()
+ ?: ""
+ val poster = fixUrlNull(
+ document.selectXpath("//script[contains(text(),'var schemaJson')]").first()?.data()
+ ?.replace("\"", "")
+ ?.substringAfter("thumbnailUrl:")
+ ?.substringBefore(",uploadDate:")
+ ?.trim() ?: ""
+ )
+ val tags =
+ document.select(" section.video-holder div.video-info div.info-holder article#tab_video_info.tab-content div.block-details div.info h3.item a")
+ .map { it.text() }
+ val recommendations =
+ document.select("div.related-videos div.list-videos article.item")
+ .mapNotNull {
+ it.toSearchResult()
+ }
+
+ return newMovieLoadResponse(title, url, TvType.NSFW, url) {
+ this.posterUrl = poster
+ 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 script =
+ document.selectXpath("//script[contains(text(),'let vpage_data')]").first()?.html()
+ var isVHQ = false
+ if (script != null && script.contains("VHQ")) {
+ isVHQ = true
+ }
+ val pattern = Regex("""window\.initPlayer\((.*])\);""")
+ val matchResult = pattern.find(script ?: "")
+
+ val jsonArray = matchResult?.groups?.get(1)?.value
+
+ val encodedString = getEncodedString(jsonArray) ?: ""
+
+ val decodedString = customBase64Decoder(encodedString)
+
+ val videos = JSONObject("{ videos:$decodedString}").getJSONArray("videos")
+ val externalLinkList = mutableListOf()
+ for (i in 0 until videos.length()) {
+ val video = videos.getJSONObject(i)
+ var quality = Qualities.Unknown.value
+ var isM3u8 = false
+ if (video.getString("format").contains("lq")) {
+ quality = Qualities.P480.value
+ }
+ if (video.getString("format").contains("hq")) {
+ quality = Qualities.P720.value
+ }
+ var url = customBase64Decoder(video.getString("video_url"))
+ if (isVHQ) {
+ url = "$url&f=video.m3u8"
+ isM3u8 = true
+ quality = Qualities.Unknown.value
+ }
+ externalLinkList.add(
+ ExtractorLink(
+ this.name,
+ this.name,
+ fixUrl(url),
+ referer = mainUrl,
+ quality = quality,
+ isM3u8 = isM3u8
+ )
+ )
+ if (isVHQ) break
+ }
+
+ externalLinkList.forEach(callback)
+ return true
+ }
+
+ private fun customBase64Decoder(encodedString: String): String {
+ val base64CharacterSet = "АВСDЕFGHIJKLМNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,~"
+ var decodedString = ""
+ var currentIndex = 0
+
+ Regex("[^АВСЕМA-Za-z0-9.,~]").find(encodedString)?.let {
+ println("Error decoding URL")
+ }
+
+ val sanitizedString = encodedString.replace("[^АВСЕМA-Za-z0-9.,~]".toRegex(), "")
+
+ do {
+ val firstCharIndex = base64CharacterSet.indexOf(sanitizedString[currentIndex++])
+ val secondCharIndex = base64CharacterSet.indexOf(sanitizedString[currentIndex++])
+ val thirdCharIndex = base64CharacterSet.indexOf(sanitizedString[currentIndex++])
+ val fourthCharIndex = base64CharacterSet.indexOf(sanitizedString[currentIndex++])
+
+ val reconstructedFirstChar = (firstCharIndex shl 2) or (secondCharIndex shr 4)
+ val reconstructedSecondChar = ((15 and secondCharIndex) shl 4) or (thirdCharIndex shr 2)
+ val lastPart = ((3 and thirdCharIndex) shl 6) or fourthCharIndex
+
+ decodedString += reconstructedFirstChar.toChar().toString()
+ if (64 != thirdCharIndex) {
+ decodedString += reconstructedSecondChar.toChar().toString()
+ }
+ if (64 != fourthCharIndex) {
+ decodedString += lastPart.toChar().toString()
+ }
+ } while (currentIndex < sanitizedString.length)
+ return java.net.URLDecoder.decode(decodedString, "UTF-8")
+ }
+
+ private fun getEncodedString(json: String?): String? {
+ val stringPattern = Regex("""'([^']+)',""")
+
+ val stringMatch = stringPattern.find(json ?: "")
+
+ return when {
+ stringMatch != null -> stringMatch.groups[1]?.value
+ else -> null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Pornhits/src/main/kotlin/com/KillerDogeEmpire/PornhitsProvider.kt b/Pornhits/src/main/kotlin/com/KillerDogeEmpire/PornhitsProvider.kt
new file mode 100644
index 0000000..f4df368
--- /dev/null
+++ b/Pornhits/src/main/kotlin/com/KillerDogeEmpire/PornhitsProvider.kt
@@ -0,0 +1,14 @@
+package com.KillerDogeEmpire
+
+import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
+import com.lagradost.cloudstream3.plugins.Plugin
+import android.content.Context
+import com.KillerDogeEmpire.Pornhits
+
+@CloudstreamPlugin
+class PornhitsProvider: Plugin() {
+ override fun load(context: Context) {
+ // All providers should be added in this manner. Please don't edit the providers list directly.
+ registerMainAPI(Pornhits())
+ }
+}
\ No newline at end of file
diff --git a/Pornhub/build.gradle.kts b/Pornhub/build.gradle.kts
index e627ac2..724c29c 100644
--- a/Pornhub/build.gradle.kts
+++ b/Pornhub/build.gradle.kts
@@ -1,12 +1,12 @@
// use an integer for version numbers
-version = 5
+version = 6
cloudstream {
// All of these properties are optional, you can safely remove them
description = "Cornhub"
- authors = listOf("Stormunblessed", "Jace")
+ authors = listOf("KillerDogeEmpire, Stormunblessed, Jace ,Hexated, Coxju")
/**
* Status int as the following:
diff --git a/Pornhub/src/main/kotlin/com/jacekun/Pornhub.kt b/Pornhub/src/main/kotlin/com/jacekun/Pornhub.kt
index 4ab7001..f7b1379 100644
--- a/Pornhub/src/main/kotlin/com/jacekun/Pornhub.kt
+++ b/Pornhub/src/main/kotlin/com/jacekun/Pornhub.kt
@@ -1,142 +1,117 @@
package com.jacekun
-import com.lagradost.cloudstream3.MainAPI
-import com.lagradost.cloudstream3.TvType
-import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.network.WebViewResolver
-import com.lagradost.cloudstream3.utils.*
+import android.util.Log
import org.jsoup.nodes.Element
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.utils.*
+import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
-class Pornhub : MainAPI() {
- private val globalTvType = TvType.NSFW
-
- override var mainUrl = "https://www.pornhub.com"
- override var name = "Pornhub"
- override val hasMainPage = true
+class PornHub : MainAPI() {
+ override var mainUrl = "https://www.pornhub.com"
+ override var name = "PornHub"
+ override val hasMainPage = true
+ override var lang = "en"
+ override val hasQuickSearch = false
+ override val hasDownloadSupport = true
override val hasChromecastSupport = true
- override val hasDownloadSupport = true
- override val vpnStatus = VPNStatus.MightBeNeeded //Cause it's a big site
- override val supportedTypes = setOf(TvType.NSFW)
+ override val supportedTypes = setOf(TvType.NSFW)
+ override val vpnStatus = VPNStatus.MightBeNeeded
override val mainPage = mainPageOf(
- "$mainUrl/video?page=" to "Main Page",
+ "${mainUrl}/video?o=mr&hd=1&page=" to "Recently Featured",
+ "${mainUrl}/video?o=tr&t=w&hd=1&page=" to "Top Rated",
+ "${mainUrl}/video?o=mv&t=w&hd=1&page=" to "Most Viewed",
+ "${mainUrl}/video?o=ht&t=w&hd=1&page=" to "Hottest",
+ "${mainUrl}/video?p=professional&hd=1&page=" to "Professional",
+ "${mainUrl}/video?o=lg&hd=1&page=" to "Longest",
+ "${mainUrl}/video?p=homemade&hd=1&page=" to "Homemade",
+ "${mainUrl}/video?o=cm&t=w&hd=1&page=" to "Newest",
)
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
- try {
- val categoryData = request.data
- val categoryName = request.name
- val pagedLink = if (page > 0) categoryData + page else categoryData
- val soup = app.get(pagedLink).document
- val home = soup.select("div.sectionWrapper div.wrap").mapNotNull {
- if (it == null) { return@mapNotNull null }
- val title = it.selectFirst("span.title a")?.text() ?: ""
- val link = fixUrlNull(it.selectFirst("a")?.attr("href")) ?: return@mapNotNull null
- val img = fetchImgUrl(it.selectFirst("img"))
- MovieSearchResponse(
- name = title,
- url = link,
- apiName = this.name,
- type = globalTvType,
- posterUrl = img
- )
- }
- if (home.isNotEmpty()) {
- return newHomePageResponse(
- list = HomePageList(
- name = categoryName,
- list = home,
- isHorizontalImages = true
- ),
- hasNext = true
- )
- } else {
- throw ErrorLoadingException("No homepage data found!")
- }
- } catch (e: Exception) {
- //e.printStackTrace()
- logError(e)
- }
- throw ErrorLoadingException()
+ val document = app.get(request.data + page).document
+ val home = document.select("li.pcVideoListItem").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("a")?.attr("title") ?: return null
+ val link = this.selectFirst("a")?.attr("href") ?: return null
+ val posterUrl = fixUrlNull(this.selectFirst("img.thumb")?.attr("src"))
+
+ return newMovieSearchResponse(title, link, TvType.Movie) { this.posterUrl = posterUrl }
}
override suspend fun search(query: String): List {
- val url = "$mainUrl/video/search?search=${query}"
- val document = app.get(url).document
- return document.select("div.sectionWrapper div.wrap").mapNotNull {
- if (it == null) { return@mapNotNull null }
- val title = it.selectFirst("span.title a")?.text() ?: return@mapNotNull null
- val link = fixUrlNull(it.selectFirst("a")?.attr("href")) ?: return@mapNotNull null
- val image = fetchImgUrl(it.selectFirst("img"))
- MovieSearchResponse(
- name = title,
- url = link,
- apiName = this.name,
- type = globalTvType,
- posterUrl = image
- )
- }.distinctBy { it.url }
+ val document = app.get("${mainUrl}/video/search?search=${query}").document
+
+ return document.select("li.pcVideoListItem").mapNotNull { it.toSearchResult() }
}
- override suspend fun load(url: String): LoadResponse {
- val soup = app.get(url).document
- val title = soup.selectFirst(".title span")?.text() ?: ""
- val poster: String? = soup.selectFirst("div.video-wrapper .mainPlayerDiv img")?.attr("src") ?:
- soup.selectFirst("head meta[property=og:image]")?.attr("content")
- val tags = soup.select("div.categoriesWrapper a")
- .map { it?.text()?.trim().toString().replace(", ","") }
- return MovieLoadResponse(
- name = title,
- url = url,
- apiName = this.name,
- type = globalTvType,
- dataUrl = url,
- posterUrl = poster,
- tags = tags,
- plot = title
- )
- }
- override suspend fun loadLinks(
- data: String,
- isCasting: Boolean,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ): Boolean {
- app.get(
- url = data,
- interceptor = WebViewResolver(
- Regex("(master\\.m3u8\\?.*)")
- )
- ).let { response ->
- M3u8Helper().m3u8Generation(
- M3u8Helper.M3u8Stream(
- response.url,
- headers = response.headers.toMap()
- ), true
- ).apmap { stream ->
- callback(
- ExtractorLink(
- source = name,
- name = "${this.name} m3u8",
- url = stream.streamUrl,
- referer = mainUrl,
- quality = getQualityFromName(stream.quality?.toString()),
- isM3u8 = true
- )
- )
+ override suspend fun quickSearch(query: String): List = search(query)
+
+ override suspend fun load(url: String): LoadResponse? {
+ val document = app.get(url).document
+
+ val title = document.selectFirst("h1.title span[class='inlineFree']")?.text()?.trim() ?: return null
+ val description = title
+ val poster = fixUrlNull(document.selectFirst("div.mainPlayerDiv img")?.attr("src"))
+ val year = Regex("""uploadDate\": \"(\d+)""").find(document.html())?.groupValues?.get(1)?.toIntOrNull()
+ val tags = document.select("div.categoriesWrapper a[data-label='Category']").map { it?.text()?.trim().toString().replace(", ","") }
+ val rating = document.selectFirst("span.percent")?.text()?.first()?.toString()?.toRatingInt()
+ val duration = Regex("duration' : '(.*)',").find(document.html())?.groupValues?.get(1)?.toIntOrNull()
+ val actors = document.select("div.pornstarsWrapper a[data-label='Pornstar']").mapNotNull {
+ Actor(it.text().trim(), it.select("img").attr("src"))
+ }
+
+ val recommendations = document.selectXpath("//a[contains(@class, 'img')]").mapNotNull {
+ val recName = it?.attr("title")?.trim() ?: return@mapNotNull null
+ val recHref = fixUrlNull(it.attr("href")) ?: return@mapNotNull null
+ val recPosterUrl = fixUrlNull(it.selectFirst("img")?.attr("src"))
+ newMovieSearchResponse(recName, recHref, TvType.NSFW) {
+ this.posterUrl = recPosterUrl
}
}
- return true
+
+ return newMovieLoadResponse(title, url, TvType.NSFW, url) {
+ this.posterUrl = poster
+ this.year = year
+ this.plot = description
+ this.tags = tags
+ this.rating = rating
+ this.duration = duration
+ this.recommendations = recommendations
+ addActors(actors)
+ }
}
- private fun fetchImgUrl(imgsrc: Element?): String? {
- return try { imgsrc?.attr("data-src")
- ?: imgsrc?.attr("data-mediabook")
- ?: imgsrc?.attr("alt")
- ?: imgsrc?.attr("data-mediumthumb")
- ?: imgsrc?.attr("data-thumb_url")
- ?: imgsrc?.attr("src")
- } catch (e:Exception) { null }
+ override suspend fun loadLinks(data: String, isCasting: Boolean, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit): Boolean {
+ Log.d("PHub", "url » ${data}")
+ val source = app.get(data).text
+ val extracted_value = Regex("""([^\"]*master.m3u8?.[^\"]*)""").find(source)?.groups?.last()?.value ?: return false
+ val m3u_link = extracted_value.replace("\\", "")
+ Log.d("PHub", "extracted_value » ${extracted_value}")
+ Log.d("PHub", "m3u_link » ${m3u_link}")
+
+ callback.invoke(
+ ExtractorLink(
+ source = this.name,
+ name = this.name,
+ url = m3u_link,
+ referer = "${mainUrl}/",
+ quality = Qualities.Unknown.value,
+ isM3u8 = true
+ )
+ )
+
+ return true
}
}
\ No newline at end of file
diff --git a/Porntrex/build.gradle.kts b/Porntrex/build.gradle.kts
new file mode 100644
index 0000000..4154860
--- /dev/null
+++ b/Porntrex/build.gradle.kts
@@ -0,0 +1,28 @@
+// use an integer for version numbers
+version = 5
+
+
+cloudstream {
+ // All of these properties are optional, you can safely remove them
+
+ description = "Porntrex"
+ authors = listOf("KillerDogeEmpire, Coxju")
+
+ /**
+ * Status int as the following:
+ * 0: Down
+ * 1: Ok
+ * 2: Slow
+ * 3: Beta only
+ * */
+ status = 1 // will be 3 if unspecified
+
+ // List of video source types. Users are able to filter for extensions in a given category.
+ // You can find a list of avaliable types here:
+ // https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html
+ tvTypes = listOf("NSFW")
+
+ iconUrl = "https://www.google.com/s2/favicons?domain=www.porntrex.com&sz=%size%"
+
+ language = "en"
+}
diff --git a/Porntrex/src/main/AndroidManifest.xml b/Porntrex/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0862a59
--- /dev/null
+++ b/Porntrex/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/Porntrex/src/main/kotlin/com/KillerDogeEmpire/Porntrex.kt b/Porntrex/src/main/kotlin/com/KillerDogeEmpire/Porntrex.kt
new file mode 100644
index 0000000..c224379
--- /dev/null
+++ b/Porntrex/src/main/kotlin/com/KillerDogeEmpire/Porntrex.kt
@@ -0,0 +1,183 @@
+package com.KillerDogeEmpire
+
+import com.lagradost.cloudstream3.HomePageList
+import com.lagradost.cloudstream3.HomePageResponse
+import com.lagradost.cloudstream3.LoadResponse
+import com.lagradost.cloudstream3.MainAPI
+import com.lagradost.cloudstream3.MainPageRequest
+import com.lagradost.cloudstream3.SearchResponse
+import com.lagradost.cloudstream3.SubtitleFile
+import com.lagradost.cloudstream3.TvType
+import com.lagradost.cloudstream3.VPNStatus
+import com.lagradost.cloudstream3.app
+import com.lagradost.cloudstream3.fixUrl
+import com.lagradost.cloudstream3.fixUrlNull
+import com.lagradost.cloudstream3.mainPageOf
+import com.lagradost.cloudstream3.newHomePageResponse
+import com.lagradost.cloudstream3.newMovieLoadResponse
+import com.lagradost.cloudstream3.newMovieSearchResponse
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.getQualityFromName
+import org.json.JSONObject
+import org.jsoup.internal.StringUtil
+import org.jsoup.nodes.Element
+
+class Porntrex : MainAPI() {
+ override var mainUrl = "https://www.porntrex.com"
+ override var name = "Porntrex"
+ override val hasMainPage = true
+ override val hasDownloadSupport = true
+ override val vpnStatus = VPNStatus.MightBeNeeded
+ override val supportedTypes = setOf(TvType.NSFW)
+
+ override val mainPage = mainPageOf(
+ "latest-updates" to "Latest Videos",
+ "most-popular/daily/?mode=async&function=get_block&block_id=list_videos_common_videos_list_norm&sort_by=video_viewed_today&from4=" to "Most popular daily",
+ "top-rated/daily/?mode=async&function=get_block&block_id=list_videos_common_videos_list_norm&sort_by=rating_today&from4=" to "Top rated daily",
+ "most-popular/weekly/?mode=async&function=get_block&block_id=list_videos_common_videos_list_norm&sort_by=video_viewed_week&from4=" to "Most popular weekly",
+ "top-rated/weekly/?mode=async&function=get_block&block_id=list_videos_common_videos_list_norm&sort_by=rating_week&from4=" to "Top rated weekly",
+ "most-popular/monthly/?mode=async&function=get_block&block_id=list_videos_common_videos_list_norm&sort_by=video_viewed_month&from4=" to "Most popular monthly",
+ "top-rated/monthly/?mode=async&function=get_block&block_id=list_videos_common_videos_list_norm&sort_by=rating_month&from4=" to "Top rated monthly",
+ "most-popular/?mode=async&function=get_block&block_id=list_videos_common_videos_list_norm&sort_by=video_viewed&from4=" to "Most popular all time",
+ "top-rated/?mode=async&function=get_block&block_id=list_videos_common_videos_list_norm&sort_by=rating&from4=" to "Top rated all time",
+ )
+
+ override suspend fun getMainPage(
+ page: Int,
+ request: MainPageRequest
+ ): HomePageResponse {
+ var url: String
+ url = if (page == 1) {
+ "$mainUrl/${request.data}/"
+ } else {
+ "$mainUrl/${request.data}/${page}/"
+ }
+ if (request.data.contains("mode=async")) {
+ url = "$mainUrl/${request.data}${page}"
+ }
+ val document = app.get(url).document
+ val home =
+ document.select("div.video-list div.video-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("p.inf a")?.text() ?: return null
+ val href = fixUrl(this.selectFirst("p.inf a")!!.attr("href"))
+ val posterUrl = fixUrlNull(this.select("a.thumb img.cover").attr("data-src"))
+ return newMovieSearchResponse(title, href, TvType.Movie) {
+ this.posterUrl = posterUrl
+ this.posterHeaders = mapOf(Pair("referer", "${mainUrl}/"))
+ }
+
+ }
+
+ override suspend fun search(query: String): List {
+ val searchResponse = mutableListOf()
+ for (i in 1..15) {
+ val url: String = if (i == 1) {
+ "$mainUrl/search/${query.replace(" ", "-")}/"
+ } else {
+ "$mainUrl/search/${query.replace(" ", "-")}/$i/"
+ }
+ val document =
+ app.get(url).document
+ val results =
+ document.select("div.video-list div.video-item")
+ .mapNotNull {
+ it.toSearchResult()
+ }
+ searchResponse.addAll(results)
+ if (results.isEmpty()) break
+ }
+ return searchResponse
+ }
+
+ override suspend fun load(url: String): LoadResponse {
+ val document = app.get(url).document
+
+ val jsonObject = JSONObject(document.selectXpath("//script[contains(text(),'var flashvars')]").first()?.data()
+ ?.substringAfter("var flashvars = ")
+ ?.substringBefore("var player_obj")
+ ?.replace(";", "") ?: "")
+
+ val title = jsonObject.getString("video_title")
+ val poster =
+ fixUrlNull(jsonObject.getString("preview_url"))
+
+ val tags = jsonObject.getString("video_tags").split(", ").map { it.replace("-", "") }.filter { it.isNotBlank() && !StringUtil.isNumeric(it) }
+ val description = jsonObject.getString("video_title")
+
+ val recommendations =
+ document.select("div#list_videos_related_videos div.video-list div.video-item").mapNotNull {
+ it.toSearchResult()
+ }
+
+ return newMovieLoadResponse(title, url, TvType.NSFW, url) {
+ this.posterUrl = poster
+ this.posterHeaders = mapOf(Pair("referer", "${mainUrl}/"))
+ this.plot = description
+ 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 jsonObject = JSONObject(document.selectXpath("//script[contains(text(),'var flashvars')]").first()?.data()
+ ?.substringAfter("var flashvars = ")
+ ?.substringBefore("var player_obj")
+ ?.replace(";", "") ?: "")
+ val extlinkList = mutableListOf()
+ for (i in 0 until 7) {
+ var url: String
+ var quality: String
+ if (i == 0) {
+ url = jsonObject.optString("video_url") ?: ""
+ quality = jsonObject.optString("video_url_text") ?: ""
+ } else {
+ if (i == 1) {
+ url = jsonObject.optString("video_alt_url") ?: ""
+ quality = jsonObject.optString("video_alt_url_text") ?: ""
+ } else {
+ url = jsonObject.optString("video_alt_url${i}") ?: ""
+ quality = jsonObject.optString("video_alt_url${i}_text") ?: ""
+ }
+ }
+ if (url == "") {
+ continue
+ }
+ extlinkList.add(
+ ExtractorLink(
+ name,
+ name,
+ fixUrl(url),
+ referer = "${mainUrl}/",
+ quality =
+ Regex("(\\d+.)").find(quality)?.groupValues?.get(1)
+ .let { getQualityFromName(it) }
+ )
+ )
+ }
+ extlinkList.forEach(callback)
+ return true
+ }
+
+}
+
diff --git a/Porntrex/src/main/kotlin/com/KillerDogeEmpire/PorntrexProvider.kt b/Porntrex/src/main/kotlin/com/KillerDogeEmpire/PorntrexProvider.kt
new file mode 100644
index 0000000..d13c510
--- /dev/null
+++ b/Porntrex/src/main/kotlin/com/KillerDogeEmpire/PorntrexProvider.kt
@@ -0,0 +1,14 @@
+package com.KillerDogeEmpire
+
+import android.content.Context
+import com.KillerDogeEmpire.Porntrex
+import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
+import com.lagradost.cloudstream3.plugins.Plugin
+
+@CloudstreamPlugin
+class PorntrexProvider : Plugin() {
+ override fun load(context: Context) {
+ // All providers should be added in this manner. Please don't edit the providers list directly.
+ registerMainAPI(Porntrex())
+ }
+}
\ No newline at end of file
diff --git a/Example/build.gradle.kts b/SxyPrn/build.gradle.kts
similarity index 71%
rename from Example/build.gradle.kts
rename to SxyPrn/build.gradle.kts
index aad9fd6..9100e9b 100644
--- a/Example/build.gradle.kts
+++ b/SxyPrn/build.gradle.kts
@@ -1,12 +1,12 @@
// use an integer for version numbers
-version = 1
+version = 6
cloudstream {
// All of these properties are optional, you can safely remove them
- description = ""
- authors = listOf("Jace")
+ description = "sxyprn"
+ authors = listOf("Coxju")
/**
* Status int as the following:
@@ -15,12 +15,14 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 0 // will be 3 if unspecified
+ status = 1 // will be 3 if unspecified
// List of video source types. Users are able to filter for extensions in a given category.
// You can find a list of avaliable types here:
// https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html
tvTypes = listOf("NSFW")
- iconUrl = "https://www.google.com/s2/favicons?domain=example.com&sz=%size%"
+ iconUrl = "https://www.google.com/s2/favicons?domain=sxyprn.com&sz=%size%"
+
+ language = "en"
}
diff --git a/SxyPrn/src/main/AndroidManifest.xml b/SxyPrn/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0862a59
--- /dev/null
+++ b/SxyPrn/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/SxyPrn/src/main/kotlin/com/KillerDogeEmpire/SxyPrn.kt b/SxyPrn/src/main/kotlin/com/KillerDogeEmpire/SxyPrn.kt
new file mode 100644
index 0000000..b5b50dc
--- /dev/null
+++ b/SxyPrn/src/main/kotlin/com/KillerDogeEmpire/SxyPrn.kt
@@ -0,0 +1,143 @@
+package com.KillerDogeEmpire
+
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.utils.AppUtils
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.Qualities
+import org.jsoup.nodes.Element
+
+class SxyPrn : MainAPI() {
+ override var mainUrl = "https://sxyprn.com"
+ override var name = "Sxyprn"
+ override val hasMainPage = true
+ override val hasDownloadSupport = true
+ override val vpnStatus = VPNStatus.MightBeNeeded
+ override val supportedTypes = setOf(TvType.NSFW)
+
+ override val mainPage = mainPageOf(
+ "$mainUrl/new.html?page=" to "New Videos",
+ "$mainUrl/new.html?sm=trending&page=" to "Trending",
+ "$mainUrl/new.html?sm=views&page=" to "Most Viewed",
+ "$mainUrl/popular/top-viewed.html?p=day" to "Popular - Day",
+ "$mainUrl/popular/top-viewed.html" to "Popular - Week",
+ "$mainUrl/popular/top-viewed.html?p=month" to "Popular - Month",
+ "$mainUrl/popular/top-viewed.html?p=all" to "Popular - All Time"
+ )
+
+ override suspend fun getMainPage(
+ page: Int, request: MainPageRequest
+ ): HomePageResponse {
+ var pageStr = ((page - 1) * 30).toString()
+
+ val document = if ("page=" in request.data) {
+ app.get(request.data + pageStr).document
+ } else if ("/blog/" in request.data) {
+ pageStr = ((page - 1) * 20).toString()
+ app.get(request.data.replace(".html", "$pageStr.html")).document
+ } else {
+ app.get(request.data.replace(".html", ".html/$pageStr")).document
+ }
+ val home = document.select("div.main_content div.post_el_small").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("div.post_text")?.text() ?: return null
+ val href = fixUrl(this.selectFirst("a.js-pop")!!.attr("href"))
+ var posterUrl = fixUrl(this.select("div.vid_container div.post_vid_thumb img").attr("src"))
+ if (posterUrl == "") {
+ posterUrl =
+ fixUrl(this.select("div.vid_container div.post_vid_thumb img").attr("data-src"))
+ }
+ return newMovieSearchResponse(title, href, TvType.Movie) {
+ this.posterUrl = posterUrl
+ }
+ }
+
+ override suspend fun search(query: String): List {
+ val searchResponse = mutableListOf()
+ for (i in 0 until 15) {
+ val document = app.get(
+ "$mainUrl/${query.replace(" ", "-")}.html?page=${i * 30}"
+ ).document
+ val results = document.select("div.main_content div.post_el_small").mapNotNull {
+ it.toSearchResult()
+ }
+ if (!searchResponse.containsAll(results)) {
+ searchResponse.addAll(results)
+ } else {
+ break
+ }
+ if (results.isEmpty()) break
+ }
+ return searchResponse
+ }
+
+ override suspend fun load(url: String): LoadResponse {
+ val document = app.get(url).document
+ val title = document.selectFirst("div.post_text")?.text()?.trim().toString()
+ val poster = fixUrlNull(
+ document.selectFirst("div#vid_container_id meta[itemprop=thumbnailUrl]")
+ ?.attr("content")
+ )
+
+ val recommendations = document.select("div.main_content div div.post_el_small").mapNotNull {
+ it.toSearchResult()
+ }
+
+ return newMovieLoadResponse(title, url, TvType.NSFW, url) {
+ this.posterUrl = poster
+ this.recommendations = recommendations
+ }
+ }
+
+ private fun updateUrl(arg: MutableList): MutableList {
+ arg[5] =
+ (Integer.parseInt(arg[5]) - (generateNumber(arg[6]) + generateNumber(arg[7]))).toString()
+ return arg
+ }
+
+ private fun generateNumber(arg: String): Int {
+ val str = arg.replace(Regex("\\D"), "")
+ var sut = 0
+ for (element in str) {
+ sut += Integer.parseInt(element.toString(), 10)
+ }
+ return sut
+ }
+
+ override suspend fun loadLinks(
+ data: String,
+ isCasting: Boolean,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit
+ ): Boolean {
+ val document = app.get(data).document
+ val parsed = AppUtils.parseJson