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…
	
	Add table
		Add a link
		
	
		Reference in a new issue