From a46583ee2ccf34f7ff8e4721eb2d0f313705aba9 Mon Sep 17 00:00:00 2001 From: hexated Date: Sun, 14 May 2023 20:55:48 +0700 Subject: [PATCH] added Anroll --- Anroll/build.gradle.kts | 26 ++ Anroll/src/main/AndroidManifest.xml | 2 + Anroll/src/main/kotlin/com/hexated/Anroll.kt | 241 ++++++++++++++++++ .../main/kotlin/com/hexated/AnrollPlugin.kt | 14 + 4 files changed, 283 insertions(+) create mode 100644 Anroll/build.gradle.kts create mode 100644 Anroll/src/main/AndroidManifest.xml create mode 100644 Anroll/src/main/kotlin/com/hexated/Anroll.kt create mode 100644 Anroll/src/main/kotlin/com/hexated/AnrollPlugin.kt diff --git a/Anroll/build.gradle.kts b/Anroll/build.gradle.kts new file mode 100644 index 00000000..75ae21f7 --- /dev/null +++ b/Anroll/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + +cloudstream { + language = "pt" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "AnimeMovie", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=www.anroll.net&sz=%size%" +} \ No newline at end of file diff --git a/Anroll/src/main/AndroidManifest.xml b/Anroll/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Anroll/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Anroll/src/main/kotlin/com/hexated/Anroll.kt b/Anroll/src/main/kotlin/com/hexated/Anroll.kt new file mode 100644 index 00000000..178db598 --- /dev/null +++ b/Anroll/src/main/kotlin/com/hexated/Anroll.kt @@ -0,0 +1,241 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import org.jsoup.nodes.Element + +class Anroll : MainAPI() { + override var mainUrl = "https://www.anroll.net" + override var name = "Anroll" + override val hasMainPage = true + override var lang = "pt" + override val hasDownloadSupport = true + override val hasQuickSearch = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + private const val searchUrl = "https://apiv2-prd.anroll.net" + private const val episodeUrl = "https://apiv3-prd.anroll.net" + private const val posterUrl = "https://static.anroll.net" + private const val videoUrl = "https://cdn-zenitsu.gamabunta.xyz" + } + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get("$mainUrl/home").document + val home = mutableListOf() + document.select("div.sc-f5d5b250-1.iJHcsI").map { div -> + val header = div.selectFirst("h2")?.text() ?: return@map + val child = HomePageList( + header, + div.select("ul li").mapNotNull { + it.toSearchResult() + }, + header == "Últimos Laçamentos" + ) + home.add(child) + } + return HomePageResponse(home) + } + + private fun Element.toSearchResult(): AnimeSearchResponse? { + val title = this.selectFirst("h1")?.text()?.trim() ?: "" + val href = fixUrl(this.selectFirst("a")?.attr("href") ?: return null) + val posterUrl = fixUrlNull(this.select("img").attr("src")) + val epNum = this.selectFirst("span.sc-f5d5b250-3.fsTgnD b")?.text()?.toIntOrNull() + val isDub = this.selectFirst("div.sc-9dbd1f1d-5.efznig")?.text() == "DUB" + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addDubStatus(isDub, epNum) + } + } + + override suspend fun quickSearch(query: String): List = search(query) + + override suspend fun search(query: String): List { + val res = app.get("$searchUrl/search?q=$query").parsedSafe() + val collection = mutableListOf() + val anime = res?.data_anime?.mapNotNull { + addAnimeSearch( + it.titulo ?: return@mapNotNull null, + "a/${it.generate_id}", + it.slug_serie ?: return@mapNotNull null, + Image.Anime + ) + } + if (anime?.isNotEmpty() == true) collection.addAll(anime) + val filme = res?.data_filme?.mapNotNull { + addAnimeSearch( + it.nome_filme ?: return@mapNotNull null, + "f/${it.generate_id}", + it.slug_filme ?: return@mapNotNull null, + Image.Filme + ) + } + if (filme?.isNotEmpty() == true) collection.addAll(filme) + return collection + } + + override suspend fun load(url: String): LoadResponse? { + val fixUrl = getProperAnimeLink(url) ?: throw ErrorLoadingException() + val document = app.get(fixUrl).document + + val article = document.selectFirst("article.sc-f5d5b250-9") ?: return null + val title = article.selectFirst("h2")?.text() ?: return null + val poster = fixUrlNull(document.select("article.sc-f5d5b250-8 img").attr("src")) + val tags = article.select("div#generos a").map { it.text() } + val year = article.selectFirst("div.sc-f5d5b250-4")?.nextElementSibling()?.text() + ?.toIntOrNull() + val description = document.select("div.sinopse").text().trim() + val type = if (fixUrl.contains("/a/")) TvType.Anime else TvType.AnimeMovie + + val episodes = mutableListOf() + + if (type == TvType.Anime) { + for (i in 1..10) { + val dataEpisode = app.get("$episodeUrl/animes/${fixUrl.substringAfterLast("/")}/episodes?page=$i&order=desc") + .parsedSafe()?.data?.map { + Episode( + Load(it.anime?.get("slug_serie"), it.n_episodio, "animes").toJson(), + it.titulo_episodio, + episode = it.n_episodio?.toIntOrNull(), + posterUrl = it.anime?.get("slug_serie")?.fixImageUrl(Image.Episode), + description = it.sinopse_episodio + ) + }?.reversed() ?: emptyList() + if(dataEpisode.isEmpty()) break else episodes.addAll(dataEpisode) + } + } else { + val dataEpisode = listOf( + Episode( + Load( + document.selectFirst("script:containsData(slug_filme)")?.data()?.let { + Regex("[\"']slug_filme[\"']:[\"'](\\S+?)[\"']").find(it)?.groupValues?.get(1) + } ?: return null, "movie", "movies" + ).toJson() + + ) + ) + episodes.addAll(dataEpisode) + } + + return newAnimeLoadResponse(title, url, type) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + plot = description + this.tags = tags + } + + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val load = tryParseJson(data) + callback.invoke( + ExtractorLink( + this.name, + this.name, + if(load?.type == "movies") { + "$videoUrl/hls/${load.type}/${load.slug_serie}/${load.n_episodio}.mp4/media-1/stream.m3u8" + } else { + "$videoUrl/cf/hls/${load?.type}/${load?.slug_serie}/${load?.n_episodio}.mp4/media-1/stream.m3u8" + }, + "$mainUrl/", + Qualities.Unknown.value, + true + ) + ) + return true + } + + private suspend fun getProperAnimeLink(uri: String): String? { + return if (uri.contains("/e/")) { + app.get(uri).document.selectFirst("div.epcontrol2 a[href*=/a/]")?.attr("href")?.let { + fixUrl(it) + } + } else { + uri + } + } + + private fun addAnimeSearch(titulo: String, id: String, slug: String, type: Image): AnimeSearchResponse { + return newAnimeSearchResponse(titulo, "$mainUrl/$id", TvType.Anime) { + this.posterUrl = slug.fixImageUrl(type) + } + } + + private fun String.fixImageUrl(param: Image): String { + return when (param) { + Image.Episode -> { + "$posterUrl/images/animes/screens/$this/130x74/007.jpg" + } + Image.Anime -> { + "$mainUrl/_next/image?url=$posterUrl/images/animes/capas/130x209/$this.jpg&w=384&q=75" + } + Image.Filme -> { + "$mainUrl/_next/image?url=$posterUrl/images/filmes/capas/130x209/$this.jpg&w=384&q=75" + } + } + } + + enum class Image { + Episode, + Anime, + Filme, + } + + data class Load( + val slug_serie: String? = null, + val n_episodio: String? = null, + val type: String? = null, + ) + + data class DataEpisode( + @JsonProperty("id_series_episodios") val id_series_episodios: Int? = null, + @JsonProperty("n_episodio") val n_episodio: String? = null, + @JsonProperty("titulo_episodio") val titulo_episodio: String? = null, + @JsonProperty("sinopse_episodio") val sinopse_episodio: String? = null, + @JsonProperty("generate_id") val generate_id: String? = null, + @JsonProperty("anime") val anime: HashMap? = null, + ) + + data class LoadAnime( + @JsonProperty("data") val data: ArrayList? = arrayListOf() + ) + + data class DataAnime( + @JsonProperty("titulo") val titulo: String? = null, + @JsonProperty("generate_id") val generate_id: String? = null, + @JsonProperty("slug_serie") val slug_serie: String? = null, + @JsonProperty("total_eps_anime") val total_eps_anime: Int? = null, + ) + + data class DataFilme( + @JsonProperty("nome_filme") val nome_filme: String? = null, + @JsonProperty("generate_id") val generate_id: String? = null, + @JsonProperty("slug_filme") val slug_filme: String? = null, + ) + + data class SearchAnime( + @JsonProperty("data_anime") val data_anime: ArrayList? = arrayListOf(), + @JsonProperty("data_filme") val data_filme: ArrayList? = arrayListOf(), + ) + +} \ No newline at end of file diff --git a/Anroll/src/main/kotlin/com/hexated/AnrollPlugin.kt b/Anroll/src/main/kotlin/com/hexated/AnrollPlugin.kt new file mode 100644 index 00000000..431b5f31 --- /dev/null +++ b/Anroll/src/main/kotlin/com/hexated/AnrollPlugin.kt @@ -0,0 +1,14 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnrollPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Anroll()) + } +} \ No newline at end of file