diff --git a/core/src/commonMain/kotlin/builder/kord/KordBuilderUtil.kt b/core/src/commonMain/kotlin/builder/kord/KordBuilderUtil.kt index 0f0d9e0dd75..fdd064726c7 100644 --- a/core/src/commonMain/kotlin/builder/kord/KordBuilderUtil.kt +++ b/core/src/commonMain/kotlin/builder/kord/KordBuilderUtil.kt @@ -1,22 +1,37 @@ package dev.kord.core.builder.kord import dev.kord.common.annotation.KordInternal +import dev.kord.common.annotation.KordUnsafe import dev.kord.common.entity.Snowflake import dev.kord.common.http.HttpEngine +import dev.kord.gateway.WebSocketCompression import io.ktor.client.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.websocket.* +import io.ktor.serialization.kotlinx.* import io.ktor.serialization.kotlinx.json.* import io.ktor.util.* import kotlinx.serialization.json.Json +@OptIn(KordUnsafe::class) internal fun HttpClientConfig<*>.defaultConfig() { expectSuccess = false + val json = Json { + encodeDefaults = false + allowStructuredMapKeys = true + ignoreUnknownKeys = true + isLenient = true + } install(ContentNegotiation) { - json() + json(json) + } + install(WebSockets) { + contentConverter = KotlinxWebsocketSerializationConverter(json) + extensions { + install(WebSocketCompression) + } } - install(WebSockets) } /** @suppress */ @@ -26,18 +41,8 @@ public fun HttpClient?.configure(): HttpClient { defaultConfig() } - val json = Json { - encodeDefaults = false - allowStructuredMapKeys = true - ignoreUnknownKeys = true - isLenient = true - } - return HttpClient(HttpEngine) { defaultConfig() - install(ContentNegotiation) { - json(json) - } } } diff --git a/gateway/api/gateway.api b/gateway/api/gateway.api index ce80685b846..10424b22414 100644 --- a/gateway/api/gateway.api +++ b/gateway/api/gateway.api @@ -199,6 +199,11 @@ public final class dev/kord/gateway/Close$ZombieConnection : dev/kord/gateway/Cl } public abstract class dev/kord/gateway/Command { + public static final field Companion Ldev/kord/gateway/Command$Companion; +} + +public final class dev/kord/gateway/Command$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class dev/kord/gateway/Command$Heartbeat : dev/kord/gateway/Command { @@ -212,8 +217,10 @@ public final class dev/kord/gateway/Command$Heartbeat : dev/kord/gateway/Command public fun toString ()Ljava/lang/String; } -public final class dev/kord/gateway/Command$SerializationStrategy : kotlinx/serialization/SerializationStrategy { +public final class dev/kord/gateway/Command$SerializationStrategy : kotlinx/serialization/KSerializer { public static final field INSTANCE Ldev/kord/gateway/Command$SerializationStrategy; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/kord/gateway/Command; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/kord/gateway/Command;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V @@ -598,13 +605,20 @@ public abstract class dev/kord/gateway/DispatchEvent : dev/kord/gateway/Event { } public abstract class dev/kord/gateway/Event { + public static final field Companion Ldev/kord/gateway/Event$Companion; } -public final class dev/kord/gateway/Event$DeserializationStrategy : kotlinx/serialization/DeserializationStrategy { +public final class dev/kord/gateway/Event$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/kord/gateway/Event$DeserializationStrategy : kotlinx/serialization/KSerializer { public static final field INSTANCE Ldev/kord/gateway/Event$DeserializationStrategy; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/kord/gateway/Event; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/kord/gateway/Event;)Ljava/lang/Void; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } public abstract interface class dev/kord/gateway/Gateway : kotlinx/coroutines/CoroutineScope { @@ -1948,6 +1962,26 @@ public final class dev/kord/gateway/VoiceStateUpdate : dev/kord/gateway/Dispatch public fun toString ()Ljava/lang/String; } +public final class dev/kord/gateway/WebSocketCompression : io/ktor/websocket/WebSocketExtension { + public static final field Companion Ldev/kord/gateway/WebSocketCompression$Companion; + public fun ()V + public fun clientNegotiation (Ljava/util/List;)Z + public fun getFactory ()Lio/ktor/websocket/WebSocketExtensionFactory; + public fun getProtocols ()Ljava/util/List; + public fun processIncomingFrame (Lio/ktor/websocket/Frame;)Lio/ktor/websocket/Frame; + public fun processOutgoingFrame (Lio/ktor/websocket/Frame;)Lio/ktor/websocket/Frame; + public fun serverNegotiation (Ljava/util/List;)Ljava/util/List; +} + +public final class dev/kord/gateway/WebSocketCompression$Companion : io/ktor/websocket/WebSocketExtensionFactory { + public fun getKey ()Lio/ktor/util/AttributeKey; + public fun getRsv1 ()Z + public fun getRsv2 ()Z + public fun getRsv3 ()Z + public fun install (Lkotlin/jvm/functions/Function1;)Ldev/kord/gateway/WebSocketCompression; + public synthetic fun install (Lkotlin/jvm/functions/Function1;)Lio/ktor/websocket/WebSocketExtension; +} + public final class dev/kord/gateway/WebhooksUpdate : dev/kord/gateway/DispatchEvent { public fun (Ldev/kord/common/entity/DiscordWebhooksUpdateData;Ljava/lang/Integer;)V public final fun component1 ()Ldev/kord/common/entity/DiscordWebhooksUpdateData; diff --git a/gateway/src/commonMain/kotlin/Command.kt b/gateway/src/commonMain/kotlin/Command.kt index e90dda4efb6..8f088c1f153 100644 --- a/gateway/src/commonMain/kotlin/Command.kt +++ b/gateway/src/commonMain/kotlin/Command.kt @@ -8,26 +8,31 @@ import dev.kord.common.serialization.InstantInEpochMillisecondsSerializer import kotlinx.atomicfu.atomic import kotlinx.datetime.Instant import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.SerializationStrategy as KSerializationStrategy +@Serializable(with = Command.SerializationStrategy::class) public sealed class Command { public data class Heartbeat(val sequenceNumber: Int?) : Command() - public object SerializationStrategy : KSerializationStrategy { + public object SerializationStrategy : KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Command") { element("op", OpCode.serializer().descriptor) element("d", JsonElement.serializer().descriptor) } + override fun deserialize(decoder: Decoder): Command = + TODO("Deserializing gateway commands is not supported yet") + @OptIn(PrivilegedIntent::class) override fun serialize(encoder: Encoder, value: Command) { val composite = encoder.beginStructure(descriptor) diff --git a/gateway/src/commonMain/kotlin/Compression.kt b/gateway/src/commonMain/kotlin/Compression.kt new file mode 100644 index 00000000000..4da49351fc0 --- /dev/null +++ b/gateway/src/commonMain/kotlin/Compression.kt @@ -0,0 +1,55 @@ +package dev.kord.gateway + +import dev.kord.common.annotation.KordUnsafe +import io.ktor.util.* +import io.ktor.websocket.* + +/** + * [WebSocketExtension] inflating incoming websocket requests using `zlib`. + * + * *Note:** Normally you don't need this and this is configured by Kord automatically, however, if you want to use + * a custom HTTP client, you might need to add this, don't use it if you don't use what you're doing + */ +@KordUnsafe +public class WebSocketCompression : WebSocketExtension { + /** + * https://discord.com/developers/docs/topics/gateway#transport-compression + * + * > Every connection to the gateway should use its own unique zlib context. + * + * https://api.ktor.io/ktor-shared/ktor-websockets/io.ktor.websocket/-web-socket-extension/index.html + * > A WebSocket extension instance. This instance is created for each WebSocket request, + * for every installed extension by WebSocketExtensionFactory. + */ + private val inflater = Inflater() + + override val factory: WebSocketExtensionFactory> + get() = Companion + override val protocols: List + get() = emptyList() + + override fun clientNegotiation(negotiatedProtocols: List): Boolean = true + + override fun processIncomingFrame(frame: Frame): Frame { + return if (frame is Frame.Binary) { + with(inflater) { Frame.Text(frame.inflateData()) } + } else { + frame + } + } + + // Discord doesn't support deflating of gateway commands + override fun processOutgoingFrame(frame: Frame): Frame = frame + + override fun serverNegotiation(requestedProtocols: List): List = + requestedProtocols + + public companion object : WebSocketExtensionFactory { + override val key: AttributeKey = AttributeKey("WebSocketCompression") + override val rsv1: Boolean = false + override val rsv2: Boolean = false + override val rsv3: Boolean = false + + override fun install(config: Unit.() -> Unit): WebSocketCompression = WebSocketCompression() + } +} diff --git a/gateway/src/commonMain/kotlin/DefaultGateway.kt b/gateway/src/commonMain/kotlin/DefaultGateway.kt index 42ad204cfb3..25fb9f85fa7 100644 --- a/gateway/src/commonMain/kotlin/DefaultGateway.kt +++ b/gateway/src/commonMain/kotlin/DefaultGateway.kt @@ -16,12 +16,12 @@ import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import kotlinx.serialization.json.Json import mu.KotlinLogging import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -76,13 +76,6 @@ public class DefaultGateway(private val data: DefaultGatewayData) : Gateway { private val handshakeHandler: HandshakeHandler - private lateinit var inflater: Inflater - - private val jsonParser = Json { - ignoreUnknownKeys = true - isLenient = true - } - private val stateMutex = Mutex() init { @@ -110,14 +103,9 @@ public class DefaultGateway(private val data: DefaultGatewayData) : Gateway { } defaultGatewayLogger.trace { "opening gateway connection to $gatewayUrl" } - socket = data.client.webSocketSession { url(gatewayUrl) } - - /** - * https://discord.com/developers/docs/topics/gateway#transport-compression - * - * > Every connection to the gateway should use its own unique zlib context. - */ - inflater = Inflater() + socket = data.client.webSocketSession { + url(gatewayUrl) + } } catch (exception: Exception) { defaultGatewayLogger.error(exception) if (exception.isTimeout()) { @@ -167,31 +155,12 @@ public class DefaultGateway(private val data: DefaultGatewayData) : Gateway { } + @OptIn(ExperimentalCoroutinesApi::class) private suspend fun readSocket() { - socket.incoming.asFlow().buffer(Channel.UNLIMITED).collect { - when (it) { - is Frame.Binary, is Frame.Text -> read(it) - else -> { /*ignore*/ - } - } - } - } - - private suspend fun read(frame: Frame) { - defaultGatewayLogger.trace { "Received raw frame: $frame" } - val json = when { - compression -> with(inflater) { frame.inflateData() } - else -> frame.data.decodeToString() - } - - try { - defaultGatewayLogger.trace { "Gateway <<< $json" } - val event = jsonParser.decodeFromString(Event.DeserializationStrategy, json) ?: return + while (!socket.incoming.isClosedForReceive) { + val event = socket.receiveDeserialized() data.eventFlow.emit(event) - } catch (exception: Exception) { - defaultGatewayLogger.error(exception) } - } private suspend fun handleClose() { @@ -209,6 +178,7 @@ public class DefaultGateway(private val data: DefaultGatewayData) : Gateway { state.update { State.Stopped } throw IllegalStateException("Gateway closed: ${reason.code} ${reason.message}") } + discordReason.resetSession -> { setStopped() } @@ -220,14 +190,6 @@ public class DefaultGateway(private val data: DefaultGatewayData) : Gateway { state.update { State.Running(true) } } - private fun ReceiveChannel.asFlow() = flow { - try { - for (value in this@asFlow) emit(value) - } catch (ignore: CancellationException) { - //reading was stopped from somewhere else, ignore - } - } - override suspend fun stop() { check(state.value !is State.Detached) { "The resources of this gateway are detached, create another one" } data.eventFlow.emit(Close.UserClose) @@ -268,14 +230,7 @@ public class DefaultGateway(private val data: DefaultGatewayData) : Gateway { private suspend fun sendUnsafe(command: Command) { data.sendRateLimiter.consume() - val json = Json.encodeToString(Command.SerializationStrategy, command) - if (command is Identify) { - defaultGatewayLogger.trace { - val copy = command.copy(token = "token") - "Gateway >>> ${Json.encodeToString(Command.SerializationStrategy, copy)}" - } - } else defaultGatewayLogger.trace { "Gateway >>> $json" } - socket.send(Frame.Text(json)) + socket.sendSerialized(command) } @OptIn(ExperimentalCoroutinesApi::class) diff --git a/gateway/src/commonMain/kotlin/DefaultGatewayBuilder.kt b/gateway/src/commonMain/kotlin/DefaultGatewayBuilder.kt index 080e2000761..f598fb5aaad 100644 --- a/gateway/src/commonMain/kotlin/DefaultGatewayBuilder.kt +++ b/gateway/src/commonMain/kotlin/DefaultGatewayBuilder.kt @@ -1,6 +1,7 @@ package dev.kord.gateway import dev.kord.common.KordConfiguration +import dev.kord.common.annotation.KordUnsafe import dev.kord.common.http.HttpEngine import dev.kord.common.ratelimit.IntervalRateLimiter import dev.kord.common.ratelimit.RateLimiter @@ -8,14 +9,14 @@ import dev.kord.gateway.ratelimit.IdentifyRateLimiter import dev.kord.gateway.retry.LinearRetry import dev.kord.gateway.retry.Retry import io.ktor.client.* -import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.websocket.* import io.ktor.client.request.* import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* +import io.ktor.serialization.kotlinx.* import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.serialization.json.Json import kotlin.time.Duration.Companion.seconds public class DefaultGatewayBuilder { @@ -28,11 +29,15 @@ public class DefaultGatewayBuilder { public var dispatcher: CoroutineDispatcher = Dispatchers.Default public var eventFlow: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE) + @OptIn(KordUnsafe::class) public fun build(): DefaultGateway { val client = client ?: HttpClient(HttpEngine) { - install(WebSockets) - install(ContentNegotiation) { - json() + install(WebSockets) { + contentConverter = KotlinxWebsocketSerializationConverter(Json) + + extensions { + install(WebSocketCompression) + } } } val retry = reconnectRetry ?: LinearRetry(2.seconds, 20.seconds, 10) diff --git a/gateway/src/commonMain/kotlin/Event.kt b/gateway/src/commonMain/kotlin/Event.kt index a47349a2a20..c60f22f3e42 100644 --- a/gateway/src/commonMain/kotlin/Event.kt +++ b/gateway/src/commonMain/kotlin/Event.kt @@ -22,7 +22,6 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import mu.KotlinLogging -import kotlinx.serialization.DeserializationStrategy as KDeserializationStrategy private val jsonLogger = KotlinLogging.logger { } @@ -30,8 +29,9 @@ public sealed class DispatchEvent : Event() { public abstract val sequence: Int? } +@Serializable(with = Event.DeserializationStrategy::class) public sealed class Event { - public object DeserializationStrategy : KDeserializationStrategy { + public object DeserializationStrategy : KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Event") { element("op", OpCode.serializer().descriptor) element("t", String.serializer().descriptor, isOptional = true) @@ -39,6 +39,9 @@ public sealed class Event { element("d", JsonObject.serializer().descriptor, isOptional = true) } + override fun serialize(encoder: Encoder, value: Event?): Nothing = + TODO("Serialization of events is not supported yet") + @OptIn(ExperimentalSerializationApi::class) override fun deserialize(decoder: Decoder): Event? { var op: OpCode? = null @@ -59,29 +62,32 @@ public sealed class Event { else -> {} } } + 1 -> eventName = decodeNullableSerializableElement(descriptor, index, String.serializer().nullable) + 2 -> sequence = decodeNullableSerializableElement(descriptor, index, Int.serializer().nullable) 3 -> data = when (op) { OpCode.Dispatch -> getByDispatchEvent(index, this, eventName, sequence) OpCode.Heartbeat -> decodeSerializableElement(descriptor, index, Heartbeat.serializer()) OpCode.HeartbeatACK -> { - @Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION") - decodeNullableSerializableElement(descriptor, index, NothingSerializer()) + @Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION") decodeNullableSerializableElement( + descriptor, + index, + NothingSerializer() + ) HeartbeatACK } + OpCode.InvalidSession -> decodeSerializableElement( - descriptor, - index, - InvalidSession.serializer() + descriptor, index, InvalidSession.serializer() ) + OpCode.Hello -> decodeSerializableElement(descriptor, index, Hello.serializer()) //some events contain undocumented data fields, we'll only assume an unknown opcode with no data to be an error else -> if (data == null) { val element = decodeNullableSerializableElement( - descriptor, - index, - JsonElement.serializer().nullable + descriptor, index, JsonElement.serializer().nullable ) error("Unknown 'd' field for Op code ${op?.code}: $element") } else { @@ -105,28 +111,34 @@ public sealed class Event { decoder.decodeNullableSerializableElement(descriptor, index, JsonElement.serializer().nullable) null //https://github.com/kordlib/kord/issues/42 } + "RESUMED" -> { decoder.decodeNullableSerializableElement(descriptor, index, JsonElement.serializer().nullable) Resumed(sequence) } + "READY" -> Ready(decoder.decodeSerializableElement(descriptor, index, ReadyData.serializer()), sequence) "APPLICATION_COMMAND_PERMISSIONS_UPDATE" -> ApplicationCommandPermissionsUpdate( decoder.decodeSerializableElement( descriptor, index, DiscordGuildApplicationCommandPermissions.serializer() ), sequence ) + "AUTO_MODERATION_RULE_CREATE" -> AutoModerationRuleCreate( rule = decoder.decodeSerializableElement(descriptor, index, DiscordAutoModerationRule.serializer()), sequence, ) + "AUTO_MODERATION_RULE_UPDATE" -> AutoModerationRuleUpdate( rule = decoder.decodeSerializableElement(descriptor, index, DiscordAutoModerationRule.serializer()), sequence, ) + "AUTO_MODERATION_RULE_DELETE" -> AutoModerationRuleDelete( rule = decoder.decodeSerializableElement(descriptor, index, DiscordAutoModerationRule.serializer()), sequence, ) + "AUTO_MODERATION_ACTION_EXECUTION" -> AutoModerationActionExecution( actionExecution = decoder.decodeSerializableElement( descriptor, @@ -135,104 +147,91 @@ public sealed class Event { ), sequence, ) + "CHANNEL_CREATE" -> ChannelCreate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordChannel.serializer() + descriptor, index, DiscordChannel.serializer() ), sequence ) + "CHANNEL_UPDATE" -> ChannelUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordChannel.serializer() + descriptor, index, DiscordChannel.serializer() ), sequence ) + "CHANNEL_DELETE" -> ChannelDelete( decoder.decodeSerializableElement( - descriptor, - index, - DiscordChannel.serializer() + descriptor, index, DiscordChannel.serializer() ), sequence ) + "CHANNEL_PINS_UPDATE" -> ChannelPinsUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordPinsUpdateData.serializer() + descriptor, index, DiscordPinsUpdateData.serializer() ), sequence ) + "TYPING_START" -> TypingStart( decoder.decodeSerializableElement( - descriptor, - index, - DiscordTyping.serializer() + descriptor, index, DiscordTyping.serializer() ), sequence ) + "GUILD_AUDIT_LOG_ENTRY_CREATE" -> GuildAuditLogEntryCreate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordAuditLogEntry.serializer() + descriptor, index, DiscordAuditLogEntry.serializer() ), sequence ) + "GUILD_CREATE" -> GuildCreate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordGuild.serializer() + descriptor, index, DiscordGuild.serializer() ), sequence ) + "GUILD_UPDATE" -> GuildUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordGuild.serializer() + descriptor, index, DiscordGuild.serializer() ), sequence ) + "GUILD_DELETE" -> GuildDelete( decoder.decodeSerializableElement( - descriptor, - index, - DiscordUnavailableGuild.serializer() + descriptor, index, DiscordUnavailableGuild.serializer() ), sequence ) + "GUILD_BAN_ADD" -> GuildBanAdd( decoder.decodeSerializableElement( - descriptor, - index, - DiscordGuildBan.serializer() + descriptor, index, DiscordGuildBan.serializer() ), sequence ) + "GUILD_BAN_REMOVE" -> GuildBanRemove( decoder.decodeSerializableElement( - descriptor, - index, - DiscordGuildBan.serializer() + descriptor, index, DiscordGuildBan.serializer() ), sequence ) + "GUILD_EMOJIS_UPDATE" -> GuildEmojisUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordUpdatedEmojis.serializer() + descriptor, index, DiscordUpdatedEmojis.serializer() ), sequence ) + "GUILD_INTEGRATIONS_UPDATE" -> GuildIntegrationsUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordGuildIntegrations.serializer() + descriptor, index, DiscordGuildIntegrations.serializer() ), sequence ) + "INTEGRATION_CREATE" -> IntegrationCreate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordIntegration.serializer() + descriptor, index, DiscordIntegration.serializer() ), sequence ) + "INTEGRATION_DELETE" -> IntegrationDelete( decoder.decodeSerializableElement( descriptor, @@ -240,177 +239,151 @@ public sealed class Event { DiscordIntegrationDelete.serializer(), ), sequence ) + "INTEGRATION_UPDATE" -> IntegrationUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordIntegration.serializer() + descriptor, index, DiscordIntegration.serializer() ), sequence ) + "GUILD_MEMBER_ADD" -> GuildMemberAdd( decoder.decodeSerializableElement( - descriptor, - index, - DiscordAddedGuildMember.serializer() + descriptor, index, DiscordAddedGuildMember.serializer() ), sequence ) + "GUILD_MEMBER_REMOVE" -> GuildMemberRemove( decoder.decodeSerializableElement( - descriptor, - index, - DiscordRemovedGuildMember.serializer() + descriptor, index, DiscordRemovedGuildMember.serializer() ), sequence ) + "GUILD_MEMBER_UPDATE" -> GuildMemberUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordUpdatedGuildMember.serializer() + descriptor, index, DiscordUpdatedGuildMember.serializer() ), sequence ) + "GUILD_ROLE_CREATE" -> GuildRoleCreate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordGuildRole.serializer() + descriptor, index, DiscordGuildRole.serializer() ), sequence ) + "GUILD_ROLE_UPDATE" -> GuildRoleUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordGuildRole.serializer() + descriptor, index, DiscordGuildRole.serializer() ), sequence ) + "GUILD_ROLE_DELETE" -> GuildRoleDelete( decoder.decodeSerializableElement( - descriptor, - index, - DiscordDeletedGuildRole.serializer() + descriptor, index, DiscordDeletedGuildRole.serializer() ), sequence ) + "GUILD_MEMBERS_CHUNK" -> GuildMembersChunk( decoder.decodeSerializableElement( - descriptor, - index, - GuildMembersChunkData.serializer() + descriptor, index, GuildMembersChunkData.serializer() ), sequence ) "INVITE_CREATE" -> InviteCreate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordCreatedInvite.serializer() + descriptor, index, DiscordCreatedInvite.serializer() ), sequence ) + "INVITE_DELETE" -> InviteDelete( decoder.decodeSerializableElement( - descriptor, - index, - DiscordDeletedInvite.serializer() + descriptor, index, DiscordDeletedInvite.serializer() ), sequence ) "MESSAGE_CREATE" -> MessageCreate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordMessage.serializer() + descriptor, index, DiscordMessage.serializer() ), sequence ) + "MESSAGE_UPDATE" -> MessageUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordPartialMessage.serializer() + descriptor, index, DiscordPartialMessage.serializer() ), sequence ) + "MESSAGE_DELETE" -> MessageDelete( decoder.decodeSerializableElement( - descriptor, - index, - DeletedMessage.serializer() + descriptor, index, DeletedMessage.serializer() ), sequence ) + "MESSAGE_DELETE_BULK" -> MessageDeleteBulk( decoder.decodeSerializableElement( - descriptor, - index, - BulkDeleteData.serializer() + descriptor, index, BulkDeleteData.serializer() ), sequence ) + "MESSAGE_REACTION_ADD" -> MessageReactionAdd( decoder.decodeSerializableElement( - descriptor, - index, - MessageReactionAddData.serializer() + descriptor, index, MessageReactionAddData.serializer() ), sequence ) + "MESSAGE_REACTION_REMOVE" -> MessageReactionRemove( decoder.decodeSerializableElement( - descriptor, - index, - MessageReactionRemoveData.serializer() + descriptor, index, MessageReactionRemoveData.serializer() ), sequence ) + "MESSAGE_REACTION_REMOVE_EMOJI" -> MessageReactionRemoveEmoji( decoder.decodeSerializableElement( - descriptor, - index, - DiscordRemovedEmoji.serializer() + descriptor, index, DiscordRemovedEmoji.serializer() ), sequence ) "MESSAGE_REACTION_REMOVE_ALL" -> MessageReactionRemoveAll( decoder.decodeSerializableElement( - descriptor, - index, - AllRemovedMessageReactions.serializer() + descriptor, index, AllRemovedMessageReactions.serializer() ), sequence ) + "PRESENCE_UPDATE" -> PresenceUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordPresenceUpdate.serializer() + descriptor, index, DiscordPresenceUpdate.serializer() ), sequence ) + "USER_UPDATE" -> UserUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordUser.serializer() + descriptor, index, DiscordUser.serializer() ), sequence ) + "VOICE_STATE_UPDATE" -> VoiceStateUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordVoiceState.serializer() + descriptor, index, DiscordVoiceState.serializer() ), sequence ) + "VOICE_SERVER_UPDATE" -> VoiceServerUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordVoiceServerUpdateData.serializer() + descriptor, index, DiscordVoiceServerUpdateData.serializer() ), sequence ) + "WEBHOOKS_UPDATE" -> WebhooksUpdate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordWebhooksUpdateData.serializer() + descriptor, index, DiscordWebhooksUpdateData.serializer() ), sequence ) + "INTERACTION_CREATE" -> InteractionCreate( decoder.decodeSerializableElement( - descriptor, - index, - DiscordInteraction.serializer() + descriptor, index, DiscordInteraction.serializer() ), sequence ) + "APPLICATION_COMMAND_CREATE" -> ApplicationCommandCreate( decoder.decodeSerializableElement(descriptor, index, DiscordApplicationCommand.serializer()), sequence @@ -427,30 +400,25 @@ public sealed class Event { ) "THREAD_CREATE" -> ThreadCreate( - decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), - sequence + decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), sequence ) "THREAD_DELETE" -> ThreadDelete( - decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), - sequence + decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), sequence ) "THREAD_UPDATE" -> ThreadUpdate( - decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), - sequence + decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), sequence ) "THREAD_LIST_SYNC" -> ThreadListSync( - decoder.decodeSerializableElement(descriptor, index, DiscordThreadListSync.serializer()), - sequence + decoder.decodeSerializableElement(descriptor, index, DiscordThreadListSync.serializer()), sequence ) "THREAD_MEMBER_UPDATE" -> ThreadMemberUpdate( - decoder.decodeSerializableElement(descriptor, index, DiscordThreadMember.serializer()), - sequence + decoder.decodeSerializableElement(descriptor, index, DiscordThreadMember.serializer()), sequence ) @@ -463,29 +431,31 @@ public sealed class Event { decoder.decodeSerializableElement(descriptor, index, DiscordGuildScheduledEvent.serializer()), sequence ) + "GUILD_SCHEDULED_EVENT_UPDATE" -> GuildScheduledEventUpdate( decoder.decodeSerializableElement(descriptor, index, DiscordGuildScheduledEvent.serializer()), sequence ) + "GUILD_SCHEDULED_EVENT_DELETE" -> GuildScheduledEventDelete( decoder.decodeSerializableElement(descriptor, index, DiscordGuildScheduledEvent.serializer()), sequence ) + "GUILD_SCHEDULED_EVENT_USER_ADD" -> GuildScheduledEventUserAdd( data = decoder.decodeSerializableElement( descriptor, index, GuildScheduledEventUserMetadata.serializer(), - ), - sequence + ), sequence ) + "GUILD_SCHEDULED_EVENT_USER_REMOVE" -> GuildScheduledEventUserRemove( data = decoder.decodeSerializableElement( descriptor, index, GuildScheduledEventUserMetadata.serializer(), - ), - sequence + ), sequence ) @@ -554,31 +524,23 @@ public object Reconnect : Event() @Serializable public data class Hello( - @SerialName("heartbeat_interval") - val heartbeatInterval: Int, + @SerialName("heartbeat_interval") val heartbeatInterval: Int, ) : Event() public data class Ready(val data: ReadyData, override val sequence: Int?) : DispatchEvent() @Serializable public data class ReadyData( - @SerialName("v") - val version: Int, + @SerialName("v") val version: Int, val user: DiscordUser, - @SerialName("private_channels") - val privateChannels: List, + @SerialName("private_channels") val privateChannels: List, val guilds: List, - @SerialName("session_id") - val sessionId: String, - @SerialName("resume_gateway_url") - val resumeGatewayUrl: String, - @SerialName("geo_ordered_rtc_regions") - val geoOrderedRtcRegions: Optional = Optional.Missing(), - @SerialName("guild_hashes") - val guildHashes: Optional = Optional.Missing(), + @SerialName("session_id") val sessionId: String, + @SerialName("resume_gateway_url") val resumeGatewayUrl: String, + @SerialName("geo_ordered_rtc_regions") val geoOrderedRtcRegions: Optional = Optional.Missing(), + @SerialName("guild_hashes") val guildHashes: Optional = Optional.Missing(), val application: Optional = Optional.Missing(), - @SerialName("_trace") - val traces: List, + @SerialName("_trace") val traces: List, val shard: Optional = Optional.Missing(), ) @@ -617,8 +579,7 @@ public data class InvalidSession(val resumable: Boolean) : Event() { } public data class ApplicationCommandPermissionsUpdate( - val permissions: DiscordGuildApplicationCommandPermissions, - override val sequence: Int? + val permissions: DiscordGuildApplicationCommandPermissions, override val sequence: Int? ) : DispatchEvent() public data class AutoModerationRuleCreate(val rule: DiscordAutoModerationRule, override val sequence: Int?) : @@ -637,26 +598,17 @@ public data class AutoModerationActionExecution( @Serializable public data class DiscordAutoModerationActionExecution( - @SerialName("guild_id") - val guildId: Snowflake, + @SerialName("guild_id") val guildId: Snowflake, val action: DiscordAutoModerationAction, - @SerialName("rule_id") - val ruleId: Snowflake, - @SerialName("rule_trigger_type") - val ruleTriggerType: AutoModerationRuleTriggerType, - @SerialName("user_id") - val userId: Snowflake, - @SerialName("channel_id") - val channelId: OptionalSnowflake = OptionalSnowflake.Missing, - @SerialName("message_id") - val messageId: OptionalSnowflake = OptionalSnowflake.Missing, - @SerialName("alert_system_message_id") - val alertSystemMessageId: OptionalSnowflake = OptionalSnowflake.Missing, + @SerialName("rule_id") val ruleId: Snowflake, + @SerialName("rule_trigger_type") val ruleTriggerType: AutoModerationRuleTriggerType, + @SerialName("user_id") val userId: Snowflake, + @SerialName("channel_id") val channelId: OptionalSnowflake = OptionalSnowflake.Missing, + @SerialName("message_id") val messageId: OptionalSnowflake = OptionalSnowflake.Missing, + @SerialName("alert_system_message_id") val alertSystemMessageId: OptionalSnowflake = OptionalSnowflake.Missing, val content: String, - @SerialName("matched_keyword") - val matchedKeyword: String?, - @SerialName("matched_content") - val matchedContent: String?, + @SerialName("matched_keyword") val matchedKeyword: String?, + @SerialName("matched_content") val matchedContent: String?, ) public data class ChannelCreate(val channel: DiscordChannel, override val sequence: Int?) : DispatchEvent() @@ -665,7 +617,9 @@ public data class ChannelDelete(val channel: DiscordChannel, override val sequen public data class ChannelPinsUpdate(val pins: DiscordPinsUpdateData, override val sequence: Int?) : DispatchEvent() public data class TypingStart(val data: DiscordTyping, override val sequence: Int?) : DispatchEvent() -public data class GuildAuditLogEntryCreate(val entry: DiscordAuditLogEntry, override val sequence: Int?): DispatchEvent() +public data class GuildAuditLogEntryCreate(val entry: DiscordAuditLogEntry, override val sequence: Int?) : + DispatchEvent() + public data class GuildCreate(val guild: DiscordGuild, override val sequence: Int?) : DispatchEvent() public data class GuildUpdate(val guild: DiscordGuild, override val sequence: Int?) : DispatchEvent() public data class GuildDelete(val guild: DiscordUnavailableGuild, override val sequence: Int?) : DispatchEvent() @@ -674,12 +628,13 @@ public data class GuildBanRemove(val ban: DiscordGuildBan, override val sequence public data class GuildEmojisUpdate(val emoji: DiscordUpdatedEmojis, override val sequence: Int?) : DispatchEvent() public data class GuildIntegrationsUpdate(val integrations: DiscordGuildIntegrations, override val sequence: Int?) : DispatchEvent() + public data class IntegrationDelete(val integration: DiscordIntegrationDelete, override val sequence: Int?) : DispatchEvent() -public data class IntegrationCreate(val integration: DiscordIntegration, override val sequence: Int?) : - DispatchEvent() -public data class IntegrationUpdate(val integration: DiscordIntegration, override val sequence: Int?) : - DispatchEvent() + +public data class IntegrationCreate(val integration: DiscordIntegration, override val sequence: Int?) : DispatchEvent() + +public data class IntegrationUpdate(val integration: DiscordIntegration, override val sequence: Int?) : DispatchEvent() public data class GuildMemberAdd(val member: DiscordAddedGuildMember, override val sequence: Int?) : DispatchEvent() public data class GuildMemberRemove(val member: DiscordRemovedGuildMember, override val sequence: Int?) : @@ -705,33 +660,23 @@ public data class InviteDelete(val invite: DiscordDeletedInvite, override val se @Serializable public data class DiscordDeletedInvite( - @SerialName("channel_id") - val channelId: Snowflake, - @SerialName("guild_id") - val guildId: OptionalSnowflake = OptionalSnowflake.Missing, + @SerialName("channel_id") val channelId: Snowflake, + @SerialName("guild_id") val guildId: OptionalSnowflake = OptionalSnowflake.Missing, val code: String, ) @Serializable public data class DiscordCreatedInvite( - @SerialName("channel_id") - val channelId: Snowflake, + @SerialName("channel_id") val channelId: Snowflake, val code: String, - @SerialName("created_at") - val createdAt: Instant, - @SerialName("guild_id") - val guildId: OptionalSnowflake = OptionalSnowflake.Missing, + @SerialName("created_at") val createdAt: Instant, + @SerialName("guild_id") val guildId: OptionalSnowflake = OptionalSnowflake.Missing, val inviter: Optional = Optional.Missing(), - @SerialName("max_age") - val maxAge: DurationInSeconds, - @SerialName("max_uses") - val maxUses: Int, - @SerialName("target_type") - val targetType: Optional = Optional.Missing(), - @SerialName("target_user") - val targetUser: Optional = Optional.Missing(), - @SerialName("target_application") - val targetApplication: Optional = Optional.Missing(), + @SerialName("max_age") val maxAge: DurationInSeconds, + @SerialName("max_uses") val maxUses: Int, + @SerialName("target_type") val targetType: Optional = Optional.Missing(), + @SerialName("target_user") val targetUser: Optional = Optional.Missing(), + @SerialName("target_application") val targetApplication: Optional = Optional.Missing(), val temporary: Boolean, val uses: Int, ) @@ -754,12 +699,9 @@ public data class MessageReactionRemoveEmoji(val reaction: DiscordRemovedEmoji, @Serializable public data class DiscordRemovedEmoji( - @SerialName("channel_id") - val channelId: Snowflake, - @SerialName("guild_id") - val guildId: Snowflake, - @SerialName("message_id") - val messageId: Snowflake, + @SerialName("channel_id") val channelId: Snowflake, + @SerialName("guild_id") val guildId: Snowflake, + @SerialName("message_id") val messageId: Snowflake, val emoji: DiscordRemovedReactionEmoji, ) @@ -828,27 +770,20 @@ public data class GuildScheduledEventUserRemove( ) : DispatchEvent() public data class UnknownDispatchEvent( - val name: String?, - val data: JsonElement?, - override val sequence: Int? + val name: String?, val data: JsonElement?, override val sequence: Int? ) : DispatchEvent() @Serializable public data class GuildScheduledEventUserMetadata( - @SerialName("guild_scheduled_event_id") - val guildScheduledEventId: Snowflake, - @SerialName("user_id") - val userId: Snowflake, - @SerialName("guild_id") - val guildId: Snowflake, + @SerialName("guild_scheduled_event_id") val guildScheduledEventId: Snowflake, + @SerialName("user_id") val userId: Snowflake, + @SerialName("guild_id") val guildId: Snowflake, ) @Serializable public data class DiscordThreadListSync( - @SerialName("guild_id") - val guildId: Snowflake, - @SerialName("channel_ids") - val channelIds: Optional> = Optional.Missing(), + @SerialName("guild_id") val guildId: Snowflake, + @SerialName("channel_ids") val channelIds: Optional> = Optional.Missing(), val threads: List, val members: List ) @@ -856,12 +791,8 @@ public data class DiscordThreadListSync( @Serializable public data class DiscordThreadMembersUpdate( val id: Snowflake, - @SerialName("guild_id") - val guildId: Snowflake, - @SerialName("member_count") - val memberCount: Int, - @SerialName("added_members") - val addedMembers: Optional> = Optional.Missing(), - @SerialName("removed_member_ids") - val removedMemberIds: Optional> = Optional.Missing() + @SerialName("guild_id") val guildId: Snowflake, + @SerialName("member_count") val memberCount: Int, + @SerialName("added_members") val addedMembers: Optional> = Optional.Missing(), + @SerialName("removed_member_ids") val removedMemberIds: Optional> = Optional.Missing() )