diff --git a/OpJav/build.gradle.kts b/OpJav/build.gradle.kts new file mode 100644 index 0000000..4578924 --- /dev/null +++ b/OpJav/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 = "Old JAV" + authors = listOf("Jace") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 2 // 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=opjav.com&sz=%size%" +} diff --git a/OpJav/src/main/AndroidManifest.xml b/OpJav/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/OpJav/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/OpJav/src/main/kotlin/com/jacekun/OpJav.kt b/OpJav/src/main/kotlin/com/jacekun/OpJav.kt new file mode 100644 index 0000000..56b5c62 --- /dev/null +++ b/OpJav/src/main/kotlin/com/jacekun/OpJav.kt @@ -0,0 +1,245 @@ +package com.jacekun + +import android.util.Log +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.app +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.extractorApis +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.nodes.Element + +class OpJav : MainAPI() { + private val globalTvType = TvType.Movie + override var name = "OpJAV" + override var mainUrl = "https://opjav.com" + override val supportedTypes = setOf(TvType.NSFW) + override val hasDownloadSupport = false + override val hasMainPage = true + override val hasQuickSearch = false + + private val prefix = "Watch JAV" + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(mainUrl).document + val all = ArrayList() + val body = document.getElementsByTag("body") + val rows = mutableListOf>() + val selectorSimple = "div.list-film-simple > div.item" + val selectorRows = "div.list-film.row > div" + + body?.select("div.content")?.forEach { + if (it != null) { + if (it.select(selectorRows).isNullOrEmpty()) { + rows.add(Pair(selectorSimple, it)) + } else { + rows.add(Pair(selectorRows, it)) + } + } + } + + var count = 0 + rows.forEach { row -> + count++ + val title = "Row $count" + val isSimple = row.first == selectorSimple + val entries = row.second.select(row.first) + val elements = entries.mapNotNull { + if (it == null) { return@mapNotNull null } + val link: String + val name: String + val image : String? + var year : Int? = null + + if (isSimple) { + //Simple load + val inner = it.select("div.info") ?: return@mapNotNull null + link = fixUrlNull(inner.select("a").get(0)?.attr("href")) ?: return@mapNotNull null + name = inner.text().trim() + val imgsrc = it.select("img") + image = imgsrc.attr("src") ?: imgsrc.attr("data-src") + } else { + val inner = it.select("div.inner") ?: return@mapNotNull null + val poster = inner.select("a.poster") ?: return@mapNotNull null + link = fixUrlNull(poster.attr("href")) ?: return@mapNotNull null + name = it.text().trim().removePrefix("HD") + image = poster.select("img")?.attr("src") + year = inner.select("dfn")?.get(1)?.text()?.toIntOrNull() + } + MovieSearchResponse( + name = name, + url = link, + apiName = this.name, + type = globalTvType, + posterUrl = image, + year = year + ) + }.distinctBy { a -> a.url } + if (elements.isNotEmpty()) { + all.add( + HomePageList( + name = title, + list = elements + ) + ) + } + } + return HomePageResponse(all) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/search/${query}/" + val document = app.get(url).document + .select("div.block-body > div.list-film.row > div") + //.select("div.item.col-lg-3.col-md-3.col-sm-6.col-xs-6") + //Log.i(this.name, "Result => (document) ${document}") + return document.mapNotNull { + val inner = it.select("div.inner") ?: return@mapNotNull null + val innerPost = inner.select("a.poster") ?: return@mapNotNull null + + val link = fixUrlNull(innerPost.attr("href")) ?: return@mapNotNull null + val title = innerPost.attr("title").trim().removePrefix(prefix).trim() + val imgsrc = innerPost.select("img") + val image = fixUrlNull(imgsrc.attr("src") ?: imgsrc.attr("data-src")) + val year = inner.select("dfn").last()?.text()?.trim()?.toIntOrNull() + + //Log.i(this.name, "Result => $") + MovieSearchResponse( + name = title, + url = link, + apiName = this.name, + type = globalTvType, + posterUrl = image, + year = year + ) + }.distinctBy { it.url } + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url).document + //Log.i(this.name, "Result => (url) ${url}") + val poster = fixUrlNull(doc.select("meta[itemprop=image]").get(1)?.attr("content")?.trim()) + val title = doc.selectFirst("meta[property=og:title]")?.attr("content").toString().removePrefix(prefix).trim() + val descript = "Title: $title ${System.lineSeparator()}" + doc.selectFirst("meta[name=keywords]")?.attr("content")?.trim() + val year = doc.selectFirst("meta[itemprop=dateCreated]")?.attr("content")?.toIntOrNull() + + val tags = doc.select("dl > dd").get(1)?.select("a")?.mapNotNull { + //Log.i(this.name, "Result => (tag) $it") + it?.text()?.trim() ?: return@mapNotNull null + } + + //Fetch server links + val watchlink = ArrayList() + val mainLink = doc.select("div.buttons.row a").attr("href") ?: "" + //Log.i(this.name, "Result => (mainLink) $mainLink") + + //Fetch episode links from mainlink + if (mainLink.isNotBlank()) { + app.get(url = mainLink, referer = mainUrl).document.let { epsDoc -> + //Fetch filmId + /*var filmId = "" + val epLinkDoc = epsDoc.getElementsByTag("head").select("script").toString() + //Log.i(this.name, "Result => (epLinkDoc) $epLinkDoc") + try { + val epTextTemp = epLinkDoc.substring(epLinkDoc.indexOf("filmID = parseInt")) + val epText = epTextTemp.substring(1, epTextTemp.indexOf("")).trim() + .filterNot { a -> a.isWhitespace() } + if (epText.isNotEmpty()) { + filmId = try { + "(?<=filmID=parseInt\\(')(.*)(?='\\);)".toRegex().find(epText)?.groupValues?.get(0) ?: "" + } catch (e: Exception) { "" } + Log.i(this.name, "Result => (filmId) $filmId") + } + } catch (e: Exception) { }*/ + //Fetch server links + epsDoc.select("div.block.servers li").mapNotNull { + val inner = it?.selectFirst("a") ?: return@mapNotNull null + val linkUrl = inner.attr("href") ?: return@mapNotNull null + val linkId = inner.attr("id") ?: return@mapNotNull null + Pair(linkUrl, linkId) + }.apmap { + //First = Url, Second = EpisodeID + //Log.i(this.name, "Result => (eplink-Id) $it") + val ajaxHead = mapOf( + Pair("Origin", mainUrl), + Pair("Referer", it.first) + ) + //https://opjav.com/movie/War%20of%20the%20Roses-64395/watch-movie.html + //EpisodeID, 442671 + //filmID, 64395 + val ajaxData = mapOf( + Pair("NextEpisode", "1"), + Pair("EpisodeID", it.second) + //Pair("filmID", filmId) + ) + app.post("$mainUrl/ajax", headers = ajaxHead, data = ajaxData) + .document.select("iframe").forEach { iframe -> + val serverLink = iframe?.attr("src")?.trim() + if (!serverLink.isNullOrBlank()) { + watchlink.add(serverLink) + Log.i(this.name, "Result => (serverLink) $serverLink") + } + } + } + } + } + val streamUrl = watchlink.distinct().toJson() + Log.i(this.name, "Result => (streamUrl) $streamUrl") + return MovieLoadResponse( + name = title, + url = url, + apiName = this.name, + type = globalTvType, + dataUrl = streamUrl, + posterUrl = poster, + year = year, + plot = descript, + tags = tags + ) + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + var count = 0 + tryParseJson>(data)?.forEach { link -> + val url = fixUrl(link.trim()) + Log.i(this.name, "Result => (url) $url") + when { + url.contains("opmovie.xyz") -> { + val ext = extractorApis.find { it.mainUrl.contains("embedsito.com") }//XStreamCdn() + if (ext != null) { + //ext.domainUrl = "opmovie.xyz" + ext.getSafeUrl( + url = url, + referer = url, + subtitleCallback = subtitleCallback, + callback = callback + ) + count++ + } + } + else -> { + val success = loadExtractor( + url = url, + referer = mainUrl, + subtitleCallback = subtitleCallback, + callback = callback + ) + if (success) { + count++ + } + } + } + } + return count > 0 + } +} \ No newline at end of file diff --git a/OpJav/src/main/kotlin/com/jacekun/OpJavPlugin.kt b/OpJav/src/main/kotlin/com/jacekun/OpJavPlugin.kt new file mode 100644 index 0000000..37f6a55 --- /dev/null +++ b/OpJav/src/main/kotlin/com/jacekun/OpJavPlugin.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 OpJavPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(OpJav()) + } +} \ No newline at end of file