mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	Added Nginx Provider and Nginx settings (#932)
* Added Nginx Provider and Nginx settings * fixed some stuff * updated NginxProvider to use latest newEpisode method * hide nginx provider when no url specified * fixed some stuff * removed useless credentials variable * added url to my website * fixed tabulation Co-authored-by: sarlay <dev@sarlays.com>
This commit is contained in:
		
							parent
							
								
									bf81258f77
								
							
						
					
					
						commit
						06b778eac2
					
				
					 8 changed files with 534 additions and 7 deletions
				
			
		|  | @ -19,6 +19,7 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi | ||||||
| import com.lagradost.cloudstream3.ui.player.SubtitleData | import com.lagradost.cloudstream3.ui.player.SubtitleData | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.toJson | import com.lagradost.cloudstream3.utils.AppUtils.toJson | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
|  | import okhttp3.Headers | ||||||
| import okhttp3.Interceptor | import okhttp3.Interceptor | ||||||
| import java.text.SimpleDateFormat | import java.text.SimpleDateFormat | ||||||
| import java.util.* | import java.util.* | ||||||
|  | @ -107,6 +108,7 @@ object APIHolder { | ||||||
|             MonoschinosProvider(), |             MonoschinosProvider(), | ||||||
|             KawaiifuProvider(), // disabled due to cloudflare |             KawaiifuProvider(), // disabled due to cloudflare | ||||||
|             //MultiAnimeProvider(), |             //MultiAnimeProvider(), | ||||||
|  | 	        NginxProvider(), | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -303,6 +305,7 @@ const val PROVIDER_STATUS_DOWN = 0 | ||||||
| data class ProvidersInfoJson( | data class ProvidersInfoJson( | ||||||
|     @JsonProperty("name") var name: String, |     @JsonProperty("name") var name: String, | ||||||
|     @JsonProperty("url") var url: String, |     @JsonProperty("url") var url: String, | ||||||
|  |     @JsonProperty("credentials") var credentials: String? = null, | ||||||
|     @JsonProperty("status") var status: Int, |     @JsonProperty("status") var status: Int, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -315,6 +318,7 @@ abstract class MainAPI { | ||||||
|     fun overrideWithNewData(data: ProvidersInfoJson) { |     fun overrideWithNewData(data: ProvidersInfoJson) { | ||||||
|         this.name = data.name |         this.name = data.name | ||||||
|         this.mainUrl = data.url |         this.mainUrl = data.url | ||||||
|  | 	    this.storedCredentials = data.credentials | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     init { |     init { | ||||||
|  | @ -325,6 +329,7 @@ abstract class MainAPI { | ||||||
| 
 | 
 | ||||||
|     open var name = "NONE" |     open var name = "NONE" | ||||||
|     open var mainUrl = "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 |     //open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -64,12 +64,14 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard | import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.navigate | import com.lagradost.cloudstream3.utils.UIHelper.navigate | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.requestRW | 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.activity_main.* | ||||||
| import kotlinx.android.synthetic.main.fragment_result_swipe.* | import kotlinx.android.synthetic.main.fragment_result_swipe.* | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.runBlocking | import kotlinx.coroutines.runBlocking | ||||||
| import kotlinx.coroutines.withContext | import kotlinx.coroutines.withContext | ||||||
| import java.io.File | import java.io.File | ||||||
|  | import kotlin.collections.HashMap | ||||||
| import kotlin.concurrent.thread | import kotlin.concurrent.thread | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -360,6 +362,57 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|             e.printStackTrace() |             e.printStackTrace() | ||||||
|             false |             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 |         // this pulls the latest data so ppl don't have to update to simply change provider url | ||||||
|         if (downloadFromGithub) { |         if (downloadFromGithub) { | ||||||
|  | @ -379,8 +432,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|                                             tryParseJson<HashMap<String, ProvidersInfoJson>>(txt) |                                             tryParseJson<HashMap<String, ProvidersInfoJson>>(txt) | ||||||
|                                         setKey(PROVIDER_STATUS_KEY, txt) |                                         setKey(PROVIDER_STATUS_KEY, txt) | ||||||
|                                         MainAPI.overrideData = newCache // update all new providers |                                         MainAPI.overrideData = newCache // update all new providers | ||||||
|  |                                          | ||||||
|  |                                         val newUpdatedCache = newCache?.let { addNginxToJson(it) ?: it } | ||||||
|  | 
 | ||||||
| 					                    for (api in apis) { // update current providers | 					                    for (api in apis) { // update current providers | ||||||
|                                             newCache?.get(api.javaClass.simpleName)?.let { data -> |                                             newUpdatedCache?.get(api.javaClass.simpleName)?.let { data -> | ||||||
|                                                 api.overrideWithNewData(data) |                                                 api.overrideWithNewData(data) | ||||||
|                                             } |                                             } | ||||||
|                                         } |                                         } | ||||||
|  | @ -397,12 +453,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|                                 newCache |                                 newCache | ||||||
|                             }?.let { providersJsonMap -> |                             }?.let { providersJsonMap -> | ||||||
|                                 MainAPI.overrideData = providersJsonMap |                                 MainAPI.overrideData = providersJsonMap | ||||||
|  |                                 val providersJsonMapUpdated = addNginxToJson(providersJsonMap)?: providersJsonMap // if return null, use unchanged one | ||||||
|                                 val acceptableProviders = |                                 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() |                                         .map { it.key }.toSet() | ||||||
| 
 | 
 | ||||||
|                                 val restrictedApis = |                                 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() |                                         .map { it.key }.toSet() else emptySet() | ||||||
| 
 | 
 | ||||||
|                                 apis = allProviders.filter { api -> |                                 apis = allProviders.filter { api -> | ||||||
|  | @ -425,6 +482,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             apis = allProviders |             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) |         loadThemes(this) | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -47,6 +47,7 @@ import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate | ||||||
| import com.lagradost.cloudstream3.utils.Qualities | import com.lagradost.cloudstream3.utils.Qualities | ||||||
| import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog | import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog | ||||||
| import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog | 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.SingleSelectionHelper.showMultiDialog | ||||||
| import com.lagradost.cloudstream3.utils.SubtitleHelper | import com.lagradost.cloudstream3.utils.SubtitleHelper | ||||||
| import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso | import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso | ||||||
|  | @ -485,6 +486,32 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|             return@setOnPreferenceClickListener true |             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 { |         getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener { | ||||||
|             val prefNames = resources.getStringArray(R.array.media_type_pref) |             val prefNames = resources.getStringArray(R.array.media_type_pref) | ||||||
|             val prefValues = resources.getIntArray(R.array.media_type_pref_values) |             val prefValues = resources.getIntArray(R.array.media_type_pref_values) | ||||||
|  |  | ||||||
|  | @ -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( |     fun Activity.showMultiDialog( | ||||||
|         items: List<String>, |         items: List<String>, | ||||||
|         selectedIndex: List<Int>, |         selectedIndex: List<Int>, | ||||||
|  | @ -211,4 +251,25 @@ object SingleSelectionHelper { | ||||||
|             dismissCallback |             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 | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
							
								
								
									
										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> | ||||||
|  | @ -31,6 +31,9 @@ | ||||||
|     <string name="provider_lang_key" translatable="false">provider_lang_key</string> |     <string name="provider_lang_key" translatable="false">provider_lang_key</string> | ||||||
|     <string name="dns_key" translatable="false">dns_key</string> |     <string name="dns_key" translatable="false">dns_key</string> | ||||||
|     <string name="download_path_key" translatable="false">download_path_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_name_download_path" translatable="false">Cloudstream</string> | ||||||
|     <string name="app_layout_key" translatable="false">app_layout_key</string> |     <string name="app_layout_key" translatable="false">app_layout_key</string> | ||||||
|     <string name="primary_color_key" translatable="false">primary_color_key</string> |     <string name="primary_color_key" translatable="false">primary_color_key</string> | ||||||
|  | @ -227,6 +230,12 @@ | ||||||
|     <string name="backup_failed_error_format">Error backing up %s</string> |     <string name="backup_failed_error_format">Error backing up %s</string> | ||||||
| 
 | 
 | ||||||
|     <string name="search">Search</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="settings_info">Info</string> | ||||||
|     <string name="advanced_search">Advanced Search</string> |     <string name="advanced_search">Advanced Search</string> | ||||||
|     <string name="advanced_search_des">Gives you the search results separated by provider</string> |     <string name="advanced_search_des">Gives you the search results separated by provider</string> | ||||||
|  | @ -352,6 +361,8 @@ | ||||||
| 
 | 
 | ||||||
|     <string name="download_path_pref">Download path</string> |     <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="display_subbed_dubbed_settings">Display Dubbed/Subbed Anime</string> | ||||||
| 
 | 
 | ||||||
|     <string name="resize_fit">Fit to screen</string> |     <string name="resize_fit">Fit to screen</string> | ||||||
|  |  | ||||||
|  | @ -162,6 +162,31 @@ | ||||||
|                 app:defaultValue="true" /> |                 app:defaultValue="true" /> | ||||||
|     </PreferenceCategory> |     </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 |     <PreferenceCategory | ||||||
|             android:key="info" |             android:key="info" | ||||||
|             android:title="@string/settings_info" |             android:title="@string/settings_info" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue