added Moflix #347

This commit is contained in:
alex 2024-01-15 13:24:24 +07:00
parent c85b95c444
commit b351a9e62d
5 changed files with 424 additions and 0 deletions

26
Moflix/build.gradle.kts Normal file
View 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%"
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.hexated"/>

View 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
)
)
}
}

View 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,
)
}
}

View 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())
}
}