mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master'
This commit is contained in:
		
						commit
						f3e7163436
					
				
					 19 changed files with 395 additions and 34 deletions
				
			
		| 
						 | 
				
			
			@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonProperty
 | 
			
		|||
import com.fasterxml.jackson.databind.DeserializationFeature
 | 
			
		||||
import com.fasterxml.jackson.databind.json.JsonMapper
 | 
			
		||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
 | 
			
		||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
 | 
			
		||||
import com.lagradost.cloudstream3.animeproviders.*
 | 
			
		||||
import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider
 | 
			
		||||
import com.lagradost.cloudstream3.movieproviders.*
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +92,7 @@ object APIHolder {
 | 
			
		|||
            TantifilmProvider(),
 | 
			
		||||
            CineblogProvider(),
 | 
			
		||||
            AltadefinizioneProvider(),
 | 
			
		||||
            FilmpertuttiProvider(),
 | 
			
		||||
            HDMovie5(),
 | 
			
		||||
            RebahinProvider(),
 | 
			
		||||
            LayarKacaProvider(),
 | 
			
		||||
| 
						 | 
				
			
			@ -899,11 +899,11 @@ interface LoadResponse {
 | 
			
		|||
            this.actors = actors?.map { actor -> ActorData(actor) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun LoadResponse.getMalId() : String? {
 | 
			
		||||
        fun LoadResponse.getMalId(): String? {
 | 
			
		||||
            return this.syncData[malIdPrefix]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun LoadResponse.getAniListId() : String? {
 | 
			
		||||
        fun LoadResponse.getAniListId(): String? {
 | 
			
		||||
            return this.syncData[aniListIdPrefix]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1004,7 +1004,7 @@ interface LoadResponse {
 | 
			
		|||
 | 
			
		||||
fun LoadResponse?.isEpisodeBased(): Boolean {
 | 
			
		||||
    if (this == null) return false
 | 
			
		||||
    return (this is AnimeLoadResponse || this is TvSeriesLoadResponse) && this.type.isEpisodeBased()
 | 
			
		||||
    return this is EpisodeResponse && this.type.isEpisodeBased()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun LoadResponse?.isAnimeBased(): Boolean {
 | 
			
		||||
| 
						 | 
				
			
			@ -1017,6 +1017,17 @@ fun TvType?.isEpisodeBased(): Boolean {
 | 
			
		|||
    return (this == TvType.TvSeries || this == TvType.Anime)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
data class NextAiring(
 | 
			
		||||
    val episode: Int,
 | 
			
		||||
    val unixTime: Long,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
interface EpisodeResponse {
 | 
			
		||||
    var showStatus: ShowStatus?
 | 
			
		||||
    var nextAiring: NextAiring?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data class TorrentLoadResponse(
 | 
			
		||||
    override var name: String,
 | 
			
		||||
    override var url: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -1050,7 +1061,7 @@ data class AnimeLoadResponse(
 | 
			
		|||
    override var year: Int? = null,
 | 
			
		||||
 | 
			
		||||
    var episodes: MutableMap<DubStatus, List<Episode>> = mutableMapOf(),
 | 
			
		||||
    var showStatus: ShowStatus? = null,
 | 
			
		||||
    override var showStatus: ShowStatus? = null,
 | 
			
		||||
 | 
			
		||||
    override var plot: String? = null,
 | 
			
		||||
    override var tags: List<String>? = null,
 | 
			
		||||
| 
						 | 
				
			
			@ -1064,7 +1075,8 @@ data class AnimeLoadResponse(
 | 
			
		|||
    override var comingSoon: Boolean = false,
 | 
			
		||||
    override var syncData: MutableMap<String, String> = mutableMapOf(),
 | 
			
		||||
    override var posterHeaders: Map<String, String>? = null,
 | 
			
		||||
) : LoadResponse
 | 
			
		||||
    override var nextAiring: NextAiring? = null,
 | 
			
		||||
) : LoadResponse, EpisodeResponse
 | 
			
		||||
 | 
			
		||||
fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<Episode>?) {
 | 
			
		||||
    if (episodes == null) return
 | 
			
		||||
| 
						 | 
				
			
			@ -1222,7 +1234,7 @@ data class TvSeriesLoadResponse(
 | 
			
		|||
    override var year: Int? = null,
 | 
			
		||||
    override var plot: String? = null,
 | 
			
		||||
 | 
			
		||||
    var showStatus: ShowStatus? = null,
 | 
			
		||||
    override var showStatus: ShowStatus? = null,
 | 
			
		||||
    override var rating: Int? = null,
 | 
			
		||||
    override var tags: List<String>? = null,
 | 
			
		||||
    override var duration: Int? = null,
 | 
			
		||||
| 
						 | 
				
			
			@ -1232,7 +1244,8 @@ data class TvSeriesLoadResponse(
 | 
			
		|||
    override var comingSoon: Boolean = false,
 | 
			
		||||
    override var syncData: MutableMap<String, String> = mutableMapOf(),
 | 
			
		||||
    override var posterHeaders: Map<String, String>? = null,
 | 
			
		||||
) : LoadResponse
 | 
			
		||||
    override var nextAiring: NextAiring? = null,
 | 
			
		||||
) : LoadResponse, EpisodeResponse
 | 
			
		||||
 | 
			
		||||
suspend fun MainAPI.newTvSeriesLoadResponse(
 | 
			
		||||
    name: String,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,9 @@ class DoodCxExtractor : DoodLaExtractor() {
 | 
			
		|||
class DoodShExtractor : DoodLaExtractor() {
 | 
			
		||||
    override var mainUrl = "https://dood.sh"
 | 
			
		||||
}
 | 
			
		||||
class DoodWatchExtractor : DoodLaExtractor() {
 | 
			
		||||
    override var mainUrl = "https://dood.watch"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DoodPmExtractor : DoodLaExtractor() {
 | 
			
		||||
    override var mainUrl = "https://dood.pm"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,9 @@ class MixDropBz : MixDrop(){
 | 
			
		|||
class MixDropCh : MixDrop(){
 | 
			
		||||
    override var mainUrl = "https://mixdrop.ch"
 | 
			
		||||
}
 | 
			
		||||
class MixDropTo : MixDrop(){
 | 
			
		||||
    override var mainUrl = "https://mixdrop.to"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
open class MixDrop : ExtractorApi() {
 | 
			
		||||
    override var name = "MixDrop"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,7 +114,7 @@ class AltadefinizioneProvider : MainAPI() {
 | 
			
		|||
 | 
			
		||||
        val tags: List<String> = document.select("#details > li:nth-child(1) > a").map { it.text() }
 | 
			
		||||
 | 
			
		||||
        val trailerurl = document.selectFirst("#showtrailer > div > div > iframe")!!.attr("src")
 | 
			
		||||
        val trailerurl = document.selectFirst("#showtrailer > div > div > iframe")?.attr("src")
 | 
			
		||||
 | 
			
		||||
        return newMovieLoadResponse(
 | 
			
		||||
                title,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.mvvm.logError
 | 
			
		|||
import com.lagradost.cloudstream3.utils.ExtractorLink
 | 
			
		||||
import com.lagradost.cloudstream3.utils.loadExtractor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CineblogProvider : MainAPI() {
 | 
			
		||||
    override var lang = "it"
 | 
			
		||||
    override var mainUrl = "https://cb01.rip"
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +29,7 @@ class CineblogProvider : MainAPI() {
 | 
			
		|||
                val home = soup.select("article.item.movies").map {
 | 
			
		||||
                    val title = it.selectFirst("div.data > h3 > a")!!.text().substringBefore("(")
 | 
			
		||||
                    val link = it.selectFirst("div.poster > a")!!.attr("href")
 | 
			
		||||
                    val quality = getQualityFromString(it.selectFirst("span.quality")?.text())
 | 
			
		||||
                    TvSeriesSearchResponse(
 | 
			
		||||
                        title,
 | 
			
		||||
                        link,
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +38,7 @@ class CineblogProvider : MainAPI() {
 | 
			
		|||
                        it.selectFirst("img")!!.attr("src"),
 | 
			
		||||
                        null,
 | 
			
		||||
                        null,
 | 
			
		||||
                        quality = quality
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -82,8 +85,7 @@ class CineblogProvider : MainAPI() {
 | 
			
		|||
                href,
 | 
			
		||||
                this.name,
 | 
			
		||||
                TvType.Movie,
 | 
			
		||||
                poster,
 | 
			
		||||
                null
 | 
			
		||||
                poster
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +98,6 @@ class CineblogProvider : MainAPI() {
 | 
			
		|||
        val title = document.selectFirst("div.data > h1")!!.text().substringBefore("(")
 | 
			
		||||
        val description = document.select("#info > div.wp-content > p").html().toString()
 | 
			
		||||
        val rating = null
 | 
			
		||||
 | 
			
		||||
        var year = document.selectFirst(" div.data > div.extra > span.date")!!.text().substringAfter(",")
 | 
			
		||||
            .filter { it.isDigit() }
 | 
			
		||||
        if (year.length > 4) {
 | 
			
		||||
| 
						 | 
				
			
			@ -114,8 +115,7 @@ class CineblogProvider : MainAPI() {
 | 
			
		|||
                href,
 | 
			
		||||
                this.name,
 | 
			
		||||
                TvType.Movie,
 | 
			
		||||
                posterUrl,
 | 
			
		||||
                null
 | 
			
		||||
                posterUrl
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,240 @@
 | 
			
		|||
package com.lagradost.cloudstream3.movieproviders
 | 
			
		||||
import androidx.core.text.parseAsHtml
 | 
			
		||||
import com.lagradost.cloudstream3.*
 | 
			
		||||
import com.lagradost.cloudstream3.mvvm.logError
 | 
			
		||||
import com.lagradost.cloudstream3.app
 | 
			
		||||
import com.lagradost.cloudstream3.utils.*
 | 
			
		||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
 | 
			
		||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addRating
 | 
			
		||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
 | 
			
		||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
 | 
			
		||||
import com.lagradost.nicehttp.NiceResponse
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FilmpertuttiProvider : MainAPI() {
 | 
			
		||||
    override var lang = "it"
 | 
			
		||||
    override var mainUrl = "https://www.filmpertutti.buzz"
 | 
			
		||||
    override var name = "Filmpertutti"
 | 
			
		||||
    override val hasMainPage = true
 | 
			
		||||
    override val hasChromecastSupport = true
 | 
			
		||||
    override val supportedTypes = setOf(
 | 
			
		||||
        TvType.Movie,
 | 
			
		||||
        TvType.TvSeries
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    override suspend fun getMainPage(): HomePageResponse {
 | 
			
		||||
        val items = ArrayList<HomePageList>()
 | 
			
		||||
        val urls = listOf(
 | 
			
		||||
            Pair("$mainUrl/category/serie-tv/", "Serie Tv"),
 | 
			
		||||
            Pair("$mainUrl/category/film/azione/", "Azione"),
 | 
			
		||||
            Pair("$mainUrl/category/film/avventura/", "Avventura"),
 | 
			
		||||
        )
 | 
			
		||||
        for ((url, name) in urls) {
 | 
			
		||||
            try {
 | 
			
		||||
                val soup = app.get(url).document
 | 
			
		||||
                val home = soup.select("ul.posts > li").map {
 | 
			
		||||
                    val title = it.selectFirst("div.title")!!.text().substringBeforeLast("(").substringBeforeLast("[")
 | 
			
		||||
                    val link = it.selectFirst("a")!!.attr("href")
 | 
			
		||||
                    val image = it.selectFirst("a")!!.attr("data-thumbnail")
 | 
			
		||||
                    val qualitydata = it.selectFirst("div.hd")
 | 
			
		||||
                    val quality = if (qualitydata!= null) {
 | 
			
		||||
                        getQualityFromString(qualitydata?.text())
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        null
 | 
			
		||||
                    }
 | 
			
		||||
                    newTvSeriesSearchResponse(
 | 
			
		||||
                        title,
 | 
			
		||||
                        link) {
 | 
			
		||||
                        this.posterUrl = image
 | 
			
		||||
                        this.quality = quality
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                items.add(HomePageList(name, home))
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                logError(e)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (items.size <= 0) throw ErrorLoadingException()
 | 
			
		||||
        return HomePageResponse(items)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun search(query: String): List<SearchResponse> {
 | 
			
		||||
        val queryformatted = query.replace(" ", "+")
 | 
			
		||||
        val url = "$mainUrl/?s=$queryformatted"
 | 
			
		||||
        val doc = app.get(url).document
 | 
			
		||||
        return doc.select("ul.posts > li").map {
 | 
			
		||||
            val title = it.selectFirst("div.title")!!.text().substringBeforeLast("(").substringBeforeLast("[")
 | 
			
		||||
            val link = it.selectFirst("a")!!.attr("href")
 | 
			
		||||
            val image = it.selectFirst("a")!!.attr("data-thumbnail")
 | 
			
		||||
            val quality = getQualityFromString(it.selectFirst("div.hd")?.text())
 | 
			
		||||
 | 
			
		||||
            MovieSearchResponse(
 | 
			
		||||
                title,
 | 
			
		||||
                link,
 | 
			
		||||
                this.name,
 | 
			
		||||
                quality = quality,
 | 
			
		||||
                posterUrl = image
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun load(url: String): LoadResponse {
 | 
			
		||||
        val document = app.get(url).document
 | 
			
		||||
        val type =
 | 
			
		||||
            if (document.selectFirst("a.taxonomy.category")!!.attr("href").contains("serie-tv")
 | 
			
		||||
                    .not()
 | 
			
		||||
            ) TvType.Movie else TvType.TvSeries
 | 
			
		||||
        val title = document.selectFirst("#content > h1")!!.text().substringBeforeLast("(")
 | 
			
		||||
            .substringBeforeLast("[")
 | 
			
		||||
 | 
			
		||||
        val description = document.selectFirst("i.fa.fa-file-text-o.fa-fw")?.parent()?.nextSibling()?.toString()?.parseAsHtml().toString()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        val rating = document.selectFirst("div.rating > div.value")?.text()
 | 
			
		||||
 | 
			
		||||
        val year =
 | 
			
		||||
            document.selectFirst("#content > h1")?.text()?.substringAfterLast("(")?.filter { it.isDigit() }?.toIntOrNull() ?:
 | 
			
		||||
            description.substringAfter("trasmessa nel").take(6).filter { it.isDigit() }.toIntOrNull() ?:
 | 
			
		||||
            (document.selectFirst("i.fa.fa-calendar.fa-fw")?.parent()?.nextSibling() as Element?)?.text()?.substringAfterLast(" ")?.filter { it.isDigit() }?.toIntOrNull()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        val poster = document.selectFirst("div.meta > div > img")?.attr("data-src")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        val trailerurl = document.selectFirst("div.youtube-player")?.attr("data-id")?.let{ urldata->
 | 
			
		||||
            "https://www.youtube.com/watch?v=$urldata"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (type == TvType.TvSeries) {
 | 
			
		||||
 | 
			
		||||
            val episodeList = ArrayList<Episode>()
 | 
			
		||||
            document.select("div.accordion-item").filter{it.selectFirst("#season > ul > li.s_title > span")!!.text().isNotEmpty()}.map { element ->
 | 
			
		||||
                val season =
 | 
			
		||||
                    element.selectFirst("#season > ul > li.s_title > span")!!.text().toInt()
 | 
			
		||||
                element.select("div.episode-wrap").map { episode ->
 | 
			
		||||
                    val href =
 | 
			
		||||
                        episode.select("#links > div > div > table > tbody:nth-child(2) > tr")
 | 
			
		||||
                            .map { it.selectFirst("a")!!.attr("href") }.toJson()
 | 
			
		||||
                    val epNum = episode.selectFirst("li.season-no")!!.text().substringAfter("x")
 | 
			
		||||
                        .filter { it.isDigit() }.toIntOrNull()
 | 
			
		||||
                    val epTitle = episode.selectFirst("li.other_link > a")?.text()
 | 
			
		||||
 | 
			
		||||
                    val posterUrl = episode.selectFirst("figure > img")?.attr("data-src")
 | 
			
		||||
                    episodeList.add(
 | 
			
		||||
                        Episode(
 | 
			
		||||
                            href,
 | 
			
		||||
                            epTitle,
 | 
			
		||||
                            season,
 | 
			
		||||
                            epNum,
 | 
			
		||||
                            posterUrl,
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return newTvSeriesLoadResponse(
 | 
			
		||||
                title,
 | 
			
		||||
                url, type, episodeList
 | 
			
		||||
            ) {
 | 
			
		||||
                this.posterUrl = poster
 | 
			
		||||
                this.year = year
 | 
			
		||||
                this.plot = description
 | 
			
		||||
                addRating(rating)
 | 
			
		||||
                addTrailer(trailerurl)
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            val urls0 = document.select("div.embed-player")
 | 
			
		||||
            val urls = if (urls0.isNotEmpty()){
 | 
			
		||||
                    urls0.map { it.attr("data-id") }.toJson()
 | 
			
		||||
                }
 | 
			
		||||
            else{ document.select("#info > ul > li ").mapNotNull { it.selectFirst("a")?.attr("href") }.toJson() }
 | 
			
		||||
 | 
			
		||||
            return newMovieLoadResponse(
 | 
			
		||||
                title,
 | 
			
		||||
                url,
 | 
			
		||||
                type,
 | 
			
		||||
                urls
 | 
			
		||||
            ) {
 | 
			
		||||
                posterUrl = fixUrlNull(poster)
 | 
			
		||||
                this.year = year
 | 
			
		||||
                this.plot = description
 | 
			
		||||
                addRating(rating)
 | 
			
		||||
                addTrailer(trailerurl)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
// to be updated when UnshortenUrl is ready
 | 
			
		||||
    suspend fun unshorten_linkup(uri: String): String {
 | 
			
		||||
        var r: NiceResponse? = null
 | 
			
		||||
        var uri = uri
 | 
			
		||||
        when{
 | 
			
		||||
            uri.contains("/tv/") -> uri = uri.replace("/tv/", "/tva/")
 | 
			
		||||
            uri.contains("delta") -> uri = uri.replace("/delta/", "/adelta/")
 | 
			
		||||
            (uri.contains("/ga/") || uri.contains("/ga2/")) -> uri = base64Decode(uri.split('/').last()).trim()
 | 
			
		||||
            uri.contains("/speedx/") -> uri = uri.replace("http://linkup.pro/speedx", "http://speedvideo.net")
 | 
			
		||||
            else -> {
 | 
			
		||||
                r = app.get(uri, allowRedirects = true)
 | 
			
		||||
                uri = r.url
 | 
			
		||||
                val link =
 | 
			
		||||
                    Regex("<iframe[^<>]*src=\\'([^'>]*)\\'[^<>]*>").find(r.text)?.value ?:
 | 
			
		||||
                    Regex("""action="(?:[^/]+.*?/[^/]+/([a-zA-Z0-9_]+))">""").find(r.text)?.value ?:
 | 
			
		||||
                    Regex("""href","((.|\\n)*?)"""").findAll(r.text).elementAtOrNull(1)?.groupValues?.get(1)
 | 
			
		||||
 | 
			
		||||
                if (link!=null) {
 | 
			
		||||
                    uri = link
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val short = Regex("""^https?://.*?(https?://.*)""").find(uri)?.value
 | 
			
		||||
        if (short!=null){
 | 
			
		||||
            uri = short
 | 
			
		||||
        }
 | 
			
		||||
        if (r==null){
 | 
			
		||||
            r = app.get(
 | 
			
		||||
                uri,
 | 
			
		||||
                allowRedirects = false)
 | 
			
		||||
            if (r.headers["location"]!= null){
 | 
			
		||||
                uri = r.headers["location"].toString()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (uri.contains("snip.")) {
 | 
			
		||||
            if (uri.contains("out_generator")) {
 | 
			
		||||
                uri = Regex("url=(.*)\$").find(uri)!!.value
 | 
			
		||||
            }
 | 
			
		||||
            else if (uri.contains("/decode/")) {
 | 
			
		||||
                uri = app.get(uri, allowRedirects = true).url
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return uri
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override suspend fun loadLinks(
 | 
			
		||||
        data: String,
 | 
			
		||||
        isCasting: Boolean,
 | 
			
		||||
        subtitleCallback: (SubtitleFile) -> Unit,
 | 
			
		||||
        callback: (ExtractorLink) -> Unit
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
        tryParseJson<List<String>>(data)?.forEach { id ->
 | 
			
		||||
            if (id.contains("buckler")){
 | 
			
		||||
                val id2 = unshorten_linkup(id).trim().replace("/v/","/e/").replace("/f/","/e/")
 | 
			
		||||
                loadExtractor(id2, data, callback)
 | 
			
		||||
            }
 | 
			
		||||
            else if (id.contains("isecure")){
 | 
			
		||||
                val doc1 = app.get(id).document
 | 
			
		||||
                val id2 = doc1.selectFirst("iframe")!!.attr("src")
 | 
			
		||||
                loadExtractor(id2, data, callback)
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                loadExtractor(id, data, callback)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
 | 
			
		|||
 | 
			
		||||
class TantifilmProvider : MainAPI() {
 | 
			
		||||
    override var lang = "it"
 | 
			
		||||
    override var mainUrl = "https://www.tantifilm.rodeo"
 | 
			
		||||
    override var mainUrl = "https://www.tantifilm.pics"
 | 
			
		||||
    override var name = "Tantifilm"
 | 
			
		||||
    override val hasMainPage = true
 | 
			
		||||
    override val hasChromecastSupport = true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,11 +36,6 @@ interface SyncAPI : OAuth2API {
 | 
			
		|||
        override var id: Int? = null,
 | 
			
		||||
    ) : SearchResponse
 | 
			
		||||
 | 
			
		||||
    data class SyncNextAiring(
 | 
			
		||||
        val episode: Int,
 | 
			
		||||
        val unixTime: Long,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class SyncStatus(
 | 
			
		||||
        val status: Int,
 | 
			
		||||
        /** 1-10 */
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +58,7 @@ interface SyncAPI : OAuth2API {
 | 
			
		|||
        var duration: Int? = null,
 | 
			
		||||
        var synopsis: String? = null,
 | 
			
		||||
        var airStatus: ShowStatus? = null,
 | 
			
		||||
        var nextAiring: SyncNextAiring? = null,
 | 
			
		||||
        var nextAiring: NextAiring? = null,
 | 
			
		||||
        var studio: List<String>? = null,
 | 
			
		||||
        var genres: List<String>? = null,
 | 
			
		||||
        var synonyms: List<String>? = null,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,7 +98,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
 | 
			
		|||
        return SyncAPI.SyncResult(
 | 
			
		||||
            season.id.toString(),
 | 
			
		||||
            nextAiring = season.nextAiringEpisode?.let {
 | 
			
		||||
                SyncAPI.SyncNextAiring(
 | 
			
		||||
                NextAiring(
 | 
			
		||||
                    it.episode ?: return@let null,
 | 
			
		||||
                    (it.timeUntilAiring ?: return@let null) + unixTime
 | 
			
		||||
                )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -300,14 +300,14 @@ class CS3IPlayer : IPlayer {
 | 
			
		|||
 | 
			
		||||
        saveData()
 | 
			
		||||
        exoPlayer?.pause()
 | 
			
		||||
        releasePlayer()
 | 
			
		||||
        //releasePlayer()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPause() {
 | 
			
		||||
        Log.i(TAG, "onPause")
 | 
			
		||||
        saveData()
 | 
			
		||||
        exoPlayer?.pause()
 | 
			
		||||
        releasePlayer()
 | 
			
		||||
        //releasePlayer()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onResume(context: Context) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,6 +88,7 @@ const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15        // in both directions
 | 
			
		|||
open class FullScreenPlayer : AbstractPlayerFragment() {
 | 
			
		||||
    protected open var lockRotation = true
 | 
			
		||||
    protected open var isFullScreenPlayer = true
 | 
			
		||||
    protected open var isTv = false
 | 
			
		||||
 | 
			
		||||
    // state of player UI
 | 
			
		||||
    protected var isShowing = false
 | 
			
		||||
| 
						 | 
				
			
			@ -1055,7 +1056,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
 | 
			
		|||
 | 
			
		||||
                    // netflix capture back and hide ~monke
 | 
			
		||||
                    KeyEvent.KEYCODE_BACK -> {
 | 
			
		||||
                        if (isShowing) {
 | 
			
		||||
                        if (isShowing && isTv) {
 | 
			
		||||
                            onClickChange()
 | 
			
		||||
                            return true
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1257,6 +1258,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
 | 
			
		|||
        player_intro_play?.setOnClickListener {
 | 
			
		||||
            player_intro_play?.isGone = true
 | 
			
		||||
            player.handleEvent(CSPlayerEvent.Play)
 | 
			
		||||
            updateUIVisibility()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // it is !not! a bug that you cant touch the right side, it does not register inputs on navbar or status bar
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -914,8 +914,9 @@ class GeneratorPlayer : FullScreenPlayer() {
 | 
			
		|||
        savedInstanceState: Bundle?
 | 
			
		||||
    ): View? {
 | 
			
		||||
        // this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
 | 
			
		||||
        isTv = context?.isTvSettings() == true
 | 
			
		||||
        layout =
 | 
			
		||||
            if (context?.isTvSettings() == true) R.layout.fragment_player_tv else R.layout.fragment_player
 | 
			
		||||
            if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player
 | 
			
		||||
 | 
			
		||||
        viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
 | 
			
		||||
        sync = ViewModelProvider(this)[SyncViewModel::class.java]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,7 @@ import com.google.android.material.button.MaterialButton
 | 
			
		|||
import com.lagradost.cloudstream3.*
 | 
			
		||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
 | 
			
		||||
import com.lagradost.cloudstream3.APIHolder.getId
 | 
			
		||||
import com.lagradost.cloudstream3.APIHolder.unixTime
 | 
			
		||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
 | 
			
		||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
 | 
			
		||||
import com.lagradost.cloudstream3.CommonActivity.getCastSession
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +101,8 @@ import kotlinx.coroutines.Job
 | 
			
		|||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const val MAX_SYNO_LENGH = 1000
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -654,6 +657,55 @@ class ResultFragment : ResultTrailerPlayer() {
 | 
			
		|||
        loadTrailer()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setNextEpisode(nextAiring: NextAiring?) {
 | 
			
		||||
        result_next_airing_holder?.isVisible =
 | 
			
		||||
            if (nextAiring == null || nextAiring.episode <= 0 || nextAiring.unixTime <= unixTime) {
 | 
			
		||||
                false
 | 
			
		||||
            } else {
 | 
			
		||||
                val seconds = nextAiring.unixTime - unixTime
 | 
			
		||||
                val days = TimeUnit.SECONDS.toDays(seconds)
 | 
			
		||||
                val hours: Long = TimeUnit.SECONDS.toHours(seconds) - days * 24
 | 
			
		||||
                val minute =
 | 
			
		||||
                    TimeUnit.SECONDS.toMinutes(seconds) - TimeUnit.SECONDS.toHours(seconds) * 60
 | 
			
		||||
                // val second =
 | 
			
		||||
                //    TimeUnit.SECONDS.toSeconds(seconds) - TimeUnit.SECONDS.toMinutes(seconds) * 60
 | 
			
		||||
                try {
 | 
			
		||||
                    val ctx = context
 | 
			
		||||
                    if (ctx == null) {
 | 
			
		||||
                        false
 | 
			
		||||
                    } else {
 | 
			
		||||
                        when {
 | 
			
		||||
                            days > 0 -> {
 | 
			
		||||
                                ctx.getString(R.string.next_episode_time_day_format).format(
 | 
			
		||||
                                    days,
 | 
			
		||||
                                    hours,
 | 
			
		||||
                                    minute
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                            hours > 0 -> ctx.getString(R.string.next_episode_time_hour_format)
 | 
			
		||||
                                .format(
 | 
			
		||||
                                    hours,
 | 
			
		||||
                                    minute
 | 
			
		||||
                                )
 | 
			
		||||
                            minute > 0 -> ctx.getString(R.string.next_episode_time_min_format)
 | 
			
		||||
                                .format(
 | 
			
		||||
                                    minute
 | 
			
		||||
                                )
 | 
			
		||||
                            else -> null
 | 
			
		||||
                        }?.also { text ->
 | 
			
		||||
                            result_next_airing_time?.text = text
 | 
			
		||||
                            result_next_airing?.text =
 | 
			
		||||
                                ctx.getString(R.string.next_episode_format).format(nextAiring.episode)
 | 
			
		||||
                        } != null
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (e: Exception) { // mistranslation
 | 
			
		||||
                    result_next_airing_holder?.isVisible = false
 | 
			
		||||
                    logError(e)
 | 
			
		||||
                    false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setActors(actors: List<ActorData>?) {
 | 
			
		||||
        if (actors.isNullOrEmpty()) {
 | 
			
		||||
            result_cast_text?.isVisible = false
 | 
			
		||||
| 
						 | 
				
			
			@ -1801,7 +1853,7 @@ class ResultFragment : ResultTrailerPlayer() {
 | 
			
		|||
                    setRating(d.rating)
 | 
			
		||||
                    setRecommendations(d.recommendations, null)
 | 
			
		||||
                    setActors(d.actors)
 | 
			
		||||
 | 
			
		||||
                    setNextEpisode(if (d is EpisodeResponse) d.nextAiring else null)
 | 
			
		||||
                    setTrailers(d.trailers)
 | 
			
		||||
 | 
			
		||||
                    if (syncModel.addSyncs(d.syncData)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,7 +85,6 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
 | 
			
		|||
        isFullScreenPlayer = fullscreen
 | 
			
		||||
        lockRotation = fullscreen
 | 
			
		||||
        player_fullscreen?.setImageResource(if (fullscreen) R.drawable.baseline_fullscreen_exit_24 else R.drawable.baseline_fullscreen_24)
 | 
			
		||||
        uiReset()
 | 
			
		||||
        if (fullscreen) {
 | 
			
		||||
            enterFullscreen()
 | 
			
		||||
            result_top_bar?.isVisible = false
 | 
			
		||||
| 
						 | 
				
			
			@ -106,6 +105,7 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
 | 
			
		|||
            exitFullscreen()
 | 
			
		||||
        }
 | 
			
		||||
        fixPlayerSize()
 | 
			
		||||
        uiReset()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ import androidx.lifecycle.LiveData
 | 
			
		|||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.lifecycle.viewModelScope
 | 
			
		||||
import androidx.preference.PreferenceManager
 | 
			
		||||
import com.lagradost.cloudstream3.*
 | 
			
		||||
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
 | 
			
		||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +28,6 @@ import com.lagradost.cloudstream3.ui.WatchType
 | 
			
		|||
import com.lagradost.cloudstream3.ui.player.IGenerator
 | 
			
		||||
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
 | 
			
		||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
 | 
			
		||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
 | 
			
		||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
 | 
			
		||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
 | 
			
		||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +142,10 @@ class ResultViewModel : ViewModel() {
 | 
			
		|||
            posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
 | 
			
		||||
            actors = actors ?: meta.actors
 | 
			
		||||
 | 
			
		||||
            if (this is EpisodeResponse) {
 | 
			
		||||
                nextAiring = nextAiring ?: meta.nextAiring
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for ((k, v) in syncs ?: emptyMap()) {
 | 
			
		||||
                syncData[k] = v
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -162,7 +164,6 @@ class ResultViewModel : ViewModel() {
 | 
			
		|||
            argamap({
 | 
			
		||||
                addTrailer(meta.trailers)
 | 
			
		||||
            }, {
 | 
			
		||||
 | 
			
		||||
                if (this !is AnimeLoadResponse) return@argamap
 | 
			
		||||
                val map = getEpisodesDetails(getMalId(), getAniListId(), isResponseRequired = false)
 | 
			
		||||
                if (map.isNullOrEmpty()) return@argamap
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -140,6 +140,8 @@ val extractorApis: Array<ExtractorApi> = arrayOf(
 | 
			
		|||
    //mixdrop extractors
 | 
			
		||||
    MixDropBz(),
 | 
			
		||||
    MixDropCh(),
 | 
			
		||||
    MixDropTo(),
 | 
			
		||||
 | 
			
		||||
    MixDrop(),
 | 
			
		||||
 | 
			
		||||
    Mcloud(),
 | 
			
		||||
| 
						 | 
				
			
			@ -186,6 +188,7 @@ val extractorApis: Array<ExtractorApi> = arrayOf(
 | 
			
		|||
    DoodLaExtractor(),
 | 
			
		||||
    DoodWsExtractor(),
 | 
			
		||||
    DoodShExtractor(),
 | 
			
		||||
    DoodWatchExtractor(),
 | 
			
		||||
 | 
			
		||||
    AsianLoad(),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -139,6 +139,7 @@
 | 
			
		|||
                    android:orientation="vertical">
 | 
			
		||||
 | 
			
		||||
                <com.facebook.shimmer.ShimmerFrameLayout
 | 
			
		||||
                        tools:visibility="gone"
 | 
			
		||||
                        android:visibility="gone"
 | 
			
		||||
                        android:id="@+id/result_trailer_loading"
 | 
			
		||||
                        android:layout_width="match_parent"
 | 
			
		||||
| 
						 | 
				
			
			@ -148,8 +149,7 @@
 | 
			
		|||
                        app:shimmer_auto_start="true"
 | 
			
		||||
                        app:shimmer_base_alpha="0.2"
 | 
			
		||||
                        app:shimmer_duration="@integer/loading_time"
 | 
			
		||||
                        app:shimmer_highlight_alpha="0.3"
 | 
			
		||||
                        tools:visibility="visible">
 | 
			
		||||
                        app:shimmer_highlight_alpha="0.3">
 | 
			
		||||
 | 
			
		||||
                    <LinearLayout
 | 
			
		||||
                            android:layout_width="match_parent"
 | 
			
		||||
| 
						 | 
				
			
			@ -423,6 +423,7 @@
 | 
			
		|||
                            tools:text="Cast: Joe Ligma" />
 | 
			
		||||
 | 
			
		||||
                    <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
                            tools:visibility="gone"
 | 
			
		||||
                            android:nextFocusUp="@id/result_bookmark_button"
 | 
			
		||||
                            android:nextFocusDown="@id/result_play_movie"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -669,6 +670,7 @@
 | 
			
		|||
                                android:visibility="gone"
 | 
			
		||||
                                tools:visibility="visible">
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            <com.google.android.material.button.MaterialButton
 | 
			
		||||
                                    android:id="@+id/result_resume_series_button"
 | 
			
		||||
                                    style="@style/WhiteButton"
 | 
			
		||||
| 
						 | 
				
			
			@ -747,6 +749,7 @@
 | 
			
		|||
                                    tools:text="69m\nremaining" />
 | 
			
		||||
                        </LinearLayout>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        <LinearLayout
 | 
			
		||||
                                android:id="@+id/result_episodes_tab"
 | 
			
		||||
                                android:layout_width="match_parent"
 | 
			
		||||
| 
						 | 
				
			
			@ -765,6 +768,7 @@
 | 
			
		|||
                                        style="@style/MultiSelectButton"
 | 
			
		||||
                                        android:layout_gravity="center_vertical"
 | 
			
		||||
                                        android:layout_marginStart="0dp"
 | 
			
		||||
                                        android:layout_marginEnd="10dp"
 | 
			
		||||
                                        android:nextFocusLeft="@id/result_episode_select"
 | 
			
		||||
                                        android:nextFocusRight="@id/result_episode_select"
 | 
			
		||||
                                        android:nextFocusUp="@id/result_description"
 | 
			
		||||
| 
						 | 
				
			
			@ -779,6 +783,8 @@
 | 
			
		|||
 | 
			
		||||
                                        android:layout_gravity="center_vertical"
 | 
			
		||||
                                        android:layout_marginStart="0dp"
 | 
			
		||||
                                        android:layout_marginEnd="10dp"
 | 
			
		||||
 | 
			
		||||
                                        android:nextFocusLeft="@id/result_season_button"
 | 
			
		||||
                                        android:nextFocusRight="@id/result_season_button"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -794,6 +800,7 @@
 | 
			
		|||
 | 
			
		||||
                                        android:layout_gravity="center_vertical"
 | 
			
		||||
                                        android:layout_marginStart="0dp"
 | 
			
		||||
                                        android:layout_marginEnd="10dp"
 | 
			
		||||
                                        android:nextFocusLeft="@id/result_season_button"
 | 
			
		||||
                                        android:nextFocusRight="@id/result_season_button"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -816,6 +823,37 @@
 | 
			
		|||
                                        tools:text="8 Episodes" />
 | 
			
		||||
                            </LinearLayout>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            <!--TODO add next airing-->
 | 
			
		||||
                            <LinearLayout
 | 
			
		||||
                                    android:id="@+id/result_next_airing_holder"
 | 
			
		||||
                                    android:layout_gravity="start"
 | 
			
		||||
                                    android:paddingBottom="15dp"
 | 
			
		||||
                                    android:orientation="horizontal"
 | 
			
		||||
                                    android:layout_width="wrap_content"
 | 
			
		||||
                                    android:layout_height="wrap_content">
 | 
			
		||||
                                <TextView
 | 
			
		||||
                                        android:gravity="center"
 | 
			
		||||
 | 
			
		||||
                                        android:id="@+id/result_next_airing"
 | 
			
		||||
                                        android:layout_width="match_parent"
 | 
			
		||||
                                        android:layout_height="wrap_content"
 | 
			
		||||
                                        android:textColor="?attr/grayTextColor"
 | 
			
		||||
                                        android:textSize="17sp"
 | 
			
		||||
                                        android:textStyle="normal"
 | 
			
		||||
                                        android:text="Episode 1022 will be released in" />
 | 
			
		||||
                                <TextView
 | 
			
		||||
                                        android:paddingStart="5dp"
 | 
			
		||||
                                        android:gravity="center"
 | 
			
		||||
                                        android:id="@+id/result_next_airing_time"
 | 
			
		||||
                                        android:layout_width="match_parent"
 | 
			
		||||
                                        android:layout_height="wrap_content"
 | 
			
		||||
                                        android:textColor="?attr/textColor"
 | 
			
		||||
                                        android:textSize="17sp"
 | 
			
		||||
                                        android:textStyle="normal"
 | 
			
		||||
                                        tools:text="5d 3h 30m" />
 | 
			
		||||
                            </LinearLayout>
 | 
			
		||||
 | 
			
		||||
                            <com.facebook.shimmer.ShimmerFrameLayout
 | 
			
		||||
                                    android:id="@+id/result_episode_loading"
 | 
			
		||||
                                    android:layout_width="match_parent"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,6 +67,10 @@
 | 
			
		|||
    <string name="year_format" translatable="false" formatted="true">%d</string>
 | 
			
		||||
    <string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
 | 
			
		||||
    <string name="cast_format" formatted="true">Cast: %s</string>
 | 
			
		||||
    <string name="next_episode_format" formatted="true">Episode %d will be released in</string>
 | 
			
		||||
    <string name="next_episode_time_day_format" formatted="true">%dd %dh %dm</string>
 | 
			
		||||
    <string name="next_episode_time_hour_format" formatted="true">%dh %dm</string>
 | 
			
		||||
    <string name="next_episode_time_min_format" formatted="true">%dm</string>
 | 
			
		||||
 | 
			
		||||
    <!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
 | 
			
		||||
    <string name="result_poster_img_des">Poster</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue