diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index d4163c43..0bdb66bb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -84,6 +84,7 @@ object APIHolder { KdramaHoodProvider(), AkwamProvider(), AnimePaheProvider(), + NineAnimeProvider(), ) val restrictedApis = arrayListOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt index 92190439..c2fe18a0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt @@ -87,13 +87,13 @@ class AllAnimeProvider : MainAPI() { @JsonProperty("data") val data: Data ) - override suspend fun search(query: String): ArrayList { + override suspend fun search(query: String): List { 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(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 { - 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 { + 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( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeFlickProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeFlickProvider.kt index 9ddc9d07..81379b35 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeFlickProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeFlickProvider.kt @@ -27,12 +27,12 @@ class AnimeFlickProvider : MainAPI() { TvType.OVA ) - override suspend fun search(query: String): ArrayList { + override suspend fun search(query: String): List { 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 { diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NineAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NineAnimeProvider.kt new file mode 100644 index 00000000..37e112ac --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NineAnimeProvider.kt @@ -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().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 { + 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().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().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) ?: 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(epserver) + } else null)?.url?.let { it1 -> getLink(it1.replace("=", "")) } + ?.replace("/embed/", "/e/") + }.apmap { url -> + loadExtractor( + url, data, callback + ) + } + } + + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt index c47d9b70..96ddb49b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt @@ -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("")) { + // 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(it) } + val mapped = parseJson(response) val sources = mutableListOf() if (mapped.success) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt index c17d9c19..6af4fd3a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt @@ -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( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 70ed69c4..b8d172c2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -94,6 +94,7 @@ suspend fun loadExtractor(url: String, referer: String? = null, callback: (Extra val extractorApis: Array = arrayOf( //AllProvider(), WcoStream(), + Vidstreamz(), Mp4Upload(), StreamTape(), MixDrop(),