diff --git a/AltadefinizioneProvider/src/main/kotlin/com/lagradost/AltadefinizioneProvider.kt b/AltadefinizioneProvider/src/main/kotlin/com/lagradost/AltadefinizioneProvider.kt index 1a3fa77..6c7a92e 100644 --- a/AltadefinizioneProvider/src/main/kotlin/com/lagradost/AltadefinizioneProvider.kt +++ b/AltadefinizioneProvider/src/main/kotlin/com/lagradost/AltadefinizioneProvider.kt @@ -1,6 +1,5 @@ package com.lagradost -//import androidx.core.text.parseAsHtml import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.ExtractorLink diff --git a/AniPlayProvider/src/main/kotlin/com/lagradost/AniPlayProvider.kt b/AniPlayProvider/src/main/kotlin/com/lagradost/AniPlayProvider.kt index 15eb860..9c5b68b 100644 --- a/AniPlayProvider/src/main/kotlin/com/lagradost/AniPlayProvider.kt +++ b/AniPlayProvider/src/main/kotlin/com/lagradost/AniPlayProvider.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId import com.lagradost.cloudstream3.LoadResponse.Companion.addDuration import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.Qualities @@ -93,7 +94,7 @@ class AniPlayProvider : MainAPI() { ) private suspend fun ApiSeason.toEpisodeList(url: String) : List { - return app.get("$url/season/${this.id}").parsed>().mapNotNull { it.toEpisode() } + return parseJson>(app.get("$url/season/${this.id}").text).mapNotNull { it.toEpisode() } } data class ApiAnime( @@ -115,7 +116,7 @@ class AniPlayProvider : MainAPI() { ) override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { - val response = app.get("$mainUrl/api/home/latest-episodes?page=0").parsed>() + val response = parseJson>(app.get("$mainUrl/api/home/latest-episodes?page=0").text) val results = response.map{ val isDub = isDub(it.title) @@ -133,7 +134,7 @@ class AniPlayProvider : MainAPI() { } override suspend fun search(query: String): List { - val response = app.get("$mainUrl/api/anime/advanced-search?page=0&size=36&query=$query").parsed>() + val response = parseJson>(app.get("$mainUrl/api/anime/advanced-search?page=0&size=36&query=$query").text) return response.map { val isDub = isDub(it.title) @@ -151,7 +152,7 @@ class AniPlayProvider : MainAPI() { override suspend fun load(url: String): LoadResponse { - val response = app.get(url).parsed() + val response = parseJson(app.get(url).text) val tags: List = response.genres.map { it.name } @@ -182,7 +183,7 @@ class AniPlayProvider : MainAPI() { callback: (ExtractorLink) -> Unit ): Boolean { - val episode = app.get(data).parsed() + val episode = parseJson(app.get(data).text) if(episode.url.contains(".m3u8")){ val m3u8Helper = M3u8Helper() diff --git a/CalcioStreamingProvider/build.gradle.kts b/CalcioStreamingProvider/build.gradle.kts new file mode 100644 index 0000000..cac4c7d --- /dev/null +++ b/CalcioStreamingProvider/build.gradle.kts @@ -0,0 +1,24 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // 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( + "Live", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=calciostreaming.live&sz=24" +} \ No newline at end of file diff --git a/CalcioStreamingProvider/src/main/AndroidManifest.xml b/CalcioStreamingProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/CalcioStreamingProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CalcioStreamingProvider/src/main/kotlin/com/lagradost/CalcioStreamingProvider.kt b/CalcioStreamingProvider/src/main/kotlin/com/lagradost/CalcioStreamingProvider.kt new file mode 100644 index 0000000..857d054 --- /dev/null +++ b/CalcioStreamingProvider/src/main/kotlin/com/lagradost/CalcioStreamingProvider.kt @@ -0,0 +1,107 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* + + +class CalcioStreamingProvider : MainAPI() { + override var lang = "it" + override var mainUrl = "https://calciostreaming.live" + override var name = "CalcioStreaming" + override val hasMainPage = true + override val hasChromecastSupport = true + override val supportedTypes = setOf( + TvType.Live, + + ) + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val document = app.get(mainUrl+"/partite-streaming.html").document + val sections = document.select("div.slider-title").filter {it -> it.select("div.item").isNotEmpty()} + + if (sections.isEmpty()) throw ErrorLoadingException() + + return HomePageResponse(sections.map { it -> + val categoryname = it.selectFirst("h2 > strong")!!.text() + val shows = it.select("div.item").map { + val href = it.selectFirst("a")!!.attr("href") + val name = it.selectFirst("a > div > h1")!!.text() + val posterurl = fixUrl(it.selectFirst("a > img")!!.attr("src")) + LiveSearchResponse( + name, + href, + this@CalcioStreamingProvider.name, + TvType.Live, + posterurl, + ) + } + HomePageList( + categoryname, + shows, + isHorizontalImages = true + ) + + }) + + } + + + override suspend fun load(url: String): LoadResponse { + + val document = app.get(url).document + val poster = fixUrl(document.select("#title-single > div").attr("style").substringAfter("url(").substringBeforeLast(")")) + val Matchstart = document.select("div.info-wrap > div").textNodes().joinToString("").trim() + return LiveStreamLoadResponse( + document.selectFirst(" div.info-t > h1")!!.text(), + url, + this.name, + url, + poster, + plot = Matchstart + ) + + + } + + + + + private suspend fun extractVideoLinks( + url: String, + callback: (ExtractorLink) -> Unit + ) { + val document = app.get(url).document + document.select("button.btn").forEach { button -> + val link1 = button.attr("data-link") + val doc2 = app.get(link1).document + val truelink = doc2.selectFirst("iframe")!!.attr("src") + val newpage = app.get(truelink, referer = link1).document + val streamurl = Regex(""""((.|\n)*?).";""").find( + getAndUnpack( + newpage.select("script")[6].childNode(0).toString() + ))!!.value.replace("""src="""", "").replace(""""""", "").replace(";", "") + + callback( + ExtractorLink( + this.name, + button.text(), + streamurl, + truelink, + quality = 0, + true + ) + ) + } + } + + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + extractVideoLinks(data, callback) + + return true + } +} diff --git a/CalcioStreamingProvider/src/main/kotlin/com/lagradost/CalcioStreamingProviderPlugin.kt b/CalcioStreamingProvider/src/main/kotlin/com/lagradost/CalcioStreamingProviderPlugin.kt new file mode 100644 index 0000000..a7ecfca --- /dev/null +++ b/CalcioStreamingProvider/src/main/kotlin/com/lagradost/CalcioStreamingProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class CalcioStreamingProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(CalcioStreamingProvider()) + } +} \ No newline at end of file diff --git a/CineBlogProvider/build.gradle.kts b/CineBlogProvider/build.gradle.kts new file mode 100644 index 0000000..2116951 --- /dev/null +++ b/CineBlogProvider/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 = "Lorem Ipsum" + // 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( + "TvSeries", + "Movie", + ) + + + iconUrl = "https://www.google.com/s2/favicons?domain=cb01.rip&sz=24" +} \ No newline at end of file diff --git a/CineBlogProvider/src/main/AndroidManifest.xml b/CineBlogProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/CineBlogProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CineBlogProvider/src/main/kotlin/com/lagradost/CineBlogProvider.kt b/CineBlogProvider/src/main/kotlin/com/lagradost/CineBlogProvider.kt new file mode 100644 index 0000000..7ab87cd --- /dev/null +++ b/CineBlogProvider/src/main/kotlin/com/lagradost/CineBlogProvider.kt @@ -0,0 +1,187 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor + + +class CineBlogProvider : MainAPI() { + override var lang = "it" + override var mainUrl = "https://cb01.rip" + override var name = "CineBlog" + override val hasMainPage = true + override val hasChromecastSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + override val mainPage = mainPageOf( + Pair("$mainUrl/popolari/page/number/?get=movies", "Film Popolari"), + Pair("$mainUrl/popolari/page/number/?get=tv", "Serie Tv Popolari"), + Pair("$mainUrl/i-piu-votati/page/number/?get=movies", "Film più votati"), + Pair("$mainUrl/i-piu-votati/page/number/?get=tv", "Serie Tv più votate"), + Pair("$mainUrl/anno/2022/page/number", "Ultime uscite"), + ) + + override suspend fun getMainPage( + page: Int, + request : MainPageRequest + ): HomePageResponse { + val url = request.data.replace("number", page.toString()) + val soup = app.get(url, referer = url.substringBefore("page")).document + val home = soup.select("article.item").map { + val title = it.selectFirst("div.data > h3 > a")!!.text().substringBefore("(") + val link = it.selectFirst("div.poster > a")!!.attr("href") + val quality = getQualityFromString(it.selectFirst("span.quality")?.text()) + TvSeriesSearchResponse( + title, + link, + this.name, + TvType.Movie, + it.selectFirst("img")!!.attr("src"), + null, + null, + quality = quality + ) + } + return newHomePageResponse(request.name, home) + } + + override suspend fun search(query: String): List { + val queryformatted = query.replace(" ", "+") + val url = "$mainUrl?s=$queryformatted" + val doc = app.get(url,referer= mainUrl ).document + return doc.select("div.result-item").map { + val href = it.selectFirst("div.image > div > a")!!.attr("href") + val poster = it.selectFirst("div.image > div > a > img")!!.attr("src") + val name = it.selectFirst("div.details > div.title > a")!!.text().substringBefore("(") + MovieSearchResponse( + name, + href, + this.name, + TvType.Movie, + poster + ) + + } + } + + override suspend fun load(url: String): LoadResponse { + val page = app.get(url) + val document = page.document + val type = if (url.contains("film")) TvType.Movie else TvType.TvSeries + val title = document.selectFirst("div.data > h1")!!.text().substringBefore("(") + val description = document.select("#info > div.wp-content > p").html().toString() + val rating = null + var year = document.selectFirst(" div.data > div.extra > span.date")!!.text().substringAfter(",") + .filter { it.isDigit() } + if (year.length > 4) { + year = year.dropLast(4) + } + + val poster = document.selectFirst("div.poster > img")!!.attr("src") + + val recomm = document.select("#single_relacionados >article").map { + val href = it.selectFirst("a")!!.attr("href") + val posterUrl = it.selectFirst("a > img")!!.attr("src") + val name = it.selectFirst("a > img")!!.attr("alt").substringBeforeLast("(") + MovieSearchResponse( + name, + href, + this.name, + TvType.Movie, + posterUrl + ) + + } + + + if (type == TvType.TvSeries) { + + val episodeList = ArrayList() + document.select("#seasons > div").reversed().map { element -> + val season = element.selectFirst("div.se-q > span.se-t")!!.text().toInt() + element.select("div.se-a > ul > li").filter { it -> it.text()!="There are still no episodes this season" }.map{ episode -> + val href = episode.selectFirst("div.episodiotitle > a")!!.attr("href") + val epNum =episode.selectFirst("div.numerando")!!.text().substringAfter("-").filter { it.isDigit() }.toIntOrNull() + val epTitle = episode.selectFirst("div.episodiotitle > a")!!.text() + val posterUrl = episode.selectFirst("div.imagen > img")!!.attr("src") + episodeList.add( + Episode( + href, + epTitle, + season, + epNum, + posterUrl, + ) + ) + } + } + return TvSeriesLoadResponse( + title, + url, + this.name, + type, + episodeList, + fixUrlNull(poster), + year.toIntOrNull(), + description, + null, + rating, + null, + null, + mutableListOf(), + recomm + ) + } else { + val actors: List = + document.select("div.person").filter{ it -> it.selectFirst("div.img > a > img")?.attr("src")!!.contains("/no/cast.png").not()}.map { actordata -> + val actorName = actordata.selectFirst("div.data > div.name > a")!!.text() + val actorImage : String? = actordata.selectFirst("div.img > a > img")?.attr("src") + val roleActor = actordata.selectFirst("div.data > div.caracter")!!.text() + ActorData(actor = Actor(actorName, image = actorImage), roleString = roleActor ) + } + return newMovieLoadResponse( + title, + url, + type, + url + ) { + posterUrl = fixUrlNull(poster) + this.year = year.toIntOrNull() + this.plot = description + this.rating = rating + this.recommendations = recomm + this.duration = null + this.actors = actors + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val doc = app.get(data).document + val type = if( data.contains("film") ){"movie"} else {"tv"} + val idpost=doc.select("#player-option-1").attr("data-post") + val test = app.post("$mainUrl/wp-admin/admin-ajax.php", headers = mapOf( + "content-type" to "application/x-www-form-urlencoded; charset=UTF-8", + "accept" to "*/*", + "X-Requested-With" to "XMLHttpRequest", + ), data = mapOf( + "action" to "doo_player_ajax", + "post" to idpost, + "nume" to "1", + "type" to type, + )) + + val url2= Regex("""src='((.|\\n)*?)'""").find(test.text)?.groups?.get(1)?.value.toString() + val trueUrl = app.get(url2, headers = mapOf("referer" to mainUrl)).url + loadExtractor(trueUrl, data, subtitleCallback, callback) + + return true + } +} \ No newline at end of file diff --git a/CineBlogProvider/src/main/kotlin/com/lagradost/CineBlogProviderPlugin.kt b/CineBlogProvider/src/main/kotlin/com/lagradost/CineBlogProviderPlugin.kt new file mode 100644 index 0000000..c5c1694 --- /dev/null +++ b/CineBlogProvider/src/main/kotlin/com/lagradost/CineBlogProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class CineBlogProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(CineBlogProvider()) + } +} \ No newline at end of file diff --git a/IlGenioDelloStreamingProvider/src/main/kotlin/com/lagradost/IlGenioDelloStreamingProvider.kt b/IlGenioDelloStreamingProvider/src/main/kotlin/com/lagradost/IlGenioDelloStreamingProvider.kt index 394050f..5d8d417 100644 --- a/IlGenioDelloStreamingProvider/src/main/kotlin/com/lagradost/IlGenioDelloStreamingProvider.kt +++ b/IlGenioDelloStreamingProvider/src/main/kotlin/com/lagradost/IlGenioDelloStreamingProvider.kt @@ -43,7 +43,7 @@ class IlGenioDelloStreamingProvider : MainAPI() { link, this.name, TvType.Movie, - it.selectFirst("img")!!.attr("data-src-img"), + it.selectFirst("img")!!.attr("src"), null, null, quality = quality @@ -58,7 +58,7 @@ class IlGenioDelloStreamingProvider : MainAPI() { val doc = app.get(url,referer= mainUrl ).document return doc.select("div.result-item").map { val href = it.selectFirst("div.image > div > a")!!.attr("href") - val poster = it.selectFirst("div.image > div > a > img")!!.attr("data-src-img") + val poster = it.selectFirst("div.image > div > a > img")!!.attr("src") val name = it.selectFirst("div.details > div.title > a")!!.text().substringBeforeLast("(").substringBeforeLast("[") MovieSearchResponse( name, @@ -84,11 +84,11 @@ class IlGenioDelloStreamingProvider : MainAPI() { year = year.dropLast(4) } - val poster = document.selectFirst("div.poster > img")!!.attr("data-src-img") + val poster = document.selectFirst("div.poster > img")!!.attr("src") val recomm = document.select("article.w_item_b").map { val href = it.selectFirst("a")!!.attr("href") - val posterUrl = it.selectFirst("img")!!.attr("data-src-img") + val posterUrl = it.selectFirst("img")!!.attr("src") val name = it.selectFirst("div.data > h3")!!.text().substringBeforeLast("(").substringBeforeLast("[") MovieSearchResponse( name, diff --git a/StreamingcommunityProvider/src/main/kotlin/com/lagradost/StreamingcommunityProvider.kt b/StreamingcommunityProvider/src/main/kotlin/com/lagradost/StreamingcommunityProvider.kt index 3596879..e1c0f6a 100644 --- a/StreamingcommunityProvider/src/main/kotlin/com/lagradost/StreamingcommunityProvider.kt +++ b/StreamingcommunityProvider/src/main/kotlin/com/lagradost/StreamingcommunityProvider.kt @@ -128,7 +128,7 @@ data class TrailerElement( class StreamingcommunityProvider : MainAPI() { override var lang = "it" - override var mainUrl = "https://streamingcommunity.best" + override var mainUrl = "https://streamingcommunity.agency" override var name = "Streamingcommunity" override val hasMainPage = true override val hasChromecastSupport = true diff --git a/TantiFilmProvider/build.gradle.kts b/TantiFilmProvider/build.gradle.kts index f98a6f1..7106ffe 100644 --- a/TantiFilmProvider/build.gradle.kts +++ b/TantiFilmProvider/build.gradle.kts @@ -16,7 +16,7 @@ cloudstream { * 2: Slow * 3: Beta only * */ - status = 1 // will be 3 if unspecified + status = 0 // will be 3 if unspecified tvTypes = listOf( "TvSeries", "Movie", diff --git a/TvItalianaProvider/build.gradle.kts b/TvItalianaProvider/build.gradle.kts new file mode 100644 index 0000000..0b1d5d6 --- /dev/null +++ b/TvItalianaProvider/build.gradle.kts @@ -0,0 +1,24 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // 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( + "Live", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=github.com&sz=24" +} \ No newline at end of file diff --git a/TvItalianaProvider/src/main/AndroidManifest.xml b/TvItalianaProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/TvItalianaProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/TvItalianaProvider/src/main/kotlin/com/lagradost/TvItalianaProvider.kt b/TvItalianaProvider/src/main/kotlin/com/lagradost/TvItalianaProvider.kt new file mode 100644 index 0000000..d0436c7 --- /dev/null +++ b/TvItalianaProvider/src/main/kotlin/com/lagradost/TvItalianaProvider.kt @@ -0,0 +1,343 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import java.io.InputStream + +class TvItalianaProvider : MainAPI() { + override var lang = "it" + override var mainUrl = "https://raw.githubusercontent.com/Tundrak/IPTV-Italia/main/iptvitaplus.m3u" + override var name = "TvItaliana" + override val hasMainPage = true + override val hasChromecastSupport = true + override val supportedTypes = setOf( + TvType.Live, + ) + + override suspend fun getMainPage( + page: Int, + request : MainPageRequest + ): HomePageResponse { + val data = IptvPlaylistParser().parseM3U(app.get(mainUrl).text) + return HomePageResponse(data.items.groupBy{it.attributes["group-title"]}.map { group -> + val title = group.key ?: "" + val show = group.value.map { channel -> + val streamurl = channel.url.toString() + val channelname = channel.title.toString() + val posterurl = channel.attributes["tvg-logo"].toString() + val nation = channel.attributes["group-title"].toString() + LiveSearchResponse( + channelname, + LoadData(streamurl, channelname, posterurl, nation).toJson(), + this@TvItalianaProvider.name, + TvType.Live, + posterurl, + lang = "ita" + ) + } + HomePageList( + title, + show, + isHorizontalImages = true + ) + }) + } + + override suspend fun search(query: String): List { + val data = IptvPlaylistParser().parseM3U(app.get(mainUrl).text) + + return data.items.filter { it.attributes["tvg-id"]?.contains(query) ?: false }.map { channel -> + val streamurl = channel.url.toString() + val channelname = channel.attributes["tvg-id"].toString() + val posterurl = channel.attributes["tvg-logo"].toString() + val nation = channel.attributes["group-title"].toString() + LiveSearchResponse( + channelname, + LoadData(streamurl, channelname, posterurl, nation).toJson(), + this@TvItalianaProvider.name, + TvType.Live, + posterurl, + ) + } + } + + override suspend fun load(url: String): LoadResponse { + val data = parseJson(url) + return LiveStreamLoadResponse( + data.title, + data.url, + this.name, + url, + data.poster, + plot = data.nation + ) + } + data class LoadData( + val url: String, + val title: String, + val poster: String, + val nation: String + + ) + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val loadData = parseJson(data) + callback.invoke( + ExtractorLink( + this.name, + loadData.title, + loadData.url, + "", + Qualities.Unknown.value, + isM3u8 = true + ) + ) + return true + } +} + + +data class Playlist( + val items: List = emptyList(), +) + +data class PlaylistItem( + val title: String? = null, + val attributes: Map = emptyMap(), + val headers: Map = emptyMap(), + val url: String? = null, + val userAgent: String? = null, +) + + +class IptvPlaylistParser { + + + /** + * Parse M3U8 string into [Playlist] + * + * @param content M3U8 content string. + * @throws PlaylistParserException if an error occurs. + */ + fun parseM3U(content: String): Playlist { + return parseM3U(content.byteInputStream()) + } + + /** + * Parse M3U8 content [InputStream] into [Playlist] + * + * @param input Stream of input data. + * @throws PlaylistParserException if an error occurs. + */ + @Throws(PlaylistParserException::class) + fun parseM3U(input: InputStream): Playlist { + val reader = input.bufferedReader() + + if (!reader.readLine().isExtendedM3u()) { + throw PlaylistParserException.InvalidHeader() + } + + val playlistItems: MutableList = mutableListOf() + var currentIndex = 0 + + var line: String? = reader.readLine() + + while (line != null) { + if (line.isNotEmpty()) { + if (line.startsWith(EXT_INF)) { + val title = line.getTitle() + val attributes = line.getAttributes() + playlistItems.add(PlaylistItem(title, attributes)) + } else if (line.startsWith(EXT_VLC_OPT)) { + val item = playlistItems[currentIndex] + val userAgent = line.getTagValue("http-user-agent") + val referrer = line.getTagValue("http-referrer") + val headers = if (referrer != null) { + item.headers + mapOf("referrer" to referrer) + } else item.headers + playlistItems[currentIndex] = + item.copy(userAgent = userAgent, headers = headers) + } else { + if (!line.startsWith("#")) { + val item = playlistItems[currentIndex] + val url = line.getUrl() + val userAgent = line.getUrlParameter("user-agent") + val referrer = line.getUrlParameter("referer") + val urlHeaders = if (referrer != null) { + item.headers + mapOf("referrer" to referrer) + } else item.headers + playlistItems[currentIndex] = + item.copy( + url = url, + headers = item.headers + urlHeaders, + userAgent = userAgent + ) + currentIndex++ + } + } + } + + line = reader.readLine() + } + return Playlist(playlistItems) + } + + /** + * Replace "" (quotes) from given string. + */ + private fun String.replaceQuotesAndTrim(): String { + return replace("\"", "").trim() + } + + /** + * Check if given content is valid M3U8 playlist. + */ + private fun String.isExtendedM3u(): Boolean = startsWith(EXT_M3U) + + /** + * Get title of media. + * + * Example:- + * + * Input: + * ``` + * #EXTINF:-1 tvg-id="1234" group-title="Kids" tvg-logo="url/to/logo", Title + * ``` + * Result: Title + */ + private fun String.getTitle(): String? { + return split(",").lastOrNull()?.replaceQuotesAndTrim() + } + + /** + * Get media url. + * + * Example:- + * + * Input: + * ``` + * https://example.com/sample.m3u8|user-agent="Custom" + * ``` + * Result: https://example.com/sample.m3u8 + */ + private fun String.getUrl(): String? { + return split("|").firstOrNull()?.replaceQuotesAndTrim() + } + + /** + * Get url parameters. + * + * Example:- + * + * Input: + * ``` + * http://192.54.104.122:8080/d/abcdef/video.mp4|User-Agent=Mozilla&Referer=CustomReferrer + * ``` + * Result will be equivalent to kotlin map: + * ```Kotlin + * mapOf( + * "User-Agent" to "Mozilla", + * "Referer" to "CustomReferrer" + * ) + * ``` + */ + private fun String.getUrlParameters(): Map { + val urlRegex = Regex("^(.*)\\|", RegexOption.IGNORE_CASE) + val headersString = replace(urlRegex, "").replaceQuotesAndTrim() + return headersString.split("&").mapNotNull { + val pair = it.split("=") + if (pair.size == 2) pair.first() to pair.last() else null + }.toMap() + } + + /** + * Get url parameter with key. + * + * Example:- + * + * Input: + * ``` + * http://192.54.104.122:8080/d/abcdef/video.mp4|User-Agent=Mozilla&Referer=CustomReferrer + * ``` + * If given key is `user-agent`, then + * + * Result: Mozilla + */ + private fun String.getUrlParameter(key: String): String? { + val urlRegex = Regex("^(.*)\\|", RegexOption.IGNORE_CASE) + val keyRegex = Regex("$key=(\\w[^&]*)", RegexOption.IGNORE_CASE) + val paramsString = replace(urlRegex, "").replaceQuotesAndTrim() + return keyRegex.find(paramsString)?.groups?.get(1)?.value + } + + /** + * Get attributes from `#EXTINF` tag as Map. + * + * Example:- + * + * Input: + * ``` + * #EXTINF:-1 tvg-id="1234" group-title="Kids" tvg-logo="url/to/logo", Title + * ``` + * Result will be equivalent to kotlin map: + * ```Kotlin + * mapOf( + * "tvg-id" to "1234", + * "group-title" to "Kids", + * "tvg-logo" to "url/to/logo" + *) + * ``` + */ + private fun String.getAttributes(): Map { + val extInfRegex = Regex("(#EXTINF:.?[0-9]+)", RegexOption.IGNORE_CASE) + val attributesString = replace(extInfRegex, "").replaceQuotesAndTrim().split(",").first() + return attributesString.split(Regex("\\s")).mapNotNull { + val pair = it.split("=") + if (pair.size == 2) pair.first() to pair.last() + .replaceQuotesAndTrim() else null + }.toMap() + } + + /** + * Get value from a tag. + * + * Example:- + * + * Input: + * ``` + * #EXTVLCOPT:http-referrer=http://example.com/ + * ``` + * Result: http://example.com/ + */ + private fun String.getTagValue(key: String): String? { + val keyRegex = Regex("$key=(.*)", RegexOption.IGNORE_CASE) + return keyRegex.find(this)?.groups?.get(1)?.value?.replaceQuotesAndTrim() + } + + companion object { + const val EXT_M3U = "#EXTM3U" + const val EXT_INF = "#EXTINF" + const val EXT_VLC_OPT = "#EXTVLCOPT" + } + +} + +/** + * Exception thrown when an error occurs while parsing playlist. + */ +sealed class PlaylistParserException(message: String) : Exception(message) { + + /** + * Exception thrown if given file content is not valid. + */ + class InvalidHeader : + PlaylistParserException("Invalid file header. Header doesn't start with #EXTM3U") + +} diff --git a/TvItalianaProvider/src/main/kotlin/com/lagradost/TvItalianaProviderPlugin.kt b/TvItalianaProvider/src/main/kotlin/com/lagradost/TvItalianaProviderPlugin.kt new file mode 100644 index 0000000..48bf233 --- /dev/null +++ b/TvItalianaProvider/src/main/kotlin/com/lagradost/TvItalianaProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class TvItalianaProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(TvItalianaProvider()) + } +} \ No newline at end of file