9anime by Stormunblessed + minor code changes

This commit is contained in:
LagradOst 2022-02-27 00:56:20 +01:00
parent 73bcb4145e
commit 428e97ab1c
7 changed files with 331 additions and 43 deletions

View file

@ -84,6 +84,7 @@ object APIHolder {
KdramaHoodProvider(), KdramaHoodProvider(),
AkwamProvider(), AkwamProvider(),
AnimePaheProvider(), AnimePaheProvider(),
NineAnimeProvider(),
) )
val restrictedApis = arrayListOf( val restrictedApis = arrayListOf(

View file

@ -87,13 +87,13 @@ class AllAnimeProvider : MainAPI() {
@JsonProperty("data") val data: Data @JsonProperty("data") val data: Data
) )
override suspend fun search(query: String): ArrayList<SearchResponse> { override suspend fun search(query: String): List<SearchResponse> {
val link = 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""" """$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 = app.get(link).text var res = app.get(link).text
if (res.contains("PERSISTED_QUERY_NOT_FOUND")) { if (res.contains("PERSISTED_QUERY_NOT_FOUND")) {
res = app.get(link).text res = app.get(link).text
if (res.contains("PERSISTED_QUERY_NOT_FOUND")) return ArrayList() if (res.contains("PERSISTED_QUERY_NOT_FOUND")) return emptyList()
} }
val response = mapper.readValue<AllAnimeQuery>(res) val response = mapper.readValue<AllAnimeQuery>(res)
@ -102,7 +102,7 @@ class AllAnimeProvider : MainAPI() {
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0) !(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
} }
return ArrayList(results.map { return results.map {
AnimeSearchResponse( AnimeSearchResponse(
it.name, it.name,
"$mainUrl/anime/${it.Id}", "$mainUrl/anime/${it.Id}",
@ -115,7 +115,7 @@ class AllAnimeProvider : MainAPI() {
it.availableEpisodes?.dub, it.availableEpisodes?.dub,
it.availableEpisodes?.sub it.availableEpisodes?.sub
) )
}) }
} }
private data class AvailableEpisodesDetail( private data class AvailableEpisodesDetail(
@ -154,11 +154,11 @@ class AllAnimeProvider : MainAPI() {
val episodes = showData.availableEpisodes.let { val episodes = showData.availableEpisodes.let {
if (it == null) return@let Pair(null, null) if (it == null) return@let Pair(null, null)
Pair(if (it.sub != 0) ArrayList((1..it.sub).map { epNum -> Pair(if (it.sub != 0) ((1..it.sub).map { epNum ->
AnimeEpisode( AnimeEpisode(
"$mainUrl/anime/${showData.Id}/episodes/sub/$epNum", episode = epNum "$mainUrl/anime/${showData.Id}/episodes/sub/$epNum", episode = epNum
) )
}) else null, if (it.dub != 0) ArrayList((1..it.dub).map { epNum -> }) else null, if (it.dub != 0) ((1..it.dub).map { epNum ->
AnimeEpisode( AnimeEpisode(
"$mainUrl/anime/${showData.Id}/episodes/dub/$epNum", episode = epNum "$mainUrl/anime/${showData.Id}/episodes/dub/$epNum", episode = epNum
) )
@ -251,10 +251,9 @@ class AllAnimeProvider : MainAPI() {
private fun getM3u8Qualities( private fun getM3u8Qualities(
m3u8Link: String, m3u8Link: String,
referer: String, referer: String,
qualityName: String qualityName: String,
): ArrayList<ExtractorLink> { ): List<ExtractorLink> {
return ArrayList( return hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(m3u8Link, null), true).map { stream ->
hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(m3u8Link, null), true).map { stream ->
val qualityString = if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p" val qualityString = if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p"
ExtractorLink( ExtractorLink(
this.name, this.name,
@ -265,7 +264,7 @@ class AllAnimeProvider : MainAPI() {
true, true,
stream.headers stream.headers
) )
}) }
} }
override suspend fun loadLinks( override suspend fun loadLinks(

View file

@ -27,12 +27,12 @@ class AnimeFlickProvider : MainAPI() {
TvType.OVA TvType.OVA
) )
override suspend fun search(query: String): ArrayList<SearchResponse> { override suspend fun search(query: String): List<SearchResponse> {
val link = "https://animeflick.net/search.php?search=$query" val link = "https://animeflick.net/search.php?search=$query"
val html = app.get(link).text val html = app.get(link).text
val doc = Jsoup.parse(html) val doc = Jsoup.parse(html)
return ArrayList(doc.select(".row.mt-2").map { return doc.select(".row.mt-2").map {
val href = mainUrl + it.selectFirst("a").attr("href") val href = mainUrl + it.selectFirst("a").attr("href")
val title = it.selectFirst("h5 > a").text() val title = it.selectFirst("h5 > a").text()
val poster = mainUrl + it.selectFirst("img").attr("src").replace("70x110", "225x320") val poster = mainUrl + it.selectFirst("img").attr("src").replace("70x110", "225x320")
@ -45,7 +45,7 @@ class AnimeFlickProvider : MainAPI() {
null, null,
EnumSet.of(DubStatus.Subbed), EnumSet.of(DubStatus.Subbed),
) )
}) }
} }
override suspend fun load(url: String): LoadResponse { override suspend fun load(url: String): LoadResponse {

View file

@ -0,0 +1,274 @@
package com.lagradost.cloudstream3.animeproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor
import org.jsoup.Jsoup
import java.util.*
class NineAnimeProvider : MainAPI() {
override val mainUrl = "https://9anime.center"
override val name = "9Anime"
override val hasMainPage = true
override val hasChromecastSupport = true
override val hasDownloadSupport = true
override val supportedTypes = setOf(TvType.Anime)
override suspend fun getMainPage(): HomePageResponse {
val items = listOf(
Pair("$mainUrl/ajax/home/widget?name=trending", "Trending"),
Pair("$mainUrl/ajax/home/widget?name=updated_all", "All"),
Pair("$mainUrl/ajax/home/widget?name=updated_sub&page=1", "Recently Updated (SUB)"),
Pair("$mainUrl/ajax/home/widget?name=updated_dub&page=1", "Recently Updated (DUB)"),
Pair(
"$mainUrl/ajax/home/widget?name=updated_chinese&page=1",
"Recently Updated (Chinese)"
),
Pair("$mainUrl/ajax/home/widget?name=random", "Random"),
).map { (url, name) ->
val home = Jsoup.parse(
app.get(
url
).mapped<Response>().html
).select("ul.anime-list li").map {
val title = it.selectFirst("a.name").text()
val link = it.selectFirst("a").attr("href")
val poster = it.selectFirst("a.poster img").attr("src")
AnimeSearchResponse(
title,
link,
this.name,
TvType.Anime,
poster,
null,
if (title.contains("(DUB)") || title.contains("(Dub)")) EnumSet.of(
DubStatus.Dubbed
) else EnumSet.of(DubStatus.Subbed),
)
}
HomePageList(name, home)
}
return HomePageResponse(items)
}
//Credits to https://github.com/jmir1
private val key = "0wMrYU+ixjJ4QdzgfN2HlyIVAt3sBOZnCT9Lm7uFDovkb/EaKpRWhqXS5168ePcG"
private fun getVrf(id: String): String? {
val reversed = ue(encode(id) + "0000000").slice(0..5).reversed()
return reversed + ue(je(reversed, encode(id) ?: return null)).replace(
"""=+$""".toRegex(),
""
)
}
private fun getLink(url: String): String? {
val i = url.slice(0..5)
val n = url.slice(6..url.lastIndex)
return decode(je(i, ze(n)))
}
private fun ue(input: String): String {
if (input.any { it.code >= 256 }) throw Exception("illegal characters!")
var output = ""
for (i in input.indices step 3) {
val a = intArrayOf(-1, -1, -1, -1)
a[0] = input[i].code shr 2
a[1] = (3 and input[i].code) shl 4
if (input.length > i + 1) {
a[1] = a[1] or (input[i + 1].code shr 4)
a[2] = (15 and input[i + 1].code) shl 2
}
if (input.length > i + 2) {
a[2] = a[2] or (input[i + 2].code shr 6)
a[3] = 63 and input[i + 2].code
}
for (n in a) {
if (n == -1) output += "="
else {
if (n in 0..63) output += key[n]
}
}
}
return output;
}
private fun je(inputOne: String, inputTwo: String): String {
val arr = IntArray(256) { it }
var output = ""
var u = 0
var r: Int
for (a in arr.indices) {
u = (u + arr[a] + inputOne[a % inputOne.length].code) % 256
r = arr[a]
arr[a] = arr[u]
arr[u] = r
}
u = 0
var c = 0
for (f in inputTwo.indices) {
c = (c + f) % 256
u = (u + arr[c]) % 256
r = arr[c]
arr[c] = arr[u]
arr[u] = r
output += (inputTwo[f].code xor arr[(arr[c] + arr[u]) % 256]).toChar()
}
return output
}
private fun ze(input: String): String {
val t = if (input.replace("""[\t\n\f\r]""".toRegex(), "").length % 4 == 0) {
input.replace("""/==?$/""".toRegex(), "")
} else input
if (t.length % 4 == 1 || t.contains("""[^+/0-9A-Za-z]""".toRegex())) throw Exception("bad input")
var i: Int
var r = ""
var e = 0
var u = 0
for (o in t.indices) {
e = e shl 6
i = key.indexOf(t[o])
e = e or i
u += 6
if (24 == u) {
r += ((16711680 and e) shr 16).toChar()
r += ((65280 and e) shr 8).toChar()
r += (255 and e).toChar()
e = 0
u = 0
}
}
return if (12 == u) {
e = e shr 4
r + e.toChar()
} else {
if (18 == u) {
e = e shr 2
r += ((65280 and e) shr 8).toChar()
r += (255 and e).toChar()
}
r
}
}
private fun encode(input: String): String? = java.net.URLEncoder.encode(input, "utf-8")
private fun decode(input: String): String? = java.net.URLDecoder.decode(input, "utf-8")
override suspend fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/filter?sort=title%3Aasc&keyword=$query"
return app.get(url).document.select("ul.anime-list li").mapNotNull {
val title = it.selectFirst("a.name").text()
val href =
fixUrlNull(it.selectFirst("a").attr("href"))?.replace(Regex("(\\?ep=(\\d+)\$)"), "")
?: return@mapNotNull null
val image = it.selectFirst("a.poster img").attr("src")
AnimeSearchResponse(
title,
href,
this.name,
TvType.Anime,
image,
null,
if (title.contains("(DUB)") || title.contains("(Dub)")) EnumSet.of(
DubStatus.Dubbed
) else EnumSet.of(DubStatus.Subbed),
)
}
}
data class Response(
@JsonProperty("html") val html: String
)
override suspend fun load(url: String): LoadResponse? {
val urlclean = url.substringAfter("watch/")
val regexID = Regex("(\\.[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)")
val animeid = regexID.find(urlclean)?.value?.replace(".", "") ?: return null
val animeidencoded = encode(getVrf(animeid) ?: return null)
val doc = app.get(url).document
val poster = doc.selectFirst("aside.main div.thumb div img").attr("src")
val title = doc.selectFirst(".info .title").text()
val description = doc.selectFirst("div.info p").text().replace("Ver menos", "").trim()
val episodes = Jsoup.parse(
app.get(
"$mainUrl/ajax/anime/servers?ep=1&id=${animeid}&vrf=$animeidencoded&ep=8&episode=&token="
).mapped<Response>().html
)?.select("ul.episodes li a")?.mapNotNull {
val link = it?.attr("href") ?: return@mapNotNull null
val epnum = it.attr("data-base")?.toIntOrNull()
AnimeEpisode(link, episode = epnum)
} ?: return null
return newAnimeLoadResponse(title, url, TvType.Anime) {
posterUrl = poster
addEpisodes(DubStatus.Subbed, episodes)
plot = description
}
}
data class Links(
@JsonProperty("url") val url: String
)
data class Servers(
@JsonProperty("28") val mcloud: String?,
@JsonProperty("35") val mp4upload: String?,
@JsonProperty("40") val streamtape: String?,
@JsonProperty("41") val vidstream: String?,
@JsonProperty("43") val videovard: String?
)
override suspend fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val urlclean = data.substringAfter("watch/")
val regexID = Regex("(\\.[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)")
val animeid = regexID.find(urlclean)?.value?.replace(".", "") ?: return false
val animeidencoded = encode(getVrf(animeid) ?: return false)
Jsoup.parse(
app.get(
"$mainUrl/ajax/anime/servers?&id=${animeid}&vrf=$animeidencoded&episode=&token="
).mapped<Response>().html
).select("div.body").map { element ->
val jsonregex = Regex("(\\{.+\\}.*$data)")
val servers = jsonregex.find(element.toString())?.value?.replace(
Regex("(\".*data-base=.*href=\"$data)"),
""
)?.replace("&quot;", "\"") ?: return@map
val jsonservers = parseJson<Servers?>(servers) ?: return@map
listOfNotNull(
jsonservers.vidstream,
jsonservers.mcloud,
jsonservers.mp4upload,
jsonservers.streamtape
).mapNotNull {
val epserver = app.get("$mainUrl/ajax/anime/episode?id=$it").text
(if (epserver.contains("url")) {
parseJson<Links>(epserver)
} else null)?.url?.let { it1 -> getLink(it1.replace("=", "")) }
?.replace("/embed/", "/e/")
}.apmap { url ->
loadExtractor(
url, data, callback
)
}
}
return true
}
}

View file

@ -1,12 +1,14 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.app
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
import com.lagradost.cloudstream3.utils.getQualityFromName
open class Mcloud : ExtractorApi() { open class Mcloud : ExtractorApi() {
override val name = "Mcloud" override val name = "Mcloud"
@ -30,6 +32,11 @@ open class Mcloud : ExtractorApi() {
val link = url.replace("$mainUrl/e/","$mainUrl/info/") val link = url.replace("$mainUrl/e/","$mainUrl/info/")
val response = app.get(link, headers = headers).text val response = app.get(link, headers = headers).text
if(response.startsWith("<!DOCTYPE html>")) {
// TODO decrypt html for link
return emptyList()
}
data class Sources ( data class Sources (
@JsonProperty("file") val file: String @JsonProperty("file") val file: String
) )
@ -43,7 +50,7 @@ open class Mcloud : ExtractorApi() {
@JsonProperty("media") val media: Media, @JsonProperty("media") val media: Media,
) )
val mapped = response.let { parseJson<JsonMcloud>(it) } val mapped = parseJson<JsonMcloud>(response)
val sources = mutableListOf<ExtractorLink>() val sources = mutableListOf<ExtractorLink>()
if (mapped.success) if (mapped.success)

View file

@ -6,7 +6,11 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mapper import com.lagradost.cloudstream3.mapper
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
class WcoStream : ExtractorApi() { class Vidstreamz : WcoStream() {
override val mainUrl: String = "https://vidstreamz.online"
}
open class WcoStream : ExtractorApi() {
override val name = "VidStream" //Cause works for animekisa and wco override val name = "VidStream" //Cause works for animekisa and wco
override val mainUrl = "https://vidstream.pro" override val mainUrl = "https://vidstream.pro"
override val requiresReferer = false override val requiresReferer = false
@ -16,8 +20,8 @@ class WcoStream : ExtractorApi() {
val baseUrl = url.split("/e/")[0] val baseUrl = url.split("/e/")[0]
val html = app.get(url, headers = mapOf("Referer" to "https://wcostream.cc/")).text val html = app.get(url, headers = mapOf("Referer" to "https://wcostream.cc/")).text
val (Id) = "/e/(.*?)?domain".toRegex().find(url)!!.destructured val (Id) = ("/e/(.*?)?domain".toRegex().find(url)?.destructured ?: Regex("""/e/(.*)""").find(url)?.destructured) ?: return emptyList()
val (skey) = """skey\s=\s['"](.*?)['"];""".toRegex().find(html)!!.destructured val (skey) = """skey\s=\s['"](.*?)['"];""".toRegex().find(html)?.destructured ?: return emptyList()
val apiLink = "$baseUrl/info/$Id?domain=wcostream.cc&skey=$skey" val apiLink = "$baseUrl/info/$Id?domain=wcostream.cc&skey=$skey"
val referrer = "$baseUrl/e/$Id?domain=wcostream.cc" val referrer = "$baseUrl/e/$Id?domain=wcostream.cc"
@ -44,8 +48,10 @@ class WcoStream : ExtractorApi() {
if (mapped.success) { if (mapped.success) {
mapped.media.sources.forEach { mapped.media.sources.forEach {
if (it.file.contains("m3u8")) { if (it.file.contains("m3u8")) {
hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(it.file, null), true).forEach { stream -> hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(it.file, null), true)
val qualityString = if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p" .forEach { stream ->
val qualityString =
if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p"
sources.add( sources.add(
ExtractorLink( ExtractorLink(
name, name,

View file

@ -94,6 +94,7 @@ suspend fun loadExtractor(url: String, referer: String? = null, callback: (Extra
val extractorApis: Array<ExtractorApi> = arrayOf( val extractorApis: Array<ExtractorApi> = arrayOf(
//AllProvider(), //AllProvider(),
WcoStream(), WcoStream(),
Vidstreamz(),
Mp4Upload(), Mp4Upload(),
StreamTape(), StreamTape(),
MixDrop(), MixDrop(),