mirror of
https://github.com/Jacekun/cs3xxx-repo.git
synced 2024-08-14 23:57:09 +00:00
[WIP] JavSub provider
This commit is contained in:
parent
86cfd92785
commit
ecdacc78c0
5 changed files with 281 additions and 0 deletions
24
JavSubProvider/build.gradle.kts
Normal file
24
JavSubProvider/build.gradle.kts
Normal file
|
@ -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")
|
||||||
|
}
|
2
JavSubProvider/src/main/AndroidManifest.xml
Normal file
2
JavSubProvider/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="com.example"/>
|
241
JavSubProvider/src/main/kotlin/com/jacekun/JavSubProvider.kt
Normal file
241
JavSubProvider/src/main/kotlin/com/jacekun/JavSubProvider.kt
Normal file
|
@ -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<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
|
||||||
|
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<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,
|
||||||
|
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<SearchResponse> {
|
||||||
|
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") ?: "<No Title>"
|
||||||
|
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<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 {
|
||||||
|
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<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('"')
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,6 +77,7 @@ subprojects {
|
||||||
implementation(kotlin("stdlib")) // adds standard kotlin features, like listOf, mapOf etc
|
implementation(kotlin("stdlib")) // adds standard kotlin features, like listOf, mapOf etc
|
||||||
implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library
|
implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library
|
||||||
implementation("org.jsoup:jsoup:1.13.1") // html parser
|
implementation("org.jsoup:jsoup:1.13.1") // html parser
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue