2022-11-16 15:37:58 +00:00
|
|
|
package com.hexated
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty
|
|
|
|
import com.lagradost.cloudstream3.*
|
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
|
|
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
|
|
|
import com.lagradost.cloudstream3.utils.Qualities
|
|
|
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
|
|
|
|
2023-07-25 07:46:32 +00:00
|
|
|
open class XCine : MainAPI() {
|
2023-01-02 02:45:14 +00:00
|
|
|
override var name = "XCine"
|
2023-07-25 07:46:32 +00:00
|
|
|
override var mainUrl = "https://xcine.ru"
|
2022-11-16 15:37:58 +00:00
|
|
|
override var lang = "de"
|
|
|
|
override val hasQuickSearch = true
|
|
|
|
override val usesWebView = false
|
|
|
|
override val hasMainPage = true
|
|
|
|
override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie)
|
2023-07-25 07:46:32 +00:00
|
|
|
open var mainAPI = "https://api.xcine.ru"
|
2022-11-16 15:37:58 +00:00
|
|
|
|
|
|
|
override val mainPage = mainPageOf(
|
2023-07-25 07:46:32 +00:00
|
|
|
"data/browse/?lang=2&keyword=&year=&rating=&votes=&genre=&country=&cast=&directors=&type=movies&order_by=trending" to "Trending",
|
|
|
|
"data/browse/?lang=2&keyword=&year=&rating=&votes=&genre=&country=&cast=&directors=&type=movies&order_by=Views" to "Most View Filme",
|
|
|
|
"data/browse/?lang=2&keyword=&year=&rating=&votes=&genre=&country=&cast=&directors=&type=tvseries&order_by=Trending" to "Trending Serien",
|
|
|
|
"data/browse/?lang=2&keyword=&year=&rating=&votes=&genre=&country=&cast=&directors=&type=movies&order_by=Updates" to "Updated Filme",
|
|
|
|
"data/browse/?lang=2&keyword=&year=&rating=&votes=&genre=&country=&cast=&directors=&type=tvseries&order_by=Updates" to "Updated Serien",
|
2022-11-16 15:37:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
private fun getImageUrl(link: String?): String? {
|
|
|
|
if (link == null) return null
|
|
|
|
return if (link.startsWith("/")) "https://image.tmdb.org/t/p/w500/$link" else link
|
|
|
|
}
|
|
|
|
|
2023-07-25 07:46:32 +00:00
|
|
|
private fun getBackupImageUrl(link: String?): String? {
|
|
|
|
if (link == null) return null
|
|
|
|
return "https://cdn.movie4k.stream/data${link.substringAfter("/data")}"
|
|
|
|
}
|
|
|
|
|
2022-11-16 15:37:58 +00:00
|
|
|
override suspend fun getMainPage(
|
|
|
|
page: Int,
|
|
|
|
request: MainPageRequest
|
|
|
|
): HomePageResponse {
|
|
|
|
val home =
|
2023-07-25 07:46:32 +00:00
|
|
|
app.get("$mainAPI/${request.data}&page=$page", referer = "$mainUrl/")
|
2022-11-16 15:37:58 +00:00
|
|
|
.parsedSafe<MediaResponse>()?.movies?.mapNotNull { res ->
|
|
|
|
res.toSearchResponse()
|
|
|
|
} ?: throw ErrorLoadingException()
|
|
|
|
return newHomePageResponse(request.name, home)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun Media.toSearchResponse(): SearchResponse? {
|
|
|
|
return newAnimeSearchResponse(
|
|
|
|
title ?: original_title ?: return null,
|
|
|
|
// Data(_id).toJson(),
|
2023-07-25 07:46:32 +00:00
|
|
|
Link(id=_id).toJson(),
|
2022-11-16 15:37:58 +00:00
|
|
|
TvType.TvSeries,
|
|
|
|
false
|
|
|
|
) {
|
2023-07-25 07:46:32 +00:00
|
|
|
this.posterUrl = getImageUrl(poster_path ?: backdrop_path) ?: getBackupImageUrl(img)
|
2022-11-16 15:37:58 +00:00
|
|
|
addDub(last_updated_epi?.toIntOrNull())
|
|
|
|
addSub(totalEpisodes?.toIntOrNull())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override suspend fun quickSearch(query: String): List<SearchResponse> = search(query)
|
|
|
|
|
|
|
|
override suspend fun search(query: String): List<SearchResponse> {
|
2023-07-25 07:46:32 +00:00
|
|
|
val res = app.get("$mainAPI/data/search/?lang=2&keyword=$query", referer = "$mainUrl/").text
|
|
|
|
return tryParseJson<ArrayList<Media>>(res)?.mapNotNull {
|
|
|
|
it.toSearchResponse()
|
2022-11-16 15:37:58 +00:00
|
|
|
} ?: throw ErrorLoadingException()
|
|
|
|
}
|
|
|
|
|
|
|
|
override suspend fun load(url: String): LoadResponse? {
|
2023-07-25 07:46:32 +00:00
|
|
|
val id = parseJson<Link>(url).id
|
2022-11-16 15:37:58 +00:00
|
|
|
|
|
|
|
val res = app.get("$mainAPI/data/watch/?_id=$id", referer = "$mainUrl/")
|
2022-11-28 09:57:03 +00:00
|
|
|
.parsedSafe<MediaDetail>() ?: throw ErrorLoadingException()
|
2022-11-16 15:37:58 +00:00
|
|
|
val type = if (res.tv == 1) "tv" else "movie"
|
|
|
|
|
|
|
|
val recommendations =
|
|
|
|
app.get("$mainAPI/data/related_movies/?lang=2&cat=$type&_id=$id&server=0").text.let {
|
|
|
|
tryParseJson<List<Media>>(it)
|
|
|
|
}?.mapNotNull {
|
|
|
|
it.toSearchResponse()
|
|
|
|
}
|
|
|
|
|
|
|
|
return if (type == "tv") {
|
2023-07-25 07:46:32 +00:00
|
|
|
val episodes = res.streams?.groupBy { it.e.toString().toIntOrNull() }?.mapNotNull { eps ->
|
|
|
|
val epsNum = eps.key
|
|
|
|
val epsLink = eps.value.map { it.stream }.toJson()
|
|
|
|
Episode(epsLink, episode = epsNum)
|
|
|
|
} ?: emptyList()
|
2022-11-16 15:37:58 +00:00
|
|
|
newTvSeriesLoadResponse(
|
2023-07-25 07:46:32 +00:00
|
|
|
res.title ?: res.original_title ?: return null,
|
2022-11-16 15:37:58 +00:00
|
|
|
url,
|
|
|
|
TvType.TvSeries,
|
|
|
|
episodes
|
|
|
|
) {
|
|
|
|
this.posterUrl = getImageUrl(res.backdrop_path ?: res.poster_path)
|
|
|
|
this.year = res.year
|
|
|
|
this.plot = res.storyline ?: res.overview
|
|
|
|
this.tags = listOf(res.genres ?: "")
|
|
|
|
this.recommendations = recommendations
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
newMovieLoadResponse(
|
|
|
|
res.original_title ?: res.title ?: return null,
|
|
|
|
url,
|
|
|
|
TvType.Movie,
|
|
|
|
res.streams?.map { Link(it.stream) }?.toJson()
|
|
|
|
) {
|
|
|
|
this.posterUrl = getImageUrl(res.backdrop_path ?: res.poster_path)
|
|
|
|
this.year = res.year
|
|
|
|
this.plot = res.storyline ?: res.overview
|
|
|
|
this.tags = listOf(res.genres ?: "")
|
|
|
|
this.recommendations = recommendations
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override suspend fun loadLinks(
|
|
|
|
data: String,
|
|
|
|
isCasting: Boolean,
|
|
|
|
subtitleCallback: (SubtitleFile) -> Unit,
|
|
|
|
callback: (ExtractorLink) -> Unit
|
|
|
|
): Boolean {
|
|
|
|
|
|
|
|
val loadData = parseJson<List<Link>>(data)
|
|
|
|
loadData.apmap {
|
|
|
|
val link = fixUrlNull(it.link) ?: return@apmap null
|
2023-07-25 07:46:32 +00:00
|
|
|
if (link.startsWith("https://dl.streamcloud")) {
|
2022-11-16 15:37:58 +00:00
|
|
|
callback.invoke(
|
|
|
|
ExtractorLink(
|
|
|
|
this.name,
|
|
|
|
this.name,
|
|
|
|
link,
|
|
|
|
"",
|
|
|
|
Qualities.Unknown.value
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
loadExtractor(
|
|
|
|
link,
|
|
|
|
"$mainUrl/",
|
|
|
|
subtitleCallback,
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
data class Link(
|
2023-07-25 07:46:32 +00:00
|
|
|
val link: String? = null,
|
|
|
|
val id: String? = null,
|
2022-11-16 15:37:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
data class Season(
|
|
|
|
@JsonProperty("_id") val _id: String? = null,
|
|
|
|
@JsonProperty("s") val s: Int? = null,
|
|
|
|
@JsonProperty("title") val title: String? = null,
|
|
|
|
@JsonProperty("year") val year: Int? = null,
|
|
|
|
@JsonProperty("streams") val streams: ArrayList<Streams>? = arrayListOf(),
|
|
|
|
)
|
|
|
|
|
|
|
|
data class Streams(
|
|
|
|
@JsonProperty("_id") val _id: String? = null,
|
|
|
|
@JsonProperty("stream") val stream: String? = null,
|
2023-07-25 07:46:32 +00:00
|
|
|
@JsonProperty("e") val e: Any? = null,
|
2022-11-16 15:37:58 +00:00
|
|
|
@JsonProperty("e_title") val e_title: String? = null,
|
|
|
|
)
|
|
|
|
|
|
|
|
data class MediaDetail(
|
|
|
|
@JsonProperty("_id") val _id: String? = null,
|
|
|
|
@JsonProperty("tv") val tv: Int? = null,
|
|
|
|
@JsonProperty("original_title") val original_title: String? = null,
|
|
|
|
@JsonProperty("title") val title: String? = null,
|
|
|
|
@JsonProperty("poster_path") val poster_path: String? = null,
|
|
|
|
@JsonProperty("backdrop_path") val backdrop_path: String? = null,
|
|
|
|
@JsonProperty("imdb_id") val imdb_id: String? = null,
|
|
|
|
@JsonProperty("year") val year: Int? = null,
|
|
|
|
@JsonProperty("rating") val rating: String? = null,
|
|
|
|
@JsonProperty("genres") val genres: String? = null,
|
|
|
|
@JsonProperty("storyline") val storyline: String? = null,
|
|
|
|
@JsonProperty("overview") val overview: String? = null,
|
|
|
|
@JsonProperty("streams") val streams: ArrayList<Streams>? = arrayListOf(),
|
|
|
|
)
|
|
|
|
|
|
|
|
data class Media(
|
|
|
|
@JsonProperty("_id") val _id: String? = null,
|
|
|
|
@JsonProperty("original_title") val original_title: String? = null,
|
|
|
|
@JsonProperty("title") val title: String? = null,
|
|
|
|
@JsonProperty("poster_path") val poster_path: String? = null,
|
|
|
|
@JsonProperty("backdrop_path") val backdrop_path: String? = null,
|
2023-07-25 07:46:32 +00:00
|
|
|
@JsonProperty("img") val img: String? = null,
|
2022-11-16 15:37:58 +00:00
|
|
|
@JsonProperty("imdb_id") val imdb_id: String? = null,
|
|
|
|
@JsonProperty("totalEpisodes") val totalEpisodes: String? = null,
|
|
|
|
@JsonProperty("last_updated_epi") val last_updated_epi: String? = null,
|
|
|
|
)
|
|
|
|
|
|
|
|
data class MediaResponse(
|
|
|
|
@JsonProperty("movies") val movies: ArrayList<Media>? = arrayListOf(),
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|