mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
9anime by Stormunblessed + minor code changes
This commit is contained in:
parent
73bcb4145e
commit
428e97ab1c
7 changed files with 331 additions and 43 deletions
|
@ -84,6 +84,7 @@ object APIHolder {
|
|||
KdramaHoodProvider(),
|
||||
AkwamProvider(),
|
||||
AnimePaheProvider(),
|
||||
NineAnimeProvider(),
|
||||
)
|
||||
|
||||
val restrictedApis = arrayListOf(
|
||||
|
|
|
@ -87,13 +87,13 @@ class AllAnimeProvider : MainAPI() {
|
|||
@JsonProperty("data") val data: Data
|
||||
)
|
||||
|
||||
override suspend fun search(query: String): ArrayList<SearchResponse> {
|
||||
override suspend fun search(query: String): List<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 = app.get(link).text
|
||||
if (res.contains("PERSISTED_QUERY_NOT_FOUND")) {
|
||||
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)
|
||||
|
||||
|
@ -102,7 +102,7 @@ class AllAnimeProvider : MainAPI() {
|
|||
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
|
||||
}
|
||||
|
||||
return ArrayList(results.map {
|
||||
return results.map {
|
||||
AnimeSearchResponse(
|
||||
it.name,
|
||||
"$mainUrl/anime/${it.Id}",
|
||||
|
@ -115,7 +115,7 @@ class AllAnimeProvider : MainAPI() {
|
|||
it.availableEpisodes?.dub,
|
||||
it.availableEpisodes?.sub
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private data class AvailableEpisodesDetail(
|
||||
|
@ -154,11 +154,11 @@ class AllAnimeProvider : MainAPI() {
|
|||
|
||||
val episodes = showData.availableEpisodes.let {
|
||||
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(
|
||||
"$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(
|
||||
"$mainUrl/anime/${showData.Id}/episodes/dub/$epNum", episode = epNum
|
||||
)
|
||||
|
@ -251,21 +251,20 @@ class AllAnimeProvider : MainAPI() {
|
|||
private fun getM3u8Qualities(
|
||||
m3u8Link: String,
|
||||
referer: String,
|
||||
qualityName: String
|
||||
): ArrayList<ExtractorLink> {
|
||||
return ArrayList(
|
||||
hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(m3u8Link, null), true).map { stream ->
|
||||
val qualityString = if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p"
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
"${this.name} - $qualityName $qualityString",
|
||||
stream.streamUrl,
|
||||
referer,
|
||||
getQualityFromName(stream.quality.toString()),
|
||||
true,
|
||||
stream.headers
|
||||
)
|
||||
})
|
||||
qualityName: String,
|
||||
): List<ExtractorLink> {
|
||||
return hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(m3u8Link, null), true).map { stream ->
|
||||
val qualityString = if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p"
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
"${this.name} - $qualityName $qualityString",
|
||||
stream.streamUrl,
|
||||
referer,
|
||||
getQualityFromName(stream.quality.toString()),
|
||||
true,
|
||||
stream.headers
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
|
|
|
@ -27,12 +27,12 @@ class AnimeFlickProvider : MainAPI() {
|
|||
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 html = app.get(link).text
|
||||
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 title = it.selectFirst("h5 > a").text()
|
||||
val poster = mainUrl + it.selectFirst("img").attr("src").replace("70x110", "225x320")
|
||||
|
@ -45,7 +45,7 @@ class AnimeFlickProvider : MainAPI() {
|
|||
null,
|
||||
EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
|
|
|
@ -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(""", "\"") ?: 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
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.USER_AGENT
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.app
|
||||
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() {
|
||||
override val name = "Mcloud"
|
||||
|
@ -30,6 +32,11 @@ open class Mcloud : ExtractorApi() {
|
|||
val link = url.replace("$mainUrl/e/","$mainUrl/info/")
|
||||
val response = app.get(link, headers = headers).text
|
||||
|
||||
if(response.startsWith("<!DOCTYPE html>")) {
|
||||
// TODO decrypt html for link
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
data class Sources (
|
||||
@JsonProperty("file") val file: String
|
||||
)
|
||||
|
@ -43,7 +50,7 @@ open class Mcloud : ExtractorApi() {
|
|||
@JsonProperty("media") val media: Media,
|
||||
)
|
||||
|
||||
val mapped = response.let { parseJson<JsonMcloud>(it) }
|
||||
val mapped = parseJson<JsonMcloud>(response)
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
|
||||
if (mapped.success)
|
||||
|
|
|
@ -6,7 +6,11 @@ import com.lagradost.cloudstream3.app
|
|||
import com.lagradost.cloudstream3.mapper
|
||||
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 mainUrl = "https://vidstream.pro"
|
||||
override val requiresReferer = false
|
||||
|
@ -16,8 +20,8 @@ class WcoStream : ExtractorApi() {
|
|||
val baseUrl = url.split("/e/")[0]
|
||||
|
||||
val html = app.get(url, headers = mapOf("Referer" to "https://wcostream.cc/")).text
|
||||
val (Id) = "/e/(.*?)?domain".toRegex().find(url)!!.destructured
|
||||
val (skey) = """skey\s=\s['"](.*?)['"];""".toRegex().find(html)!!.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 ?: return emptyList()
|
||||
|
||||
val apiLink = "$baseUrl/info/$Id?domain=wcostream.cc&skey=$skey"
|
||||
val referrer = "$baseUrl/e/$Id?domain=wcostream.cc"
|
||||
|
@ -44,19 +48,21 @@ class WcoStream : ExtractorApi() {
|
|||
if (mapped.success) {
|
||||
mapped.media.sources.forEach {
|
||||
if (it.file.contains("m3u8")) {
|
||||
hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(it.file, null), true).forEach { stream ->
|
||||
val qualityString = if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p"
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
"$name $qualityString",
|
||||
stream.streamUrl,
|
||||
"",
|
||||
getQualityFromName(stream.quality.toString()),
|
||||
true
|
||||
hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(it.file, null), true)
|
||||
.forEach { stream ->
|
||||
val qualityString =
|
||||
if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p"
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
"$name $qualityString",
|
||||
stream.streamUrl,
|
||||
"",
|
||||
getQualityFromName(stream.quality.toString()),
|
||||
true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
|
|
|
@ -94,6 +94,7 @@ suspend fun loadExtractor(url: String, referer: String? = null, callback: (Extra
|
|||
val extractorApis: Array<ExtractorApi> = arrayOf(
|
||||
//AllProvider(),
|
||||
WcoStream(),
|
||||
Vidstreamz(),
|
||||
Mp4Upload(),
|
||||
StreamTape(),
|
||||
MixDrop(),
|
||||
|
|
Loading…
Reference in a new issue