From 9de4e22cba3bc310e9e2a54d8334c63bbb9f1c72 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:07:59 -0600 Subject: [PATCH 1/5] Make some classes serializable with backward compatibility --- .../com/lagradost/cloudstream3/MainAPI.kt | 60 +++++++++++-------- .../cloudstream3/utils/ExtractorApi.kt | 4 +- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 5d4deba2432..1464142489d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -35,9 +35,11 @@ import kotlinx.datetime.format.byUnicodePattern import kotlinx.datetime.format.char import kotlinx.datetime.format.parse import kotlinx.datetime.toInstant +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json 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 @@ -384,18 +386,19 @@ const val PROVIDER_STATUS_SLOW = 2 const val PROVIDER_STATUS_OK = 1 const val PROVIDER_STATUS_DOWN = 0 +@Serializable data class ProvidersInfoJson( - @JsonProperty("name") var name: String, - @JsonProperty("url") var url: String, - @JsonProperty("credentials") var credentials: String? = null, - @JsonProperty("status") var status: Int, + @SerialName("name") var name: String, + @SerialName("url") var url: String, + @SerialName("credentials") var credentials: String? = null, + @SerialName("status") var status: Int, ) +@Serializable data class SettingsJson( - @JsonProperty("enableAdult") var enableAdult: Boolean = false, + @SerialName("enableAdult") var enableAdult: Boolean = false, ) - data class MainPageData( val name: String, val data: String, @@ -863,9 +866,10 @@ enum class DubStatus(val id: Int) { * of this as a decimal class specifically for ratings. * */ @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +@Serializable class Score private constructor( /** Decimal between [0, 10^9] representing the min score and max score respectively */ - @JsonProperty("data") + @SerialName("data") private val data: Int, ) { override fun hashCode(): Int = this.data.hashCode() @@ -1192,9 +1196,10 @@ suspend fun newSubtitleFile( * @see newAudioFile * */ @ConsistentCopyVisibility +@Serializable data class AudioFile internal constructor( - var url: String, - var headers: Map? = null + @SerialName("url") var url: String, + @SerialName("headers") var headers: Map? = null ) /** Creates an AudioFile with optional initializer for setting additional properties. @@ -2171,6 +2176,7 @@ data class NextAiring( * @param name To be shown next to the season like "Season $displaySeason $name" but if displaySeason is null then "$name" * @param displaySeason What to be displayed next to the season name, if null then the name is the only thing shown. * */ +@Serializable data class SeasonData( val season: Int, val name: String? = null, @@ -2732,32 +2738,38 @@ data class Tracker( val cover: String? = null, ) +@Serializable data class AniSearch( - @JsonProperty("data") var data: Data? = Data() + @SerialName("data") var data: Data? = Data() ) { + @Serializable data class Data( - @JsonProperty("Page") var page: Page? = Page() + @SerialName("Page") @JsonProperty("Page") var page: Page? = Page() ) { + @Serializable data class Page( - @JsonProperty("media") var media: ArrayList = arrayListOf() + @SerialName("media") var media: ArrayList = arrayListOf() ) { + @Serializable data class Media( - @JsonProperty("title") var title: Title? = null, - @JsonProperty("id") var id: Int? = null, - @JsonProperty("idMal") var idMal: Int? = null, - @JsonProperty("seasonYear") var seasonYear: Int? = null, - @JsonProperty("format") var format: String? = null, - @JsonProperty("coverImage") var coverImage: CoverImage? = null, - @JsonProperty("bannerImage") var bannerImage: String? = null, + @SerialName("title") var title: Title? = null, + @SerialName("id") var id: Int? = null, + @SerialName("idMal") var idMal: Int? = null, + @SerialName("seasonYear") var seasonYear: Int? = null, + @SerialName("format") var format: String? = null, + @SerialName("coverImage") var coverImage: CoverImage? = null, + @SerialName("bannerImage") var bannerImage: String? = null, ) { + @Serializable data class CoverImage( - @JsonProperty("extraLarge") var extraLarge: String? = null, - @JsonProperty("large") var large: String? = null, + @SerialName("extraLarge") var extraLarge: String? = null, + @SerialName("large") var large: String? = null, ) + @Serializable data class Title( - @JsonProperty("romaji") var romaji: String? = null, - @JsonProperty("english") var english: String? = null, + @SerialName("romaji") var romaji: String? = null, + @SerialName("english") var english: String? = null, ) { fun isMatchingTitles(title: String?): Boolean { if (title == null) return false 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 f42128b1013..44a20f26348 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.utils -import com.fasterxml.jackson.annotation.JsonIgnore import com.lagradost.cloudstream3.AudioFile import com.lagradost.cloudstream3.IDownloadableMinimum import com.lagradost.cloudstream3.Prerelease @@ -315,6 +314,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive +import kotlinx.serialization.Serializable import org.jsoup.Jsoup import java.net.URI import kotlin.coroutines.cancellation.CancellationException @@ -674,6 +674,7 @@ open class DrmExtractorLink private constructor( * @property audioTracks List of separate audio tracks that can be used with this video * @see newExtractorLink * */ +@Serializable open class ExtractorLink @Deprecated("Use newExtractorLink", level = DeprecationLevel.WARNING) constructor( @@ -712,7 +713,6 @@ constructor( return videoSize } - @JsonIgnore fun getAllHeaders(): Map { if (referer.isBlank()) { return headers From f0319f1116ac92f6dc47e36a15839b9c87e783ee Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:03:45 -0600 Subject: [PATCH 2/5] Add SerialName for consistency --- .../kotlin/com/lagradost/cloudstream3/MainAPI.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 1464142489d..72aac400bd4 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -869,8 +869,7 @@ enum class DubStatus(val id: Int) { @Serializable class Score private constructor( /** Decimal between [0, 10^9] representing the min score and max score respectively */ - @SerialName("data") - private val data: Int, + @SerialName("data") private val data: Int, ) { override fun hashCode(): Int = this.data.hashCode() override fun equals(other: Any?): Boolean = other is Score && this.data == other.data @@ -2178,9 +2177,9 @@ data class NextAiring( * */ @Serializable data class SeasonData( - val season: Int, - val name: String? = null, - val displaySeason: Int? = null, // will use season if null + @SerialName("season") val season: Int, + @SerialName("name") val name: String? = null, + @SerialName("displaySeason") val displaySeason: Int? = null, // will use season if null ) /** Abstract interface of EpisodeResponse */ From 7caa7e22f4110968837bbdcf8942d159c589f19c Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:11:19 -0600 Subject: [PATCH 3/5] Add SerialName for consistency --- .../cloudstream3/utils/ExtractorApi.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) 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 44a20f26348..82b8f84e81f 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -314,6 +314,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import org.jsoup.Jsoup import java.net.URI @@ -678,17 +679,17 @@ open class DrmExtractorLink private constructor( open class ExtractorLink @Deprecated("Use newExtractorLink", level = DeprecationLevel.WARNING) constructor( - open val source: String, - open val name: String, - override val url: String, - override var referer: String, - open var quality: Int, - override var headers: Map = mapOf(), + @SerialName("source") open val source: String, + @SerialName("name") open val name: String, + @SerialName("url") override val url: String, + @SerialName("referer") override var referer: String, + @SerialName("quality") open var quality: Int, + @SerialName("headers") override var headers: Map = mapOf(), /** Used for getExtractorVerifierJob() */ - open var extractorData: String? = null, - open var type: ExtractorLinkType, + @SerialName("extractorData") open var extractorData: String? = null, + @SerialName("type") open var type: ExtractorLinkType, /** List of separate audio tracks that can be merged with this video */ - open var audioTracks: List = emptyList(), + @SerialName("audioTracks") open var audioTracks: List = emptyList(), ) : IDownloadableMinimum { val isM3u8: Boolean get() = type == ExtractorLinkType.M3U8 val isDash: Boolean get() = type == ExtractorLinkType.DASH From 491882c3e44bf4c5388c8982b2b6792448540fb1 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:27:15 -0600 Subject: [PATCH 4/5] , --- .../kotlin/com/lagradost/cloudstream3/MainAPI.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 72aac400bd4..fa0889e7ee9 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -1198,7 +1198,7 @@ suspend fun newSubtitleFile( @Serializable data class AudioFile internal constructor( @SerialName("url") var url: String, - @SerialName("headers") var headers: Map? = null + @SerialName("headers") var headers: Map? = null, ) /** Creates an AudioFile with optional initializer for setting additional properties. @@ -2739,15 +2739,15 @@ data class Tracker( @Serializable data class AniSearch( - @SerialName("data") var data: Data? = Data() + @SerialName("data") var data: Data? = Data(), ) { @Serializable data class Data( - @SerialName("Page") @JsonProperty("Page") var page: Page? = Page() + @SerialName("Page") @JsonProperty("Page") var page: Page? = Page(), ) { @Serializable data class Page( - @SerialName("media") var media: ArrayList = arrayListOf() + @SerialName("media") var media: ArrayList = arrayListOf(), ) { @Serializable data class Media( From f2e8829d6006f7d5c050afa5796be0dfe6aaa14c Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:42:53 -0600 Subject: [PATCH 5/5] Add more that will be needed for future migrations --- .../ui/player/PlayerSubtitleHelper.kt | 25 ++++----- .../cloudstream3/ui/result/ResultFragment.kt | 53 ++++++++++--------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt index ee6170aa53f..ba1eeb804ca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt @@ -13,6 +13,8 @@ import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.setSubtitleViewStyle import com.lagradost.cloudstream3.utils.SubtitleHelper.fromLanguageToTagIETF import com.lagradost.cloudstream3.utils.UIHelper.toPx +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable enum class SubtitleStatus { IS_ACTIVE, @@ -32,17 +34,18 @@ enum class SubtitleOrigin { * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend id * @param headers if empty it will use the base onlineDataSource headers else only the specified headers * @param languageCode usually, tags such as "en", "es-mx", or "zh-hant-TW". But it could be something like "English 4" - * */ + */ +@Serializable data class SubtitleData( - val originalName: String, - val nameSuffix: String, - val url: String, - val origin: SubtitleOrigin, - val mimeType: String, - val headers: Map, - val languageCode: String?, + @SerialName("originalName") val originalName: String, + @SerialName("nameSuffix") val nameSuffix: String, + @SerialName("url") val url: String, + @SerialName("origin") val origin: SubtitleOrigin, + @SerialName("mimeType") val mimeType: String, + @SerialName("headers") val headers: Map, + @SerialName("languageCode") val languageCode: String?, ) { - /** Internal ID for exoplayer, unique for each link*/ + /** Internal ID for media3, unique for each link. */ fun getId(): String { return if (origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) url else "$url|$name" @@ -67,9 +70,7 @@ data class SubtitleData( // Some extensions fail to include the protocol, this helps with that. val fixedSubUrl = if (this.url.startsWith("//")) { "https:${this.url}" - } else { - this.url - } + } else this.url return fixedSubUrl } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index cbf94fd9796..71909c5d308 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -21,6 +21,8 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.UiImage +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_LOAD_EP = 2 @@ -34,33 +36,32 @@ enum class VideoWatchState { Watched } +@Serializable data class ResultEpisode( - val headerName: String, - val name: String?, - val poster: String?, - val episode: Int, - val seasonIndex: Int?, // this is the "season" index used season names - val season: Int?, // this is the display - val data: String, - val apiName: String, - val id: Int, - val index: Int, - val position: Long, // time in MS - val duration: Long, // duration in MS - val score: Score?, - val description: String?, - val isFiller: Boolean?, - val tvType: TvType, - val parentId: Int, - /** - * Conveys if the episode itself is marked as watched - **/ - val videoWatchState: VideoWatchState, - /** Sum of all previous season episode counts + episode */ - val totalEpisodeIndex: Int? = null, - val airDate: Long? = null, - val runTime: Int? = null, - val seasonData: SeasonData? = null, + @SerialName("headerName") val headerName: String, + @SerialName("name") val name: String?, + @SerialName("poster") val poster: String?, + @SerialName("episode") val episode: Int, + @SerialName("seasonIndex") val seasonIndex: Int?, // this is the "season" index used season names + @SerialName("season") val season: Int?, // this is the display + @SerialName("data") val data: String, + @SerialName("apiName") val apiName: String, + @SerialName("id") val id: Int, + @SerialName("index") val index: Int, + @SerialName("position") val position: Long, // time in MS + @SerialName("duration") val duration: Long, // duration in MS + @SerialName("score") val score: Score?, + @SerialName("description") val description: String?, + @SerialName("isFiller") val isFiller: Boolean?, + @SerialName("tvType") val tvType: TvType, + @SerialName("parentId") val parentId: Int, + /** Conveys if the episode itself is marked as watched. */ + @SerialName("videoWatchState") val videoWatchState: VideoWatchState, + /** Sum of all previous season episode counts + episode. */ + @SerialName("totalEpisodeIndex") val totalEpisodeIndex: Int? = null, + @SerialName("airDate") val airDate: Long? = null, + @SerialName("runTime") val runTime: Int? = null, + @SerialName("seasonData") val seasonData: SeasonData? = null, ) fun ResultEpisode.getRealPosition(): Long {