mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
added Anroll
This commit is contained in:
parent
b74f198e4d
commit
a46583ee2c
4 changed files with 283 additions and 0 deletions
26
Anroll/build.gradle.kts
Normal file
26
Anroll/build.gradle.kts
Normal file
|
@ -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%"
|
||||||
|
}
|
2
Anroll/src/main/AndroidManifest.xml
Normal file
2
Anroll/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="com.hexated"/>
|
241
Anroll/src/main/kotlin/com/hexated/Anroll.kt
Normal file
241
Anroll/src/main/kotlin/com/hexated/Anroll.kt
Normal file
|
@ -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<HomePageList>()
|
||||||
|
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<SearchResponse> = search(query)
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse> {
|
||||||
|
val res = app.get("$searchUrl/search?q=$query").parsedSafe<SearchAnime>()
|
||||||
|
val collection = mutableListOf<SearchResponse>()
|
||||||
|
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<Episode>()
|
||||||
|
|
||||||
|
if (type == TvType.Anime) {
|
||||||
|
for (i in 1..10) {
|
||||||
|
val dataEpisode = app.get("$episodeUrl/animes/${fixUrl.substringAfterLast("/")}/episodes?page=$i&order=desc")
|
||||||
|
.parsedSafe<LoadAnime>()?.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<Load>(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<String, String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class LoadAnime(
|
||||||
|
@JsonProperty("data") val data: ArrayList<DataEpisode>? = 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<DataAnime>? = arrayListOf(),
|
||||||
|
@JsonProperty("data_filme") val data_filme: ArrayList<DataFilme>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
14
Anroll/src/main/kotlin/com/hexated/AnrollPlugin.kt
Normal file
14
Anroll/src/main/kotlin/com/hexated/AnrollPlugin.kt
Normal file
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue