cloudstream-extensions/DubbedAnimeProvider/src/main/kotlin/com/lagradost/DubbedAnimeProvider.kt

311 lines
12 KiB
Kotlin

package com.lagradost
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
import org.jsoup.Jsoup
import java.util.*
class DubbedAnimeProvider : MainAPI() {
override var mainUrl = "https://bestdubbedanime.com"
override var name = "DubbedAnime"
override val hasQuickSearch = true
override val hasMainPage = true
override val supportedTypes = setOf(
TvType.AnimeMovie,
TvType.Anime,
)
data class QueryEpisodeResultRoot(
@JsonProperty("result")
val result: QueryEpisodeResult,
)
data class QueryEpisodeResult(
@JsonProperty("anime") val anime: List<EpisodeInfo>,
@JsonProperty("error") val error: Boolean,
@JsonProperty("errorMSG") val errorMSG: String?,
)
data class EpisodeInfo(
@JsonProperty("serversHTML") val serversHTML: String,
@JsonProperty("title") val title: String,
@JsonProperty("preview_img") val previewImg: String?,
@JsonProperty("wideImg") val wideImg: String?,
@JsonProperty("year") val year: String?,
@JsonProperty("desc") val desc: String?,
/*
@JsonProperty("rowid") val rowid: String,
@JsonProperty("status") val status: String,
@JsonProperty("skips") val skips: String,
@JsonProperty("totalEp") val totalEp: Long,
@JsonProperty("ep") val ep: String,
@JsonProperty("NextEp") val nextEp: Long,
@JsonProperty("slug") val slug: String,
@JsonProperty("showid") val showid: String,
@JsonProperty("Epviews") val epviews: String,
@JsonProperty("TotalViews") val totalViews: String,
@JsonProperty("tags") val tags: String,*/
)
private suspend fun parseDocumentTrending(url: String): List<SearchResponse> {
val response = app.get(url).text
val document = Jsoup.parse(response)
return document.select("li > a").mapNotNull {
val href = fixUrl(it.attr("href"))
val title = it.selectFirst("> div > div.cittx")?.text() ?: return@mapNotNull null
val poster = fixUrlNull(it.selectFirst("> div > div.imghddde > img")?.attr("src"))
AnimeSearchResponse(
title,
href,
this.name,
TvType.Anime,
poster,
null,
EnumSet.of(DubStatus.Dubbed),
)
}
}
private suspend fun parseDocument(
url: String,
trimEpisode: Boolean = false
): List<SearchResponse> {
val response = app.get(url).text
val document = Jsoup.parse(response)
return document.select("a.grid__link").mapNotNull {
val href = fixUrl(it.attr("href"))
val title = it.selectFirst("> div.gridtitlek")?.text() ?: return@mapNotNull null
val poster =
fixUrl(it.selectFirst("> img.grid__img")?.attr("src") ?: return@mapNotNull null)
AnimeSearchResponse(
title,
if (trimEpisode) href.removeRange(href.lastIndexOf('/'), href.length) else href,
this.name,
TvType.Anime,
poster,
null,
EnumSet.of(DubStatus.Dubbed),
)
}
}
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
val trendingUrl = "$mainUrl/xz/trending.php?_=$unixTimeMS"
val lastEpisodeUrl = "$mainUrl/xz/epgrid.php?p=1&_=$unixTimeMS"
val recentlyAddedUrl = "$mainUrl/xz/gridgrabrecent.php?p=1&_=$unixTimeMS"
//val allUrl = "$mainUrl/xz/gridgrab.php?p=1&limit=12&_=$unixTimeMS"
val listItems = listOf(
HomePageList("Trending", parseDocumentTrending(trendingUrl)),
HomePageList("Recently Added", parseDocument(recentlyAddedUrl)),
HomePageList("Recent Releases", parseDocument(lastEpisodeUrl, true)),
// HomePageList("All", parseDocument(allUrl))
)
return HomePageResponse(listItems)
}
private suspend fun getEpisode(slug: String, isMovie: Boolean): EpisodeInfo {
val url =
mainUrl + (if (isMovie) "/movies/jsonMovie" else "/xz/v3/jsonEpi") + ".php?slug=$slug&_=$unixTime"
val response = app.get(url).text
val mapped = parseJson<QueryEpisodeResultRoot>(response)
return mapped.result.anime.first()
}
private fun getIsMovie(href: String): Boolean {
return href.contains("movies/")
}
private fun getSlug(href: String): String {
return href.replace("$mainUrl/", "")
}
override suspend fun quickSearch(query: String): List<SearchResponse> {
val url = "$mainUrl/xz/searchgrid.php?p=1&limit=12&s=$query&_=$unixTime"
val response = app.get(url).text
val document = Jsoup.parse(response)
val items = document.select("div.grid__item > a")
if (items.isEmpty()) return emptyList()
return items.mapNotNull { i ->
val href = fixUrl(i.attr("href"))
val title = i.selectFirst("div.gridtitlek")?.text() ?: return@mapNotNull null
val img = fixUrlNull(i.selectFirst("img.grid__img")?.attr("src"))
if (getIsMovie(href)) {
MovieSearchResponse(
title, href, this.name, TvType.AnimeMovie, img, null
)
} else {
AnimeSearchResponse(
title,
href,
this.name,
TvType.Anime,
img,
null,
EnumSet.of(DubStatus.Dubbed),
)
}
}
}
override suspend fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/search/$query"
val response = app.get(url).text
val document = Jsoup.parse(response)
val items = document.select("div.resultinner > a.resulta")
if (items.isEmpty()) return ArrayList()
return items.mapNotNull { i ->
val innerDiv = i.selectFirst("> div.result")
val href = fixUrl(i.attr("href"))
val img = fixUrl(innerDiv?.selectFirst("> div.imgkz > img")?.attr("src") ?: return@mapNotNull null)
val title = innerDiv.selectFirst("> div.titleresults")?.text() ?: return@mapNotNull null
if (getIsMovie(href)) {
MovieSearchResponse(
title, href, this.name, TvType.AnimeMovie, img, null
)
} else {
AnimeSearchResponse(
title,
href,
this.name,
TvType.Anime,
img,
null,
EnumSet.of(DubStatus.Dubbed),
)
}
}
}
override suspend fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val serversHTML = (if (data.startsWith(mainUrl)) { // CLASSIC EPISODE
val slug = getSlug(data)
getEpisode(slug, false).serversHTML
} else data).replace("\\", "")
val hls = ArrayList("hl=\"(.*?)\"".toRegex().findAll(serversHTML).map {
it.groupValues[1]
}.toList())
for (hl in hls) {
try {
val sources = app.get("$mainUrl/xz/api/playeri.php?url=$hl&_=$unixTime").text
val find = "src=\"(.*?)\".*?label=\"(.*?)\"".toRegex().find(sources)
if (find != null) {
val quality = find.groupValues[2]
callback.invoke(
ExtractorLink(
this.name,
this.name + " " + quality + if (quality.endsWith('p')) "" else 'p',
fixUrl(find.groupValues[1]),
this.mainUrl,
getQualityFromName(quality)
)
)
}
} catch (e: Exception) {
//IDK
}
}
return true
}
override suspend fun load(url: String): LoadResponse {
if (getIsMovie(url)) {
val realSlug = url.replace("movies/", "")
val episode = getEpisode(realSlug, true)
val poster = episode.previewImg ?: episode.wideImg
return MovieLoadResponse(
episode.title,
realSlug,
this.name,
TvType.AnimeMovie,
episode.serversHTML,
if (poster == null) null else fixUrl(poster),
episode.year?.toIntOrNull(),
episode.desc,
null
)
} else {
val response = app.get(url).text
val document = Jsoup.parse(response)
val title = document.selectFirst("h4")!!.text()
val descriptHeader = document.selectFirst("div.animeDescript")
val descript = descriptHeader?.selectFirst("> p")?.text()
val year = descriptHeader?.selectFirst("> div.distatsx > div.sroverd")
?.text()
?.replace("Released: ", "")
?.toIntOrNull()
val (malId, anilistId, image, cover) = getTracker(title, "tv", year)
val episodes = document.select("a.epibloks").map {
val epTitle = it.selectFirst("> div.inwel > span.isgrxx")?.text()
Episode(fixUrl(it.attr("href")), epTitle)
}
val img = fixUrl(document.select("div.fkimgs > img").attr("src"))
return newAnimeLoadResponse(title, url, TvType.Anime) {
posterUrl = img
this.year = year
addMalId(malId)
addAniListId(anilistId?.toIntOrNull())
addEpisodes(DubStatus.Dubbed, episodes)
plot = descript
}
}
}
private suspend fun getTracker(title: String?, type: String?, year: Int?): Tracker {
val res = app.get("https://api.consumet.org/meta/anilist/$title")
.parsedSafe<AniSearch>()?.results?.find { media ->
media.title?.english.equals(title, true) || media.title?.romaji.equals( title, true )
|| media.type.equals(type, true) && media.releaseDate == year
}
return Tracker(res?.malId, res?.aniId, res?.image, res?.cover)
}
data class Tracker(
val malId: Int? = null,
val aniId: String? = null,
val image: String? = null,
val cover: String? = null,
)
data class Title(
@JsonProperty("romaji") val romaji: String? = null,
@JsonProperty("english") val english: String? = null,
)
data class Results(
@JsonProperty("id") val aniId: String? = null,
@JsonProperty("malId") val malId: Int? = null,
@JsonProperty("title") val title: Title? = null,
@JsonProperty("releaseDate") val releaseDate: Int? = null,
@JsonProperty("type") val type: String? = null,
@JsonProperty("image") val image: String? = null,
@JsonProperty("cover") val cover: String? = null,
)
data class AniSearch(
@JsonProperty("results") val results: ArrayList<Results>? = arrayListOf(),
)
}