Compare commits

...

4 commits

Author SHA1 Message Date
Luna712
7b3c938ba7
Add more custom serializers for kotlinx serialization (#2978)
This adds numerous other serializers to replicate Jackson mapper configuration behavior, that some extensions will need.
2026-07-12 22:38:08 +00:00
Luna712
15632b864a
[skip ci] TraktProvider: migrate to kotlinx serialization (#2872) 2026-07-12 22:18:22 +00:00
Luna712
505722c1b5
[skip ci] Torrent: migrate to kotlinx serialization (#2882) 2026-07-12 22:09:36 +00:00
firelight
70469f55db
[skip ci] Migration guide (#3058) 2026-07-12 21:43:59 +00:00
13 changed files with 1306 additions and 402 deletions

21
COMPOSE.md Normal file
View file

@ -0,0 +1,21 @@
# Migration guide to Compose
### 1. MVI instead of MVVM
The current design of CloudStream loosely uses the MVVM architecture.
This means that the UI invokes the viewmodel with function calls, and it responds with LiveData fields that are observed. While this has worked, it generates a lot of boilerplate and has created some friction.
To make it easier to work with Compose, the new architecture will be based on MVI. In short this means that the viewmodel exposes a singular immutable class that is observed, and receives all UI events with a singular event that is a sealed class. All the UI should be able to be recreated based on this singular state class, and all interactions should be able to be replayed using only the event callback.
For a more detailed overview, see: https://www.youtube.com/watch?v=b2z1jvD4VMQ
This is part of the effort to make CloudStream cross platform, as it allows us to decouple UI and logic.
### 2. KMP-compatible libraries
We plan to leverage Kotlin's KMP project to compile our code to different architectures. However, this requires us to only use KMP-compatible libraries, no Java. Therefore any pull requests must ensure that they use KMP-compatible libraries only.
### 3. UI Changes
While migrating to the new compose UI, you also have the opportunity to change the UI. However, this should only be to freshen up the UI, not completely redesign it. It is also important to stress that this process should not lose any features of the old UI, and be very conservative with adding new features.

View file

@ -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<String> = emptyList(),
val meta: Map<String, String> = emptyMap(),
val name: String = "hello",
) {
object Serializer : NonEmptySerializer<NonEmptyData>(NonEmptyData.generatedSerializer())
}
@OptIn(ExperimentalSerializationApi::class)
@KeepGeneratedSerializer
@Serializable(with = WriteOnlyData.Serializer::class)
data class WriteOnlyData(
val fieldA: String = "",
val fieldB: String = "",
) {
object Serializer : WriteOnlySerializer<WriteOnlyData>(
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>(
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<NonEmptyData>(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<WriteOnlyData>(input)
assertEquals("hello", result.fieldA)
assertEquals("secret", result.fieldB)
}
@Test
fun writeOnlySerializerDeserializesMissingAsDefault() {
val input = """{"fieldA":"hello"}"""
val result = parseJson<WriteOnlyData>(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<UriData>(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<UriData>(encoded)
assertEquals(data.uri, decoded.uri)
}
}

View file

@ -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<UriData>(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<UriData>(encoded)
assertEquals(data.uri, decoded.uri)
}
}

View file

@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.ExtractorLinkType
import com.lagradost.cloudstream3.utils.newExtractorLink import com.lagradost.cloudstream3.utils.newExtractorLink
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import torrServer.TorrServer import torrServer.TorrServer
import java.io.File import java.io.File
import java.net.ConnectException import java.net.ConnectException
@ -32,14 +34,14 @@ object Torrent {
/** Returns true if the server is up */ /** Returns true if the server is up */
private suspend fun echo(): Boolean { private suspend fun echo(): Boolean {
if(TORRENT_SERVER_URL.isEmpty()) { if (TORRENT_SERVER_URL.isEmpty()) {
return false return false
} }
return try { return try {
app.get( app.get(
"$TORRENT_SERVER_URL/echo", "$TORRENT_SERVER_URL/echo",
).text.isNotEmpty() ).text.isNotEmpty()
} catch (e: ConnectException) { } catch (_: ConnectException) {
// `Failed to connect to /127.0.0.1:8090` if the server is down // `Failed to connect to /127.0.0.1:8090` if the server is down
false false
} catch (t: Throwable) { } catch (t: Throwable) {
@ -52,7 +54,7 @@ object Torrent {
/** Gracefully shutdown the server. /** Gracefully shutdown the server.
* should not be used because I am unable to start it again, and the stopTorrentServer() crashes the app */ * should not be used because I am unable to start it again, and the stopTorrentServer() crashes the app */
suspend fun shutdown(): Boolean { suspend fun shutdown(): Boolean {
if(TORRENT_SERVER_URL.isEmpty()) { if (TORRENT_SERVER_URL.isEmpty()) {
return false return false
} }
return try { return try {
@ -68,7 +70,7 @@ object Torrent {
/** Lists all torrents by the server */ /** Lists all torrents by the server */
@Throws @Throws
private suspend fun list(): Array<TorrentStatus> { private suspend fun list(): Array<TorrentStatus> {
if(TORRENT_SERVER_URL.isEmpty()) { if (TORRENT_SERVER_URL.isEmpty()) {
throw ErrorLoadingException("Not initialized") throw ErrorLoadingException("Not initialized")
} }
return app.post( 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 */ /** Drops a single torrent, (I think) this means closing the stream. Returns returns if it is successful */
private suspend fun drop(hash: String): Boolean { private suspend fun drop(hash: String): Boolean {
if(TORRENT_SERVER_URL.isEmpty()) { if (TORRENT_SERVER_URL.isEmpty()) {
return false return false
} }
return try { return try {
@ -104,7 +106,7 @@ object Torrent {
/** Removes a single torrent from the server registry */ /** Removes a single torrent from the server registry */
private suspend fun rem(hash: String): Boolean { private suspend fun rem(hash: String): Boolean {
if(TORRENT_SERVER_URL.isEmpty()) { if (TORRENT_SERVER_URL.isEmpty()) {
return false return false
} }
return try { return try {
@ -126,7 +128,7 @@ object Torrent {
/** Removes all torrents from the server, and returns if it is successful */ /** Removes all torrents from the server, and returns if it is successful */
suspend fun clearAll(): Boolean { suspend fun clearAll(): Boolean {
if(TORRENT_SERVER_URL.isEmpty()) { if (TORRENT_SERVER_URL.isEmpty()) {
return true return true
} }
return try { return try {
@ -164,10 +166,8 @@ object Torrent {
/** Gets all the metadata of a torrent, will throw if that hash does not exists /** 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 */ * https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/web/api/torrents.go#L126 */
@Throws @Throws
suspend fun get( suspend fun get(hash: String): TorrentStatus {
hash: String, if (TORRENT_SERVER_URL.isEmpty()) {
): TorrentStatus {
if(TORRENT_SERVER_URL.isEmpty()) {
throw ErrorLoadingException("Not initialized") throw ErrorLoadingException("Not initialized")
} }
return app.post( 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*/ /** 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 @Throws
private suspend fun add(url: String): TorrentStatus { private suspend fun add(url: String): TorrentStatus {
if(TORRENT_SERVER_URL.isEmpty()) { if (TORRENT_SERVER_URL.isEmpty()) {
throw ErrorLoadingException("Not initialized") throw ErrorLoadingException("Not initialized")
} }
return app.post( return app.post(
@ -204,7 +204,7 @@ object Torrent {
return true return true
} }
val port = TorrServer.startTorrentServer(dir, 0) val port = TorrServer.startTorrentServer(dir, 0)
if(port < 0) { if (port < 0) {
return false return false
} }
TORRENT_SERVER_URL = "http://127.0.0.1:$port" 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/c18f58e51b6738f053261bc863177078aa9c1c98/web/api/torrents.go#L18
// https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/main/web/api/route.go#L7 // https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/main/web/api/route.go#L7
@Serializable
data class TorrentRequest( data class TorrentRequest(
@JsonProperty("action") @JsonProperty("action") @SerialName("action") val action: String,
val action: String, @JsonProperty("hash") @SerialName("hash") val hash: String = "",
@JsonProperty("hash") @JsonProperty("link") @SerialName("link") val link: String = "",
val hash: String = "", @JsonProperty("title") @SerialName("title") val title: String = "",
@JsonProperty("link") @JsonProperty("poster") @SerialName("poster") val poster: String = "",
val link: String = "", @JsonProperty("data") @SerialName("data") val data: String = "",
@JsonProperty("title") @JsonProperty("save_to_db") @SerialName("save_to_db") val saveToDB: Boolean = false,
val title: String = "",
@JsonProperty("poster")
val poster: String = "",
@JsonProperty("data")
val data: String = "",
@JsonProperty("save_to_db")
val saveToDB: Boolean = false,
) )
// https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/torr/state/state.go#L33 // https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/torr/state/state.go#L33
// omitempty = nullable // omitempty = nullable
@Serializable
data class TorrentStatus( data class TorrentStatus(
@JsonProperty("title") @JsonProperty("title") @SerialName("title") var title: String,
var title: String, @JsonProperty("poster") @SerialName("poster") var poster: String,
@JsonProperty("poster") @JsonProperty("data") @SerialName("data") var data: String?,
var poster: String, @JsonProperty("timestamp") @SerialName("timestamp") var timestamp: Long,
@JsonProperty("data") @JsonProperty("name") @SerialName("name") var name: String?,
var data: String?, @JsonProperty("hash") @SerialName("hash") var hash: String?,
@JsonProperty("timestamp") @JsonProperty("stat") @SerialName("stat") var stat: Int,
var timestamp: Long, @JsonProperty("stat_string") @SerialName("stat_string") var statString: String,
@JsonProperty("name") @JsonProperty("loaded_size") @SerialName("loaded_size") var loadedSize: Long?,
var name: String?, @JsonProperty("torrent_size") @SerialName("torrent_size") var torrentSize: Long?,
@JsonProperty("hash") @JsonProperty("preloaded_bytes") @SerialName("preloaded_bytes") var preloadedBytes: Long?,
var hash: String?, @JsonProperty("preload_size") @SerialName("preload_size") var preloadSize: Long?,
@JsonProperty("stat") @JsonProperty("download_speed") @SerialName("download_speed") var downloadSpeed: Double?,
var stat: Int, @JsonProperty("upload_speed") @SerialName("upload_speed") var uploadSpeed: Double?,
@JsonProperty("stat_string") @JsonProperty("total_peers") @SerialName("total_peers") var totalPeers: Int?,
var statString: String, @JsonProperty("pending_peers") @SerialName("pending_peers") var pendingPeers: Int?,
@JsonProperty("loaded_size") @JsonProperty("active_peers") @SerialName("active_peers") var activePeers: Int?,
var loadedSize: Long?, @JsonProperty("connected_seeders") @SerialName("connected_seeders") var connectedSeeders: Int?,
@JsonProperty("torrent_size") @JsonProperty("half_open_peers") @SerialName("half_open_peers") var halfOpenPeers: Int?,
var torrentSize: Long?, @JsonProperty("bytes_written") @SerialName("bytes_written") var bytesWritten: Long?,
@JsonProperty("preloaded_bytes") @JsonProperty("bytes_written_data") @SerialName("bytes_written_data") var bytesWrittenData: Long?,
var preloadedBytes: Long?, @JsonProperty("bytes_read") @SerialName("bytes_read") var bytesRead: Long?,
@JsonProperty("preload_size") @JsonProperty("bytes_read_data") @SerialName("bytes_read_data") var bytesReadData: Long?,
var preloadSize: Long?, @JsonProperty("bytes_read_useful_data") @SerialName("bytes_read_useful_data") var bytesReadUsefulData: Long?,
@JsonProperty("download_speed") @JsonProperty("chunks_written") @SerialName("chunks_written") var chunksWritten: Long?,
var downloadSpeed: Double?, @JsonProperty("chunks_read") @SerialName("chunks_read") var chunksRead: Long?,
@JsonProperty("upload_speed") @JsonProperty("chunks_read_useful") @SerialName("chunks_read_useful") var chunksReadUseful: Long?,
var uploadSpeed: Double?, @JsonProperty("chunks_read_wasted") @SerialName("chunks_read_wasted") var chunksReadWasted: Long?,
@JsonProperty("total_peers") @JsonProperty("pieces_dirtied_good") @SerialName("pieces_dirtied_good") var piecesDirtiedGood: Long?,
var totalPeers: Int?, @JsonProperty("pieces_dirtied_bad") @SerialName("pieces_dirtied_bad") var piecesDirtiedBad: Long?,
@JsonProperty("pending_peers") @JsonProperty("duration_seconds") @SerialName("duration_seconds") var durationSeconds: Double?,
var pendingPeers: Int?, @JsonProperty("bit_rate") @SerialName("bit_rate") var bitRate: String?,
@JsonProperty("active_peers") @JsonProperty("file_stats") @SerialName("file_stats") var fileStats: List<TorrentFileStat>?,
var activePeers: Int?, @JsonProperty("trackers") @SerialName("trackers") var trackers: List<String>?,
@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<TorrentFileStat>?,
@JsonProperty("trackers")
var trackers: List<String>?,
) { ) {
fun streamUrl(url: String): String { fun streamUrl(url: String): String {
val fileName = val fileName =
@ -381,12 +342,10 @@ object Torrent {
} }
} }
@Serializable
data class TorrentFileStat( data class TorrentFileStat(
@JsonProperty("id") @JsonProperty("id") @SerialName("id") val id: Int?,
val id: Int?, @JsonProperty("path") @SerialName("path") val path: String?,
@JsonProperty("path") @JsonProperty("length") @SerialName("length") val length: Long?,
val path: String?,
@JsonProperty("length")
val length: Long?,
) )
} }

View file

@ -5831,6 +5831,7 @@ public class com/lagradost/cloudstream3/metaproviders/TraktProvider : com/lagrad
} }
public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs { public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V public fun <init> (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 <init> (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 public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V public fun <init> (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V
public synthetic fun <init> (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)V public fun <init> (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (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 fun <init> (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 <init> (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 public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (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 fun <init> (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 <init> (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 public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (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 fun <init> (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 <init> (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 synthetic fun <init> (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 component1 ()Ljava/lang/String;
public final fun component10 ()Ljava/lang/String; public final fun component10 ()Ljava/lang/String;
public final fun component11 ()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 component23 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;
public final fun component24 ()Ljava/lang/String; public final fun component24 ()Ljava/lang/String;
public final fun component25 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; 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 component3 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;
public final fun component4 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String;
public final fun component5 ()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 component7 ()Ljava/lang/Integer;
public final fun component8 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String;
public final fun component9 ()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 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$MediaDetails;ILjava/lang/Object;)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 fun equals (Ljava/lang/Object;)Z
public final fun getAiredEpisodes ()Ljava/lang/Integer; public final fun getAiredEpisodes ()Ljava/lang/Integer;
public final fun getAirs ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; 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 getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;
public final fun getLanguage ()Ljava/lang/String; public final fun getLanguage ()Ljava/lang/String;
public final fun getLanguages ()Ljava/util/List; 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 getNetwork ()Ljava/lang/String;
public final fun getOverview ()Ljava/lang/String; public final fun getOverview ()Ljava/lang/String;
public final fun getRating ()Ljava/lang/Double; 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 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 <init> ()V
public fun <init> (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 <init> (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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$People {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (Ljava/util/List;)V public fun <init> (Ljava/util/List;)V
public synthetic fun <init> (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V public fun <init> (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V
public synthetic fun <init> (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (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 fun <init> (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 <init> (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 public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode {
public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$Companion;
public fun <init> ()V public fun <init> ()V
public fun <init> (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 fun <init> (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 <init> (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 public synthetic fun <init> (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 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 final class com/lagradost/cloudstream3/mvvm/ArchComponentExtKt {
public static final field DEBUG_EXCEPTION Ljava/lang/String; public static final field DEBUG_EXCEPTION Ljava/lang/String;
public static final field DEBUG_PRINT Ljava/lang/String; public static final field DEBUG_PRINT Ljava/lang/String;

View file

@ -34,6 +34,10 @@ import com.lagradost.cloudstream3.newTvSeriesLoadResponse
import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse
import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson 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() { open class TraktProvider : MainAPI() {
override var name = "Trakt" override var name = "Trakt"
@ -46,7 +50,6 @@ open class TraktProvider : MainAPI() {
) )
private val traktApiUrl = "https://api.trakt.tv" private val traktApiUrl = "https://api.trakt.tv"
private val traktClientId: String = BuildConfig.TRAKT_CLIENT_ID private val traktClientId: String = BuildConfig.TRAKT_CLIENT_ID
override val mainPage = mainPageOf( override val mainPage = mainPageOf(
@ -58,16 +61,21 @@ open class TraktProvider : MainAPI() {
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
val apiResponse = getApi("${request.data}?extended=full,images&page=$page") val apiResponse = getApi("${request.data}?extended=full,images&page=$page")
val results = parseJson<List<MediaDetails>>(apiResponse).map { element -> val results = parseJson<List<MediaDetails>>(apiResponse).map { element ->
element.toSearchResponse() element.toSearchResponse()
} }
return newHomePageResponse(request.name, results) return newHomePageResponse(request.name, results)
} }
private fun MediaDetails.toSearchResponse(): SearchResponse { private fun MediaDetails.toSearchResponse(): SearchResponse {
val media = this.media ?: MediaSummary(
val media = this.media ?: this 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 mediaType = if (media.ids?.tvdb == null) TvType.Movie else TvType.TvSeries
val poster = media.images?.poster?.firstOrNull() val poster = media.images?.poster?.firstOrNull()
return if (mediaType == TvType.Movie) { return if (mediaType == TvType.Movie) {
@ -75,7 +83,7 @@ open class TraktProvider : MainAPI() {
name = media.title ?: "", name = media.title ?: "",
url = Data( url = Data(
type = mediaType, type = mediaType,
mediaDetails = media, mediaDetails = this,
).toJson(), ).toJson(),
type = TvType.Movie, type = TvType.Movie,
) { ) {
@ -87,7 +95,7 @@ open class TraktProvider : MainAPI() {
name = media.title ?: "", name = media.title ?: "",
url = Data( url = Data(
type = mediaType, type = mediaType,
mediaDetails = media, mediaDetails = this,
).toJson(), ).toJson(),
type = TvType.TvSeries, type = TvType.TvSeries,
) { ) {
@ -98,9 +106,7 @@ open class TraktProvider : MainAPI() {
} }
override suspend fun search(query: String, page: Int): SearchResponseList? { override suspend fun search(query: String, page: Int): SearchResponseList? {
val apiResponse = val apiResponse = getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=$page&query=$query")
getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=$page&query=$query")
return newSearchResponseList(parseJson<List<MediaDetails>>(apiResponse).map { element -> return newSearchResponseList(parseJson<List<MediaDetails>>(apiResponse).map { element ->
element.toSearchResponse() element.toSearchResponse()
}) })
@ -115,9 +121,7 @@ open class TraktProvider : MainAPI() {
val backDropUrl = fixPath(mediaDetails?.images?.fanart?.firstOrNull()) val backDropUrl = fixPath(mediaDetails?.images?.fanart?.firstOrNull())
val logoUrl = fixPath(mediaDetails?.images?.logo?.firstOrNull()) val logoUrl = fixPath(mediaDetails?.images?.logo?.firstOrNull())
val resActor = val resActor = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=full,images")
getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=full,images")
val actors = parseJson<People>(resActor).cast?.map { val actors = parseJson<People>(resActor).cast?.map {
ActorData( ActorData(
Actor( Actor(
@ -128,9 +132,7 @@ open class TraktProvider : MainAPI() {
) )
} }
val resRelated = val resRelated = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=full,images&limit=20")
getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=full,images&limit=20")
val relatedMedia = parseJson<List<MediaDetails>>(resRelated).map { it.toSearchResponse() } val relatedMedia = parseJson<List<MediaDetails>>(resRelated).map { it.toSearchResponse() }
val isCartoon = val isCartoon =
@ -142,7 +144,6 @@ open class TraktProvider : MainAPI() {
val uniqueUrl = data.mediaDetails?.ids?.trakt?.toJson() ?: data.toJson() val uniqueUrl = data.mediaDetails?.ids?.trakt?.toJson() ?: data.toJson()
if (data.type == TvType.Movie) { if (data.type == TvType.Movie) {
val linkData = LinkData( val linkData = LinkData(
id = mediaDetails?.ids?.tmdb, id = mediaDetails?.ids?.tmdb,
traktId = mediaDetails?.ids?.trakt, traktId = mediaDetails?.ids?.trakt,
@ -156,7 +157,7 @@ open class TraktProvider : MainAPI() {
year = mediaDetails?.year, year = mediaDetails?.year,
orgTitle = mediaDetails?.title, orgTitle = mediaDetails?.title,
isAnime = isAnime, 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 airedDate = mediaDetails?.released
?: mediaDetails?.firstAired, ?: mediaDetails?.firstAired,
isAsian = isAsian, isAsian = isAsian,
@ -190,7 +191,6 @@ open class TraktProvider : MainAPI() {
addTMDbId(mediaDetails.ids?.tmdb.toString()) addTMDbId(mediaDetails.ids?.tmdb.toString())
} }
} else { } else {
val resSeasons = val resSeasons =
getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=full,images,episodes") getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=full,images,episodes")
val episodes = mutableListOf<Episode>() val episodes = mutableListOf<Episode>()
@ -198,9 +198,7 @@ open class TraktProvider : MainAPI() {
var nextAir: NextAiring? = null var nextAir: NextAiring? = null
seasons.forEach { season -> seasons.forEach { season ->
season.episodes?.map { episode -> season.episodes?.map { episode ->
val linkData = LinkData( val linkData = LinkData(
id = mediaDetails?.ids?.tmdb, id = mediaDetails?.ids?.tmdb,
traktId = mediaDetails?.ids?.trakt, traktId = mediaDetails?.ids?.trakt,
@ -234,8 +232,7 @@ open class TraktProvider : MainAPI() {
this.episode = episode.number this.episode = episode.number
this.description = episode.overview this.description = episode.overview
this.runTime = episode.runtime this.runTime = episode.runtime
this.posterUrl = fixPath( episode.images?.screenshot?.firstOrNull()) this.posterUrl = fixPath(episode.images?.screenshot?.firstOrNull())
//this.rating = episode.rating?.times(10)?.roundToInt()
this.score = Score.from10(episode.rating) this.score = Score.from10(episode.rating)
this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
@ -307,143 +304,164 @@ open class TraktProvider : MainAPI() {
return "https://$url" return "https://$url"
} }
@Serializable
data class Data( data class Data(
val type: TvType? = null, @JsonProperty("type") @SerialName("type") val type: TvType? = null,
val mediaDetails: MediaDetails? = 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( data class MediaDetails(
@JsonProperty("title") val title: String? = null, @JsonProperty("title") @SerialName("title") val title: String? = null,
@JsonProperty("year") val year: Int? = null, @JsonProperty("year") @SerialName("year") val year: Int? = null,
@JsonProperty("ids") val ids: Ids? = null, @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null,
@JsonProperty("tagline") val tagline: String? = null, @JsonProperty("tagline") @SerialName("tagline") val tagline: String? = null,
@JsonProperty("overview") val overview: String? = null, @JsonProperty("overview") @SerialName("overview") val overview: String? = null,
@JsonProperty("released") val released: String? = null, @JsonProperty("released") @SerialName("released") val released: String? = null,
@JsonProperty("runtime") val runtime: Int? = null, @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null,
@JsonProperty("country") val country: String? = null, @JsonProperty("country") @SerialName("country") val country: String? = null,
@JsonProperty("updatedAt") val updatedAt: String? = null, @JsonProperty("updatedAt") @SerialName("updatedAt") val updatedAt: String? = null,
@JsonProperty("trailer") val trailer: String? = null, @JsonProperty("trailer") @SerialName("trailer") val trailer: String? = null,
@JsonProperty("homepage") val homepage: String? = null, @JsonProperty("homepage") @SerialName("homepage") val homepage: String? = null,
@JsonProperty("status") val status: String? = null, @JsonProperty("status") @SerialName("status") val status: String? = null,
@JsonProperty("rating") val rating: Double? = null, @JsonProperty("rating") @SerialName("rating") val rating: Double? = null,
@JsonProperty("votes") val votes: Long? = null, @JsonProperty("votes") @SerialName("votes") val votes: Long? = null,
@JsonProperty("comment_count") val commentCount: Long? = null, @JsonProperty("comment_count") @SerialName("comment_count") val commentCount: Long? = null,
@JsonProperty("language") val language: String? = null, @JsonProperty("language") @SerialName("language") val language: String? = null,
@JsonProperty("languages") val languages: List<String>? = null, @JsonProperty("languages") @SerialName("languages") val languages: List<String>? = null,
@JsonProperty("available_translations") val availableTranslations: List<String>? = null, @JsonProperty("available_translations") @SerialName("available_translations") val availableTranslations: List<String>? = null,
@JsonProperty("genres") val genres: List<String>? = null, @JsonProperty("genres") @SerialName("genres") val genres: List<String>? = null,
@JsonProperty("certification") val certification: String? = null, @JsonProperty("certification") @SerialName("certification") val certification: String? = null,
@JsonProperty("aired_episodes") val airedEpisodes: Int? = null, @JsonProperty("aired_episodes") @SerialName("aired_episodes") val airedEpisodes: Int? = null,
@JsonProperty("first_aired") val firstAired: String? = null, @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null,
@JsonProperty("airs") val airs: Airs? = null, @JsonProperty("airs") @SerialName("airs") val airs: Airs? = null,
@JsonProperty("network") val network: String? = null, @JsonProperty("network") @SerialName("network") val network: String? = null,
@JsonProperty("images") val images: Images? = null, @JsonProperty("images") @SerialName("images") val images: Images? = null,
@JsonProperty("movie") @JsonAlias("show") val media: MediaDetails? = null @JsonProperty("media") @JsonAlias("movie", "show") @SerialName("media") @JsonNames("movie", "show") val media: MediaSummary? = null,
) )
@Serializable
data class Airs( data class Airs(
@JsonProperty("day") val day: String? = null, @JsonProperty("day") @SerialName("day") val day: String? = null,
@JsonProperty("time") val time: String? = null, @JsonProperty("time") @SerialName("time") val time: String? = null,
@JsonProperty("timezone") val timezone: String? = null, @JsonProperty("timezone") @SerialName("timezone") val timezone: String? = null,
) )
@Serializable
data class Ids( data class Ids(
@JsonProperty("trakt") val trakt: Int? = null, @JsonProperty("trakt") @SerialName("trakt") val trakt: Int? = null,
@JsonProperty("slug") val slug: String? = null, @JsonProperty("slug") @SerialName("slug") val slug: String? = null,
@JsonProperty("tvdb") val tvdb: Int? = null, @JsonProperty("tvdb") @SerialName("tvdb") val tvdb: Int? = null,
@JsonProperty("imdb") val imdb: String? = null, @JsonProperty("imdb") @SerialName("imdb") val imdb: String? = null,
@JsonProperty("tmdb") val tmdb: Int? = null, @JsonProperty("tmdb") @SerialName("tmdb") val tmdb: Int? = null,
@JsonProperty("tvrage") val tvrage: String? = null, @JsonProperty("tvrage") @SerialName("tvrage") val tvrage: String? = null,
) )
@Serializable
data class Images( data class Images(
@JsonProperty("poster") val poster: List<String>? = null, @JsonProperty("poster") @SerialName("poster") val poster: List<String>? = null,
@JsonProperty("fanart") val fanart: List<String>? = null, @JsonProperty("fanart") @SerialName("fanart") val fanart: List<String>? = null,
@JsonProperty("logo") val logo: List<String>? = null, @JsonProperty("logo") @SerialName("logo") val logo: List<String>? = null,
@JsonProperty("clearart") val clearArt: List<String>? = null, @JsonProperty("clearart") @SerialName("clearart") val clearArt: List<String>? = null,
@JsonProperty("banner") val banner: List<String>? = null, @JsonProperty("banner") @SerialName("banner") val banner: List<String>? = null,
@JsonProperty("thumb") val thumb: List<String>? = null, @JsonProperty("thumb") @SerialName("thumb") val thumb: List<String>? = null,
@JsonProperty("screenshot") val screenshot: List<String>? = null, @JsonProperty("screenshot") @SerialName("screenshot") val screenshot: List<String>? = null,
@JsonProperty("headshot") val headshot: List<String>? = null, @JsonProperty("headshot") @SerialName("headshot") val headshot: List<String>? = null,
) )
@Serializable
data class People( data class People(
@JsonProperty("cast") val cast: List<Cast>? = null, @JsonProperty("cast") @SerialName("cast") val cast: List<Cast>? = null,
) )
@Serializable
data class Cast( data class Cast(
@JsonProperty("character") val character: String? = null, @JsonProperty("character") @SerialName("character") val character: String? = null,
@JsonProperty("characters") val characters: List<String>? = null, @JsonProperty("characters") @SerialName("characters") val characters: List<String>? = null,
@JsonProperty("episode_count") val episodeCount: Long? = null, @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Long? = null,
@JsonProperty("person") val person: Person? = null, @JsonProperty("person") @SerialName("person") val person: Person? = null,
@JsonProperty("images") val images: Images? = null, @JsonProperty("images") @SerialName("images") val images: Images? = null,
) )
@Serializable
data class Person( data class Person(
@JsonProperty("name") val name: String? = null, @JsonProperty("name") @SerialName("name") val name: String? = null,
@JsonProperty("ids") val ids: Ids? = null, @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null,
@JsonProperty("images") val images: Images? = null, @JsonProperty("images") @SerialName("images") val images: Images? = null,
) )
@Serializable
data class Seasons( data class Seasons(
@JsonProperty("aired_episodes") val airedEpisodes: Int? = null, @JsonProperty("aired_episodes") @SerialName("aired_episodes") val airedEpisodes: Int? = null,
@JsonProperty("episode_count") val episodeCount: Int? = null, @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Int? = null,
@JsonProperty("episodes") val episodes: List<TraktEpisode>? = null, @JsonProperty("episodes") @SerialName("episodes") val episodes: List<TraktEpisode>? = null,
@JsonProperty("first_aired") val firstAired: String? = null, @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null,
@JsonProperty("ids") val ids: Ids? = null, @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null,
@JsonProperty("images") val images: Images? = null, @JsonProperty("images") @SerialName("images") val images: Images? = null,
@JsonProperty("network") val network: String? = null, @JsonProperty("network") @SerialName("network") val network: String? = null,
@JsonProperty("number") val number: Int? = null, @JsonProperty("number") @SerialName("number") val number: Int? = null,
@JsonProperty("overview") val overview: String? = null, @JsonProperty("overview") @SerialName("overview") val overview: String? = null,
@JsonProperty("rating") val rating: Double? = null, @JsonProperty("rating") @SerialName("rating") val rating: Double? = null,
@JsonProperty("title") val title: String? = null, @JsonProperty("title") @SerialName("title") val title: String? = null,
@JsonProperty("updated_at") val updatedAt: String? = null, @JsonProperty("updated_at") @SerialName("updated_at") val updatedAt: String? = null,
@JsonProperty("votes") val votes: Int? = null, @JsonProperty("votes") @SerialName("votes") val votes: Int? = null,
) )
@Serializable
data class TraktEpisode( data class TraktEpisode(
@JsonProperty("available_translations") val availableTranslations: List<String>? = null, @JsonProperty("available_translations") @SerialName("available_translations") val availableTranslations: List<String>? = null,
@JsonProperty("comment_count") val commentCount: Int? = null, @JsonProperty("comment_count") @SerialName("comment_count") val commentCount: Int? = null,
@JsonProperty("episode_type") val episodeType: String? = null, @JsonProperty("episode_type") @SerialName("episode_type") val episodeType: String? = null,
@JsonProperty("first_aired") val firstAired: String? = null, @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null,
@JsonProperty("ids") val ids: Ids? = null, @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null,
@JsonProperty("images") val images: Images? = null, @JsonProperty("images") @SerialName("images") val images: Images? = null,
@JsonProperty("number") val number: Int? = null, @JsonProperty("number") @SerialName("number") val number: Int? = null,
@JsonProperty("number_abs") val numberAbs: Int? = null, @JsonProperty("number_abs") @SerialName("number_abs") val numberAbs: Int? = null,
@JsonProperty("overview") val overview: String? = null, @JsonProperty("overview") @SerialName("overview") val overview: String? = null,
@JsonProperty("rating") val rating: Double? = null, @JsonProperty("rating") @SerialName("rating") val rating: Double? = null,
@JsonProperty("runtime") val runtime: Int? = null, @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null,
@JsonProperty("season") val season: Int? = null, @JsonProperty("season") @SerialName("season") val season: Int? = null,
@JsonProperty("title") val title: String? = null, @JsonProperty("title") @SerialName("title") val title: String? = null,
@JsonProperty("updated_at") val updatedAt: String? = null, @JsonProperty("updated_at") @SerialName("updated_at") val updatedAt: String? = null,
@JsonProperty("votes") val votes: Int? = null, @JsonProperty("votes") @SerialName("votes") val votes: Int? = null,
) )
@Serializable
data class LinkData( data class LinkData(
@JsonProperty("id") val id: Int? = null, @JsonProperty("id") @SerialName("id") val id: Int? = null,
@JsonProperty("trakt_id") val traktId: Int? = null, @JsonProperty("trakt_id") @SerialName("trakt_id") val traktId: Int? = null,
@JsonProperty("trakt_slug") val traktSlug: String? = null, @JsonProperty("trakt_slug") @SerialName("trakt_slug") val traktSlug: String? = null,
@JsonProperty("tmdb_id") val tmdbId: Int? = null, @JsonProperty("tmdb_id") @SerialName("tmdb_id") val tmdbId: Int? = null,
@JsonProperty("imdb_id") val imdbId: String? = null, @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null,
@JsonProperty("tvdb_id") val tvdbId: Int? = null, @JsonProperty("tvdb_id") @SerialName("tvdb_id") val tvdbId: Int? = null,
@JsonProperty("tvrage_id") val tvrageId: String? = null, @JsonProperty("tvrage_id") @SerialName("tvrage_id") val tvrageId: String? = null,
@JsonProperty("type") val type: String? = null, @JsonProperty("type") @SerialName("type") val type: String? = null,
@JsonProperty("season") val season: Int? = null, @JsonProperty("season") @SerialName("season") val season: Int? = null,
@JsonProperty("episode") val episode: Int? = null, @JsonProperty("episode") @SerialName("episode") val episode: Int? = null,
@JsonProperty("ani_id") val aniId: String? = null, @JsonProperty("ani_id") @SerialName("ani_id") val aniId: String? = null,
@JsonProperty("anime_id") val animeId: String? = null, @JsonProperty("anime_id") @SerialName("anime_id") val animeId: String? = null,
@JsonProperty("title") val title: String? = null, @JsonProperty("title") @SerialName("title") val title: String? = null,
@JsonProperty("year") val year: Int? = null, @JsonProperty("year") @SerialName("year") val year: Int? = null,
@JsonProperty("org_title") val orgTitle: String? = null, @JsonProperty("org_title") @SerialName("org_title") val orgTitle: String? = null,
@JsonProperty("is_anime") val isAnime: Boolean = false, @JsonProperty("is_anime") @SerialName("is_anime") val isAnime: Boolean = false,
@JsonProperty("aired_year") val airedYear: Int? = null, @JsonProperty("aired_year") @SerialName("aired_year") val airedYear: Int? = null,
@JsonProperty("last_season") val lastSeason: Int? = null, @JsonProperty("last_season") @SerialName("last_season") val lastSeason: Int? = null,
@JsonProperty("eps_title") val epsTitle: String? = null, @JsonProperty("eps_title") @SerialName("eps_title") val epsTitle: String? = null,
@JsonProperty("jp_title") val jpTitle: String? = null, @JsonProperty("jp_title") @SerialName("jp_title") val jpTitle: String? = null,
@JsonProperty("date") val date: String? = null, @JsonProperty("date") @SerialName("date") val date: String? = null,
@JsonProperty("aired_date") val airedDate: String? = null, @JsonProperty("aired_date") @SerialName("aired_date") val airedDate: String? = null,
@JsonProperty("is_asian") val isAsian: Boolean = false, @JsonProperty("is_asian") @SerialName("is_asian") val isAsian: Boolean = false,
@JsonProperty("is_bollywood") val isBollywood: Boolean = false, @JsonProperty("is_bollywood") @SerialName("is_bollywood") val isBollywood: Boolean = false,
@JsonProperty("is_cartoon") val isCartoon: Boolean = false, @JsonProperty("is_cartoon") @SerialName("is_cartoon") val isCartoon: Boolean = false,
) )
} }

View file

@ -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<Int> {
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)
}

View file

@ -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<Long> {
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)
}

View file

@ -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<String>? = null,
* @SerialName("meta") val meta: MyMeta? = null,
* ) {
* object Serializer : JsonTransformSerializer<MyData>(
* MyData.generatedSerializer(),
* singleValueAsListKeys = setOf("tags"),
* emptyStringAsNullKeys = setOf("title", "tags"),
* emptyArrayAsNullKeys = setOf("tags", "meta"),
* )
* }
*/
@Prerelease
abstract class JsonTransformSerializer<T : Any>(
tSerializer: KSerializer<T>,
private val singleValueAsListKeys: Set<String> = emptySet(),
private val emptyArrayAsNullKeys: Set<String> = emptySet(),
private val emptyStringAsNullKeys: Set<String> = emptySet(),
) : JsonTransformingSerializer<T>(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
})
}
}

View file

@ -17,13 +17,13 @@ import kotlinx.serialization.json.JsonTransformingSerializer
* *
* Usage: * Usage:
* *
* @OptIn(ExperimentalSerializationApi::class) * @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now
* @KeepGeneratedSerializer * @KeepGeneratedSerializer
* @Serializable(with = MyData.Serializer::class) * @Serializable(with = MyData.Serializer::class)
* data class MyData( * data class MyData(
* val tags: List<String> = emptyList(), * @SerialName("tags") val tags: List<String> = emptyList(),
* val title: String = "", * @SerialName("title") val title: String = "",
* val meta: Map<String, String> = emptyMap(), * @SerialName("meta") val meta: Map<String, String> = emptyMap(),
* ) { * ) {
* object Serializer : NonEmptySerializer<MyData>(MyData.generatedSerializer()) * object Serializer : NonEmptySerializer<MyData>(MyData.generatedSerializer())
* } * }
@ -33,7 +33,6 @@ abstract class NonEmptySerializer<T : Any>(tSerializer: KSerializer<T>) :
override fun transformSerialize(element: JsonElement): JsonElement { override fun transformSerialize(element: JsonElement): JsonElement {
if (element !is JsonObject) return element if (element !is JsonObject) return element
return JsonObject(element.filterValues { value -> return JsonObject(element.filterValues { value ->
when (value) { when (value) {
is JsonPrimitive -> value.content.isNotEmpty() is JsonPrimitive -> value.content.isNotEmpty()

View file

@ -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<String?> {
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)
}
}

View file

@ -12,12 +12,12 @@ import kotlinx.serialization.json.JsonTransformingSerializer
* *
* Usage: * Usage:
* *
* @OptIn(ExperimentalSerializationApi::class) * @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now
* @KeepGeneratedSerializer * @KeepGeneratedSerializer
* @Serializable(with = MyData.Serializer::class) * @Serializable(with = MyData.Serializer::class)
* data class MyData( * data class MyData(
* val fieldA: String = "", * @SerialName("fieldA") val fieldA: String = "",
* val fieldB: String = "", * @SerialName("fieldB") val fieldB: String = "",
* ) { * ) {
* object Serializer : WriteOnlySerializer<MyData>( * object Serializer : WriteOnlySerializer<MyData>(
* MyData.generatedSerializer(), * MyData.generatedSerializer(),

View file

@ -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<String> = emptyList(),
@SerialName("meta") val meta: Map<String, String> = emptyMap(),
@SerialName("name") val name: String = "hello",
) {
object Serializer : NonEmptySerializer<NonEmptyData>(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>(
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>(
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<String> = emptyList(),
@SerialName("nums") val nums: List<Int> = emptyList(),
) {
object Serializer : JsonTransformSerializer<SingleValueData>(
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>(
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>(
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<String>? = null,
@SerialName("nums") val nums: List<Int>? = null,
@SerialName("other") val other: String = "hello",
) {
object Serializer : JsonTransformSerializer<SingleValueOrNullData>(
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>(
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<String> = emptyList(),
@SerialName("title") val title: String? = null,
) {
object Serializer : JsonTransformSerializer<SingleValueOrEmptyStringData>(
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<String>? = null,
@SerialName("title") val title: String? = null,
@SerialName("meta") val meta: NestedMeta? = null,
@SerialName("other") val other: String = "hello",
) {
object Serializer : JsonTransformSerializer<AllTransformsData>(
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<NonEmptyData>(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<WriteOnlyData>(input)
assertEquals("hello", result.fieldA)
assertEquals("secret", result.fieldB)
}
@Test
fun writeOnlySerializerDeserializesMissingAsDefault() {
val input = """{"fieldA":"hello"}"""
val result = parseJson<WriteOnlyData>(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<SingleValueData>(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<SingleValueData>(input)
assertEquals(listOf("a"), result.tags)
}
@Test
fun singleValueAsListWrapsBareIntInList() {
val input = """{"tags":[],"nums":42}"""
val result = parseJson<SingleValueData>(input)
assertEquals(listOf(42), result.nums)
}
@Test
fun singleValueAsListDecodesNullAsEmptyList() {
val input = """{"tags":null,"nums":[]}"""
val result = parseJson<SingleValueData>(input)
assertEquals(emptyList<String>(), result.tags)
}
@Test
fun singleValueAsListRoundtripsCorrectly() {
val data = SingleValueData(tags = listOf("x", "y"), nums = listOf(1, 2))
val encoded = data.toJson()
val decoded = parseJson<SingleValueData>(encoded)
assertEquals(data, decoded)
}
@Test
fun emptyArrayAsNullDecodesEmptyArrayAsNull() {
val input = """{"meta":[],"other":"hello"}"""
val result = parseJson<EmptyArrayData>(input)
assertNull(result.meta)
}
@Test
fun emptyArrayAsNullDecodesNullAsNull() {
val input = """{"meta":null,"other":"hello"}"""
val result = parseJson<EmptyArrayData>(input)
assertNull(result.meta)
}
@Test
fun emptyArrayAsNullDecodesObjectNormally() {
val input = """{"meta":{"key":"value"},"other":"hello"}"""
val result = parseJson<EmptyArrayData>(input)
assertEquals(NestedMeta("value"), result.meta)
}
@Test
fun emptyArrayAsNullDoesNotAffectOtherFields() {
val input = """{"meta":[],"other":"world"}"""
val result = parseJson<EmptyArrayData>(input)
assertEquals("world", result.other)
}
@Test
fun emptyArrayAsNullRoundtripsCorrectly() {
val data = EmptyArrayData(meta = NestedMeta("test"), other = "hello")
val encoded = data.toJson()
val decoded = parseJson<EmptyArrayData>(encoded)
assertEquals(data, decoded)
}
@Test
fun emptyStringAsNullDecodesEmptyStringAsNull() {
val input = """{"title":"","episode":null,"keep":"hello"}"""
val result = parseJson<EmptyStringData>(input)
assertNull(result.title)
}
@Test
fun emptyStringAsNullDecodesNullAsNull() {
val input = """{"title":null,"episode":null,"keep":"hello"}"""
val result = parseJson<EmptyStringData>(input)
assertNull(result.title)
}
@Test
fun emptyStringAsNullKeepsNonEmptyString() {
val input = """{"title":"hello","episode":null,"keep":"world"}"""
val result = parseJson<EmptyStringData>(input)
assertEquals("hello", result.title)
}
@Test
fun emptyStringAsNullDecodesObjectNormally() {
val input = """{"title":null,"episode":{"key":"value"},"keep":"hello"}"""
val result = parseJson<EmptyStringData>(input)
assertEquals(NestedMeta("value"), result.episode)
}
@Test
fun emptyStringAsNullDoesNotAffectNonEmptyFields() {
val input = """{"title":"","episode":null,"keep":"world"}"""
val result = parseJson<EmptyStringData>(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<EmptyStringData>(encoded)
assertEquals(data, decoded)
}
@Test
fun singleValueOrNullWrapsBareValueInList() {
val input = """{"tags":"a","nums":1,"other":"hello"}"""
val result = parseJson<SingleValueOrNullData>(input)
assertEquals(listOf("a"), result.tags)
assertEquals(listOf(1), result.nums)
}
@Test
fun singleValueOrNullTreatsEmptyArrayAsNull() {
val input = """{"tags":[],"nums":[],"other":"hello"}"""
val result = parseJson<SingleValueOrNullData>(input)
assertNull(result.tags)
assertNull(result.nums)
}
@Test
fun singleValueOrNullDecodesArrayNormally() {
val input = """{"tags":["a","b"],"nums":[1,2],"other":"hello"}"""
val result = parseJson<SingleValueOrNullData>(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<SingleValueOrNullData>(input)
assertEquals("world", result.other)
}
@Test
fun nullableFieldsEmptyStringBecomesNull() {
val input = """{"title":"","meta":{"key":"value"},"other":"hello"}"""
val result = parseJson<NullableFieldsData>(input)
assertNull(result.title)
assertEquals(NestedMeta("value"), result.meta)
}
@Test
fun nullableFieldsEmptyArrayBecomesNull() {
val input = """{"title":"hello","meta":[],"other":"hello"}"""
val result = parseJson<NullableFieldsData>(input)
assertEquals("hello", result.title)
assertNull(result.meta)
}
@Test
fun nullableFieldsBothNullAtOnce() {
val input = """{"title":"","meta":[],"other":"hello"}"""
val result = parseJson<NullableFieldsData>(input)
assertNull(result.title)
assertNull(result.meta)
}
@Test
fun nullableFieldsDoesNotAffectOtherFields() {
val input = """{"title":"","meta":[],"other":"world"}"""
val result = parseJson<NullableFieldsData>(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<NullableFieldsData>(encoded)
assertEquals(data, decoded)
}
@Test
fun singleValueOrEmptyStringWrapsBareString() {
val input = """{"tags":"a","title":"hello"}"""
val result = parseJson<SingleValueOrEmptyStringData>(input)
assertEquals(listOf("a"), result.tags)
assertEquals("hello", result.title)
}
@Test
fun singleValueOrEmptyStringTurnsEmptyTitleToNull() {
val input = """{"tags":["a"],"title":""}"""
val result = parseJson<SingleValueOrEmptyStringData>(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<SingleValueOrEmptyStringData>(encoded)
assertEquals(data, decoded)
}
@Test
fun allTransformsWrapsBareStringInList() {
val input = """{"tags":"a","title":"hello","meta":{"key":"value"},"other":"world"}"""
val result = parseJson<AllTransformsData>(input)
assertEquals(listOf("a"), result.tags)
}
@Test
fun allTransformsEmptyArrayTagsBecomesNull() {
val input = """{"tags":[],"title":"hello","meta":{"key":"value"},"other":"world"}"""
val result = parseJson<AllTransformsData>(input)
assertNull(result.tags)
}
@Test
fun allTransformsEmptyMetaBecomesNull() {
val input = """{"tags":["a"],"title":"hello","meta":[],"other":"world"}"""
val result = parseJson<AllTransformsData>(input)
assertNull(result.meta)
}
@Test
fun allTransformsEmptyTitleBecomesNull() {
val input = """{"tags":["a"],"title":"","meta":{"key":"value"},"other":"world"}"""
val result = parseJson<AllTransformsData>(input)
assertNull(result.title)
}
@Test
fun allTransformsAllNullAtOnce() {
val input = """{"tags":[],"title":"","meta":[],"other":"world"}"""
val result = parseJson<AllTransformsData>(input)
assertNull(result.tags)
assertNull(result.title)
assertNull(result.meta)
}
@Test
fun allTransformsDoesNotAffectOtherFields() {
val input = """{"tags":[],"title":"","meta":[],"other":"world"}"""
val result = parseJson<AllTransformsData>(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<AllTransformsData>(encoded)
assertEquals(data, decoded)
}
@Test
fun floatAsIntTruncatesFloat() {
val input = """{"count":3.9}"""
val result = parseJson<FloatIntData>(input)
assertEquals(3, result.count)
}
@Test
fun floatAsIntDecodesIntNormally() {
val input = """{"count":42}"""
val result = parseJson<FloatIntData>(input)
assertEquals(42, result.count)
}
@Test
fun floatAsIntHandlesNegativeFloat() {
val input = """{"count":-2.7}"""
val result = parseJson<FloatIntData>(input)
assertEquals(-2, result.count)
}
@Test
fun floatAsIntRoundtripsCorrectly() {
val data = FloatIntData(count = 7)
val encoded = data.toJson()
val decoded = parseJson<FloatIntData>(encoded)
assertEquals(data, decoded)
}
@Test
fun floatAsLongTruncatesFloat() {
val input = """{"timestamp":1234567890.9}"""
val result = parseJson<FloatLongData>(input)
assertEquals(1234567890L, result.timestamp)
}
@Test
fun floatAsLongDecodesLongNormally() {
val input = """{"timestamp":9999999999}"""
val result = parseJson<FloatLongData>(input)
assertEquals(9999999999L, result.timestamp)
}
@Test
fun floatAsLongHandlesNegativeFloat() {
val input = """{"timestamp":-100.6}"""
val result = parseJson<FloatLongData>(input)
assertEquals(-100L, result.timestamp)
}
@Test
fun floatAsLongRoundtripsCorrectly() {
val data = FloatLongData(timestamp = 1700000000L)
val encoded = data.toJson()
val decoded = parseJson<FloatLongData>(encoded)
assertEquals(data, decoded)
}
@Test
fun nullableStringDecodesEmptyStringAsNull() {
val input = """{"title":""}"""
val result = parseJson<NullableStringData>(input)
assertNull(result.title)
}
@Test
fun nullableStringDecodesNullAsNull() {
val input = """{"title":null}"""
val result = parseJson<NullableStringData>(input)
assertNull(result.title)
}
@Test
fun nullableStringKeepsNonEmptyString() {
val input = """{"title":"hello"}"""
val result = parseJson<NullableStringData>(input)
assertEquals("hello", result.title)
}
@Test
fun nullableStringRoundtripsCorrectly() {
val data = NullableStringData(title = "world")
val encoded = data.toJson()
val decoded = parseJson<NullableStringData>(encoded)
assertEquals(data, decoded)
}
}