updated nginx provider

This commit is contained in:
sarlay 2022-08-28 15:01:55 +02:00 committed by Cloudburst
parent c8f6c84de1
commit 9617444dcc
3 changed files with 337 additions and 197 deletions

View file

@ -5,7 +5,7 @@ dependencies {
} }
// use an integer for version numbers // use an integer for version numbers
version = 7 version = 8
cloudstream { cloudstream {

View file

@ -52,7 +52,7 @@
NginxProvider.loginCredentials = null NginxProvider.loginCredentials = null
return return
} }
NginxProvider.overrideUrl = data.server?.removeSuffix("/") NginxProvider.overrideUrl = data.server?.removeSuffix("/") + "/"
NginxProvider.loginCredentials = "${data.username ?: ""}:${data.password ?: ""}" NginxProvider.loginCredentials = "${data.username ?: ""}:${data.password ?: ""}"
} }

View file

@ -1,20 +1,46 @@
package com.lagradost package com.lagradost
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.loadExtractor
class NginxProvider : MainAPI() { class NginxProvider : MainAPI() {
override var name = "Nginx" override var name = "Nginx"
override val hasQuickSearch = false // override var lang = "en" //no specific language
override val hasMainPage = true override val hasMainPage = true
override val hasQuickSearch = false
override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie) override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie)
// override val hasSearch = false
companion object { companion object {
var companionName = "Nginx"
var loginCredentials: String? = null var loginCredentials: String? = null
var overrideUrl: String? = null var overrideUrl: String? = null
var pathToLibrary: String? = null // the library displayed when getting overrideUrl /home/username/media/
const val ERROR_STRING = "No nginx url specified in the settings" const val ERROR_STRING = "No nginx url specified in the settings"
fun getDirectMediaLink(path: String): String? {
val storedPathToLibrary = pathToLibrary
val storedOverrideUrl = overrideUrl // may be null if change value later
if (storedPathToLibrary == null || storedOverrideUrl == null) {
return null
}
return path.replace(storedPathToLibrary, storedOverrideUrl)
// /home/username/media/Movies/The Northman (2022)/The.Northman.2022.MULTi.1080p.AMZN.WEBRip.DDP5.1.Atmos.x264-VROOMM.mkv
// becomes:
// https://website.hosting.com/Movies/The Northman (2022)/The.Northman.2022.MULTi.1080p.AMZN.WEBRip.DDP5.1.Atmos.x264-VROOMM.mkv
}
} }
private fun getAuthHeader(): Map<String, String> { private fun getAuthHeader(): Map<String, String> {
@ -31,252 +57,365 @@ class NginxProvider : MainAPI() {
} }
val basicAuthToken = val basicAuthToken =
base64Encode(localCredentials.toByteArray()) // will this be loaded when not using the provider ??? can increase load base64Encode(localCredentials.toByteArray())
return mapOf("Authorization" to "Basic $basicAuthToken") return mapOf("Authorization" to "Basic $basicAuthToken")
} }
override suspend fun load(url: String): LoadResponse {
val authHeader =
getAuthHeader() // 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 mainRootDocument = app.get(url, authHeader).document override suspend fun load(url: String): LoadResponse? {
val nfoUrl = url + mainRootDocument.getElementsByAttributeValueContaining("href", ".nfo") val authHeader = getAuthHeader() // reload
.attr("href") // metadata url file
val metadataDocument = app.get(nfoUrl, authHeader).document // get the metadata nfo file val isValid = url.contains(".nfo")
val isSerie = url.contains("tvshow.nfo")
val isMovie = !nfoUrl.contains("tvshow.nfo") val metadataDocument = app.get(url, authHeader).document // get the metadata nfo file
val title = metadataDocument.selectFirst("title")!!.text() val title = metadataDocument.selectFirst("title")?.text()?.replace("/", "") ?: url.substringBeforeLast("/").substringAfterLast("/")
// gets the content between slashes: https://g.com/nameOfShow/tvshow.nfo
val description = metadataDocument.selectFirst("plot")!!.text() val description = metadataDocument.selectFirst("plot")?.text()
if (isMovie) { val mediaRootUrl = url.substringBeforeLast("/") + "/"
val poster = metadataDocument.selectFirst("thumb")!!.text() val mediaRootDocument = app.get(mediaRootUrl, authHeader).document
val trailer = metadataDocument.select("trailer").mapNotNull {
it?.text()?.replace( if (!isValid) {
"plugin://plugin.video.youtube/play/?video_id=", if (".mp4" in url || ".mkv" in url) {
"https://www.youtube.com/watch?v=" val fixedUrl = fixUrl(url)
return newMovieLoadResponse(
title,
fixedUrl,
TvType.Movie,
fixedUrl,
) )
} }
val partialUrl = return null
mainRootDocument.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 =
mainRootDocument.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 newMovieLoadResponse(
title,
url,
TvType.Movie,
data
) {
this.year = date
this.plot = description
this.rating = ratingAverage
this.tags = tagsList
addTrailer(trailer)
addPoster(poster, authHeader)
}
} 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 // will require headers too
this.description = plot
addDate(date)
}
)
}
}
return newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodeList) {
this.name = title
this.url = url
this.episodes = episodeList
this.plot = description
this.tags = tagsList
addPoster(posterUrl, authHeader)
}
} }
// There is metadata and library is correctly organised!
if (!isSerie) { // Movie
val poster = metadataDocument.selectFirst("thumb[aspect=poster]")?.text()
val fanart = metadataDocument.selectFirst("fanart > thumb")?.text()
val trailer = metadataDocument.selectFirst("trailer")?.text()?.replace(
"plugin://plugin.video.youtube/play/?video_id=",
"https://www.youtube.com/watch?v="
)
val date = metadataDocument.selectFirst("year")?.text()?.toIntOrNull()
val durationInMinutes = metadataDocument.selectFirst("duration")?.text()?.toIntOrNull()
val ratingAverage = metadataDocument.selectFirst("value")?.text()?.toIntOrNull()
val tagsList = metadataDocument.select("genre")
.mapNotNull { // all the tags like action, thriller ...
it?.text()
}
val actors = metadataDocument.select("actor").mapNotNull {
val name = it?.selectFirst("name")?.text() ?: return@mapNotNull null
val image = it.selectFirst("thumb")?.text() ?: return@mapNotNull null
Actor(name, image)
}
val mkvElementsResult = mediaRootDocument.getElementsByAttributeValueContaining( // list of all urls of the mkv url in the webpage
"href",
".mkv"
)
val mp4ElementsResult = mediaRootDocument.getElementsByAttributeValueContaining( // list of all urls of the webpage
"href",
".mp4"
)
val dataList = if (mkvElementsResult.isNotEmpty()) { // there is probably a better way to do it
mkvElementsResult[0].attr("href").toString()
} else {
if(mp4ElementsResult.isNotEmpty()) {
mp4ElementsResult[0].attr("href").toString()
} else {
null
}
}
if (dataList != null) {
return newMovieLoadResponse(
title,
mediaRootUrl,
TvType.Movie,
mediaRootUrl + dataList,
) {
this.year = date
this.plot = description
this.rating = ratingAverage
this.tags = tagsList
this.backgroundPosterUrl = fanart
this.duration = durationInMinutes
addPoster(poster, authHeader)
addActors(actors)
addTrailer(trailer)
}
}
} else {
// a tv show
val list = ArrayList<Pair<Int, String>>()
val posterUrl = mediaRootUrl + "poster.jpg"
val tagsList = metadataDocument.select("genre")
.mapNotNull { // all the tags like action, thriller ...; unused variable
it?.text()
}
val actorsList = metadataDocument.select("actor")
.mapNotNull { // all the tags like action, thriller ...; unused variable
if (it.selectFirst("name")?.text() != null && it.selectFirst("role")?.text() != null) {
it.selectFirst("name")?.text()
it.selectFirst("role")?.text()
} else null
}
val seasons =
mediaRootDocument.getElementsByAttributeValueContaining("href", "Season%20")
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, make sure to use season folders")
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 // will require headers too
this.description = plot
addDate(date)
}
)
}
}
return newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodeList) {
this.name = title
this.url = url
this.episodes = episodeList
this.plot = description
this.tags = tagsList
addPoster(posterUrl, authHeader)
addActors(actorsList)
}
}
///
return null
} }
override suspend fun loadLinks( override suspend fun loadLinks(
data: String, data: String,
isCasting: Boolean, isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
): Boolean { ): Boolean {
// loadExtractor(data, null) { callback(it.copy(headers=authHeader)) } // println("loadling link $data")
val authHeader =
getAuthHeader() // call again because it isn't reloaded if in main class and storedCredentials loads after val authHeader = getAuthHeader() // refresh crendentials
callback.invoke(
try {
val mediaRootUrl = data.substringBeforeLast("/") + "/"
val mediaRootDocument = app.get(mediaRootUrl, authHeader).document
val subtitlesElements = mediaRootDocument.getElementsByAttributeValueContaining(
// list of all urls of the mkv url in the webpage
"href",
".srt", // all of subtitles files in the folder
)
subtitlesElements.forEach {
subtitleCallback.invoke(
SubtitleFile(
SubtitleHelper.fromTwoLettersToLanguage(it.attr("href").replace(".srt", "").substringAfterLast(".")) ?: "English",
mediaRootUrl + "/" + it.attr("href")
)
)
}
} catch (e: Exception) {
logError(e)
}
callback.invoke (
ExtractorLink( ExtractorLink(
name, name,
name, name,
data, data,
data, // referer not needed "", // referer not needed
Qualities.Unknown.value, Qualities.Unknown.value,
false, false,
authHeader, authHeader,
) )
) )
return true return true
} }
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { private fun cleanElement(elementUrl: String): String {
val authHeader = return if (elementUrl[0] == '/') { // if starts by "/", remove it
getAuthHeader() // call again because it isn't reloaded if in main class and storedCredentials loads after elementUrl.drop(1)
} else {
elementUrl
}
}
override suspend fun getMainPage(
page: Int,
request : MainPageRequest,
): HomePageResponse {
val authHeader = getAuthHeader() // reload
if (mainUrl == "NONE" || mainUrl == "" || mainUrl == "nginx_url_key"){ // mainurl: http://192.168.1.10/media/
throw ErrorLoadingException("No nginx url specified in the settings: Nginx Settigns > Nginx server url, try again in a few seconds") // getString(R.string.nginx_load_exception) or @strings/nginx_load_exception
}
val document = app.get(mainUrl, authHeader).document val document = app.get(mainUrl, authHeader).document
val categories = document.select("a") val categories = document.select("a") // select all of the categories
val returnList = categories.mapNotNull { val returnList = categories.mapNotNull { // for each category
val categoryTitle = it.text() // get the category title like Movies or Series val categoryPath = mainUrl + cleanElement(it.attr("href")) // ?: return@mapNotNull null // get the url of the category; like http://192.168.1.10/media/Movies/
if (categoryTitle != "../" && categoryTitle != "Music/") { // exclude parent dir and Music dir val categoryTitle = it.text() // get the category title like Movies/ or Series/
val href = it?.attr("href") if (categoryTitle != "../" && categoryTitle != "/") { // exclude parent dir
val categoryPath = fixUrlNull(href?.trim()) val categoryDocument = app.get(categoryPath, authHeader).document // queries the page http://192.168.1.10/media/Movies/
?: return@mapNotNull null // get the url of the category; like http://192.168.1.10/media/Movies/ val contentLinks = categoryDocument.select("a") // select all links
val currentList = contentLinks.mapNotNull { head -> // for each of those elements
val categoryDocument = app.get( val linkToElement = head.attr("href")
categoryPath, if (linkToElement != "../" && linkToElement != "/") {
authHeader val isAFolderBool = linkToElement.endsWith("/")
).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 { try {
val mediaRootUrl = val mediaRootUrl =
categoryPath + head.attr("href")// like http://192.168.1.10/media/Series/Chernobyl/ categoryPath + cleanElement(linkToElement) // like http://192.168.1.10/Series/Chernobyl/ or http://192.168.1.10/Movies/zjfbfvz.mp4
val mediaDocument = app.get(mediaRootUrl, authHeader).document if (isAFolderBool) { // content is organised in folders for each element
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() Movies
val posterUrl = mediaRootUrl + "poster.jpg" Eternals (2021) // the media root folder will be 'http://192.168.1.10/media/Movies/Eternals (2021)/'
Eternals.2021.MULTi.WiTH.TRUEFRENCH.iMAX.1080p.DSNP.WEB-DL.DDP5.1.H264-FRATERNiTY.mkv
Eternals.2021.MULTi.WiTH.TRUEFRENCH.iMAX.1080p.DSNP.WEB-DL.DDP5.1.H264-FRATERNiTY.en.srt
Eternals.2021.MULTi.WiTH.TRUEFRENCH.iMAX.1080p.DSNP.WEB-DL.DDP5.1.H264-FRATERNiTY.nfo
fanart.jpg
poster.jpg
*/
val mediaDocument = app.get(mediaRootUrl, authHeader).document
val nfoFilename = try {
mediaDocument.getElementsByAttributeValueContaining(
"href",
".nfo"
)[0]?.attr("href")
} catch (e: Exception) {
logError(e)
null
}
val isSerieType =
nfoFilename.toString() == "tvshow.nfo" // will be a movie if no metadata
val nfoPath = if (nfoFilename != null) {
mediaRootUrl + nfoFilename // metadata must exist
} else {
return@mapNotNull newMovieSearchResponse(
linkToElement,
mediaRootUrl,
TvType.Movie,
)
}
val nfoContent = app.get(nfoPath, authHeader).document // get all the metadata
if (isSerieType) {
val serieName = nfoContent.select("title").text() ?: linkToElement // name of the tv show
val posterUrl = mediaRootUrl + "poster.jpg" // poster.jpg in the same folder
newTvSeriesSearchResponse(
serieName,
nfoPath,
TvType.TvSeries,
) {
addPoster(posterUrl, authHeader)
}
} else { // Movie
val movieName = nfoContent.select("title").text() ?: linkToElement
val posterUrl = nfoContent.selectFirst("thumb[aspect=poster]")?.text() // poster should be stored in the same folder
// val fanartUrl = mediaRootUrl + "fanart.jpg" // backdrop should be stored in the same folder
return@mapNotNull newMovieSearchResponse(
movieName,
nfoPath,
TvType.Movie,
) {
addPoster(posterUrl, authHeader)
}
}
} else { // return direct file
/*
Movies
Eternals.2021.MULTi.WiTH.TRUEFRENCH.iMAX.1080p.DSNP.WEB-DL.DDP5.1.H264-FRATERNiTY.mkv // the media root folder
Juste la fin du monde (2016) VOF 1080p mHD x264 AC3-SANTACRUZ.mkv
*/
return@mapNotNull newMovieSearchResponse( return@mapNotNull newMovieSearchResponse(
movieName, linkToElement,
mediaRootUrl, mediaRootUrl,
TvType.Movie, TvType.Movie,
) { )
addPoster(posterUrl, authHeader)
}
} else { // tv serie
val serieName = nfoContent.select("title").text()
val posterUrl = mediaRootUrl + "poster.jpg"
newTvSeriesSearchResponse(
serieName,
nfoPath,
TvType.TvSeries,
) {
addPoster(posterUrl, authHeader)
}
} }
} catch (e: Exception) { // can cause issues invisible errors } catch (e: Exception) { // can cause issues invisible errors
null null
//logError(e) // not working because it changes the return type of currentList to Any //logError(e) // not working because it changes the return type of currentList to Any
} }
} else null } else null
} }
if (currentList.isNotEmpty() && categoryTitle != "../") { // exclude upper dir if (currentList.isNotEmpty() && categoryTitle != "../" && categoryTitle != "/") { // exclude upper dir
HomePageList(categoryTitle, currentList) HomePageList(categoryTitle.replace("/", " "), currentList)
} else null } else null
} else null // the path is ../ which is parent directory } else null // the path is ../ which is parent directory
} }
@ -284,3 +423,4 @@ class NginxProvider : MainAPI() {
return HomePageResponse(returnList) return HomePageResponse(returnList)
} }
} }