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