forked from recloudstream/cloudstream
added Thenos
chrome-casting is probably possible if the function gets redone to include authorization
This commit is contained in:
parent
d153baebef
commit
3777728dfd
5 changed files with 502 additions and 1 deletions
|
@ -1,5 +1,6 @@
|
||||||
package com.lagradost.cloudstream3
|
package com.lagradost.cloudstream3
|
||||||
|
|
||||||
|
import ThenosProvider
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
@ -42,6 +43,7 @@ object APIHolder {
|
||||||
WatchCartoonOnlineProvider(),
|
WatchCartoonOnlineProvider(),
|
||||||
AllMoviesForYouProvider(),
|
AllMoviesForYouProvider(),
|
||||||
AsiaFlixProvider(),
|
AsiaFlixProvider(),
|
||||||
|
ThenosProvider()
|
||||||
)
|
)
|
||||||
|
|
||||||
val restrictedApis = arrayListOf(
|
val restrictedApis = arrayListOf(
|
||||||
|
|
|
@ -0,0 +1,493 @@
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class ThenosProvider : MainAPI() {
|
||||||
|
override val mainUrl: String
|
||||||
|
get() = "https://www.thenos.org"
|
||||||
|
override val name: String
|
||||||
|
get() = "Thenos"
|
||||||
|
|
||||||
|
override val hasQuickSearch: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val hasMainPage: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val hasChromecastSupport: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
override val supportedTypes: Set<TvType>
|
||||||
|
get() = setOf(
|
||||||
|
TvType.Movie,
|
||||||
|
TvType.TvSeries,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val vpnStatus: VPNStatus
|
||||||
|
get() = VPNStatus.None
|
||||||
|
|
||||||
|
override fun getMainPage(): HomePageResponse? {
|
||||||
|
val map = mapOf(
|
||||||
|
"New Releases" to "released",
|
||||||
|
"Recently Added in Movies" to "recent",
|
||||||
|
"Recently Added in Shows" to "recent/shows",
|
||||||
|
"Top Rated" to "rating"
|
||||||
|
)
|
||||||
|
val list = ArrayList<HomePageList>()
|
||||||
|
map.entries.forEach {
|
||||||
|
val url = "$apiUrl/library/${it.value}"
|
||||||
|
val response = khttp.get(url)
|
||||||
|
val mapped = mapper.readValue<ThenosLoadResponse>(response.text)
|
||||||
|
|
||||||
|
mapped.Metadata?.mapNotNull {
|
||||||
|
it?.toSearchResponse()
|
||||||
|
}?.let { searchResponses ->
|
||||||
|
list.add(
|
||||||
|
HomePageList(
|
||||||
|
it.key,
|
||||||
|
searchResponses
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HomePageResponse(
|
||||||
|
list
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun secondsToReadable(seconds: Int, completedValue: String): String {
|
||||||
|
var secondsLong = seconds.toLong()
|
||||||
|
val days = TimeUnit.SECONDS
|
||||||
|
.toDays(secondsLong)
|
||||||
|
secondsLong -= TimeUnit.DAYS.toSeconds(days)
|
||||||
|
|
||||||
|
val hours = TimeUnit.SECONDS
|
||||||
|
.toHours(secondsLong)
|
||||||
|
secondsLong -= TimeUnit.HOURS.toSeconds(hours)
|
||||||
|
|
||||||
|
val minutes = TimeUnit.SECONDS
|
||||||
|
.toMinutes(secondsLong)
|
||||||
|
secondsLong -= TimeUnit.MINUTES.toSeconds(minutes)
|
||||||
|
if (minutes < 0) {
|
||||||
|
return completedValue
|
||||||
|
}
|
||||||
|
//println("$days $hours $minutes")
|
||||||
|
return "${if (days != 0L) "$days" + "d " else ""}${if (hours != 0L) "$hours" + "h " else ""}${minutes}m"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val apiUrl = "https://api.thenos.org"
|
||||||
|
|
||||||
|
override fun quickSearch(query: String): List<SearchResponse> {
|
||||||
|
val url = "$apiUrl/library/search?query=$query"
|
||||||
|
return searchFromUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ThenosSearchResponse(
|
||||||
|
@JsonProperty("size") val size: Int?,
|
||||||
|
@JsonProperty("Hub") val Hub: List<Hub>?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Part(
|
||||||
|
@JsonProperty("id") val id: Long?,
|
||||||
|
@JsonProperty("key") val key: String?,
|
||||||
|
@JsonProperty("duration") val duration: Long?,
|
||||||
|
@JsonProperty("file") val file: String?,
|
||||||
|
@JsonProperty("size") val size: Long?,
|
||||||
|
@JsonProperty("audioProfile") val audioProfile: String?,
|
||||||
|
@JsonProperty("container") val container: String?,
|
||||||
|
@JsonProperty("has64bitOffsets") val has64bitOffsets: Boolean?,
|
||||||
|
@JsonProperty("optimizedForStreaming") val optimizedForStreaming: Boolean?,
|
||||||
|
@JsonProperty("videoProfile") val videoProfile: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Media(
|
||||||
|
@JsonProperty("id") val id: Long?,
|
||||||
|
@JsonProperty("duration") val duration: Long?,
|
||||||
|
@JsonProperty("bitrate") val bitrate: Long?,
|
||||||
|
@JsonProperty("width") val width: Long?,
|
||||||
|
@JsonProperty("height") val height: Long?,
|
||||||
|
@JsonProperty("aspectRatio") val aspectRatio: Double?,
|
||||||
|
@JsonProperty("audioChannels") val audioChannels: Long?,
|
||||||
|
@JsonProperty("audioCodec") val audioCodec: String?,
|
||||||
|
@JsonProperty("videoCodec") val videoCodec: String?,
|
||||||
|
@JsonProperty("videoResolution") val videoResolution: String?,
|
||||||
|
@JsonProperty("container") val container: String?,
|
||||||
|
@JsonProperty("videoFrameRate") val videoFrameRate: String?,
|
||||||
|
@JsonProperty("optimizedForStreaming") val optimizedForStreaming: Long?,
|
||||||
|
@JsonProperty("audioProfile") val audioProfile: String?,
|
||||||
|
@JsonProperty("has64bitOffsets") val has64bitOffsets: Boolean?,
|
||||||
|
@JsonProperty("videoProfile") val videoProfile: String?,
|
||||||
|
@JsonProperty("Part") val Part: List<Part>?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Genre(
|
||||||
|
@JsonProperty("tag") val tag: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
data class Country(
|
||||||
|
@JsonProperty("tag") val tag: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
data class Role(
|
||||||
|
@JsonProperty("tag") val tag: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Hub(
|
||||||
|
@JsonProperty("title") val title: String?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("hubIdentifier") val hubIdentifier: String?,
|
||||||
|
@JsonProperty("context") val context: String?,
|
||||||
|
@JsonProperty("size") val size: Int?,
|
||||||
|
@JsonProperty("more") val more: Boolean?,
|
||||||
|
@JsonProperty("style") val style: String?,
|
||||||
|
@JsonProperty("Metadata") val Metadata: List<Metadata>?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Metadata(
|
||||||
|
@JsonProperty("librarySectionTitle") val librarySectionTitle: String?,
|
||||||
|
@JsonProperty("ratingKey") val ratingKey: String?,
|
||||||
|
@JsonProperty("key") val key: String?,
|
||||||
|
@JsonProperty("guid") val guid: String?,
|
||||||
|
@JsonProperty("studio") val studio: String?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("title") val title: String?,
|
||||||
|
@JsonProperty("librarySectionID") val librarySectionID: Int?,
|
||||||
|
@JsonProperty("librarySectionKey") val librarySectionKey: String?,
|
||||||
|
@JsonProperty("contentRating") val contentRating: String?,
|
||||||
|
@JsonProperty("summary") val summary: String?,
|
||||||
|
@JsonProperty("audienceRating") val audienceRating: Int?,
|
||||||
|
@JsonProperty("year") val year: Int?,
|
||||||
|
@JsonProperty("thumb") val thumb: String?,
|
||||||
|
@JsonProperty("art") val art: String?,
|
||||||
|
@JsonProperty("duration") val duration: Int?,
|
||||||
|
@JsonProperty("originallyAvailableAt") val originallyAvailableAt: String?,
|
||||||
|
@JsonProperty("addedAt") val addedAt: Int?,
|
||||||
|
@JsonProperty("updatedAt") val updatedAt: Int?,
|
||||||
|
@JsonProperty("audienceRatingImage") val audienceRatingImage: String?,
|
||||||
|
@JsonProperty("Media") val Media: List<Media>?,
|
||||||
|
@JsonProperty("Genre") val Genre: List<Genre>?,
|
||||||
|
@JsonProperty("Director") val Director: List<Director>?,
|
||||||
|
@JsonProperty("Country") val Country: List<Country>?,
|
||||||
|
@JsonProperty("Role") val Role: List<Role>?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Director(
|
||||||
|
@JsonProperty("tag") val tag: String
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun Metadata.toSearchResponse(): SearchResponse? {
|
||||||
|
if (type == "movie") {
|
||||||
|
return MovieSearchResponse(
|
||||||
|
title ?: "",
|
||||||
|
ratingKey ?: return null,
|
||||||
|
this@ThenosProvider.name,
|
||||||
|
TvType.Movie,
|
||||||
|
art?.let { "$apiUrl$it" },
|
||||||
|
year
|
||||||
|
|
||||||
|
)
|
||||||
|
} else if (type == "show") {
|
||||||
|
return TvSeriesSearchResponse(
|
||||||
|
title ?: "",
|
||||||
|
ratingKey ?: return null,
|
||||||
|
this@ThenosProvider.name,
|
||||||
|
TvType.TvSeries,
|
||||||
|
art?.let { "$apiUrl$it" },
|
||||||
|
year,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchFromUrl(url: String): List<SearchResponse> {
|
||||||
|
val response = khttp.get(url)
|
||||||
|
val test = mapper.readValue<ThenosSearchResponse>(response.text)
|
||||||
|
val returnValue = ArrayList<SearchResponse>()
|
||||||
|
|
||||||
|
test.Hub?.forEach {
|
||||||
|
it.Metadata?.forEach metadata@{
|
||||||
|
if (it.ratingKey == null || it.title == null) return@metadata
|
||||||
|
it.toSearchResponse()?.let { response -> returnValue.add(response) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun search(query: String): List<SearchResponse> {
|
||||||
|
val url = "$apiUrl/library/search/advance?query=$query"
|
||||||
|
return searchFromUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ThenosSource(
|
||||||
|
@JsonProperty("title") val title: String?,
|
||||||
|
@JsonProperty("image") val image: String?,
|
||||||
|
@JsonProperty("sources") val sources: List<Sources>?,
|
||||||
|
@JsonProperty("tracks") val tracks: List<Tracks>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Sources(
|
||||||
|
@JsonProperty("file") val file: String?,
|
||||||
|
@JsonProperty("label") val label: String?,
|
||||||
|
@JsonProperty("default") val default: Boolean?,
|
||||||
|
@JsonProperty("type") val type: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Tracks(
|
||||||
|
@JsonProperty("file") val file: String?,
|
||||||
|
@JsonProperty("label") val label: String?,
|
||||||
|
@JsonProperty("kind") val kind: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
val url = "https://api.thenos.org/library/watch/$data"
|
||||||
|
val response = khttp.get(url)
|
||||||
|
val mapped = mapper.readValue<ThenosSource>(response.text)
|
||||||
|
|
||||||
|
mapped.sources?.forEach {
|
||||||
|
val isM3u8 = it.type != "video/mp4"
|
||||||
|
val token = khttp.get("https://token.noss.workers.dev/").text
|
||||||
|
val authorization =
|
||||||
|
String(android.util.Base64.decode(token, android.util.Base64.DEFAULT), Charsets.ISO_8859_1)
|
||||||
|
callback.invoke(
|
||||||
|
ExtractorLink(
|
||||||
|
this.name,
|
||||||
|
"${this.name} ${it.label ?: ""}",
|
||||||
|
(it.file)?.split("/")?.lastOrNull()?.let {
|
||||||
|
"https://www.googleapis.com/drive/v3/files/$it?alt=media"
|
||||||
|
} ?: return@forEach,
|
||||||
|
"https://www.thenos.org/",
|
||||||
|
getQualityFromName(it.label ?: ""),
|
||||||
|
isM3u8,
|
||||||
|
mapOf("authorization" to "Bearer $authorization")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped.tracks.forEach {
|
||||||
|
subtitleCallback.invoke(
|
||||||
|
SubtitleFile(
|
||||||
|
it.label ?: "English",
|
||||||
|
it.file ?: return@forEach
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ThenosLoadResponse(
|
||||||
|
@JsonProperty("size") val size: Long?,
|
||||||
|
@JsonProperty("allowSync") val allowSync: Boolean?,
|
||||||
|
@JsonProperty("augmentationKey") val augmentationKey: String?,
|
||||||
|
@JsonProperty("identifier") val identifier: String?,
|
||||||
|
@JsonProperty("librarySectionID") val librarySectionID: Long?,
|
||||||
|
@JsonProperty("librarySectionTitle") val librarySectionTitle: String?,
|
||||||
|
@JsonProperty("librarySectionUUID") val librarySectionUUID: String?,
|
||||||
|
@JsonProperty("mediaTagPrefix") val mediaTagPrefix: String?,
|
||||||
|
@JsonProperty("mediaTagVersion") val mediaTagVersion: Long?,
|
||||||
|
@JsonProperty("Metadata") val Metadata: List<Metadata?>?
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
data class ThenosSeriesResponse(
|
||||||
|
@JsonProperty("size") val size: Long?,
|
||||||
|
@JsonProperty("allowSync") val allowSync: Boolean?,
|
||||||
|
@JsonProperty("art") val art: String?,
|
||||||
|
@JsonProperty("identifier") val identifier: String?,
|
||||||
|
@JsonProperty("key") val key: String?,
|
||||||
|
@JsonProperty("librarySectionID") val librarySectionID: Long?,
|
||||||
|
@JsonProperty("librarySectionTitle") val librarySectionTitle: String?,
|
||||||
|
@JsonProperty("librarySectionUUID") val librarySectionUUID: String?,
|
||||||
|
@JsonProperty("mediaTagPrefix") val mediaTagPrefix: String?,
|
||||||
|
@JsonProperty("mediaTagVersion") val mediaTagVersion: Long?,
|
||||||
|
@JsonProperty("nocache") val nocache: Boolean?,
|
||||||
|
@JsonProperty("parentIndex") val parentIndex: Long?,
|
||||||
|
@JsonProperty("parentTitle") val parentTitle: String?,
|
||||||
|
@JsonProperty("parentYear") val parentYear: Long?,
|
||||||
|
@JsonProperty("summary") val summary: String?,
|
||||||
|
@JsonProperty("theme") val theme: String?,
|
||||||
|
@JsonProperty("thumb") val thumb: String?,
|
||||||
|
@JsonProperty("title1") val title1: String?,
|
||||||
|
@JsonProperty("title2") val title2: String?,
|
||||||
|
@JsonProperty("viewGroup") val viewGroup: String?,
|
||||||
|
@JsonProperty("viewMode") val viewMode: Long?,
|
||||||
|
@JsonProperty("Metadata") val Metadata: List<SeriesMetadata>?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SeriesMetadata(
|
||||||
|
@JsonProperty("ratingKey") val ratingKey: String?,
|
||||||
|
@JsonProperty("key") val key: String?,
|
||||||
|
@JsonProperty("parentRatingKey") val parentRatingKey: String?,
|
||||||
|
@JsonProperty("guid") val guid: String?,
|
||||||
|
@JsonProperty("parentGuid") val parentGuid: String?,
|
||||||
|
@JsonProperty("parentStudio") val parentStudio: String?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("title") val title: String?,
|
||||||
|
@JsonProperty("parentKey") val parentKey: String?,
|
||||||
|
@JsonProperty("parentTitle") val parentTitle: String?,
|
||||||
|
@JsonProperty("summary") val summary: String?,
|
||||||
|
@JsonProperty("index") val index: Long?,
|
||||||
|
@JsonProperty("parentIndex") val parentIndex: Long?,
|
||||||
|
@JsonProperty("parentYear") val parentYear: Long?,
|
||||||
|
@JsonProperty("thumb") val thumb: String?,
|
||||||
|
@JsonProperty("art") val art: String?,
|
||||||
|
@JsonProperty("parentThumb") val parentThumb: String?,
|
||||||
|
@JsonProperty("parentTheme") val parentTheme: String?,
|
||||||
|
@JsonProperty("leafCount") val leafCount: Long?,
|
||||||
|
@JsonProperty("viewedLeafCount") val viewedLeafCount: Long?,
|
||||||
|
@JsonProperty("addedAt") val addedAt: Long?,
|
||||||
|
@JsonProperty("updatedAt") val updatedAt: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SeasonResponse(
|
||||||
|
@JsonProperty("size") val size: Long?,
|
||||||
|
@JsonProperty("allowSync") val allowSync: Boolean?,
|
||||||
|
@JsonProperty("art") val art: String?,
|
||||||
|
@JsonProperty("grandparentContentRating") val grandparentContentRating: String?,
|
||||||
|
@JsonProperty("grandparentRatingKey") val grandparentRatingKey: Long?,
|
||||||
|
@JsonProperty("grandparentStudio") val grandparentStudio: String?,
|
||||||
|
@JsonProperty("grandparentTheme") val grandparentTheme: String?,
|
||||||
|
@JsonProperty("grandparentThumb") val grandparentThumb: String?,
|
||||||
|
@JsonProperty("grandparentTitle") val grandparentTitle: String?,
|
||||||
|
@JsonProperty("identifier") val identifier: String?,
|
||||||
|
@JsonProperty("key") val key: String?,
|
||||||
|
@JsonProperty("librarySectionID") val librarySectionID: Long?,
|
||||||
|
@JsonProperty("librarySectionTitle") val librarySectionTitle: String?,
|
||||||
|
@JsonProperty("librarySectionUUID") val librarySectionUUID: String?,
|
||||||
|
@JsonProperty("mediaTagPrefix") val mediaTagPrefix: String?,
|
||||||
|
@JsonProperty("mediaTagVersion") val mediaTagVersion: Long?,
|
||||||
|
@JsonProperty("nocache") val nocache: Boolean?,
|
||||||
|
@JsonProperty("parentIndex") val parentIndex: Long?,
|
||||||
|
@JsonProperty("parentTitle") val parentTitle: String?,
|
||||||
|
@JsonProperty("summary") val summary: String?,
|
||||||
|
@JsonProperty("theme") val theme: String?,
|
||||||
|
@JsonProperty("thumb") val thumb: String?,
|
||||||
|
@JsonProperty("title1") val title1: String?,
|
||||||
|
@JsonProperty("title2") val title2: String?,
|
||||||
|
@JsonProperty("viewGroup") val viewGroup: String?,
|
||||||
|
@JsonProperty("viewMode") val viewMode: Long?,
|
||||||
|
@JsonProperty("Metadata") val Metadata: List<SeasonMetadata>?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SeasonMetadata(
|
||||||
|
@JsonProperty("ratingKey") val ratingKey: String?,
|
||||||
|
@JsonProperty("key") val key: String?,
|
||||||
|
@JsonProperty("parentRatingKey") val parentRatingKey: String?,
|
||||||
|
@JsonProperty("grandparentRatingKey") val grandparentRatingKey: String?,
|
||||||
|
@JsonProperty("guid") val guid: String?,
|
||||||
|
@JsonProperty("parentGuid") val parentGuid: String?,
|
||||||
|
@JsonProperty("grandparentGuid") val grandparentGuid: String?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("title") val title: String?,
|
||||||
|
@JsonProperty("grandparentKey") val grandparentKey: String?,
|
||||||
|
@JsonProperty("parentKey") val parentKey: String?,
|
||||||
|
@JsonProperty("grandparentTitle") val grandparentTitle: String?,
|
||||||
|
@JsonProperty("parentTitle") val parentTitle: String?,
|
||||||
|
@JsonProperty("contentRating") val contentRating: String?,
|
||||||
|
@JsonProperty("summary") val summary: String?,
|
||||||
|
@JsonProperty("index") val index: Int?,
|
||||||
|
@JsonProperty("parentIndex") val parentIndex: Int?,
|
||||||
|
@JsonProperty("audienceRating") val audienceRating: Double?,
|
||||||
|
@JsonProperty("thumb") val thumb: String?,
|
||||||
|
@JsonProperty("art") val art: String?,
|
||||||
|
@JsonProperty("parentThumb") val parentThumb: String?,
|
||||||
|
@JsonProperty("grandparentThumb") val grandparentThumb: String?,
|
||||||
|
@JsonProperty("grandparentArt") val grandparentArt: String?,
|
||||||
|
@JsonProperty("grandparentTheme") val grandparentTheme: String?,
|
||||||
|
@JsonProperty("duration") val duration: Long?,
|
||||||
|
@JsonProperty("originallyAvailableAt") val originallyAvailableAt: String?,
|
||||||
|
@JsonProperty("addedAt") val addedAt: Long?,
|
||||||
|
@JsonProperty("updatedAt") val updatedAt: Long?,
|
||||||
|
@JsonProperty("audienceRatingImage") val audienceRatingImage: String?,
|
||||||
|
@JsonProperty("Media") val Media: List<Media>?,
|
||||||
|
@JsonProperty("Director") val Director: List<Director>?,
|
||||||
|
@JsonProperty("Role") val Role: List<Role>?
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getAllEpisodes(id: String): List<TvSeriesEpisode> {
|
||||||
|
val episodes = ArrayList<TvSeriesEpisode>()
|
||||||
|
val url = "$apiUrl/library/metadata/$id/children"
|
||||||
|
val response = khttp.get(url)
|
||||||
|
val mapped = mapper.readValue<ThenosSeriesResponse>(response.text)
|
||||||
|
mapped.Metadata?.forEach {
|
||||||
|
val fixedUrl = "https://api.thenos.org" + it.key
|
||||||
|
val child = khttp.get(fixedUrl)
|
||||||
|
val mappedSeason = mapper.readValue<SeasonResponse>(child.text)
|
||||||
|
mappedSeason.Metadata?.forEach mappedSeason@{
|
||||||
|
episodes.add(
|
||||||
|
TvSeriesEpisode(
|
||||||
|
it.title,
|
||||||
|
it.parentIndex,
|
||||||
|
it.index,
|
||||||
|
it.ratingKey ?: return@mappedSeason,
|
||||||
|
it.parentThumb?.let { "$apiUrl$it" },
|
||||||
|
it.originallyAvailableAt,
|
||||||
|
(it.audienceRating?.times(10))?.toInt(),
|
||||||
|
it.summary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return episodes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun load(url: String): LoadResponse? {
|
||||||
|
val fixedUrl = "$apiUrl/library/metadata/${url.split("/").last()}"
|
||||||
|
val response = khttp.get(fixedUrl)
|
||||||
|
val mapped = mapper.readValue<ThenosLoadResponse>(response.text)
|
||||||
|
|
||||||
|
val isShow = mapped.Metadata?.any { it?.type == "show" } == true
|
||||||
|
val metadata = mapped.Metadata?.getOrNull(0) ?: return null
|
||||||
|
|
||||||
|
return if (!isShow) {
|
||||||
|
MovieLoadResponse(
|
||||||
|
metadata.title ?: "No title found",
|
||||||
|
"$mainUrl/movie/${metadata.ratingKey}",
|
||||||
|
this.name,
|
||||||
|
TvType.Movie,
|
||||||
|
metadata.ratingKey ?: return null,
|
||||||
|
metadata.art?.let { "$apiUrl$it" },
|
||||||
|
metadata.year,
|
||||||
|
metadata.summary,
|
||||||
|
null, // with Guid this is possible
|
||||||
|
metadata.audienceRating?.times(10),
|
||||||
|
metadata.Genre?.mapNotNull { it.tag },
|
||||||
|
metadata.duration?.let { secondsToReadable(it / 1000, "") },
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TvSeriesLoadResponse(
|
||||||
|
metadata.title ?: "No title found",
|
||||||
|
"$mainUrl/show/${metadata.ratingKey}",
|
||||||
|
this.name,
|
||||||
|
TvType.TvSeries,
|
||||||
|
metadata.ratingKey?.let { getAllEpisodes(it) } ?: return null,
|
||||||
|
metadata.art?.let { "$apiUrl$it" },
|
||||||
|
metadata.year,
|
||||||
|
metadata.summary,
|
||||||
|
null, // with Guid this is possible
|
||||||
|
null,// with Guid this is possible
|
||||||
|
metadata.audienceRating?.times(10),
|
||||||
|
metadata.Genre?.mapNotNull { it.tag },
|
||||||
|
metadata.duration?.let { secondsToReadable(it / 1000, "") },
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1797,7 +1797,7 @@ class PlayerFragment : Fragment() {
|
||||||
"sec-fetch-user" to "?1",
|
"sec-fetch-user" to "?1",
|
||||||
"sec-fetch-mode" to "navigate",
|
"sec-fetch-mode" to "navigate",
|
||||||
"sec-fetch-dest" to "video"
|
"sec-fetch-dest" to "video"
|
||||||
)
|
) + currentUrl.headers // Adds the headers from the provider, e.g Authorization
|
||||||
setDefaultRequestProperties(headers)
|
setDefaultRequestProperties(headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,14 @@ data class ExtractorLink(
|
||||||
override val referer: String,
|
override val referer: String,
|
||||||
val quality: Int,
|
val quality: Int,
|
||||||
val isM3u8: Boolean = false,
|
val isM3u8: Boolean = false,
|
||||||
|
override val headers: Map<String, String> = mapOf()
|
||||||
) : VideoDownloadManager.IDownloadableMinimum
|
) : VideoDownloadManager.IDownloadableMinimum
|
||||||
|
|
||||||
data class ExtractorSubtitleLink(
|
data class ExtractorSubtitleLink(
|
||||||
val name: String,
|
val name: String,
|
||||||
override val url: String,
|
override val url: String,
|
||||||
override val referer: String,
|
override val referer: String,
|
||||||
|
override val headers: Map<String, String> = mapOf()
|
||||||
) : VideoDownloadManager.IDownloadableMinimum
|
) : VideoDownloadManager.IDownloadableMinimum
|
||||||
|
|
||||||
enum class Qualities(var value: Int) {
|
enum class Qualities(var value: Int) {
|
||||||
|
|
|
@ -96,6 +96,7 @@ object VideoDownloadManager {
|
||||||
interface IDownloadableMinimum {
|
interface IDownloadableMinimum {
|
||||||
val url: String
|
val url: String
|
||||||
val referer: String
|
val referer: String
|
||||||
|
val headers: Map<String, String>
|
||||||
}
|
}
|
||||||
|
|
||||||
fun IDownloadableMinimum.getId(): Int {
|
fun IDownloadableMinimum.getId(): Int {
|
||||||
|
@ -833,6 +834,9 @@ object VideoDownloadManager {
|
||||||
connection.setRequestProperty("sec-fetch-user", "?1")
|
connection.setRequestProperty("sec-fetch-user", "?1")
|
||||||
connection.setRequestProperty("sec-fetch-mode", "navigate")
|
connection.setRequestProperty("sec-fetch-mode", "navigate")
|
||||||
connection.setRequestProperty("sec-fetch-dest", "video")
|
connection.setRequestProperty("sec-fetch-dest", "video")
|
||||||
|
link.headers.entries.forEach {
|
||||||
|
connection.setRequestProperty(it.key, it.value)
|
||||||
|
}
|
||||||
|
|
||||||
if (resume)
|
if (resume)
|
||||||
connection.setRequestProperty("Range", "bytes=${fileLength}-")
|
connection.setRequestProperty("Range", "bytes=${fileLength}-")
|
||||||
|
|
Loading…
Reference in a new issue