forked from recloudstream/cloudstream
fixed TenshiProvider.kt
This commit is contained in:
parent
cbeecb7b8e
commit
917ff0ac4a
4 changed files with 109 additions and 33 deletions
|
@ -4,10 +4,12 @@ import android.annotation.SuppressLint
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.network.DdosGuardKiller
|
||||||
|
import com.lagradost.cloudstream3.network.getHeaders
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
import java.net.URI
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -27,10 +29,10 @@ class TenshiProvider : MainAPI() {
|
||||||
override val name = "Tenshi.moe"
|
override val name = "Tenshi.moe"
|
||||||
override val hasQuickSearch = false
|
override val hasQuickSearch = false
|
||||||
override val hasMainPage = true
|
override val hasMainPage = true
|
||||||
|
|
||||||
|
|
||||||
override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie, TvType.ONA)
|
override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie, TvType.ONA)
|
||||||
|
|
||||||
|
private val ddosGuardKiller = DdosGuardKiller(true)
|
||||||
|
|
||||||
/*private fun loadToken(): Boolean {
|
/*private fun loadToken(): Boolean {
|
||||||
return try {
|
return try {
|
||||||
val response = get(mainUrl)
|
val response = get(mainUrl)
|
||||||
|
@ -45,7 +47,7 @@ class TenshiProvider : MainAPI() {
|
||||||
|
|
||||||
override fun getMainPage(): HomePageResponse {
|
override fun getMainPage(): HomePageResponse {
|
||||||
val items = ArrayList<HomePageList>()
|
val items = ArrayList<HomePageList>()
|
||||||
val soup = Jsoup.parse(app.get(mainUrl).text)
|
val soup = app.get(mainUrl, interceptor = ddosGuardKiller).document
|
||||||
for (section in soup.select("#content > section")) {
|
for (section in soup.select("#content > section")) {
|
||||||
try {
|
try {
|
||||||
if (section.attr("id") == "toplist-tabs") {
|
if (section.attr("id") == "toplist-tabs") {
|
||||||
|
@ -136,7 +138,8 @@ class TenshiProvider : MainAPI() {
|
||||||
val format = SimpleDateFormat("dd 'of' MMM',' yyyy")
|
val format = SimpleDateFormat("dd 'of' MMM',' yyyy")
|
||||||
val newFormat = SimpleDateFormat("dd-MM-yyyy")
|
val newFormat = SimpleDateFormat("dd-MM-yyyy")
|
||||||
val data = format.parse(
|
val data = format.parse(
|
||||||
dateString.replace("th ", " ").replace("st ", " ").replace("nd ", " ").replace("rd ", " ")
|
dateString.replace("th ", " ").replace("st ", " ").replace("nd ", " ")
|
||||||
|
.replace("rd ", " ")
|
||||||
) ?: return null
|
) ?: return null
|
||||||
return newFormat.format(data)
|
return newFormat.format(data)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -197,16 +200,23 @@ class TenshiProvider : MainAPI() {
|
||||||
|
|
||||||
override fun search(query: String): ArrayList<SearchResponse> {
|
override fun search(query: String): ArrayList<SearchResponse> {
|
||||||
val url = "$mainUrl/anime"
|
val url = "$mainUrl/anime"
|
||||||
var response = app.get(url, params = mapOf("q" to query), cookies = mapOf("loop-view" to "thumb")).text
|
var document = app.get(
|
||||||
var document = Jsoup.parse(response)
|
url,
|
||||||
|
params = mapOf("q" to query),
|
||||||
|
cookies = mapOf("loop-view" to "thumb"),
|
||||||
|
interceptor = ddosGuardKiller
|
||||||
|
).document
|
||||||
|
|
||||||
val returnValue = parseSearchPage(document)
|
val returnValue = parseSearchPage(document)
|
||||||
|
|
||||||
while (!document.select("""a.page-link[rel="next"]""").isEmpty()) {
|
while (!document.select("""a.page-link[rel="next"]""").isEmpty()) {
|
||||||
val link = document.select("""a.page-link[rel="next"]""")
|
val link = document.select("""a.page-link[rel="next"]""")
|
||||||
if (link != null && !link.isEmpty()) {
|
if (link != null && !link.isEmpty()) {
|
||||||
response = app.get(link[0].attr("href"), cookies = mapOf("loop-view" to "thumb")).text
|
document = app.get(
|
||||||
document = Jsoup.parse(response)
|
link[0].attr("href"),
|
||||||
|
cookies = mapOf("loop-view" to "thumb"),
|
||||||
|
interceptor = ddosGuardKiller
|
||||||
|
).document
|
||||||
returnValue.addAll(parseSearchPage(document))
|
returnValue.addAll(parseSearchPage(document))
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
|
@ -217,22 +227,31 @@ class TenshiProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun load(url: String): LoadResponse {
|
override fun load(url: String): LoadResponse {
|
||||||
var response = app.get(url, cookies = mapOf("loop-view" to "thumb")).text
|
var document = app.get(
|
||||||
var document = Jsoup.parse(response)
|
url,
|
||||||
|
cookies = mapOf("loop-view" to "thumb"),
|
||||||
|
interceptor = ddosGuardKiller
|
||||||
|
).document
|
||||||
|
|
||||||
val englishTitle = document.selectFirst("span.value > span[title=\"English\"]")?.parent()?.text()?.trim()
|
val englishTitle =
|
||||||
val japaneseTitle = document.selectFirst("span.value > span[title=\"Japanese\"]")?.parent()?.text()?.trim()
|
document.selectFirst("span.value > span[title=\"English\"]")?.parent()?.text()?.trim()
|
||||||
|
val japaneseTitle =
|
||||||
|
document.selectFirst("span.value > span[title=\"Japanese\"]")?.parent()?.text()?.trim()
|
||||||
val canonicalTitle = document.selectFirst("header.entry-header > h1.mb-3").text().trim()
|
val canonicalTitle = document.selectFirst("header.entry-header > h1.mb-3").text().trim()
|
||||||
|
|
||||||
val episodeNodes = document.select("li[class*=\"episode\"] > a").toMutableList()
|
val episodeNodes = document.select("li[class*=\"episode\"] > a").toMutableList()
|
||||||
val totalEpisodePages = if (document.select(".pagination").size > 0)
|
val totalEpisodePages = if (document.select(".pagination").size > 0)
|
||||||
document.select(".pagination .page-item a.page-link:not([rel])").last().text().toIntOrNull()
|
document.select(".pagination .page-item a.page-link:not([rel])").last().text()
|
||||||
|
.toIntOrNull()
|
||||||
else 1
|
else 1
|
||||||
|
|
||||||
if (totalEpisodePages != null && totalEpisodePages > 1) {
|
if (totalEpisodePages != null && totalEpisodePages > 1) {
|
||||||
for (pageNum in 2..totalEpisodePages) {
|
for (pageNum in 2..totalEpisodePages) {
|
||||||
response = app.get("$url?page=$pageNum", cookies = mapOf("loop-view" to "thumb")).text
|
document = app.get(
|
||||||
document = Jsoup.parse(response)
|
"$url?page=$pageNum",
|
||||||
|
cookies = mapOf("loop-view" to "thumb"),
|
||||||
|
interceptor = ddosGuardKiller
|
||||||
|
).document
|
||||||
episodeNodes.addAll(document.select("li[class*=\"episode\"] > a"))
|
episodeNodes.addAll(document.select("li[class*=\"episode\"] > a"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,10 +279,12 @@ class TenshiProvider : MainAPI() {
|
||||||
val type = document.selectFirst("a[href*=\"$mainUrl/type/\"]")?.text()?.trim()
|
val type = document.selectFirst("a[href*=\"$mainUrl/type/\"]")?.text()?.trim()
|
||||||
|
|
||||||
val synopsis = document.selectFirst(".entry-description > .card-body")?.text()?.trim()
|
val synopsis = document.selectFirst(".entry-description > .card-body")?.text()?.trim()
|
||||||
val genre = document.select("li.genre.meta-data > span.value").map { it?.text()?.trim().toString() }
|
val genre =
|
||||||
|
document.select("li.genre.meta-data > span.value").map { it?.text()?.trim().toString() }
|
||||||
|
|
||||||
val synonyms =
|
val synonyms =
|
||||||
document.select("li.synonym.meta-data > div.info-box > span.value").map { it?.text()?.trim().toString() }
|
document.select("li.synonym.meta-data > div.info-box > span.value")
|
||||||
|
.map { it?.text()?.trim().toString() }
|
||||||
|
|
||||||
return newAnimeLoadResponse(canonicalTitle, url, getType(type ?: "")) {
|
return newAnimeLoadResponse(canonicalTitle, url, getType(type ?: "")) {
|
||||||
engName = englishTitle
|
engName = englishTitle
|
||||||
|
@ -287,8 +308,7 @@ class TenshiProvider : MainAPI() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val response = app.get(data).text
|
val soup = app.get(data, interceptor = ddosGuardKiller).document
|
||||||
val soup = Jsoup.parse(response)
|
|
||||||
|
|
||||||
data class Quality(
|
data class Quality(
|
||||||
@JsonProperty("src") val src: String,
|
@JsonProperty("src") val src: String,
|
||||||
|
@ -300,10 +320,12 @@ class TenshiProvider : MainAPI() {
|
||||||
val release = source.text().replace("/", "").trim()
|
val release = source.text().replace("/", "").trim()
|
||||||
val sourceHTML = app.get(
|
val sourceHTML = app.get(
|
||||||
"https://tenshi.moe/embed?v=${source.attr("href").split("v=")[1].split("&")[0]}",
|
"https://tenshi.moe/embed?v=${source.attr("href").split("v=")[1].split("&")[0]}",
|
||||||
headers = mapOf("Referer" to data)
|
headers = mapOf("Referer" to data), interceptor = ddosGuardKiller
|
||||||
).text
|
).text
|
||||||
|
|
||||||
val match = Regex("""sources: (\[(?:.|\s)+?type: ['"]video/.*?['"](?:.|\s)+?])""").find(sourceHTML)
|
val match = Regex("""sources: (\[(?:.|\s)+?type: ['"]video/.*?['"](?:.|\s)+?])""").find(
|
||||||
|
sourceHTML
|
||||||
|
)
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
val qualities = mapper.readValue<List<Quality>>(
|
val qualities = mapper.readValue<List<Quality>>(
|
||||||
match.destructured.component1()
|
match.destructured.component1()
|
||||||
|
@ -319,15 +341,18 @@ class TenshiProvider : MainAPI() {
|
||||||
"${this.name} $release - " + it.size + "p",
|
"${this.name} $release - " + it.size + "p",
|
||||||
fixUrl(it.src),
|
fixUrl(it.src),
|
||||||
this.mainUrl,
|
this.mainUrl,
|
||||||
getQualityFromName("${it.size}")
|
getQualityFromName("${it.size}"),
|
||||||
|
headers = getHeaders(
|
||||||
|
mapOf(),
|
||||||
|
null,
|
||||||
|
ddosGuardKiller.savedCookiesMap[URI(this.mainUrl).host] ?: mapOf()
|
||||||
|
).toMap()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (source in sources) {
|
sources.forEach(callback)
|
||||||
callback.invoke(source)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.lagradost.cloudstream3.network
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param alwaysBypass will pre-emptively fetch ddos guard cookies if true.
|
||||||
|
* If false it will only try to get cookies when a request returns 403
|
||||||
|
* */
|
||||||
|
class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor {
|
||||||
|
val savedCookiesMap = mutableMapOf<String, Map<String, String>>()
|
||||||
|
|
||||||
|
// As seen in https://github.com/anime-dl/anime-downloader/blob/master/anime_downloader/sites/erairaws.py
|
||||||
|
private val resp = app.get(
|
||||||
|
"https://check.ddos-guard.net/check.js"
|
||||||
|
).text
|
||||||
|
private val ddosBypassPath = Regex("'(.*?)'").find(resp)?.groupValues?.get(1)
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
if (alwaysBypass) return bypassDdosGuard(request)
|
||||||
|
|
||||||
|
val response = chain.proceed(request)
|
||||||
|
return if (response.code == 403){
|
||||||
|
bypassDdosGuard(request)
|
||||||
|
} else response
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bypassDdosGuard(request: Request): Response {
|
||||||
|
val cookies =
|
||||||
|
savedCookiesMap[request.url.host]
|
||||||
|
// If no cookies are found fetch and save em.
|
||||||
|
?: (request.url.scheme + "://" + request.url.host + (ddosBypassPath ?: "")).let {
|
||||||
|
app.get(it, cacheTime = 0).cookies.also { cookies ->
|
||||||
|
savedCookiesMap[request.url.host] = cookies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val headers = getHeaders(request.headers.toMap(), null, cookies)
|
||||||
|
return app.baseClient.newCall(
|
||||||
|
request.newBuilder()
|
||||||
|
.headers(headers)
|
||||||
|
.build()
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
}
|
|
@ -128,7 +128,7 @@ private fun getCache(cacheTime: Int, cacheUnit: TimeUnit): CacheControl {
|
||||||
/**
|
/**
|
||||||
* Referer > Set headers > Set cookies > Default headers > Default Cookies
|
* Referer > Set headers > Set cookies > Default headers > Default Cookies
|
||||||
*/
|
*/
|
||||||
private fun getHeaders(
|
fun getHeaders(
|
||||||
headers: Map<String, String>,
|
headers: Map<String, String>,
|
||||||
referer: String?,
|
referer: String?,
|
||||||
cookie: Map<String, String>
|
cookie: Map<String, String>
|
||||||
|
@ -137,12 +137,10 @@ private fun getHeaders(
|
||||||
val cookieHeaders = (DEFAULT_COOKIES + cookie)
|
val cookieHeaders = (DEFAULT_COOKIES + cookie)
|
||||||
val cookieMap =
|
val cookieMap =
|
||||||
if (cookieHeaders.isNotEmpty()) mapOf(
|
if (cookieHeaders.isNotEmpty()) mapOf(
|
||||||
"Cookie" to cookieHeaders.entries.joinToString(
|
"Cookie" to cookieHeaders.entries.joinToString() {
|
||||||
separator = "; "
|
|
||||||
) {
|
|
||||||
"${it.key}=${it.value};"
|
"${it.key}=${it.value};"
|
||||||
}) else mapOf()
|
}) else mapOf()
|
||||||
val tempHeaders = (DEFAULT_HEADERS + cookieMap + headers + refererMap)
|
val tempHeaders = (DEFAULT_HEADERS + headers + cookieMap + refererMap)
|
||||||
return tempHeaders.toHeaders()
|
return tempHeaders.toHeaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.bumptech.glide.module.AppGlideModule
|
import com.bumptech.glide.module.AppGlideModule
|
||||||
import com.bumptech.glide.request.Request
|
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
import com.bumptech.glide.signature.ObjectKey
|
||||||
|
import com.lagradost.cloudstream3.network.DdosGuardKiller
|
||||||
import com.lagradost.cloudstream3.network.Requests
|
import com.lagradost.cloudstream3.network.Requests
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
|
@ -31,7 +31,12 @@ class GlideModule : AppGlideModule() {
|
||||||
// Needed for DOH
|
// Needed for DOH
|
||||||
// https://stackoverflow.com/a/61634041
|
// https://stackoverflow.com/a/61634041
|
||||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||||
val client = Requests().initClient(context)
|
val client =
|
||||||
|
Requests().initClient(context)
|
||||||
|
.newBuilder()
|
||||||
|
.addInterceptor(DdosGuardKiller(false))
|
||||||
|
.build()
|
||||||
|
|
||||||
registry.replace(
|
registry.replace(
|
||||||
GlideUrl::class.java,
|
GlideUrl::class.java,
|
||||||
InputStream::class.java,
|
InputStream::class.java,
|
||||||
|
|
Loading…
Reference in a new issue