From ecdacc78c09c6eab08b33725625ac46ab4cabb1b Mon Sep 17 00:00:00 2001 From: Jace <54625750+Jacekun@users.noreply.github.com> Date: Fri, 19 Aug 2022 09:51:03 +0800 Subject: [PATCH] [WIP] JavSub provider --- JavSubProvider/build.gradle.kts | 24 ++ JavSubProvider/src/main/AndroidManifest.xml | 2 + .../main/kotlin/com/jacekun/JavSubProvider.kt | 241 ++++++++++++++++++ .../com/jacekun/JavSubProviderPlugin.kt | 13 + build.gradle.kts | 1 + 5 files changed, 281 insertions(+) create mode 100644 JavSubProvider/build.gradle.kts create mode 100644 JavSubProvider/src/main/AndroidManifest.xml create mode 100644 JavSubProvider/src/main/kotlin/com/jacekun/JavSubProvider.kt create mode 100644 JavSubProvider/src/main/kotlin/com/jacekun/JavSubProviderPlugin.kt diff --git a/JavSubProvider/build.gradle.kts b/JavSubProvider/build.gradle.kts new file mode 100644 index 0000000..d815975 --- /dev/null +++ b/JavSubProvider/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/JavSubProvider/src/main/AndroidManifest.xml b/JavSubProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1863f02 --- /dev/null +++ b/JavSubProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/JavSubProvider/src/main/kotlin/com/jacekun/JavSubProvider.kt b/JavSubProvider/src/main/kotlin/com/jacekun/JavSubProvider.kt new file mode 100644 index 0000000..40d536e --- /dev/null +++ b/JavSubProvider/src/main/kotlin/com/jacekun/JavSubProvider.kt @@ -0,0 +1,241 @@ +package com.jacekun + +import android.util.Log +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.extractors.FEmbed +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.Jsoup + +class JavSubProvider : MainAPI() { + override var name = "JavSub" + override var mainUrl = "https://javsub.co" + override val supportedTypes: Set get() = setOf(TvType.NSFW) + override val hasDownloadSupport: Boolean get() = true + override val hasMainPage: Boolean get() = true + override val hasQuickSearch: Boolean get() = false + + private val prefixTag = "dummyTag" //For use on stream links to differentiate links + private val tvType = TvType.NSFW + + data class ResponseMovieDetails( + @JsonProperty("name") val name: String?, + @JsonProperty("description") val description: String?, + @JsonProperty("thumbnailUrl") val thumbnailUrl: String?, + @JsonProperty("uploadDate") val uploadDate: String?, + @JsonProperty("contentUrl") val contentUrl: String? + ) + + override val mainPage = mainPageOf( + "$mainUrl/page/" to "Main Page", + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val categoryData = request.data + val categoryName = request.name + val pagedlink = if (page > 0) categoryData + page else categoryData + val document = app.get(pagedlink).document + val homepage = document.select("main#main-content").map { it2 -> + val inner = it2?.select("article > div.post-item-wrap") ?: return@map null + //Log.i(this.name, "inner => $inner") + val elements: List = inner.mapNotNull { + //Log.i(this.name, "Inner content => $innerArticle") + val innerA = it.selectFirst("div.blog-pic-wrap > a")?: return@mapNotNull null + val link = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null + + val imgArticle = innerA.selectFirst("img") + val name = innerA.attr("title") ?: imgArticle?.attr("alt") ?: "" + val image = imgArticle?.attr("data-src") + val year = null + //Log.i(this.name, "image => $image") + + MovieSearchResponse( + name = name, + url = link, + apiName = this.name, + type = tvType, + posterUrl = image, + year = year + ) + }.distinctBy { a -> a.url } + + HomePageList( + name = categoryName, + list = elements, + isHorizontalImages = true + ) + }.filterNotNull().filter { a -> a.list.isNotEmpty() } + //TODO: Replace 'homepage.first()' with 'homepage' after adding overload on newHomePageResponse() + if (homepage.isNotEmpty()) { + return newHomePageResponse( + list = homepage.first(), + hasNext = true + ) + } + throw ErrorLoadingException("No homepage data found!") + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/?s=${query}" + val document = app.get(url).document.getElementsByTag("body") + .select("main#main-content")?.select("article") + + return document?.mapNotNull { + if (it == null) { return@mapNotNull null } + val innerA = it.selectFirst("div.blog-pic-wrap > a")?: return@mapNotNull null + val link = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null + + val imgArticle = innerA.selectFirst("img") + val title = innerA.attr("title") ?: imgArticle?.attr("alt") ?: "" + val image = imgArticle?.attr("data-src") + val year = null + + MovieSearchResponse( + name = title, + url = link, + apiName = this.name, + type = tvType, + posterUrl = image, + year = year + ) + }?.distinctBy { b -> b.url } ?: listOf() + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + val body = document.getElementsByTag("body") + + // Default values + var title = "" + var poster : String? = null + var year : Int? = null + var descript : String? = null + + // Video details + var scriptJson = "" + run breaking@{ + body.select("script").forEach { + val scrAttr = it?.attr("type") ?: return@forEach + if (scrAttr.equals("application/ld+json", ignoreCase = true)) { + scriptJson = it.html() ?: "" + return@breaking + } + } + } + //Log.i(this.name, "Result => (scriptJson) $scriptJson") + + // Video stream + val playerIframes: MutableList = try { + //Note: Fetch all multi-link urls + document.selectFirst("div.series-listing")?.select("a")?.mapNotNull { + it?.attr("href") ?: return@mapNotNull null + }?.toMutableList() ?: mutableListOf() + } catch (e: Exception) { + Log.i(this.name, "Result => Exception (load) $e") + mutableListOf() + } + + // JAV Info + tryParseJson(scriptJson)?.let { + val contentUrl = it.contentUrl + title = it.name ?: "" + poster = it.thumbnailUrl + year = it.uploadDate?.take(4)?.toIntOrNull() + descript = "Title: $title ${System.lineSeparator()} ${it.description}" + + // Add additional links, Raw link without needing to fetch from JavSub API + //if (!contentUrl.isNullOrBlank()) { + //playerIframes.add("$prefixTag$contentUrl") + //} + //Log.i(this.name, "Result => (contentUrl) $contentUrl") + } + + Log.i(this.name, "Result => (playerIframes) ${playerIframes.toJson()}") + + return MovieLoadResponse( + name = title, + url = url, + apiName = this.name, + type = tvType, + dataUrl = playerIframes.toJson(), + posterUrl = poster, + year = year, + plot = descript + ) + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + var count = 0 + tryParseJson>(data)?.apmap { link -> + Log.i(this.name, "Result => (link) $link") + if (link.startsWith(prefixTag)) { + val linkUrl = link.removePrefix(prefixTag) + val success = extractStreamLink(linkUrl, subtitleCallback, callback) + if (success) { + count++ + } + } + else { + val innerDoc = + app.get(link).document.selectFirst("script#beeteam368_obj_wes-js-extra") + var innerText = innerDoc?.html() ?: "" + if (innerText.isNotBlank()) { + "(?<=single_video_url\":)(.*)(?=,)".toRegex().find(innerText) + ?.groupValues?.get(0)?.let { iframe -> + innerText = iframe.trim().trim('"') + } + Jsoup.parse(innerText)?.selectFirst("iframe")?.attr("src")?.let { server -> + val serverLink = server.replace("\\", "").replace("\"", "") + val success = extractStreamLink(serverLink, subtitleCallback, callback) + if (success) { + count++ + } + Log.i(this.name, "Result => (streamLink add) $serverLink") + } + } + } + } + //Log.i(this.name, "Result => count: $count") + return count > 0 + } + + private suspend fun extractStreamLink( + link: String, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit) + : Boolean { + if (link.isNotBlank()) { + when { + link.contains("watch-jav") -> { + val extractor = FEmbed() + extractor.domainUrl = "embedsito.com" + extractor.getSafeUrl( + url = link, + referer = mainUrl, + subtitleCallback = subtitleCallback, + callback = callback + ) + return true + } + else -> { + return loadExtractor( + url = link, + referer = mainUrl, + subtitleCallback = subtitleCallback, + callback = callback + ) + } + } + } + return false + } +} \ No newline at end of file diff --git a/JavSubProvider/src/main/kotlin/com/jacekun/JavSubProviderPlugin.kt b/JavSubProvider/src/main/kotlin/com/jacekun/JavSubProviderPlugin.kt new file mode 100644 index 0000000..8e82d6d --- /dev/null +++ b/JavSubProvider/src/main/kotlin/com/jacekun/JavSubProviderPlugin.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 TestPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(JavSubProvider()) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 26f4b82..7a039fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -77,6 +77,7 @@ subprojects { implementation(kotlin("stdlib")) // adds standard kotlin features, like listOf, mapOf etc implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library implementation("org.jsoup:jsoup:1.13.1") // html parser + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") } }