From 96856cec0a66b4a85f32ccdf82b0aa2c2bb36a80 Mon Sep 17 00:00:00 2001 From: Jace <54625750+Jacekun@users.noreply.github.com> Date: Sat, 8 Oct 2022 17:20:56 +0800 Subject: [PATCH] [Provider] Vlxx. (Beta only) Sometimes work. --- Vlxx/build.gradle.kts | 26 +++ Vlxx/src/main/AndroidManifest.xml | 2 + Vlxx/src/main/kotlin/com/jacekun/Vlxx.kt | 192 ++++++++++++++++++ .../src/main/kotlin/com/jacekun/VlxxPlugin.kt | 13 ++ 4 files changed, 233 insertions(+) create mode 100644 Vlxx/build.gradle.kts create mode 100644 Vlxx/src/main/AndroidManifest.xml create mode 100644 Vlxx/src/main/kotlin/com/jacekun/Vlxx.kt create mode 100644 Vlxx/src/main/kotlin/com/jacekun/VlxxPlugin.kt diff --git a/Vlxx/build.gradle.kts b/Vlxx/build.gradle.kts new file mode 100644 index 0000000..0595da9 --- /dev/null +++ b/Vlxx/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 = "Vietnamese Subbed. CloudFare." + authors = listOf("duongnv1996", "Jace") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 3 // 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=vlxx.sex&sz=%size%" +} diff --git a/Vlxx/src/main/AndroidManifest.xml b/Vlxx/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/Vlxx/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Vlxx/src/main/kotlin/com/jacekun/Vlxx.kt b/Vlxx/src/main/kotlin/com/jacekun/Vlxx.kt new file mode 100644 index 0000000..69ab8fa --- /dev/null +++ b/Vlxx/src/main/kotlin/com/jacekun/Vlxx.kt @@ -0,0 +1,192 @@ +package com.jacekun + +import com.lagradost.cloudstream3.MainAPI +import com.lagradost.cloudstream3.TvType +import android.util.Log +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.network.CloudflareKiller +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName +import com.lagradost.nicehttp.NiceResponse + +class Vlxx : MainAPI() { + private val DEV = "DevDebug" + private val globaltvType = TvType.Movie + + override var name = "Vlxx" + override var mainUrl = "https://vlxx.sex" + override val supportedTypes = setOf(TvType.NSFW) + override val hasDownloadSupport = false + override val hasMainPage = true + override val hasQuickSearch = false + private val interceptor = CloudflareKiller() + + private suspend fun getPage(url: String, referer: String): NiceResponse { + var count = 0 + var resp = app.get(url, referer = referer, interceptor = interceptor) + Log.i(DEV, "Page Response => ${resp}") +// while (!resp.isSuccessful) { +// resp = app.get(url, interceptor = interceptor) +// count++ +// if (count > 4) { +// return resp +// } +// } + return resp + } + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val apiName = this.name + val document = getPage(mainUrl, mainUrl).document + val all = ArrayList() + val title = "Homepage" + Log.i(DEV, "Fetching videos..") + val elements = document.select("div#video-list > div.video-item") + .mapNotNull { + val firstA = it.selectFirst("a") + val link = fixUrlNull(firstA?.attr("href")) ?: return@mapNotNull null + val img = it.selectFirst("img")?.attr("data-original") + val name = it.selectFirst("div.video-name")?.text() ?: it.text() + Log.i(DEV, "Result => $link") + newMovieSearchResponse( + name = name, + url = link, + type = globaltvType, + ) { + //this.apiName = apiName + this.posterUrl = img + } + }.distinctBy { it.url } + + if (elements.isNotEmpty()) { + all.add( + HomePageList( + title, elements + ) + ) + } + return HomePageResponse(all) + } + + override suspend fun search(query: String): List { + return getPage("$mainUrl/search/${query}/", mainUrl).document + .select("#container .box .video-list") + .mapNotNull { + val link = fixUrlNull(it.select("a").attr("href")) ?: return@mapNotNull null + val imgArticle = it.select(".video-image").attr("src") + val name = it.selectFirst(".video-name")?.text() ?: "" + val year = null + + newMovieSearchResponse( + name = name, + url = link, + type = globaltvType, + ) { + //this.apiName = apiName + this.posterUrl = imgArticle + this.year = year + this.posterHeaders = interceptor.getCookieHeaders(url).toMap() + } + }.distinctBy { it.url } + } + + override suspend fun load(url: String): LoadResponse { + val apiName = this.name + val doc = getPage(url, url).document + + val container = doc.selectFirst("div#container") + val title = container?.selectFirst("h2")?.text() ?: "No Title" + val descript = container?.selectFirst("div.video-description")?.text() + val year = null + val poster = null //No image on load page + return newMovieLoadResponse( + name = title, + url = url, + dataUrl = url, + type = globaltvType, + ) { + this.apiName = apiName + this.posterUrl = poster + this.year = year + this.plot = descript + this.posterHeaders = interceptor.getCookieHeaders(url).toMap() + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val pathSplits = data.split("/") + val id = pathSplits[pathSplits.size - 2] + Log.i(DEV, "Data -> ${data} id -> ${id}") + val res = app.post( + "${mainUrl}/ajax.php", + headers = interceptor.getCookieHeaders(data).toMap(), + data = mapOf( + Pair("vlxx_server", "1"), + Pair("id", id), + Pair("server", "1"), + ), + referer = mainUrl + ).text + Log.i(DEV, "res ${res}") + + val json = getParamFromJS(res, "var opts = {\\r\\n\\t\\t\\t\\t\\t\\tsources:", "}]") + Log.i(DEV, "json ${json}") + json?.let { + tryParseJson>(it)?.forEach { vidlink -> + vidlink?.file?.let { file -> + callback.invoke( + ExtractorLink( + source = file, + name = this.name, + url = file, + referer = data, + quality = getQualityFromName(vidlink.label), + isM3u8 = file.endsWith("m3u8") + ) + ) + } + } + } + return true + + } + + private fun getParamFromJS(str: String, key: String, keyEnd: String): String? { + try { + val firstIndex = str.indexOf(key) + key.length // 4 to index point to first char. + val temp = str.substring(firstIndex) + val lastIndex = temp.indexOf(keyEnd) + (keyEnd.length) + val jsonConfig = temp.substring(0, lastIndex) // + Log.i(DEV, "jsonConfig ${jsonConfig}") + + val re = jsonConfig.replace("\\r", "") + .replace("\\t", "") + .replace("\\\"", "\"") + .replace("\\\\\\/", "/") + .replace("\\n", "") + + return re + } catch (e: Exception) { + //e.printStackTrace() + logError(e) + } + return null + } + + data class Sources( + @JsonProperty("file") val file: String?, + @JsonProperty("type") val type: String?, + @JsonProperty("label") val label: String? + ) +} \ No newline at end of file diff --git a/Vlxx/src/main/kotlin/com/jacekun/VlxxPlugin.kt b/Vlxx/src/main/kotlin/com/jacekun/VlxxPlugin.kt new file mode 100644 index 0000000..855eeec --- /dev/null +++ b/Vlxx/src/main/kotlin/com/jacekun/VlxxPlugin.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 VlxxPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Vlxx()) + } +} \ No newline at end of file