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
|
||||||
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)
|
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)
|
||||||
|
@ -619,4 +687,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
val output = src?.doMath()
|
val output = src?.doMath()
|
||||||
println("MASTER OUTPUT = $output")*/
|
println("MASTER OUTPUT = $output")*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
|
@ -192,7 +232,7 @@ object SingleSelectionHelper {
|
||||||
selectedIndex: Int,
|
selectedIndex: Int,
|
||||||
name: String,
|
name: String,
|
||||||
showApply: Boolean,
|
showApply: Boolean,
|
||||||
dismissCallback: () -> Unit,
|
dismissCallback: () -> Unit,
|
||||||
callback: (Int) -> Unit,
|
callback: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
val builder =
|
val builder =
|
||||||
|
@ -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…
Reference in a new issue