Skip to content

Commit

Permalink
Add AutoComplete (#435)
Browse files Browse the repository at this point in the history
* Add support for AutoComplete options

* Update Core representation

* Fix broken imports

* Add focused to OptionData

* Add focused to OptionValue

* Make DiscordApplicationCommand.description nullable

* Make guild/global variants of events and interactions

* Improve documentation and method naming

* Rename Interaction to MessageRespondingInteraction

* Update underlying interactions accordingly

* Update docs according

* Update docs according to requested changes

* Rename MessageRespondingInteraction to DataInteraction
  • Loading branch information
DRSchlaubi authored Nov 25, 2021
1 parent 01c9bf9 commit 93c6366
Show file tree
Hide file tree
Showing 26 changed files with 1,134 additions and 630 deletions.
98 changes: 74 additions & 24 deletions common/src/main/kotlin/entity/Interactions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,34 @@ import dev.kord.common.annotation.KordExperimental
import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalBoolean
import dev.kord.common.entity.optional.OptionalSnowflake
import kotlinx.serialization.*
import kotlinx.serialization.Contextual
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.encoding.encodeStructure
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.double
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import kotlinx.serialization.json.longOrNull
import mu.KotlinLogging

val kordLogger = KotlinLogging.logger { }
Expand All @@ -21,6 +43,9 @@ data class DiscordApplicationCommand(
@SerialName("application_id")
val applicationId: Snowflake,
val name: String,
/**
* Don't trust the docs: This is nullable on non chat input commands.
*/
val description: String?,
@SerialName("guild_id")
val guildId: OptionalSnowflake = OptionalSnowflake.Missing,
Expand Down Expand Up @@ -64,6 +89,7 @@ class ApplicationCommandOption(
val required: OptionalBoolean = OptionalBoolean.Missing,
@OptIn(KordExperimental::class)
val choices: Optional<List<Choice<@Serializable(NotSerializable::class) Any?>>> = Optional.Missing(),
val autocomplete: OptionalBoolean = OptionalBoolean.Missing,
val options: Optional<List<ApplicationCommandOption>> = Optional.Missing(),
)

Expand Down Expand Up @@ -247,12 +273,15 @@ sealed class InteractionType(val type: Int) {
* this type exists and is needed for components even though it's not documented
*/
object Component : InteractionType(3)

object AutoComplete : InteractionType(4)
class Unknown(type: Int) : InteractionType(type)

override fun toString(): String = when (this) {
Ping -> "InteractionType.Ping($type)"
ApplicationCommand -> "InteractionType.ApplicationCommand($type)"
Component -> "InteractionType.ComponentInvoke($type)"
AutoComplete -> "InteractionType.AutoComplete($type)"
is Unknown -> "InteractionType.Unknown($type)"
}

Expand All @@ -267,6 +296,7 @@ sealed class InteractionType(val type: Int) {
1 -> Ping
2 -> ApplicationCommand
3 -> Component
4 -> AutoComplete
else -> Unknown(type)
}
}
Expand Down Expand Up @@ -306,6 +336,7 @@ sealed class Option {
element("value", JsonElement.serializer().descriptor, isOptional = true)
element("options", JsonArray.serializer().descriptor, isOptional = true)
element("type", ApplicationCommandOptionType.serializer().descriptor, isOptional = false)
element("focused", String.serializer().descriptor, isOptional = true)
}

override fun deserialize(decoder: Decoder): Option {
Expand All @@ -316,6 +347,7 @@ sealed class Option {
var jsonValue: JsonElement? = null
var jsonOptions: JsonArray? = null
var type: ApplicationCommandOptionType? = null
var focused: OptionalBoolean = OptionalBoolean.Missing
decoder.decodeStructure(descriptor) {
while (true) {
when (val index = decodeElementIndex(descriptor)) {
Expand All @@ -324,7 +356,8 @@ sealed class Option {
2 -> jsonOptions = decodeSerializableElement(descriptor, index, JsonArray.serializer())
3 -> type =
decodeSerializableElement(descriptor, index, ApplicationCommandOptionType.serializer())

4 -> focused =
decodeSerializableElement(descriptor, index, OptionalBoolean.serializer())
CompositeDecoder.DECODE_DONE -> return@decodeStructure
else -> throw SerializationException("unknown index: $index")
}
Expand Down Expand Up @@ -359,7 +392,7 @@ sealed class Option {
ApplicationCommandOptionType.Role,
ApplicationCommandOptionType.String,
ApplicationCommandOptionType.User -> CommandArgument.Serializer.deserialize(
json, jsonValue!!, name, type!!
json, jsonValue!!, name, type!!, focused
)
else -> error("unknown ApplicationCommandOptionType $type")
}
Expand Down Expand Up @@ -412,10 +445,12 @@ data class SubCommand(
sealed class CommandArgument<out T> : Option() {

abstract val value: T
abstract val focused: OptionalBoolean

class StringArgument(
override val name: String,
override val value: String
override val value: String,
override val focused: OptionalBoolean = OptionalBoolean.Missing
) : CommandArgument<String>() {
override val type: ApplicationCommandOptionType
get() = ApplicationCommandOptionType.String
Expand All @@ -425,7 +460,8 @@ sealed class CommandArgument<out T> : Option() {

class IntegerArgument(
override val name: String,
override val value: Long
override val value: Long,
override val focused: OptionalBoolean = OptionalBoolean.Missing
) : CommandArgument<Long>() {
override val type: ApplicationCommandOptionType
get() = ApplicationCommandOptionType.Integer
Expand All @@ -435,7 +471,8 @@ sealed class CommandArgument<out T> : Option() {

class NumberArgument(
override val name: String,
override val value: Double
override val value: Double,
override val focused: OptionalBoolean = OptionalBoolean.Missing
) : CommandArgument<Double>() {
override val type: ApplicationCommandOptionType
get() = ApplicationCommandOptionType.Number
Expand All @@ -445,7 +482,8 @@ sealed class CommandArgument<out T> : Option() {

class BooleanArgument(
override val name: String,
override val value: Boolean
override val value: Boolean,
override val focused: OptionalBoolean = OptionalBoolean.Missing
) : CommandArgument<Boolean>() {
override val type: ApplicationCommandOptionType
get() = ApplicationCommandOptionType.Boolean
Expand All @@ -455,7 +493,8 @@ sealed class CommandArgument<out T> : Option() {

class UserArgument(
override val name: String,
override val value: Snowflake
override val value: Snowflake,
override val focused: OptionalBoolean = OptionalBoolean.Missing
) : CommandArgument<Snowflake>() {
override val type: ApplicationCommandOptionType
get() = ApplicationCommandOptionType.User
Expand All @@ -465,7 +504,8 @@ sealed class CommandArgument<out T> : Option() {

class ChannelArgument(
override val name: String,
override val value: Snowflake
override val value: Snowflake,
override val focused: OptionalBoolean = OptionalBoolean.Missing
) : CommandArgument<Snowflake>() {
override val type: ApplicationCommandOptionType
get() = ApplicationCommandOptionType.Channel
Expand All @@ -475,7 +515,8 @@ sealed class CommandArgument<out T> : Option() {

class RoleArgument(
override val name: String,
override val value: Snowflake
override val value: Snowflake,
override val focused: OptionalBoolean = OptionalBoolean.Missing
) : CommandArgument<Snowflake>() {
override val type: ApplicationCommandOptionType
get() = ApplicationCommandOptionType.Role
Expand All @@ -485,7 +526,8 @@ sealed class CommandArgument<out T> : Option() {

class MentionableArgument(
override val name: String,
override val value: Snowflake
override val value: Snowflake,
override val focused: OptionalBoolean = OptionalBoolean.Missing
) : CommandArgument<Snowflake>() {
override val type: ApplicationCommandOptionType
get() = ApplicationCommandOptionType.Mentionable
Expand Down Expand Up @@ -542,32 +584,33 @@ sealed class CommandArgument<out T> : Option() {
json: Json,
element: JsonElement,
name: String,
type: ApplicationCommandOptionType
type: ApplicationCommandOptionType,
focused: OptionalBoolean
): CommandArgument<*> = when (type) {
ApplicationCommandOptionType.Boolean -> BooleanArgument(
name, json.decodeFromJsonElement(Boolean.serializer(), element)
name, json.decodeFromJsonElement(Boolean.serializer(), element), focused
)
ApplicationCommandOptionType.String -> StringArgument(
name, json.decodeFromJsonElement(String.serializer(), element)
name, json.decodeFromJsonElement(String.serializer(), element), focused
)
ApplicationCommandOptionType.Integer -> IntegerArgument(
name, json.decodeFromJsonElement(Long.serializer(), element)
name, json.decodeFromJsonElement(Long.serializer(), element), focused
)

ApplicationCommandOptionType.Number -> NumberArgument(
name, json.decodeFromJsonElement(Double.serializer(), element)
name, json.decodeFromJsonElement(Double.serializer(), element), focused
)
ApplicationCommandOptionType.Channel -> ChannelArgument(
name, json.decodeFromJsonElement(Snowflake.serializer(), element)
name, json.decodeFromJsonElement(Snowflake.serializer(), element), focused
)
ApplicationCommandOptionType.Mentionable -> MentionableArgument(
name, json.decodeFromJsonElement(Snowflake.serializer(), element)
name, json.decodeFromJsonElement(Snowflake.serializer(), element), focused
)
ApplicationCommandOptionType.Role -> RoleArgument(
name, json.decodeFromJsonElement(Snowflake.serializer(), element)
name, json.decodeFromJsonElement(Snowflake.serializer(), element), focused
)
ApplicationCommandOptionType.User -> UserArgument(
name, json.decodeFromJsonElement(Snowflake.serializer(), element)
name, json.decodeFromJsonElement(Snowflake.serializer(), element), focused
)
ApplicationCommandOptionType.SubCommand,
ApplicationCommandOptionType.SubCommandGroup,
Expand Down Expand Up @@ -598,7 +641,7 @@ sealed class CommandArgument<out T> : Option() {

requireNotNull(element)
requireNotNull(type)
return deserialize(json, element, name, type)
return deserialize(json, element, name, type, OptionalBoolean.Missing)
}
}
}
Expand Down Expand Up @@ -640,6 +683,7 @@ sealed class InteractionResponseType(val type: Int) {
object DeferredChannelMessageWithSource : InteractionResponseType(5)
object DeferredUpdateMessage : InteractionResponseType(6)
object UpdateMessage : InteractionResponseType(7)
object ApplicationCommandAutoCompleteResult : InteractionResponseType(8)
class Unknown(type: Int) : InteractionResponseType(type)

companion object;
Expand All @@ -656,6 +700,7 @@ sealed class InteractionResponseType(val type: Int) {
5 -> DeferredChannelMessageWithSource
6 -> DeferredUpdateMessage
7 -> UpdateMessage
8 -> ApplicationCommandAutoCompleteResult
else -> Unknown(type)
}
}
Expand Down Expand Up @@ -712,3 +757,8 @@ data class DiscordGuildApplicationCommandPermission(
}
}
}

@Serializable
data class DiscordAutoComplete<T>(
val choices: List<Choice<T>>
)
10 changes: 4 additions & 6 deletions core/src/main/kotlin/behavior/GuildInteractionBehavior.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
package dev.kord.core.behavior

import dev.kord.core.behavior.interaction.InteractionBehavior
import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.interaction.InteractionBehavior
import dev.kord.core.entity.Guild
import dev.kord.core.entity.interaction.ActionInteraction

/**
* The behavior of a [dev.kord.core.entity.interaction.Interaction][Interaction] that was invoked in a [Guild]
* The behavior of a [ActionInteraction] that was invoked in a [Guild]
*/
public interface GuildInteractionBehavior : InteractionBehavior {

public val guildId: Snowflake


/**
* The [GuildBehavior] for the guild the command was executed in.
*/
public val guildBehavior: GuildBehavior get() = GuildBehavior(guildId, kord)


public suspend fun getGuildOrNull(): Guild? = supplier.getGuildOrNull(guildId)

public suspend fun getGuild(): Guild = supplier.getGuild(guildId)

public companion object;
public companion object

}
Loading

0 comments on commit 93c6366

Please sign in to comment.