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
						e5515b575c
					
				
					 22 changed files with 1010 additions and 437 deletions
				
			
		| 
						 | 
				
			
			@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
 | 
			
		|||
import com.lagradost.cloudstream3.ui.player.SubtitleData
 | 
			
		||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
 | 
			
		||||
import com.lagradost.cloudstream3.utils.ExtractorLink
 | 
			
		||||
import okhttp3.Headers
 | 
			
		||||
import okhttp3.Interceptor
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.*
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +108,7 @@ object APIHolder {
 | 
			
		|||
            MonoschinosProvider(),
 | 
			
		||||
            KawaiifuProvider(), // disabled due to cloudflare
 | 
			
		||||
            //MultiAnimeProvider(),
 | 
			
		||||
	        NginxProvider(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -307,6 +309,7 @@ const val PROVIDER_STATUS_DOWN = 0
 | 
			
		|||
data class ProvidersInfoJson(
 | 
			
		||||
    @JsonProperty("name") var name: String,
 | 
			
		||||
    @JsonProperty("url") var url: String,
 | 
			
		||||
    @JsonProperty("credentials") var credentials: String? = null,
 | 
			
		||||
    @JsonProperty("status") var status: Int,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -319,6 +322,7 @@ abstract class MainAPI {
 | 
			
		|||
    fun overrideWithNewData(data: ProvidersInfoJson) {
 | 
			
		||||
        this.name = data.name
 | 
			
		||||
        this.mainUrl = data.url
 | 
			
		||||
	    this.storedCredentials = data.credentials
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
| 
						 | 
				
			
			@ -329,6 +333,7 @@ abstract class MainAPI {
 | 
			
		|||
 | 
			
		||||
    open var name = "NONE"
 | 
			
		||||
    open var mainUrl = "NONE"
 | 
			
		||||
    open var storedCredentials: String? = null
 | 
			
		||||
 | 
			
		||||
    //open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -591,17 +596,22 @@ fun getQualityFromString(string: String?): SearchQuality? {
 | 
			
		|||
        "cam" -> SearchQuality.Cam
 | 
			
		||||
        "camrip" -> SearchQuality.CamRip
 | 
			
		||||
        "hdcam" -> SearchQuality.HdCam
 | 
			
		||||
        "hdtc" -> SearchQuality.HdCam
 | 
			
		||||
        "hdts" -> SearchQuality.HdCam
 | 
			
		||||
        "highquality" -> SearchQuality.HQ
 | 
			
		||||
        "hq" -> SearchQuality.HQ
 | 
			
		||||
        "highdefinition" -> SearchQuality.HD
 | 
			
		||||
        "hdrip" -> SearchQuality.HD
 | 
			
		||||
        "hd" -> SearchQuality.HD
 | 
			
		||||
        "hdtv" -> SearchQuality.HD
 | 
			
		||||
        "rip" -> SearchQuality.CamRip
 | 
			
		||||
        "telecine" -> SearchQuality.Telecine
 | 
			
		||||
        "tc" -> SearchQuality.Telecine
 | 
			
		||||
        "telesync" -> SearchQuality.Telesync
 | 
			
		||||
        "ts" -> SearchQuality.Telesync
 | 
			
		||||
        "dvd" -> SearchQuality.DVD
 | 
			
		||||
        "dvdrip" -> SearchQuality.DVD
 | 
			
		||||
        "dvdscr" -> SearchQuality.DVD
 | 
			
		||||
        "blueray" -> SearchQuality.BlueRay
 | 
			
		||||
        "bluray" -> SearchQuality.BlueRay
 | 
			
		||||
        "br" -> SearchQuality.BlueRay
 | 
			
		||||
| 
						 | 
				
			
			@ -613,6 +623,7 @@ fun getQualityFromString(string: String?): SearchQuality? {
 | 
			
		|||
        "wp" -> SearchQuality.WorkPrint
 | 
			
		||||
        "workprint" -> SearchQuality.WorkPrint
 | 
			
		||||
        "webrip" -> SearchQuality.WebRip
 | 
			
		||||
        "webdl" -> SearchQuality.WebRip
 | 
			
		||||
        "web" -> SearchQuality.WebRip
 | 
			
		||||
        "hdr" -> SearchQuality.HDR
 | 
			
		||||
        "sdr" -> SearchQuality.SDR
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,12 +64,14 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
 | 
			
		|||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
 | 
			
		||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
 | 
			
		||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
 | 
			
		||||
import com.lagradost.cloudstream3.movieproviders.NginxProvider
 | 
			
		||||
import kotlinx.android.synthetic.main.activity_main.*
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import java.io.File
 | 
			
		||||
import kotlin.collections.HashMap
 | 
			
		||||
import kotlin.concurrent.thread
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -360,6 +362,57 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
 | 
			
		|||
            e.printStackTrace()
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    fun addNginxToJson(data: java.util.HashMap<String, ProvidersInfoJson>): java.util.HashMap<String, ProvidersInfoJson>? {
 | 
			
		||||
        try {
 | 
			
		||||
            val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
 | 
			
		||||
            val nginxUrl =
 | 
			
		||||
                settingsManager.getString(getString(R.string.nginx_url_key), "nginx_url_key").toString()
 | 
			
		||||
            val nginxCredentials =
 | 
			
		||||
                settingsManager.getString(getString(R.string.nginx_credentials), "nginx_credentials")
 | 
			
		||||
                    .toString()
 | 
			
		||||
            val StoredNginxProvider = NginxProvider()
 | 
			
		||||
            if (nginxUrl == "nginx_url_key" || nginxUrl == "") { // if key is default value, or empty:
 | 
			
		||||
                data[StoredNginxProvider.javaClass.simpleName] = ProvidersInfoJson(
 | 
			
		||||
                    url = nginxUrl,
 | 
			
		||||
                    name = StoredNginxProvider.name,
 | 
			
		||||
                    status = PROVIDER_STATUS_DOWN,  // the provider will not be display
 | 
			
		||||
                    credentials = nginxCredentials
 | 
			
		||||
                )
 | 
			
		||||
            } else {  // valid url
 | 
			
		||||
                data[StoredNginxProvider.javaClass.simpleName] = ProvidersInfoJson(
 | 
			
		||||
                    url = nginxUrl,
 | 
			
		||||
                    name = StoredNginxProvider.name,
 | 
			
		||||
                    status = PROVIDER_STATUS_OK,
 | 
			
		||||
                    credentials = nginxCredentials
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return data
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            logError(e)
 | 
			
		||||
            return data
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fun createNginxJson() : ProvidersInfoJson? { //java.util.HashMap<String, ProvidersInfoJson>
 | 
			
		||||
        return try {
 | 
			
		||||
            val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
 | 
			
		||||
            val nginxUrl = settingsManager.getString(getString(R.string.nginx_url_key), "nginx_url_key").toString()
 | 
			
		||||
            val nginxCredentials = settingsManager.getString(getString(R.string.nginx_credentials), "nginx_credentials").toString()
 | 
			
		||||
            if (nginxUrl == "nginx_url_key" || nginxUrl == "") { // if key is default value or empty:
 | 
			
		||||
                null // don't overwrite anything
 | 
			
		||||
            } else {
 | 
			
		||||
                ProvidersInfoJson(
 | 
			
		||||
                    url = nginxUrl,
 | 
			
		||||
                    name = NginxProvider().name,
 | 
			
		||||
                    status = PROVIDER_STATUS_OK,
 | 
			
		||||
                    credentials = nginxCredentials
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            logError(e)
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        // this pulls the latest data so ppl don't have to update to simply change provider url
 | 
			
		||||
        if (downloadFromGithub) {
 | 
			
		||||
| 
						 | 
				
			
			@ -379,8 +432,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
 | 
			
		|||
                                            tryParseJson<HashMap<String, ProvidersInfoJson>>(txt)
 | 
			
		||||
                                        setKey(PROVIDER_STATUS_KEY, txt)
 | 
			
		||||
                                        MainAPI.overrideData = newCache // update all new providers
 | 
			
		||||
                                        for (api in apis) { // update current providers
 | 
			
		||||
                                            newCache?.get(api.javaClass.simpleName)?.let { data ->
 | 
			
		||||
                                        
 | 
			
		||||
                                        val newUpdatedCache = newCache?.let { addNginxToJson(it) ?: it }
 | 
			
		||||
 | 
			
		||||
					                    for (api in apis) { // update current providers
 | 
			
		||||
                                            newUpdatedCache?.get(api.javaClass.simpleName)?.let { data ->
 | 
			
		||||
                                                api.overrideWithNewData(data)
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
| 
						 | 
				
			
			@ -397,12 +453,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
 | 
			
		|||
                                newCache
 | 
			
		||||
                            }?.let { providersJsonMap ->
 | 
			
		||||
                                MainAPI.overrideData = providersJsonMap
 | 
			
		||||
                                val providersJsonMapUpdated = addNginxToJson(providersJsonMap)?: providersJsonMap // if return null, use unchanged one
 | 
			
		||||
                                val acceptableProviders =
 | 
			
		||||
                                    providersJsonMap.filter { it.value.status == PROVIDER_STATUS_OK || it.value.status == PROVIDER_STATUS_SLOW }
 | 
			
		||||
                                    providersJsonMapUpdated.filter { it.value.status == PROVIDER_STATUS_OK || it.value.status == PROVIDER_STATUS_SLOW }
 | 
			
		||||
                                        .map { it.key }.toSet()
 | 
			
		||||
 | 
			
		||||
                                val restrictedApis =
 | 
			
		||||
                                    if (hasBenene) providersJsonMap.filter { it.value.status == PROVIDER_STATUS_BETA_ONLY }
 | 
			
		||||
                                    if (hasBenene) providersJsonMapUpdated.filter { it.value.status == PROVIDER_STATUS_BETA_ONLY }
 | 
			
		||||
                                        .map { it.key }.toSet() else emptySet()
 | 
			
		||||
 | 
			
		||||
                                apis = allProviders.filter { api ->
 | 
			
		||||
| 
						 | 
				
			
			@ -425,6 +482,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
 | 
			
		|||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            apis = allProviders
 | 
			
		||||
            try {
 | 
			
		||||
                val nginxProviderName = NginxProvider().name
 | 
			
		||||
                val nginxProviderIndex = apis.indexOf(APIHolder.getApiFromName(nginxProviderName))
 | 
			
		||||
                val createdJsonProvider = createNginxJson()
 | 
			
		||||
                if (createdJsonProvider != null) {
 | 
			
		||||
                    apis[nginxProviderIndex].overrideWithNewData(createdJsonProvider) // people will have access to it if they disable metadata check (they are not filtered)
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                logError(e)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        loadThemes(this)
 | 
			
		||||
| 
						 | 
				
			
			@ -619,4 +687,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
 | 
			
		|||
        val output = src?.doMath()
 | 
			
		||||
        println("MASTER OUTPUT = $output")*/
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,14 @@ class VizcloudLive : WcoStream() {
 | 
			
		|||
    override var mainUrl = "https://vizcloud.live"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class VizcloudInfo : WcoStream() {
 | 
			
		||||
    override var mainUrl = "https://vizcloud.info"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MwvnVizcloudInfo : WcoStream() {
 | 
			
		||||
    override var mainUrl = "https://mwvn.vizcloud.info"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
open class WcoStream : ExtractorApi() {
 | 
			
		||||
    override var name = "VidStream" //Cause works for animekisa and wco
 | 
			
		||||
    override var mainUrl = "https://vidstream.pro"
 | 
			
		||||
| 
						 | 
				
			
			@ -103,8 +111,9 @@ open class WcoStream : ExtractorApi() {
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (mainUrl == "https://vidstream.pro" || mainUrl == "https://vidstreamz.online" || mainUrl == "https://vizcloud2.online"
 | 
			
		||||
                    || mainUrl == "https://vizcloud.xyz" || mainUrl == "https://vizcloud.live") {
 | 
			
		||||
                if (it.file.contains("m3u8")) {
 | 
			
		||||
                    || mainUrl == "https://vizcloud.xyz" || mainUrl == "https://vizcloud.live" || mainUrl == "https://vizcloud.info"
 | 
			
		||||
                     || mainUrl == "https://mwvn.vizcloud.info") {
 | 
			
		||||
                 if (it.file.contains("m3u8")) {
 | 
			
		||||
                    hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(it.file.replace("#.mp4",""), null,
 | 
			
		||||
                    headers = mapOf("Referer" to url)), true)
 | 
			
		||||
                        .forEach { stream ->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
 | 
			
		|||
 | 
			
		||||
class EgyBestProvider : MainAPI() {
 | 
			
		||||
    override val lang = "ar"
 | 
			
		||||
    override var mainUrl = "https://egy.best"
 | 
			
		||||
    override var mainUrl = "https://www.egy.best"
 | 
			
		||||
    override var name = "EgyBest"
 | 
			
		||||
    override val usesWebView = false
 | 
			
		||||
    override val hasMainPage = true
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +26,7 @@ class EgyBestProvider : MainAPI() {
 | 
			
		|||
        val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url)
 | 
			
		||||
        val tvType = if (isMovie) TvType.Movie else TvType.TvSeries
 | 
			
		||||
        title = if (year !== null) title else title.split(" (")[0].trim()
 | 
			
		||||
        val quality = select("span.ribbon span").text().replace("-", "")
 | 
			
		||||
        // If you need to differentiate use the url.
 | 
			
		||||
        return MovieSearchResponse(
 | 
			
		||||
            title,
 | 
			
		||||
| 
						 | 
				
			
			@ -35,18 +36,22 @@ class EgyBestProvider : MainAPI() {
 | 
			
		|||
            posterUrl,
 | 
			
		||||
            year,
 | 
			
		||||
            null,
 | 
			
		||||
            quality = getQualityFromString(quality)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun getMainPage(): HomePageResponse {
 | 
			
		||||
        // url, title
 | 
			
		||||
        val doc = app.get(mainUrl).document
 | 
			
		||||
        val pages = doc.select("#mainLoad div.mbox").apmap {
 | 
			
		||||
        val pages = arrayListOf<HomePageList>()
 | 
			
		||||
            doc.select("#mainLoad div.mbox").apmap {
 | 
			
		||||
            val name = it.select(".bdb.pda > strong").text()
 | 
			
		||||
            val list = it.select(".movie").mapNotNull { element ->
 | 
			
		||||
                element.toSearchResponse()
 | 
			
		||||
            if (it.select(".movie").first().attr("href").contains("season-(.....)|ep-(.....)".toRegex())) return@apmap
 | 
			
		||||
            val list = arrayListOf<SearchResponse>()
 | 
			
		||||
            it.select(".movie").map { element ->
 | 
			
		||||
                list.add(element.toSearchResponse()!!)
 | 
			
		||||
            }
 | 
			
		||||
            HomePageList(name, list)
 | 
			
		||||
            pages.add(HomePageList(name, list))
 | 
			
		||||
        }
 | 
			
		||||
        return HomePageResponse(pages)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +77,7 @@ class EgyBestProvider : MainAPI() {
 | 
			
		|||
        val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url)
 | 
			
		||||
        val posterUrl = doc.select("div.movie_img a img")?.attr("src")
 | 
			
		||||
        val year = doc.select("div.movie_title h1 a")?.text()?.toIntOrNull()
 | 
			
		||||
        val title = doc.select("div.movie_title h1 span[itemprop=\"name\"]").text()
 | 
			
		||||
        val title = doc.select("div.movie_title h1 span").text()
 | 
			
		||||
 | 
			
		||||
        val synopsis = doc.select("div.mbox").firstOrNull {
 | 
			
		||||
            it.text().contains("القصة")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,268 @@
 | 
			
		|||
package com.lagradost.cloudstream3.movieproviders
 | 
			
		||||
 | 
			
		||||
import com.lagradost.cloudstream3.*
 | 
			
		||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addRating
 | 
			
		||||
import com.lagradost.cloudstream3.utils.ExtractorLink
 | 
			
		||||
import com.lagradost.cloudstream3.TvType
 | 
			
		||||
import com.lagradost.cloudstream3.app
 | 
			
		||||
import com.lagradost.cloudstream3.utils.Qualities
 | 
			
		||||
import java.lang.Exception
 | 
			
		||||
 | 
			
		||||
class NginxProvider : MainAPI() {
 | 
			
		||||
    override var name = "Nginx"
 | 
			
		||||
    override val hasQuickSearch = false
 | 
			
		||||
    override val hasMainPage = true
 | 
			
		||||
    override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    fun getAuthHeader(storedCredentials: String?): Map<String, String> {
 | 
			
		||||
        if (storedCredentials == null) {
 | 
			
		||||
            return mapOf(Pair("Authorization", "Basic "))  // no Authorization headers
 | 
			
		||||
        }
 | 
			
		||||
        val basicAuthToken = base64Encode(storedCredentials.toByteArray())  // will this be loaded when not using the provider ??? can increase load
 | 
			
		||||
        return mapOf(Pair("Authorization", "Basic $basicAuthToken"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun load(url: String): LoadResponse {
 | 
			
		||||
        val authHeader = getAuthHeader(storedCredentials)  // call again because it isn't reloaded if in main class and storedCredentials loads after
 | 
			
		||||
        // url can be tvshow.nfo for series or mediaRootUrl for movies
 | 
			
		||||
 | 
			
		||||
        val mediaRootDocument = app.get(url, authHeader).document
 | 
			
		||||
 | 
			
		||||
        val nfoUrl = url + mediaRootDocument.getElementsByAttributeValueContaining("href", ".nfo").attr("href")  // metadata url file
 | 
			
		||||
 | 
			
		||||
        val metadataDocument = app.get(nfoUrl, authHeader).document  // get the metadata nfo file
 | 
			
		||||
 | 
			
		||||
        val isMovie = !nfoUrl.contains("tvshow.nfo")
 | 
			
		||||
 | 
			
		||||
        val title = metadataDocument.selectFirst("title").text()
 | 
			
		||||
 | 
			
		||||
        val description = metadataDocument.selectFirst("plot").text()
 | 
			
		||||
 | 
			
		||||
        if (isMovie) {
 | 
			
		||||
            val poster = metadataDocument.selectFirst("thumb").text()
 | 
			
		||||
            val trailer = metadataDocument.select("trailer")?.mapNotNull {
 | 
			
		||||
               it?.text()?.replace(
 | 
			
		||||
                   "plugin://plugin.video.youtube/play/?video_id=",
 | 
			
		||||
                   "https://www.youtube.com/watch?v="
 | 
			
		||||
               )
 | 
			
		||||
            }
 | 
			
		||||
            val partialUrl = mediaRootDocument.getElementsByAttributeValueContaining("href", ".nfo").attr("href").replace(".nfo", ".")
 | 
			
		||||
            val date = metadataDocument.selectFirst("year")?.text()?.toIntOrNull()
 | 
			
		||||
            val ratingAverage = metadataDocument.selectFirst("value")?.text()?.toIntOrNull()
 | 
			
		||||
            val tagsList = metadataDocument.select("genre")
 | 
			
		||||
                ?.mapNotNull {   // all the tags like action, thriller ...
 | 
			
		||||
                    it?.text()
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            val dataList = mediaRootDocument.getElementsByAttributeValueContaining(  // list of all urls of the webpage
 | 
			
		||||
                "href",
 | 
			
		||||
                partialUrl
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            val data = url + dataList.firstNotNullOf { item -> item.takeIf { (!it.attr("href").contains(".nfo") &&  !it.attr("href").contains(".jpg"))} }.attr("href").toString()  // exclude poster and nfo (metadata) file
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            return MovieLoadResponse(
 | 
			
		||||
                title,
 | 
			
		||||
                data,
 | 
			
		||||
                this.name,
 | 
			
		||||
                TvType.Movie,
 | 
			
		||||
                data,
 | 
			
		||||
                poster,
 | 
			
		||||
                date,
 | 
			
		||||
                description,
 | 
			
		||||
                ratingAverage,
 | 
			
		||||
                tagsList,
 | 
			
		||||
                null,
 | 
			
		||||
                trailer,
 | 
			
		||||
                null,
 | 
			
		||||
                null,
 | 
			
		||||
            )
 | 
			
		||||
        } else  // a tv serie
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            val list = ArrayList<Pair<Int, String>>()
 | 
			
		||||
            val mediaRootUrl = url.replace("tvshow.nfo", "")
 | 
			
		||||
            val posterUrl = mediaRootUrl + "poster.jpg"
 | 
			
		||||
            val mediaRootDocument = app.get(mediaRootUrl, authHeader).document
 | 
			
		||||
            val seasons =
 | 
			
		||||
                mediaRootDocument.getElementsByAttributeValueContaining("href", "Season%20")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            val tagsList = metadataDocument.select("genre")
 | 
			
		||||
                ?.mapNotNull {   // all the tags like action, thriller ...; unused variable
 | 
			
		||||
                    it?.text()
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            //val actorsList = document.select("actor")
 | 
			
		||||
            //    ?.mapNotNull {   // all the tags like action, thriller ...; unused variable
 | 
			
		||||
            //        it?.text()
 | 
			
		||||
            //    }
 | 
			
		||||
 | 
			
		||||
            seasons.forEach { element ->
 | 
			
		||||
                val season =
 | 
			
		||||
                    element.attr("href")?.replace("Season%20", "")?.replace("/", "")?.toIntOrNull()
 | 
			
		||||
                val href = mediaRootUrl + element.attr("href")
 | 
			
		||||
                if (season != null && season > 0 && href.isNotBlank()) {
 | 
			
		||||
                    list.add(Pair(season, href))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (list.isEmpty()) throw ErrorLoadingException("No Seasons Found")
 | 
			
		||||
 | 
			
		||||
            val episodeList = ArrayList<Episode>()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            list.apmap { (seasonInt, seasonString) ->
 | 
			
		||||
                val seasonDocument = app.get(seasonString, authHeader).document
 | 
			
		||||
                val episodes = seasonDocument.getElementsByAttributeValueContaining(
 | 
			
		||||
                    "href",
 | 
			
		||||
                    ".nfo"
 | 
			
		||||
                ) // get metadata
 | 
			
		||||
                    episodes.forEach { episode ->
 | 
			
		||||
                        val nfoDocument = app.get(seasonString + episode.attr("href"), authHeader).document // get episode metadata file
 | 
			
		||||
                        val epNum = nfoDocument.selectFirst("episode")?.text()?.toIntOrNull()
 | 
			
		||||
                        val poster =
 | 
			
		||||
                            seasonString + episode.attr("href").replace(".nfo", "-thumb.jpg")
 | 
			
		||||
                        val name = nfoDocument.selectFirst("title").text()
 | 
			
		||||
                        // val seasonInt = nfoDocument.selectFirst("season").text().toIntOrNull()
 | 
			
		||||
                        val date = nfoDocument.selectFirst("aired")?.text()
 | 
			
		||||
                        val plot = nfoDocument.selectFirst("plot")?.text()
 | 
			
		||||
 | 
			
		||||
                        val dataList = seasonDocument.getElementsByAttributeValueContaining(
 | 
			
		||||
                            "href",
 | 
			
		||||
                            episode.attr("href").replace(".nfo", "")
 | 
			
		||||
                        )
 | 
			
		||||
                        val data = seasonString + dataList.firstNotNullOf { item -> item.takeIf { (!it.attr("href").contains(".nfo") &&  !it.attr("href").contains(".jpg"))} }.attr("href").toString()  // exclude poster and nfo (metadata) file
 | 
			
		||||
 | 
			
		||||
                        episodeList.add(
 | 
			
		||||
                            newEpisode(data) {
 | 
			
		||||
                                    this.name = name
 | 
			
		||||
                                    this.season = seasonInt
 | 
			
		||||
                                    this.episode = epNum
 | 
			
		||||
                                    this.posterUrl = poster
 | 
			
		||||
                                    addDate(date)
 | 
			
		||||
                                    this.description = plot
 | 
			
		||||
                            }
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
            return newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodeList) {
 | 
			
		||||
                this.name = title
 | 
			
		||||
                this.url = url
 | 
			
		||||
                this.posterUrl = posterUrl
 | 
			
		||||
                this.episodes = episodeList
 | 
			
		||||
                this.plot = description
 | 
			
		||||
                this.tags = tagsList
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override suspend fun loadLinks(
 | 
			
		||||
        data: String,
 | 
			
		||||
        isCasting: Boolean,
 | 
			
		||||
        subtitleCallback: (SubtitleFile) -> Unit,
 | 
			
		||||
        callback: (ExtractorLink) -> Unit
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
        // loadExtractor(data, null) { callback(it.copy(headers=authHeader)) }
 | 
			
		||||
        val authHeader = getAuthHeader(storedCredentials)  // call again because it isn't reloaded if in main class and storedCredentials loads after
 | 
			
		||||
        callback.invoke (
 | 
			
		||||
            ExtractorLink(
 | 
			
		||||
                name,
 | 
			
		||||
                name,
 | 
			
		||||
                data,
 | 
			
		||||
                data,  // referer not needed
 | 
			
		||||
                Qualities.Unknown.value,
 | 
			
		||||
                false,
 | 
			
		||||
                authHeader,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override suspend fun getMainPage(): HomePageResponse? {
 | 
			
		||||
        val authHeader = getAuthHeader(storedCredentials)  // call again because it isn't reloaded if in main class and storedCredentials loads after
 | 
			
		||||
        if (mainUrl == "NONE"){
 | 
			
		||||
            throw ErrorLoadingException("No nginx url specified in the settings: Nginx Settigns > Nginx server url, try again in a few seconds")
 | 
			
		||||
        }
 | 
			
		||||
        val document = app.get(mainUrl, authHeader).document
 | 
			
		||||
        val categories = document.select("a")
 | 
			
		||||
        val returnList = categories.mapNotNull {
 | 
			
		||||
            val categoryPath = mainUrl + it.attr("href") ?: return@mapNotNull null // get the url of the category; like http://192.168.1.10/media/Movies/
 | 
			
		||||
            val categoryTitle = it.text()  // get the category title like Movies or Series
 | 
			
		||||
            if (categoryTitle != "../" && categoryTitle != "Music/") {  // exclude parent dir and Music dir
 | 
			
		||||
                val categoryDocument = app.get(categoryPath, authHeader).document // queries the page http://192.168.1.10/media/Movies/
 | 
			
		||||
                val contentLinks = categoryDocument.select("a")
 | 
			
		||||
                val currentList = contentLinks.mapNotNull { head ->
 | 
			
		||||
                    if (head.attr("href") != "../") {
 | 
			
		||||
                        try {
 | 
			
		||||
                            val mediaRootUrl =
 | 
			
		||||
                                categoryPath + head.attr("href")// like http://192.168.1.10/media/Series/Chernobyl/
 | 
			
		||||
                            val mediaDocument = app.get(mediaRootUrl, authHeader).document
 | 
			
		||||
                            val nfoFilename = mediaDocument.getElementsByAttributeValueContaining(
 | 
			
		||||
                                "href",
 | 
			
		||||
                                ".nfo"
 | 
			
		||||
                            )[0].attr("href")
 | 
			
		||||
                            val isMovieType = nfoFilename != "tvshow.nfo"
 | 
			
		||||
                            val nfoPath =
 | 
			
		||||
                                mediaRootUrl + nfoFilename // must exist or will raise errors, only the first one is taken
 | 
			
		||||
                            val nfoContent =
 | 
			
		||||
                                app.get(nfoPath, authHeader).document  // all the metadata
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            if (isMovieType) {
 | 
			
		||||
                                val movieName = nfoContent.select("title").text()
 | 
			
		||||
 | 
			
		||||
                                val posterUrl = mediaRootUrl + "poster.jpg"
 | 
			
		||||
 | 
			
		||||
                                return@mapNotNull MovieSearchResponse(
 | 
			
		||||
                                    movieName,
 | 
			
		||||
                                    mediaRootUrl,
 | 
			
		||||
                                    this.name,
 | 
			
		||||
                                    TvType.Movie,
 | 
			
		||||
                                    posterUrl,
 | 
			
		||||
                                    null,
 | 
			
		||||
                                )
 | 
			
		||||
                            } else {  // tv serie
 | 
			
		||||
                                val serieName = nfoContent.select("title").text()
 | 
			
		||||
 | 
			
		||||
                                val posterUrl = mediaRootUrl + "poster.jpg"
 | 
			
		||||
 | 
			
		||||
                                TvSeriesSearchResponse(
 | 
			
		||||
                                    serieName,
 | 
			
		||||
                                    nfoPath,
 | 
			
		||||
                                    this.name,
 | 
			
		||||
                                    TvType.TvSeries,
 | 
			
		||||
                                    posterUrl,
 | 
			
		||||
                                    null,
 | 
			
		||||
                                    null,
 | 
			
		||||
                                )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
                        } catch (e: Exception) {  // can cause issues invisible errors
 | 
			
		||||
                            null
 | 
			
		||||
                            //logError(e) // not working because it changes the return type of currentList to Any
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    } else null
 | 
			
		||||
                }
 | 
			
		||||
                if (currentList.isNotEmpty() && categoryTitle != "../") {  // exclude upper dir
 | 
			
		||||
                    HomePageList(categoryTitle, currentList)
 | 
			
		||||
                } else null
 | 
			
		||||
            } else null  // the path is ../ which is parent directory
 | 
			
		||||
        }
 | 
			
		||||
        // if (returnList.isEmpty()) return null // maybe doing nothing idk
 | 
			
		||||
        return HomePageResponse(returnList)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -663,7 +663,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
 | 
			
		|||
        @JsonProperty("name") val name: String,
 | 
			
		||||
        @JsonProperty("location") val location: String,
 | 
			
		||||
        @JsonProperty("joined_at") val joined_at: String,
 | 
			
		||||
        @JsonProperty("picture") val picture: String,
 | 
			
		||||
        @JsonProperty("picture") val picture: String?,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class MalMainPicture(
 | 
			
		||||
| 
						 | 
				
			
			@ -694,4 +694,4 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
 | 
			
		|||
        val id: Int,
 | 
			
		||||
        val name: String,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -545,10 +545,14 @@ class GeneratorPlayer : FullScreenPlayer() {
 | 
			
		|||
                tvType = meta.tvType
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        player_episode_filler_holder?.isVisible = isFiller ?: false
 | 
			
		||||
 | 
			
		||||
        player_video_title?.text = if (headerName != null) {
 | 
			
		||||
        //Get limit of characters on Video Title
 | 
			
		||||
        var limitTitle = 0
 | 
			
		||||
        context?.let {
 | 
			
		||||
            val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
 | 
			
		||||
            limitTitle = settingsManager.getInt(getString(R.string.prefer_limit_title_key), 0)
 | 
			
		||||
        }
 | 
			
		||||
        //Generate video title
 | 
			
		||||
        var playerVideoTitle = if (headerName != null) {
 | 
			
		||||
            (headerName +
 | 
			
		||||
                    if (tvType.isEpisodeBased() && episode != null)
 | 
			
		||||
                        if (season == null)
 | 
			
		||||
| 
						 | 
				
			
			@ -559,6 +563,15 @@ class GeneratorPlayer : FullScreenPlayer() {
 | 
			
		|||
        } else {
 | 
			
		||||
            ""
 | 
			
		||||
        }
 | 
			
		||||
        //Truncate video title if it exceeds limit
 | 
			
		||||
        val differenceInLength = playerVideoTitle.length - limitTitle
 | 
			
		||||
        val margin = 3 //If the difference is smaller than or equal to this value, ignore it
 | 
			
		||||
        if (limitTitle > 0 && differenceInLength > margin) {
 | 
			
		||||
            playerVideoTitle = playerVideoTitle.substring(0, limitTitle-1) + "..."
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        player_episode_filler_holder?.isVisible = isFiller ?: false
 | 
			
		||||
        player_video_title?.text = playerVideoTitle
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,6 +47,7 @@ import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
 | 
			
		|||
import com.lagradost.cloudstream3.utils.Qualities
 | 
			
		||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
 | 
			
		||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
 | 
			
		||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showNginxTextInputDialog
 | 
			
		||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
 | 
			
		||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
 | 
			
		||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
 | 
			
		||||
| 
						 | 
				
			
			@ -485,6 +486,32 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
	getPref(R.string.nginx_url_key)?.setOnPreferenceClickListener {
 | 
			
		||||
 | 
			
		||||
            activity?.showNginxTextInputDialog(
 | 
			
		||||
                settingsManager.getString(getString(R.string.nginx_url_pref), "Nginx server url").toString(),
 | 
			
		||||
                settingsManager.getString(getString(R.string.nginx_url_key), "").toString(),  // key: the actual you use rn
 | 
			
		||||
                android.text.InputType.TYPE_TEXT_VARIATION_URI,  // uri
 | 
			
		||||
                {}) {
 | 
			
		||||
                settingsManager.edit()
 | 
			
		||||
                    .putString(getString(R.string.nginx_url_key), it).apply()  // change the stored url in nginx_url_key to it
 | 
			
		||||
            }
 | 
			
		||||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getPref(R.string.nginx_credentials)?.setOnPreferenceClickListener {
 | 
			
		||||
 | 
			
		||||
            activity?.showNginxTextInputDialog(
 | 
			
		||||
                settingsManager.getString(getString(R.string.nginx_credentials_title), "Nginx Credentials").toString(),
 | 
			
		||||
                settingsManager.getString(getString(R.string.nginx_credentials), "").toString(),  // key: the actual you use rn
 | 
			
		||||
                android.text.InputType.TYPE_TEXT_VARIATION_URI,
 | 
			
		||||
                {}) {
 | 
			
		||||
                settingsManager.edit()
 | 
			
		||||
                    .putString(getString(R.string.nginx_credentials), it).apply()  // change the stored url in nginx_url_key to it
 | 
			
		||||
            }
 | 
			
		||||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val prefNames = resources.getStringArray(R.array.media_type_pref)
 | 
			
		||||
            val prefValues = resources.getIntArray(R.array.media_type_pref_values)
 | 
			
		||||
| 
						 | 
				
			
			@ -678,6 +705,24 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getPref(R.string.prefer_limit_title_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val prefNames = resources.getStringArray(R.array.limit_title_pref_names)
 | 
			
		||||
            val prefValues = resources.getIntArray(R.array.limit_title_pref_values)
 | 
			
		||||
            val current = settingsManager.getInt(getString(R.string.prefer_limit_title_key), 0)
 | 
			
		||||
 | 
			
		||||
            activity?.showBottomDialog(
 | 
			
		||||
                prefNames.toList(),
 | 
			
		||||
                prefValues.indexOf(current),
 | 
			
		||||
                getString(R.string.limit_title),
 | 
			
		||||
                true,
 | 
			
		||||
                {}) {
 | 
			
		||||
                settingsManager.edit()
 | 
			
		||||
                    .putInt(getString(R.string.prefer_limit_title_key), prefValues[it])
 | 
			
		||||
                    .apply()
 | 
			
		||||
            }
 | 
			
		||||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getPref(R.string.dns_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val prefNames = resources.getStringArray(R.array.dns_pref)
 | 
			
		||||
            val prefValues = resources.getIntArray(R.array.dns_pref_values)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -100,6 +100,8 @@ val extractorApis: Array<ExtractorApi> = arrayOf(
 | 
			
		|||
    VizcloudOnline(),
 | 
			
		||||
    VizcloudXyz(),
 | 
			
		||||
    VizcloudLive(),
 | 
			
		||||
    VizcloudInfo(),
 | 
			
		||||
    MwvnVizcloudInfo(),
 | 
			
		||||
    Mp4Upload(),
 | 
			
		||||
    StreamTape(),
 | 
			
		||||
    MixDrop(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -144,6 +144,46 @@ object SingleSelectionHelper {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun Activity.showInputDialog(
 | 
			
		||||
        dialog: Dialog,
 | 
			
		||||
        value: String,
 | 
			
		||||
        name: String,
 | 
			
		||||
        textInputType: Int?,
 | 
			
		||||
        callback: (String) -> Unit,
 | 
			
		||||
        dismissCallback: () -> Unit
 | 
			
		||||
    ) {
 | 
			
		||||
        val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!!
 | 
			
		||||
        val textView = dialog.findViewById<TextView>(R.id.text1)!!
 | 
			
		||||
        val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
 | 
			
		||||
        val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!!
 | 
			
		||||
        val applyHolder = dialog.findViewById<LinearLayout>(R.id.apply_btt_holder)!!
 | 
			
		||||
 | 
			
		||||
        applyHolder.isVisible = true
 | 
			
		||||
        textView.text = name
 | 
			
		||||
 | 
			
		||||
        if (textInputType != null) {
 | 
			
		||||
            inputView.inputType = textInputType // 16 for website url input type
 | 
			
		||||
        }
 | 
			
		||||
        inputView.setText(value, TextView.BufferType.EDITABLE)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        applyButton.setOnClickListener {
 | 
			
		||||
            callback.invoke(inputView.text.toString())  // try to save the setting, using callback
 | 
			
		||||
            dialog.dismissSafe(this)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cancelButton.setOnClickListener {  // just dismiss
 | 
			
		||||
            dialog.dismissSafe(this)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        dialog.setOnDismissListener {
 | 
			
		||||
            dismissCallback.invoke()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun Activity.showMultiDialog(
 | 
			
		||||
        items: List<String>,
 | 
			
		||||
        selectedIndex: List<Int>,
 | 
			
		||||
| 
						 | 
				
			
			@ -192,7 +232,7 @@ object SingleSelectionHelper {
 | 
			
		|||
        selectedIndex: Int,
 | 
			
		||||
        name: String,
 | 
			
		||||
        showApply: Boolean,
 | 
			
		||||
        dismissCallback: () -> Unit,
 | 
			
		||||
         dismissCallback: () -> Unit,
 | 
			
		||||
        callback: (Int) -> Unit,
 | 
			
		||||
    ) {
 | 
			
		||||
        val builder =
 | 
			
		||||
| 
						 | 
				
			
			@ -211,4 +251,25 @@ object SingleSelectionHelper {
 | 
			
		|||
            dismissCallback
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        fun Activity.showNginxTextInputDialog(
 | 
			
		||||
            name: String,
 | 
			
		||||
            value: String,
 | 
			
		||||
            textInputType: Int?,
 | 
			
		||||
            dismissCallback: () -> Unit,
 | 
			
		||||
            callback: (String) -> Unit,
 | 
			
		||||
    ) {
 | 
			
		||||
        val builder = BottomSheetDialog(this)  // probably the stuff at the bottom
 | 
			
		||||
        builder.setContentView(R.layout.bottom_input_dialog)  // input layout
 | 
			
		||||
 | 
			
		||||
        builder.show()
 | 
			
		||||
        showInputDialog(
 | 
			
		||||
            builder,
 | 
			
		||||
            value,
 | 
			
		||||
            name,
 | 
			
		||||
            textInputType,  // type is a uri
 | 
			
		||||
            callback,
 | 
			
		||||
            dismissCallback
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								app/src/main/res/drawable/ic_baseline_text_format_24.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/res/drawable/ic_baseline_text_format_24.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="24dp"
 | 
			
		||||
    android:height="24dp"
 | 
			
		||||
    android:viewportWidth="24"
 | 
			
		||||
    android:viewportHeight="24"
 | 
			
		||||
    android:tint="?attr/white">
 | 
			
		||||
  <path
 | 
			
		||||
      android:fillColor="@android:color/white"
 | 
			
		||||
      android:pathData="M5,17v2h14v-2L5,17zM9.5,12.8h5l0.9,2.2h2.1L12.75,4h-1.5L6.5,15h2.1l0.9,-2.2zM12,5.98L13.87,11h-3.74L12,5.98z"/>
 | 
			
		||||
</vector>
 | 
			
		||||
							
								
								
									
										62
									
								
								app/src/main/res/layout/bottom_input_dialog.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								app/src/main/res/layout/bottom_input_dialog.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
        android:nextFocusDown="@id/nginx_text_input"
 | 
			
		||||
        android:orientation="vertical"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/text1"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_rowWeight="1"
 | 
			
		||||
        android:layout_marginTop="20dp"
 | 
			
		||||
        android:layout_marginBottom="10dp"
 | 
			
		||||
        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
 | 
			
		||||
        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
 | 
			
		||||
        android:textColor="?attr/textColor"
 | 
			
		||||
        android:textSize="20sp"
 | 
			
		||||
        android:textStyle="bold"
 | 
			
		||||
        tools:text="Test" />
 | 
			
		||||
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/nginx_text_input"
 | 
			
		||||
            android:nextFocusRight="@id/cancel_btt"
 | 
			
		||||
            android:nextFocusLeft="@id/apply_btt"
 | 
			
		||||
            android:layout_marginBottom="60dp"
 | 
			
		||||
            android:layout_marginHorizontal="10dp"
 | 
			
		||||
            android:paddingTop="10dp"
 | 
			
		||||
            android:requiresFadingEdge="vertical"
 | 
			
		||||
            tools:text="nginx.com"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:layout_rowWeight="1"
 | 
			
		||||
            android:autofillHints="Autofill Hint"
 | 
			
		||||
            android:inputType="text"
 | 
			
		||||
            tools:ignore="LabelFor" />
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
            android:id="@+id/apply_btt_holder"
 | 
			
		||||
            android:orientation="horizontal"
 | 
			
		||||
            android:layout_gravity="bottom"
 | 
			
		||||
            android:gravity="bottom|end"
 | 
			
		||||
            android:layout_marginTop="-60dp"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="60dp">
 | 
			
		||||
 | 
			
		||||
        <com.google.android.material.button.MaterialButton
 | 
			
		||||
                style="@style/WhiteButton"
 | 
			
		||||
                android:layout_gravity="center_vertical|end"
 | 
			
		||||
                android:text="@string/sort_apply"
 | 
			
		||||
                android:id="@+id/apply_btt"
 | 
			
		||||
                android:layout_width="wrap_content" />
 | 
			
		||||
 | 
			
		||||
        <com.google.android.material.button.MaterialButton
 | 
			
		||||
                style="@style/BlackButton"
 | 
			
		||||
                android:layout_gravity="center_vertical|end"
 | 
			
		||||
                android:text="@string/sort_cancel"
 | 
			
		||||
                android:id="@+id/cancel_btt"
 | 
			
		||||
                android:layout_width="wrap_content" />
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +32,7 @@
 | 
			
		|||
    </array>
 | 
			
		||||
 | 
			
		||||
    <array name="dns_pref">
 | 
			
		||||
        <item>None</item>
 | 
			
		||||
        <item>@string/none</item>
 | 
			
		||||
        <item>Google</item>
 | 
			
		||||
        <item>Cloudflare</item>
 | 
			
		||||
        <!--        <item>OpenDns</item>-->
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +59,21 @@
 | 
			
		|||
        <item>3</item>
 | 
			
		||||
    </array>
 | 
			
		||||
 | 
			
		||||
    <array name="limit_title_pref_names">
 | 
			
		||||
        <item>@string/none</item>
 | 
			
		||||
        <item>16 characters</item>
 | 
			
		||||
        <item>32 characters</item>
 | 
			
		||||
        <item>64 characters</item>
 | 
			
		||||
        <item>128 characters</item>
 | 
			
		||||
    </array>
 | 
			
		||||
    <array name="limit_title_pref_values">
 | 
			
		||||
        <item>0</item>
 | 
			
		||||
        <item>16</item>
 | 
			
		||||
        <item>32</item>
 | 
			
		||||
        <item>64</item>
 | 
			
		||||
        <item>128</item>
 | 
			
		||||
    </array>
 | 
			
		||||
 | 
			
		||||
    <array name="video_buffer_length_names">
 | 
			
		||||
        <item>@string/automatic</item>
 | 
			
		||||
        <item>1min</item>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@
 | 
			
		|||
    <string name="subtitle_settings_key" translatable="false">subtitle_settings_key</string>
 | 
			
		||||
    <string name="subtitle_settings_chromecast_key" translatable="false">subtitle_settings_chromecast_key</string>
 | 
			
		||||
    <string name="quality_pref_key" translatable="false">quality_pref_key</string>
 | 
			
		||||
    <string name="prefer_limit_title_key" translatable="false">prefer_limit_title_key</string>
 | 
			
		||||
    <string name="video_buffer_size_key" translatable="false">video_buffer_size_key</string>
 | 
			
		||||
    <string name="video_buffer_length_key" translatable="false">video_buffer_length_key</string>
 | 
			
		||||
    <string name="video_buffer_clear_key" translatable="false">video_buffer_clear_key</string>
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +32,9 @@
 | 
			
		|||
    <string name="provider_lang_key" translatable="false">provider_lang_key</string>
 | 
			
		||||
    <string name="dns_key" translatable="false">dns_key</string>
 | 
			
		||||
    <string name="download_path_key" translatable="false">download_path_key</string>
 | 
			
		||||
    <string name="nginx_url_key" translatable="false">nginx_url_key</string>
 | 
			
		||||
    <string name="nginx_credentials" translatable="false">nginx_credentials</string>
 | 
			
		||||
    <string name="nginx_info" translatable="false">nginx_info</string>
 | 
			
		||||
    <string name="app_name_download_path" translatable="false">Cloudstream</string>
 | 
			
		||||
    <string name="app_layout_key" translatable="false">app_layout_key</string>
 | 
			
		||||
    <string name="primary_color_key" translatable="false">primary_color_key</string>
 | 
			
		||||
| 
						 | 
				
			
			@ -227,6 +231,12 @@
 | 
			
		|||
    <string name="backup_failed_error_format">Error backing up %s</string>
 | 
			
		||||
 | 
			
		||||
    <string name="search">Search</string>
 | 
			
		||||
    <string name="nginx_category">Nginx Settings</string>
 | 
			
		||||
    <string name="nginx_credentials_title">Nginx Credential</string>
 | 
			
		||||
    <string name="nginx_credentials_summary">You have to use the following format mycoolusername:mysecurepassword123</string>
 | 
			
		||||
    <string name="nginx_info_title">What is Nginx ?</string>
 | 
			
		||||
    <string name="nginx_info_summary">Nginx is a software that can be used to display files from a server that you own. Click to see a Nginx setup guide</string>
 | 
			
		||||
 | 
			
		||||
    <string name="settings_info">Info</string>
 | 
			
		||||
    <string name="advanced_search">Advanced Search</string>
 | 
			
		||||
    <string name="advanced_search_des">Gives you the search results separated by provider</string>
 | 
			
		||||
| 
						 | 
				
			
			@ -339,6 +349,7 @@
 | 
			
		|||
    <string name="dont_show_again">Don\'t show again</string>
 | 
			
		||||
    <string name="update">Update</string>
 | 
			
		||||
    <string name="watch_quality_pref">Preferred watch quality</string>
 | 
			
		||||
    <string name="limit_title">Limit title characters on player</string>
 | 
			
		||||
    <string name="video_buffer_size_settings">Video buffer size</string>
 | 
			
		||||
    <string name="video_buffer_length_settings">Video buffer length</string>
 | 
			
		||||
    <string name="video_buffer_disk_settings">Video cache on disk</string>
 | 
			
		||||
| 
						 | 
				
			
			@ -352,6 +363,8 @@
 | 
			
		|||
 | 
			
		||||
    <string name="download_path_pref">Download path</string>
 | 
			
		||||
 | 
			
		||||
    <string name="nginx_url_pref">Nginx server url</string>
 | 
			
		||||
 | 
			
		||||
    <string name="display_subbed_dubbed_settings">Display Dubbed/Subbed Anime</string>
 | 
			
		||||
 | 
			
		||||
    <string name="resize_fit">Fit to screen</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,10 @@
 | 
			
		|||
                android:key="@string/quality_pref_key"
 | 
			
		||||
                android:title="@string/watch_quality_pref"
 | 
			
		||||
                android:icon="@drawable/ic_baseline_hd_24" />
 | 
			
		||||
        <Preference
 | 
			
		||||
            android:key="@string/prefer_limit_title_key"
 | 
			
		||||
            android:title="@string/limit_title"
 | 
			
		||||
            android:icon="@drawable/ic_baseline_text_format_24" />
 | 
			
		||||
 | 
			
		||||
        <SwitchPreference
 | 
			
		||||
                android:icon="@drawable/ic_baseline_picture_in_picture_alt_24"
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +166,31 @@
 | 
			
		|||
                app:defaultValue="true" />
 | 
			
		||||
    </PreferenceCategory>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <PreferenceCategory
 | 
			
		||||
        android:key="@string/nginx_category"
 | 
			
		||||
        android:title="@string/nginx_category"
 | 
			
		||||
        app:isPreferenceVisible="true">
 | 
			
		||||
        <Preference
 | 
			
		||||
            android:key="@string/nginx_url_key"
 | 
			
		||||
            android:title="@string/nginx_url_pref"
 | 
			
		||||
            android:icon="@drawable/ic_baseline_play_arrow_24" />
 | 
			
		||||
        <Preference
 | 
			
		||||
            android:key="@string/nginx_credentials"
 | 
			
		||||
            android:title="@string/nginx_credentials_title"
 | 
			
		||||
            android:icon="@drawable/video_locked"
 | 
			
		||||
            android:summary="@string/nginx_credentials_summary"/>
 | 
			
		||||
        <Preference
 | 
			
		||||
            android:key="@string/nginx_info"
 | 
			
		||||
            android:title="@string/nginx_info_title"
 | 
			
		||||
            android:icon="@drawable/ic_baseline_play_arrow_24"
 | 
			
		||||
	        android:summary="@string/nginx_info_summary" >
 | 
			
		||||
            <intent
 | 
			
		||||
                android:action="android.intent.action.VIEW"
 | 
			
		||||
                android:data="https://www.sarlays.com/use-nginx-with-cloudstream/" />
 | 
			
		||||
        </Preference>
 | 
			
		||||
    </PreferenceCategory>
 | 
			
		||||
 | 
			
		||||
    <PreferenceCategory
 | 
			
		||||
            android:key="info"
 | 
			
		||||
            android:title="@string/settings_info"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue