diff --git a/Pornhub/build.gradle.kts b/Pornhub/build.gradle.kts index e627ac2..d28a61c 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("Stormunblessed", "Jace", "KillerDogeEmpire") /** * 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