From e7ed786076b756c9f360746491eadc998622d757 Mon Sep 17 00:00:00 2001 From: Jace <54625750+Jacekun@users.noreply.github.com> Date: Fri, 19 Aug 2022 10:51:43 +0800 Subject: [PATCH] Xvideos provider --- XvideosProvider/build.gradle.kts | 24 +++ XvideosProvider/src/main/AndroidManifest.xml | 2 + .../kotlin/com/jacekun/XvideosProvider.kt | 189 ++++++++++++++++++ .../com/jacekun/XvideosProviderPlugin.kt | 13 ++ 4 files changed, 228 insertions(+) create mode 100644 XvideosProvider/build.gradle.kts create mode 100644 XvideosProvider/src/main/AndroidManifest.xml create mode 100644 XvideosProvider/src/main/kotlin/com/jacekun/XvideosProvider.kt create mode 100644 XvideosProvider/src/main/kotlin/com/jacekun/XvideosProviderPlugin.kt diff --git a/XvideosProvider/build.gradle.kts b/XvideosProvider/build.gradle.kts new file mode 100644 index 0000000..d815975 --- /dev/null +++ b/XvideosProvider/build.gradle.kts @@ -0,0 +1,24 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + description = "High quality JAV subbed" + 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") +} diff --git a/XvideosProvider/src/main/AndroidManifest.xml b/XvideosProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1863f02 --- /dev/null +++ b/XvideosProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/XvideosProvider/src/main/kotlin/com/jacekun/XvideosProvider.kt b/XvideosProvider/src/main/kotlin/com/jacekun/XvideosProvider.kt new file mode 100644 index 0000000..663fdc6 --- /dev/null +++ b/XvideosProvider/src/main/kotlin/com/jacekun/XvideosProvider.kt @@ -0,0 +1,189 @@ +package com.jacekun + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.* + + +class XvideosProvider : MainAPI() { + private val globalTvType = TvType.NSFW + override var mainUrl = "https://www.xvideos.com" + override var name = "Xvideos" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf(globalTvType) + + override val mainPage = mainPageOf( + Pair(mainUrl, "Main Page"), + Pair("$mainUrl/new/", "New") + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val categoryData = request.data + val categoryName = request.name + val isPaged = categoryData.endsWith('/') + val pagedLink = if (isPaged) categoryData + page else categoryData + try { + if (!isPaged && page < 2 || isPaged) { + val soup = app.get(pagedLink).document + val home = soup.select("div.thumb-block").mapNotNull { + if (it == null) { return@mapNotNull null } + val title = it.selectFirst("p.title a")?.text() ?: "" + val link = fixUrlNull(it.selectFirst("div.thumb a")?.attr("href")) ?: return@mapNotNull null + val image = it.selectFirst("div.thumb a img")?.attr("data-src") + MovieSearchResponse( + name = title, + url = link, + apiName = this.name, + type = globalTvType, + posterUrl = image, + year = null + ) + } + 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?k=${query}" + val document = app.get(url).document + return document.select("div.thumb-block").mapNotNull { + val title = it.selectFirst("p.title a")?.text() + ?: it.selectFirst("p.profile-name a")?.text() + ?: "" + val href = fixUrlNull(it.selectFirst("div.thumb a")?.attr("href")) ?: return@mapNotNull null + val image = if (href.contains("channels") || href.contains("pornstars")) null else it.selectFirst("div.thumb-inside a img")?.attr("data-src") + val finaltitle = if (href.contains("channels") || href.contains("pornstars")) "" else title + MovieSearchResponse( + name = finaltitle, + url = href, + apiName = this.name, + type = globalTvType, + posterUrl = image + ) + + }.toList() + } + override suspend fun load(url: String): LoadResponse? { + val soup = app.get(url).document + val title = if (url.contains("channels")||url.contains("pornstars")) soup.selectFirst("html.xv-responsive.is-desktop head title")?.text() else + soup.selectFirst(".page-title")?.text() + val poster: String? = if (url.contains("channels") || url.contains("pornstars")) soup.selectFirst(".profile-pic img")?.attr("data-src") else + soup.selectFirst("head meta[property=og:image]")?.attr("content") + val tags = soup.select(".video-tags-list li a") + .map { it?.text()?.trim().toString().replace(", ","") } + val episodes = soup.select("div.thumb-block").mapNotNull { + val href = it?.selectFirst("a")?.attr("href") ?: return@mapNotNull null + val name = it.selectFirst("p.title a")?.text() ?: "" + val epthumb = it.selectFirst("div.thumb a img")?.attr("data-src") + Episode( + name = name, + data = href, + posterUrl = epthumb, + ) + } + val tvType = if (url.contains("channels") || url.contains("pornstars")) TvType.TvSeries else globalTvType + return when (tvType) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + name = title ?: "", + url = url, + apiName = this.name, + type = globalTvType, + episodes = episodes, + posterUrl = poster, + plot = title, + showStatus = ShowStatus.Ongoing, + tags = tags, + ) + } + TvType.NSFW -> { + MovieLoadResponse( + name = title ?: "", + url = url, + apiName = this.name, + type = tvType, + dataUrl = url, + posterUrl = poster, + plot = title, + tags = tags, + ) + } + else -> null + } + } + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("script").apmap { script -> + if (script.data().contains("HTML5Player")) { + val extractedlink = script.data().substringAfter(".setVideoHLS('") + .substringBefore("');") + if (extractedlink.isNotBlank()) { + M3u8Helper().m3u8Generation( + M3u8Helper.M3u8Stream( + extractedlink, + headers = app.get(data).headers.toMap() + ), true + ).map { stream -> + callback( + ExtractorLink( + source = this.name, + name = "${this.name} m3u8", + url = stream.streamUrl, + referer = data, + quality = getQualityFromName(stream.quality?.toString()), + isM3u8 = true + ) + ) + } + } + val mp4linkhigh = script.data().substringAfter("html5player.setVideoUrlHigh('").substringBefore("');") + if (mp4linkhigh.isNotBlank()) { + callback( + ExtractorLink( + source = this.name, + name = "${this.name} MP4 High", + url = mp4linkhigh, + referer = data, + quality = Qualities.Unknown.value, + ) + ) + } + val mp4linklow = script.data().substringAfter("html5player.setVideoUrlLow('").substringBefore("');") + if (mp4linklow.isNotBlank()) { + callback( + ExtractorLink( + source = this.name, + name = "${this.name} MP4 Low", + url = mp4linklow, + referer = data, + quality = Qualities.Unknown.value, + ) + ) + } + } + } + return true + } +} diff --git a/XvideosProvider/src/main/kotlin/com/jacekun/XvideosProviderPlugin.kt b/XvideosProvider/src/main/kotlin/com/jacekun/XvideosProviderPlugin.kt new file mode 100644 index 0000000..2234f3e --- /dev/null +++ b/XvideosProvider/src/main/kotlin/com/jacekun/XvideosProviderPlugin.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 XvideosProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(XvideosProvider()) + } +} \ No newline at end of file