From d8ed50157c83222b23e42372ac45a5b5a029c198 Mon Sep 17 00:00:00 2001 From: Cloudburst <18114966+C10udburst@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:14:42 +0200 Subject: [PATCH] added webflix --- WebFlix/build.gradle.kts | 26 ++ WebFlix/icon.png | Bin 0 -> 4839 bytes WebFlix/src/main/AndroidManifest.xml | 2 + .../kotlin/com/lagradost/WebFlixProvider.kt | 222 ++++++++++++++++++ .../com/lagradost/WebFlixProviderPlugin.kt | 19 ++ 5 files changed, 269 insertions(+) create mode 100644 WebFlix/build.gradle.kts create mode 100644 WebFlix/icon.png create mode 100644 WebFlix/src/main/AndroidManifest.xml create mode 100644 WebFlix/src/main/kotlin/com/lagradost/WebFlixProvider.kt create mode 100644 WebFlix/src/main/kotlin/com/lagradost/WebFlixProviderPlugin.kt diff --git a/WebFlix/build.gradle.kts b/WebFlix/build.gradle.kts new file mode 100644 index 0000000..956eb3b --- /dev/null +++ b/WebFlix/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + description = "Adds multiple sites using WebFlix. This includes sites in English, Polish, Portuguese and Arabic" + authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Movies", + "TvSeries", + "Live" + ) + + iconUrl = "https://raw.githubusercontent.com/recloudstream/cloudstream-extensions-multilinugal/master/WebFlix/icon.png" +} diff --git a/WebFlix/icon.png b/WebFlix/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0bd09f707e4c0daed0e652d82b63c8f04749da85 GIT binary patch literal 4839 zcmY*dcQhMr_fM;+-ICTS-kPbjR&7O<#3pvBt#;H*)r@c3uMwNtL8wuyD5X|ZiCH7| zruJ5wq~e#pzrWr;p8K40&$*v_@8{n8+;g5Nn4UTVEe9GgqarX&k9opFW9Xdb6Yuo0kT!VYgJC-nv7Tz%>=&k#6TS5ac4W z&gi^{_FbRPQ1Ba2=GjXjDl9DQ6CC%lg9aKD0~4Set_6wzE7(6?gGmc$(32LHYyy`3 z{B4)A9i_72))d`iw1}#Wo2oMvNt!0J01}%m6rRSZ7%=-S89TuRx|PXRu~FNhs|Dq!uil` zEHOi9YP3sCGAuFXGU`WaWS5JVCCsmfLlx9zzm=)@Fg{kbp%7P?`KHA78-JA1494*MU zDMs5oNSvUplxteCK+z87~=3htC9utCFwYQ$CaL1Dd7f zedwFMq8sy7qS|IjvTrJQ{UkYOF_w`My#sEC)Hl|JKD_>VA+R(1TxKA2x+RRDRhB ztcM-Q^+N(EZfLe8mNY?~8ZlP?m|x*J`vb>4g1^gwXr$odlQP#&sREE8{e-&6Lk{V{ z?}jN{MpvK0dmmp>28z=3wowAD5Cg7{z5Q;s!#XsqO4>C-4aN_)X1wF*o@alzBvSOb z9JHvV>Rh^Ul!n1+%vk+paeS3yvW=!qu3Cs6u(OH#{jOQaq<2CrH|n;);ALzq zcP~tQDPVPr$WopL@;Y9r)?Ga{4ON(X0-wkA!)O?d#_B1$;^jMw1~PwM*x86zw0hwm6E=Xz5FgsO z&%eJK(H?IO%7*homtuCyN|}DGJm~x6k1uZhzJ*uWnyJ=aSMDO4K5Sfm9p2YyTnov8 z4hvB0TAsQ;SG~>W4eb%CPsmAz8C$2~Hb!#y(f6H4>qknJZ2HMY&GmA7W+L0ezh9V2 zp+v^SA^IXjsq(zYL|KQipBrgahcOh<-*&EE9~k&bBgP9>vS2;4?u0q6Jq(ts$&Hi+Km0jaulV+^vgR>2r(*vIZX8aSD9|eZ zHc)hhk=enhz6-lCtAaQ2`Qb^K75c9o$e#ZcD(|WrH2>?2P&EfX1bTYeozP%ySnnl<*XEs|18Ggf^Wes|5ip1 zQs{SI4;!0Y6TjGT7XJn=3Rh0gk8w6~_Rwjr-C?WX5j8*xT3M@CxHwSONbi5a;aN`4 zR|I4cw^IL>$eEZBgqOY0@(|8r*{T)q_MqRrNG7tjtW}j%ia6;`H~ce>%#T#+Hl?PE zLubSklu^#&qQJmm21rbYoIs1Gy>1zd-lYT=#q%dp(PPwqGLbwfWK$HUUPzHXm+(=3 zf>NR}rvmzTvuiTgmQ31!IlC{7XQ{vFYM<-@h3Vvw$Uh>=jllw0gf07jg%95miCoQ3gEK`bm?<~U8CoFS$ja+R_*Gm%}lR?{Q1wC*}sz1eEpv*|f`TpaD; zRQL#V88n@B7QVZ$;jYrCUTT%T)GO$jR|SXs=C$W%+z{m;RS;hPBu&Uqu4EnlB7iLZ zh}~XxQw4&F5MIUK9fg(;R2uz{cqC_8>ugero)XlAv1<;o!|^b4<^4uJac1gcVozWW zp>DrPb!gmk#CC6of>e9X&FlMt#3|?$UbU&uYEq*BHy|)gKUve94E+x^Gtf$=0{bz( znvOYEWz!4@fGl#rz<@2;H3Usq}=edMZF}`zrX!x4WB`CGN z6G1`~i0Wr&^Lj)Yj~gA2ep6^&wJmeGuw02eM86DAK1)#n=VEXS!29fzlgG8YWv^H7 z+wvXsJ30Q+4!&z~8~^%_(Ejv%&xOvx?AbAa72f*itdpuJRO>a7;XRiSr};K2U({;{ z1XZPkSLa#D@u##zAWGN+gX|^K5Hi4bk9O&J)-+=Zu51K7>4YXjH^+nvXgrWxC8GCw zN8>#Q)xmbe6rT&`qEMf9rf02^E~ULlDu@kySQ!H@CS_a+L<3HCIj!{~5)VILGdnJ6 zs%Ae~1H18QMkXGMd_oR5OjTXRI{PGfAEvHOJSI6ZqQ$p_;mVPKk#vG)VrUiMwI#xM z*bf-8NqXV3ecm7tIqx)VzOjUoywW%+xAY}MY991PnXLaeNPiWI`Kk=SjLpRo4j~%= z9&waOC*+Mse5|{)W~&1Vgs#MF4&5=|=RUg$l_Tk0h?hewN<}*FsYij4g(~IxbFuwD zTdpql>~cCZ2%MW?x{PonX_JMu`cMxrWH1MOH(%Fhv=B#|iC_@GcC{((e_vXK&qUOM zG^e`Sp6@Fp9XCYzY0gM|@>0kOr298u0<9dFE+n2rf)aX)@{?73cdw6~72C|e{rZFz z2M}}PFXw4>Y_}Y%H)2Ajce5ei`v@iPfy+!wVaH=Fe!C{Ni)~7>>}b7j6TGL7c|1yo^V(dHJS0uj(+GHuV5#C5GlEie=e^CN$43a> z5&DXQ{qh~(CQ4=}ax$sUke73~4 zrcxT&i|8{nI&RqJB(Vcj^euKk?i$G3xEfWNz64_G#<>4Ojhj+cw+7sS<*#Iop68%n zH|KWqhtmek*CrtPjoI^ZnT41=1%*0ya?do^cw5wg z^+>}sDf)RGCbD%RPN=Mg^FGsQZ_RvvJ8zDnm%NIy*p;&{YL~L{&Dh!tttrZd@sAw_ zioC`8b~56Eq81c#xqpFppIqNmW`q2T01tC8mNHW=gF(ysB1K5>rijG5EzWzd-sOvq zxsCV;DneSDPE?}!%09I^bQ660%h&z)wljsqnfHGh$=5rqdVYS-xQXX?q5i7lO|h+R z^K4iM@mUR@7PQ$$?5o8!_&>o?YoXWLDO>)g^3z|wy5w&v=n>-V)9Dzz-_fAMJO#rK<0CWID57({&MO*p4n7#l9Xoa-w`#a^$XnajeO?zBA{Eb% z5W{)MH2DJs2Ig9ut|Hv@+&L_S1CME7UgINTzA#p0QFag1_b%X=W_d?Uy<=^HVH; zSkH(nPEW_iD0L3Jkc^COJPNfII&=6oApn(NyVGZ(>b(V%XlErL5EtQegA%wB1h!Z9 z_F#q9jqJ06+1Z>npxN!oyYvbJ4hEje=;pWUvOD3p+w!V)@%oi6jI8(%~ko8 z&j@oAxg{}-N?-agd%*J@n!EG0v5W0QvEqFw9b2_isf?j@#v<5ZG#d<6A9Q?<%4F-Y zg=@4P2`%~5L5`w)&=Mw8A;_c> zvBLGn$J1m!B-p_B+UsjdC`2kls}n=x?>-3RhS=!l_FWnr_msP5D$G|c1Wf{`4bAdvHFp_!Kl}wc|wX^nM_dpdXBAAF*e(Xu%M%KVlt?Y(a#bSH{FRz zFK$sHj{_dL;x%2U^TDNPTHfV}SMxB7damd3kl@KK_@d10prq2}AmD{G0!Yg#v#K*{ z&126$zabOC=7Fl45Ye{2S^}Ha6)kNXEl9sTCE5kVxu1{vJ9NAvbVthKdGED!7R}oH z@M6-8>EKkHdxWWMqGtJ^1mDVwHe4?qlO+V}Ga>%jGP>SP54+4*B#qyhPukFi5R4@^?urky>atc!KBtk?fUpDoM%tMF!r zMqsidrO^yCUrv2W^w(h8L3B+e2jN*PKQ=-r*m@@EWI6gY zF!WEU0}j6qIi>{$L?#rKb86*Q53;;Kx$Aj^<=EKT@*(`)q=tFuuPx`%Jz>tI0&|IC zd;LOr5~%r7bZHt}_gGKTKpS6LG#~!yN=tNOPWBkxzU||4Z#nq>GdlzOWQCl~KT7{$ zf)0<{5oWSeT>7<-Kp4tgckQ9u_!%!mO<31tPq2Ghb~)bOM9xU@tNvnqRp@f?0284#<6H>Stc|5uetxjs-%0XX?{ zb5aBN?<{8T#(Iflo(0q`wD*NF=_@b;S#BT zAr9K(CR0NJm>g;rNoGhMTkj+^UxOaU5E4wK4ftNsO2ZkIU(Fx@b^i7AVQ6#mgR*ft zH>_dlWf2QtalC<(BPu_SUcisf0Eo()4LS}kNbcor$OxQ!Z~qB>Ey%xQ`tOK8_$G8FHZPsJZ^;U-X5O p(@t@f@sh(De6jy4`a!!$iTOxQOh0EM`R59t3DHyg{LJ?4{{a%Bo=*S( literal 0 HcmV?d00001 diff --git a/WebFlix/src/main/AndroidManifest.xml b/WebFlix/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/WebFlix/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/WebFlix/src/main/kotlin/com/lagradost/WebFlixProvider.kt b/WebFlix/src/main/kotlin/com/lagradost/WebFlixProvider.kt new file mode 100644 index 0000000..0eb0366 --- /dev/null +++ b/WebFlix/src/main/kotlin/com/lagradost/WebFlixProvider.kt @@ -0,0 +1,222 @@ +package com.lagradost + +import android.util.Base64 +import android.util.Log +import com.lagradost.WebFlixProvider.Companion.toHomePageList +import com.lagradost.cloudstream3.* +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 +import java.net.URLEncoder + + +class WebFlixProvider(override var lang: String, override var mainUrl: String, override var name: String, override val supportedTypes: Set) : MainAPI() { + val magicPath = base64Decode("NEY1QTlDM0Q5QTg2RkE1NEVBQ0VEREQ2MzUxODUvZDUwNmFiZmQtOWZlMi00YjcxLWI5NzktZmVmZjIxYmNhZDEzLw==") + override val hasMainPage = true + override val hasChromecastSupport = true + + override suspend fun getMainPage( + page: Int, + request : MainPageRequest + ): HomePageResponse? { + val res = tryParseJson(app.get("$mainUrl/api/first/$magicPath").text) ?: return null + return HomePageResponse( + res.getHomePageLists(this), + false + ) + } + + override suspend fun search(query: String): List? { + val res = tryParseJson(app.get("$mainUrl/api/search/${query.encodeUri()}/$magicPath").text) ?: return null + return res.posters.map { it.toSearchResponse(this) } + } + + override suspend fun load(url: String): LoadResponse? { + val data = tryParseJson(app.get(url).text) ?: return null + return data.toLoadResponse(this) + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val sources = tryParseJson>(data) ?: return false + sources.forEach { + it.load(subtitleCallback, callback) + } + return true + } + + private data class ApiSearchResponse( + val posters: List + ) + + private data class HomeResponse(val genres: List, val channels: List, val slides: List) { + fun getHomePageLists(provider: WebFlixProvider): List { + val lists = mutableListOf() + if (channels.isNotEmpty()) { + channels.forEach { + if (it.type == null) it.type = "channel" + } + lists.add(channels.toHomePageList("Channels", provider)) + } + if (slides.isNotEmpty()) lists.add(slides.toHomePageList("Slides", provider)) + lists.addAll(genres.map { it.toHomePageList(provider) }) + return lists + } + } + private data class HomeReponseGenre( + val title: String, + val posters: List + ) { + fun toHomePageList(provider: WebFlixProvider) = posters.toHomePageList(title, provider) + } + + data class Source( + val title: String?, + val url: String?, + val quality: String?, + ) { + suspend fun load(subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { + if (url == null) return; + when (url.split(".").last()) { + "mp4", "m3u8", "mov" -> callback.invoke( + ExtractorLink( + quality ?: "", + title ?: "", + url, + "", + Qualities.Unknown.value, + isM3u8 = (url.endsWith("m3u8")) + )) + else -> loadExtractor(url, subtitleCallback, callback) + } + } + } + + private data class ApiEpisode( + val id: Int, + val title: String?, + val description: String?, + val sources: List = emptyList() + ) { + fun toEpisode(season: Int, episode: Int) = Episode( + sources.toJson(), + title, + season, + episode, + null, + null, + description + ) + } + + private data class ApiSeason( + val title: String?, + val episodes: List = emptyList() + ) { + fun getEpisodes(season: Int): List = episodes.mapIndexed { idx, episode -> episode.toEpisode(season, idx + 1) } + } + + data class Entry( + val id: Int, + val title: String, + val label: String?, + val sublabel: String?, + val image: String?, + val description: String?, + var type: String?, + val year: String?, + val imdb: Double?, + val sources: List = emptyList() + ) { + fun getTvType() = when (type) { + "serie" -> TvType.TvSeries + "movie" -> TvType.Movie + "channel", "4" -> TvType.Live + else -> { + Log.d("WebFlix", "other: $type") + TvType.Others + } + } + + fun toSearchResponse(provider: WebFlixProvider): SearchResponse { + val entry = this + return when(getTvType()) { + TvType.Movie -> provider.newMovieSearchResponse( + title, + "${provider.mainUrl}/api/movie/by/$id/${provider.magicPath}", + getTvType(), + ) { + posterUrl = image + year = entry.year?.toIntOrNull() + } + TvType.TvSeries -> provider.newTvSeriesSearchResponse( + title, + "${provider.mainUrl}/api/series/by/$id/${provider.magicPath}", + TvType.TvSeries + ) { + posterUrl = image + //year = entry.year?.toIntOrNull() + } + TvType.Live -> provider.newMovieSearchResponse( + title, + "${provider.mainUrl}/api/channel/by/$id/${provider.magicPath}", + getTvType(), + ) { + posterUrl = image + year = entry.year?.toIntOrNull() + } + else -> provider.newMovieSearchResponse( + title, + "${provider.mainUrl}/api/$type/by/$id/${provider.magicPath}", + getTvType(), + ) { + posterUrl = image + year = entry.year?.toIntOrNull() + } + } + } + + suspend fun toLoadResponse(provider: WebFlixProvider): LoadResponse? { + val entry = this + return when(getTvType()) { + TvType.TvSeries -> { + val res = tryParseJson>(app.get("${provider.mainUrl}/api/season/by/serie/${id}/${provider.magicPath}").text) ?: return null + provider.newTvSeriesLoadResponse( + title, + "", + TvType.TvSeries, + res.mapIndexed { idx, season -> season.getEpisodes(idx + 1) } + .flatten() + ) { + this.posterUrl = entry.image + this.year = entry.year?.toIntOrNull() + this.plot = description + this.rating = if (entry.imdb != null) (entry.imdb*10).toInt() else null + } + } + else -> provider.newMovieLoadResponse( + title, + "", + getTvType(), + sources.toJson() + ) { + this.posterUrl = entry.image + this.year = entry.year?.toIntOrNull() + this.plot = description + this.rating = if (entry.imdb != null) (entry.imdb*10).toInt() else null + } + } + } + } + + companion object { + fun String.encodeUri() = URLEncoder.encode(this, "utf8") + fun List.toHomePageList(name: String, provider: WebFlixProvider) = HomePageList(name, this.map { it.toSearchResponse(provider) }) + } +} \ No newline at end of file diff --git a/WebFlix/src/main/kotlin/com/lagradost/WebFlixProviderPlugin.kt b/WebFlix/src/main/kotlin/com/lagradost/WebFlixProviderPlugin.kt new file mode 100644 index 0000000..586c54b --- /dev/null +++ b/WebFlix/src/main/kotlin/com/lagradost/WebFlixProviderPlugin.kt @@ -0,0 +1,19 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context +import com.lagradost.cloudstream3.TvType + +@CloudstreamPlugin +class WebFlixProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(WebFlixProvider("en", "https://dhfilmtv.com", "DHFilmTv", setOf(TvType.Movie, TvType.TvSeries))) + registerMainAPI(WebFlixProvider("pl", "https://app.vodi.cc", "Vodi.cc", setOf(TvType.Movie, TvType.TvSeries))) + registerMainAPI(WebFlixProvider("fr", "http://www.vanflix.cm", "Vanflix", setOf(TvType.Movie, TvType.TvSeries, TvType.Live))) + registerMainAPI(WebFlixProvider("pt-pt", "https://www.brflix.xyz", "BrFlix", setOf(TvType.Movie, TvType.TvSeries, TvType.Live))) + registerMainAPI(WebFlixProvider("ar", "https://ifilm.live", "ifilm.live", setOf(TvType.Movie, TvType.TvSeries))) + } +} \ No newline at end of file