diff --git a/Example/build.gradle.kts b/Example/build.gradle.kts
index 683c0be..aad9fd6 100644
--- a/Example/build.gradle.kts
+++ b/Example/build.gradle.kts
@@ -15,12 +15,12 @@ cloudstream {
* 2: Slow
* 3: Beta only
* */
- status = 1 // will be 3 if unspecified
+ status = 0 // 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 = ""
+ iconUrl = "https://www.google.com/s2/favicons?domain=example.com&sz=%size%"
}
diff --git a/Example/src/main/kotlin/com/jacekun/Example.kt b/Example/src/main/kotlin/com/jacekun/Example.kt
index 09f6a9d..1aad8d1 100644
--- a/Example/src/main/kotlin/com/jacekun/Example.kt
+++ b/Example/src/main/kotlin/com/jacekun/Example.kt
@@ -4,5 +4,6 @@ import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.TvType
class Example : MainAPI() {
- private val globalTvType = TvType.Movie
+ private val DEV = "DevDebug"
+ private val globaltvType = TvType.Movie
}
\ No newline at end of file
diff --git a/JavTube/build.gradle.kts b/JavTube/build.gradle.kts
new file mode 100644
index 0000000..e2109d4
--- /dev/null
+++ b/JavTube/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 = "Watch Jav Tube FULL HD"
+ 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")
+
+ iconUrl = "https://www.google.com/s2/favicons?domain=javtube.watch&sz=%size%"
+}
diff --git a/JavTube/src/main/AndroidManifest.xml b/JavTube/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..29aec9d
--- /dev/null
+++ b/JavTube/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/JavTube/src/main/kotlin/com/jacekun/JavTube.kt b/JavTube/src/main/kotlin/com/jacekun/JavTube.kt
new file mode 100644
index 0000000..acc42e7
--- /dev/null
+++ b/JavTube/src/main/kotlin/com/jacekun/JavTube.kt
@@ -0,0 +1,243 @@
+package com.jacekun
+
+import android.util.Log
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.app
+import com.lagradost.cloudstream3.utils.AppUtils
+import com.lagradost.cloudstream3.utils.AppUtils.toJson
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.getQualityFromName
+import com.lagradost.cloudstream3.utils.loadExtractor
+import org.jsoup.Jsoup
+
+class JavTube : MainAPI() {
+ private val DEV = "DevDebug"
+ private val globaltvType = TvType.Movie
+ override var name = "JavTube"
+ override var mainUrl = "https://javtube.watch"
+ override val supportedTypes = setOf(TvType.NSFW)
+ override val hasDownloadSupport = true
+ override val hasMainPage = true
+ override val hasQuickSearch = false
+
+ override suspend fun getMainPage(
+ page: Int,
+ request: MainPageRequest
+ ): HomePageResponse {
+ val document = app.get(mainUrl).document
+ val all = ArrayList()
+
+ // Fetch row title
+ val title = "Latest videos"
+ // Fetch list of items and map
+ val inner = document.selectFirst("div.videos-list")?.select("article") ?: return HomePageResponse(all)
+ //Log.i(DEV, "Inner => $inner")
+ val elements: List = inner.mapNotNull {
+
+ //Log.i(DEV, "Inner content => $innerArticle")
+ val aa = it.select("a").last() ?: return@mapNotNull null
+ val link = fixUrlNull(aa.attr("href")) ?: return@mapNotNull null
+
+ val imgArticle = aa.select("img")
+ val name = imgArticle.attr("alt") ?: ""
+ var image = imgArticle.attr("data-src")
+ if (image.isNullOrEmpty()) {
+ image = imgArticle.attr("src")
+ }
+
+ MovieSearchResponse(
+ name = name,
+ url = link,
+ apiName = this.name,
+ type = globaltvType,
+ posterUrl = image,
+ year = null,
+ id = null,
+ )
+ }.distinctBy { a -> a.url }
+
+ all.add(
+ HomePageList(
+ title, elements
+ )
+ )
+
+ return HomePageResponse(all.filter { a -> a.list.isNotEmpty() })
+ }
+
+ override suspend fun search(query: String): List {
+ val url = "$mainUrl/search/$query"
+ val document = app.get(url).document.select("article#post")
+
+ return document.mapNotNull {
+ val innerA = it?.selectFirst("a") ?: return@mapNotNull null
+ val linkUrl = fixUrlNull(innerA.attr("href")) ?: return@mapNotNull null
+ if (linkUrl.startsWith("https://javtube.watch/tag/")) {
+ //Log.i(DEV, "Result => (innerA) $innerA")
+ return@mapNotNull null
+ }
+
+ val title = innerA.select("header.entry-header").text()
+ val imgLink = innerA.select("img")
+ var image = imgLink.attr("data-src")
+ if (image.isNullOrEmpty()) {
+ image = imgLink.attr("src")
+ }
+ val year = null
+
+ MovieSearchResponse(
+ name = title,
+ url = linkUrl,
+ apiName = this.name,
+ type = globaltvType,
+ posterUrl = image,
+ year = year
+ )
+ }
+ }
+
+ override suspend fun load(url: String): LoadResponse {
+ val document = app.get(url).document
+ //Log.i(DEV, "Result => ${body}")
+
+ // Video details
+ val content = document.selectFirst("article#post")?.select("div.video-player")
+ //Log.i(DEV, "Result => (content) $content")
+ val title = content?.select("meta[itemprop=\"name\"]")?.attr("content") ?: ""
+ val descript =content?.select("meta[itemprop=\"description\"]")?.attr("content")
+ //Log.i(DEV, "Result => (descript) $descript")
+ val year = null
+
+ // Poster Image
+ val poster = content?.select("meta[itemprop=\"thumbnailUrl\"]")?.attr("content")
+ //Log.i(DEV, "Result => (poster) $poster")
+
+ //TODO: Fetch links
+ //Video stream
+ val streamUrl: String = try {
+ val strPost = "post(\"https://javtube.watch/hash-javtubewatch\""
+ val scripts = document.select("script").toString()
+ val idxA = scripts.indexOf(strPost)
+ val firstParse = scripts.substring(idxA)
+ val idxB = firstParse.indexOf("function")
+
+ val secondParse = firstParse.substring(strPost.length, idxB).trim().trim(',')
+ .replace("num:", "\"num\":")
+ .replace(":'", ":\"")
+ .replace("'}", "\"}")
+ .trim().trim(',')
+ .trimEnd('}')
+ "$secondParse,\"url\":\"${url}\"}"
+ } catch (e: Exception) {
+ Log.i(DEV, "Result => Exception (load) $e")
+ ""
+ }
+ Log.i(DEV, "streamUrl => $streamUrl")
+ return MovieLoadResponse(
+ name = title,
+ url = url,
+ apiName = this.name,
+ type = globaltvType,
+ dataUrl = streamUrl,
+ posterUrl = poster,
+ year = year,
+ plot = descript,
+ )
+ }
+
+ //TODO: LoadLinks
+ override suspend fun loadLinks(
+ data: String,
+ isCasting: Boolean,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit
+ ): Boolean {
+ if (data.isEmpty()) return false
+ if (data == "about:blank") return false
+
+ AppUtils.tryParseJson(data)?.let { reqdata ->
+ Log.i(DEV, "Referer => ${reqdata.url}")
+ app.post(
+ url = "$mainUrl/hash-javtubewatch",
+ referer = reqdata.url,
+ data = mapOf(
+ Pair("migboob", reqdata.migboob),
+ Pair("mix", reqdata.mix),
+ Pair("num", reqdata.num),
+ ),
+ headers = mapOf(
+ Pair("Origin", mainUrl),
+ Pair("Sec-Fetch-Mode", "cors"),
+ Pair("User-Agent", USER_AGENT),
+ )
+ ).let { postreq ->
+ Log.i(DEV, "Post => (${postreq.code}) ${postreq.text}")
+
+ val doc = Jsoup.parse(postreq.text)
+ val src = doc.selectFirst("iframe")?.attr("src") ?: ""
+ Log.i(DEV, "Post Url => $src")
+
+ val id = src.trimEnd('/').split("/").last()
+ val newUrl = "https://fembed-hd.com/api/source/${id}"
+ Log.i(DEV, "newUrl => $newUrl")
+ loadExtractor(
+ url = newUrl,
+ referer = reqdata.url,
+ callback = callback,
+ subtitleCallback = subtitleCallback
+ )
+ //TODO: Fix headers, returning 403 Forbidden
+ /*val headers = mapOf(
+ Pair("Host", "javjav.top"),
+ Pair("Origin", src),
+ Pair("Referer", reqdata.url),
+ Pair("User-Agent", USER_AGENT),
+ )
+ Log.i(DEV, "headers => ${headers.toJson()}")
+ val postlink = app.post(
+ url = newUrl,
+ headers = mapOf(
+ Pair("Host", "javjav.top"),
+ Pair("Origin", src),
+ Pair("Referer", reqdata.url),
+ Pair("User-Agent", USER_AGENT),
+ )
+ )
+ Log.i(DEV, "Post Link => (${postlink.code}) ${postlink.text}")
+
+ val streamLinks = AppUtils.tryParseJson(postlink.text)?.data ?: listOf()
+ streamLinks.forEach{ stream ->
+ callback.invoke(
+ ExtractorLink(
+ source = "JavTube",
+ name = name,
+ url = stream.file,
+ referer = reqdata.url,
+ quality = getQualityFromName(stream.label)
+ )
+ )
+ }*/
+ }
+ }
+
+ return true
+ }
+
+ private data class JsonRequest(
+ @JsonProperty("migboob") val migboob: String,
+ @JsonProperty("mix") val mix: String,
+ @JsonProperty("num") val num: String,
+ @JsonProperty("url") val url: String
+ )
+
+ private data class JsonResponse(
+ @JsonProperty("success") val success: Boolean,
+ @JsonProperty("data") val data: List?
+ )
+ private data class JsonResponseData(
+ @JsonProperty("file") val file: String,
+ @JsonProperty("label") val label: String,
+ //val type: String // Mp4
+ )
+}
\ No newline at end of file
diff --git a/JavTube/src/main/kotlin/com/jacekun/JavTubePlugin.kt b/JavTube/src/main/kotlin/com/jacekun/JavTubePlugin.kt
new file mode 100644
index 0000000..9c8ba7f
--- /dev/null
+++ b/JavTube/src/main/kotlin/com/jacekun/JavTubePlugin.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 JavTubePlugin: Plugin() {
+ override fun load(context: Context) {
+ // All providers should be added in this manner. Please don't edit the providers list directly.
+ registerMainAPI(JavTube())
+ }
+}
\ No newline at end of file