forked from recloudstream/cloudstream
Provider: AllAnime
This commit is contained in:
parent
d790f113c0
commit
225744984a
2 changed files with 301 additions and 0 deletions
|
@ -28,6 +28,7 @@ object APIHolder {
|
||||||
|
|
||||||
val apis = arrayListOf(
|
val apis = arrayListOf(
|
||||||
GogoanimeProvider(),
|
GogoanimeProvider(),
|
||||||
|
AllAnimeProvider(),
|
||||||
//ShiroProvider(), // v2 fucked me
|
//ShiroProvider(), // v2 fucked me
|
||||||
//AnimePaheProvider(), //ddos guard
|
//AnimePaheProvider(), //ddos guard
|
||||||
AnimeFlickProvider(),
|
AnimeFlickProvider(),
|
||||||
|
|
|
@ -0,0 +1,300 @@
|
||||||
|
package com.lagradost.cloudstream3.animeproviders
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.network.get
|
||||||
|
import com.lagradost.cloudstream3.network.text
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
import org.mozilla.javascript.Context
|
||||||
|
import org.mozilla.javascript.Scriptable
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
|
||||||
|
|
||||||
|
class AllAnimeProvider : MainAPI() {
|
||||||
|
override val mainUrl: String
|
||||||
|
get() = "https://allanime.site"
|
||||||
|
override val name: String
|
||||||
|
get() = "AllAnime"
|
||||||
|
override val hasQuickSearch: Boolean
|
||||||
|
get() = false
|
||||||
|
override val hasMainPage: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
private fun getStatus(t: String): ShowStatus {
|
||||||
|
return when (t) {
|
||||||
|
"Finished" -> ShowStatus.Completed
|
||||||
|
"Releasing" -> ShowStatus.Ongoing
|
||||||
|
else -> ShowStatus.Completed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val supportedTypes: Set<TvType>
|
||||||
|
get() = setOf(TvType.Anime, TvType.AnimeMovie)
|
||||||
|
|
||||||
|
private data class Data (
|
||||||
|
@JsonProperty("shows") val shows: Shows
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class Shows (
|
||||||
|
@JsonProperty("pageInfo") val pageInfo: PageInfo,
|
||||||
|
@JsonProperty("edges") val edges: List<Edges>,
|
||||||
|
@JsonProperty("__typename") val _typename: String
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class Edges (
|
||||||
|
@JsonProperty("_id") val Id: String?,
|
||||||
|
@JsonProperty("name") val name: String,
|
||||||
|
@JsonProperty("englishName") val englishName: String?,
|
||||||
|
@JsonProperty("nativeName") val nativeName: String?,
|
||||||
|
@JsonProperty("thumbnail") val thumbnail: String?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("season") val season: Season?,
|
||||||
|
@JsonProperty("score") val score: Double?,
|
||||||
|
@JsonProperty("airedStart") val airedStart: AiredStart?,
|
||||||
|
@JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes?,
|
||||||
|
@JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AvailableEpisodesDetail?,
|
||||||
|
@JsonProperty("studios") val studios: List<String>?,
|
||||||
|
@JsonProperty("description") val description: String?,
|
||||||
|
@JsonProperty("status") val status: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class AvailableEpisodes (
|
||||||
|
@JsonProperty("sub") val sub: Int,
|
||||||
|
@JsonProperty("dub") val dub: Int,
|
||||||
|
@JsonProperty("raw") val raw: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class AiredStart (
|
||||||
|
@JsonProperty("year") val year: Int,
|
||||||
|
@JsonProperty("month") val month: Int,
|
||||||
|
@JsonProperty("date") val date: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class Season (
|
||||||
|
@JsonProperty("quarter") val quarter: String,
|
||||||
|
@JsonProperty("year") val year: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class PageInfo (
|
||||||
|
@JsonProperty("total") val total: Int,
|
||||||
|
@JsonProperty("__typename") val _typename: String
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class AllAnimeQuery (
|
||||||
|
@JsonProperty("data") val data: Data
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun search(query: String): ArrayList<SearchResponse> {
|
||||||
|
val link = """$mainUrl/graphql?variables=%7B%22search%22%3A%7B%22allowAdult%22%3Afalse%2C%22query%22%3A%22$query%22%7D%2C%22limit%22%3A26%2C%22page%22%3A1%2C%22translationType%22%3A%22sub%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%229343797cc3d9e3f444e2d3b7db9a84d759b816a4d84512ea72d079f85bb96e98%22%7D%7D"""
|
||||||
|
var res = get(link).text
|
||||||
|
if (res.contains("PERSISTED_QUERY_NOT_FOUND")) {
|
||||||
|
res = get(link).text
|
||||||
|
if (res.contains("PERSISTED_QUERY_NOT_FOUND")) return ArrayList()
|
||||||
|
}
|
||||||
|
val response = mapper.readValue<AllAnimeQuery>(res)
|
||||||
|
|
||||||
|
val results = response.data.shows.edges.filter {
|
||||||
|
// filtering in case there is an anime with 0 episodes available on the site.
|
||||||
|
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ArrayList(results.map {
|
||||||
|
AnimeSearchResponse(
|
||||||
|
it.name,
|
||||||
|
"$mainUrl/anime/${it.Id}",
|
||||||
|
this.name,
|
||||||
|
TvType.Anime,
|
||||||
|
it.thumbnail,
|
||||||
|
it.airedStart?.year,
|
||||||
|
EnumSet.of(DubStatus.Subbed), //, DubStatus.Dubbed),
|
||||||
|
it.englishName,
|
||||||
|
null, //it.availableEpisodes?.dub,
|
||||||
|
it.availableEpisodes?.sub
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class AvailableEpisodesDetail (
|
||||||
|
@JsonProperty("sub") val sub: List<String>,
|
||||||
|
@JsonProperty("dub") val dub: List<String>,
|
||||||
|
@JsonProperty("raw") val raw: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
override fun load(url: String): LoadResponse? {
|
||||||
|
val rhino = Context.enter()
|
||||||
|
rhino.initStandardObjects()
|
||||||
|
rhino.optimizationLevel = -1
|
||||||
|
val scope: Scriptable = rhino.initStandardObjects()
|
||||||
|
|
||||||
|
|
||||||
|
val html = get(url).text
|
||||||
|
val soup = Jsoup.parse(html)
|
||||||
|
|
||||||
|
val script = soup.select("script").firstOrNull {
|
||||||
|
it.html().contains("window.__NUXT__")
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
val js = """
|
||||||
|
const window = {}
|
||||||
|
${script.html()}
|
||||||
|
const returnValue = JSON.stringify(window.__NUXT__.fetch[0].show)
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
rhino.evaluateString(scope, js, "JavaScript", 1, null)
|
||||||
|
val jsEval = scope.get("returnValue", scope) ?: return null
|
||||||
|
val showData = mapper.readValue<Edges>(jsEval as String)
|
||||||
|
|
||||||
|
val title = showData.name
|
||||||
|
val description = showData.description
|
||||||
|
val poster = showData.thumbnail
|
||||||
|
|
||||||
|
val episodes = showData.availableEpisodes.let {
|
||||||
|
if (it == null) return@let Pair(null, null)
|
||||||
|
Pair(if (it.sub != 0) ArrayList((1 .. it.sub).map { epNum ->
|
||||||
|
AnimeEpisode(
|
||||||
|
"$mainUrl/anime/${showData.Id}/episodes/sub/$epNum",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
epNum
|
||||||
|
)
|
||||||
|
}) else null, if (it.dub != 0) ArrayList((1 .. it.dub).map { epNum ->
|
||||||
|
AnimeEpisode(
|
||||||
|
"$mainUrl/anime/${showData.Id}/episodes/dub/$epNum",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
epNum
|
||||||
|
)
|
||||||
|
}) else null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimeLoadResponse(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
this.name,
|
||||||
|
TvType.Anime,
|
||||||
|
poster,
|
||||||
|
showData.airedStart?.year,
|
||||||
|
null, // no dub, because there is no way to switch from dub to sub //episodes.second,
|
||||||
|
episodes.first,
|
||||||
|
getStatus(showData.status.toString()),
|
||||||
|
description.replace(Regex("""\<(.*?)\>"""), "")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val embedBlackList = listOf(
|
||||||
|
"https://mp4upload.com/",
|
||||||
|
"https://streamsb.net/",
|
||||||
|
"https://dood.to/",
|
||||||
|
"https://videobin.co/",
|
||||||
|
"https://ok.ru",
|
||||||
|
"https://streamlare.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun embedIsBlacklisted(url: String): Boolean {
|
||||||
|
embedBlackList.forEach {
|
||||||
|
if (it.javaClass.name == "kotlin.text.Regex") {
|
||||||
|
if ((it as Regex).matches(url)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (url.contains(it as String)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.sanitize(): String {
|
||||||
|
var out = this
|
||||||
|
listOf(Pair("\\u002F", "/")).forEach {
|
||||||
|
out = out.replace(it.first, it.second)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Links (
|
||||||
|
@JsonProperty("link") val link: String,
|
||||||
|
@JsonProperty("hls") val hls: Boolean?,
|
||||||
|
@JsonProperty("resolutionStr") val resolutionStr: String,
|
||||||
|
@JsonProperty("src") val src: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class AllAnimeVideoApiResponse (
|
||||||
|
@JsonProperty("links") val links: List<Links>
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class ApiEndPoint(
|
||||||
|
@JsonProperty("episodeIframeHead") val episodeIframeHead: String
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
var apiEndPoint = mapper.readValue<ApiEndPoint>(get("$mainUrl/getVersion").text).episodeIframeHead
|
||||||
|
if (apiEndPoint.endsWith("/")) apiEndPoint = apiEndPoint.slice(0 until apiEndPoint.length - 1)
|
||||||
|
|
||||||
|
val html = get(data).text
|
||||||
|
|
||||||
|
val sources = Regex("""sourceUrl[:=]"(.+?)"""").findAll(html).toList().map { URLDecoder.decode(it.destructured.component1().sanitize(), "UTF-8") }
|
||||||
|
sources.forEach {
|
||||||
|
var link = it
|
||||||
|
if (URI(link).isAbsolute || link.startsWith("//")) {
|
||||||
|
if (link.startsWith("//")) link = "https:$it"
|
||||||
|
|
||||||
|
if (Regex("""streaming\.php\?""").matches(link)) {
|
||||||
|
// for now ignore
|
||||||
|
} else if (!embedIsBlacklisted(link)) {
|
||||||
|
callback(
|
||||||
|
ExtractorLink(
|
||||||
|
"AllAnime - " + URI(link).host,
|
||||||
|
"",
|
||||||
|
link,
|
||||||
|
data,
|
||||||
|
getQualityFromName("1080"),
|
||||||
|
URI(link).path.contains(".m3u")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
link = apiEndPoint + URI(link).path + ".json?" + URI(link).query
|
||||||
|
val response = get(link)
|
||||||
|
|
||||||
|
if (response.code < 400) {
|
||||||
|
val links = mapper.readValue<AllAnimeVideoApiResponse>(response.text).links
|
||||||
|
links.forEach { server ->
|
||||||
|
callback(ExtractorLink(
|
||||||
|
"AllAnime - " + URI(server.link).host,
|
||||||
|
server.resolutionStr,
|
||||||
|
server.link,
|
||||||
|
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(server.link).path),
|
||||||
|
getQualityFromName("1080"),
|
||||||
|
server.hls != null && server.hls
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue