diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/SerializerTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/SerializerTest.kt deleted file mode 100644 index 15ad532f8..000000000 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/SerializerTest.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.lagradost.cloudstream3.utils.serializers - -import android.net.Uri -import com.lagradost.cloudstream3.utils.AppUtils.parseJson -import com.lagradost.cloudstream3.utils.AppUtils.toJson -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KeepGeneratedSerializer -import kotlinx.serialization.Serializable -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Test - -@OptIn(ExperimentalSerializationApi::class) -@KeepGeneratedSerializer -@Serializable(with = NonEmptyData.Serializer::class) -data class NonEmptyData( - val title: String = "", - val tags: List = emptyList(), - val meta: Map = emptyMap(), - val name: String = "hello", -) { - object Serializer : NonEmptySerializer(NonEmptyData.generatedSerializer()) -} - -@OptIn(ExperimentalSerializationApi::class) -@KeepGeneratedSerializer -@Serializable(with = WriteOnlyData.Serializer::class) -data class WriteOnlyData( - val fieldA: String = "", - val fieldB: String = "", -) { - object Serializer : WriteOnlySerializer( - WriteOnlyData.generatedSerializer(), - setOf("fieldB"), - ) -} - -@OptIn(ExperimentalSerializationApi::class) -@KeepGeneratedSerializer -@Serializable(with = MultiWriteOnly.Serializer::class) -data class MultiWriteOnly( - val fieldA: String = "", - val fieldB: String = "", - val fieldC: String = "", -) { - object Serializer : WriteOnlySerializer( - MultiWriteOnly.generatedSerializer(), - setOf("fieldB", "fieldC"), - ) -} - -@Serializable -data class UriData( - @Serializable(with = UriSerializer::class) - val uri: Uri = Uri.EMPTY, -) - -class SerializerTest { - - @Test - fun nonEmptySerializerOmitsEmptyStrings() { - val data = NonEmptyData(title = "", name = "hello") - val result = data.toJson() - assertFalse(result.contains("title")) - assertTrue(result.contains("name")) - } - - @Test - fun nonEmptySerializerOmitsEmptyLists() { - val data = NonEmptyData(tags = emptyList(), name = "hello") - val result = data.toJson() - assertFalse(result.contains("tags")) - } - - @Test - fun nonEmptySerializerOmitsEmptyMaps() { - val data = NonEmptyData(meta = emptyMap(), name = "hello") - val result = data.toJson() - assertFalse(result.contains("meta")) - } - - @Test - fun nonEmptySerializerKeepsNonEmptyFields() { - val data = NonEmptyData(title = "hello", tags = listOf("a"), meta = mapOf("k" to "v")) - val result = data.toJson() - assertTrue(result.contains("title")) - assertTrue(result.contains("tags")) - assertTrue(result.contains("meta")) - } - - @Test - fun nonEmptySerializerDoesNotAffectDeserialization() { - val input = """{"title":"hello","tags":["a"],"meta":{"k":"v"},"name":"world"}""" - val result = parseJson(input) - assertEquals("hello", result.title) - assertEquals(listOf("a"), result.tags) - assertEquals(mapOf("k" to "v"), result.meta) - assertEquals("world", result.name) - } - - @Test - fun writeOnlySerializerOmitsFieldOnSerialize() { - val data = WriteOnlyData(fieldA = "hello", fieldB = "secret") - val result = data.toJson() - assertTrue(result.contains("fieldA")) - assertFalse(result.contains("fieldB")) - } - - @Test - fun writeOnlySerializerDeserializesNormally() { - val input = """{"fieldA":"hello","fieldB":"secret"}""" - val result = parseJson(input) - assertEquals("hello", result.fieldA) - assertEquals("secret", result.fieldB) - } - - @Test - fun writeOnlySerializerDeserializesMissingAsDefault() { - val input = """{"fieldA":"hello"}""" - val result = parseJson(input) - assertEquals("hello", result.fieldA) - assertEquals("", result.fieldB) - } - - @Test - fun writeOnlySerializerHandlesMultipleKeys() { - val data = MultiWriteOnly(fieldA = "hello", fieldB = "secret1", fieldC = "secret2") - val result = data.toJson() - assertTrue(result.contains("fieldA")) - assertFalse(result.contains("fieldB")) - assertFalse(result.contains("fieldC")) - } - - @Test - fun uriSerializerSerializesUriToString() { - val data = UriData(uri = Uri.parse("https://example.com/path?query=1")) - val result = data.toJson() - assertTrue(result.contains("https://example.com/path?query=1")) - } - - @Test - fun uriSerializerDeserializesStringToUri() { - val input = """{"uri":"https://example.com/path?query=1"}""" - val result = parseJson(input) - assertEquals(Uri.parse("https://example.com/path?query=1"), result.uri) - } - - @Test - fun uriSerializerRoundtripsCorrectly() { - val data = UriData(uri = Uri.parse("https://example.com/path?query=1")) - val encoded = data.toJson() - val decoded = parseJson(encoded) - assertEquals(data.uri, decoded.uri) - } -} diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/UriSerializerTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/UriSerializerTest.kt new file mode 100644 index 000000000..3ffd37124 --- /dev/null +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/UriSerializerTest.kt @@ -0,0 +1,41 @@ +package com.lagradost.cloudstream3.utils.serializers + +import android.net.Uri +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +@Serializable +data class UriData( + @Serializable(with = UriSerializer::class) + @SerialName("uri") val uri: Uri = Uri.EMPTY, +) + +class UriSerializerTest { + + @Test + fun uriSerializerSerializesUriToString() { + val data = UriData(uri = Uri.parse("https://example.com/path?query=1")) + val result = data.toJson() + assertTrue(result.contains("https://example.com/path?query=1")) + } + + @Test + fun uriSerializerDeserializesStringToUri() { + val input = """{"uri":"https://example.com/path?query=1"}""" + val result = parseJson(input) + assertEquals(Uri.parse("https://example.com/path?query=1"), result.uri) + } + + @Test + fun uriSerializerRoundtripsCorrectly() { + val data = UriData(uri = Uri.parse("https://example.com/path?query=1")) + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data.uri, decoded.uri) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt index 2e554f75e..7e5d04eb0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt @@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.mvvm.logError 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 import torrServer.TorrServer import java.io.File import java.net.ConnectException @@ -32,14 +34,14 @@ object Torrent { /** Returns true if the server is up */ private suspend fun echo(): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return false } return try { app.get( "$TORRENT_SERVER_URL/echo", ).text.isNotEmpty() - } catch (e: ConnectException) { + } catch (_: ConnectException) { // `Failed to connect to /127.0.0.1:8090` if the server is down false } catch (t: Throwable) { @@ -52,7 +54,7 @@ object Torrent { /** Gracefully shutdown the server. * should not be used because I am unable to start it again, and the stopTorrentServer() crashes the app */ suspend fun shutdown(): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return false } return try { @@ -68,7 +70,7 @@ object Torrent { /** Lists all torrents by the server */ @Throws private suspend fun list(): Array { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { throw ErrorLoadingException("Not initialized") } return app.post( @@ -83,7 +85,7 @@ object Torrent { /** Drops a single torrent, (I think) this means closing the stream. Returns returns if it is successful */ private suspend fun drop(hash: String): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return false } return try { @@ -104,7 +106,7 @@ object Torrent { /** Removes a single torrent from the server registry */ private suspend fun rem(hash: String): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return false } return try { @@ -126,7 +128,7 @@ object Torrent { /** Removes all torrents from the server, and returns if it is successful */ suspend fun clearAll(): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return true } return try { @@ -164,10 +166,8 @@ object Torrent { /** Gets all the metadata of a torrent, will throw if that hash does not exists * https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/web/api/torrents.go#L126 */ @Throws - suspend fun get( - hash: String, - ): TorrentStatus { - if(TORRENT_SERVER_URL.isEmpty()) { + suspend fun get(hash: String): TorrentStatus { + if (TORRENT_SERVER_URL.isEmpty()) { throw ErrorLoadingException("Not initialized") } return app.post( @@ -184,7 +184,7 @@ object Torrent { /** Adds a torrent to the server, this is needed for us to get the hash for further modification, as well as start streaming it*/ @Throws private suspend fun add(url: String): TorrentStatus { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { throw ErrorLoadingException("Not initialized") } return app.post( @@ -204,7 +204,7 @@ object Torrent { return true } val port = TorrServer.startTorrentServer(dir, 0) - if(port < 0) { + if (port < 0) { return false } TORRENT_SERVER_URL = "http://127.0.0.1:$port" @@ -278,94 +278,55 @@ object Torrent { // https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/web/api/torrents.go#L18 // https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/main/web/api/route.go#L7 + @Serializable data class TorrentRequest( - @JsonProperty("action") - val action: String, - @JsonProperty("hash") - val hash: String = "", - @JsonProperty("link") - val link: String = "", - @JsonProperty("title") - val title: String = "", - @JsonProperty("poster") - val poster: String = "", - @JsonProperty("data") - val data: String = "", - @JsonProperty("save_to_db") - val saveToDB: Boolean = false, + @JsonProperty("action") @SerialName("action") val action: String, + @JsonProperty("hash") @SerialName("hash") val hash: String = "", + @JsonProperty("link") @SerialName("link") val link: String = "", + @JsonProperty("title") @SerialName("title") val title: String = "", + @JsonProperty("poster") @SerialName("poster") val poster: String = "", + @JsonProperty("data") @SerialName("data") val data: String = "", + @JsonProperty("save_to_db") @SerialName("save_to_db") val saveToDB: Boolean = false, ) // https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/torr/state/state.go#L33 // omitempty = nullable + @Serializable data class TorrentStatus( - @JsonProperty("title") - var title: String, - @JsonProperty("poster") - var poster: String, - @JsonProperty("data") - var data: String?, - @JsonProperty("timestamp") - var timestamp: Long, - @JsonProperty("name") - var name: String?, - @JsonProperty("hash") - var hash: String?, - @JsonProperty("stat") - var stat: Int, - @JsonProperty("stat_string") - var statString: String, - @JsonProperty("loaded_size") - var loadedSize: Long?, - @JsonProperty("torrent_size") - var torrentSize: Long?, - @JsonProperty("preloaded_bytes") - var preloadedBytes: Long?, - @JsonProperty("preload_size") - var preloadSize: Long?, - @JsonProperty("download_speed") - var downloadSpeed: Double?, - @JsonProperty("upload_speed") - var uploadSpeed: Double?, - @JsonProperty("total_peers") - var totalPeers: Int?, - @JsonProperty("pending_peers") - var pendingPeers: Int?, - @JsonProperty("active_peers") - var activePeers: Int?, - @JsonProperty("connected_seeders") - var connectedSeeders: Int?, - @JsonProperty("half_open_peers") - var halfOpenPeers: Int?, - @JsonProperty("bytes_written") - var bytesWritten: Long?, - @JsonProperty("bytes_written_data") - var bytesWrittenData: Long?, - @JsonProperty("bytes_read") - var bytesRead: Long?, - @JsonProperty("bytes_read_data") - var bytesReadData: Long?, - @JsonProperty("bytes_read_useful_data") - var bytesReadUsefulData: Long?, - @JsonProperty("chunks_written") - var chunksWritten: Long?, - @JsonProperty("chunks_read") - var chunksRead: Long?, - @JsonProperty("chunks_read_useful") - var chunksReadUseful: Long?, - @JsonProperty("chunks_read_wasted") - var chunksReadWasted: Long?, - @JsonProperty("pieces_dirtied_good") - var piecesDirtiedGood: Long?, - @JsonProperty("pieces_dirtied_bad") - var piecesDirtiedBad: Long?, - @JsonProperty("duration_seconds") - var durationSeconds: Double?, - @JsonProperty("bit_rate") - var bitRate: String?, - @JsonProperty("file_stats") - var fileStats: List?, - @JsonProperty("trackers") - var trackers: List?, + @JsonProperty("title") @SerialName("title") var title: String, + @JsonProperty("poster") @SerialName("poster") var poster: String, + @JsonProperty("data") @SerialName("data") var data: String?, + @JsonProperty("timestamp") @SerialName("timestamp") var timestamp: Long, + @JsonProperty("name") @SerialName("name") var name: String?, + @JsonProperty("hash") @SerialName("hash") var hash: String?, + @JsonProperty("stat") @SerialName("stat") var stat: Int, + @JsonProperty("stat_string") @SerialName("stat_string") var statString: String, + @JsonProperty("loaded_size") @SerialName("loaded_size") var loadedSize: Long?, + @JsonProperty("torrent_size") @SerialName("torrent_size") var torrentSize: Long?, + @JsonProperty("preloaded_bytes") @SerialName("preloaded_bytes") var preloadedBytes: Long?, + @JsonProperty("preload_size") @SerialName("preload_size") var preloadSize: Long?, + @JsonProperty("download_speed") @SerialName("download_speed") var downloadSpeed: Double?, + @JsonProperty("upload_speed") @SerialName("upload_speed") var uploadSpeed: Double?, + @JsonProperty("total_peers") @SerialName("total_peers") var totalPeers: Int?, + @JsonProperty("pending_peers") @SerialName("pending_peers") var pendingPeers: Int?, + @JsonProperty("active_peers") @SerialName("active_peers") var activePeers: Int?, + @JsonProperty("connected_seeders") @SerialName("connected_seeders") var connectedSeeders: Int?, + @JsonProperty("half_open_peers") @SerialName("half_open_peers") var halfOpenPeers: Int?, + @JsonProperty("bytes_written") @SerialName("bytes_written") var bytesWritten: Long?, + @JsonProperty("bytes_written_data") @SerialName("bytes_written_data") var bytesWrittenData: Long?, + @JsonProperty("bytes_read") @SerialName("bytes_read") var bytesRead: Long?, + @JsonProperty("bytes_read_data") @SerialName("bytes_read_data") var bytesReadData: Long?, + @JsonProperty("bytes_read_useful_data") @SerialName("bytes_read_useful_data") var bytesReadUsefulData: Long?, + @JsonProperty("chunks_written") @SerialName("chunks_written") var chunksWritten: Long?, + @JsonProperty("chunks_read") @SerialName("chunks_read") var chunksRead: Long?, + @JsonProperty("chunks_read_useful") @SerialName("chunks_read_useful") var chunksReadUseful: Long?, + @JsonProperty("chunks_read_wasted") @SerialName("chunks_read_wasted") var chunksReadWasted: Long?, + @JsonProperty("pieces_dirtied_good") @SerialName("pieces_dirtied_good") var piecesDirtiedGood: Long?, + @JsonProperty("pieces_dirtied_bad") @SerialName("pieces_dirtied_bad") var piecesDirtiedBad: Long?, + @JsonProperty("duration_seconds") @SerialName("duration_seconds") var durationSeconds: Double?, + @JsonProperty("bit_rate") @SerialName("bit_rate") var bitRate: String?, + @JsonProperty("file_stats") @SerialName("file_stats") var fileStats: List?, + @JsonProperty("trackers") @SerialName("trackers") var trackers: List?, ) { fun streamUrl(url: String): String { val fileName = @@ -381,12 +342,10 @@ object Torrent { } } + @Serializable data class TorrentFileStat( - @JsonProperty("id") - val id: Int?, - @JsonProperty("path") - val path: String?, - @JsonProperty("length") - val length: Long?, + @JsonProperty("id") @SerialName("id") val id: Int?, + @JsonProperty("path") @SerialName("path") val path: String?, + @JsonProperty("length") @SerialName("length") val length: Long?, ) -} \ No newline at end of file +} diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 025ebc391..cd2aaeea1 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -5831,6 +5831,7 @@ public class com/lagradost/cloudstream3/metaproviders/TraktProvider : com/lagrad } public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5847,7 +5848,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5868,7 +5885,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data$Companion; public fun ()V public fun (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V public synthetic fun (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5883,7 +5916,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$Companion; public fun ()V public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)V public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5906,7 +5955,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images$Companion; public fun ()V public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5933,7 +5998,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$Companion; public fun ()V public fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ZLjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZ)V public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ZLjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5994,10 +6075,26 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkDa public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$Companion; public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component10 ()Ljava/lang/String; public final fun component11 ()Ljava/lang/String; @@ -6016,7 +6113,7 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaD public final fun component23 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; public final fun component24 ()Ljava/lang/String; public final fun component25 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; - public final fun component26 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun component26 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; public final fun component3 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -6024,8 +6121,8 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaD public final fun component7 ()Ljava/lang/Integer; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; public fun equals (Ljava/lang/Object;)Z public final fun getAiredEpisodes ()Ljava/lang/Integer; public final fun getAirs ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; @@ -6040,7 +6137,7 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaD public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; public final fun getLanguage ()Ljava/lang/String; public final fun getLanguages ()Ljava/util/List; - public final fun getMedia ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun getMedia ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; public final fun getNetwork ()Ljava/lang/String; public final fun getOverview ()Ljava/lang/String; public final fun getRating ()Ljava/lang/Double; @@ -6057,7 +6154,60 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaD public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun component4 ()Ljava/lang/Double; + public final fun component5 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; + public fun equals (Ljava/lang/Object;)Z + public final fun getIds ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun getRating ()Ljava/lang/Double; + public final fun getTitle ()Ljava/lang/String; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$People { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People$Companion; public fun ()V public fun (Ljava/util/List;)V public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6070,7 +6220,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$People public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$People$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$People$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person$Companion; public fun ()V public fun (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V public synthetic fun (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6087,7 +6253,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$Companion; public fun ()V public fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6124,7 +6306,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Season public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$Companion; public fun ()V public fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V public synthetic fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6165,6 +6363,21 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktE public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/mvvm/ArchComponentExtKt { public static final field DEBUG_EXCEPTION Ljava/lang/String; public static final field DEBUG_PRINT Ljava/lang/String; diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt index dafec4d97..6011e14ea 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt @@ -34,6 +34,10 @@ import com.lagradost.cloudstream3.newTvSeriesLoadResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonNames open class TraktProvider : MainAPI() { override var name = "Trakt" @@ -46,7 +50,6 @@ open class TraktProvider : MainAPI() { ) private val traktApiUrl = "https://api.trakt.tv" - private val traktClientId: String = BuildConfig.TRAKT_CLIENT_ID override val mainPage = mainPageOf( @@ -58,16 +61,21 @@ open class TraktProvider : MainAPI() { override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { val apiResponse = getApi("${request.data}?extended=full,images&page=$page") - val results = parseJson>(apiResponse).map { element -> element.toSearchResponse() } + return newHomePageResponse(request.name, results) } private fun MediaDetails.toSearchResponse(): SearchResponse { - - val media = this.media ?: this + val media = this.media ?: MediaSummary( + title = this.title, + year = this.year, + ids = this.ids, + rating = this.rating, + images = this.images, + ) val mediaType = if (media.ids?.tvdb == null) TvType.Movie else TvType.TvSeries val poster = media.images?.poster?.firstOrNull() return if (mediaType == TvType.Movie) { @@ -75,7 +83,7 @@ open class TraktProvider : MainAPI() { name = media.title ?: "", url = Data( type = mediaType, - mediaDetails = media, + mediaDetails = this, ).toJson(), type = TvType.Movie, ) { @@ -87,7 +95,7 @@ open class TraktProvider : MainAPI() { name = media.title ?: "", url = Data( type = mediaType, - mediaDetails = media, + mediaDetails = this, ).toJson(), type = TvType.TvSeries, ) { @@ -98,9 +106,7 @@ open class TraktProvider : MainAPI() { } override suspend fun search(query: String, page: Int): SearchResponseList? { - val apiResponse = - getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=$page&query=$query") - + val apiResponse = getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=$page&query=$query") return newSearchResponseList(parseJson>(apiResponse).map { element -> element.toSearchResponse() }) @@ -115,9 +121,7 @@ open class TraktProvider : MainAPI() { val backDropUrl = fixPath(mediaDetails?.images?.fanart?.firstOrNull()) val logoUrl = fixPath(mediaDetails?.images?.logo?.firstOrNull()) - val resActor = - getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=full,images") - + val resActor = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=full,images") val actors = parseJson(resActor).cast?.map { ActorData( Actor( @@ -128,9 +132,7 @@ open class TraktProvider : MainAPI() { ) } - val resRelated = - getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=full,images&limit=20") - + val resRelated = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=full,images&limit=20") val relatedMedia = parseJson>(resRelated).map { it.toSearchResponse() } val isCartoon = @@ -142,7 +144,6 @@ open class TraktProvider : MainAPI() { val uniqueUrl = data.mediaDetails?.ids?.trakt?.toJson() ?: data.toJson() if (data.type == TvType.Movie) { - val linkData = LinkData( id = mediaDetails?.ids?.tmdb, traktId = mediaDetails?.ids?.trakt, @@ -156,7 +157,7 @@ open class TraktProvider : MainAPI() { year = mediaDetails?.year, orgTitle = mediaDetails?.title, isAnime = isAnime, - //jpTitle = later if needed as it requires another network request, + // jpTitle = later if needed as it requires another network request, airedDate = mediaDetails?.released ?: mediaDetails?.firstAired, isAsian = isAsian, @@ -190,7 +191,6 @@ open class TraktProvider : MainAPI() { addTMDbId(mediaDetails.ids?.tmdb.toString()) } } else { - val resSeasons = getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=full,images,episodes") val episodes = mutableListOf() @@ -198,9 +198,7 @@ open class TraktProvider : MainAPI() { var nextAir: NextAiring? = null seasons.forEach { season -> - season.episodes?.map { episode -> - val linkData = LinkData( id = mediaDetails?.ids?.tmdb, traktId = mediaDetails?.ids?.trakt, @@ -234,8 +232,7 @@ open class TraktProvider : MainAPI() { this.episode = episode.number this.description = episode.overview this.runTime = episode.runtime - this.posterUrl = fixPath( episode.images?.screenshot?.firstOrNull()) - //this.rating = episode.rating?.times(10)?.roundToInt() + this.posterUrl = fixPath(episode.images?.screenshot?.firstOrNull()) this.score = Score.from10(episode.rating) this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") @@ -307,143 +304,164 @@ open class TraktProvider : MainAPI() { return "https://$url" } + @Serializable data class Data( - val type: TvType? = null, - val mediaDetails: MediaDetails? = null, + @JsonProperty("type") @SerialName("type") val type: TvType? = null, + @JsonProperty("mediaDetails") @SerialName("mediaDetails") val mediaDetails: MediaDetails? = null, ) + @Serializable + data class MediaSummary( + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, + ) + + @OptIn(ExperimentalSerializationApi::class) // JsonNames is an experimental annotation for now + @Serializable data class MediaDetails( - @JsonProperty("title") val title: String? = null, - @JsonProperty("year") val year: Int? = null, - @JsonProperty("ids") val ids: Ids? = null, - @JsonProperty("tagline") val tagline: String? = null, - @JsonProperty("overview") val overview: String? = null, - @JsonProperty("released") val released: String? = null, - @JsonProperty("runtime") val runtime: Int? = null, - @JsonProperty("country") val country: String? = null, - @JsonProperty("updatedAt") val updatedAt: String? = null, - @JsonProperty("trailer") val trailer: String? = null, - @JsonProperty("homepage") val homepage: String? = null, - @JsonProperty("status") val status: String? = null, - @JsonProperty("rating") val rating: Double? = null, - @JsonProperty("votes") val votes: Long? = null, - @JsonProperty("comment_count") val commentCount: Long? = null, - @JsonProperty("language") val language: String? = null, - @JsonProperty("languages") val languages: List? = null, - @JsonProperty("available_translations") val availableTranslations: List? = null, - @JsonProperty("genres") val genres: List? = null, - @JsonProperty("certification") val certification: String? = null, - @JsonProperty("aired_episodes") val airedEpisodes: Int? = null, - @JsonProperty("first_aired") val firstAired: String? = null, - @JsonProperty("airs") val airs: Airs? = null, - @JsonProperty("network") val network: String? = null, - @JsonProperty("images") val images: Images? = null, - @JsonProperty("movie") @JsonAlias("show") val media: MediaDetails? = null + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("tagline") @SerialName("tagline") val tagline: String? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("released") @SerialName("released") val released: String? = null, + @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null, + @JsonProperty("country") @SerialName("country") val country: String? = null, + @JsonProperty("updatedAt") @SerialName("updatedAt") val updatedAt: String? = null, + @JsonProperty("trailer") @SerialName("trailer") val trailer: String? = null, + @JsonProperty("homepage") @SerialName("homepage") val homepage: String? = null, + @JsonProperty("status") @SerialName("status") val status: String? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("votes") @SerialName("votes") val votes: Long? = null, + @JsonProperty("comment_count") @SerialName("comment_count") val commentCount: Long? = null, + @JsonProperty("language") @SerialName("language") val language: String? = null, + @JsonProperty("languages") @SerialName("languages") val languages: List? = null, + @JsonProperty("available_translations") @SerialName("available_translations") val availableTranslations: List? = null, + @JsonProperty("genres") @SerialName("genres") val genres: List? = null, + @JsonProperty("certification") @SerialName("certification") val certification: String? = null, + @JsonProperty("aired_episodes") @SerialName("aired_episodes") val airedEpisodes: Int? = null, + @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null, + @JsonProperty("airs") @SerialName("airs") val airs: Airs? = null, + @JsonProperty("network") @SerialName("network") val network: String? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, + @JsonProperty("media") @JsonAlias("movie", "show") @SerialName("media") @JsonNames("movie", "show") val media: MediaSummary? = null, ) + @Serializable data class Airs( - @JsonProperty("day") val day: String? = null, - @JsonProperty("time") val time: String? = null, - @JsonProperty("timezone") val timezone: String? = null, + @JsonProperty("day") @SerialName("day") val day: String? = null, + @JsonProperty("time") @SerialName("time") val time: String? = null, + @JsonProperty("timezone") @SerialName("timezone") val timezone: String? = null, ) + @Serializable data class Ids( - @JsonProperty("trakt") val trakt: Int? = null, - @JsonProperty("slug") val slug: String? = null, - @JsonProperty("tvdb") val tvdb: Int? = null, - @JsonProperty("imdb") val imdb: String? = null, - @JsonProperty("tmdb") val tmdb: Int? = null, - @JsonProperty("tvrage") val tvrage: String? = null, + @JsonProperty("trakt") @SerialName("trakt") val trakt: Int? = null, + @JsonProperty("slug") @SerialName("slug") val slug: String? = null, + @JsonProperty("tvdb") @SerialName("tvdb") val tvdb: Int? = null, + @JsonProperty("imdb") @SerialName("imdb") val imdb: String? = null, + @JsonProperty("tmdb") @SerialName("tmdb") val tmdb: Int? = null, + @JsonProperty("tvrage") @SerialName("tvrage") val tvrage: String? = null, ) + @Serializable data class Images( - @JsonProperty("poster") val poster: List? = null, - @JsonProperty("fanart") val fanart: List? = null, - @JsonProperty("logo") val logo: List? = null, - @JsonProperty("clearart") val clearArt: List? = null, - @JsonProperty("banner") val banner: List? = null, - @JsonProperty("thumb") val thumb: List? = null, - @JsonProperty("screenshot") val screenshot: List? = null, - @JsonProperty("headshot") val headshot: List? = null, + @JsonProperty("poster") @SerialName("poster") val poster: List? = null, + @JsonProperty("fanart") @SerialName("fanart") val fanart: List? = null, + @JsonProperty("logo") @SerialName("logo") val logo: List? = null, + @JsonProperty("clearart") @SerialName("clearart") val clearArt: List? = null, + @JsonProperty("banner") @SerialName("banner") val banner: List? = null, + @JsonProperty("thumb") @SerialName("thumb") val thumb: List? = null, + @JsonProperty("screenshot") @SerialName("screenshot") val screenshot: List? = null, + @JsonProperty("headshot") @SerialName("headshot") val headshot: List? = null, ) + @Serializable data class People( - @JsonProperty("cast") val cast: List? = null, + @JsonProperty("cast") @SerialName("cast") val cast: List? = null, ) + @Serializable data class Cast( - @JsonProperty("character") val character: String? = null, - @JsonProperty("characters") val characters: List? = null, - @JsonProperty("episode_count") val episodeCount: Long? = null, - @JsonProperty("person") val person: Person? = null, - @JsonProperty("images") val images: Images? = null, + @JsonProperty("character") @SerialName("character") val character: String? = null, + @JsonProperty("characters") @SerialName("characters") val characters: List? = null, + @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Long? = null, + @JsonProperty("person") @SerialName("person") val person: Person? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, ) + @Serializable data class Person( - @JsonProperty("name") val name: String? = null, - @JsonProperty("ids") val ids: Ids? = null, - @JsonProperty("images") val images: Images? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, ) + @Serializable data class Seasons( - @JsonProperty("aired_episodes") val airedEpisodes: Int? = null, - @JsonProperty("episode_count") val episodeCount: Int? = null, - @JsonProperty("episodes") val episodes: List? = null, - @JsonProperty("first_aired") val firstAired: String? = null, - @JsonProperty("ids") val ids: Ids? = null, - @JsonProperty("images") val images: Images? = null, - @JsonProperty("network") val network: String? = null, - @JsonProperty("number") val number: Int? = null, - @JsonProperty("overview") val overview: String? = null, - @JsonProperty("rating") val rating: Double? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("updated_at") val updatedAt: String? = null, - @JsonProperty("votes") val votes: Int? = null, + @JsonProperty("aired_episodes") @SerialName("aired_episodes") val airedEpisodes: Int? = null, + @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Int? = null, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List? = null, + @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, + @JsonProperty("network") @SerialName("network") val network: String? = null, + @JsonProperty("number") @SerialName("number") val number: Int? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("updated_at") @SerialName("updated_at") val updatedAt: String? = null, + @JsonProperty("votes") @SerialName("votes") val votes: Int? = null, ) + @Serializable data class TraktEpisode( - @JsonProperty("available_translations") val availableTranslations: List? = null, - @JsonProperty("comment_count") val commentCount: Int? = null, - @JsonProperty("episode_type") val episodeType: String? = null, - @JsonProperty("first_aired") val firstAired: String? = null, - @JsonProperty("ids") val ids: Ids? = null, - @JsonProperty("images") val images: Images? = null, - @JsonProperty("number") val number: Int? = null, - @JsonProperty("number_abs") val numberAbs: Int? = null, - @JsonProperty("overview") val overview: String? = null, - @JsonProperty("rating") val rating: Double? = null, - @JsonProperty("runtime") val runtime: Int? = null, - @JsonProperty("season") val season: Int? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("updated_at") val updatedAt: String? = null, - @JsonProperty("votes") val votes: Int? = null, + @JsonProperty("available_translations") @SerialName("available_translations") val availableTranslations: List? = null, + @JsonProperty("comment_count") @SerialName("comment_count") val commentCount: Int? = null, + @JsonProperty("episode_type") @SerialName("episode_type") val episodeType: String? = null, + @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, + @JsonProperty("number") @SerialName("number") val number: Int? = null, + @JsonProperty("number_abs") @SerialName("number_abs") val numberAbs: Int? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null, + @JsonProperty("season") @SerialName("season") val season: Int? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("updated_at") @SerialName("updated_at") val updatedAt: String? = null, + @JsonProperty("votes") @SerialName("votes") val votes: Int? = null, ) + @Serializable data class LinkData( - @JsonProperty("id") val id: Int? = null, - @JsonProperty("trakt_id") val traktId: Int? = null, - @JsonProperty("trakt_slug") val traktSlug: String? = null, - @JsonProperty("tmdb_id") val tmdbId: Int? = null, - @JsonProperty("imdb_id") val imdbId: String? = null, - @JsonProperty("tvdb_id") val tvdbId: Int? = null, - @JsonProperty("tvrage_id") val tvrageId: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("season") val season: Int? = null, - @JsonProperty("episode") val episode: Int? = null, - @JsonProperty("ani_id") val aniId: String? = null, - @JsonProperty("anime_id") val animeId: String? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("year") val year: Int? = null, - @JsonProperty("org_title") val orgTitle: String? = null, - @JsonProperty("is_anime") val isAnime: Boolean = false, - @JsonProperty("aired_year") val airedYear: Int? = null, - @JsonProperty("last_season") val lastSeason: Int? = null, - @JsonProperty("eps_title") val epsTitle: String? = null, - @JsonProperty("jp_title") val jpTitle: String? = null, - @JsonProperty("date") val date: String? = null, - @JsonProperty("aired_date") val airedDate: String? = null, - @JsonProperty("is_asian") val isAsian: Boolean = false, - @JsonProperty("is_bollywood") val isBollywood: Boolean = false, - @JsonProperty("is_cartoon") val isCartoon: Boolean = false, + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("trakt_id") @SerialName("trakt_id") val traktId: Int? = null, + @JsonProperty("trakt_slug") @SerialName("trakt_slug") val traktSlug: String? = null, + @JsonProperty("tmdb_id") @SerialName("tmdb_id") val tmdbId: Int? = null, + @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null, + @JsonProperty("tvdb_id") @SerialName("tvdb_id") val tvdbId: Int? = null, + @JsonProperty("tvrage_id") @SerialName("tvrage_id") val tvrageId: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("season") @SerialName("season") val season: Int? = null, + @JsonProperty("episode") @SerialName("episode") val episode: Int? = null, + @JsonProperty("ani_id") @SerialName("ani_id") val aniId: String? = null, + @JsonProperty("anime_id") @SerialName("anime_id") val animeId: String? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("org_title") @SerialName("org_title") val orgTitle: String? = null, + @JsonProperty("is_anime") @SerialName("is_anime") val isAnime: Boolean = false, + @JsonProperty("aired_year") @SerialName("aired_year") val airedYear: Int? = null, + @JsonProperty("last_season") @SerialName("last_season") val lastSeason: Int? = null, + @JsonProperty("eps_title") @SerialName("eps_title") val epsTitle: String? = null, + @JsonProperty("jp_title") @SerialName("jp_title") val jpTitle: String? = null, + @JsonProperty("date") @SerialName("date") val date: String? = null, + @JsonProperty("aired_date") @SerialName("aired_date") val airedDate: String? = null, + @JsonProperty("is_asian") @SerialName("is_asian") val isAsian: Boolean = false, + @JsonProperty("is_bollywood") @SerialName("is_bollywood") val isBollywood: Boolean = false, + @JsonProperty("is_cartoon") @SerialName("is_cartoon") val isCartoon: Boolean = false, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsIntSerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsIntSerializer.kt new file mode 100644 index 000000000..12c8f6ed0 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsIntSerializer.kt @@ -0,0 +1,42 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.Prerelease +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.intOrNull + +/** + * Replicates Jackson's ACCEPT_FLOAT_AS_INT behaviour for Int fields. + * A floating-point JSON number is truncated to Int by dropping the + * fractional part, exactly like Jackson's default truncation behaviour. + * + * Usage: + * + * @Serializable + * data class MyData( + * @Serializable(with = FloatAsIntSerializer::class) + * @SerialName("count") val count: Int = 0, + * ) + */ +@Prerelease +object FloatAsIntSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("FloatAsInt", PrimitiveKind.INT) + + override fun deserialize(decoder: Decoder): Int { + val jsonDecoder = decoder as? JsonDecoder ?: return decoder.decodeInt() + val element = jsonDecoder.decodeJsonElement() + if (element !is JsonPrimitive) return 0 + return element.intOrNull ?: element.doubleOrNull?.toInt() ?: 0 + } + + override fun serialize(encoder: Encoder, value: Int) = encoder.encodeInt(value) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsLongSerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsLongSerializer.kt new file mode 100644 index 000000000..7fc394107 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsLongSerializer.kt @@ -0,0 +1,42 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.Prerelease +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.longOrNull + +/** + * Replicates Jackson's ACCEPT_FLOAT_AS_INT behaviour for Long fields. + * A floating-point JSON number is truncated to Long by dropping the + * fractional part, exactly like Jackson's default truncation behaviour. + * + * Usage: + * + * @Serializable + * data class MyData( + * @Serializable(with = FloatAsLongSerializer::class) + * @SerialName("timestamp") val timestamp: Long = 0L, + * ) + */ +@Prerelease +object FloatAsLongSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("FloatAsLong", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): Long { + val jsonDecoder = decoder as? JsonDecoder ?: return decoder.decodeLong() + val element = jsonDecoder.decodeJsonElement() + if (element !is JsonPrimitive) return 0L + return element.longOrNull ?: element.doubleOrNull?.toLong() ?: 0L + } + + override fun serialize(encoder: Encoder, value: Long) = encoder.encodeLong(value) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/JsonTransformSerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/JsonTransformSerializer.kt new file mode 100644 index 000000000..6b9c25b59 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/JsonTransformSerializer.kt @@ -0,0 +1,75 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.Prerelease +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.JsonTransformingSerializer + +/** + * Composable deserialize transformer that applies multiple Jackson-equivalent + * behaviours to specific fields by key. Pass only the sets you need; all + * default to empty (no-op). + * + * Behaviours (applied in order per field): + * singleValueAsListKeys ACCEPT_SINGLE_VALUE_AS_ARRAY + * emptyStringAsNullKeys ACCEPT_EMPTY_STRING_AS_NULL_OBJECT + * emptyArrayAsNullKeys ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT + * + * Usage: + * + * @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + * @KeepGeneratedSerializer + * @Serializable(with = MyData.Serializer::class) + * data class MyData( + * @SerialName("title") val title: String? = null, + * @SerialName("tags") val tags: List? = null, + * @SerialName("meta") val meta: MyMeta? = null, + * ) { + * object Serializer : JsonTransformSerializer( + * MyData.generatedSerializer(), + * singleValueAsListKeys = setOf("tags"), + * emptyStringAsNullKeys = setOf("title", "tags"), + * emptyArrayAsNullKeys = setOf("tags", "meta"), + * ) + * } + */ +@Prerelease +abstract class JsonTransformSerializer( + tSerializer: KSerializer, + private val singleValueAsListKeys: Set = emptySet(), + private val emptyArrayAsNullKeys: Set = emptySet(), + private val emptyStringAsNullKeys: Set = emptySet(), +) : JsonTransformingSerializer(tSerializer) { + + override fun transformDeserialize(element: JsonElement): JsonElement { + if (element !is JsonObject) return element + return JsonObject(element.mapValues { (key, value) -> + var result = value + if (key in singleValueAsListKeys) { + result = when (result) { + is JsonArray -> result + JsonNull -> JsonArray(emptyList()) + else -> JsonArray(listOf(result)) + } + } + + if (key in emptyStringAsNullKeys) { + if (result is JsonPrimitive && result.isString && result.content.isEmpty()) { + result = JsonNull + } + } + + if (key in emptyArrayAsNullKeys) { + if (result is JsonArray && result.isEmpty()) { + result = JsonNull + } + } + + result + }) + } +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt index 62b306e1f..8bc8f1734 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt @@ -17,13 +17,13 @@ import kotlinx.serialization.json.JsonTransformingSerializer * * Usage: * - * @OptIn(ExperimentalSerializationApi::class) + * @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now * @KeepGeneratedSerializer * @Serializable(with = MyData.Serializer::class) * data class MyData( - * val tags: List = emptyList(), - * val title: String = "", - * val meta: Map = emptyMap(), + * @SerialName("tags") val tags: List = emptyList(), + * @SerialName("title") val title: String = "", + * @SerialName("meta") val meta: Map = emptyMap(), * ) { * object Serializer : NonEmptySerializer(MyData.generatedSerializer()) * } @@ -33,7 +33,6 @@ abstract class NonEmptySerializer(tSerializer: KSerializer) : override fun transformSerialize(element: JsonElement): JsonElement { if (element !is JsonObject) return element - return JsonObject(element.filterValues { value -> when (value) { is JsonPrimitive -> value.content.isNotEmpty() diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NullableStringSerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NullableStringSerializer.kt new file mode 100644 index 000000000..6755304a5 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NullableStringSerializer.kt @@ -0,0 +1,49 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.Prerelease +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.decodeFromJsonElement + +/** + * Convenience serializer for nullable String fields that combines + * ACCEPT_EMPTY_STRING_AS_NULL_OBJECT with null passthrough. + * An empty JSON string (`""`) or JSON null is decoded as null. + * + * Usage: + * + * @Serializable + * data class MyData( + * @Serializable(with = NullableStringSerializer::class) + * @SerialName("title") val title: String? = null, + * ) + */ +@Prerelease +object NullableStringSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("NullableString", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): String? { + val jsonDecoder = decoder as? JsonDecoder ?: return decoder.decodeString() + return when (val element = jsonDecoder.decodeJsonElement()) { + is JsonPrimitive -> element.content.ifEmpty { null } + JsonNull -> null + else -> jsonDecoder.json.decodeFromJsonElement(String.serializer(), element) + } + } + + override fun serialize(encoder: Encoder, value: String?) { + @OptIn(ExperimentalSerializationApi::class) // encodeNull is experimental for now + if (value == null) encoder.encodeNull() else encoder.encodeString(value) + } +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt index 1d06b7fca..1b6ca9aee 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt @@ -12,12 +12,12 @@ import kotlinx.serialization.json.JsonTransformingSerializer * * Usage: * - * @OptIn(ExperimentalSerializationApi::class) + * @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now * @KeepGeneratedSerializer * @Serializable(with = MyData.Serializer::class) * data class MyData( - * val fieldA: String = "", - * val fieldB: String = "", + * @SerialName("fieldA") val fieldA: String = "", + * @SerialName("fieldB") val fieldB: String = "", * ) { * object Serializer : WriteOnlySerializer( * MyData.generatedSerializer(), diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/serializers/SerializersTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/serializers/SerializersTest.kt new file mode 100644 index 000000000..89c0fea81 --- /dev/null +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/serializers/SerializersTest.kt @@ -0,0 +1,602 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KeepGeneratedSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = NonEmptyData.Serializer::class) +data class NonEmptyData( + @SerialName("title") val title: String = "", + @SerialName("tags") val tags: List = emptyList(), + @SerialName("meta") val meta: Map = emptyMap(), + @SerialName("name") val name: String = "hello", +) { + object Serializer : NonEmptySerializer(NonEmptyData.generatedSerializer()) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = WriteOnlyData.Serializer::class) +data class WriteOnlyData( + @SerialName("fieldA") val fieldA: String = "", + @SerialName("fieldB") val fieldB: String = "", +) { + object Serializer : WriteOnlySerializer( + WriteOnlyData.generatedSerializer(), + setOf("fieldB"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = MultiWriteOnly.Serializer::class) +data class MultiWriteOnly( + @SerialName("fieldA") val fieldA: String = "", + @SerialName("fieldB") val fieldB: String = "", + @SerialName("fieldC") val fieldC: String = "", +) { + object Serializer : WriteOnlySerializer( + MultiWriteOnly.generatedSerializer(), + setOf("fieldB", "fieldC"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = SingleValueData.Serializer::class) +data class SingleValueData( + @SerialName("tags") val tags: List = emptyList(), + @SerialName("nums") val nums: List = emptyList(), +) { + object Serializer : JsonTransformSerializer( + SingleValueData.generatedSerializer(), + singleValueAsListKeys = setOf("tags", "nums"), + ) +} + +@Serializable +data class NestedMeta( + @SerialName("key") val key: String = "", +) + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = EmptyArrayData.Serializer::class) +data class EmptyArrayData( + @SerialName("meta") val meta: NestedMeta? = null, + @SerialName("other") val other: String = "hello", +) { + object Serializer : JsonTransformSerializer( + EmptyArrayData.generatedSerializer(), + emptyArrayAsNullKeys = setOf("meta"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = EmptyStringData.Serializer::class) +data class EmptyStringData( + @SerialName("title") val title: String? = null, + @SerialName("episode") val episode: NestedMeta? = null, + @SerialName("keep") val keep: String? = "hello", +) { + object Serializer : JsonTransformSerializer( + EmptyStringData.generatedSerializer(), + emptyStringAsNullKeys = setOf("title", "episode", "keep"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = SingleValueOrNullData.Serializer::class) +data class SingleValueOrNullData( + @SerialName("tags") val tags: List? = null, + @SerialName("nums") val nums: List? = null, + @SerialName("other") val other: String = "hello", +) { + object Serializer : JsonTransformSerializer( + SingleValueOrNullData.generatedSerializer(), + singleValueAsListKeys = setOf("tags", "nums"), + emptyArrayAsNullKeys = setOf("tags", "nums"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = NullableFieldsData.Serializer::class) +data class NullableFieldsData( + @SerialName("title") val title: String? = null, + @SerialName("meta") val meta: NestedMeta? = null, + @SerialName("other") val other: String = "hello", +) { + object Serializer : JsonTransformSerializer( + NullableFieldsData.generatedSerializer(), + emptyStringAsNullKeys = setOf("title"), + emptyArrayAsNullKeys = setOf("meta"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = SingleValueOrEmptyStringData.Serializer::class) +data class SingleValueOrEmptyStringData( + @SerialName("tags") val tags: List = emptyList(), + @SerialName("title") val title: String? = null, +) { + object Serializer : JsonTransformSerializer( + SingleValueOrEmptyStringData.generatedSerializer(), + singleValueAsListKeys = setOf("tags"), + emptyStringAsNullKeys = setOf("title"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = AllTransformsData.Serializer::class) +data class AllTransformsData( + @SerialName("tags") val tags: List? = null, + @SerialName("title") val title: String? = null, + @SerialName("meta") val meta: NestedMeta? = null, + @SerialName("other") val other: String = "hello", +) { + object Serializer : JsonTransformSerializer( + AllTransformsData.generatedSerializer(), + singleValueAsListKeys = setOf("tags"), + emptyArrayAsNullKeys = setOf("tags", "meta"), + emptyStringAsNullKeys = setOf("title", "tags"), + ) +} + +@Serializable +data class FloatIntData( + @Serializable(with = FloatAsIntSerializer::class) + @SerialName("count") val count: Int = 0, +) + +@Serializable +data class FloatLongData( + @Serializable(with = FloatAsLongSerializer::class) + @SerialName("timestamp") val timestamp: Long = 0L, +) + +@Serializable +data class NullableStringData( + @Serializable(with = NullableStringSerializer::class) + @SerialName("title") val title: String? = null, +) + +class SerializersTest { + + @Test + fun nonEmptySerializerOmitsEmptyStrings() { + val data = NonEmptyData(title = "", name = "hello") + val result = data.toJson() + assertFalse(result.contains("title")) + assertTrue(result.contains("name")) + } + + @Test + fun nonEmptySerializerOmitsEmptyLists() { + val data = NonEmptyData(tags = emptyList(), name = "hello") + val result = data.toJson() + assertFalse(result.contains("tags")) + } + + @Test + fun nonEmptySerializerOmitsEmptyMaps() { + val data = NonEmptyData(meta = emptyMap(), name = "hello") + val result = data.toJson() + assertFalse(result.contains("meta")) + } + + @Test + fun nonEmptySerializerKeepsNonEmptyFields() { + val data = NonEmptyData(title = "hello", tags = listOf("a"), meta = mapOf("k" to "v")) + val result = data.toJson() + assertTrue(result.contains("title")) + assertTrue(result.contains("tags")) + assertTrue(result.contains("meta")) + } + + @Test + fun nonEmptySerializerDoesNotAffectDeserialization() { + val input = """{"title":"hello","tags":["a"],"meta":{"k":"v"},"name":"world"}""" + val result = parseJson(input) + assertEquals("hello", result.title) + assertEquals(listOf("a"), result.tags) + assertEquals(mapOf("k" to "v"), result.meta) + assertEquals("world", result.name) + } + + @Test + fun writeOnlySerializerOmitsFieldOnSerialize() { + val data = WriteOnlyData(fieldA = "hello", fieldB = "secret") + val result = data.toJson() + assertTrue(result.contains("fieldA")) + assertFalse(result.contains("fieldB")) + } + + @Test + fun writeOnlySerializerDeserializesNormally() { + val input = """{"fieldA":"hello","fieldB":"secret"}""" + val result = parseJson(input) + assertEquals("hello", result.fieldA) + assertEquals("secret", result.fieldB) + } + + @Test + fun writeOnlySerializerDeserializesMissingAsDefault() { + val input = """{"fieldA":"hello"}""" + val result = parseJson(input) + assertEquals("hello", result.fieldA) + assertEquals("", result.fieldB) + } + + @Test + fun writeOnlySerializerHandlesMultipleKeys() { + val data = MultiWriteOnly(fieldA = "hello", fieldB = "secret1", fieldC = "secret2") + val result = data.toJson() + assertTrue(result.contains("fieldA")) + assertFalse(result.contains("fieldB")) + assertFalse(result.contains("fieldC")) + } + + @Test + fun singleValueAsListDecodesArrayNormally() { + val input = """{"tags":["a","b"],"nums":[1,2]}""" + val result = parseJson(input) + assertEquals(listOf("a", "b"), result.tags) + assertEquals(listOf(1, 2), result.nums) + } + + @Test + fun singleValueAsListWrapsBareStringInList() { + val input = """{"tags":"a","nums":[1]}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + } + + @Test + fun singleValueAsListWrapsBareIntInList() { + val input = """{"tags":[],"nums":42}""" + val result = parseJson(input) + assertEquals(listOf(42), result.nums) + } + + @Test + fun singleValueAsListDecodesNullAsEmptyList() { + val input = """{"tags":null,"nums":[]}""" + val result = parseJson(input) + assertEquals(emptyList(), result.tags) + } + + @Test + fun singleValueAsListRoundtripsCorrectly() { + val data = SingleValueData(tags = listOf("x", "y"), nums = listOf(1, 2)) + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun emptyArrayAsNullDecodesEmptyArrayAsNull() { + val input = """{"meta":[],"other":"hello"}""" + val result = parseJson(input) + assertNull(result.meta) + } + + @Test + fun emptyArrayAsNullDecodesNullAsNull() { + val input = """{"meta":null,"other":"hello"}""" + val result = parseJson(input) + assertNull(result.meta) + } + + @Test + fun emptyArrayAsNullDecodesObjectNormally() { + val input = """{"meta":{"key":"value"},"other":"hello"}""" + val result = parseJson(input) + assertEquals(NestedMeta("value"), result.meta) + } + + @Test + fun emptyArrayAsNullDoesNotAffectOtherFields() { + val input = """{"meta":[],"other":"world"}""" + val result = parseJson(input) + assertEquals("world", result.other) + } + + @Test + fun emptyArrayAsNullRoundtripsCorrectly() { + val data = EmptyArrayData(meta = NestedMeta("test"), other = "hello") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun emptyStringAsNullDecodesEmptyStringAsNull() { + val input = """{"title":"","episode":null,"keep":"hello"}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun emptyStringAsNullDecodesNullAsNull() { + val input = """{"title":null,"episode":null,"keep":"hello"}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun emptyStringAsNullKeepsNonEmptyString() { + val input = """{"title":"hello","episode":null,"keep":"world"}""" + val result = parseJson(input) + assertEquals("hello", result.title) + } + + @Test + fun emptyStringAsNullDecodesObjectNormally() { + val input = """{"title":null,"episode":{"key":"value"},"keep":"hello"}""" + val result = parseJson(input) + assertEquals(NestedMeta("value"), result.episode) + } + + @Test + fun emptyStringAsNullDoesNotAffectNonEmptyFields() { + val input = """{"title":"","episode":null,"keep":"world"}""" + val result = parseJson(input) + assertEquals("world", result.keep) + } + + @Test + fun emptyStringAsNullRoundtripsCorrectly() { + val data = EmptyStringData(title = "hello", episode = NestedMeta("x"), keep = "world") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun singleValueOrNullWrapsBareValueInList() { + val input = """{"tags":"a","nums":1,"other":"hello"}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + assertEquals(listOf(1), result.nums) + } + + @Test + fun singleValueOrNullTreatsEmptyArrayAsNull() { + val input = """{"tags":[],"nums":[],"other":"hello"}""" + val result = parseJson(input) + assertNull(result.tags) + assertNull(result.nums) + } + + @Test + fun singleValueOrNullDecodesArrayNormally() { + val input = """{"tags":["a","b"],"nums":[1,2],"other":"hello"}""" + val result = parseJson(input) + assertEquals(listOf("a", "b"), result.tags) + assertEquals(listOf(1, 2), result.nums) + } + + @Test + fun singleValueOrNullDoesNotAffectOtherFields() { + val input = """{"tags":[],"nums":[],"other":"world"}""" + val result = parseJson(input) + assertEquals("world", result.other) + } + + @Test + fun nullableFieldsEmptyStringBecomesNull() { + val input = """{"title":"","meta":{"key":"value"},"other":"hello"}""" + val result = parseJson(input) + assertNull(result.title) + assertEquals(NestedMeta("value"), result.meta) + } + + @Test + fun nullableFieldsEmptyArrayBecomesNull() { + val input = """{"title":"hello","meta":[],"other":"hello"}""" + val result = parseJson(input) + assertEquals("hello", result.title) + assertNull(result.meta) + } + + @Test + fun nullableFieldsBothNullAtOnce() { + val input = """{"title":"","meta":[],"other":"hello"}""" + val result = parseJson(input) + assertNull(result.title) + assertNull(result.meta) + } + + @Test + fun nullableFieldsDoesNotAffectOtherFields() { + val input = """{"title":"","meta":[],"other":"world"}""" + val result = parseJson(input) + assertEquals("world", result.other) + } + + @Test + fun nullableFieldsRoundtripsCorrectly() { + val data = NullableFieldsData(title = "hello", meta = NestedMeta("x"), other = "world") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun singleValueOrEmptyStringWrapsBareString() { + val input = """{"tags":"a","title":"hello"}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + assertEquals("hello", result.title) + } + + @Test + fun singleValueOrEmptyStringTurnsEmptyTitleToNull() { + val input = """{"tags":["a"],"title":""}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + assertNull(result.title) + } + + @Test + fun singleValueOrEmptyStringRoundtripsCorrectly() { + val data = SingleValueOrEmptyStringData(tags = listOf("x"), title = "hello") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun allTransformsWrapsBareStringInList() { + val input = """{"tags":"a","title":"hello","meta":{"key":"value"},"other":"world"}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + } + + @Test + fun allTransformsEmptyArrayTagsBecomesNull() { + val input = """{"tags":[],"title":"hello","meta":{"key":"value"},"other":"world"}""" + val result = parseJson(input) + assertNull(result.tags) + } + + @Test + fun allTransformsEmptyMetaBecomesNull() { + val input = """{"tags":["a"],"title":"hello","meta":[],"other":"world"}""" + val result = parseJson(input) + assertNull(result.meta) + } + + @Test + fun allTransformsEmptyTitleBecomesNull() { + val input = """{"tags":["a"],"title":"","meta":{"key":"value"},"other":"world"}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun allTransformsAllNullAtOnce() { + val input = """{"tags":[],"title":"","meta":[],"other":"world"}""" + val result = parseJson(input) + assertNull(result.tags) + assertNull(result.title) + assertNull(result.meta) + } + + @Test + fun allTransformsDoesNotAffectOtherFields() { + val input = """{"tags":[],"title":"","meta":[],"other":"world"}""" + val result = parseJson(input) + assertEquals("world", result.other) + } + + @Test + fun allTransformsRoundtripsCorrectly() { + val data = AllTransformsData(tags = listOf("a"), title = "hello", meta = NestedMeta("x"), other = "world") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun floatAsIntTruncatesFloat() { + val input = """{"count":3.9}""" + val result = parseJson(input) + assertEquals(3, result.count) + } + + @Test + fun floatAsIntDecodesIntNormally() { + val input = """{"count":42}""" + val result = parseJson(input) + assertEquals(42, result.count) + } + + @Test + fun floatAsIntHandlesNegativeFloat() { + val input = """{"count":-2.7}""" + val result = parseJson(input) + assertEquals(-2, result.count) + } + + @Test + fun floatAsIntRoundtripsCorrectly() { + val data = FloatIntData(count = 7) + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun floatAsLongTruncatesFloat() { + val input = """{"timestamp":1234567890.9}""" + val result = parseJson(input) + assertEquals(1234567890L, result.timestamp) + } + + @Test + fun floatAsLongDecodesLongNormally() { + val input = """{"timestamp":9999999999}""" + val result = parseJson(input) + assertEquals(9999999999L, result.timestamp) + } + + @Test + fun floatAsLongHandlesNegativeFloat() { + val input = """{"timestamp":-100.6}""" + val result = parseJson(input) + assertEquals(-100L, result.timestamp) + } + + @Test + fun floatAsLongRoundtripsCorrectly() { + val data = FloatLongData(timestamp = 1700000000L) + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun nullableStringDecodesEmptyStringAsNull() { + val input = """{"title":""}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun nullableStringDecodesNullAsNull() { + val input = """{"title":null}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun nullableStringKeepsNonEmptyString() { + val input = """{"title":"hello"}""" + val result = parseJson(input) + assertEquals("hello", result.title) + } + + @Test + fun nullableStringRoundtripsCorrectly() { + val data = NullableStringData(title = "world") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } +}