|
|
|
@ -2,10 +2,9 @@ package com.lagradost
|
|
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty
|
|
|
|
|
import com.lagradost.cloudstream3.*
|
|
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
|
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
|
|
|
|
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
|
|
|
|
import com.lagradost.cloudstream3.utils.Qualities
|
|
|
|
|
import org.jsoup.Jsoup
|
|
|
|
|
import org.mozilla.javascript.Context
|
|
|
|
|
import org.mozilla.javascript.Scriptable
|
|
|
|
|
import java.util.*
|
|
|
|
@ -13,7 +12,8 @@ import java.util.*
|
|
|
|
|
|
|
|
|
|
class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
override var name = "WatchCartoonOnline"
|
|
|
|
|
override var mainUrl = "https://www.wcostream.com"
|
|
|
|
|
override var mainUrl = "https://www.wcostream.net"
|
|
|
|
|
override val hasMainPage = true
|
|
|
|
|
|
|
|
|
|
override val supportedTypes = setOf(
|
|
|
|
|
TvType.Cartoon,
|
|
|
|
@ -22,34 +22,57 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
TvType.TvSeries
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
override suspend fun search(query: String): List<SearchResponse> {
|
|
|
|
|
val url = "https://www.wcostream.com/search"
|
|
|
|
|
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse? {
|
|
|
|
|
val doc = app.get(mainUrl).document
|
|
|
|
|
val rows = doc.select("div.recent-release").mapNotNull {
|
|
|
|
|
val rowName = it.text()
|
|
|
|
|
val parent = it.parent() ?: return@mapNotNull null
|
|
|
|
|
val list = parent.select("ul.items > li").map { item ->
|
|
|
|
|
val link = item.select("a").attr("href")
|
|
|
|
|
val name = item.text()
|
|
|
|
|
newTvSeriesSearchResponse(
|
|
|
|
|
name, link
|
|
|
|
|
) {
|
|
|
|
|
this.posterUrl = fixUrl(item.select("img").attr("src"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
HomePageList(rowName, list)
|
|
|
|
|
}
|
|
|
|
|
return HomePageResponse(rows, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var response =
|
|
|
|
|
|
|
|
|
|
override suspend fun search(query: String): List<SearchResponse> {
|
|
|
|
|
val url = "$mainUrl/search"
|
|
|
|
|
|
|
|
|
|
val seriesDocument =
|
|
|
|
|
app.post(
|
|
|
|
|
url,
|
|
|
|
|
headers = mapOf("Referer" to url),
|
|
|
|
|
data = mapOf("catara" to query, "konuara" to "series")
|
|
|
|
|
).text
|
|
|
|
|
var document = Jsoup.parse(response)
|
|
|
|
|
var items = document.select("div#blog > div.cerceve").toList()
|
|
|
|
|
).document
|
|
|
|
|
|
|
|
|
|
val returnValue = ArrayList<SearchResponse>()
|
|
|
|
|
|
|
|
|
|
for (item in items) {
|
|
|
|
|
val header = item.selectFirst("> div.iccerceve")
|
|
|
|
|
val titleHeader = header!!.selectFirst("> div.aramadabaslik > a")
|
|
|
|
|
val series = seriesDocument.select("div#blog > div.cerceve").toList().mapNotNull { item ->
|
|
|
|
|
val header = item.selectFirst("> div.iccerceve") ?: return@mapNotNull null
|
|
|
|
|
val titleHeader = header.selectFirst("> div.aramadabaslik > a")
|
|
|
|
|
val title = titleHeader!!.text()
|
|
|
|
|
val href = fixUrl(titleHeader.attr("href"))
|
|
|
|
|
val poster = fixUrl(header.selectFirst("> a > img")!!.attr("src"))
|
|
|
|
|
val genreText = item.selectFirst("div.cerceve-tur-ve-genre")!!.ownText()
|
|
|
|
|
if (genreText.contains("cartoon")) {
|
|
|
|
|
returnValue.add(TvSeriesSearchResponse(title, href, this.name, TvType.Cartoon, poster, null, null))
|
|
|
|
|
TvSeriesSearchResponse(
|
|
|
|
|
title,
|
|
|
|
|
href,
|
|
|
|
|
this.name,
|
|
|
|
|
TvType.Cartoon,
|
|
|
|
|
poster,
|
|
|
|
|
null,
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
val isDubbed = genreText.contains("dubbed")
|
|
|
|
|
val set: EnumSet<DubStatus> =
|
|
|
|
|
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
|
|
|
|
|
returnValue.add(
|
|
|
|
|
AnimeSearchResponse(
|
|
|
|
|
title,
|
|
|
|
|
href,
|
|
|
|
@ -59,30 +82,30 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
null,
|
|
|
|
|
set,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "episodes-search", is used for finding movies, anime episodes should be filtered out
|
|
|
|
|
response =
|
|
|
|
|
val episodesDocument =
|
|
|
|
|
app.post(
|
|
|
|
|
url,
|
|
|
|
|
headers = mapOf("Referer" to url),
|
|
|
|
|
data = mapOf("catara" to query, "konuara" to "episodes")
|
|
|
|
|
).text
|
|
|
|
|
document = Jsoup.parse(response)
|
|
|
|
|
items = document.select("#catlist-listview2 > ul > li")
|
|
|
|
|
.filter { it -> it?.text() != null && !it.text().toString().contains("Episode") }
|
|
|
|
|
).document
|
|
|
|
|
val items = episodesDocument.select("#catlist-listview2 > ul > li")
|
|
|
|
|
// Filter away episodes and blanks
|
|
|
|
|
.filterNot { element ->
|
|
|
|
|
element.text().contains("Episode") || element.text().isNullOrBlank()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (item in items) {
|
|
|
|
|
val titleHeader = item.selectFirst("a")
|
|
|
|
|
val title = titleHeader!!.text()
|
|
|
|
|
val episodes = items.mapNotNull { item ->
|
|
|
|
|
val titleHeader = item.selectFirst("a") ?: return@mapNotNull null
|
|
|
|
|
val title = titleHeader.text()
|
|
|
|
|
val href = fixUrl(titleHeader.attr("href"))
|
|
|
|
|
//val isDubbed = title.contains("dubbed")
|
|
|
|
|
//val set: EnumSet<DubStatus> =
|
|
|
|
|
// EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
|
|
|
|
|
returnValue.add(
|
|
|
|
|
TvSeriesSearchResponse(
|
|
|
|
|
MovieSearchResponse(
|
|
|
|
|
title,
|
|
|
|
|
href,
|
|
|
|
|
this.name,
|
|
|
|
@ -91,25 +114,24 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return returnValue
|
|
|
|
|
return series + episodes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override suspend fun load(url: String): LoadResponse {
|
|
|
|
|
val isMovie = !url.contains("/anime/")
|
|
|
|
|
val response = app.get(url).text
|
|
|
|
|
val document = Jsoup.parse(response)
|
|
|
|
|
val isSeries = url.contains("/anime/")
|
|
|
|
|
val document = app.get(url).document
|
|
|
|
|
|
|
|
|
|
return if (!isMovie) {
|
|
|
|
|
return if (isSeries) {
|
|
|
|
|
val title = document.selectFirst("td.vsbaslik > h2")!!.text()
|
|
|
|
|
val poster = fixUrlNull(document.selectFirst("div#cat-img-desc > div > img")?.attr("src"))
|
|
|
|
|
val poster =
|
|
|
|
|
fixUrlNull(document.selectFirst("div#cat-img-desc > div > img")?.attr("src"))
|
|
|
|
|
val plot = document.selectFirst("div.iltext")!!.text()
|
|
|
|
|
val genres = document.select("div#cat-genre > div.wcobtn > a").map { it.text() }
|
|
|
|
|
val episodes = document.select("div#catlist-listview > ul > li > a").reversed().map {
|
|
|
|
|
val text = it.text()
|
|
|
|
|
val match = Regex("Season ([0-9]*) Episode ([0-9]*).*? (.*)").find(text)
|
|
|
|
|
val match = Regex("""Season (\d*) Episode (\d*).*? (.*)""").find(text)
|
|
|
|
|
val href = it.attr("href")
|
|
|
|
|
if (match != null) {
|
|
|
|
|
val last = match.groupValues[3]
|
|
|
|
@ -120,7 +142,7 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
match.groupValues[2].toIntOrNull(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
val match2 = Regex("Episode ([0-9]*).*? (.*)").find(text)
|
|
|
|
|
val match2 = Regex("""Episode (\d*).*? (.*)""").find(text)
|
|
|
|
|
if (match2 != null) {
|
|
|
|
|
val last = match2.groupValues[2]
|
|
|
|
|
return@map Episode(
|
|
|
|
@ -149,36 +171,28 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
tags = genres
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
val title = document.selectFirst(".iltext .Apple-style-span")?.text().toString()
|
|
|
|
|
val b = document.select(".iltext b")
|
|
|
|
|
val description = if (b.isNotEmpty()) {
|
|
|
|
|
b.last()!!.html().split("<br>")[0]
|
|
|
|
|
} else null
|
|
|
|
|
val title = document.selectFirst("td.ilxbaslik8")?.text().toString()
|
|
|
|
|
|
|
|
|
|
TvSeriesLoadResponse(
|
|
|
|
|
newMovieLoadResponse(
|
|
|
|
|
title,
|
|
|
|
|
url,
|
|
|
|
|
this.name,
|
|
|
|
|
TvType.TvSeries,
|
|
|
|
|
listOf(Episode(url,title)),
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
description,
|
|
|
|
|
null,
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
TvType.AnimeMovie,
|
|
|
|
|
url
|
|
|
|
|
) {
|
|
|
|
|
plot = document.select("div.iltext").text()
|
|
|
|
|
.substringAfter("Episode Description:")
|
|
|
|
|
.substringBefore("Share:")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data class LinkResponse(
|
|
|
|
|
// @JsonProperty("cdn")
|
|
|
|
|
// val cdn: String,
|
|
|
|
|
@JsonProperty("enc")
|
|
|
|
|
val enc: String,
|
|
|
|
|
@JsonProperty("hd")
|
|
|
|
|
val hd: String,
|
|
|
|
|
@JsonProperty("server")
|
|
|
|
|
val server: String,
|
|
|
|
|
@JsonProperty("enc") val enc: String,
|
|
|
|
|
@JsonProperty("hd") val hd: String,
|
|
|
|
|
@JsonProperty("fhd") val fhd: String,
|
|
|
|
|
@JsonProperty("server") val server: String,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
override suspend fun loadLinks(
|
|
|
|
@ -187,18 +201,20 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
subtitleCallback: (SubtitleFile) -> Unit,
|
|
|
|
|
callback: (ExtractorLink) -> Unit
|
|
|
|
|
): Boolean {
|
|
|
|
|
val response = app.get(data).text
|
|
|
|
|
/*val embedUrl = fixUrl(
|
|
|
|
|
Regex("itemprop=\"embedURL\" content=\"(.*?)\"").find(response.text)?.groupValues?.get(1) ?: return false
|
|
|
|
|
)*/
|
|
|
|
|
val start = response.indexOf("itemprop=\"embedURL")
|
|
|
|
|
val foundJS = Regex("<script>(.*?)</script>").find(response, start)?.groupValues?.get(1)
|
|
|
|
|
?.replace("document.write", "var returnValue = ")
|
|
|
|
|
val document = app.get(data).document
|
|
|
|
|
val foundJS = document.select("div.iltext > script").html()
|
|
|
|
|
|
|
|
|
|
// Find the variable name, eg: var HAi = "";
|
|
|
|
|
val varRegex = Regex("""var (\S*)""")
|
|
|
|
|
|
|
|
|
|
val varName = varRegex.find(foundJS)?.groupValues?.get(1)
|
|
|
|
|
?: throw RuntimeException("Cannot find var name!")
|
|
|
|
|
|
|
|
|
|
val src = document.select("iframe#frameNewAnimeuploads0").attr("src") ?: mainWork {
|
|
|
|
|
// Rhino needs to be on main
|
|
|
|
|
val rhino = Context.enter()
|
|
|
|
|
rhino.initStandardObjects()
|
|
|
|
|
rhino.optimizationLevel = -1
|
|
|
|
|
val scope: Scriptable = rhino.initStandardObjects()
|
|
|
|
|
val scope: Scriptable = rhino.initSafeStandardObjects()
|
|
|
|
|
|
|
|
|
|
val decodeBase64 = "atob = function(s) {\n" +
|
|
|
|
|
" var e={},i,b=0,c,x,l=0,a,r='',w=String.fromCharCode,L=s.length;\n" +
|
|
|
|
@ -210,20 +226,29 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
" }\n" +
|
|
|
|
|
" return r;\n" +
|
|
|
|
|
"};"
|
|
|
|
|
val documentJs = """
|
|
|
|
|
document = { write: function () {} };
|
|
|
|
|
""".trimIndent()
|
|
|
|
|
rhino.evaluateString(scope, documentJs + decodeBase64 + foundJS, "JavaScript", 1, null)
|
|
|
|
|
|
|
|
|
|
rhino.evaluateString(scope, decodeBase64 + foundJS, "JavaScript", 1, null)
|
|
|
|
|
val jsEval = scope.get("returnValue", scope) ?: return false
|
|
|
|
|
val src = fixUrl(Regex("src=\"(.*?)\"").find(jsEval as String)?.groupValues?.get(1) ?: return false)
|
|
|
|
|
val jsEval = scope.get(varName, scope)?.toString()
|
|
|
|
|
?: throw RuntimeException("Cannot get the Rhino scope variable")
|
|
|
|
|
val url =
|
|
|
|
|
Regex("src=\"(.*?)\"").find(jsEval)?.groupValues?.get(1) ?: return@mainWork null
|
|
|
|
|
fixUrl(url)
|
|
|
|
|
} ?: return false
|
|
|
|
|
|
|
|
|
|
val embedResponse = app.get(
|
|
|
|
|
(src),
|
|
|
|
|
headers = mapOf("Referer" to data)
|
|
|
|
|
src,
|
|
|
|
|
headers = mapOf("Referer" to src)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val getVidLink = fixUrl(
|
|
|
|
|
Regex("get\\(\"(.*?)\"").find(embedResponse.text)?.groupValues?.get(1) ?: return false
|
|
|
|
|
Regex("getJSON\\(\"(.*?)\"").find(embedResponse.text)?.groupValues?.get(1)
|
|
|
|
|
?: return false
|
|
|
|
|
)
|
|
|
|
|
val linkResponse = app.get(
|
|
|
|
|
|
|
|
|
|
val link = app.get(
|
|
|
|
|
getVidLink, headers = mapOf(
|
|
|
|
|
"sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"",
|
|
|
|
|
"sec-ch-ua-mobile" to "?0",
|
|
|
|
@ -236,34 +261,24 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
|
|
|
|
"user-agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
|
|
|
|
"cookie" to "countrytabs=0"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
).parsed<LinkResponse>()
|
|
|
|
|
|
|
|
|
|
val link = parseJson<LinkResponse>(linkResponse.text)
|
|
|
|
|
|
|
|
|
|
val hdLink = "${link.server}/getvid?evid=${link.hd}"
|
|
|
|
|
val sdLink = "${link.server}/getvid?evid=${link.enc}"
|
|
|
|
|
|
|
|
|
|
if (link.hd.isNotBlank())
|
|
|
|
|
fun loadLink(server: String, link: String?, quality: Qualities) {
|
|
|
|
|
if (link.isNullOrBlank()) return
|
|
|
|
|
callback.invoke(
|
|
|
|
|
ExtractorLink(
|
|
|
|
|
this.name,
|
|
|
|
|
this.name + " HD",
|
|
|
|
|
hdLink,
|
|
|
|
|
"",
|
|
|
|
|
Qualities.P720.value
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (link.enc.isNotBlank())
|
|
|
|
|
callback.invoke(
|
|
|
|
|
ExtractorLink(
|
|
|
|
|
this.name,
|
|
|
|
|
this.name + " SD",
|
|
|
|
|
sdLink,
|
|
|
|
|
"${server}/getvid?evid=${link}",
|
|
|
|
|
"",
|
|
|
|
|
Qualities.P480.value
|
|
|
|
|
quality.value
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadLink(link.server, link.hd, Qualities.P720)
|
|
|
|
|
loadLink(link.server, link.enc, Qualities.P480)
|
|
|
|
|
loadLink(link.server, link.fhd, Qualities.P1080)
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|