AquaStream/app/src/main/java/com/lagradost/cloudstream3/animeproviders/TenshiProvider.kt

356 lines
14 KiB
Kotlin
Raw Normal View History

2021-07-21 23:51:35 +00:00
package com.lagradost.cloudstream3.animeproviders
2021-07-22 13:37:59 +00:00
import android.annotation.SuppressLint
2021-09-23 21:01:54 +00:00
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
2021-07-21 23:51:35 +00:00
import com.lagradost.cloudstream3.*
2021-12-16 19:33:14 +00:00
import com.lagradost.cloudstream3.network.DdosGuardKiller
import com.lagradost.cloudstream3.network.getHeaders
2021-07-21 23:51:35 +00:00
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
import org.jsoup.nodes.Document
2021-12-16 19:33:14 +00:00
import java.net.URI
2021-07-22 13:37:59 +00:00
import java.text.SimpleDateFormat
2021-07-23 16:41:37 +00:00
import java.util.*
2021-07-21 23:51:35 +00:00
class TenshiProvider : MainAPI() {
companion object {
2021-10-03 00:09:13 +00:00
//var token: String? = null
//var cookie: Map<String, String> = mapOf()
2021-07-21 23:51:35 +00:00
fun getType(t: String): TvType {
2022-01-31 20:47:59 +00:00
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
2021-08-14 19:35:26 +00:00
else if (t.contains("Movie")) TvType.AnimeMovie
2021-07-23 16:41:37 +00:00
else TvType.Anime
2021-07-21 23:51:35 +00:00
}
}
override val mainUrl = "https://tenshi.moe"
override val name = "Tenshi.moe"
override val hasQuickSearch = false
override val hasMainPage = true
2022-01-31 20:47:59 +00:00
override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie, TvType.OVA)
2021-12-16 20:18:47 +00:00
private var ddosGuardKiller = DdosGuardKiller(true)
2021-12-16 19:33:14 +00:00
2021-10-03 00:09:13 +00:00
/*private fun loadToken(): Boolean {
2021-07-21 23:51:35 +00:00
return try {
val response = get(mainUrl)
2021-07-21 23:51:35 +00:00
cookie = response.cookies
val document = Jsoup.parse(response.text)
token = document.selectFirst("""meta[name="csrf-token"]""").attr("content")
token != null
} catch (e: Exception) {
false
}
2021-10-03 00:09:13 +00:00
}*/
2022-01-16 22:31:42 +00:00
override suspend fun getMainPage(): HomePageResponse {
2021-09-05 15:19:25 +00:00
val items = ArrayList<HomePageList>()
2021-12-16 19:33:14 +00:00
val soup = app.get(mainUrl, interceptor = ddosGuardKiller).document
2021-09-05 15:19:25 +00:00
for (section in soup.select("#content > section")) {
try {
if (section.attr("id") == "toplist-tabs") {
for (top in section.select(".tab-content > [role=\"tabpanel\"]")) {
val title = "Top - " + top.attr("id").split("-")[1].replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.UK
) else it.toString()
}
2021-09-05 15:19:25 +00:00
val anime = top.select("li > a").map {
AnimeSearchResponse(
it.selectFirst(".thumb-title").text(),
fixUrl(it.attr("href")),
this.name,
TvType.Anime,
it.selectFirst("img").attr("src"),
null,
EnumSet.of(DubStatus.Subbed),
)
}
items.add(HomePageList(title, anime))
}
} else {
val title = section.selectFirst("h2").text()
val anime = section.select("li > a").map {
AnimeSearchResponse(
it.selectFirst(".thumb-title")?.text() ?: "",
2021-09-05 15:19:25 +00:00
fixUrl(it.attr("href")),
this.name,
TvType.Anime,
it.selectFirst("img").attr("src"),
null,
EnumSet.of(DubStatus.Subbed),
)
}
items.add(HomePageList(title, anime))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
if (items.size <= 0) throw ErrorLoadingException()
2021-09-05 15:19:25 +00:00
return HomePageResponse(items)
}
2021-07-21 23:51:35 +00:00
private fun getIsMovie(type: String, id: Boolean = false): Boolean {
if (!id) return type == "Movie"
val movies = listOf("rrso24fa", "e4hqvtym", "bl5jdbqn", "u4vtznut", "37t6h2r4", "cq4azcrj")
val aniId = type.replace("$mainUrl/anime/", "")
return movies.contains(aniId)
}
2021-12-19 13:26:42 +00:00
private fun parseSearchPage(soup: Document): List<SearchResponse> {
2021-07-21 23:51:35 +00:00
val items = soup.select("ul.thumb > li > a")
2021-12-19 13:26:42 +00:00
return items.map {
val href = fixUrl(it.attr("href"))
val img = fixUrl(it.selectFirst("img").attr("src"))
val title = it.attr("title")
if (getIsMovie(href, true)) {
MovieSearchResponse(
title, href, this.name, TvType.Movie, img, null
)
} else {
AnimeSearchResponse(
title,
href,
this.name,
TvType.Anime,
img,
null,
EnumSet.of(DubStatus.Subbed),
)
}
2021-07-21 23:51:35 +00:00
}
}
2021-07-22 13:37:59 +00:00
@SuppressLint("SimpleDateFormat")
2021-09-30 17:14:39 +00:00
private fun dateParser(dateString: String?): String? {
if (dateString == null) return null
try {
val format = SimpleDateFormat("dd 'of' MMM',' yyyy")
val newFormat = SimpleDateFormat("dd-MM-yyyy")
val data = format.parse(
2021-12-16 19:33:14 +00:00
dateString.replace("th ", " ").replace("st ", " ").replace("nd ", " ")
.replace("rd ", " ")
) ?: return null
return newFormat.format(data)
} catch (e: Exception) {
return null
}
2021-07-22 13:37:59 +00:00
}
2021-07-21 23:51:35 +00:00
// data class TenshiSearchResponse(
// @JsonProperty("url") var url : String,
// @JsonProperty("title") var title : String,
// @JsonProperty("cover") var cover : String,
// @JsonProperty("genre") var genre : String,
// @JsonProperty("year") var year : Int,
// @JsonProperty("type") var type : String,
// @JsonProperty("eps") var eps : String,
// @JsonProperty("cen") var cen : String
// )
2022-01-16 22:31:42 +00:00
// override suspend fun quickSearch(query: String): ArrayList<SearchResponse>? {
2021-07-21 23:51:35 +00:00
// if (!autoLoadToken()) return quickSearch(query)
// val url = "$mainUrl/anime/search"
// val response = khttp.post(
// url,
// data=mapOf("q" to query),
// headers=mapOf("x-csrf-token" to token, "x-requested-with" to "XMLHttpRequest"),
// cookies = cookie
//
// )
//
// val items = mapper.readValue<List<TenshiSearchResponse>>(response.text)
//
// if (items.isEmpty()) return ArrayList()
//
// val returnValue = ArrayList<SearchResponse>()
// for (i in items) {
// val href = fixUrl(i.url)
// val title = i.title
// val img = fixUrl(i.cover)
// val year = i.year
//
// returnValue.add(
// if (getIsMovie(i.type)) {
// MovieSearchResponse(
// title, href, getSlug(href), this.name, TvType.Movie, img, year
// )
// } else {
// AnimeSearchResponse(
// title, href, getSlug(href), this.name,
// TvType.Anime, img, year, null,
// EnumSet.of(DubStatus.Subbed),
// null, null
// )
// }
// )
// }
// return returnValue
// }
2022-01-16 22:31:42 +00:00
override suspend fun search(query: String): ArrayList<SearchResponse> {
2021-07-21 23:51:35 +00:00
val url = "$mainUrl/anime"
2021-12-16 19:33:14 +00:00
var document = app.get(
url,
params = mapOf("q" to query),
cookies = mapOf("loop-view" to "thumb"),
interceptor = ddosGuardKiller
).document
2021-10-02 17:53:20 +00:00
2021-12-19 13:26:42 +00:00
val returnValue = parseSearchPage(document).toMutableList()
2021-07-21 23:51:35 +00:00
while (!document.select("""a.page-link[rel="next"]""").isEmpty()) {
2021-07-21 23:51:35 +00:00
val link = document.select("""a.page-link[rel="next"]""")
if (link != null && !link.isEmpty()) {
2021-12-16 19:33:14 +00:00
document = app.get(
link[0].attr("href"),
cookies = mapOf("loop-view" to "thumb"),
interceptor = ddosGuardKiller
).document
2021-07-21 23:51:35 +00:00
returnValue.addAll(parseSearchPage(document))
} else {
break
}
}
2021-12-19 13:26:42 +00:00
return ArrayList(returnValue)
2021-07-21 23:51:35 +00:00
}
2022-01-16 22:31:42 +00:00
override suspend fun load(url: String): LoadResponse {
2021-12-16 19:33:14 +00:00
var document = app.get(
url,
cookies = mapOf("loop-view" to "thumb"),
interceptor = ddosGuardKiller
).document
2021-07-21 23:51:35 +00:00
val canonicalTitle = document.selectFirst("header.entry-header > h1.mb-3").text().trim()
val episodeNodes = document.select("li[class*=\"episode\"] > a").toMutableList()
val totalEpisodePages = if (document.select(".pagination").size > 0)
2021-12-16 19:33:14 +00:00
document.select(".pagination .page-item a.page-link:not([rel])").last().text()
.toIntOrNull()
else 1
if (totalEpisodePages != null && totalEpisodePages > 1) {
for (pageNum in 2..totalEpisodePages) {
2021-12-16 19:33:14 +00:00
document = app.get(
"$url?page=$pageNum",
cookies = mapOf("loop-view" to "thumb"),
interceptor = ddosGuardKiller
).document
episodeNodes.addAll(document.select("li[class*=\"episode\"] > a"))
}
}
2021-07-21 23:51:35 +00:00
val episodes = ArrayList(episodeNodes.map {
2021-07-22 13:37:59 +00:00
AnimeEpisode(
2021-08-14 17:31:27 +00:00
it.attr("href"),
it.selectFirst(".episode-title")?.text()?.trim(),
it.selectFirst("img")?.attr("src"),
2021-09-30 17:14:39 +00:00
dateParser(it?.selectFirst(".episode-date")?.text()?.trim()),
2021-08-14 17:31:27 +00:00
null,
it.attr("data-content").trim(),
)
2021-10-03 00:09:13 +00:00
})
2021-07-21 23:51:35 +00:00
2022-02-05 01:05:13 +00:00
val similarAnime = document.select("ul.anime-loop > li > a")?.mapNotNull { element ->
val href = element.attr("href") ?: return@mapNotNull null
val title =
element.selectFirst("> .overlay > .thumb-title")?.text() ?: return@mapNotNull null
val img = element.selectFirst("> img")?.attr("src")
AnimeSearchResponse(title, href, this.name, TvType.Anime, img)
}
2021-12-19 13:26:42 +00:00
2021-07-23 16:41:37 +00:00
val type = document.selectFirst("a[href*=\"$mainUrl/type/\"]")?.text()?.trim()
2021-07-21 23:51:35 +00:00
2021-12-19 13:26:42 +00:00
return newAnimeLoadResponse(canonicalTitle, url, getType(type ?: "")) {
2022-02-05 01:05:13 +00:00
recommendations = similarAnime
2021-12-19 13:26:42 +00:00
posterUrl = document.selectFirst("img.cover-image")?.attr("src")
plot = document.selectFirst(".entry-description > .card-body")?.text()?.trim()
tags =
document.select("li.genre.meta-data > span.value")
.map { it?.text()?.trim().toString() }
2021-07-21 23:51:35 +00:00
2021-12-19 13:26:42 +00:00
synonyms =
document.select("li.synonym.meta-data > div.info-box > span.value")
.map { it?.text()?.trim().toString() }
2021-07-21 23:51:35 +00:00
2021-12-19 13:26:42 +00:00
engName =
document.selectFirst("span.value > span[title=\"English\"]")?.parent()?.text()
?.trim()
japName =
document.selectFirst("span.value > span[title=\"Japanese\"]")?.parent()?.text()
?.trim()
2021-11-02 15:09:29 +00:00
2022-02-27 01:03:01 +00:00
val pattern = Regex("(\\d{4})")
2021-12-19 13:26:42 +00:00
val yearText = document.selectFirst("li.release-date .value").text()
year = pattern.find(yearText)?.groupValues?.get(1)?.toIntOrNull()
2021-11-02 15:09:29 +00:00
addEpisodes(DubStatus.Subbed, episodes)
2021-12-19 13:26:42 +00:00
showStatus = when (document.selectFirst("li.status > .value")?.text()?.trim()) {
"Ongoing" -> ShowStatus.Ongoing
"Completed" -> ShowStatus.Completed
else -> null
}
2021-11-02 15:09:29 +00:00
}
2021-07-21 23:51:35 +00:00
}
2022-01-16 22:31:42 +00:00
override suspend fun loadLinks(
2021-07-21 23:51:35 +00:00
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
2021-12-16 19:33:14 +00:00
val soup = app.get(data, interceptor = ddosGuardKiller).document
2021-09-23 21:01:30 +00:00
data class Quality(
@JsonProperty("src") val src: String,
@JsonProperty("size") val size: Int
)
val sources = ArrayList<ExtractorLink>()
for (source in soup.select("""[aria-labelledby="mirror-dropdown"] > li > a.dropdown-item""")) {
val release = source.text().replace("/", "").trim()
val sourceHTML = app.get(
2021-09-23 21:01:30 +00:00
"https://tenshi.moe/embed?v=${source.attr("href").split("v=")[1].split("&")[0]}",
2021-12-16 19:33:14 +00:00
headers = mapOf("Referer" to data), interceptor = ddosGuardKiller
2021-09-23 21:01:30 +00:00
).text
2021-12-16 19:33:14 +00:00
val match = Regex("""sources: (\[(?:.|\s)+?type: ['"]video/.*?['"](?:.|\s)+?])""").find(
sourceHTML
)
2021-09-23 21:01:30 +00:00
if (match != null) {
val qualities = mapper.readValue<List<Quality>>(
match.destructured.component1()
.replace("'", "\"")
.replace(Regex("""(\w+): """), "\"\$1\": ")
.replace(Regex("""\s+"""), "")
.replace(",}", "}")
.replace(",]", "]")
)
sources.addAll(qualities.map {
ExtractorLink(
this.name,
"${this.name} $release - " + it.size + "p",
fixUrl(it.src),
this.mainUrl,
2021-12-16 19:33:14 +00:00
getQualityFromName("${it.size}"),
headers = getHeaders(
mapOf(),
null,
2021-12-16 20:12:27 +00:00
ddosGuardKiller?.savedCookiesMap?.get(URI(this.mainUrl).host) ?: mapOf()
2021-12-16 19:33:14 +00:00
).toMap()
2021-09-23 21:01:30 +00:00
)
})
}
}
2021-07-21 23:51:35 +00:00
2021-12-16 19:33:14 +00:00
sources.forEach(callback)
2021-07-21 23:51:35 +00:00
return true
}
}