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..24d2c19 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 = "Hentai Haven" + 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/src/main/AndroidManifest.xml b/NoodleMagazineProvider/src/main/AndroidManifest.xml index 7b2bd9d..29aec9d 100644 --- a/NoodleMagazineProvider/src/main/AndroidManifest.xml +++ b/NoodleMagazineProvider/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazineProvider.kt b/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazineProvider.kt index c5cd297..4013e55 100644 --- a/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazineProvider.kt +++ b/NoodleMagazineProvider/src/main/kotlin/com/KillerDogeEmpire/NoodleMagazineProvider.kt @@ -38,18 +38,22 @@ class NoodleMagazineProvider : MainAPI() { // all providers must be an instance return newHomePageResponse(request.name, home) } + private fun Element.toSearchResult(): MovieSearchResponse? { + 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 newMovieSearchResponse(title, href, TvType.Movie) { + 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 { 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>( + document.select("span.vidsnfo").attr("data-vnfo") + ) + parsed[parsed.keys.toList()[0]] + var url = parsed[parsed.keys.toList()[0]].toString() + + var tmp = url.split("/").toMutableList() + tmp[1] += "8" + tmp = updateUrl(tmp) + + url = fixUrl(tmp.joinToString("/")) + + callback.invoke( + ExtractorLink( + this.name, this.name, url, referer = data, quality = Qualities.Unknown.value + ) + ) + + return true + } + +} \ No newline at end of file diff --git a/SxyPrn/src/main/kotlin/com/KillerDogeEmpire/SxyPrnProvider.kt b/SxyPrn/src/main/kotlin/com/KillerDogeEmpire/SxyPrnProvider.kt new file mode 100644 index 0000000..80830ca --- /dev/null +++ b/SxyPrn/src/main/kotlin/com/KillerDogeEmpire/SxyPrnProvider.kt @@ -0,0 +1,14 @@ +package com.KillerDogeEmpire + +import android.content.Context +import com.KillerDogeEmpire.SxyPrn +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin + +@CloudstreamPlugin +class SxyPrnProvider : Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(SxyPrn()) + } +} \ No newline at end of file diff --git a/UncutMaza/build.gradle.kts b/UncutMaza/build.gradle.kts new file mode 100644 index 0000000..996acae --- /dev/null +++ b/UncutMaza/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 = "Uncutmaza" + authors = listOf("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=uncutmaza.com&sz=%size%" + + language = "en" +} diff --git a/UncutMaza/src/main/AndroidManifest.xml b/UncutMaza/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862a59 --- /dev/null +++ b/UncutMaza/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/UncutMaza/src/main/kotlin/com/KillerDogeEmpire/UncutMaza.kt b/UncutMaza/src/main/kotlin/com/KillerDogeEmpire/UncutMaza.kt new file mode 100644 index 0000000..0d8e859 --- /dev/null +++ b/UncutMaza/src/main/kotlin/com/KillerDogeEmpire/UncutMaza.kt @@ -0,0 +1,118 @@ +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.fixTitle +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.jsoup.nodes.Element + +class UncutMaza : MainAPI() { + override var mainUrl = "https://uncutmaza.com" + override var name = "Uncutmaza" + override val hasMainPage = true + override val hasDownloadSupport = true + override val vpnStatus = VPNStatus.MightBeNeeded + override val supportedTypes = setOf(TvType.NSFW) + + override val mainPage = mainPageOf( + "$mainUrl/page/" to "Home", "$mainUrl/category/niks-indian-porn/page/" to "Niks Indian" + ) + + override suspend fun getMainPage( + page: Int, request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + val home = document.select("div.videos-list > article.post").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse( + list = HomePageList( + name = request.name, list = home, isHorizontalImages = true + ), hasNext = true + ) + } + + private fun Element.toSearchResult(): SearchResponse { + val title = fixTitle(this.select("a").attr("title")) + val href = fixUrl(this.select("a").attr("href")) + val posterUrl = fixUrlNull( + this.select("a > div.post-thumbnail>div.post-thumbnail-container>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..5) { + val document = app.get( + "$mainUrl/page/$i?s=$query" + ).document + val results = document.select("article.post").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("meta[property=og:title]")?.attr("content")?.trim().toString() + val poster = + fixUrlNull(document.selectFirst("meta[property=og:image]")?.attr("content").toString()) + val description = + document.selectFirst("meta[property=og:description]")?.attr("content")?.trim() + + return newMovieLoadResponse(title, url, TvType.NSFW, url) { + this.posterUrl = poster + this.plot = description + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val document = app.get(data).document + document.select("div.video-player").map { res -> + callback.invoke( + ExtractorLink( + this.name, this.name, fixUrl( + res.selectFirst("meta[itemprop=contentURL]")?.attr("content")?.trim() + .toString() + ), referer = data, quality = Qualities.Unknown.value + ) + ) + } + + return true + } + +} \ No newline at end of file diff --git a/UncutMaza/src/main/kotlin/com/KillerDogeEmpire/UncutMazaProvider.kt b/UncutMaza/src/main/kotlin/com/KillerDogeEmpire/UncutMazaProvider.kt new file mode 100644 index 0000000..bb95eba --- /dev/null +++ b/UncutMaza/src/main/kotlin/com/KillerDogeEmpire/UncutMazaProvider.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.UncutMaza + +@CloudstreamPlugin +class UncutMazaProvider: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(UncutMaza()) + } +} \ No newline at end of file diff --git a/Vlxx/build.gradle.kts b/Vlxx/build.gradle.kts index a0c3635..a5d4483 100644 --- a/Vlxx/build.gradle.kts +++ b/Vlxx/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/Xhamster/build.gradle.kts b/Xhamster/build.gradle.kts new file mode 100644 index 0000000..55dd225 --- /dev/null +++ b/Xhamster/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 = "Xhamster" + 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=xhamster.com&sz=%size%" + + language = "en" +} diff --git a/Xhamster/src/main/AndroidManifest.xml b/Xhamster/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862a59 --- /dev/null +++ b/Xhamster/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Xhamster/src/main/kotlin/com/KillerDogeEmpire/Xhamster.kt b/Xhamster/src/main/kotlin/com/KillerDogeEmpire/Xhamster.kt new file mode 100644 index 0000000..d114e55 --- /dev/null +++ b/Xhamster/src/main/kotlin/com/KillerDogeEmpire/Xhamster.kt @@ -0,0 +1,100 @@ +package com.KillerDogeEmpire + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import org.jsoup.nodes.Element + +class Xhamster : MainAPI() { + override var mainUrl = "https://xhamster.com" + override var name = "xHamster" + 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}/newest/" to "Newest", + "${mainUrl}/most-viewed/weekly/" to "Most viewed weekly", + "${mainUrl}/most-viewed/monthly/" to "Most viewed monthly", + "${mainUrl}/most-viewed" to "Most viewed all time", + "${mainUrl}/most-viewed/weekly/" to "Most viewed weekly" + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val document = app.get(request.data + page + "?x_platform_switch=desktop").document + val home = document.select("div.thumb-list div.thumb-list__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("a.video-thumb-info__name")?.text() ?: return null + val href = fixUrl(this.selectFirst("a.video-thumb-info__name")!!.attr("href")) + val posterUrl = fixUrlNull(this.select("img.thumb-image-container__image").attr("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}/search/${query.replace(" ", "+")}/?page=$i&x_platform_switch=desktop").document + + val results = document.select("div.thumb-list div.thumb-list__item").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.with-player-container h1")?.text()?.trim().toString() + val poster = fixUrlNull(document.selectFirst("div.xp-preload-image")?.attr("style")?.substringAfter("https:")?.substringBefore("\');")) + val tags = document.select(" nav#video-tags-list-container ul.root-8199e.video-categories-tags.collapsed-8199e li.item-8199e a.video-tag").map { it.text() } + val recommendations = document.select("div.related-container div.thumb-list div.thumb-list__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 { + app.get(url = data).let { response -> + callback( + ExtractorLink( + source = name, + name = name, + url = fixUrl(response.document.selectXpath("//link[contains(@href,'.m3u8')]")[0]?.attr("href").toString()), + referer = mainUrl, + quality = Qualities.Unknown.value, + isM3u8 = true + ) + ) + } + + return true + } +} \ No newline at end of file diff --git a/Example/src/main/kotlin/com/jacekun/ExamplePlugin.kt b/Xhamster/src/main/kotlin/com/KillerDogeEmpire/XhamsterProvider.kt similarity index 75% rename from Example/src/main/kotlin/com/jacekun/ExamplePlugin.kt rename to Xhamster/src/main/kotlin/com/KillerDogeEmpire/XhamsterProvider.kt index bdef9af..6dc7711 100644 --- a/Example/src/main/kotlin/com/jacekun/ExamplePlugin.kt +++ b/Xhamster/src/main/kotlin/com/KillerDogeEmpire/XhamsterProvider.kt @@ -1,13 +1,13 @@ -package com.jacekun +package com.KillerDogeEmpire import com.lagradost.cloudstream3.plugins.CloudstreamPlugin import com.lagradost.cloudstream3.plugins.Plugin import android.content.Context @CloudstreamPlugin -class ExamplePlugin: Plugin() { +class XhamsterProvider: Plugin() { override fun load(context: Context) { // All providers should be added in this manner. Please don't edit the providers list directly. - registerMainAPI(Example()) + registerMainAPI(Xhamster()) } } \ No newline at end of file