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…
	
	Add table
		Add a link
		
	
		Reference in a new issue