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