diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7b63a5ddcae..f467286b57f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ kotlinxCollectionsImmutable = "0.4.0" kotlinxCoroutinesCore = "1.10.2" 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 ead73eec2c4..fce25d97e9e 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.newpipeextractor) 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 bc443b3f878..2f9c9b628cd 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 5d4deba2432..fa673cbe039 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 java.util.EnumSet import kotlinx.serialization.json.Json import kotlin.io.encoding.Base64 @@ -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) - - // 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() + return try { + val original = Url(url) + val updated = Url(mainUrl) + + 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 cc7293b8026..b29d29f5d4e 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 4b7f8a1cd3d..5c9f58efca9 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 62c45007371..a85aff8d48a 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 db6db39d588..4732cafcf6f 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 12bc5a0c53d..bce017276ad 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 e0fefe8aae8..ba297067e73 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 4f83bad2545..a974df15c46 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 40d817e99c2..1ac6c789cbb 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 98481970b4c..9886300aa18 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 846fd851db9..63ceb1f3dfc 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 a16d419438e..31618a32b95 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 2563d40e1ba..c3b50c7a55e 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 66055d7def5..965aae27ad7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -309,11 +309,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 java.util.UUID import kotlin.coroutines.cancellation.CancellationException @@ -414,7 +415,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 @@ -764,7 +765,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 898550b2467..faf4f6b0377 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 = 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 @@ -2078,4 +2078,4 @@ object HlsPlaylistParser { sessionKeyDrmInitData = sessionKeyDrmInitData ) } -} \ No newline at end of file +} 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 23226418b48..1205f49db99 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].toByteArray() 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 1e3a2ffb78e..b9233490a91 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 6d9862d3a3e..8bdbf3788b4 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 +}