All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.entity.Interactions.kt Maven / Gradle / Ivy

The newest version!
@file:Generate(
    INT_KORD_ENUM, name = "ApplicationCommandType",
    docUrl = "https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types",
    entries = [
        Entry("ChatInput", intValue = 1, kDoc = "A text-based command that shows up when a user types `/`."),
        Entry("User", intValue = 2, kDoc = "A UI-based command that shows up when you right-click or tap on a user."),
        Entry(
            "Message", intValue = 3,
            kDoc = "A UI-based command that shows up when you right-click or tap on a message.",
        ),
    ],
)

@file:Generate(
    INT_KORD_ENUM, name = "ApplicationCommandOptionType", valueName = "type",
    docUrl = "https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type",
    entries = [
        Entry("SubCommand", intValue = 1),
        Entry("SubCommandGroup", intValue = 2),
        Entry("String", intValue = 3),
        Entry("Integer", intValue = 4, kDoc = "Any integer between `-2^53` and `2^53`."),
        Entry("Boolean", intValue = 5),
        Entry("User", intValue = 6),
        Entry("Channel", intValue = 7, kDoc = "Includes all channel types + categories."),
        Entry("Role", intValue = 8),
        Entry("Mentionable", intValue = 9, kDoc = "Includes users and roles."),
        Entry("Number", intValue = 10, kDoc = "Any double between `-2^53` and `2^53`."),
        Entry("Attachment", intValue = 11),
    ],
)

@file:Generate(
    INT_KORD_ENUM, name = "InteractionType", valueName = "type",
    docUrl = "https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-type",
    entries = [
        Entry("Ping", intValue = 1),
        Entry("ApplicationCommand", intValue = 2),
        Entry("Component", intValue = 3),
        Entry("AutoComplete", intValue = 4),
        Entry("ModalSubmit", intValue = 5),
    ],
)

@file:Generate(
    INT_KORD_ENUM, name = "InteractionResponseType", valueName = "type",
    docUrl = "https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type",
    entries = [
        Entry("Pong", intValue = 1, kDoc = "ACK a [Ping][dev.kord.common.entity.InteractionType.Ping]."),
        Entry("ChannelMessageWithSource", intValue = 4, kDoc = "Respond to an interaction with a message."),
        Entry(
            "DeferredChannelMessageWithSource", intValue = 5,
            kDoc = "ACK an interaction and edit a response later, the user sees a loading state.",
        ),
        Entry(
            "DeferredUpdateMessage", intValue = 6,
            kDoc = "For components, ACK an interaction and edit the original message later; the user does not see a " +
                    "loading state.",
        ),
        Entry("UpdateMessage", intValue = 7, kDoc = "For components, edit the message the component was attached to."),
        Entry(
            "ApplicationCommandAutoCompleteResult", intValue = 8,
            kDoc = "Respond to an autocomplete interaction with suggested choices.",
        ),
        Entry("Modal", intValue = 9, kDoc = "Respond to an interaction with a popup modal."),
    ],
)

@file:Generate(
    INT_KORD_ENUM, name = "ApplicationCommandPermissionType",
    docUrl = "https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type",
    entries = [
        Entry("Role", intValue = 1),
        Entry("User", intValue = 2),
        Entry("Channel", intValue = 3),
    ],
)

package dev.kord.common.entity

import dev.kord.common.Locale
import dev.kord.common.entity.optional.*
import dev.kord.ksp.Generate
import dev.kord.ksp.Generate.EntityType.INT_KORD_ENUM
import dev.kord.ksp.Generate.Entry
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.nullable
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*

@Serializable
public data class DiscordApplicationCommand(
    val id: Snowflake,
    val type: Optional = Optional.Missing(),
    @SerialName("application_id")
    val applicationId: Snowflake,
    val name: String,
    @SerialName("name_localizations")
    val nameLocalizations: Optional?> = Optional.Missing(),
    /**
     * Don't trust the docs: This is nullable on non chat input commands.
     */
    val description: String?,
    @SerialName("description_localizations")
    val descriptionLocalizations: Optional?> = Optional.Missing(),
    @SerialName("guild_id")
    val guildId: OptionalSnowflake = OptionalSnowflake.Missing,
    val options: Optional> = Optional.Missing(),
    @SerialName("default_member_permissions")
    val defaultMemberPermissions: Permissions?,
    @SerialName("dm_permission")
    val dmPermission: OptionalBoolean = OptionalBoolean.Missing,
    @SerialName("default_permission")
    @Deprecated("'defaultPermission' is deprecated in favor of 'defaultMemberPermissions' and 'dmPermission'.")
    val defaultPermission: OptionalBoolean? = OptionalBoolean.Missing,
    val nsfw: OptionalBoolean = OptionalBoolean.Missing,
    val version: Snowflake
)

@Serializable
public data class ApplicationCommandOption(
    val type: ApplicationCommandOptionType,
    val name: String,
    @SerialName("name_localizations")
    val nameLocalizations: Optional?> = Optional.Missing(),
    val description: String,
    @SerialName("description_localizations")
    val descriptionLocalizations: Optional?> = Optional.Missing(),
    val default: OptionalBoolean = OptionalBoolean.Missing,
    val required: OptionalBoolean = OptionalBoolean.Missing,
    val choices: Optional> = Optional.Missing(),
    val autocomplete: OptionalBoolean = OptionalBoolean.Missing,
    val options: Optional> = Optional.Missing(),
    @SerialName("channel_types")
    val channelTypes: Optional> = Optional.Missing(),
    @SerialName("min_value")
    val minValue: Optional = Optional.Missing(),
    @SerialName("max_value")
    val maxValue: Optional = Optional.Missing(),
    @SerialName("min_length")
    val minLength: OptionalInt = OptionalInt.Missing,
    @SerialName("max_length")
    val maxLength: OptionalInt = OptionalInt.Missing
)


@Serializable(Choice.Serializer::class)
public sealed class Choice {
    public abstract val name: String
    public abstract val nameLocalizations: Optional?>
    public abstract val value: Any

    public data class IntegerChoice(
        override val name: String,
        override val nameLocalizations: Optional?>,
        override val value: Long,
    ) : Choice()

    public data class NumberChoice(
        override val name: String,
        override val nameLocalizations: Optional?>,
        override val value: Double
    ) : Choice()

    public data class StringChoice(
        override val name: String,
        override val nameLocalizations: Optional?>,
        override val value: String
    ) : Choice()

    internal object Serializer : KSerializer {
        private val localizationsSerializer = MapSerializer(Locale.serializer(), String.serializer()).nullable

        override val descriptor = buildClassSerialDescriptor("dev.kord.common.entity.Choice") {
            element("name", String.serializer().descriptor)
            element("value", JsonPrimitive.serializer().descriptor)
            element("name_localizations", localizationsSerializer.descriptor, isOptional = true)
        }

        override fun serialize(encoder: Encoder, value: Choice) = encoder.encodeStructure(descriptor) {
            encodeStringElement(descriptor, index = 0, value.name)
            when (value) {
                is IntegerChoice -> encodeLongElement(descriptor, index = 1, value.value)
                is NumberChoice -> encodeDoubleElement(descriptor, index = 1, value.value)
                is StringChoice -> encodeStringElement(descriptor, index = 1, value.value)
            }
            if (value.nameLocalizations !is Optional.Missing) {
                encodeSerializableElement(descriptor, index = 2, localizationsSerializer, value.nameLocalizations.value)
            }
        }

        override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) {
            var name: String? = null
            var nameLocalizations: Optional?> = Optional.Missing()
            var value: JsonPrimitive? = null

            while (true) {
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> name = decodeStringElement(descriptor, index)
                    1 -> value = decodeSerializableElement(descriptor, index, JsonPrimitive.serializer())
                    2 -> nameLocalizations =
                        Optional(decodeSerializableElement(descriptor, index, localizationsSerializer))

                    CompositeDecoder.DECODE_DONE -> break
                    else -> throw SerializationException("Unexpected index: $index")
                }
            }

            @OptIn(ExperimentalSerializationApi::class)
            if (name == null || value == null) throw MissingFieldException(
                missingFields = listOfNotNull("name".takeIf { name == null }, "value".takeIf { value == null }),
                serialName = descriptor.serialName,
            )

            if (value.isString) {
                StringChoice(name, nameLocalizations, value.content)
            } else {
                value.longOrNull?.let { IntegerChoice(name, nameLocalizations, it) }
                    ?: value.doubleOrNull?.let { NumberChoice(name, nameLocalizations, it) }
                    ?: throw SerializationException("Illegal choice value: $value")
            }
        }
    }
}

@Serializable
public data class ResolvedObjects(
    val members: Optional> = Optional.Missing(),
    val users: Optional> = Optional.Missing(),
    val roles: Optional> = Optional.Missing(),
    val channels: Optional> = Optional.Missing(),
    val messages: Optional> = Optional.Missing(),
    val attachments: Optional> = Optional.Missing()
)

@Serializable
public data class DiscordInteraction(
    val id: Snowflake,
    @SerialName("application_id")
    val applicationId: Snowflake,
    val type: InteractionType,
    val data: InteractionCallbackData,
    @SerialName("guild_id")
    val guildId: OptionalSnowflake = OptionalSnowflake.Missing,
    val channel: Optional = Optional.Missing(),
    @SerialName("channel_id")
    val channelId: OptionalSnowflake = OptionalSnowflake.Missing,
    val member: Optional = Optional.Missing(),
    val user: Optional = Optional.Missing(),
    val token: String,
    val version: Int,
    @Serializable(with = MaybeMessageSerializer::class)
    val message: Optional = Optional.Missing(),
    @SerialName("app_permissions")
    val appPermissions: Optional = Optional.Missing(),
    val locale: Optional = Optional.Missing(),
    @SerialName("guild_locale")
    val guildLocale: Optional = Optional.Missing(),
    // Don't trust the docs: This can be missing
    val entitlements: Optional> = Optional.Missing(),
) {

    /**
     * Serializer that handles incomplete messages in [DiscordInteraction.message]. Discards
     * any incomplete messages as missing optionals.
     */
    private object MaybeMessageSerializer :
        KSerializer> by Optional.serializer(DiscordMessage.serializer()) {

        override fun deserialize(decoder: Decoder): Optional {
            decoder as JsonDecoder

            val element = decoder.decodeJsonElement().jsonObject

            //check if required fields are present, if not, discard the data
            return if (
                element["channel_id"] == null ||
                element["author"] == null
            ) {
                Optional.Missing()
            } else {
                decoder.json.decodeFromJsonElement(
                    Optional.serializer(DiscordMessage.serializer()), element
                )
            }
        }


    }
}


@Serializable
public data class InteractionCallbackData(
    val id: OptionalSnowflake = OptionalSnowflake.Missing,
    val type: Optional = Optional.Missing(),
    @SerialName("target_id")
    val targetId: OptionalSnowflake = OptionalSnowflake.Missing,
    val name: Optional = Optional.Missing(),
    val resolved: Optional = Optional.Missing(),
    val options: Optional> = Optional.Missing(),
    @SerialName("guild_id")
    val guildId: OptionalSnowflake = OptionalSnowflake.Missing,
    @SerialName("custom_id")
    val customId: Optional = Optional.Missing(),
    @SerialName("component_type")
    val componentType: Optional = Optional.Missing(),
    val values: Optional> = Optional.Missing(),
    val components: Optional> = Optional.Missing()
)

@Serializable(with = Option.Serializer::class)
public sealed class Option {
    public abstract val name: String
    public abstract val type: ApplicationCommandOptionType

    internal object Serializer : KSerializer




© 2015 - 2024 Weber Informatics LLC | Privacy Policy