From fea413ea176479de3b6a02646b939958166253f2 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 29 May 2026 12:59:25 -0600 Subject: [PATCH 01/10] Testing --- .../cloudstream3/utils/InAppUpdater.kt | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 1973d4d6c2a..2facd06b9a6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -12,7 +12,6 @@ import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.content.edit import androidx.preference.PreferenceManager -import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit @@ -27,6 +26,8 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.GitInfo.currentCommitHash import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import okio.BufferedSink import okio.buffer import okio.sink @@ -42,45 +43,50 @@ object InAppUpdater { private const val PRERELEASE_PACKAGE_NAME = "com.lagradost.cloudstream3.prerelease" private const val LOG_TAG = "InAppUpdater" + @Serializable private data class GithubAsset( - @JsonProperty("name") val name: String, - @JsonProperty("size") val size: Int, // Size in bytes - @JsonProperty("browser_download_url") val browserDownloadUrl: String, - @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive + @SerialName("name") val name: String, + @SerialName("size") val size: Int, // Size in bytes + @SerialName("browser_download_url") val browserDownloadUrl: String, + @SerialName("content_type") val contentType: String, // application/vnd.android.package-archive ) + @Serializable private data class GithubRelease( - @JsonProperty("tag_name") val tagName: String, // Version code - @JsonProperty("body") val body: String, // Description - @JsonProperty("assets") val assets: List, - @JsonProperty("target_commitish") val targetCommitish: String, // Branch - @JsonProperty("prerelease") val prerelease: Boolean, - @JsonProperty("node_id") val nodeId: String, + @SerialName("tag_name") val tagName: String, // Version code + @SerialName("body") val body: String, // Description + @SerialName("assets") val assets: List, + @SerialName("target_commitish") val targetCommitish: String, // Branch + @SerialName("prerelease") val prerelease: Boolean, + @SerialName("node_id") val nodeId: String, ) + @Serializable private data class GithubObject( - @JsonProperty("sha") val sha: String, // SHA-256 hash - @JsonProperty("type") val type: String, - @JsonProperty("url") val url: String, + @SerialName("sha") val sha: String, // SHA-256 hash + @SerialName("type") val type: String, + @SerialName("url") val url: String, ) + @Serializable private data class GithubTag( - @JsonProperty("object") val githubObject: GithubObject, + @SerialName("object") val githubObject: GithubObject, ) + @Serializable private data class Update( - @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, - @JsonProperty("updateURL") val updateURL: String?, - @JsonProperty("updateVersion") val updateVersion: String?, - @JsonProperty("changelog") val changelog: String?, - @JsonProperty("updateNodeId") val updateNodeId: String?, + @SerialName("shouldUpdate") val shouldUpdate: Boolean, + @SerialName("updateURL") val updateURL: String?, + @SerialName("updateVersion") val updateVersion: String?, + @SerialName("changelog") val changelog: String?, + @SerialName("updateNodeId") val updateNodeId: String?, ) private suspend fun Activity.getAppUpdate(installPrerelease: Boolean): Update { return try { when { // No updates on debug version - BuildConfig.DEBUG -> Update(false, null, null, null, null) + BuildConfig.DEBUG -> getPreReleaseUpdate() BuildConfig.FLAVOR == "prerelease" || installPrerelease -> getPreReleaseUpdate() else -> getReleaseUpdate() } @@ -93,7 +99,7 @@ object InAppUpdater { private suspend fun Activity.getReleaseUpdate(): Update { val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( + val response = parseJson>( app.get(url, headers = headers).text ).toList() @@ -103,9 +109,7 @@ object InAppUpdater { !rel.prerelease }.sortedWith(compareBy { release -> release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> - versionRegex.find( - it1 - )?.groupValues?.let { + versionRegex.find(it1)?.groupValues?.let { it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() } } @@ -150,7 +154,7 @@ object InAppUpdater { "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( + val response = parseJson>( app.get(releaseUrl, headers = headers).text ).toList() From fbc9538b4cf9f04f52defc0421b94b4ee3d05553 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 29 May 2026 13:00:40 -0600 Subject: [PATCH 02/10] - --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8f5c6286636..bf499d2a77e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,7 +27,7 @@ jobs: cache-read-only: false - name: Run Gradle - run: ./gradlew assemblePrereleaseDebug lint check + run: ./gradlew assemblePrereleaseDebug - name: Upload Artifact uses: actions/upload-artifact@v7 From 1d1993d163ace50b8727301766be18cd477db00f Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 29 May 2026 13:15:04 -0600 Subject: [PATCH 03/10] Try a fix to prevent type erasure --- .../lagradost/cloudstream3/utils/AppUtils.kt | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt index 96dda5b2599..13317f30346 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.module.kotlin.readValue import com.lagradost.cloudstream3.InternalAPI import com.lagradost.cloudstream3.json @@ -9,6 +10,7 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException +import kotlinx.serialization.serializer import kotlinx.serialization.serializerOrNull import kotlin.reflect.KClass @@ -21,7 +23,8 @@ object AppUtils { } inline fun parseJson(value: String): T { - return parseJson(value, T::class) + val serializer = try { serializer() } catch (_: SerializationException) { null } + return parseJson(value, T::class, serializer, object : TypeReference() {}) } @Deprecated( @@ -63,19 +66,26 @@ object AppUtils { } @InternalAPI - fun parseJson(value: String, kClass: KClass): T { + fun parseJson( + value: String, + kClass: KClass, + serializer: KSerializer? = null, + typeReference: TypeReference? = null, + ): T { // @Serializable generates a serializer at compile time; contextual serializers are // registered manually in serializersModule, we need both to support all cases - val serializer = kClass.serializerOrNull() ?: json.serializersModule.getContextual(kClass) - return if (serializer != null) { + val s = serializer ?: kClass.serializerOrNull() ?: json.serializersModule.getContextual(kClass) + return if (s != null) { try { - json.decodeFromString(serializer, value) + json.decodeFromString(s, value) } catch (e: SerializationException) { logError(e) - mapper.readValue(value, kClass.java) + if (typeReference != null) mapper.readValue(value, typeReference) + else mapper.readValue(value, kClass.java) } } else { - mapper.readValue(value, kClass.java) + if (typeReference != null) mapper.readValue(value, typeReference) + else mapper.readValue(value, kClass.java) } } } From a4be606e80074e010c47ff1170bf19453cf2786b Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 29 May 2026 13:21:23 -0600 Subject: [PATCH 04/10] Try with Jackson again --- .../cloudstream3/utils/InAppUpdater.kt | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 2facd06b9a6..353f2219169 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.content.edit import androidx.preference.PreferenceManager +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit @@ -26,8 +27,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.GitInfo.currentCommitHash import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable import okio.BufferedSink import okio.buffer import okio.sink @@ -43,43 +42,38 @@ object InAppUpdater { private const val PRERELEASE_PACKAGE_NAME = "com.lagradost.cloudstream3.prerelease" private const val LOG_TAG = "InAppUpdater" - @Serializable private data class GithubAsset( - @SerialName("name") val name: String, - @SerialName("size") val size: Int, // Size in bytes - @SerialName("browser_download_url") val browserDownloadUrl: String, - @SerialName("content_type") val contentType: String, // application/vnd.android.package-archive + @JsonProperty("name") val name: String, + @JsonProperty("size") val size: Int, // Size in bytes + @JsonProperty("browser_download_url") val browserDownloadUrl: String, + @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive ) - @Serializable private data class GithubRelease( - @SerialName("tag_name") val tagName: String, // Version code - @SerialName("body") val body: String, // Description - @SerialName("assets") val assets: List, - @SerialName("target_commitish") val targetCommitish: String, // Branch - @SerialName("prerelease") val prerelease: Boolean, - @SerialName("node_id") val nodeId: String, + @JsonProperty("tag_name") val tagName: String, // Version code + @JsonProperty("body") val body: String, // Description + @JsonProperty("assets") val assets: List, + @JsonProperty("target_commitish") val targetCommitish: String, // Branch + @JsonProperty("prerelease") val prerelease: Boolean, + @JsonProperty("node_id") val nodeId: String, ) - @Serializable private data class GithubObject( - @SerialName("sha") val sha: String, // SHA-256 hash - @SerialName("type") val type: String, - @SerialName("url") val url: String, + @JsonProperty("sha") val sha: String, // SHA-256 hash + @JsonProperty("type") val type: String, + @JsonProperty("url") val url: String, ) - @Serializable private data class GithubTag( - @SerialName("object") val githubObject: GithubObject, + @JsonProperty("object") val githubObject: GithubObject, ) - @Serializable private data class Update( - @SerialName("shouldUpdate") val shouldUpdate: Boolean, - @SerialName("updateURL") val updateURL: String?, - @SerialName("updateVersion") val updateVersion: String?, - @SerialName("changelog") val changelog: String?, - @SerialName("updateNodeId") val updateNodeId: String?, + @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, + @JsonProperty("updateURL") val updateURL: String?, + @JsonProperty("updateVersion") val updateVersion: String?, + @JsonProperty("changelog") val changelog: String?, + @JsonProperty("updateNodeId") val updateNodeId: String?, ) private suspend fun Activity.getAppUpdate(installPrerelease: Boolean): Update { From 1a59c536a8161525d6499cacf0ec973d82708d01 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 29 May 2026 13:23:54 -0600 Subject: [PATCH 05/10] Add comment --- .../kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt index 13317f30346..17a17830933 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -23,6 +23,8 @@ object AppUtils { } inline fun parseJson(value: String): T { + // serializer() preserves full generic type info (e.g. List) + // and must be resolved here while T is still reified, same for TypeReference val serializer = try { serializer() } catch (_: SerializationException) { null } return parseJson(value, T::class, serializer, object : TypeReference() {}) } From 950c1d2e395662cd67caa983a1c011c62dfe456f Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 29 May 2026 13:26:12 -0600 Subject: [PATCH 06/10] - --- .../main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 353f2219169..a6f94d55ac6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -80,7 +80,7 @@ object InAppUpdater { return try { when { // No updates on debug version - BuildConfig.DEBUG -> getPreReleaseUpdate() + BuildConfig.DEBUG -> Update(false, null, null, null, null) BuildConfig.FLAVOR == "prerelease" || installPrerelease -> getPreReleaseUpdate() else -> getReleaseUpdate() } From ef54138d470dedb1b18d2fa131555a27fa09dc34 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 29 May 2026 13:26:38 -0600 Subject: [PATCH 07/10] + --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index bf499d2a77e..8f5c6286636 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,7 +27,7 @@ jobs: cache-read-only: false - name: Run Gradle - run: ./gradlew assemblePrereleaseDebug + run: ./gradlew assemblePrereleaseDebug lint check - name: Upload Artifact uses: actions/upload-artifact@v7 From a2afe0b37dee288cc93e0f2501d7aa59be0707b7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 29 May 2026 13:30:35 -0600 Subject: [PATCH 08/10] - --- .../java/com/lagradost/cloudstream3/utils/InAppUpdater.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index a6f94d55ac6..94d227c1f8f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -95,7 +95,7 @@ object InAppUpdater { val headers = mapOf("Accept" to "application/vnd.github.v3+json") val response = parseJson>( app.get(url, headers = headers).text - ).toList() + ) val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") @@ -150,7 +150,7 @@ object InAppUpdater { val headers = mapOf("Accept" to "application/vnd.github.v3+json") val response = parseJson>( app.get(releaseUrl, headers = headers).text - ).toList() + ) val found = response.lastOrNull { rel -> rel.prerelease || rel.tagName == "pre-release" From cf59384725f94b84edcac9c6d940c44fc4c8f299 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 30 May 2026 19:10:28 -0600 Subject: [PATCH 09/10] Keep using Array --- .../java/com/lagradost/cloudstream3/utils/InAppUpdater.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 94d227c1f8f..b01f6e07e27 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -93,9 +93,9 @@ object InAppUpdater { private suspend fun Activity.getReleaseUpdate(): Update { val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( + val response = parseJson>( app.get(url, headers = headers).text - ) + ).toList() val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") @@ -148,9 +148,9 @@ object InAppUpdater { "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( + val response = parseJson>( app.get(releaseUrl, headers = headers).text - ) + ).toList() val found = response.lastOrNull { rel -> rel.prerelease || rel.tagName == "pre-release" From 26dfd407982d6dff2de2e81dd11d6859fb72e37a Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 30 May 2026 19:33:00 -0600 Subject: [PATCH 10/10] Apply suggestions --- .../lagradost/cloudstream3/utils/AppUtils.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt index 17a17830933..5cc91b6d64f 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -25,7 +25,7 @@ object AppUtils { inline fun parseJson(value: String): T { // serializer() preserves full generic type info (e.g. List) // and must be resolved here while T is still reified, same for TypeReference - val serializer = try { serializer() } catch (_: SerializationException) { null } + val serializer = try { serializer() } catch (_: Exception) { null } return parseJson(value, T::class, serializer, object : TypeReference() {}) } @@ -76,18 +76,22 @@ object AppUtils { ): T { // @Serializable generates a serializer at compile time; contextual serializers are // registered manually in serializersModule, we need both to support all cases - val s = serializer ?: kClass.serializerOrNull() ?: json.serializersModule.getContextual(kClass) - return if (s != null) { + val s = + serializer ?: kClass.serializerOrNull() ?: json.serializersModule.getContextual(kClass) + + // Prefer Kotlin Serialization over Jackson + if (s != null) { try { - json.decodeFromString(s, value) + return json.decodeFromString(s, value) } catch (e: SerializationException) { logError(e) - if (typeReference != null) mapper.readValue(value, typeReference) - else mapper.readValue(value, kClass.java) } + } + + return if (typeReference != null) { + mapper.readValue(value, typeReference) } else { - if (typeReference != null) mapper.readValue(value, typeReference) - else mapper.readValue(value, kClass.java) + mapper.readValue(value, kClass.java) } } }