2022-08-19 01:51:03 +00:00
|
|
|
package com.jacekun
|
|
|
|
|
|
|
|
import android.util.Log
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty
|
|
|
|
import com.lagradost.cloudstream3.*
|
|
|
|
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<TvType> 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
|
2022-08-23 02:03:41 +00:00
|
|
|
private val globalTvType = TvType.Movie
|
2022-08-19 01:51:03 +00:00
|
|
|
|
|
|
|
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<SearchResponse> = 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") ?: "<No Title>"
|
|
|
|
val image = imgArticle?.attr("data-src")
|
|
|
|
val year = null
|
|
|
|
//Log.i(this.name, "image => $image")
|
|
|
|
|
|
|
|
MovieSearchResponse(
|
|
|
|
name = name,
|
|
|
|
url = link,
|
|
|
|
apiName = this.name,
|
2022-08-23 02:03:41 +00:00
|
|
|
type = globalTvType,
|
2022-08-19 01:51:03 +00:00
|
|
|
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<SearchResponse> {
|
|
|
|
val url = "$mainUrl/?s=${query}"
|
|
|
|
val document = app.get(url).document.getElementsByTag("body")
|
2022-08-21 08:57:29 +00:00
|
|
|
.select("main#main-content").select("article")
|
2022-08-19 01:51:03 +00:00
|
|
|
|
2022-08-21 08:57:29 +00:00
|
|
|
return document.mapNotNull {
|
2022-08-19 01:51:03 +00:00
|
|
|
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") ?: "<No Title>"
|
|
|
|
val image = imgArticle?.attr("data-src")
|
|
|
|
val year = null
|
|
|
|
|
|
|
|
MovieSearchResponse(
|
|
|
|
name = title,
|
|
|
|
url = link,
|
|
|
|
apiName = this.name,
|
2022-08-23 02:03:41 +00:00
|
|
|
type = globalTvType,
|
2022-08-19 01:51:03 +00:00
|
|
|
posterUrl = image,
|
|
|
|
year = year
|
|
|
|
)
|
2022-08-21 08:57:29 +00:00
|
|
|
}.distinctBy { b -> b.url }
|
2022-08-19 01:51:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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<String> = 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<ResponseMovieDetails>(scriptJson)?.let {
|
2022-08-21 08:57:29 +00:00
|
|
|
//val contentUrl = it.contentUrl
|
2022-08-19 01:51:03 +00:00
|
|
|
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,
|
2022-08-23 02:03:41 +00:00
|
|
|
type = globalTvType,
|
2022-08-19 01:51:03 +00:00
|
|
|
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<List<String>>(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('"')
|
|
|
|
}
|
2022-08-21 08:57:29 +00:00
|
|
|
Jsoup.parse(innerText).selectFirst("iframe")?.attr("src")?.let { server ->
|
2022-08-19 01:51:03 +00:00
|
|
|
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,
|
2022-08-20 23:17:35 +00:00
|
|
|
callback: (ExtractorLink) -> Unit) : Boolean {
|
|
|
|
|
2022-08-19 01:51:03 +00:00
|
|
|
if (link.isNotBlank()) {
|
|
|
|
when {
|
|
|
|
link.contains("watch-jav") -> {
|
2022-08-21 08:57:29 +00:00
|
|
|
val editedLink = link.removePrefix("https://")
|
|
|
|
val idx = editedLink.indexOf('/', 0) + 1
|
|
|
|
val finalLink = "https://embedsito.com/${editedLink.substring(idx)}"
|
|
|
|
//Log.i(this.name, "WatchJav link => $finalLink / $link")
|
|
|
|
return loadExtractor(
|
|
|
|
url = finalLink,
|
2022-08-19 01:51:03 +00:00
|
|
|
referer = mainUrl,
|
|
|
|
subtitleCallback = subtitleCallback,
|
|
|
|
callback = callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
return loadExtractor(
|
|
|
|
url = link,
|
|
|
|
referer = mainUrl,
|
|
|
|
subtitleCallback = subtitleCallback,
|
|
|
|
callback = callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|