@@ -4,70 +4,71 @@ import dev.kord.common.entity.Snowflake
44import dev.kord.voice.EncryptionMode
55import dev.kord.voice.SpeakingFlags
66import io.github.oshai.kotlinlogging.KotlinLogging
7- import kotlinx.serialization.ExperimentalSerializationApi
8- import kotlinx.serialization.SerialName
9- import kotlinx.serialization.Serializable
10- import kotlinx.serialization.builtins.nullable
7+ import kotlinx.serialization.*
8+ import kotlinx.serialization.descriptors.PrimitiveKind
9+ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
1110import kotlinx.serialization.descriptors.SerialDescriptor
1211import kotlinx.serialization.descriptors.buildClassSerialDescriptor
1312import kotlinx.serialization.encoding.CompositeDecoder
1413import kotlinx.serialization.encoding.Decoder
14+ import kotlinx.serialization.encoding.Encoder
15+ import kotlinx.serialization.encoding.decodeStructure
16+ import kotlinx.serialization.json.JsonDecoder
1517import kotlinx.serialization.json.JsonElement
1618import kotlinx.serialization.DeserializationStrategy as KDeserializationStrategy
1719
1820private val jsonLogger = KotlinLogging .logger { }
1921
2022public sealed class VoiceEvent {
2123 public object DeserializationStrategy : KDeserializationStrategy<VoiceEvent?> {
22- override val descriptor: SerialDescriptor = buildClassSerialDescriptor(" Event" ) {
23- element(" op" , OpCode .Serializer .descriptor)
24- element(" d" , JsonElement .serializer().descriptor)
24+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor(" dev.kord.voice.gateway. Event" ) {
25+ element(" op" , OpCode .serializer() .descriptor)
26+ element(" d" , JsonElement .serializer().descriptor, isOptional = true )
2527 }
2628
27- @OptIn(ExperimentalSerializationApi ::class )
28- override fun deserialize (decoder : Decoder ): VoiceEvent ? {
29+ override fun deserialize (decoder : Decoder ): VoiceEvent ? = decoder.decodeStructure(descriptor) {
2930 var op: OpCode ? = null
30- var data: VoiceEvent ? = null
31-
32- with (decoder.beginStructure(descriptor)) {
33- loop@ while (true ) {
34- when (val index = decodeElementIndex(descriptor)) {
35- CompositeDecoder .DECODE_DONE -> break @loop
36- 0 -> op = OpCode .Serializer .deserialize(decoder)
37- 1 -> data = when (op) {
38- OpCode .Hello -> decodeSerializableElement(
39- descriptor,
40- index,
41- Hello .serializer()
42- )
43- OpCode .HeartbeatAck -> {
44- HeartbeatAck (decodeInlineElement(HeartbeatAck .serializer().descriptor, 0 ).decodeLong())
45- }
46- OpCode .Ready -> decodeSerializableElement(descriptor, index, Ready .serializer())
47- OpCode .SessionDescription -> decodeSerializableElement(
48- descriptor,
49- index,
50- SessionDescription .serializer()
51- )
52- OpCode .Speaking -> decodeSerializableElement(descriptor, index, Speaking .serializer())
53- OpCode .Resumed -> Resumed
54- else -> {
55- val element = decodeNullableSerializableElement(
56- descriptor,
57- index,
58- JsonElement .serializer().nullable
59- )
60-
61- jsonLogger.debug { " Unknown event with Opcode $op : $element " }
62- null
63- }
64- }
65- }
31+ var d: JsonElement ? = null
32+ while (true ) {
33+ when (val index = decodeElementIndex(descriptor)) {
34+ 0 -> op = decodeSerializableElement(descriptor, index, OpCode .serializer(), op)
35+ 1 -> d = decodeSerializableElement(descriptor, index, JsonElement .serializer(), d)
36+ CompositeDecoder .DECODE_DONE -> break
37+ else -> throw SerializationException (" Unexpected index: $index " )
38+ }
39+ }
40+ when (op) {
41+ null ->
42+ throw @OptIn(ExperimentalSerializationApi ::class ) MissingFieldException (" op" , descriptor.serialName)
43+ OpCode .Ready -> decodeEvent(decoder, op, Ready .serializer(), d)
44+ OpCode .SessionDescription -> decodeEvent(decoder, op, SessionDescription .serializer(), d)
45+ OpCode .Speaking -> decodeEvent(decoder, op, Speaking .serializer(), d)
46+ OpCode .HeartbeatAck -> decodeEvent(decoder, op, HeartbeatAck .serializer(), d)
47+ OpCode .Hello -> decodeEvent(decoder, op, Hello .serializer(), d)
48+ OpCode .Resumed -> {
49+ // ignore the d field, Resumed is supposed to have null here:
50+ // https://discord.com/developers/docs/topics/voice-connections#resuming-voice-connection-example-resumed-payload
51+ Resumed
52+ }
53+ OpCode .Identify , OpCode .SelectProtocol , OpCode .Heartbeat , OpCode .Resume , OpCode .ClientDisconnect ,
54+ OpCode .Unknown ,
55+ -> {
56+ jsonLogger.debug { " Unknown voice gateway event with opcode $op : $d " }
57+ null
6658 }
67- endStructure(descriptor)
68- return data
6959 }
7060 }
61+
62+ private fun <T > decodeEvent (
63+ decoder : Decoder ,
64+ op : OpCode ,
65+ deserializer : KDeserializationStrategy <T >,
66+ d : JsonElement ? ,
67+ ): T {
68+ requireNotNull(d) { " Voice gateway event is missing 'd' field for opcode $op " }
69+ // this cast will always succeed, otherwise decoder couldn't have decoded d
70+ return (decoder as JsonDecoder ).json.decodeFromJsonElement(deserializer, d)
71+ }
7172 }
7273}
7374
@@ -87,8 +88,14 @@ public data class Hello(
8788 val heartbeatInterval : Double
8889) : VoiceEvent()
8990
90- @Serializable
91- public data class HeartbeatAck (val nonce : Long ) : VoiceEvent()
91+ @Serializable(with = HeartbeatAck .Serializer ::class )
92+ public data class HeartbeatAck (val nonce : Long ) : VoiceEvent() {
93+ internal object Serializer : KSerializer<HeartbeatAck> {
94+ override val descriptor = PrimitiveSerialDescriptor (" dev.kord.voice.gateway.HeartbeatAck" , PrimitiveKind .LONG )
95+ override fun serialize (encoder : Encoder , value : HeartbeatAck ) = encoder.encodeLong(value.nonce)
96+ override fun deserialize (decoder : Decoder ) = HeartbeatAck (nonce = decoder.decodeLong())
97+ }
98+ }
9299
93100@Serializable
94101public data class SessionDescription (
@@ -105,8 +112,23 @@ public data class Speaking(
105112 val speaking : SpeakingFlags
106113) : VoiceEvent()
107114
108- @Serializable
109- public object Resumed : VoiceEvent()
115+ public object Resumed : VoiceEvent() {
116+ @Deprecated(
117+ " 'Resumed' is no longer serializable, deserialize it with 'VoiceEvent.DeserializationStrategy' instead. " +
118+ " Deprecated without a replacement." ,
119+ level = DeprecationLevel .WARNING ,
120+ )
121+ public fun serializer (): KSerializer <Resumed > = serializer
122+
123+ private val serializer: KSerializer <Resumed > by lazy(LazyThreadSafetyMode .PUBLICATION ) {
124+ @Suppress(" INVISIBLE_MEMBER" )
125+ kotlinx.serialization.internal.ObjectSerializer (
126+ serialName = " dev.kord.voice.gateway.Resumed" ,
127+ objectInstance = Resumed ,
128+ classAnnotations = arrayOf(),
129+ )
130+ }
131+ }
110132
111133public sealed class Close : VoiceEvent () {
112134 /* *
0 commit comments