diff --git a/Pornhub/build.gradle.kts b/Pornhub/build.gradle.kts new file mode 100644 index 0000000..156c158 --- /dev/null +++ b/Pornhub/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + description = "Cornhub" + authors = listOf("Jace") + + /** + * 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=pornhub.com&sz=%size%" +} diff --git a/Pornhub/src/main/AndroidManifest.xml b/Pornhub/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/Pornhub/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Pornhub/src/main/kotlin/com/jacekun/Pornhub.kt b/Pornhub/src/main/kotlin/com/jacekun/Pornhub.kt new file mode 100644 index 0000000..f6c8f3c --- /dev/null +++ b/Pornhub/src/main/kotlin/com/jacekun/Pornhub.kt @@ -0,0 +1,142 @@ +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 org.jsoup.nodes.Element + +class Pornhub : MainAPI() { + private val globalTvType = TvType.Movie + + override var mainUrl = "https://www.pornhub.com" + override var name = "Pornhub" + override val hasMainPage = 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 mainPage = mainPageOf( + "$mainUrl/video?page=" to "Main Page", + ) + + 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() + } + + 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 } + } + + 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 + ) + ) + } + } + return true + } + + 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 } + } +} \ No newline at end of file diff --git a/Pornhub/src/main/kotlin/com/jacekun/PornhubPlugin.kt b/Pornhub/src/main/kotlin/com/jacekun/PornhubPlugin.kt new file mode 100644 index 0000000..e4a340f --- /dev/null +++ b/Pornhub/src/main/kotlin/com/jacekun/PornhubPlugin.kt @@ -0,0 +1,13 @@ +package com.jacekun + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class PornhubPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Pornhub()) + } +} \ No newline at end of file