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