From fd579fcc18b053be93b7883d893af14cc2d40879 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 8 Jun 2026 22:22:50 +0200 Subject: [PATCH 01/15] Translated using Weblate (German) Currently translated at 100.0% (729 of 729 strings) Translated using Weblate (German) Currently translated at 100.0% (729 of 729 strings) Translated using Weblate (Macedonian) Currently translated at 100.0% (729 of 729 strings) Co-authored-by: Deleted User Co-authored-by: Hosted Weblate Co-authored-by: WertZuz <97708601+wertzuz@users.noreply.github.com> Co-authored-by: stojkovskistefan Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/de/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/mk/ Translation: Cloudstream/App --- app/src/main/res/values-b+de/strings.xml | 12 +++++--- app/src/main/res/values-b+mk/strings.xml | 37 ++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index 6ccb9bc69..e674fafd6 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -252,7 +252,7 @@ Update Bevorzugte Videoqualität (WLAN) Videoplayertitel max. Zeichen - Playerinformationen anzeigen + Zeige Playerinformationen Videopuffergröße Videopufferlänge Video-Cache in Speicher @@ -587,7 +587,7 @@ Sicherheit Konten Repository öffnen - Besuche%s auf dem Smartphone oder Computer und gebe den obenstehenden Code ein + Besuche %s auf dem Smartphone oder Computer und gebe den obenstehenden Code ein PIN-Code vom Gerät nicht abrufbar, versuche lokale Authentifizierung Zur Zeit sind keine Downloads verfügbar. Lokales Video öffnen @@ -712,8 +712,8 @@ Zusätzliche Helligkeit Aktiviere Helligkeitsfilter, wenn 100% Bildschirmhelligkeit überschritten ist Erhöhte Helligkeit aktiviert - Cast-Panel zeigen - Medieninfo + Zeige Cast-Panel + Mediainfo Quellname Alle herunterladen Möchtest du Episode %s herunter laden? @@ -731,4 +731,8 @@ Es befinden sich keine Downloads in der Warteschlange. Quellpriorität Entscheide, wie Videoquellen im Player sortiert werden sollen + Zeige Player-Metadaten + Video + Vorschau + Live diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index ea467833e..4e37afdea 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -244,7 +244,7 @@ TC Претплатен на %s Преводи - Предупредување: CloudStream 3 не презема никаква одговорност за користење на екстензии од трети страни и не обезбедува никаква поддршка за нив! + Предупредување: CloudStream не презема никаква одговорност за користење на екстензии од трети страни и не обезбедува никаква поддршка за нив! Недостасуваат дозволи за складирање. Обиди се повторно. Зачувај Вчитај од датотека @@ -445,7 +445,7 @@ Грешка при правење резервна копија на %s Сокриј го избраниот квалитет на видеото во резултатите од пребарувањето Некои уреди не го поддржуваат новиот инсталатор на пакети. Испробај ја легаси(старата) опција, ако ажурирањата не се инсталираат. - Резолуција на видео плеер + Прикажи информации за плеерот Големина на видео баферот Распоред Стандардно @@ -705,4 +705,37 @@ Горе во центар Горе на десно Пушти ја целата серија + Редица за преземање + Моментално нема преземања во редицата. + Дополнителна осветленост + Овозможи филтер за осветленост кога ќе се надмине 100% осветленост на екранот + овозможена_дополнителна_осветленост + Предлози за пребарување + Прикажувај предлози за пребарување додека пишуваш + Исчисти предлози + Прикажи преклоп со метаподатоци на плеерот + Прикажи панел за емитување + Инсталирај предиздавачка верзија + Предиздавачката верзија е веќе инсталирана. + Неуспешна инсталација на предиздавачката верзија. + Видео + Текст на епизода + Информации за медиумот + Преглед + Приоритет на извор + Одреди како ќе се подредуваат видео изворите во плеерот + Име на изворот + Преземи сѐ + Откажи сѐ + Дали сакате да ја преземете епизодата %s? + Дали сакате да ги откажете сите преземања во редицата? + + %d активно преземање + %d активни преземања + + + %d преземање во редицата + %d преземања во редицата + + Во живо From f7cbf25b30b35d8d5a8b411cbe5de7a56fb8dbf0 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:03:48 -0600 Subject: [PATCH 02/15] Replace EnumSet for dubStatus (#2845) --- .../commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 5d4deba24..fc2fac005 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -36,7 +36,6 @@ import kotlinx.datetime.format.char import kotlinx.datetime.format.parse import kotlinx.datetime.toInstant import java.net.URI -import java.util.EnumSet import kotlinx.serialization.json.Json import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi @@ -1510,7 +1509,7 @@ constructor( override var posterUrl: String? = null, var year: Int? = null, - var dubStatus: EnumSet? = null, + var dubStatus: MutableSet? = null, var otherName: String? = null, var episodes: MutableMap = mutableMapOf(), @@ -1522,7 +1521,7 @@ constructor( ) : SearchResponse fun AnimeSearchResponse.addDubStatus(status: DubStatus, episodes: Int? = null) { - this.dubStatus = dubStatus?.also { it.add(status) } ?: EnumSet.of(status) + this.dubStatus = dubStatus?.also { it.add(status) } ?: mutableSetOf(status) if (this.type?.isMovieType() != true) if (episodes != null && episodes > 0) this.episodes[status] = episodes From 2181243dd14506a52921c5090c52ace690842a6e Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:08:58 -0600 Subject: [PATCH 03/15] Bump NewPipeExtractor to fix trailers and other YouTube videos (#2906) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b93c4474c..8456da66e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ lifecycleKtx = "2.10.0" material = "1.14.0" media3 = "1.9.3" navigationKtx = "2.9.8" -newpipeextractor = "v0.26.2" +newpipeextractor = "v0.26.3" nextlibMedia3 = "1.9.3-0.12.0" nicehttp = "0.4.18" overlappingpanels = "0.1.5" From 8012c580690fe9b2c67e7178ea29f780ccb02147 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:24:50 -0600 Subject: [PATCH 04/15] Set explicitNulls = false for kotlinx serialization (#2897) This is more inline with Jackson's behavior otherwise for nullable types with non default values, and they don't exist, gives a Missingfield exception whereas it worked on Jackson. We may need coerceInputValues = true also but I am unsure of that right now. --- .../src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index fc2fac005..fe3e03ef2 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -89,6 +89,7 @@ class ErrorLoadingException(message: String? = null) : Exception(message) @Prerelease val json = Json { encodeDefaults = true + explicitNulls = false ignoreUnknownKeys = true } From 18a857723b003891b70b8291f2f2e4f22ec7c9b4 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:26:49 -0600 Subject: [PATCH 05/15] Replace java.net.uri in library (#2839) --- gradle/libs.versions.toml | 2 + library/build.gradle.kts | 1 + .../network/WebViewResolver.android.kt | 5 +- .../com/lagradost/cloudstream3/MainAPI.kt | 37 ++-- .../cloudstream3/extractors/ByseSX.kt | 7 +- .../lagradost/cloudstream3/extractors/Cda.kt | 4 +- .../cloudstream3/extractors/CineMMRedirect.kt | 6 +- .../cloudstream3/extractors/Dailymotion.kt | 11 +- .../cloudstream3/extractors/DoodExtractor.kt | 6 +- .../cloudstream3/extractors/GDMirrorbot.kt | 4 +- .../cloudstream3/extractors/HubCloud.kt | 6 +- .../extractors/InternetArchive.kt | 4 +- .../cloudstream3/extractors/Streamplay.kt | 8 +- .../cloudstream3/extractors/VidStack.kt | 4 +- .../extractors/helper/GogoHelper.kt | 6 +- .../extractors/helper/NineAnimeHelper.kt | 10 +- .../cloudstream3/utils/ExtractorApi.kt | 7 +- .../cloudstream3/utils/HlsPlaylistParser.kt | 198 +++++++++--------- .../cloudstream3/utils/M3u8Helper.kt | 12 +- .../cloudstream3/utils/StringUtils.kt | 32 ++- .../cloudstream3/utils/UnshortenUrl.kt | 110 +++++----- 21 files changed, 247 insertions(+), 233 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8456da66e..f28bc41ab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ kotlinxCollectionsImmutable = "0.4.0" kotlinxCoroutinesCore = "1.11.0" kotlinxDatetime = "0.8.0" kotlinxSerializationJson = "1.11.0" +ktor = "3.5.0" lifecycleKtx = "2.10.0" material = "1.14.0" media3 = "1.9.3" @@ -95,6 +96,7 @@ kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collec kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +ktor-http = { module = "io.ktor:ktor-http", version.ref = "ktor" } lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleKtx" } lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" } material = { module = "com.google.android.material:material", version.ref = "material" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 1652970a6..a1f30fede 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -61,6 +61,7 @@ kotlin { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.serialization.json) // JSON Parser + implementation(libs.ktor.http) implementation(libs.jsoup) // HTML Parser implementation(libs.rhino) // Run JavaScript implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit diff --git a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.android.kt b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.android.kt index bc443b3f8..2f9c9b628 100644 --- a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.android.kt +++ b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.android.kt @@ -15,12 +15,13 @@ import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.nicehttp.requestCreator +import io.ktor.http.Url +import io.ktor.http.decodeURLPart import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response -import java.net.URI /** * When used as Interceptor additionalUrls cannot be returned, use WebViewResolver(...).resolveUsingWebView(...) @@ -211,7 +212,7 @@ actual class WebViewResolver actual constructor( * */ return@runBlocking try { when { - blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith( + blacklistedFiles.any { Url(webViewUrl).encodedPath.decodeURLPart().contains(it) } || webViewUrl.endsWith( "/favicon.ico" ) -> WebResourceResponse( "image/png", diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index fe3e03ef2..4a9e0b10a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -22,6 +22,10 @@ import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.cloudstream3.utils.SubtitleHelper.fromCodeToLangTagIETF import com.lagradost.cloudstream3.utils.SubtitleHelper.fromLanguageToTagIETF import com.lagradost.nicehttp.RequestBodyTypes +import io.ktor.http.Url +import io.ktor.http.URLBuilder +import io.ktor.http.encodedPath +import io.ktor.http.takeFrom import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody @@ -35,7 +39,6 @@ import kotlinx.datetime.format.byUnicodePattern import kotlinx.datetime.format.char import kotlinx.datetime.format.parse import kotlinx.datetime.toInstant -import java.net.URI import kotlinx.serialization.json.Json import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi @@ -176,9 +179,9 @@ object APIHolder { // To get the key suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? { try { - val uri = URI.create(url) + val _url = Url(url) val domain = base64Encode( - (uri.scheme + "://" + uri.host + ":443").encodeToByteArray(), + (_url.protocol.name + "://" + _url.host + ":443").encodeToByteArray(), ).replace("\n", "").replace("=", ".") val vToken = @@ -1330,23 +1333,23 @@ fun getQualityFromString(string: String?): SearchQuality? { * ``` */ fun MainAPI.updateUrl(url: String): String { - try { - val original = URI(url) - val updated = URI(mainUrl) + return try { + val original = Url(url) + val updated = Url(mainUrl) - // URI(String scheme, String userInfo, String host, int port, String path, String query, String fragment) - return URI( - updated.scheme, - original.userInfo, - updated.host, - updated.port, - original.path, - original.query, - original.fragment - ).toString() + URLBuilder().apply { + takeFrom(updated) + user = original.user + password = original.password + encodedPath = original.encodedPath + fragment = original.fragment + + parameters.clear() + parameters.appendAll(original.parameters) + }.buildString() } catch (t: Throwable) { logError(t) - return url + url } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt index cc7293b80..b29d29f5d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt @@ -8,7 +8,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -import java.net.URI +import io.ktor.http.Url +import io.ktor.http.decodeURLPart import javax.crypto.Cipher import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.SecretKeySpec @@ -45,11 +46,11 @@ open class ByseSX : ExtractorApi() { } private fun getBaseUrl(url: String): String { - return URI(url).let { "${it.scheme}://${it.host}" } + return Url(url).let { "${it.protocol.name}://${it.host}" } } private fun getCodeFromUrl(url: String): String { - val path = URI(url).path ?: "" + val path = Url(url).encodedPath.decodeURLPart() return path.trimEnd('/').substringAfterLast('/') } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt index 4b7f8a1cd..5c9f58efc 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.StringUtils.decodeUri +import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl import com.lagradost.cloudstream3.utils.newExtractorLink open class Cda : ExtractorApi() { @@ -64,7 +64,7 @@ open class Cda : ExtractorApi() { .replace("_QWE", "") .replace("_Q5", "") .replace("_IKSDE", "") - a = a.decodeUri() + a = a.decodeUrl() a = a.map { char -> if (char.code in 33..126) { return@map (33 + (char.code + 14) % 94).toChar().toString() diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/CineMMRedirect.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/CineMMRedirect.kt index 62c450073..a85aff8d4 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/CineMMRedirect.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/CineMMRedirect.kt @@ -4,7 +4,7 @@ import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor -import okhttp3.HttpUrl.Companion.toHttpUrl +import io.ktor.http.Url // deobfuscated from https://hglink.to/main.js?v=1.1.3 using https://deobfuscate.io/ private val mirrors = arrayOf( @@ -90,7 +90,7 @@ abstract class CineMMRedirect : ExtractorApi() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val videoId = url.toHttpUrl().encodedPath + val videoId = Url(url).encodedPath val mirror = mirrors.random() // re-use existing extractors by calling the ExtractorApi @@ -98,4 +98,4 @@ abstract class CineMMRedirect : ExtractorApi() { val mirrorUrlWithVideoId = "https://$mirror$videoId" loadExtractor(mirrorUrlWithVideoId, referer, subtitleCallback, callback) } -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt index db6db39d5..4732cafcf 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt @@ -7,9 +7,8 @@ import com.lagradost.cloudstream3.newSubtitleFile import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 -import java.net.URI - - +import io.ktor.http.Url +import io.ktor.http.decodeURLPart class Geodailymotion : Dailymotion() { override val name = "GeoDailymotion" @@ -57,7 +56,6 @@ open class Dailymotion : ExtractorApi() { } } - private fun getEmbedUrl(url: String): String? { if (url.contains("/embed/") || url.contains("/video/")) return url if (url.contains("geo.dailymotion.com")) { @@ -67,9 +65,8 @@ open class Dailymotion : ExtractorApi() { return null } - private fun getVideoId(url: String): String? { - val path = URI(url).path + val path = Url(url).encodedPath.decodeURLPart() val id = path.substringAfter("/video/") return if (id.matches(videoIdRegex)) id else null } @@ -82,7 +79,6 @@ open class Dailymotion : ExtractorApi() { return generateM3u8(name, streamLink, "").forEach(callback) } - data class MetaData( val qualities: Map>?, val subtitles: SubtitlesWrapper? @@ -102,5 +98,4 @@ open class Dailymotion : ExtractorApi() { val label: String, val urls: List ) - } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt index 12bc5a0c5..bce017276 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink -import java.net.URI +import io.ktor.http.Url class Doodspro : DoodLaExtractor() { override var mainUrl = "https://doods.pro" @@ -138,8 +138,6 @@ open class DoodLaExtractor : ExtractorApi() { } private fun getBaseUrl(url: String): String { - return URI(url).let { - "${it.scheme}://${it.host}" - } + return Url(url).let { "${it.protocol.name}://${it.host}" } } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt index e0fefe8aa..ba297067e 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor -import java.net.URI +import io.ktor.http.Url class Techinmind: GDMirrorbot() { override var name = "Techinmind Cloud AIO" @@ -103,7 +103,7 @@ open class GDMirrorbot : ExtractorApi() { } private fun getBaseUrl(url: String): String { - return URI(url).let { "${it.scheme}://${it.host}" } + return Url(url).let { "${it.protocol.name}://${it.host}" } } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt index 4f83bad25..a974df15c 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.loadExtractor import com.lagradost.cloudstream3.utils.newExtractorLink -import java.net.URI +import io.ktor.http.Url class HubCloud : ExtractorApi() { override val name = "Hub-Cloud" @@ -24,7 +24,7 @@ class HubCloud : ExtractorApi() { ) { val tag = "HubCloud" val realUrl = url.takeIf { - try { URI(it).toURL(); true } catch (e: Exception) { Log.e(tag, "Invalid URL: ${e.message}"); false } + try { Url(it); true } catch (e: Exception) { Log.e(tag, "Invalid URL: ${e.message}"); false } } ?: return val baseUrl=getBaseUrl(realUrl) @@ -161,7 +161,7 @@ class HubCloud : ExtractorApi() { private fun getBaseUrl(url: String): String { return try { - URI(url).let { "${it.scheme}://${it.host}" } + Url(url).let { "${it.protocol.name}://${it.host}" } } catch (_: Exception) { "" } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/InternetArchive.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/InternetArchive.kt index 40d817e99..1ac6c789c 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/InternetArchive.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/InternetArchive.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.newSubtitleFile import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.StringUtils.decodeUri +import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl import com.lagradost.cloudstream3.utils.newExtractorLink import org.jsoup.nodes.Document @@ -96,7 +96,7 @@ open class InternetArchive : ExtractorApi() { if (mediaUrl.isNotEmpty()) { val name = if (mediaUrl.count() > 1) { val fileExtension = mediaUrl.substringAfterLast(".") - val fileNameCleaned = fileName.decodeUri().substringBeforeLast('.') + val fileNameCleaned = fileName.decodeUrl().substringBeforeLast('.') "$fileNameCleaned ($fileExtension)" } else this.name callback( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamplay.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamplay.kt index 98481970b..9886300aa 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamplay.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamplay.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -import java.net.URI +import io.ktor.http.Url open class Streamplay : ExtractorApi() { override val name = "Streamplay" @@ -22,9 +22,7 @@ open class Streamplay : ExtractorApi() { ) { val request = app.get(url, referer = referer) val redirectUrl = request.url - val mainServer = URI(redirectUrl).let { - "${it.scheme}://${it.host}" - } + val mainServer = Url(redirectUrl).let { "${it.protocol.name}://${it.host}" } val key = redirectUrl.substringAfter("embed-").substringBefore(".html") val token = request.document.select("script").find { it.data().contains("sitekey:") }?.data() @@ -79,4 +77,4 @@ open class Streamplay : ExtractorApi() { @JsonProperty("label") val label: String? = null, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidStack.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidStack.kt index 846fd851d..63ceb1f3d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidStack.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidStack.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.fixUrl import com.lagradost.cloudstream3.utils.newExtractorLink -import java.net.URI +import io.ktor.http.Url import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec @@ -84,7 +84,7 @@ open class VidStack : ExtractorApi() { private fun getBaseUrl(url: String): String { return try { - URI(url).let { "${it.scheme}://${it.host}" } + Url(url).let { "${it.protocol.name}://${it.host}" } } catch (e: Exception) { Log.e("Vidstack", "getBaseUrl fallback: ${e.message}") mainUrl diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt index a16d41943..31618a32b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt @@ -12,8 +12,8 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +import io.ktor.http.Url import org.jsoup.nodes.Document -import java.net.URI import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec @@ -88,8 +88,8 @@ object GogoHelper { val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall val foundDecryptKey = secretDecryptKey ?: foundKey - val uri = URI(iframeUrl) - val mainUrl = "https://" + uri.host + val url = Url(iframeUrl) + val mainUrl = "https://${url.host}" val encryptedId = cryptoHandler(id, foundIv, foundKey) val encryptRequestData = if (isUsingAdaptiveData) { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/NineAnimeHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/NineAnimeHelper.kt index 2563d40e1..c3b50c7a5 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/NineAnimeHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/NineAnimeHelper.kt @@ -1,7 +1,7 @@ package com.lagradost.cloudstream3.extractors.helper -import com.lagradost.cloudstream3.utils.StringUtils.decodeUri -import com.lagradost.cloudstream3.utils.StringUtils.encodeUri +import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl +import com.lagradost.cloudstream3.utils.StringUtils.encodeUrl // Taken from https://github.com/saikou-app/saikou/blob/b35364c8c2a00364178a472fccf1ab72f09815b4/app/src/main/java/ani/saikou/parsers/anime/NineAnime.kt // GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md @@ -108,8 +108,6 @@ object NineAnimeHelper { } } - fun encode(input: String): String = - input.encodeUri().replace("+", "%20") - - private fun decode(input: String): String = input.decodeUri() + fun encode(input: String): String = input.encodeUrl() + private fun decode(input: String): String = input.decodeUrl() } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index f42128b10..8a71714cf 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -312,11 +312,12 @@ import com.lagradost.cloudstream3.extractors.ZplayerV2 import com.lagradost.cloudstream3.extractors.Ztreamhub import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf +import io.ktor.http.Url +import io.ktor.http.decodeURLPart import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import org.jsoup.Jsoup -import java.net.URI import kotlin.coroutines.cancellation.CancellationException import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @@ -420,7 +421,7 @@ enum class ExtractorLinkType { private fun inferTypeFromUrl(url: String): ExtractorLinkType { val path = try { - URI(url).path + Url(url).encodedPath.decodeURLPart() } catch (_: Throwable) { // don't log magnet links as errors null @@ -819,7 +820,7 @@ constructor( /** * Removes https:// and www. - * To match urls regardless of schema, perhaps Uri() can be used? + * To match urls regardless of schema, perhaps Url() can be used? */ val schemaStripRegex = Regex("""^(https:|)//(www\.|)""") diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index f6da18390..37d5f6285 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -19,8 +19,8 @@ */ package com.lagradost.cloudstream3.utils +import io.ktor.http.Url import java.io.IOException -import java.net.URI import java.nio.ByteBuffer import java.util.UUID import kotlin.io.encoding.Base64 @@ -276,29 +276,29 @@ object HlsPlaylistParser { } } - object UriUtil { - fun resolveToUri(baseUri: String?, referenceUri: String?): URI { - return URI.create(resolve(baseUri, referenceUri)) + object UrlUtil { + fun resolveToUrl(baseUrl: String?, referenceUrl: String?): Url { + return Url(resolve(baseUrl, referenceUrl)) } - /** The length of arrays returned by [.getUriIndices]. */ + /** The length of arrays returned by [.getUrlIndices]. */ private const val INDEX_COUNT: Int = 4 /** - * An index into an array returned by [.getUriIndices]. + * An index into an array returned by [.getUrlIndices]. * * * The value at this position in the array is the index of the ':' after the scheme. Equals -1 - * if the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1), - * including when the URI has no scheme. + * if the URL is a relative reference (no scheme). The hier-part starts at (schemeColon + 1), + * including when the URL has no scheme. */ private const val SCHEME_COLON: Int = 0 /** - * An index into an array returned by [.getUriIndices]. + * An index into an array returned by [.getUrlIndices]. * * * The value at this position in the array is the index of the path part. Equals (schemeColon + @@ -310,7 +310,7 @@ object HlsPlaylistParser { const val PATH: Int = 1 /** - * An index into an array returned by [.getUriIndices]. + * An index into an array returned by [.getUrlIndices]. * * * The value at this position in the array is the index of the query part, including the '?' @@ -321,87 +321,87 @@ object HlsPlaylistParser { const val QUERY: Int = 2 /** - * An index into an array returned by [.getUriIndices]. + * An index into an array returned by [.getUrlIndices]. * * * The value at this position in the array is the index of the fragment part, including the '#' - * before the fragment. Equal to the length of the URI if no fragment part, and (length - 1) if + * before the fragment. Equal to the length of the URL if no fragment part, and (length - 1) if * the fragment part is a single '#' with no data. */ private const val FRAGMENT: Int = 3 /** - * Performs relative resolution of a `referenceUri` with respect to a `baseUri`. + * Performs relative resolution of a `referenceUrl` with respect to a `baseUrl`. * * * The resolution is performed as specified by RFC-3986. * - * @param baseUri The base URI. - * @param referenceUri The reference URI to resolve. + * @param baseUrl The base URL. + * @param referenceUrl The reference URL to resolve. */ - private fun resolve(baseUri: String?, referenceUri: String?): String { - var baseUri = baseUri - var referenceUri = referenceUri - val uri = StringBuilder() + private fun resolve(baseUrl: String?, referenceUrl: String?): String { + var baseUrl = baseUrl + var referenceUrl = referenceUrl + val url = StringBuilder() // Map null onto empty string, to make the following logic simpler. - baseUri = baseUri ?: "" - referenceUri = referenceUri ?: "" + baseUrl = baseUrl ?: "" + referenceUrl = referenceUrl ?: "" - val refIndices: IntArray = getUriIndices(referenceUri) + val refIndices: IntArray = getUrlIndices(referenceUrl) if (refIndices[SCHEME_COLON] != -1) { - // The reference is absolute. The target Uri is the reference. - uri.append(referenceUri) - removeDotSegments(uri, refIndices[PATH], refIndices[QUERY]) - return uri.toString() + // The reference is absolute. The target Url is the reference. + url.append(referenceUrl) + removeDotSegments(url, refIndices[PATH], refIndices[QUERY]) + return url.toString() } - val baseIndices: IntArray = getUriIndices(baseUri) + val baseIndices: IntArray = getUrlIndices(baseUrl) if (refIndices[FRAGMENT] == 0) { - // The reference is empty or contains just the fragment part, then the target Uri is the - // concatenation of the base Uri without its fragment, and the reference. - return uri.append(baseUri, 0, baseIndices[FRAGMENT]).append(referenceUri).toString() + // The reference is empty or contains just the fragment part, then the target Url is the + // concatenation of the base Url without its fragment, and the reference. + return url.append(baseUrl, 0, baseIndices[FRAGMENT]).append(referenceUrl).toString() } if (refIndices[QUERY] == 0) { // The reference starts with the query part. The target is the base up to (but excluding) the // query, plus the reference. - return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString() + return url.append(baseUrl, 0, baseIndices[QUERY]).append(referenceUrl).toString() } if (refIndices[PATH] != 0) { // The reference has authority. The target is the base scheme plus the reference. val baseLimit = baseIndices[SCHEME_COLON] + 1 - uri.append(baseUri, 0, baseLimit).append(referenceUri) + url.append(baseUrl, 0, baseLimit).append(referenceUrl) return removeDotSegments( - uri, + url, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY] ) } - if (referenceUri[refIndices[PATH]] == '/') { + if (referenceUrl[refIndices[PATH]] == '/') { // The reference path is rooted. The target is the base scheme and authority (if any), plus // the reference. - uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri) + url.append(baseUrl, 0, baseIndices[PATH]).append(referenceUrl) return removeDotSegments( - uri, + url, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] ) } - // The target Uri is the concatenation of the base Uri up to (but excluding) the last segment, + // The target Url is the concatenation of the base Url up to (but excluding) the last segment, // and the reference. This can be split into 2 cases: if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH] && baseIndices[PATH] == baseIndices[QUERY] ) { // Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is // needed after the authority, before appending the reference. - uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri) + url.append(baseUrl, 0, baseIndices[PATH]).append('/').append(referenceUrl) return removeDotSegments( - uri, + url, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] + 1 ) @@ -410,22 +410,22 @@ object HlsPlaylistParser { // it. If base hier-part has no '/', it could only mean that it is completely empty or // contains only one segment, in which case the whole hier-part is excluded and the reference // is appended right after the base scheme colon without an added '/'. - val lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1) + val lastSlashIndex = baseUrl.lastIndexOf('/', baseIndices[QUERY] - 1) val baseLimit = if (lastSlashIndex == -1) baseIndices[PATH] else lastSlashIndex + 1 - uri.append(baseUri, 0, baseLimit).append(referenceUri) - return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY]) + url.append(baseUrl, 0, baseLimit).append(referenceUrl) + return removeDotSegments(url, baseIndices[PATH], baseLimit + refIndices[QUERY]) } } /** - * Removes dot segments from the path of a URI. + * Removes dot segments from the path of a URL. * - * @param uri A [StringBuilder] containing the URI. - * @param offset The index of the start of the path in `uri`. - * @param limit The limit (exclusive) of the path in `uri`. + * @param url A [StringBuilder] containing the URL. + * @param offset The index of the start of the path in `url`. + * @param limit The limit (exclusive) of the path in `url`. */ private fun removeDotSegments( - uri: StringBuilder, + url: StringBuilder, offset: Int, limit: Int ): String { @@ -433,9 +433,9 @@ object HlsPlaylistParser { var limit = limit if (offset >= limit) { // Nothing to do. - return uri.toString() + return url.toString() } - if (uri[offset] == '/') { + if (url[offset] == '/') { // If the path starts with a /, always retain it. offset++ } @@ -445,7 +445,7 @@ object HlsPlaylistParser { while (i <= limit) { val nextSegmentStart = if (i == limit) { i - } else if (uri[i] == '/') { + } else if (url[i] == '/') { i + 1 } else { i++ @@ -453,16 +453,16 @@ object HlsPlaylistParser { } // We've encountered the end of a segment or the end of the path. If the final segment was // "." or "..", remove the appropriate segments of the path. - if (i == segmentStart + 1 && uri[segmentStart] == '.') { + if (i == segmentStart + 1 && url[segmentStart] == '.') { // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". - uri.delete(segmentStart, nextSegmentStart) + url.delete(segmentStart, nextSegmentStart) limit -= nextSegmentStart - segmentStart i = segmentStart - } else if (i == segmentStart + 2 && uri[segmentStart] == '.' && uri[segmentStart + 1] == '.') { + } else if (i == segmentStart + 2 && url[segmentStart] == '.' && url[segmentStart + 1] == '.') { // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". - val prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1 + val prevSegmentStart = url.lastIndexOf("/", segmentStart - 2) + 1 val removeFrom = if (prevSegmentStart > offset) prevSegmentStart else offset - uri.delete(removeFrom, nextSegmentStart) + url.delete(removeFrom, nextSegmentStart) limit -= nextSegmentStart - removeFrom segmentStart = prevSegmentStart i = prevSegmentStart @@ -471,41 +471,41 @@ object HlsPlaylistParser { segmentStart = i } } - return uri.toString() + return url.toString() } /** - * Calculates indices of the constituent components of a URI. + * Calculates indices of the constituent components of a URL. * - * @param uriString The URI as a string. + * @param urlString The URL as a string. * @return The corresponding indices. */ - private fun getUriIndices(uriString: String?): IntArray { + private fun getUrlIndices(urlString: String?): IntArray { val indices = IntArray(INDEX_COUNT) - if (uriString.isNullOrEmpty()) { + if (urlString.isNullOrEmpty()) { indices[SCHEME_COLON] = -1 return indices } // Determine outer structure from right to left. - // Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ] - val length = uriString.length - var fragmentIndex = uriString.indexOf('#') + // Url = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + val length = urlString.length + var fragmentIndex = urlString.indexOf('#') if (fragmentIndex == -1) { fragmentIndex = length } - var queryIndex = uriString.indexOf('?') + var queryIndex = urlString.indexOf('?') if (queryIndex == -1 || queryIndex > fragmentIndex) { // '#' before '?': '?' is within the fragment. queryIndex = fragmentIndex } // Slashes are allowed only in hier-part so any colon after the first slash is part of the // hier-part, not the scheme colon separator. - var schemeIndexLimit = uriString.indexOf('/') + var schemeIndexLimit = urlString.indexOf('/') if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) { schemeIndexLimit = queryIndex } - var schemeIndex = uriString.indexOf(':') + var schemeIndex = urlString.indexOf(':') if (schemeIndex > schemeIndexLimit) { // '/' before ':' schemeIndex = -1 @@ -514,10 +514,10 @@ object HlsPlaylistParser { // Determine hier-part structure: hier-part = "//" authority path / path // This block can also cope with schemeIndex == -1. val hasAuthority = - schemeIndex + 2 < queryIndex && uriString[schemeIndex + 1] == '/' && uriString[schemeIndex + 2] == '/' + schemeIndex + 2 < queryIndex && urlString[schemeIndex + 1] == '/' && urlString[schemeIndex + 2] == '/' var pathIndex: Int if (hasAuthority) { - pathIndex = uriString.indexOf('/', schemeIndex + 3) // find first '/' after "://" + pathIndex = urlString.indexOf('/', schemeIndex + 3) // find first '/' after "://" if (pathIndex == -1 || pathIndex > queryIndex) { pathIndex = queryIndex } @@ -806,7 +806,7 @@ object HlsPlaylistParser { const val APPLICATION_MEDIA3_CUES: String = "$BASE_TYPE_APPLICATION/x-media3-cues" - /** MIME type for an image URI loaded from an external image management framework. */ + /** MIME type for an image URL loaded from an external image management framework. */ const val APPLICATION_EXTERNALLY_LOADED_IMAGE: String = "$BASE_TYPE_APPLICATION/x-image-uri" @@ -1177,11 +1177,11 @@ object HlsPlaylistParser { val keyFormatVersions = parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions) if (KEYFORMAT_WIDEVINE_PSSH_BINARY == keyFormat) { - val uriString = parseStringAttr(line, REGEX_URI, variableDefinitions) + val urlString = parseStringAttr(line, REGEX_URI, variableDefinitions) return SchemeData( uuid = C.WIDEVINE_UUID, mimeType = MimeTypes.VIDEO_MP4, - data = Base64.Default.decode(uriString.substring(uriString.indexOf(','))) + data = Base64.Default.decode(urlString.substring(urlString.indexOf(','))) ) } else if (KEYFORMAT_WIDEVINE_PSSH_JSON == keyFormat) { return SchemeData( @@ -1190,9 +1190,9 @@ object HlsPlaylistParser { data = line.encodeToByteArray() ) } else if (KEYFORMAT_PLAYREADY == keyFormat && "1" == keyFormatVersions) { - val uriString = parseStringAttr(line, REGEX_URI, variableDefinitions) + val urlString = parseStringAttr(line, REGEX_URI, variableDefinitions) val data: ByteArray = - Base64.Default.decode(uriString.substring(uriString.indexOf(','))) + Base64.Default.decode(urlString.substring(urlString.indexOf(','))) val psshData: ByteArray = PsshAtomUtil.buildPsshAtom( systemId = C.PLAYREADY_UUID, @@ -1270,7 +1270,7 @@ object HlsPlaylistParser { } data class Variant( - val url: URI, + val url: Url, val format: Format, val videoGroupId: String?, val audioGroupId: String?, @@ -1323,7 +1323,7 @@ object HlsPlaylistParser { data class Rendition( /** The rendition's url, or null if the tag does not have a URI attribute. */ - val url: URI?, + val url: Url?, /** Format information associated with this rendition. */ val format: Format, @@ -1336,14 +1336,14 @@ object HlsPlaylistParser { ) data class HlsMultivariantPlaylist( - /** The base uri. Used to resolve relative paths. */ + /** The base url. Used to resolve relative paths. */ val baseUri: String, /** The list of tags in the playlist. */ val tags: List, /** All of the media playlist URLs referenced by the playlist. */ - //val mediaPlaylistUrls: List, + //val mediaPlaylistUrls: List, /** The variants declared by the playlist. */ val variants: List, @@ -1729,8 +1729,8 @@ object HlsPlaylistParser { private fun parseMultivariantPlaylist( iterator: Iterator, baseUri: String ): HlsMultivariantPlaylist { - val urlToVariantInfos: HashMap?> = - HashMap?>() + val urlToVariantInfos: HashMap?> = + HashMap?>() val variableDefinitions = HashMap() val variants: ArrayList = ArrayList() val videos: ArrayList = ArrayList() @@ -1853,10 +1853,10 @@ object HlsPlaylistParser { parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions) val closedCaptionsGroupId: String? = parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions) - val uri: URI + val url: Url if (isIFrameOnlyVariant) { - uri = - UriUtil.resolveToUri( + url = + UrlUtil.resolveToUrl( baseUri, parseStringAttr(line, REGEX_URI, variableDefinitions) ) @@ -1865,14 +1865,14 @@ object HlsPlaylistParser { "#EXT-X-STREAM-INF must be followed by another line", /* cause= */null ) } else { - // The following line contains #EXT-X-STREAM-INF's URI. + // The following line contains #EXT-X-STREAM-INF's URL. line = replaceVariableReferences(iterator.next(), variableDefinitions) - uri = UriUtil.resolveToUri(baseUri, line) + url = UrlUtil.resolveToUrl(baseUri, line) } val variant = Variant( - url = uri, + url = url, format = Format( id = variants.size.toString(), containerMimeType = MimeTypes.APPLICATION_M3U8, @@ -1890,10 +1890,10 @@ object HlsPlaylistParser { captionGroupId = closedCaptionsGroupId ) variants.add(variant) - var variantInfosForUrl: ArrayList? = urlToVariantInfos[uri] + var variantInfosForUrl: ArrayList? = urlToVariantInfos[url] if (variantInfosForUrl == null) { variantInfosForUrl = ArrayList() - urlToVariantInfos[uri] = variantInfosForUrl + urlToVariantInfos[url] = variantInfosForUrl } variantInfosForUrl.add( VariantInfo( @@ -1911,7 +1911,7 @@ object HlsPlaylistParser { // TODO: Don't deduplicate variants by URL. val deduplicatedVariants = variants.distinctBy { it.url } /*val deduplicatedVariants: ArrayList = ArrayList() - val urlsInDeduplicatedVariants = HashSet() + val urlsInDeduplicatedVariants = HashSet() for (i in variants.indices) { val variant: Variant = variants[i] if (urlsInDeduplicatedVariants.add(variant.url)) { @@ -1945,10 +1945,10 @@ object HlsPlaylistParser { containerMimeType = MimeTypes.APPLICATION_M3U8, ) - val referenceUri: String? = + val referenceUrl: String? = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions) - val uri: URI? = - if (referenceUri == null) null else UriUtil.resolveToUri(baseUri, referenceUri) + val url: Url? = + if (referenceUrl == null) null else UrlUtil.resolveToUrl(baseUri, referenceUrl) //val metadata = // Metadata(HlsTrackMetadataEntry(groupId, name, emptyList())) when (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) { @@ -1963,11 +1963,11 @@ object HlsPlaylistParser { codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO) ) } - if (uri == null) { - // TODO: Remove this case and add a Rendition with a null uri to videos. + if (url == null) { + // TODO: Remove this case and add a Rendition with a null url to videos. } else { //formatBuilder.setMetadata(metadata) - videos.add(Rendition(url = uri, format = formatBuilder, groupId, name)) + videos.add(Rendition(url = url, format = formatBuilder, groupId, name)) } } @@ -1995,11 +1995,11 @@ object HlsPlaylistParser { } } val format = formatBuilder.copy(sampleMimeType = sampleMimeType) - if (uri != null) { + if (url != null) { //formatBuilder.setMetadata(metadata) - audios.add(Rendition(uri, format, groupId, name)) + audios.add(Rendition(url, format, groupId, name)) } else if (variant != null) { - // TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios. + // TODO: Remove muxedAudioFormat and add a Rendition with a null url to audios. muxedAudioFormat = format } } @@ -2018,10 +2018,10 @@ object HlsPlaylistParser { if (sampleMimeType == null) { sampleMimeType = MimeTypes.TEXT_VTT } - if (uri != null) { + if (url != null) { subtitles.add( Rendition( - uri, + url, formatBuilder.copy(sampleMimeType = sampleMimeType), groupId, name diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt index b203fd338..00d448d94 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt @@ -112,8 +112,8 @@ object M3u8Helper2 { return c.doFinal(data) } - private fun getParentLink(uri: String): String { - val split = uri.split("/").toMutableList() + private fun getParentLink(url: String): String { + val split = url.split("/").toMutableList() split.removeAt(split.lastIndex) return split.joinToString("/") } @@ -322,15 +322,15 @@ object M3u8Helper2 { if (!match.isNullOrEmpty()) { encryptionState = true - var encryptionUri = match[2] + var encryptionUrl = match[2] - if (isNotCompleteUrl(encryptionUri)) { - encryptionUri = "${getParentLink(playlistStream.streamUrl)}/$encryptionUri" + if (isNotCompleteUrl(encryptionUrl)) { + encryptionUrl = "${getParentLink(playlistStream.streamUrl)}/$encryptionUrl" } encryptionIv = match[3].encodeToByteArray() val encryptionKeyResponse = - app.get(encryptionUri, headers = playlistStream.headers, verify = false) + app.get(encryptionUrl, headers = playlistStream.headers, verify = false) val body = encryptionKeyResponse.body encryptionData = body.bytes() body.close() diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt index 1e3a2ffb7..b9233490a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt @@ -1,14 +1,32 @@ package com.lagradost.cloudstream3.utils -import java.net.URLDecoder -import java.net.URLEncoder +import com.lagradost.cloudstream3.Prerelease +import io.ktor.http.decodeURLQueryComponent +import io.ktor.http.encodeURLParameter object StringUtils { - fun String.encodeUri(): String { - return URLEncoder.encode(this, "UTF-8") + @Prerelease + fun String.decodeUrl(): String { + return this.decodeURLQueryComponent() } - fun String.decodeUri(): String { - return URLDecoder.decode(this, "UTF-8") + @Prerelease + fun String.encodeUrl(): String { + return this.encodeURLParameter() } -} \ No newline at end of file + + // Deprecate after next stable + + /* @Deprecated( + message = "Use Ktor 'Url' naming convention instead.", + replaceWith = ReplaceWith("this.encodeUrl()") + ) */ + fun String.encodeUri(): String = encodeUrl() + + /* @Deprecated( + message = "Use Ktor 'Url' naming convention instead.", + replaceWith = ReplaceWith("this.decodeUrl()") + ) */ + fun String.decodeUri(): String = decodeUrl() +} + diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/UnshortenUrl.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/UnshortenUrl.kt index 6d9862d3a..8bdbf3788 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/UnshortenUrl.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/UnshortenUrl.kt @@ -2,9 +2,9 @@ package com.lagradost.cloudstream3.utils import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode -import com.lagradost.cloudstream3.utils.StringUtils.decodeUri +import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl import com.lagradost.nicehttp.NiceResponse -import java.net.URI +import io.ktor.http.Url // Code heavily based on unshortenit.py form kodiondemand /addon @@ -48,8 +48,8 @@ object ShortLink { } } - suspend fun unshorten(uri: String, type: String? = null): String { - var currentUrl = uri + suspend fun unshorten(url: String, type: String? = null): String { + var currentUrl = url val visitedUrls = mutableSetOf() var count = 10 @@ -57,9 +57,7 @@ object ShortLink { visitedUrls += currentUrl count -= 1 - val domain = - URI(currentUrl.trim()).host - ?: throw IllegalArgumentException("No domain found in URI!") + val domain = Url(currentUrl.trim()).host currentUrl = shortList.firstOrNull { it.regex.find(domain) != null || type == it.type }?.function?.let { it(currentUrl) } ?: break @@ -67,8 +65,8 @@ object ShortLink { return currentUrl.trim() } - suspend fun unshortenAdfly(uri: String): String { - val html = app.get(uri).text + suspend fun unshortenAdfly(url: String): String { + val html = app.get(url).text val ysmm = Regex("""var ysmm =.*;?""").find(html)!!.value if (ysmm.isNotEmpty()) { @@ -81,46 +79,46 @@ object ShortLink { left += c[0] right = c[1] + right } - val encodedUri = (left + right).toMutableList() + val encodedUrl = (left + right).toMutableList() val numbers = - encodedUri.mapIndexed { i, n -> Pair(i, n) }.filter { it.second.isDigit() } + encodedUrl.mapIndexed { i, n -> Pair(i, n) }.filter { it.second.isDigit() } for (el in numbers.chunked(2).dropLastWhile { it.size == 1 }) { val xor = (el[0].second).code.xor(el[1].second.code) if (xor < 10) { - encodedUri[el[0].first] = xor.digitToChar() + encodedUrl[el[0].first] = xor.digitToChar() } } - val encodedbytearray = encodedUri.map { it.code.toByte() }.toByteArray() - var decodedUri = + val encodedbytearray = encodedUrl.map { it.code.toByte() }.toByteArray() + var decodedUrl = base64Decode(encodedbytearray.toString()).dropLast(16) .drop(16) - if (Regex("""go\.php\?u=""").find(decodedUri) != null) { - decodedUri = - base64Decode(decodedUri.replace(Regex("""(.*?)u="""), "")) + if (Regex("""go\.php\?u=""").find(decodedUrl) != null) { + decodedUrl = + base64Decode(decodedUrl.replace(Regex("""(.*?)u="""), "")) } - return decodedUri + return decodedUrl } else { - return uri + return url } } - suspend fun unshortenLinkup(uri: String): String { + suspend fun unshortenLinkup(url: String): String { var r: NiceResponse? = null - var uri = uri + var url = url when { - uri.contains("/tv/") -> uri = uri.replace("/tv/", "/tva/") - uri.contains("delta") -> uri = uri.replace("/delta/", "/adelta/") - (uri.contains("/ga/") || uri.contains("/ga2/")) -> uri = - base64Decode(uri.split('/').last()).trim() + url.contains("/tv/") -> url = url.replace("/tv/", "/tva/") + url.contains("delta") -> url = url.replace("/delta/", "/adelta/") + (url.contains("/ga/") || url.contains("/ga2/")) -> url = + base64Decode(url.split('/').last()).trim() - uri.contains("/speedx/") -> uri = - uri.replace("http://linkup.pro/speedx", "http://speedvideo.net") + url.contains("/speedx/") -> url = + url.replace("http://linkup.pro/speedx", "http://speedvideo.net") else -> { - r = app.get(uri, allowRedirects = true) - uri = r.url + r = app.get(url, allowRedirects = true) + url = r.url val link = Regex("]*src=\\'([^'>]*)\\'[^<>]*>").find(r.text)?.value ?: Regex("""action="(?:[^/]+.*?/[^/]+/([a-zA-Z0-9_]+))">""").find(r.text)?.value @@ -128,40 +126,40 @@ object ShortLink { .elementAtOrNull(1)?.groupValues?.get(1) if (link != null) { - uri = link + url = link } } } - val short = Regex("""^https?://.*?(https?://.*)""").find(uri)?.value + val short = Regex("""^https?://.*?(https?://.*)""").find(url)?.value if (short != null) { - uri = short + url = short } if (r == null) { r = app.get( - uri, + url, allowRedirects = false ) if (r.headers["location"] != null) { - uri = r.headers["location"].toString() + url = r.headers["location"].toString() } } - if (uri.contains("snip.")) { - if (uri.contains("out_generator")) { - uri = Regex("url=(.*)\$").find(uri)!!.value - } else if (uri.contains("/decode/")) { - uri = app.get(uri, allowRedirects = true).url + if (url.contains("snip.")) { + if (url.contains("out_generator")) { + url = Regex("url=(.*)\$").find(url)!!.value + } else if (url.contains("/decode/")) { + url = app.get(url, allowRedirects = true).url } } - return uri + return url } - fun unshortenLinksafe(uri: String): String { - return base64Decode(uri.split("?url=").last()) + fun unshortenLinksafe(url: String): String { + return base64Decode(url.split("?url=").last()) } - suspend fun unshortenNuovoIndirizzo(uri: String): String { - val soup = app.get(uri, allowRedirects = true) + suspend fun unshortenNuovoIndirizzo(url: String): String { + val soup = app.get(url, allowRedirects = true) val header = soup.headers["refresh"] val link: String = if (header != null) { soup.headers["refresh"]!!.substringAfter("=") @@ -171,29 +169,29 @@ object ShortLink { return link } - suspend fun unshortenNuovoLink(uri: String): String { - return app.get(uri, allowRedirects = true).document.selectFirst("a")!!.attr("href") + suspend fun unshortenNuovoLink(url: String): String { + return app.get(url, allowRedirects = true).document.selectFirst("a")!!.attr("href") } - suspend fun unshortenUprot(uri: String): String { - val page = app.get(uri).text + suspend fun unshortenUprot(url: String): String { + val page = app.get(url).text Regex("""]+href="([^"]+)".*Continue""").findAll(page) .map { it.value.replace(""" - if (link.contains("https://maxstream.video") || link.contains("https://uprot.net") && link != uri) { + if (link.contains("https://maxstream.video") || link.contains("https://uprot.net") && link != url) { return link } } - return uri + return url } - fun unshortenDavisonbarker(uri: String): String { - return uri.substringAfter("dest=").decodeUri() + fun unshortenDavisonbarker(url: String): String { + return url.substringAfter("dest=").decodeUrl() } - suspend fun unshortenIsecure(uri: String): String { - val doc = app.get(uri).document - return doc.selectFirst("iframe")?.attr("src")?.trim() ?: uri + suspend fun unshortenIsecure(url: String): String { + val doc = app.get(url).document + return doc.selectFirst("iframe")?.attr("src")?.trim() ?: url } -} \ No newline at end of file +} From b222911e29dadbfdba62c2d44e92f7973c4a647b Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Jun 2026 04:29:29 -0600 Subject: [PATCH 06/15] Fix parseJson inline on stable once again! (#2908) --- .../kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt index 6233fd820..6ab9d3e95 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -60,8 +60,9 @@ object AppUtils { inline fun parseJson(value: String): T { // @Serializable generates a serializer at compile time; contextual serializers are // registered manually in serializersModule, we need both to support all cases - val serializer = runCatching { serializer() }.getOrNull() - ?: json.serializersModule.getContextual(T::class) + val serializer = runCatching { serializer() } + .recoverCatching { json.serializersModule.getContextual(T::class) } + .getOrNull() // Prefer Kotlin Serialization over Jackson if (serializer != null) { From 6f9646e52f475f94005467f6256870357cb948bc Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:06:23 +0200 Subject: [PATCH 07/15] Fix one last issue in JSON parsing :I --- .../kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt index 6ab9d3e95..1c635013f 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -70,6 +70,8 @@ object AppUtils { return json.decodeFromString(serializer, value) } catch (e: SerializationException) { logError(e) + } catch (_: Throwable) { + // Pass, the above code will trigger a NoSuchMethodError on stable due to our previously undefined json variable } } From 55450a02fa14d2364eca8ab5e551d2c0552b4870 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 13 Jun 2026 02:56:45 +0200 Subject: [PATCH 08/15] Merge pull request #2904 from recloudstream/mpvrx Feat: MpvRx --- .../cloudstream3/actions/VideoClickAction.kt | 2 + .../cloudstream3/actions/temp/MpvRxPackage.kt | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvRxPackage.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index 3ed858ef3..ef042e3a6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -20,6 +20,7 @@ import com.lagradost.cloudstream3.actions.temp.MpvExPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage import com.lagradost.cloudstream3.actions.temp.MpvPackage +import com.lagradost.cloudstream3.actions.temp.MpvRxPackage import com.lagradost.cloudstream3.actions.temp.MpvYTDLPackage import com.lagradost.cloudstream3.actions.temp.NextPlayerPackage import com.lagradost.cloudstream3.actions.temp.PlayInBrowserAction @@ -64,6 +65,7 @@ object VideoClickActionHolder { MpvYTDLPackage(), MpvKtPackage(), MpvKtPreviewPackage(), + MpvRxPackage(), // Always Ask option AlwaysAskAction(), // added by plugins diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvRxPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvRxPackage.kt new file mode 100644 index 000000000..e8bb93a99 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvRxPackage.kt @@ -0,0 +1,75 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Context +import android.content.Intent +import androidx.core.net.toUri +import com.lagradost.api.Log +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.actions.updateDurationAndPosition +import com.lagradost.cloudstream3.isEpisodeBased +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.txt + +/** https://github.com/Riteshp2001/mpvRx + * + * https://github.com/Riteshp2001/mpvRx/blob/00e0c5e803ab53e5757426cbf2248448ba1f49bf/app/src/main/java/app/gyrolet/mpvrx/utils/media/MediaUtils.kt#L132 + * https://github.com/Riteshp2001/mpvRx/blob/00e0c5e803ab53e5757426cbf2248448ba1f49bf/app/src/main/java/app/gyrolet/mpvrx/utils/media/MediaUtils.kt#L56 + * */ +class MpvRxPackage : OpenInAppAction( + appName = txt("mpvRx"), + packageName = "app.gyrolet.mpvrx", + intentClass = "app.gyrolet.mpvrx.ui.player.PlayerActivity" +) { + override val oneSource = true + override suspend fun putExtra( + context: Context, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + intent.apply { + putExtra("title", video.name) + val link = result.links[index!!] + val headers = link.headers + + setData(link.url.toUri()) + if (headers.isNotEmpty()) { + // PlayerActivity expects a flat array: [key1, value1, key2, value2, ...] + val flat = headers.entries.flatMap { listOf(it.key, it.value) }.toTypedArray() + intent.putExtra("headers", flat) + } + /*val subs = result.subs // disabled due to https://github.com/Riteshp2001/mpvRx/issues/146 + intent.putExtra("subs", subs.map { it.url.toUri() }.toTypedArray()) + intent.putExtra( + "subs.titles", + subs.map { it.name }.toTypedArray(), + ) + intent.putExtra( + "subs.langs", + subs.map { it.languageCode }.toTypedArray(), + ) + val selected = subs.firstOrNull { it.matchesLanguageCode("en") }?.url?.toUri() + intent.putExtra("subs.enable", selected?.let { arrayOf(it) } ?: arrayOf() )*/ + + if (video.tvType.isEpisodeBased()) { + video.season?.let { intent.putExtra("introdb_season", it) } + video.episode.let { intent.putExtra("introdb_episode", it) } + } + + val position = getViewPos(video.id)?.position + if (position != null) + putExtra("position", position.toInt()) + } + } + + override fun onResult(activity: Activity, intent: Intent?) { + val position = intent?.getIntExtra("position", -1) ?: -1 + val duration = intent?.getIntExtra("duration", -1) ?: -1 + Log.d("MPV", "Position: $position, Duration: $duration") + updateDurationAndPosition(position.toLong(), duration.toLong()) + } +} \ No newline at end of file From 3417fe016051d988361d32d093e0d4df55b34a8f Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 13 Jun 2026 02:58:43 +0200 Subject: [PATCH 09/15] Feat: OnlyPlayer (#2905) --- .../cloudstream3/actions/VideoClickAction.kt | 2 + .../cloudstream3/actions/temp/OnlyPlayer.kt | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/OnlyPlayer.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index ef042e3a6..a864b5fb7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.actions.temp.MpvPackage import com.lagradost.cloudstream3.actions.temp.MpvRxPackage import com.lagradost.cloudstream3.actions.temp.MpvYTDLPackage import com.lagradost.cloudstream3.actions.temp.NextPlayerPackage +import com.lagradost.cloudstream3.actions.temp.OnlyPlayer import com.lagradost.cloudstream3.actions.temp.PlayInBrowserAction import com.lagradost.cloudstream3.actions.temp.PlayMirrorAction import com.lagradost.cloudstream3.actions.temp.ViewM3U8Action @@ -65,6 +66,7 @@ object VideoClickActionHolder { MpvYTDLPackage(), MpvKtPackage(), MpvKtPreviewPackage(), + OnlyPlayer(), MpvRxPackage(), // Always Ask option AlwaysAskAction(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/OnlyPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/OnlyPlayer.kt new file mode 100644 index 000000000..348be440a --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/OnlyPlayer.kt @@ -0,0 +1,44 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.core.net.toUri +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.utils.txt + +/** https://github.com/Kindness-Kismet/only_player/tree/main + * https://github.com/Kindness-Kismet/only_player/blob/main/feature/player/src/main/java/one/only/player/feature/player/PlayerActivity.kt */ +class OnlyPlayer : OpenInAppAction( + txt("Only Player"), + "one.only.player", + intentClass = "one.only.player.feature.player.PlayerActivity" +) { + override val oneSource = true + override suspend fun putExtra( + context: Context, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + /** https://github.com/Kindness-Kismet/only_player/blob/d3f55049a2913fa762d31b311146073cc2da46cb/app/src/main/java/one/only/player/navigation/CloudNavGraph.kt#L39 */ + intent.apply { + val link = result.links[index!!] + setData(link.url.toUri()) + + putExtra("headers", Bundle().apply { + for ((key, value) in link.headers) { + putExtra(key, value) + } + }) + } + } + + override fun onResult(activity: Activity, intent: Intent?) { + /* onResult does not get called */ + } +} \ No newline at end of file From 2c03a3d97664f6f82db2a773e41c20bb579b84dd Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 13 Jun 2026 02:59:02 +0200 Subject: [PATCH 10/15] fix gradient (#2912) --- app/src/main/res/layout/player_custom_layout.xml | 1 + app/src/main/res/layout/player_custom_layout_tv.xml | 1 + app/src/main/res/layout/trailer_custom_layout.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 407de4a3f..04a2a1f1f 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -11,6 +11,7 @@ android:id="@+id/player_metadata_scrim" android:layout_width="640dp" android:layout_height="match_parent" + android:layout_marginTop="-10dp" android:background="@drawable/bg_player_metadata_scrim_netflix" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml index 3a3076943..077929d87 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -12,6 +12,7 @@ android:id="@+id/player_metadata_scrim" android:layout_width="680dp" android:layout_height="match_parent" + android:layout_marginTop="-10dp" android:background="@drawable/bg_player_metadata_scrim_netflix" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index 76231a2d3..88a318874 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -11,6 +11,7 @@ android:id="@+id/player_metadata_scrim" android:layout_width="640dp" android:layout_height="match_parent" + android:layout_marginTop="-10dp" android:background="@drawable/bg_player_metadata_scrim_netflix" android:visibility="gone" app:layout_constraintStart_toStartOf="parent" From c045bfdc0d9a691aa0405d9831a93fb875cc2196 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:03:28 -0600 Subject: [PATCH 11/15] [skip ci] MainAPI: remove `@OptIn(ExperimentalEncodingApi::class)` (#2930) It is stable since Kotlin 2.2.0 --- .../commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 4a9e0b10a..ffc0a938d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -41,7 +41,6 @@ import kotlinx.datetime.format.parse import kotlinx.datetime.toInstant import kotlinx.serialization.json.Json import kotlin.io.encoding.Base64 -import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.math.absoluteValue import kotlin.math.roundToInt import kotlin.time.Clock @@ -716,12 +715,10 @@ fun base64Decode(string: String): String { } } -@OptIn(ExperimentalEncodingApi::class) fun base64DecodeArray(string: String): ByteArray { return Base64.decode(string) } -@OptIn(ExperimentalEncodingApi::class) fun base64Encode(array: ByteArray): String { return Base64.encode(array) } From 943bc551e950bbf292058d26fc89c62278a87494 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:34:25 -0600 Subject: [PATCH 12/15] [skip ci] HlsPlaylistParser: use base64DecodeArray (#2929) --- .../com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index 37d5f6285..cd5d752b3 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -19,12 +19,11 @@ */ package com.lagradost.cloudstream3.utils +import com.lagradost.cloudstream3.base64DecodeArray import io.ktor.http.Url import java.io.IOException import java.nio.ByteBuffer import java.util.UUID -import kotlin.io.encoding.Base64 -import kotlin.io.encoding.ExperimentalEncodingApi @Suppress("unused") object HlsPlaylistParser { @@ -1169,7 +1168,6 @@ object HlsPlaylistParser { return parseOptionalStringAttr(line, pattern, null, variableDefinitions) } - @OptIn(ExperimentalEncodingApi::class) @Throws(ParserException::class) private fun parseDrmSchemeData( line: String, keyFormat: String, variableDefinitions: Map @@ -1181,7 +1179,7 @@ object HlsPlaylistParser { return SchemeData( uuid = C.WIDEVINE_UUID, mimeType = MimeTypes.VIDEO_MP4, - data = Base64.Default.decode(urlString.substring(urlString.indexOf(','))) + data = base64DecodeArray(urlString.substring(urlString.indexOf(','))) ) } else if (KEYFORMAT_WIDEVINE_PSSH_JSON == keyFormat) { return SchemeData( @@ -1192,7 +1190,7 @@ object HlsPlaylistParser { } else if (KEYFORMAT_PLAYREADY == keyFormat && "1" == keyFormatVersions) { val urlString = parseStringAttr(line, REGEX_URI, variableDefinitions) val data: ByteArray = - Base64.Default.decode(urlString.substring(urlString.indexOf(','))) + base64DecodeArray(urlString.substring(urlString.indexOf(','))) val psshData: ByteArray = PsshAtomUtil.buildPsshAtom( systemId = C.PLAYREADY_UUID, From b4100dbfca6ef9cc6ea9842abbed189709b94eb2 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Thu, 18 Jun 2026 01:40:30 +0200 Subject: [PATCH 13/15] feat(extractors): add flyfile.app extractor (#2925) --- .../cloudstream3/extractors/Flyfile.kt | 48 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 2 + 2 files changed, 50 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt new file mode 100644 index 000000000..eb6d474a5 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt @@ -0,0 +1,48 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.Prerelease +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Prerelease +open class Flyfile : ExtractorApi() { + override val name: String = "FlyFile" + override val mainUrl: String = "https://flyfile.app" + open val apiUrl: String = "https://api.flyfile.app" + override val requiresReferer: Boolean = false + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val videoId = url.substringAfterLast("/") + val videoInfo = app.get("$apiUrl/api/streaming/assign/$videoId") + .parsed() + + val streamUrl = "${videoInfo.url}/hls/${videoInfo.token}/master.m3u8" + callback.invoke( + newExtractorLink( + source = name, + name = name, + url = streamUrl, + type = ExtractorLinkType.M3U8 + ) + ) + } + + @Serializable + private data class StreamInfo( + @SerialName("url") + val url: String, + @SerialName("token") + val token: String + ) +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 8a71714cf..f9167d08c 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -81,6 +81,7 @@ import com.lagradost.cloudstream3.extractors.FilemoonV2 import com.lagradost.cloudstream3.extractors.Filesim import com.lagradost.cloudstream3.extractors.Multimoviesshg import com.lagradost.cloudstream3.extractors.FlaswishCom +import com.lagradost.cloudstream3.extractors.Flyfile import com.lagradost.cloudstream3.extractors.FourCX import com.lagradost.cloudstream3.extractors.FourPichive import com.lagradost.cloudstream3.extractors.FourPlayRu @@ -1298,6 +1299,7 @@ val extractorApis: AtomicMutableList = atomicListOf( GUpload(), HlsWish(), ByseQekaho(), + Flyfile() ) From 6f458fc9b57b014ee0fce42c8d90a3b4bc6c6e15 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:41:02 -0600 Subject: [PATCH 14/15] Remove unused classgraph dependency (#2924) --- app/build.gradle.kts | 1 - .../com/lagradost/cloudstream3/SerializationClassTester.kt | 3 +-- gradle/libs.versions.toml | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6c784f3ef..02c1f99e8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -207,7 +207,6 @@ dependencies { testImplementation(libs.junit) testImplementation(libs.json) androidTestImplementation(libs.core) - androidTestImplementation(libs.classgraph) androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.instancio.core) diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt index d1a11e003..80c7b49b0 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt @@ -101,7 +101,7 @@ class SerializationClassTester { } // DEX files are the best solution to read all our classes dynamically. - // ClassGraph() can be used instead, but it only gives results on the JVM, not Android. + // classgraph could be used instead, but it only gives results on the JVM, not Android. @Suppress("DEPRECATION") private fun findSerializableClasses(packageName: String): List> { val context = InstrumentationRegistry @@ -109,7 +109,6 @@ class SerializationClassTester { .targetContext val dexFile = DexFile(context.packageCodePath) - return dexFile.entries() .toList() .filter { it.startsWith(packageName) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f28bc41ab..80e342c2c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,6 @@ annotation = "1.10.0" appcompat = "1.7.1" biometric = "1.4.0-alpha07" buildkonfigGradlePlugin = "0.21.2" -classgraph = "4.8.184" coil = { strictly = "3.3.0" } # Later versions require jvmTarget 11 or later colorpicker = "6b46b49" conscryptAndroid = { strictly = "2.5.2" } # 2.5.3 crashes everything @@ -70,7 +69,6 @@ anime-db = { module = "com.github.recloudstream:anime-db", version.ref = "animeD annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } -classgraph = { group = "io.github.classgraph", name = "classgraph", version.ref = "classgraph" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } colorpicker = { module = "com.github.recloudstream:color-picker-android", version.ref = "colorpicker" } From 3c6bf2984e6976e20e8861dcc543bcf3e628e79b Mon Sep 17 00:00:00 2001 From: KingLucius Date: Fri, 19 Jun 2026 01:06:46 +0300 Subject: [PATCH 15/15] SyncApi Search query fix (#2932) --- .../syncproviders/providers/AniListApi.kt | 25 ++++++++++--------- .../syncproviders/providers/MALApi.kt | 14 +++++------ .../syncproviders/providers/SimklApi.kt | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 7a46b4113..177018e19 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -50,7 +50,8 @@ class AniListApi : SyncAPI() { override suspend fun login(redirectUrl: String, payload: String?): AuthToken? { val sanitizer = splitRedirectUrl(redirectUrl) val token = AuthToken( - accessToken = sanitizer["access_token"] ?: throw ErrorLoadingException("No access token"), + accessToken = sanitizer["access_token"] + ?: throw ErrorLoadingException("No access token"), //refreshToken = sanitizer["refresh_token"], accessTokenLifetime = unixTime + sanitizer["expires_in"]!!.toLong(), ) @@ -83,8 +84,8 @@ class AniListApi : SyncAPI() { return "$mainUrl/anime/$id" } - override suspend fun search(auth : AuthData?, query: String): List? { - val data = searchShows(name) ?: return null + override suspend fun search(auth: AuthData?, query: String): List? { + val data = searchShows(query) ?: return null return data.data?.page?.media?.map { SyncAPI.SyncSearchResult( it.title.romaji ?: return null, @@ -96,7 +97,7 @@ class AniListApi : SyncAPI() { } } - override suspend fun load(auth : AuthData?, id: String): SyncAPI.SyncResult? { + override suspend fun load(auth: AuthData?, id: String): SyncAPI.SyncResult? { val internalId = (Regex("anilist\\.co/anime/(\\d*)").find(id)?.groupValues?.getOrNull(1) ?: id).toIntOrNull() ?: throw ErrorLoadingException("Invalid internalId") val season = getSeason(internalId).data.media @@ -158,7 +159,7 @@ class AniListApi : SyncAPI() { ) } - override suspend fun status(auth : AuthData?, id: String): SyncAPI.AbstractSyncStatus? { + override suspend fun status(auth: AuthData?, id: String): SyncAPI.AbstractSyncStatus? { val internalId = id.toIntOrNull() ?: return null val data = getDataAboutId(auth ?: return null, internalId) ?: return null @@ -459,7 +460,7 @@ class AniListApi : SyncAPI() { } } - private suspend fun getDataAboutId(auth : AuthData, id: Int): AniListTitleHolder? { + private suspend fun getDataAboutId(auth: AuthData, id: Int): AniListTitleHolder? { val q = """query (${'$'}id: Int = $id) { # Define which variables will be used in the query (id) Media (id: ${'$'}id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query) @@ -506,7 +507,7 @@ class AniListApi : SyncAPI() { } - private suspend fun postApi(token : AuthToken, q: String, cache: Boolean = false): String? { + private suspend fun postApi(token: AuthToken, q: String, cache: Boolean = false): String? { return app.post( "https://graphql.anilist.co/", headers = mapOf( @@ -638,7 +639,7 @@ class AniListApi : SyncAPI() { } } - override suspend fun library(auth : AuthData?): SyncAPI.LibraryMetadata? { + override suspend fun library(auth: AuthData?): SyncAPI.LibraryMetadata? { val list = getAniListAnimeListSmart(auth ?: return null)?.groupBy { convertAniListStringToStatus(it.status ?: "").stringRes }?.mapValues { group -> @@ -666,7 +667,7 @@ class AniListApi : SyncAPI() { ) } - private suspend fun getFullAniListList(auth : AuthData): FullAnilistList? { + private suspend fun getFullAniListList(auth: AuthData): FullAnilistList? { val userID = auth.user.id val mediaType = "ANIME" @@ -714,7 +715,7 @@ class AniListApi : SyncAPI() { return text?.toKotlinObject() } - suspend fun toggleLike(auth : AuthData, id: Int): Boolean { + suspend fun toggleLike(auth: AuthData, id: Int): Boolean { val q = """mutation (${'$'}animeId: Int = $id) { ToggleFavourite (animeId: ${'$'}animeId) { anime { @@ -737,7 +738,7 @@ class AniListApi : SyncAPI() { data class MediaListId(@JsonProperty("id") val id: Long? = null) private suspend fun postDataAboutId( - auth : AuthData, + auth: AuthData, id: Int, type: AniListStatusType, score: Score?, @@ -786,7 +787,7 @@ class AniListApi : SyncAPI() { return data != "" } - private suspend fun getUser(token : AuthToken): AniListUser? { + private suspend fun getUser(token: AuthToken): AniListUser? { val q = """ { Viewer { diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index ba0195be6..c0a80b3c9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -98,9 +98,9 @@ class MALApi : SyncAPI() { ) } - override suspend fun search(auth : AuthData?, query: String): List? { + override suspend fun search(auth: AuthData?, query: String): List? { val auth = auth?.token?.accessToken ?: return null - val url = "$apiUrl/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT" + val url = "$apiUrl/v2/anime?q=$query&limit=$MAL_MAX_SEARCH_LIMIT" val res = app.get( url, headers = mapOf( "Authorization" to "Bearer $auth", @@ -122,7 +122,7 @@ class MALApi : SyncAPI() { Regex("""/anime/((.*)/|(.*))""").find(url)!!.groupValues.first() override suspend fun updateStatus( - auth : AuthData?, + auth: AuthData?, id: String, newStatus: SyncAPI.AbstractSyncStatus ): Boolean { @@ -225,7 +225,7 @@ class MALApi : SyncAPI() { ) } - override suspend fun load(auth : AuthData?, id: String): SyncAPI.SyncResult? { + override suspend fun load(auth: AuthData?, id: String): SyncAPI.SyncResult? { val auth = auth?.token?.accessToken ?: return null val internalId = id.toIntOrNull() ?: return null val url = @@ -271,7 +271,7 @@ class MALApi : SyncAPI() { } } - override suspend fun status(auth : AuthData?, id: String): SyncAPI.AbstractSyncStatus? { + override suspend fun status(auth: AuthData?, id: String): SyncAPI.AbstractSyncStatus? { val auth = auth?.token?.accessToken ?: return null // https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get @@ -477,7 +477,7 @@ class MALApi : SyncAPI() { @JsonProperty("start_time") val startTime: String? ) - override suspend fun library(auth : AuthData?): LibraryMetadata? { + override suspend fun library(auth: AuthData?): LibraryMetadata? { val list = getMalAnimeListSmart(auth ?: return null)?.groupBy { convertToStatus(it.listStatus?.status ?: "").stringRes }?.mapValues { group -> @@ -505,7 +505,7 @@ class MALApi : SyncAPI() { ) } - private suspend fun getMalAnimeListSmart(auth : AuthData): Array? { + private suspend fun getMalAnimeListSmart(auth: AuthData): Array? { return if (requireLibraryRefresh) { val list = getMalAnimeList(auth.token) setKey(MAL_CACHED_LIST, auth.user.id.toString(), list) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt index 84a498bb0..3110b23ac 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt @@ -911,7 +911,7 @@ class SimklApi : SyncAPI() { override suspend fun search(auth: AuthData?, query: String): List? { return app.get( - "$mainUrl/search/", params = mapOf("client_id" to CLIENT_ID, "q" to name) + "$mainUrl/search/", params = mapOf("client_id" to CLIENT_ID, "q" to query) ).parsedSafe>()?.mapNotNull { it.toSyncSearchResult() } }