mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
added Moflix #347
This commit is contained in:
parent
c85b95c444
commit
b351a9e62d
5 changed files with 424 additions and 0 deletions
26
Moflix/build.gradle.kts
Normal file
26
Moflix/build.gradle.kts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// use an integer for version numbers
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
|
||||||
|
cloudstream {
|
||||||
|
language = "de"
|
||||||
|
// All of these properties are optional, you can safely remove them
|
||||||
|
|
||||||
|
// description = "Lorem Ipsum"
|
||||||
|
authors = listOf("Hexated")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status int as the following:
|
||||||
|
* 0: Down
|
||||||
|
* 1: Ok
|
||||||
|
* 2: Slow
|
||||||
|
* 3: Beta only
|
||||||
|
* */
|
||||||
|
status = 1 // will be 3 if unspecified
|
||||||
|
tvTypes = listOf(
|
||||||
|
"TvSeries",
|
||||||
|
"Movie",
|
||||||
|
)
|
||||||
|
|
||||||
|
iconUrl = "https://www.google.com/s2/favicons?domain=moflix-stream.xyz&sz=%size%"
|
||||||
|
}
|
2
Moflix/src/main/AndroidManifest.xml
Normal file
2
Moflix/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="com.hexated"/>
|
52
Moflix/src/main/kotlin/com/hexated/Extractors.kt
Normal file
52
Moflix/src/main/kotlin/com/hexated/Extractors.kt
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.SubtitleFile
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
|
||||||
|
class MoflixLink : MoflixClick() {
|
||||||
|
override val name = "MoflixLink"
|
||||||
|
override val mainUrl = "https://moflix-stream.link"
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoflixFans : MoflixClick() {
|
||||||
|
override val name = "MoflixFans"
|
||||||
|
override val mainUrl = "https://moflix-stream.fans"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Highstream : MoflixClick() {
|
||||||
|
override val name = "Highstream"
|
||||||
|
override val mainUrl = "https://highstream.tv"
|
||||||
|
}
|
||||||
|
|
||||||
|
open class MoflixClick : ExtractorApi() {
|
||||||
|
override val name = "MoflixClick"
|
||||||
|
override val mainUrl = "https://moflix-stream.click"
|
||||||
|
override val requiresReferer = true
|
||||||
|
|
||||||
|
override suspend fun getUrl(
|
||||||
|
url: String,
|
||||||
|
referer: String?,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
val response = app.get(url, referer = referer)
|
||||||
|
val script = if (!getPacked(response.text).isNullOrEmpty()) {
|
||||||
|
getAndUnpack(response.text)
|
||||||
|
} else {
|
||||||
|
response.document.selectFirst("script:containsData(sources:)")?.data()
|
||||||
|
}
|
||||||
|
val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1)
|
||||||
|
callback.invoke(
|
||||||
|
ExtractorLink(
|
||||||
|
name,
|
||||||
|
name,
|
||||||
|
m3u8 ?: return,
|
||||||
|
"$mainUrl/",
|
||||||
|
Qualities.Unknown.value,
|
||||||
|
INFER_TYPE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
326
Moflix/src/main/kotlin/com/hexated/Moflix.kt
Normal file
326
Moflix/src/main/kotlin/com/hexated/Moflix.kt
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class Moflix : MainAPI() {
|
||||||
|
override var name = "Moflix"
|
||||||
|
override var mainUrl = "https://moflix-stream.xyz"
|
||||||
|
override var lang = "de"
|
||||||
|
override val hasMainPage = true
|
||||||
|
override val hasQuickSearch = true
|
||||||
|
override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getType(isSeries: Boolean?): TvType {
|
||||||
|
return when (isSeries) {
|
||||||
|
true -> TvType.TvSeries
|
||||||
|
else -> TvType.Movie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStatus(t: String?): ShowStatus {
|
||||||
|
return when (t) {
|
||||||
|
"ongoing" -> ShowStatus.Ongoing
|
||||||
|
else -> ShowStatus.Completed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val mainPage = mainPageOf(
|
||||||
|
"351" to "Kürzlich hinzugefügt",
|
||||||
|
"345" to "Movie-Datenbank",
|
||||||
|
"352" to "Angesagte Serien",
|
||||||
|
"358" to "Kinder & Familien",
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||||
|
val order = if (request.data == "345") "popularity:desc" else "channelables.order:asc"
|
||||||
|
val home = app.get(
|
||||||
|
"$mainUrl/api/v1/channel/${request.data}?returnContentOnly=true&restriction=&order=$order&paginate=simple&perPage=50&query=&page=$page",
|
||||||
|
referer = "$mainUrl/"
|
||||||
|
).parsedSafe<Responses>()?.pagination?.data?.mapNotNull { it.toSearchResponse() }
|
||||||
|
?: emptyList()
|
||||||
|
|
||||||
|
return newHomePageResponse(request.name, home)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Data.toSearchResponse(): SearchResponse? {
|
||||||
|
return newTvSeriesSearchResponse(
|
||||||
|
this.name ?: return null,
|
||||||
|
"${this.id}",
|
||||||
|
TvType.TvSeries,
|
||||||
|
false
|
||||||
|
) {
|
||||||
|
posterUrl = this@toSearchResponse.poster?.compress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun quickSearch(query: String): List<SearchResponse>? = search(query)
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse>? {
|
||||||
|
return app.get("$mainUrl/api/v1/search/$query?loader=searchPage", referer = "$mainUrl/")
|
||||||
|
.parsedSafe<Responses>()?.results?.mapNotNull { it.toSearchResponse() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(url: String): LoadResponse {
|
||||||
|
val res = app.get(
|
||||||
|
"$mainUrl/api/v1/titles/${url.removePrefix("$mainUrl/")}?loader=titlePage",
|
||||||
|
referer = "$mainUrl/"
|
||||||
|
)
|
||||||
|
.parsedSafe<Responses>()
|
||||||
|
|
||||||
|
val id = res?.title?.id
|
||||||
|
val title = res?.title?.name ?: ""
|
||||||
|
val poster = res?.title?.poster
|
||||||
|
val backdrop = res?.title?.backdrop
|
||||||
|
val tags = res?.title?.keywords?.mapNotNull { it.displayName }
|
||||||
|
val year = res?.title?.year
|
||||||
|
val isSeries = res?.title?.isSeries
|
||||||
|
val type = getType(isSeries)
|
||||||
|
val description = res?.title?.description
|
||||||
|
val trailers = res?.title?.videos?.filter { it.category.equals("trailer", true) }
|
||||||
|
?.mapNotNull { it.src }
|
||||||
|
val rating = "${res?.title?.rating}".toRatingInt()
|
||||||
|
val actors = res?.credits?.actors?.mapNotNull {
|
||||||
|
ActorData(
|
||||||
|
Actor(it.name ?: return@mapNotNull null, it.poster),
|
||||||
|
roleString = it.pivot?.character
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val recommendations = app.get("$mainUrl/api/v1/titles/$id/related", referer = "$mainUrl/")
|
||||||
|
.parsedSafe<Responses>()?.titles?.mapNotNull { it.toSearchResponse() }
|
||||||
|
|
||||||
|
return if (type == TvType.TvSeries) {
|
||||||
|
val episodes = res?.seasons?.data?.mapNotNull { season ->
|
||||||
|
app.get(
|
||||||
|
"$mainUrl/api/v1/titles/${res.title?.id}/seasons/${season.number}?loader=seasonPage",
|
||||||
|
referer = "$mainUrl/"
|
||||||
|
).parsedSafe<Responses>()?.episodes?.data?.map { episode ->
|
||||||
|
val status =
|
||||||
|
if (episode.status.equals("upcoming", true)) " • [UPCOMING]" else ""
|
||||||
|
Episode(
|
||||||
|
LoadData(
|
||||||
|
id,
|
||||||
|
episode.seasonNumber,
|
||||||
|
episode.episodeNumber,
|
||||||
|
res.title?.isSeries
|
||||||
|
).toJson(),
|
||||||
|
episode.name + status,
|
||||||
|
episode.seasonNumber,
|
||||||
|
episode.episodeNumber,
|
||||||
|
episode.poster,
|
||||||
|
episode.rating?.times(10)?.roundToInt(),
|
||||||
|
episode.description,
|
||||||
|
).apply {
|
||||||
|
this.addDate(episode.releaseDate?.substringBefore("T"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.flatten() ?: emptyList()
|
||||||
|
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.backgroundPosterUrl = backdrop
|
||||||
|
this.year = year
|
||||||
|
this.showStatus = getStatus(res?.title?.status)
|
||||||
|
this.plot = description
|
||||||
|
this.tags = tags
|
||||||
|
this.rating = rating
|
||||||
|
this.actors = actors
|
||||||
|
this.recommendations = recommendations
|
||||||
|
addTrailer(trailers)
|
||||||
|
addImdbId(res?.title?.imdbId)
|
||||||
|
addTMDbId(res?.title?.tmdbId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val urls = res?.title?.videos?.filter { it.category.equals("full", true) }
|
||||||
|
|
||||||
|
newMovieLoadResponse(
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
TvType.Movie,
|
||||||
|
LoadData(isSeries = isSeries, urls = urls)
|
||||||
|
) {
|
||||||
|
this.posterUrl = poster
|
||||||
|
this.backgroundPosterUrl = backdrop
|
||||||
|
this.year = year
|
||||||
|
this.comingSoon = res?.title?.status.equals("upcoming", true)
|
||||||
|
this.plot = description
|
||||||
|
this.tags = tags
|
||||||
|
this.rating = rating
|
||||||
|
this.actors = actors
|
||||||
|
this.recommendations = recommendations
|
||||||
|
addTrailer(trailers)
|
||||||
|
addImdbId(res?.title?.imdbId)
|
||||||
|
addTMDbId(res?.title?.tmdbId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
|
val json = parseJson<LoadData>(data)
|
||||||
|
|
||||||
|
val iframes = if (json.isSeries == true) {
|
||||||
|
app.get(
|
||||||
|
"$mainUrl/api/v1/titles/${json.id}/seasons/${json.season}/episodes/${json.episode}?loader=episodePage",
|
||||||
|
referer = "$mainUrl/"
|
||||||
|
).parsedSafe<Episodes>()?.episode?.videos?.filter { it.category.equals("full", true) }
|
||||||
|
} else {
|
||||||
|
json.urls
|
||||||
|
}
|
||||||
|
|
||||||
|
iframes?.apmap { iframe ->
|
||||||
|
loadCustomExtractor(
|
||||||
|
iframe.src ?: return@apmap,
|
||||||
|
"$mainUrl/",
|
||||||
|
subtitleCallback,
|
||||||
|
callback,
|
||||||
|
iframe.quality?.filter { it.isDigit() }?.toIntOrNull()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadCustomExtractor(
|
||||||
|
url: String,
|
||||||
|
referer: String? = null,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit,
|
||||||
|
quality: Int? = null,
|
||||||
|
) {
|
||||||
|
loadExtractor(url, referer, subtitleCallback) { link ->
|
||||||
|
if (link.quality == Qualities.Unknown.value) {
|
||||||
|
callback.invoke(
|
||||||
|
ExtractorLink(
|
||||||
|
link.source,
|
||||||
|
link.name,
|
||||||
|
link.url,
|
||||||
|
link.referer,
|
||||||
|
quality ?: link.quality,
|
||||||
|
link.type,
|
||||||
|
link.headers,
|
||||||
|
link.extractorData
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.compress(): String {
|
||||||
|
return this.replace("/original/", "/w500/")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LoadData(
|
||||||
|
val id: Int? = null,
|
||||||
|
val season: Int? = null,
|
||||||
|
val episode: Int? = null,
|
||||||
|
val isSeries: Boolean? = null,
|
||||||
|
val urls: List<Videos>? = listOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Responses(
|
||||||
|
@JsonProperty("pagination") val pagination: Pagination? = null,
|
||||||
|
@JsonProperty("title") val title: Title? = null,
|
||||||
|
@JsonProperty("credits") val credits: Credits? = null,
|
||||||
|
@JsonProperty("seasons") val seasons: Seasons? = null,
|
||||||
|
@JsonProperty("episodes") val episodes: Episodes? = null,
|
||||||
|
@JsonProperty("titles") val titles: ArrayList<Data>? = arrayListOf(),
|
||||||
|
@JsonProperty("results") val results: ArrayList<Data>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Seasons(
|
||||||
|
@JsonProperty("data") val data: ArrayList<Data>? = arrayListOf(),
|
||||||
|
) {
|
||||||
|
data class Data(
|
||||||
|
@JsonProperty("id") val id: Int? = null,
|
||||||
|
@JsonProperty("number") val number: Int? = null,
|
||||||
|
@JsonProperty("poster") val poster: String? = null,
|
||||||
|
@JsonProperty("release_date") val releaseDate: String? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Episodes(
|
||||||
|
@JsonProperty("data") val data: ArrayList<Data>? = arrayListOf(),
|
||||||
|
@JsonProperty("episode") val episode: Data? = null,
|
||||||
|
) {
|
||||||
|
data class Data(
|
||||||
|
@JsonProperty("id") val id: Int? = null,
|
||||||
|
@JsonProperty("name") val name: String? = null,
|
||||||
|
@JsonProperty("description") val description: String? = null,
|
||||||
|
@JsonProperty("season_number") val seasonNumber: Int? = null,
|
||||||
|
@JsonProperty("episode_number") val episodeNumber: Int? = null,
|
||||||
|
@JsonProperty("rating") val rating: Float? = null,
|
||||||
|
@JsonProperty("poster") val poster: String? = null,
|
||||||
|
@JsonProperty("release_date") val releaseDate: String? = null,
|
||||||
|
@JsonProperty("status") val status: String? = null,
|
||||||
|
@JsonProperty("videos") val videos: ArrayList<Videos>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Pagination(
|
||||||
|
@JsonProperty("data") val data: ArrayList<Data>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Data(
|
||||||
|
@JsonProperty("id") val id: Any? = null,
|
||||||
|
@JsonProperty("name") val name: String? = null,
|
||||||
|
@JsonProperty("poster") val poster: String? = null,
|
||||||
|
@JsonProperty("backdrop") val backdrop: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Credits(
|
||||||
|
@JsonProperty("actors") val actors: ArrayList<Actors>? = arrayListOf(),
|
||||||
|
) {
|
||||||
|
data class Actors(
|
||||||
|
@JsonProperty("name") val name: String? = null,
|
||||||
|
@JsonProperty("poster") val poster: String? = null,
|
||||||
|
@JsonProperty("pivot") val pivot: Pivot? = null,
|
||||||
|
) {
|
||||||
|
data class Pivot(
|
||||||
|
@JsonProperty("character") val character: String? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Videos(
|
||||||
|
@JsonProperty("category") val category: String? = null,
|
||||||
|
@JsonProperty("src") val src: String? = null,
|
||||||
|
@JsonProperty("quality") val quality: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Title(
|
||||||
|
@JsonProperty("id") val id: Int? = null,
|
||||||
|
@JsonProperty("name") val name: String? = null,
|
||||||
|
@JsonProperty("release_date") val releaseDate: String? = null,
|
||||||
|
@JsonProperty("year") val year: Int? = null,
|
||||||
|
@JsonProperty("poster") val poster: String? = null,
|
||||||
|
@JsonProperty("backdrop") val backdrop: String? = null,
|
||||||
|
@JsonProperty("description") val description: String? = null,
|
||||||
|
@JsonProperty("certification") val certification: String? = null,
|
||||||
|
@JsonProperty("rating") val rating: Float? = null,
|
||||||
|
@JsonProperty("imdb_id") val imdbId: String? = null,
|
||||||
|
@JsonProperty("tmdb_id") val tmdbId: String? = null,
|
||||||
|
@JsonProperty("status") val status: String? = null,
|
||||||
|
@JsonProperty("is_series") val isSeries: Boolean? = null,
|
||||||
|
@JsonProperty("videos") val videos: ArrayList<Videos>? = arrayListOf(),
|
||||||
|
@JsonProperty("keywords") val keywords: ArrayList<Keywords>? = arrayListOf(),
|
||||||
|
) {
|
||||||
|
data class Keywords(
|
||||||
|
@JsonProperty("display_name") val displayName: String? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
18
Moflix/src/main/kotlin/com/hexated/MoflixPlugin.kt
Normal file
18
Moflix/src/main/kotlin/com/hexated/MoflixPlugin.kt
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
package com.hexated
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||||
|
import com.lagradost.cloudstream3.plugins.Plugin
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
@CloudstreamPlugin
|
||||||
|
class MoflixPlugin: Plugin() {
|
||||||
|
override fun load(context: Context) {
|
||||||
|
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||||
|
registerMainAPI(Moflix())
|
||||||
|
registerExtractorAPI(MoflixClick())
|
||||||
|
registerExtractorAPI(Highstream())
|
||||||
|
registerExtractorAPI(MoflixFans())
|
||||||
|
registerExtractorAPI(MoflixLink())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue