cloudstream-extensions/AnimeWorldProvider/src/main/kotlin/com/lagradost/AnimeWorldProvider.kt

266 lines
35 KiB
Kotlin
Raw Normal View History

2022-08-11 14:55:58 +00:00
package com.lagradost
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.addDuration
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
import com.lagradost.cloudstream3.LoadResponse.Companion.addRating
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.nicehttp.NiceResponse
import org.jsoup.nodes.Element
import org.mozilla.javascript.ConsString
import org.mozilla.javascript.Context
import org.mozilla.javascript.Scriptable
class AnimeWorldProvider : MainAPI() {
override var mainUrl = "https://www.animeworld.tv"
override var name = "AnimeWorld"
override var lang = "it"
override val hasMainPage = true
override val supportedTypes = setOf(
TvType.Anime,
TvType.AnimeMovie,
TvType.OVA
)
companion object {
private var cookies = emptyMap<String, String>()
// Disabled authentication as site did
private suspend fun request(url: String): NiceResponse {
// if (cookies.isEmpty()) {
// cookies = getCookies(url)
// }
return app.get(url
// , cookies = cookies
)
}
private suspend fun getCookies(url: String): Map<String, String> {
val rhino = Context.enter()
rhino.optimizationLevel = -1
val scope: Scriptable = rhino.initSafeStandardObjects()
val slowAes = app.get("https://www.animeworld.tv/aes.min.js").text
// val slowAes = """
// var _0x2465=["\x72\x6F\x74\x61\x74\x65","\x73\x62\x6F\x78","\x52\x63\x6F\x6E","\x6E\x75\x6D\x62\x65\x72\x4F\x66\x52\x6F\x75\x6E\x64\x73","\x63\x6F\x72\x65","\x53\x49\x5A\x45\x5F\x32\x35\x36","\x6B\x65\x79\x53\x69\x7A\x65","\x72\x73\x62\x6F\x78","\x73\x68\x69\x66\x74\x52\x6F\x77","\x6D\x69\x78\x43\x6F\x6C\x75\x6D\x6E","\x67\x61\x6C\x6F\x69\x73\x5F\x6D\x75\x6C\x74\x69\x70\x6C\x69\x63\x61\x74\x69\x6F\x6E","\x73\x75\x62\x42\x79\x74\x65\x73","\x73\x68\x69\x66\x74\x52\x6F\x77\x73","\x6D\x69\x78\x43\x6F\x6C\x75\x6D\x6E\x73","\x61\x64\x64\x52\x6F\x75\x6E\x64\x4B\x65\x79","\x63\x72\x65\x61\x74\x65\x52\x6F\x75\x6E\x64\x4B\x65\x79","\x72\x6F\x75\x6E\x64","\x69\x6E\x76\x52\x6F\x75\x6E\x64","\x53\x49\x5A\x45\x5F\x31\x32\x38","\x53\x49\x5A\x45\x5F\x31\x39\x32","\x65\x78\x70\x61\x6E\x64\x4B\x65\x79","\x6D\x61\x69\x6E","\x69\x6E\x76\x4D\x61\x69\x6E","\x73\x6C\x69\x63\x65","\x6C\x65\x6E\x67\x74\x68","\x69\x76\x20\x6C\x65\x6E\x67\x74\x68\x20\x6D\x75\x73\x74\x20\x62\x65\x20\x31\x32\x38\x20\x62\x69\x74\x73\x2E","\x43\x42\x43","\x6D\x6F\x64\x65\x4F\x66\x4F\x70\x65\x72\x61\x74\x69\x6F\x6E","\x70\x61\x64\x42\x79\x74\x65\x73\x49\x6E","\x63\x65\x69\x6C","\x67\x65\x74\x42\x6C\x6F\x63\x6B","\x43\x46\x42","\x65\x6E\x63\x72\x79\x70\x74","\x61\x65\x73","\x70\x75\x73\x68","\x4F\x46\x42","\x64\x65\x63\x72\x79\x70\x74","\x75\x6E\x70\x61\x64\x42\x79\x74\x65\x73\x4F\x75\x74","\x73\x70\x6C\x69\x63\x65"];var slowAES={aes:{keySize:{SIZE_128:16,SIZE_192:24,SIZE_256:32},sbox:[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16],rsbox:[0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d],rotate:function(_0x85f6x2){var _0x85f6x3=_0x85f
// """.trimIndent()
val decodeBase64 = "atob = function(s) {\n" +
" var e={},i,b=0,c,x,l=0,a,r='',w=String.fromCharCode,L=s.length;\n" +
" var A=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n" +
" for(i=0;i<64;i++){e[A.charAt(i)]=i;}\n" +
" for(x=0;x<L;x++){\n" +
" c=e[s.charAt(x)];b=(b<<6)+c;l+=6;\n" +
" while(l>=8){((a=(b>>>(l-=8))&0xff)||(x<(L-2)))&&(r+=w(a));}\n" +
" }\n" +
" return r;\n" +
"};"
val doc = "var document = {};"
val siteScriptRegex = Regex("""script>(.*)\+";\s*path""")
val html = app.get(url).text
val siteScript = siteScriptRegex.find(html)?.groupValues?.getOrNull(1)
rhino.evaluateString(
scope,
"$doc$decodeBase64$slowAes;$siteScript;",
"JavaScript",
1,
null
)
val jsEval = scope.get("document", scope) as? Scriptable
val cookies = jsEval?.get("cookie", jsEval) as? ConsString
return cookies?.split(";")?.associate {
val split = it.split("=")
(split.getOrNull(0)?.trim() ?: "") to (split.getOrNull(1)?.trim() ?: "")
}?.filter { it.key.isNotBlank() && it.value.isNotBlank() } ?: emptyMap()
}
fun getType(t: String?): TvType {
return when (t?.lowercase()) {
"movie" -> TvType.AnimeMovie
"ova" -> TvType.OVA
else -> TvType.Anime
}
}
fun getStatus(t: String?): ShowStatus? {
return when (t?.lowercase()) {
"finito" -> ShowStatus.Completed
"in corso" -> ShowStatus.Ongoing
else -> null
}
}
}
private fun Element.toSearchResult(showEpisode: Boolean = true): AnimeSearchResponse {
fun String.parseHref(): String {
val h = this.split('.').toMutableList()
h[1] = h[1].substringBeforeLast('/')
return h.joinToString(".")
}
val anchor = this.select("a.name").firstOrNull() ?: throw ErrorLoadingException("Error")
val title = anchor.text().removeSuffix(" (ITA)")
val otherTitle = anchor.attr("data-jtitle").removeSuffix(" (ITA)")
val url = fixUrl(anchor.attr("href").parseHref())
val poster = this.select("a.poster img").attr("src")
val statusElement = this.select("div.status") // .first()
val dub = statusElement.select(".dub").isNotEmpty()
val episode = if (showEpisode) statusElement.select(".ep").text().split(' ').last()
.toIntOrNull() else null
val type = when {
statusElement.select(".movie").isNotEmpty() -> TvType.AnimeMovie
statusElement.select(".ova").isNotEmpty() -> TvType.OVA
else -> TvType.Anime
}
return newAnimeSearchResponse(title, url, type) {
addDubStatus(dub, episode)
this.otherName = otherTitle
this.posterUrl = poster
}
}
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
val document = request(mainUrl).document
val list = ArrayList<HomePageList>()
val widget = document.select(".widget.hotnew")
widget.select(".tabs [data-name=\"sub\"], .tabs [data-name=\"dub\"]").forEach { tab ->
val tabId = tab.attr("data-name")
val tabName = tab.text().removeSuffix("-ITA")
val animeList = widget.select("[data-name=\"$tabId\"] .film-list .item").map {
it.toSearchResult()
}
list.add(HomePageList(tabName, animeList))
}
widget.select(".tabs [data-name=\"trending\"]").forEach { tab ->
val tabId = tab.attr("data-name")
val tabName = tab.text()
val animeList = widget.select("[data-name=\"$tabId\"] .film-list .item").map {
it.toSearchResult(showEpisode = false)
}.distinctBy { it.url }
list.add(HomePageList(tabName, animeList))
}
return HomePageResponse(list)
}
override suspend fun search(query: String): List<SearchResponse> {
val document = request("$mainUrl/search?keyword=$query").document
return document.select(".film-list > .item").map {
it.toSearchResult(showEpisode = false)
}
}
override suspend fun load(url: String): LoadResponse {
val document = request(url).document
val widget = document.select("div.widget.info")
val title = widget.select(".info .title").text().removeSuffix(" (ITA)")
val otherTitle = widget.select(".info .title").attr("data-jtitle").removeSuffix(" (ITA)")
val description =
widget.select(".desc .long").first()?.text() ?: widget.select(".desc").text()
val poster = document.select(".thumb img").attr("src")
val type: TvType = getType(widget.select("dd").first()?.text())
val genres = widget.select(".meta").select("a[href*=\"/genre/\"]").map { it.text() }
val rating = widget.select("#average-vote").text()
val trailerUrl = document.select(".trailer[data-url]").attr("data-url")
val malId = document.select("#mal-button").attr("href")
.split('/').last().toIntOrNull()
val anlId = document.select("#anilist-button").attr("href")
.split('/').last().toIntOrNull()
var dub = false
var year: Int? = null
var status: ShowStatus? = null
var duration: String? = null
for (meta in document.select(".meta dt, .meta dd")) {
val text = meta.text()
if (text.contains("Audio"))
dub = meta.nextElementSibling()?.text() == "Italiano"
else if (year == null && text.contains("Data"))
year = meta.nextElementSibling()?.text()?.split(' ')?.last()?.toIntOrNull()
else if (status == null && text.contains("Stato"))
status = getStatus(meta.nextElementSibling()?.text())
else if (status == null && text.contains("Durata"))
duration = meta.nextElementSibling()?.text()
}
val servers = document.select(".widget.servers")
val episodes = servers.select(".server[data-name=\"9\"] .episode").map {
val id = it.select("a").attr("data-id")
val number = it.select("a").attr("data-episode-num").toIntOrNull()
Episode(
"$mainUrl/api/episode/info?id=$id",
episode = number
)
}
val comingSoon = episodes.isEmpty()
val recommendations = document.select(".film-list.interesting .item").map {
it.toSearchResult(showEpisode = false)
}
return newAnimeLoadResponse(title, url, type) {
engName = title
japName = otherTitle
addPoster(poster)
this.year = year
addEpisodes(if (dub) DubStatus.Dubbed else DubStatus.Subbed, episodes)
showStatus = status
plot = description
tags = genres
addMalId(malId)
addAniListId(anlId)
addRating(rating)
addDuration(duration)
addTrailer(trailerUrl)
this.recommendations = recommendations
this.comingSoon = comingSoon
}
}
data class Json(
@JsonProperty("grabber") val grabber: String,
@JsonProperty("name") val name: String,
@JsonProperty("target") val target: String,
)
override suspend fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val url = tryParseJson<Json>(
request(data).text
)?.grabber
if (url.isNullOrEmpty())
return false
callback.invoke(
ExtractorLink(
name,
name,
url,
referer = mainUrl,
quality = Qualities.Unknown.value
)
)
return true
}
}