diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 02c1f99e8..6c784f3ef 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -207,6 +207,7 @@ 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 80c7b49b0..d1a11e003 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 could be used instead, but it only gives results on the JVM, not Android. + // ClassGraph() can 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,6 +109,7 @@ class SerializationClassTester { .targetContext val dexFile = DexFile(context.packageCodePath) + return dexFile.entries() .toList() .filter { it.startsWith(packageName) } 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 a864b5fb7..ef042e3a6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -23,7 +23,6 @@ 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 @@ -66,7 +65,6 @@ 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 deleted file mode 100644 index 348be440a..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/OnlyPlayer.kt +++ /dev/null @@ -1,44 +0,0 @@ -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 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 177018e19..7a46b4113 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,8 +50,7 @@ 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(), ) @@ -84,8 +83,8 @@ class AniListApi : SyncAPI() { return "$mainUrl/anime/$id" } - override suspend fun search(auth: AuthData?, query: String): List? { - val data = searchShows(query) ?: return null + override suspend fun search(auth : AuthData?, query: String): List? { + val data = searchShows(name) ?: return null return data.data?.page?.media?.map { SyncAPI.SyncSearchResult( it.title.romaji ?: return null, @@ -97,7 +96,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 @@ -159,7 +158,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 @@ -460,7 +459,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) @@ -507,7 +506,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( @@ -639,7 +638,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 -> @@ -667,7 +666,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" @@ -715,7 +714,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 { @@ -738,7 +737,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?, @@ -787,7 +786,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 c0a80b3c9..ba0195be6 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=$query&limit=$MAL_MAX_SEARCH_LIMIT" + val url = "$apiUrl/v2/anime?q=$name&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 3110b23ac..84a498bb0 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 query) + "$mainUrl/search/", params = mapOf("client_id" to CLIENT_ID, "q" to name) ).parsedSafe>()?.mapNotNull { it.toSyncSearchResult() } } diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 04a2a1f1f..407de4a3f 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -11,7 +11,6 @@ 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 077929d87..3a3076943 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -12,7 +12,6 @@ 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 88a318874..76231a2d3 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -11,7 +11,6 @@ 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" diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index e674fafd6..6ccb9bc69 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 - Zeige Playerinformationen + Playerinformationen anzeigen 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 - Zeige Cast-Panel - Mediainfo + Cast-Panel zeigen + Medieninfo Quellname Alle herunterladen Möchtest du Episode %s herunter laden? @@ -731,8 +731,4 @@ 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 4e37afdea..ea467833e 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 не презема никаква одговорност за користење на екстензии од трети страни и не обезбедува никаква поддршка за нив! + Предупредување: CloudStream 3 не презема никаква одговорност за користење на екстензии од трети страни и не обезбедува никаква поддршка за нив! Недостасуваат дозволи за складирање. Обиди се повторно. Зачувај Вчитај од датотека @@ -445,7 +445,7 @@ Грешка при правење резервна копија на %s Сокриј го избраниот квалитет на видеото во резултатите од пребарувањето Некои уреди не го поддржуваат новиот инсталатор на пакети. Испробај ја легаси(старата) опција, ако ажурирањата не се инсталираат. - Прикажи информации за плеерот + Резолуција на видео плеер Големина на видео баферот Распоред Стандардно @@ -705,37 +705,4 @@ Горе во центар Горе на десно Пушти ја целата серија - Редица за преземање - Моментално нема преземања во редицата. - Дополнителна осветленост - Овозможи филтер за осветленост кога ќе се надмине 100% осветленост на екранот - овозможена_дополнителна_осветленост - Предлози за пребарување - Прикажувај предлози за пребарување додека пишуваш - Исчисти предлози - Прикажи преклоп со метаподатоци на плеерот - Прикажи панел за емитување - Инсталирај предиздавачка верзија - Предиздавачката верзија е веќе инсталирана. - Неуспешна инсталација на предиздавачката верзија. - Видео - Текст на епизода - Информации за медиумот - Преглед - Приоритет на извор - Одреди како ќе се подредуваат видео изворите во плеерот - Име на изворот - Преземи сѐ - Откажи сѐ - Дали сакате да ја преземете епизодата %s? - Дали сакате да ги откажете сите преземања во редицата? - - %d активно преземање - %d активни преземања - - - %d преземање во редицата - %d преземања во редицата - - Во живо diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80e342c2c..b93c4474c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ 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 @@ -31,12 +32,11 @@ 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" navigationKtx = "2.9.8" -newpipeextractor = "v0.26.3" +newpipeextractor = "v0.26.2" nextlibMedia3 = "1.9.3-0.12.0" nicehttp = "0.4.18" overlappingpanels = "0.1.5" @@ -69,6 +69,7 @@ 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" } @@ -94,7 +95,6 @@ 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 a1f30fede..1652970a6 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -61,7 +61,6 @@ 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 2f9c9b628..bc443b3f8 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,13 +15,12 @@ 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(...) @@ -212,7 +211,7 @@ actual class WebViewResolver actual constructor( * */ return@runBlocking try { when { - blacklistedFiles.any { Url(webViewUrl).encodedPath.decodeURLPart().contains(it) } || webViewUrl.endsWith( + blacklistedFiles.any { URI(webViewUrl).path.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 ffc0a938d..5d4deba24 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -22,10 +22,6 @@ 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 @@ -39,8 +35,11 @@ import kotlinx.datetime.format.byUnicodePattern 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 import kotlin.math.absoluteValue import kotlin.math.roundToInt import kotlin.time.Clock @@ -91,7 +90,6 @@ class ErrorLoadingException(message: String? = null) : Exception(message) @Prerelease val json = Json { encodeDefaults = true - explicitNulls = false ignoreUnknownKeys = true } @@ -178,9 +176,9 @@ object APIHolder { // To get the key suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? { try { - val _url = Url(url) + val uri = URI.create(url) val domain = base64Encode( - (_url.protocol.name + "://" + _url.host + ":443").encodeToByteArray(), + (uri.scheme + "://" + uri.host + ":443").encodeToByteArray(), ).replace("\n", "").replace("=", ".") val vToken = @@ -715,10 +713,12 @@ 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) } @@ -1330,23 +1330,23 @@ fun getQualityFromString(string: String?): SearchQuality? { * ``` */ fun MainAPI.updateUrl(url: String): String { - return try { - val original = Url(url) - val updated = Url(mainUrl) + try { + val original = URI(url) + val updated = URI(mainUrl) - URLBuilder().apply { - takeFrom(updated) - user = original.user - password = original.password - encodedPath = original.encodedPath - fragment = original.fragment - - parameters.clear() - parameters.appendAll(original.parameters) - }.buildString() + // 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() } catch (t: Throwable) { logError(t) - url + return url } } @@ -1510,7 +1510,7 @@ constructor( override var posterUrl: String? = null, var year: Int? = null, - var dubStatus: MutableSet? = null, + var dubStatus: EnumSet? = null, var otherName: String? = null, var episodes: MutableMap = mutableMapOf(), @@ -1522,7 +1522,7 @@ constructor( ) : SearchResponse fun AnimeSearchResponse.addDubStatus(status: DubStatus, episodes: Int? = null) { - this.dubStatus = dubStatus?.also { it.add(status) } ?: mutableSetOf(status) + this.dubStatus = dubStatus?.also { it.add(status) } ?: EnumSet.of(status) if (this.type?.isMovieType() != true) if (episodes != null && episodes > 0) this.episodes[status] = episodes 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 b29d29f5d..cc7293b80 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt @@ -8,8 +8,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.M3u8Helper -import io.ktor.http.Url -import io.ktor.http.decodeURLPart +import java.net.URI import javax.crypto.Cipher import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.SecretKeySpec @@ -46,11 +45,11 @@ open class ByseSX : ExtractorApi() { } private fun getBaseUrl(url: String): String { - return Url(url).let { "${it.protocol.name}://${it.host}" } + return URI(url).let { "${it.scheme}://${it.host}" } } private fun getCodeFromUrl(url: String): String { - val path = Url(url).encodedPath.decodeURLPart() + val path = URI(url).path ?: "" 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 5c9f58efc..4b7f8a1cd 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.decodeUrl +import com.lagradost.cloudstream3.utils.StringUtils.decodeUri 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.decodeUrl() + a = a.decodeUri() 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 a85aff8d4..62c450073 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 io.ktor.http.Url +import okhttp3.HttpUrl.Companion.toHttpUrl // 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(url).encodedPath + val videoId = url.toHttpUrl().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 4732cafcf..db6db39d5 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt @@ -7,8 +7,9 @@ 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 io.ktor.http.Url -import io.ktor.http.decodeURLPart +import java.net.URI + + class Geodailymotion : Dailymotion() { override val name = "GeoDailymotion" @@ -56,6 +57,7 @@ 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")) { @@ -65,8 +67,9 @@ open class Dailymotion : ExtractorApi() { return null } + private fun getVideoId(url: String): String? { - val path = Url(url).encodedPath.decodeURLPart() + val path = URI(url).path val id = path.substringAfter("/video/") return if (id.matches(videoIdRegex)) id else null } @@ -79,6 +82,7 @@ open class Dailymotion : ExtractorApi() { return generateM3u8(name, streamLink, "").forEach(callback) } + data class MetaData( val qualities: Map>?, val subtitles: SubtitlesWrapper? @@ -98,4 +102,5 @@ 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 bce017276..12bc5a0c5 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 io.ktor.http.Url +import java.net.URI class Doodspro : DoodLaExtractor() { override var mainUrl = "https://doods.pro" @@ -138,6 +138,8 @@ open class DoodLaExtractor : ExtractorApi() { } private fun getBaseUrl(url: String): String { - return Url(url).let { "${it.protocol.name}://${it.host}" } + return URI(url).let { + "${it.scheme}://${it.host}" + } } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt deleted file mode 100644 index eb6d474a5..000000000 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt +++ /dev/null @@ -1,48 +0,0 @@ -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/extractors/GDMirrorbot.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt index ba297067e..e0fefe8aa 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 io.ktor.http.Url +import java.net.URI class Techinmind: GDMirrorbot() { override var name = "Techinmind Cloud AIO" @@ -103,7 +103,7 @@ open class GDMirrorbot : ExtractorApi() { } private fun getBaseUrl(url: String): String { - return Url(url).let { "${it.protocol.name}://${it.host}" } + return URI(url).let { "${it.scheme}://${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 a974df15c..4f83bad25 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 io.ktor.http.Url +import java.net.URI class HubCloud : ExtractorApi() { override val name = "Hub-Cloud" @@ -24,7 +24,7 @@ class HubCloud : ExtractorApi() { ) { val tag = "HubCloud" val realUrl = url.takeIf { - try { Url(it); true } catch (e: Exception) { Log.e(tag, "Invalid URL: ${e.message}"); false } + try { URI(it).toURL(); 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 { - Url(url).let { "${it.protocol.name}://${it.host}" } + URI(url).let { "${it.scheme}://${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 1ac6c789c..40d817e99 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.decodeUrl +import com.lagradost.cloudstream3.utils.StringUtils.decodeUri 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.decodeUrl().substringBeforeLast('.') + val fileNameCleaned = fileName.decodeUri().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 9886300aa..98481970b 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 io.ktor.http.Url +import java.net.URI open class Streamplay : ExtractorApi() { override val name = "Streamplay" @@ -22,7 +22,9 @@ open class Streamplay : ExtractorApi() { ) { val request = app.get(url, referer = referer) val redirectUrl = request.url - val mainServer = Url(redirectUrl).let { "${it.protocol.name}://${it.host}" } + val mainServer = URI(redirectUrl).let { + "${it.scheme}://${it.host}" + } val key = redirectUrl.substringAfter("embed-").substringBefore(".html") val token = request.document.select("script").find { it.data().contains("sitekey:") }?.data() @@ -77,4 +79,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 63ceb1f3d..846fd851d 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 io.ktor.http.Url +import java.net.URI 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 { - Url(url).let { "${it.protocol.name}://${it.host}" } + URI(url).let { "${it.scheme}://${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 31618a32b..a16d41943 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 url = Url(iframeUrl) - val mainUrl = "https://${url.host}" + val uri = URI(iframeUrl) + val mainUrl = "https://" + uri.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 c3b50c7a5..2563d40e1 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.decodeUrl -import com.lagradost.cloudstream3.utils.StringUtils.encodeUrl +import com.lagradost.cloudstream3.utils.StringUtils.decodeUri +import com.lagradost.cloudstream3.utils.StringUtils.encodeUri // 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,6 +108,8 @@ object NineAnimeHelper { } } - fun encode(input: String): String = input.encodeUrl() - private fun decode(input: String): String = input.decodeUrl() + fun encode(input: String): String = + input.encodeUri().replace("+", "%20") + + private fun decode(input: String): String = input.decodeUri() } \ No newline at end of file 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 1c635013f..6233fd820 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -60,9 +60,8 @@ 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() } - .recoverCatching { json.serializersModule.getContextual(T::class) } - .getOrNull() + val serializer = runCatching { serializer() }.getOrNull() + ?: json.serializersModule.getContextual(T::class) // Prefer Kotlin Serialization over Jackson if (serializer != null) { @@ -70,8 +69,6 @@ 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 } } 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 f9167d08c..f42128b10 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -81,7 +81,6 @@ 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 @@ -313,12 +312,11 @@ 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 @@ -422,7 +420,7 @@ enum class ExtractorLinkType { private fun inferTypeFromUrl(url: String): ExtractorLinkType { val path = try { - Url(url).encodedPath.decodeURLPart() + URI(url).path } catch (_: Throwable) { // don't log magnet links as errors null @@ -821,7 +819,7 @@ constructor( /** * Removes https:// and www. - * To match urls regardless of schema, perhaps Url() can be used? + * To match urls regardless of schema, perhaps Uri() can be used? */ val schemaStripRegex = Regex("""^(https:|)//(www\.|)""") @@ -1299,7 +1297,6 @@ val extractorApis: AtomicMutableList = atomicListOf( GUpload(), HlsWish(), ByseQekaho(), - Flyfile() ) 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 cd5d752b3..f6da18390 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -19,11 +19,12 @@ */ package com.lagradost.cloudstream3.utils -import com.lagradost.cloudstream3.base64DecodeArray -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 +import kotlin.io.encoding.ExperimentalEncodingApi @Suppress("unused") object HlsPlaylistParser { @@ -275,29 +276,29 @@ object HlsPlaylistParser { } } - object UrlUtil { - fun resolveToUrl(baseUrl: String?, referenceUrl: String?): Url { - return Url(resolve(baseUrl, referenceUrl)) + object UriUtil { + fun resolveToUri(baseUri: String?, referenceUri: String?): URI { + return URI.create(resolve(baseUri, referenceUri)) } - /** The length of arrays returned by [.getUrlIndices]. */ + /** The length of arrays returned by [.getUriIndices]. */ private const val INDEX_COUNT: Int = 4 /** - * An index into an array returned by [.getUrlIndices]. + * An index into an array returned by [.getUriIndices]. * * * The value at this position in the array is the index of the ':' after the scheme. Equals -1 - * if the URL is a relative reference (no scheme). The hier-part starts at (schemeColon + 1), - * including when the URL has no scheme. + * if the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1), + * including when the URI has no scheme. */ private const val SCHEME_COLON: Int = 0 /** - * An index into an array returned by [.getUrlIndices]. + * An index into an array returned by [.getUriIndices]. * * * The value at this position in the array is the index of the path part. Equals (schemeColon + @@ -309,7 +310,7 @@ object HlsPlaylistParser { const val PATH: Int = 1 /** - * An index into an array returned by [.getUrlIndices]. + * An index into an array returned by [.getUriIndices]. * * * The value at this position in the array is the index of the query part, including the '?' @@ -320,87 +321,87 @@ object HlsPlaylistParser { const val QUERY: Int = 2 /** - * An index into an array returned by [.getUrlIndices]. + * An index into an array returned by [.getUriIndices]. * * * 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 URL if no fragment part, and (length - 1) if + * before the fragment. Equal to the length of the URI 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 `referenceUrl` with respect to a `baseUrl`. + * Performs relative resolution of a `referenceUri` with respect to a `baseUri`. * * * The resolution is performed as specified by RFC-3986. * - * @param baseUrl The base URL. - * @param referenceUrl The reference URL to resolve. + * @param baseUri The base URI. + * @param referenceUri The reference URI to resolve. */ - private fun resolve(baseUrl: String?, referenceUrl: String?): String { - var baseUrl = baseUrl - var referenceUrl = referenceUrl - val url = StringBuilder() + private fun resolve(baseUri: String?, referenceUri: String?): String { + var baseUri = baseUri + var referenceUri = referenceUri + val uri = StringBuilder() // Map null onto empty string, to make the following logic simpler. - baseUrl = baseUrl ?: "" - referenceUrl = referenceUrl ?: "" + baseUri = baseUri ?: "" + referenceUri = referenceUri ?: "" - val refIndices: IntArray = getUrlIndices(referenceUrl) + val refIndices: IntArray = getUriIndices(referenceUri) if (refIndices[SCHEME_COLON] != -1) { - // The reference is absolute. The target Url is the reference. - url.append(referenceUrl) - removeDotSegments(url, refIndices[PATH], refIndices[QUERY]) - return url.toString() + // The reference is absolute. The target Uri is the reference. + uri.append(referenceUri) + removeDotSegments(uri, refIndices[PATH], refIndices[QUERY]) + return uri.toString() } - val baseIndices: IntArray = getUrlIndices(baseUrl) + val baseIndices: IntArray = getUriIndices(baseUri) if (refIndices[FRAGMENT] == 0) { - // 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() + // 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() } 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 url.append(baseUrl, 0, baseIndices[QUERY]).append(referenceUrl).toString() + return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString() } if (refIndices[PATH] != 0) { // The reference has authority. The target is the base scheme plus the reference. val baseLimit = baseIndices[SCHEME_COLON] + 1 - url.append(baseUrl, 0, baseLimit).append(referenceUrl) + uri.append(baseUri, 0, baseLimit).append(referenceUri) return removeDotSegments( - url, + uri, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY] ) } - if (referenceUrl[refIndices[PATH]] == '/') { + if (referenceUri[refIndices[PATH]] == '/') { // The reference path is rooted. The target is the base scheme and authority (if any), plus // the reference. - url.append(baseUrl, 0, baseIndices[PATH]).append(referenceUrl) + uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri) return removeDotSegments( - url, + uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] ) } - // The target Url is the concatenation of the base Url up to (but excluding) the last segment, + // The target Uri is the concatenation of the base Uri 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. - url.append(baseUrl, 0, baseIndices[PATH]).append('/').append(referenceUrl) + uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri) return removeDotSegments( - url, + uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] + 1 ) @@ -409,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 = baseUrl.lastIndexOf('/', baseIndices[QUERY] - 1) + val lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1) val baseLimit = if (lastSlashIndex == -1) baseIndices[PATH] else lastSlashIndex + 1 - url.append(baseUrl, 0, baseLimit).append(referenceUrl) - return removeDotSegments(url, baseIndices[PATH], baseLimit + refIndices[QUERY]) + uri.append(baseUri, 0, baseLimit).append(referenceUri) + return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY]) } } /** - * Removes dot segments from the path of a URL. + * Removes dot segments from the path of a 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`. + * @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`. */ private fun removeDotSegments( - url: StringBuilder, + uri: StringBuilder, offset: Int, limit: Int ): String { @@ -432,9 +433,9 @@ object HlsPlaylistParser { var limit = limit if (offset >= limit) { // Nothing to do. - return url.toString() + return uri.toString() } - if (url[offset] == '/') { + if (uri[offset] == '/') { // If the path starts with a /, always retain it. offset++ } @@ -444,7 +445,7 @@ object HlsPlaylistParser { while (i <= limit) { val nextSegmentStart = if (i == limit) { i - } else if (url[i] == '/') { + } else if (uri[i] == '/') { i + 1 } else { i++ @@ -452,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 && url[segmentStart] == '.') { + if (i == segmentStart + 1 && uri[segmentStart] == '.') { // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". - url.delete(segmentStart, nextSegmentStart) + uri.delete(segmentStart, nextSegmentStart) limit -= nextSegmentStart - segmentStart i = segmentStart - } else if (i == segmentStart + 2 && url[segmentStart] == '.' && url[segmentStart + 1] == '.') { + } else if (i == segmentStart + 2 && uri[segmentStart] == '.' && uri[segmentStart + 1] == '.') { // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". - val prevSegmentStart = url.lastIndexOf("/", segmentStart - 2) + 1 + val prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1 val removeFrom = if (prevSegmentStart > offset) prevSegmentStart else offset - url.delete(removeFrom, nextSegmentStart) + uri.delete(removeFrom, nextSegmentStart) limit -= nextSegmentStart - removeFrom segmentStart = prevSegmentStart i = prevSegmentStart @@ -470,41 +471,41 @@ object HlsPlaylistParser { segmentStart = i } } - return url.toString() + return uri.toString() } /** - * Calculates indices of the constituent components of a URL. + * Calculates indices of the constituent components of a URI. * - * @param urlString The URL as a string. + * @param uriString The URI as a string. * @return The corresponding indices. */ - private fun getUrlIndices(urlString: String?): IntArray { + private fun getUriIndices(uriString: String?): IntArray { val indices = IntArray(INDEX_COUNT) - if (urlString.isNullOrEmpty()) { + if (uriString.isNullOrEmpty()) { indices[SCHEME_COLON] = -1 return indices } // Determine outer structure from right to left. - // Url = scheme ":" hier-part [ "?" query ] [ "#" fragment ] - val length = urlString.length - var fragmentIndex = urlString.indexOf('#') + // Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + val length = uriString.length + var fragmentIndex = uriString.indexOf('#') if (fragmentIndex == -1) { fragmentIndex = length } - var queryIndex = urlString.indexOf('?') + var queryIndex = uriString.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 = urlString.indexOf('/') + var schemeIndexLimit = uriString.indexOf('/') if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) { schemeIndexLimit = queryIndex } - var schemeIndex = urlString.indexOf(':') + var schemeIndex = uriString.indexOf(':') if (schemeIndex > schemeIndexLimit) { // '/' before ':' schemeIndex = -1 @@ -513,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 && urlString[schemeIndex + 1] == '/' && urlString[schemeIndex + 2] == '/' + schemeIndex + 2 < queryIndex && uriString[schemeIndex + 1] == '/' && uriString[schemeIndex + 2] == '/' var pathIndex: Int if (hasAuthority) { - pathIndex = urlString.indexOf('/', schemeIndex + 3) // find first '/' after "://" + pathIndex = uriString.indexOf('/', schemeIndex + 3) // find first '/' after "://" if (pathIndex == -1 || pathIndex > queryIndex) { pathIndex = queryIndex } @@ -805,7 +806,7 @@ object HlsPlaylistParser { const val APPLICATION_MEDIA3_CUES: String = "$BASE_TYPE_APPLICATION/x-media3-cues" - /** MIME type for an image URL loaded from an external image management framework. */ + /** MIME type for an image URI loaded from an external image management framework. */ const val APPLICATION_EXTERNALLY_LOADED_IMAGE: String = "$BASE_TYPE_APPLICATION/x-image-uri" @@ -1168,6 +1169,7 @@ object HlsPlaylistParser { return parseOptionalStringAttr(line, pattern, null, variableDefinitions) } + @OptIn(ExperimentalEncodingApi::class) @Throws(ParserException::class) private fun parseDrmSchemeData( line: String, keyFormat: String, variableDefinitions: Map @@ -1175,11 +1177,11 @@ object HlsPlaylistParser { val keyFormatVersions = parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions) if (KEYFORMAT_WIDEVINE_PSSH_BINARY == keyFormat) { - val urlString = parseStringAttr(line, REGEX_URI, variableDefinitions) + val uriString = parseStringAttr(line, REGEX_URI, variableDefinitions) return SchemeData( uuid = C.WIDEVINE_UUID, mimeType = MimeTypes.VIDEO_MP4, - data = base64DecodeArray(urlString.substring(urlString.indexOf(','))) + data = Base64.Default.decode(uriString.substring(uriString.indexOf(','))) ) } else if (KEYFORMAT_WIDEVINE_PSSH_JSON == keyFormat) { return SchemeData( @@ -1188,9 +1190,9 @@ object HlsPlaylistParser { data = line.encodeToByteArray() ) } else if (KEYFORMAT_PLAYREADY == keyFormat && "1" == keyFormatVersions) { - val urlString = parseStringAttr(line, REGEX_URI, variableDefinitions) + val uriString = parseStringAttr(line, REGEX_URI, variableDefinitions) val data: ByteArray = - base64DecodeArray(urlString.substring(urlString.indexOf(','))) + Base64.Default.decode(uriString.substring(uriString.indexOf(','))) val psshData: ByteArray = PsshAtomUtil.buildPsshAtom( systemId = C.PLAYREADY_UUID, @@ -1268,7 +1270,7 @@ object HlsPlaylistParser { } data class Variant( - val url: Url, + val url: URI, val format: Format, val videoGroupId: String?, val audioGroupId: String?, @@ -1321,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: Url?, + val url: URI?, /** Format information associated with this rendition. */ val format: Format, @@ -1334,14 +1336,14 @@ object HlsPlaylistParser { ) data class HlsMultivariantPlaylist( - /** The base url. Used to resolve relative paths. */ + /** The base uri. 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, @@ -1727,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() @@ -1851,10 +1853,10 @@ object HlsPlaylistParser { parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions) val closedCaptionsGroupId: String? = parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions) - val url: Url + val uri: URI if (isIFrameOnlyVariant) { - url = - UrlUtil.resolveToUrl( + uri = + UriUtil.resolveToUri( baseUri, parseStringAttr(line, REGEX_URI, variableDefinitions) ) @@ -1863,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 URL. + // The following line contains #EXT-X-STREAM-INF's URI. line = replaceVariableReferences(iterator.next(), variableDefinitions) - url = UrlUtil.resolveToUrl(baseUri, line) + uri = UriUtil.resolveToUri(baseUri, line) } val variant = Variant( - url = url, + url = uri, format = Format( id = variants.size.toString(), containerMimeType = MimeTypes.APPLICATION_M3U8, @@ -1888,10 +1890,10 @@ object HlsPlaylistParser { captionGroupId = closedCaptionsGroupId ) variants.add(variant) - var variantInfosForUrl: ArrayList? = urlToVariantInfos[url] + var variantInfosForUrl: ArrayList? = urlToVariantInfos[uri] if (variantInfosForUrl == null) { variantInfosForUrl = ArrayList() - urlToVariantInfos[url] = variantInfosForUrl + urlToVariantInfos[uri] = variantInfosForUrl } variantInfosForUrl.add( VariantInfo( @@ -1909,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)) { @@ -1943,10 +1945,10 @@ object HlsPlaylistParser { containerMimeType = MimeTypes.APPLICATION_M3U8, ) - val referenceUrl: String? = + val referenceUri: String? = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions) - val url: Url? = - if (referenceUrl == null) null else UrlUtil.resolveToUrl(baseUri, referenceUrl) + val uri: URI? = + if (referenceUri == null) null else UriUtil.resolveToUri(baseUri, referenceUri) //val metadata = // Metadata(HlsTrackMetadataEntry(groupId, name, emptyList())) when (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) { @@ -1961,11 +1963,11 @@ object HlsPlaylistParser { codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO) ) } - if (url == null) { - // TODO: Remove this case and add a Rendition with a null url to videos. + if (uri == null) { + // TODO: Remove this case and add a Rendition with a null uri to videos. } else { //formatBuilder.setMetadata(metadata) - videos.add(Rendition(url = url, format = formatBuilder, groupId, name)) + videos.add(Rendition(url = uri, format = formatBuilder, groupId, name)) } } @@ -1993,11 +1995,11 @@ object HlsPlaylistParser { } } val format = formatBuilder.copy(sampleMimeType = sampleMimeType) - if (url != null) { + if (uri != null) { //formatBuilder.setMetadata(metadata) - audios.add(Rendition(url, format, groupId, name)) + audios.add(Rendition(uri, format, groupId, name)) } else if (variant != null) { - // TODO: Remove muxedAudioFormat and add a Rendition with a null url to audios. + // TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios. muxedAudioFormat = format } } @@ -2016,10 +2018,10 @@ object HlsPlaylistParser { if (sampleMimeType == null) { sampleMimeType = MimeTypes.TEXT_VTT } - if (url != null) { + if (uri != null) { subtitles.add( Rendition( - url, + uri, 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 00d448d94..b203fd338 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(url: String): String { - val split = url.split("/").toMutableList() + private fun getParentLink(uri: String): String { + val split = uri.split("/").toMutableList() split.removeAt(split.lastIndex) return split.joinToString("/") } @@ -322,15 +322,15 @@ object M3u8Helper2 { if (!match.isNullOrEmpty()) { encryptionState = true - var encryptionUrl = match[2] + var encryptionUri = match[2] - if (isNotCompleteUrl(encryptionUrl)) { - encryptionUrl = "${getParentLink(playlistStream.streamUrl)}/$encryptionUrl" + if (isNotCompleteUrl(encryptionUri)) { + encryptionUri = "${getParentLink(playlistStream.streamUrl)}/$encryptionUri" } encryptionIv = match[3].encodeToByteArray() val encryptionKeyResponse = - app.get(encryptionUrl, headers = playlistStream.headers, verify = false) + app.get(encryptionUri, 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 b9233490a..1e3a2ffb7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt @@ -1,32 +1,14 @@ package com.lagradost.cloudstream3.utils -import com.lagradost.cloudstream3.Prerelease -import io.ktor.http.decodeURLQueryComponent -import io.ktor.http.encodeURLParameter +import java.net.URLDecoder +import java.net.URLEncoder object StringUtils { - @Prerelease - fun String.decodeUrl(): String { - return this.decodeURLQueryComponent() + fun String.encodeUri(): String { + return URLEncoder.encode(this, "UTF-8") } - @Prerelease - fun String.encodeUrl(): String { - return this.encodeURLParameter() + fun String.decodeUri(): String { + return URLDecoder.decode(this, "UTF-8") } - - // 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() -} - +} \ No newline at end of file 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 8bdbf3788..6d9862d3a 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.decodeUrl +import com.lagradost.cloudstream3.utils.StringUtils.decodeUri import com.lagradost.nicehttp.NiceResponse -import io.ktor.http.Url +import java.net.URI // Code heavily based on unshortenit.py form kodiondemand /addon @@ -48,8 +48,8 @@ object ShortLink { } } - suspend fun unshorten(url: String, type: String? = null): String { - var currentUrl = url + suspend fun unshorten(uri: String, type: String? = null): String { + var currentUrl = uri val visitedUrls = mutableSetOf() var count = 10 @@ -57,7 +57,9 @@ object ShortLink { visitedUrls += currentUrl count -= 1 - val domain = Url(currentUrl.trim()).host + val domain = + URI(currentUrl.trim()).host + ?: throw IllegalArgumentException("No domain found in URI!") currentUrl = shortList.firstOrNull { it.regex.find(domain) != null || type == it.type }?.function?.let { it(currentUrl) } ?: break @@ -65,8 +67,8 @@ object ShortLink { return currentUrl.trim() } - suspend fun unshortenAdfly(url: String): String { - val html = app.get(url).text + suspend fun unshortenAdfly(uri: String): String { + val html = app.get(uri).text val ysmm = Regex("""var ysmm =.*;?""").find(html)!!.value if (ysmm.isNotEmpty()) { @@ -79,46 +81,46 @@ object ShortLink { left += c[0] right = c[1] + right } - val encodedUrl = (left + right).toMutableList() + val encodedUri = (left + right).toMutableList() val numbers = - encodedUrl.mapIndexed { i, n -> Pair(i, n) }.filter { it.second.isDigit() } + encodedUri.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) { - encodedUrl[el[0].first] = xor.digitToChar() + encodedUri[el[0].first] = xor.digitToChar() } } - val encodedbytearray = encodedUrl.map { it.code.toByte() }.toByteArray() - var decodedUrl = + val encodedbytearray = encodedUri.map { it.code.toByte() }.toByteArray() + var decodedUri = base64Decode(encodedbytearray.toString()).dropLast(16) .drop(16) - if (Regex("""go\.php\?u=""").find(decodedUrl) != null) { - decodedUrl = - base64Decode(decodedUrl.replace(Regex("""(.*?)u="""), "")) + if (Regex("""go\.php\?u=""").find(decodedUri) != null) { + decodedUri = + base64Decode(decodedUri.replace(Regex("""(.*?)u="""), "")) } - return decodedUrl + return decodedUri } else { - return url + return uri } } - suspend fun unshortenLinkup(url: String): String { + suspend fun unshortenLinkup(uri: String): String { var r: NiceResponse? = null - var url = url + var uri = uri when { - 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("/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("/speedx/") -> url = - url.replace("http://linkup.pro/speedx", "http://speedvideo.net") + uri.contains("/speedx/") -> uri = + uri.replace("http://linkup.pro/speedx", "http://speedvideo.net") else -> { - r = app.get(url, allowRedirects = true) - url = r.url + r = app.get(uri, allowRedirects = true) + uri = r.url val link = Regex("]*src=\\'([^'>]*)\\'[^<>]*>").find(r.text)?.value ?: Regex("""action="(?:[^/]+.*?/[^/]+/([a-zA-Z0-9_]+))">""").find(r.text)?.value @@ -126,40 +128,40 @@ object ShortLink { .elementAtOrNull(1)?.groupValues?.get(1) if (link != null) { - url = link + uri = link } } } - val short = Regex("""^https?://.*?(https?://.*)""").find(url)?.value + val short = Regex("""^https?://.*?(https?://.*)""").find(uri)?.value if (short != null) { - url = short + uri = short } if (r == null) { r = app.get( - url, + uri, allowRedirects = false ) if (r.headers["location"] != null) { - url = r.headers["location"].toString() + uri = r.headers["location"].toString() } } - 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 + 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 } } - return url + return uri } - fun unshortenLinksafe(url: String): String { - return base64Decode(url.split("?url=").last()) + fun unshortenLinksafe(uri: String): String { + return base64Decode(uri.split("?url=").last()) } - suspend fun unshortenNuovoIndirizzo(url: String): String { - val soup = app.get(url, allowRedirects = true) + suspend fun unshortenNuovoIndirizzo(uri: String): String { + val soup = app.get(uri, allowRedirects = true) val header = soup.headers["refresh"] val link: String = if (header != null) { soup.headers["refresh"]!!.substringAfter("=") @@ -169,29 +171,29 @@ object ShortLink { return link } - suspend fun unshortenNuovoLink(url: String): String { - return app.get(url, allowRedirects = true).document.selectFirst("a")!!.attr("href") + suspend fun unshortenNuovoLink(uri: String): String { + return app.get(uri, allowRedirects = true).document.selectFirst("a")!!.attr("href") } - suspend fun unshortenUprot(url: String): String { - val page = app.get(url).text + suspend fun unshortenUprot(uri: String): String { + val page = app.get(uri).text Regex("""]+href="([^"]+)".*Continue""").findAll(page) .map { it.value.replace(""" - if (link.contains("https://maxstream.video") || link.contains("https://uprot.net") && link != url) { + if (link.contains("https://maxstream.video") || link.contains("https://uprot.net") && link != uri) { return link } } - return url + return uri } - fun unshortenDavisonbarker(url: String): String { - return url.substringAfter("dest=").decodeUrl() + fun unshortenDavisonbarker(uri: String): String { + return uri.substringAfter("dest=").decodeUri() } - suspend fun unshortenIsecure(url: String): String { - val doc = app.get(url).document - return doc.selectFirst("iframe")?.attr("src")?.trim() ?: url + suspend fun unshortenIsecure(uri: String): String { + val doc = app.get(uri).document + return doc.selectFirst("iframe")?.attr("src")?.trim() ?: uri } -} +} \ No newline at end of file