From 6522056cc6843f09f89a4ef4df3580936f5ba473 Mon Sep 17 00:00:00 2001 From: PokerFace <117321707+pokerface-bad@users.noreply.github.com> Date: Thu, 16 Mar 2023 06:41:23 +0700 Subject: [PATCH 1/4] fixed stremio --- .../kotlin/com/lagradost/StremioProvider.kt | 83 +++++++++++-------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt b/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt index c47d8b3..d406fe4 100644 --- a/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt +++ b/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt @@ -1,19 +1,17 @@ package com.lagradost import android.util.Log -import com.lagradost.StremioProvider.Companion.encodeUri import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.AppUtils.toJson - import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.loadExtractor import org.json.JSONObject import java.net.URLEncoder -private const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt" +private const val TRACKER_LIST_URL = + "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt" class StremioProvider : MainAPI() { override var mainUrl = "https://stremio.github.io/stremio-static-addon-example" @@ -46,7 +44,8 @@ class StremioProvider : MainAPI() { override suspend fun load(url: String): LoadResponse? { val res = tryParseJson(url) ?: throw RuntimeException(url) - return res.toLoadResponse(this) + val json = app.get("${mainUrl}/meta/${res.type}/${res.id}.json").parsedSafe()?.meta ?: throw RuntimeException(url) + return json.toLoadResponse(this) } override suspend fun loadLinks( @@ -77,8 +76,11 @@ class StremioProvider : MainAPI() { suspend fun search(query: String, provider: StremioProvider): List { val entries = mutableListOf() types.forEach { type -> - val res = tryParseJson(app.get("${provider.mainUrl}/catalog/${type.encodeUri()}/${id.encodeUri()}/search=${query.encodeUri()}.json").text) ?: return@forEach - res.metas.forEach { entry -> + val json = app.get("${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json").text + val res = + tryParseJson(json) + ?: return@forEach + res.metas?.forEach { entry -> entries.add(entry.toSearchResponse(provider)) } } @@ -88,8 +90,11 @@ class StremioProvider : MainAPI() { suspend fun toHomePageList(provider: StremioProvider): HomePageList? { val entries = mutableListOf() types.forEach { type -> - val res = tryParseJson(app.get("${provider.mainUrl}/catalog/${type.encodeUri()}/${id.encodeUri()}.json").text) ?: return@forEach - res.metas.forEach { entry -> + val json = app.get("${provider.mainUrl}/catalog/${type}/${id}.json").text + val res = + tryParseJson(json) + ?: return@forEach + res.metas?.forEach { entry -> entries.add(entry.toSearchResponse(provider)) } } @@ -100,7 +105,7 @@ class StremioProvider : MainAPI() { } } - private data class CatalogResponse(val metas: List) + private data class CatalogResponse(val metas: List?,val meta: CatalogEntry?) private data class CatalogEntry( val name: String, val id: String, @@ -118,13 +123,14 @@ class StremioProvider : MainAPI() { posterUrl = poster } } + suspend fun toLoadResponse(provider: StremioProvider): LoadResponse { if (videos == null || videos.isEmpty()) { return provider.newMovieLoadResponse( name, - "${provider.mainUrl}/meta/${type?.encodeUri()}/${id.encodeUri()}.json", + "${provider.mainUrl}/meta/${type}/${id}.json", TvType.Others, - "${provider.mainUrl}/stream/${type?.encodeUri()}/${id.encodeUri()}.json" + "${provider.mainUrl}/stream/${type}/${id}.json" ) { posterUrl = poster plot = description @@ -132,7 +138,7 @@ class StremioProvider : MainAPI() { } else { return provider.newTvSeriesLoadResponse( name, - "${provider.mainUrl}/meta/${type?.encodeUri()}/${id.encodeUri()}.json", + "${provider.mainUrl}/meta/${type}/${id}.json", TvType.Others, videos.map { it.toEpisode(provider, type) @@ -146,10 +152,15 @@ class StremioProvider : MainAPI() { } } - private data class Video(val id: String, val title: String?, val thumbnail: String?, val overview: String?) { + private data class Video( + val id: String, + val title: String?, + val thumbnail: String?, + val overview: String? + ) { fun toEpisode(provider: StremioProvider, type: String?): Episode { return provider.newEpisode( - "${provider.mainUrl}/stream/${type?.encodeUri()}/${id.encodeUri()}.json" + "${provider.mainUrl}/stream/${type}/${id}.json" ) { this.name = title this.posterUrl = thumbnail @@ -169,13 +180,17 @@ class StremioProvider : MainAPI() { val infoHash: String?, val sources: List = emptyList() ) { - suspend fun runCallback(subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { + suspend fun runCallback( + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { if (url != null) { var referer: String? = null try { val headers = ((behaviorHints?.get("proxyHeaders") as? JSONObject) ?.get("request") as? JSONObject) - referer = headers?.get("referer") as? String ?: headers?.get("origin") as? String + referer = + headers?.get("referer") as? String ?: headers?.get("origin") as? String } catch (ex: Throwable) { Log.e("Stremio", Log.getStackTraceString(ex)) } @@ -183,13 +198,14 @@ class StremioProvider : MainAPI() { if (url.endsWith(".m3u8")) { callback.invoke( ExtractorLink( - name ?: "", - title ?: name ?: "", - url, + name ?: "", + title ?: name ?: "", + url, referer ?: "", - Qualities.Unknown.value, - isM3u8 = true - )) + Qualities.Unknown.value, + isM3u8 = true + ) + ) } else { callback.invoke( ExtractorLink( @@ -199,7 +215,8 @@ class StremioProvider : MainAPI() { referer ?: "", Qualities.Unknown.value, isM3u8 = false - )) + ) + ) } } if (ytId != null) { @@ -212,17 +229,13 @@ class StremioProvider : MainAPI() { val resp = app.get(TRACKER_LIST_URL).text val otherTrackers = resp .split("\n") - .filterIndexed{i, s -> i%2==0} - .filter{s -> !s.isNullOrEmpty()} - .map{it -> "&tr=$it"} - .joinToString("") - + .filterIndexed { i, s -> i % 2 == 0 } + .filter { s -> s.isNotEmpty() }.joinToString("") { "&tr=$it" } + val sourceTrackers = sources - .filter{it->it.startsWith("tracker:")} - .map{it->it.removePrefix("tracker:")} - .filter{s -> !s.isNullOrEmpty()} - .map{it -> "&tr=$it"} - .joinToString("") + .filter { it -> it.startsWith("tracker:") } + .map { it -> it.removePrefix("tracker:") } + .filter { s -> s.isNotEmpty() }.joinToString("") { "&tr=$it" } val magnet = "magnet:?xt=urn:btih:${infoHash}${sourceTrackers}${otherTrackers}" callback.invoke( @@ -239,6 +252,6 @@ class StremioProvider : MainAPI() { } companion object { - fun String.encodeUri() = URLEncoder.encode(this, "utf8") + fun String.encodeUri(): String = URLEncoder.encode(this, "utf8") } } From a624eacbbe4aa2259f2346f12d9cecafe3434356 Mon Sep 17 00:00:00 2001 From: PokerFace <117321707+pokerface-bad@users.noreply.github.com> Date: Thu, 16 Mar 2023 06:41:44 +0700 Subject: [PATCH 2/4] bump --- StremioProvider/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/StremioProvider/build.gradle.kts b/StremioProvider/build.gradle.kts index 0a0b6c7..29c0e52 100644 --- a/StremioProvider/build.gradle.kts +++ b/StremioProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 2 +version = 3 cloudstream { @@ -21,4 +21,4 @@ cloudstream { tvTypes = listOf("Others") iconUrl = "https://www.google.com/s2/favicons?domain=www.stremio.com&sz=%size%" -} \ No newline at end of file +} From d77851596944d3077dd9eaae20fe7e06f4fa71f7 Mon Sep 17 00:00:00 2001 From: PokerFace <117321707+pokerface-bad@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:25:45 +0700 Subject: [PATCH 3/4] add stream support for catalog addons --- .../kotlin/com/lagradost/StremioProvider.kt | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt b/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt index d406fe4..12382fc 100644 --- a/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt +++ b/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt @@ -2,11 +2,10 @@ package com.lagradost import android.util.Log import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson 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 org.json.JSONObject import java.net.URLEncoder @@ -15,10 +14,20 @@ private const val TRACKER_LIST_URL = class StremioProvider : MainAPI() { override var mainUrl = "https://stremio.github.io/stremio-static-addon-example" + + /** add this as default stream because we don't support torrent yet + and this add ons is the best for http stream */ + private var streamUrl = "https://2ecbbd610840-cinestream.baby-beamup.club" + override var name = "Stremio example" override val supportedTypes = setOf(TvType.Others) override val hasMainPage = true + // check if id is imdb/tmdb cause stremio addons like torrentio works base on imdbId + private fun isImdborTmdb(url: String?): Boolean { + return imdbUrlToIdNullable(url) != null || url?.startsWith("tmdb:") == true + } + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse? { val res = tryParseJson(app.get("${mainUrl}/manifest.json").text) ?: return null val lists = mutableListOf() @@ -43,8 +52,9 @@ class StremioProvider : MainAPI() { } override suspend fun load(url: String): LoadResponse? { - val res = tryParseJson(url) ?: throw RuntimeException(url) + val res = parseJson(url) val json = app.get("${mainUrl}/meta/${res.type}/${res.id}.json").parsedSafe()?.meta ?: throw RuntimeException(url) + isMetaId = isImdborTmdb(res.id) return json.toLoadResponse(this) } @@ -116,7 +126,7 @@ class StremioProvider : MainAPI() { ) { fun toSearchResponse(provider: StremioProvider): SearchResponse { return provider.newMovieSearchResponse( - name, + fixTitle(name), this.toJson(), TvType.Others ) { @@ -130,7 +140,7 @@ class StremioProvider : MainAPI() { name, "${provider.mainUrl}/meta/${type}/${id}.json", TvType.Others, - "${provider.mainUrl}/stream/${type}/${id}.json" + if(isMetaId) "${provider.streamUrl}/stream/${type}/${id}.json" else "${provider.mainUrl}/stream/${type}/${id}.json" ) { posterUrl = poster plot = description @@ -159,26 +169,36 @@ class StremioProvider : MainAPI() { val overview: String? ) { fun toEpisode(provider: StremioProvider, type: String?): Episode { + val ids = id.split(":") return provider.newEpisode( - "${provider.mainUrl}/stream/${type}/${id}.json" + if(isMetaId) "${provider.streamUrl}/stream/${type}/${id}.json" else "${provider.mainUrl}/stream/${type}/${id}.json" ) { this.name = title this.posterUrl = thumbnail this.description = overview + this.episode = ids.lastOrNull()?.toIntOrNull() + this.season = ids.getOrNull(ids.lastIndex - 1)?.toIntOrNull() } } } private data class StreamsResponse(val streams: List) + private data class Subtitle( + val url: String?, + val lang: String?, + val id: String?, + ) private data class Stream( val name: String?, val title: String?, val url: String?, + val description: String?, val ytId: String?, val externalUrl: String?, val behaviorHints: JSONObject?, val infoHash: String?, - val sources: List = emptyList() + val sources: List = emptyList(), + val subtitles: List = emptyList() ) { suspend fun runCallback( subtitleCallback: (SubtitleFile) -> Unit, @@ -202,7 +222,7 @@ class StremioProvider : MainAPI() { title ?: name ?: "", url, referer ?: "", - Qualities.Unknown.value, + getQualityFromName(description), isM3u8 = true ) ) @@ -218,6 +238,14 @@ class StremioProvider : MainAPI() { ) ) } + subtitles.map { sub -> + subtitleCallback.invoke( + SubtitleFile( + SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang ?: "", + sub.url ?: return@map + ) + ) + } } if (ytId != null) { loadExtractor("https://www.youtube.com/watch?v=$ytId", subtitleCallback, callback) @@ -252,6 +280,7 @@ class StremioProvider : MainAPI() { } companion object { + private var isMetaId = false fun String.encodeUri(): String = URLEncoder.encode(this, "utf8") } } From f6eb96aea514676bc95d5d285571fe1d13b9ff19 Mon Sep 17 00:00:00 2001 From: PokerFace <117321707+pokerface-bad@users.noreply.github.com> Date: Fri, 17 Mar 2023 08:46:03 +0700 Subject: [PATCH 4/4] small fix --- .../kotlin/com/lagradost/StremioProvider.kt | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt b/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt index 12382fc..1c7824c 100644 --- a/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt +++ b/StremioProvider/src/main/kotlin/com/lagradost/StremioProvider.kt @@ -7,6 +7,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import org.json.JSONObject +import java.net.URI import java.net.URLEncoder private const val TRACKER_LIST_URL = @@ -214,30 +215,16 @@ class StremioProvider : MainAPI() { } catch (ex: Throwable) { Log.e("Stremio", Log.getStackTraceString(ex)) } - - if (url.endsWith(".m3u8")) { - callback.invoke( - ExtractorLink( - name ?: "", - title ?: name ?: "", - url, - referer ?: "", - getQualityFromName(description), - isM3u8 = true - ) + callback.invoke( + ExtractorLink( + name ?: "", + title ?: name ?: "", + url, + referer ?: "", + getQualityFromName(description), + isM3u8 = URI(url).path.endsWith(".m3u8") ) - } else { - callback.invoke( - ExtractorLink( - name ?: "", - title ?: name ?: "", - url, - referer ?: "", - Qualities.Unknown.value, - isM3u8 = false - ) - ) - } + ) subtitles.map { sub -> subtitleCallback.invoke( SubtitleFile(