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…
	
	Add table
		Add a link
		
	
		Reference in a new issue