From cb36f951df5ad6caa9c206ee341cb725f7656f84 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 28 Oct 2021 16:09:43 +0200 Subject: [PATCH 01/13] Add support for AutoComplete options --- common/src/main/kotlin/entity/Interactions.kt | 68 +++++++++++++++---- .../main/kotlin/service/InteractionService.kt | 41 +++++++++-- 2 files changed, 92 insertions(+), 17 deletions(-) diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index e671f51ad47c..57309434f53b 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -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.int +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import mu.KotlinLogging val kordLogger = KotlinLogging.logger { } @@ -64,6 +86,7 @@ class ApplicationCommandOption( val required: OptionalBoolean = OptionalBoolean.Missing, @OptIn(KordExperimental::class) val choices: Optional>> = Optional.Missing(), + val autocomplete: OptionalBoolean = OptionalBoolean.Missing, val options: Optional> = Optional.Missing(), ) @@ -247,12 +270,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)" } @@ -267,6 +293,7 @@ sealed class InteractionType(val type: Int) { 1 -> Ping 2 -> ApplicationCommand 3 -> Component + 4 -> AutoComplete else -> Unknown(type) } } @@ -306,6 +333,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 { @@ -412,10 +440,12 @@ data class SubCommand( sealed class CommandArgument : 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() { override val type: ApplicationCommandOptionType get() = ApplicationCommandOptionType.String @@ -425,7 +455,8 @@ sealed class CommandArgument : Option() { class IntegerArgument( override val name: String, - override val value: Long + override val value: Long, + override val focused: OptionalBoolean = OptionalBoolean.Missing ) : CommandArgument() { override val type: ApplicationCommandOptionType get() = ApplicationCommandOptionType.Integer @@ -435,7 +466,8 @@ sealed class CommandArgument : Option() { class NumberArgument( override val name: String, - override val value: Double + override val value: Double, + override val focused: OptionalBoolean = OptionalBoolean.Missing ) : CommandArgument() { override val type: ApplicationCommandOptionType get() = ApplicationCommandOptionType.Number @@ -445,7 +477,8 @@ sealed class CommandArgument : Option() { class BooleanArgument( override val name: String, - override val value: Boolean + override val value: Boolean, + override val focused: OptionalBoolean = OptionalBoolean.Missing ) : CommandArgument() { override val type: ApplicationCommandOptionType get() = ApplicationCommandOptionType.Boolean @@ -455,7 +488,8 @@ sealed class CommandArgument : Option() { class UserArgument( override val name: String, - override val value: Snowflake + override val value: Snowflake, + override val focused: OptionalBoolean = OptionalBoolean.Missing ) : CommandArgument() { override val type: ApplicationCommandOptionType get() = ApplicationCommandOptionType.User @@ -465,7 +499,8 @@ sealed class CommandArgument : Option() { class ChannelArgument( override val name: String, - override val value: Snowflake + override val value: Snowflake, + override val focused: OptionalBoolean = OptionalBoolean.Missing ) : CommandArgument() { override val type: ApplicationCommandOptionType get() = ApplicationCommandOptionType.Channel @@ -475,7 +510,8 @@ sealed class CommandArgument : Option() { class RoleArgument( override val name: String, - override val value: Snowflake + override val value: Snowflake, + override val focused: OptionalBoolean = OptionalBoolean.Missing ) : CommandArgument() { override val type: ApplicationCommandOptionType get() = ApplicationCommandOptionType.Role @@ -485,7 +521,8 @@ sealed class CommandArgument : Option() { class MentionableArgument( override val name: String, - override val value: Snowflake + override val value: Snowflake, + override val focused: OptionalBoolean = OptionalBoolean.Missing ) : CommandArgument() { override val type: ApplicationCommandOptionType get() = ApplicationCommandOptionType.Mentionable @@ -640,6 +677,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; @@ -656,6 +694,7 @@ sealed class InteractionResponseType(val type: Int) { 5 -> DeferredChannelMessageWithSource 6 -> DeferredUpdateMessage 7 -> UpdateMessage + 8 -> ApplicationCommandAutoCompleteResult else -> Unknown(type) } } @@ -712,3 +751,8 @@ data class DiscordGuildApplicationCommandPermission( } } } + +@Serializable +data class DiscordAutoComplete( + val choices: List> +) diff --git a/rest/src/main/kotlin/service/InteractionService.kt b/rest/src/main/kotlin/service/InteractionService.kt index 678f3614bca4..142407190021 100644 --- a/rest/src/main/kotlin/service/InteractionService.kt +++ b/rest/src/main/kotlin/service/InteractionService.kt @@ -4,15 +4,36 @@ import dev.kord.common.entity.* import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.coerceToMissing import dev.kord.common.entity.optional.orEmpty -import dev.kord.rest.builder.interaction.* +import dev.kord.rest.builder.interaction.ApplicationCommandPermissionsBulkModifyBuilder +import dev.kord.rest.builder.interaction.ApplicationCommandPermissionsModifyBuilder +import dev.kord.rest.builder.interaction.ChatInputCreateBuilder +import dev.kord.rest.builder.interaction.ChatInputModifyBuilder +import dev.kord.rest.builder.interaction.MessageCommandCreateBuilder +import dev.kord.rest.builder.interaction.MessageCommandModifyBuilder +import dev.kord.rest.builder.interaction.MultiApplicationCommandBuilder +import dev.kord.rest.builder.interaction.UserCommandCreateBuilder +import dev.kord.rest.builder.interaction.UserCommandModifyBuilder import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder import dev.kord.rest.builder.message.modify.FollowupMessageModifyBuilder import dev.kord.rest.builder.message.modify.InteractionResponseModifyBuilder -import dev.kord.rest.json.request.* +import dev.kord.rest.json.request.ApplicationCommandCreateRequest +import dev.kord.rest.json.request.ApplicationCommandModifyRequest +import dev.kord.rest.json.request.ApplicationCommandPermissionsEditRequest +import dev.kord.rest.json.request.FollowupMessageCreateRequest +import dev.kord.rest.json.request.FollowupMessageModifyRequest +import dev.kord.rest.json.request.InteractionApplicationCommandCallbackData +import dev.kord.rest.json.request.InteractionResponseCreateRequest +import dev.kord.rest.json.request.InteractionResponseModifyRequest +import dev.kord.rest.json.request.MultipartFollowupMessageCreateRequest +import dev.kord.rest.json.request.MultipartFollowupMessageModifyRequest +import dev.kord.rest.json.request.MultipartInteractionResponseCreateRequest +import dev.kord.rest.json.request.MultipartInteractionResponseModifyRequest import dev.kord.rest.request.RequestHandler import dev.kord.rest.route.Route +import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.serializer import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -124,6 +145,17 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa body(InteractionResponseCreateRequest.serializer(), request) } + suspend inline fun createAutoCompleteInteractionResponse( + interactionId: Snowflake, + interactionToken: String, + autoComplete: DiscordAutoComplete, + typeSerializer: KSerializer = serializer() + ) = call(Route.InteractionResponseCreate) { + keys[Route.InteractionId] = interactionId + keys[Route.InteractionToken] = interactionToken + body(DiscordAutoComplete.serializer(typeSerializer), autoComplete) + } + suspend fun getInteractionResponse( applicationId: Snowflake, interactionToken: String, @@ -367,7 +399,6 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa } - @OptIn(ExperimentalContracts::class) suspend inline fun createGuildChatInputApplicationCommand( applicationId: Snowflake, @@ -579,11 +610,11 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa } public suspend fun acknowledge(interactionId: Snowflake, interactionToken: String, ephemeral: Boolean = false) { - val request = InteractionResponseCreateRequest( + val request = InteractionResponseCreateRequest( type = InteractionResponseType.DeferredChannelMessageWithSource, data = Optional( InteractionApplicationCommandCallbackData( - flags = Optional(if(ephemeral) MessageFlags(MessageFlag.Ephemeral) else null).coerceToMissing() + flags = Optional(if (ephemeral) MessageFlags(MessageFlag.Ephemeral) else null).coerceToMissing() ) ) ) From 441754dae1d88b290acc49c7039ab753f67e364a Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 28 Oct 2021 18:25:30 +0200 Subject: [PATCH 02/13] Update Core representation --- common/src/main/kotlin/entity/Interactions.kt | 27 +++--- .../AutoCompleteInteractionBehavior.kt | 73 +++++++++++++++ .../entity/interaction/ContextInteraction.kt | 20 ++++- .../kotlin/entity/interaction/Interaction.kt | 4 + .../event/interaction/ApplicationCreate.kt | 22 +++-- .../handler/InteractionEventHandler.kt | 47 +++++++++- .../builder/interaction/OptionsBuilder.kt | 25 ++++-- .../json/request/InteractionsRequests.kt | 23 +++-- .../main/kotlin/service/InteractionService.kt | 89 ++++++++++++++++++- 9 files changed, 295 insertions(+), 35 deletions(-) create mode 100644 core/src/main/kotlin/behavior/interaction/AutoCompleteInteractionBehavior.kt diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index 57309434f53b..6d1552aa340b 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -344,6 +344,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)) { @@ -352,7 +353,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") } @@ -387,7 +389,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") } @@ -579,32 +581,33 @@ sealed class CommandArgument : 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, @@ -635,7 +638,7 @@ sealed class CommandArgument : Option() { requireNotNull(element) requireNotNull(type) - return deserialize(json, element, name, type) + return deserialize(json, element, name, type, OptionalBoolean.Missing) } } } diff --git a/core/src/main/kotlin/behavior/interaction/AutoCompleteInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/AutoCompleteInteractionBehavior.kt new file mode 100644 index 000000000000..a70e357d5f75 --- /dev/null +++ b/core/src/main/kotlin/behavior/interaction/AutoCompleteInteractionBehavior.kt @@ -0,0 +1,73 @@ +package dev.kord.core.behavior.interaction + +import dev.kord.common.entity.Choice +import dev.kord.common.entity.DiscordAutoComplete +import dev.kord.rest.builder.interaction.IntChoiceBuilder +import dev.kord.rest.builder.interaction.NumberChoiceBuilder +import dev.kord.rest.builder.interaction.StringChoiceBuilder +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Behavior of an AutoComplete interaction. + * + * @see respondNumber + * @see respondString + * @see respondInt + * @see respond + */ +public interface AutoCompleteInteractionBehavior : InteractionBehavior + +/** + * Responds with the int choices specified by [builder]. + * + * @see IntChoiceBuilder + */ +@OptIn(ExperimentalContracts::class) +public suspend inline fun AutoCompleteInteractionBehavior.respondInt(builder: IntChoiceBuilder.() -> Unit) { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + } + + kord.rest.interaction.createIntAutoCompleteInteractionResponse(id, token, builder) +} + +/** + * Responds with the number choices specified by [builder]. + * + * @see NumberChoiceBuilder + */ +@OptIn(ExperimentalContracts::class) +public suspend inline fun AutoCompleteInteractionBehavior.respondNumber(builder: NumberChoiceBuilder.() -> Unit) { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + } + + kord.rest.interaction.createNumberAutoCompleteInteractionResponse(id, token, builder) +} + +/** + * Responds with the string choices specified by [builder]. + * + * @see StringChoiceBuilder + */ +@OptIn(ExperimentalContracts::class) +public suspend inline fun AutoCompleteInteractionBehavior.respondString(builder: StringChoiceBuilder.() -> Unit) { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + } + + kord.rest.interaction.createStringAutoCompleteInteractionResponse(id, token, builder) +} + +/** + * Responds with [choices] to this auto-complete request. + */ +public suspend inline fun AutoCompleteInteractionBehavior.respond(choices: List>) { + kord.rest.interaction.createAutoCompleteInteractionResponse( + id, + token, + DiscordAutoComplete(choices) + ) +} diff --git a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt index 5a02db9237e3..d4244037c46a 100644 --- a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt @@ -7,12 +7,13 @@ import dev.kord.core.Kord import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.interaction.ApplicationCommandInteractionBehavior +import dev.kord.core.behavior.interaction.AutoCompleteInteractionBehavior import dev.kord.core.cache.data.InteractionData import dev.kord.core.entity.Message import dev.kord.core.entity.User import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy -import java.util.* +import java.util.Objects /** * Represents an interaction of type [ApplicationCommand][dev.kord.common.entity.InteractionType.ApplicationCommand] @@ -205,3 +206,20 @@ public class UnknownApplicationCommandInteraction( return UnknownApplicationCommandInteraction(data, kord, strategy.supply(kord)) } } + +/** + * Interaction indicating an auto-complete request from Discord. + * + * **Follow-ups and normals responses don't work on this type** + * + * Check [AutoCompleteInteractionBehavior] for response options + */ +public class AutoCompleteInteraction( + override val data: InteractionData, + override val user: UserBehavior, + override val kord: Kord, + override val supplier: EntitySupplier = kord.defaultSupplier +) : AutoCompleteInteractionBehavior, ChatInputCommandInteraction { + override fun withStrategy(strategy: EntitySupplyStrategy<*>): Interaction = + AutoCompleteInteraction(data, user, kord, strategy.supply(kord)) +} diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt index 1d3288296d1d..f7ab9caf5686 100644 --- a/core/src/main/kotlin/entity/interaction/Interaction.kt +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -62,6 +62,10 @@ public sealed interface Interaction : InteractionBehavior { ): Interaction { return when { data.type == InteractionType.Component -> ComponentInteraction(data, kord, strategy.supply(kord)) + data.type == InteractionType.AutoComplete -> { + val user = User(data.user.value!!, kord, strategy.supply(kord)) + AutoCompleteInteraction(data, user, kord, strategy.supply(kord)) + } data.guildId !is OptionalSnowflake.Missing -> GuildApplicationCommandInteraction( data, kord, diff --git a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt index e7fe9c383a2b..e1e2fe99d440 100644 --- a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt +++ b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt @@ -6,7 +6,6 @@ import dev.kord.core.entity.application.* import dev.kord.core.entity.interaction.* import dev.kord.core.event.kordCoroutineScope import kotlinx.coroutines.CoroutineScope -import kotlin.coroutines.CoroutineContext /** * This event fires when an interaction is created. @@ -46,7 +45,7 @@ public sealed interface GuildApplicationInteractionCreateEvent : ApplicationInte override val interaction: GuildApplicationCommandInteraction } -public sealed interface UserCommandInteractionCreateEvent : ApplicationInteractionCreateEvent { +public sealed interface UserCommandInteractionCreateEvent : ApplicationInteractionCreateEvent { override val interaction: UserCommandInteraction } @@ -65,7 +64,7 @@ public class GlobalUserCommandInteractionCreateEvent( ) : GlobalApplicationInteractionCreateEvent, UserCommandInteractionCreateEvent, CoroutineScope by coroutineScope -public sealed interface MessageCommandInteractionCreateEvent : ApplicationInteractionCreateEvent { +public sealed interface MessageCommandInteractionCreateEvent : ApplicationInteractionCreateEvent { override val interaction: MessageCommandInteraction } @@ -84,8 +83,7 @@ public class GlobalMessageCommandInteractionCreateEvent( ) : GlobalApplicationInteractionCreateEvent, MessageCommandInteractionCreateEvent, CoroutineScope by coroutineScope - -public sealed interface ChatInputCommandInteractionCreateEvent : ApplicationInteractionCreateEvent { +public sealed interface ChatInputCommandInteractionCreateEvent : ApplicationInteractionCreateEvent { override val interaction: ChatInputCommandInteraction } @@ -102,3 +100,17 @@ public class GlobalChatInputCommandInteractionCreateEvent( override val shard: Int, public val coroutineScope: CoroutineScope = kordCoroutineScope(kord) ) : GlobalApplicationInteractionCreateEvent, ChatInputCommandInteractionCreateEvent, CoroutineScope by coroutineScope + +/** + * Interaction received when a users types into an auto-completed option. + * + * Check [AutoCompleteInteractionBehavior] on how to reply. + * + * @see AutoCompleteInteraction + */ +public class AutoCompleteInteractionCreateEvent( + override val interaction: AutoCompleteInteraction, + override val kord: Kord, + override val shard: Int, + public val coroutineScope: CoroutineScope = kordCoroutineScope(kord) +) : InteractionCreateEvent, CoroutineScope by coroutineScope diff --git a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt index 2f3084e42437..87d79b56d2d8 100644 --- a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt +++ b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt @@ -7,9 +7,48 @@ import dev.kord.core.Kord import dev.kord.core.cache.data.ApplicationCommandData import dev.kord.core.cache.data.InteractionData import dev.kord.core.cache.idEq -import dev.kord.core.entity.application.* -import dev.kord.core.entity.interaction.* -import dev.kord.core.event.interaction.* +import dev.kord.core.entity.application.GuildApplicationCommand +import dev.kord.core.entity.application.GuildChatInputCommand +import dev.kord.core.entity.application.GuildMessageCommand +import dev.kord.core.entity.application.GuildUserCommand +import dev.kord.core.entity.application.UnknownGuildApplicationCommand +import dev.kord.core.entity.interaction.AutoCompleteInteraction +import dev.kord.core.entity.interaction.GlobalButtonInteraction +import dev.kord.core.entity.interaction.GlobalChatInputCommandInteraction +import dev.kord.core.entity.interaction.GlobalMessageCommandInteraction +import dev.kord.core.entity.interaction.GlobalSelectMenuInteraction +import dev.kord.core.entity.interaction.GlobalUserCommandInteraction +import dev.kord.core.entity.interaction.GuildButtonInteraction +import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction +import dev.kord.core.entity.interaction.GuildMessageCommandInteraction +import dev.kord.core.entity.interaction.GuildSelectMenuInteraction +import dev.kord.core.entity.interaction.GuildUserCommandInteraction +import dev.kord.core.entity.interaction.Interaction +import dev.kord.core.entity.interaction.UnknownApplicationCommandInteraction +import dev.kord.core.entity.interaction.UnknownComponentInteraction +import dev.kord.core.event.interaction.AutoCompleteInteractionCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandDeleteEvent +import dev.kord.core.event.interaction.ChatInputCommandUpdateEvent +import dev.kord.core.event.interaction.GlobalButtonInteractionCreateEvent +import dev.kord.core.event.interaction.GlobalChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.GlobalMessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.GlobalSelectMenuInteractionCreateEvent +import dev.kord.core.event.interaction.GlobalUserCommandInteractionCreateEvent +import dev.kord.core.event.interaction.GuildButtonInteractionCreateEvent +import dev.kord.core.event.interaction.GuildChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.GuildMessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.GuildSelectMenuInteractionCreateEvent +import dev.kord.core.event.interaction.GuildUserCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandCreateEvent +import dev.kord.core.event.interaction.MessageCommandDeleteEvent +import dev.kord.core.event.interaction.MessageCommandUpdateEvent +import dev.kord.core.event.interaction.UnknownApplicationCommandCreateEvent +import dev.kord.core.event.interaction.UnknownApplicationCommandDeleteEvent +import dev.kord.core.event.interaction.UnknownApplicationCommandUpdateEvent +import dev.kord.core.event.interaction.UserCommandCreateEvent +import dev.kord.core.event.interaction.UserCommandDeleteEvent +import dev.kord.core.event.interaction.UserCommandUpdateEvent import dev.kord.gateway.* import kotlinx.coroutines.CoroutineScope import dev.kord.core.event.Event as CoreEvent @@ -32,6 +71,7 @@ public class InteractionEventHandler( val data = InteractionData.from(event.interaction) val interaction = Interaction.from(data, kord) val coreEvent = when(interaction) { + is AutoCompleteInteraction -> AutoCompleteInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalChatInputCommandInteraction -> GlobalChatInputCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalUserCommandInteraction -> GlobalUserCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalMessageCommandInteraction -> GlobalMessageCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) @@ -74,7 +114,6 @@ public class InteractionEventHandler( ): CoreEvent { val data = ApplicationCommandData.from(event.application) cache.put(data) - val application = GuildApplicationCommand(data, kord.rest.interaction) val coreEvent = when (val application = GuildApplicationCommand(data, kord.rest.interaction)) { is GuildChatInputCommand -> ChatInputCommandUpdateEvent(application, kord, shard, coroutineScope) diff --git a/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt index 77ce7f81b561..1b93a77fcd3e 100644 --- a/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt @@ -1,7 +1,6 @@ package dev.kord.rest.builder.interaction import dev.kord.common.annotation.KordDsl -import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.ApplicationCommandOption import dev.kord.common.entity.ApplicationCommandOptionType import dev.kord.common.entity.Choice @@ -27,12 +26,26 @@ sealed class OptionsBuilder( internal var _required: OptionalBoolean = OptionalBoolean.Missing var required: Boolean? by ::_required.delegate() + internal var _autocomplete: OptionalBoolean = OptionalBoolean.Missing + + /** + * Setting this to `true` allows you to dynamically respond with your choices, depending on the user input. + * + * **Warning:** This disables all input validation, users can submit values before responding to the AutoComplete request + * + * **Note:** If you set this to `true` you can't add any other choice + * + * Core users can check the `AutoCompleteInteractionCreateEvent` and `AutoCompleteInteraction` classes. + */ + var autocomplete: Boolean? by ::_autocomplete.delegate() + override fun toRequest() = ApplicationCommandOption( type, name, description, _default, - _required + _required, + autocomplete = _autocomplete ) } @@ -41,8 +54,7 @@ sealed class BaseChoiceBuilder( name: String, description: String, type: ApplicationCommandOptionType -) : - OptionsBuilder(name, description, type) { +) : OptionsBuilder(name, description, type) { private var _choices: Optional>> = Optional.Missing() var choices: MutableList>? by ::_choices.delegate() @@ -54,7 +66,8 @@ sealed class BaseChoiceBuilder( description, choices = _choices, required = _required, - default = _default + default = _default, + autocomplete = _autocomplete ) } @@ -150,4 +163,4 @@ class GroupCommandBuilder(name: String, description: String) : if (options == null) options = mutableListOf() options!!.add(SubCommandBuilder(name, description).apply(builder)) } -} \ No newline at end of file +} diff --git a/rest/src/main/kotlin/json/request/InteractionsRequests.kt b/rest/src/main/kotlin/json/request/InteractionsRequests.kt index 38c7e816931b..aa7478d0e6bb 100644 --- a/rest/src/main/kotlin/json/request/InteractionsRequests.kt +++ b/rest/src/main/kotlin/json/request/InteractionsRequests.kt @@ -1,13 +1,19 @@ package dev.kord.rest.json.request -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.* +import dev.kord.common.entity.AllowedMentions +import dev.kord.common.entity.ApplicationCommandOption +import dev.kord.common.entity.ApplicationCommandType +import dev.kord.common.entity.DiscordAttachment +import dev.kord.common.entity.DiscordAutoComplete +import dev.kord.common.entity.DiscordComponent +import dev.kord.common.entity.DiscordGuildApplicationCommandPermission +import dev.kord.common.entity.InteractionResponseType +import dev.kord.common.entity.MessageFlags import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.rest.NamedFile import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import java.io.InputStream @Serializable @@ -49,12 +55,17 @@ data class MultipartInteractionResponseModifyRequest( ) @Serializable - data class InteractionResponseCreateRequest( val type: InteractionResponseType, val data: Optional = Optional.Missing() ) +@Serializable +data class AutoCompleteResponseCreateRequest( + val type: InteractionResponseType, + val data: DiscordAutoComplete +) + data class MultipartInteractionResponseCreateRequest( val request: InteractionResponseCreateRequest, @@ -70,7 +81,7 @@ class InteractionApplicationCommandCallbackData( @SerialName("allowed_mentions") val allowedMentions: Optional = Optional.Missing(), val flags: Optional = Optional.Missing(), - val components: Optional> = Optional.Missing() + val components: Optional> = Optional.Missing(), ) @@ -114,5 +125,5 @@ data class MultipartFollowupMessageModifyRequest( @Serializable data class ApplicationCommandPermissionsEditRequest( - val permissions: List + val permissions: List ) diff --git a/rest/src/main/kotlin/service/InteractionService.kt b/rest/src/main/kotlin/service/InteractionService.kt index 142407190021..93cbdaf203f3 100644 --- a/rest/src/main/kotlin/service/InteractionService.kt +++ b/rest/src/main/kotlin/service/InteractionService.kt @@ -3,14 +3,26 @@ package dev.kord.rest.service import dev.kord.common.entity.* import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.coerceToMissing +import dev.kord.common.entity.Choice +import dev.kord.common.entity.DiscordApplicationCommand +import dev.kord.common.entity.DiscordAutoComplete +import dev.kord.common.entity.DiscordGuildApplicationCommandPermissions +import dev.kord.common.entity.DiscordMessage +import dev.kord.common.entity.InteractionResponseType +import dev.kord.common.entity.PartialDiscordGuildApplicationCommandPermissions +import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.orEmpty import dev.kord.rest.builder.interaction.ApplicationCommandPermissionsBulkModifyBuilder import dev.kord.rest.builder.interaction.ApplicationCommandPermissionsModifyBuilder +import dev.kord.rest.builder.interaction.BaseChoiceBuilder import dev.kord.rest.builder.interaction.ChatInputCreateBuilder import dev.kord.rest.builder.interaction.ChatInputModifyBuilder +import dev.kord.rest.builder.interaction.IntChoiceBuilder import dev.kord.rest.builder.interaction.MessageCommandCreateBuilder import dev.kord.rest.builder.interaction.MessageCommandModifyBuilder import dev.kord.rest.builder.interaction.MultiApplicationCommandBuilder +import dev.kord.rest.builder.interaction.NumberChoiceBuilder +import dev.kord.rest.builder.interaction.StringChoiceBuilder import dev.kord.rest.builder.interaction.UserCommandCreateBuilder import dev.kord.rest.builder.interaction.UserCommandModifyBuilder import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder @@ -20,6 +32,7 @@ import dev.kord.rest.builder.message.modify.InteractionResponseModifyBuilder import dev.kord.rest.json.request.ApplicationCommandCreateRequest import dev.kord.rest.json.request.ApplicationCommandModifyRequest import dev.kord.rest.json.request.ApplicationCommandPermissionsEditRequest +import dev.kord.rest.json.request.AutoCompleteResponseCreateRequest import dev.kord.rest.json.request.FollowupMessageCreateRequest import dev.kord.rest.json.request.FollowupMessageModifyRequest import dev.kord.rest.json.request.InteractionApplicationCommandCallbackData @@ -153,7 +166,81 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa ) = call(Route.InteractionResponseCreate) { keys[Route.InteractionId] = interactionId keys[Route.InteractionToken] = interactionToken - body(DiscordAutoComplete.serializer(typeSerializer), autoComplete) + + body( + AutoCompleteResponseCreateRequest.serializer(typeSerializer), + AutoCompleteResponseCreateRequest( + InteractionResponseType.ApplicationCommandAutoCompleteResult, + autoComplete + ) + ) + } + + @PublishedApi + internal suspend inline fun > createBuilderAutoCompleteInteractionResponse( + interactionId: Snowflake, + interactionToken: String, + builder: Builder, + builderFunction: Builder.() -> Unit + ) { + @Suppress("UNCHECKED_CAST") + val choices = builder.apply(builderFunction).choices as List> + + return createAutoCompleteInteractionResponse(interactionId, interactionToken, DiscordAutoComplete(choices)) + } + + @OptIn(ExperimentalContracts::class) + suspend inline fun createIntAutoCompleteInteractionResponse( + interactionId: Snowflake, + interactionToken: String, + builderFunction: IntChoiceBuilder.() -> Unit + ) { + contract { + callsInPlace(builderFunction, InvocationKind.EXACTLY_ONCE) + } + + return createBuilderAutoCompleteInteractionResponse( + interactionId, + interactionToken, + IntChoiceBuilder("", ""), + builderFunction + ) + } + + @OptIn(ExperimentalContracts::class) + suspend inline fun createNumberAutoCompleteInteractionResponse( + interactionId: Snowflake, + interactionToken: String, + builderFunction: NumberChoiceBuilder.() -> Unit + ) { + contract { + callsInPlace(builderFunction, InvocationKind.EXACTLY_ONCE) + } + + return createBuilderAutoCompleteInteractionResponse( + interactionId, + interactionToken, + NumberChoiceBuilder("", ""), + builderFunction + ) + } + + @OptIn(ExperimentalContracts::class) + suspend inline fun createStringAutoCompleteInteractionResponse( + interactionId: Snowflake, + interactionToken: String, + builderFunction: StringChoiceBuilder.() -> Unit + ) { + contract { + callsInPlace(builderFunction, InvocationKind.EXACTLY_ONCE) + } + + return createBuilderAutoCompleteInteractionResponse( + interactionId, + interactionToken, + StringChoiceBuilder("", ""), + builderFunction + ) } suspend fun getInteractionResponse( From 52de49e91c66416e418f7266b5a068d55f04369e Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 28 Oct 2021 18:42:13 +0200 Subject: [PATCH 03/13] Fix broken imports --- common/src/main/kotlin/entity/Interactions.kt | 4 ++-- core/src/samples/kotlin/PingBot.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index 6d1552aa340b..9daffb8874db 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -28,10 +28,10 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.double import kotlinx.serialization.json.doubleOrNull -import kotlinx.serialization.json.int -import kotlinx.serialization.json.intOrNull 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 { } diff --git a/core/src/samples/kotlin/PingBot.kt b/core/src/samples/kotlin/PingBot.kt index 048abf4e1b50..588a3d287316 100644 --- a/core/src/samples/kotlin/PingBot.kt +++ b/core/src/samples/kotlin/PingBot.kt @@ -10,5 +10,5 @@ suspend fun main(args: Array) { if (message.content == "!ping") message.channel.createMessage("pong") } - kord.login { playing("!ping to pong") } + kord.login { presence { playing("!ping to pong") } } } From a62241e2ac180d2b7b8e55185637c9937f89f19c Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 28 Oct 2021 19:25:27 +0200 Subject: [PATCH 04/13] Add focused to OptionData --- .../main/kotlin/cache/data/InteractionData.kt | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/cache/data/InteractionData.kt b/core/src/main/kotlin/cache/data/InteractionData.kt index edbe981376a8..ea0dc7f1195a 100644 --- a/core/src/main/kotlin/cache/data/InteractionData.kt +++ b/core/src/main/kotlin/cache/data/InteractionData.kt @@ -1,9 +1,21 @@ package dev.kord.core.cache.data import dev.kord.common.annotation.KordExperimental -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.* +import dev.kord.common.entity.ApplicationCommandType +import dev.kord.common.entity.Choice +import dev.kord.common.entity.CommandArgument +import dev.kord.common.entity.CommandGroup +import dev.kord.common.entity.ComponentType +import dev.kord.common.entity.DiscordInteraction +import dev.kord.common.entity.InteractionCallbackData +import dev.kord.common.entity.InteractionType +import dev.kord.common.entity.Option +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.ResolvedObjects +import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.SubCommand import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.OptionalSnowflake import dev.kord.common.entity.optional.flatMap import dev.kord.common.entity.optional.map @@ -119,13 +131,14 @@ public data class OptionData( @OptIn(KordExperimental::class) val value: Optional> = Optional.Missing(), val values: Optional>> = Optional.Missing(), - val subCommands: Optional> = Optional.Missing() + val subCommands: Optional> = Optional.Missing(), + val focused: OptionalBoolean = OptionalBoolean.Missing ) { public companion object { public fun from(data: Option): OptionData = with(data) { when (data) { is SubCommand -> OptionData(name, values = data.options) - is CommandArgument<*> -> OptionData(name, value = Optional(data)) + is CommandArgument<*> -> OptionData(name, value = Optional(data), focused = data.focused) is CommandGroup -> OptionData(name, subCommands = data.options) } } From f60a193cda9978672afdc540dbcc0f2f138b29db Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 28 Oct 2021 19:31:24 +0200 Subject: [PATCH 05/13] Add focused to OptionValue --- .../kotlin/entity/interaction/Interaction.kt | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt index f7ab9caf5686..4c5c4b50e1c6 100644 --- a/core/src/main/kotlin/entity/interaction/Interaction.kt +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -274,42 +274,43 @@ public class ResolvedObjects( } -public sealed class OptionValue(public val value: T) { +public sealed class OptionValue(public val value: T, public val focused: Boolean) { - public class RoleOptionValue(value: Role) : OptionValue(value) { + public class RoleOptionValue(value: Role, focused: Boolean) : OptionValue(value, focused) { override fun toString(): String = "RoleOptionValue(value=$value)" } - public open class UserOptionValue(value: User) : OptionValue(value) { + public open class UserOptionValue(value: User, focused: Boolean) : OptionValue(value, focused) { override fun toString(): String = "UserOptionValue(value=$value)" } - public class MemberOptionValue(value: Member) : UserOptionValue(value) { + public class MemberOptionValue(value: Member, focused: Boolean) : UserOptionValue(value, focused) { override fun toString(): String = "MemberOptionValue(value=$value)" } - public class ChannelOptionValue(value: ResolvedChannel) : OptionValue(value) { + public class ChannelOptionValue(value: ResolvedChannel, focused: Boolean) : + OptionValue(value, focused) { override fun toString(): String = "ChannelOptionValue(value=$value)" } - public class IntOptionValue(value: Long) : OptionValue(value) { + public class IntOptionValue(value: Long, focused: Boolean) : OptionValue(value, focused) { override fun toString(): String = "IntOptionValue(value=$value)" } - public class NumberOptionValue(value: Double) : OptionValue(value) { + public class NumberOptionValue(value: Double, focused: Boolean) : OptionValue(value, focused) { override fun toString(): String = "DoubleOptionValue(value=$value)" } - public class StringOptionValue(value: String) : OptionValue(value) { + public class StringOptionValue(value: String, focused: Boolean) : OptionValue(value, focused) { override fun toString(): String = "StringOptionValue(value=$value)" } - public class BooleanOptionValue(value: Boolean) : OptionValue(value) { + public class BooleanOptionValue(value: Boolean, focused: Boolean) : OptionValue(value, focused) { override fun toString(): String = "BooleanOptionValue(value=$value)" } - public class MentionableOptionValue(value: Entity) : OptionValue(value) { + public class MentionableOptionValue(value: Entity, focused: Boolean) : OptionValue(value, focused) { override fun toString(): String = "MentionableOptionValue(value=$value)" } @@ -317,16 +318,17 @@ public sealed class OptionValue(public val value: T) { public fun OptionValue(value: CommandArgument<*>, resolvedObjects: ResolvedObjects?): OptionValue<*> { + val focused = value.focused.orElse(false) return when (value) { - is CommandArgument.NumberArgument -> OptionValue.NumberOptionValue(value.value) - is CommandArgument.BooleanArgument -> OptionValue.BooleanOptionValue(value.value) - is CommandArgument.IntegerArgument -> OptionValue.IntOptionValue(value.value) - is CommandArgument.StringArgument -> OptionValue.StringOptionValue(value.value) + is CommandArgument.NumberArgument -> OptionValue.NumberOptionValue(value.value, focused) + is CommandArgument.BooleanArgument -> OptionValue.BooleanOptionValue(value.value, focused) + is CommandArgument.IntegerArgument -> OptionValue.IntOptionValue(value.value, focused) + is CommandArgument.StringArgument -> OptionValue.StringOptionValue(value.value, focused) is CommandArgument.ChannelArgument -> { val channel = resolvedObjects?.channels.orEmpty()[value.value] requireNotNull(channel) { "channel expected for $value but was missing" } - OptionValue.ChannelOptionValue(channel) + OptionValue.ChannelOptionValue(channel, focused) } is CommandArgument.MentionableArgument -> { @@ -338,25 +340,25 @@ public fun OptionValue(value: CommandArgument<*>, resolvedObjects: ResolvedObjec val entity = channel ?: member ?: user ?: role requireNotNull(entity) { "user, member, or channel expected for $value but was missing" } - OptionValue.MentionableOptionValue(entity) + OptionValue.MentionableOptionValue(entity, focused) } is CommandArgument.RoleArgument -> { val role = resolvedObjects?.roles.orEmpty()[value.value] requireNotNull(role) { "role expected for $value but was missing" } - OptionValue.RoleOptionValue(role) + OptionValue.RoleOptionValue(role, focused) } is CommandArgument.UserArgument -> { val member = resolvedObjects?.members.orEmpty()[value.value] - if (member != null) return OptionValue.MemberOptionValue(member) + if (member != null) return OptionValue.MemberOptionValue(member, focused) val user = resolvedObjects?.users.orEmpty()[value.value] requireNotNull(user) { "user expected for $value but was missing" } - OptionValue.UserOptionValue(user) + OptionValue.UserOptionValue(user, focused) } } } From 65c094836a1eb6e12c51ddc6003a2c8a77892695 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Fri, 29 Oct 2021 15:42:52 +0200 Subject: [PATCH 06/13] Make DiscordApplicationCommand.description nullable --- common/src/main/kotlin/entity/Interactions.kt | 5 ++++- .../kotlin/cache/data/ApplicationCommandData.kt | 17 ++++++++++++----- .../application/ChatInputCommandCommand.kt | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index 9daffb8874db..53fe1eb065d0 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -43,7 +43,10 @@ data class DiscordApplicationCommand( @SerialName("application_id") val applicationId: Snowflake, val name: String, - val description: 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, val options: Optional> = Optional.Missing(), diff --git a/core/src/main/kotlin/cache/data/ApplicationCommandData.kt b/core/src/main/kotlin/cache/data/ApplicationCommandData.kt index a7fa0d5c1f7b..dc3aa3e32dfa 100644 --- a/core/src/main/kotlin/cache/data/ApplicationCommandData.kt +++ b/core/src/main/kotlin/cache/data/ApplicationCommandData.kt @@ -2,10 +2,17 @@ package dev.kord.core.cache.data import dev.kord.cache.api.data.DataDescription import dev.kord.cache.api.data.description -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.* -import dev.kord.common.entity.optional.* -import dev.kord.core.entity.application.ApplicationCommand +import dev.kord.common.entity.ApplicationCommandOption +import dev.kord.common.entity.ApplicationCommandOptionType +import dev.kord.common.entity.ApplicationCommandType +import dev.kord.common.entity.Choice +import dev.kord.common.entity.DiscordApplicationCommand +import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean +import dev.kord.common.entity.optional.OptionalSnowflake +import dev.kord.common.entity.optional.mapList +import dev.kord.common.entity.optional.orEmpty import kotlinx.serialization.Serializable @Serializable @@ -14,7 +21,7 @@ public data class ApplicationCommandData( val type: Optional = Optional.Missing(), val applicationId: Snowflake, val name: String, - val description: String, + val description: String?, val guildId: OptionalSnowflake, val options: Optional>, val defaultPermission: OptionalBoolean = OptionalBoolean.Missing, diff --git a/core/src/main/kotlin/entity/application/ChatInputCommandCommand.kt b/core/src/main/kotlin/entity/application/ChatInputCommandCommand.kt index ea7ac5129f36..5c61b74b367b 100644 --- a/core/src/main/kotlin/entity/application/ChatInputCommandCommand.kt +++ b/core/src/main/kotlin/entity/application/ChatInputCommandCommand.kt @@ -15,7 +15,7 @@ import dev.kord.rest.service.InteractionService public sealed interface ChatInputCommandCommand : ApplicationCommand, ChatInputCommandBehavior { - public val description: String + public val description: String? get() = data.description /** * The groups of this command, each group contains at least one [sub command][ChatInputSubCommand]. From 2a25753396c043fc2091ab282570c5e0c7453f3e Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Fri, 29 Oct 2021 16:08:01 +0200 Subject: [PATCH 07/13] Make guild/global variants of events and interactions --- .../entity/interaction/ContextInteraction.kt | 52 +++++++++++++++++-- .../kotlin/entity/interaction/Interaction.kt | 5 +- .../event/interaction/ApplicationCreate.kt | 43 +++++++++++++-- .../handler/InteractionEventHandler.kt | 3 +- 4 files changed, 89 insertions(+), 14 deletions(-) diff --git a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt index d4244037c46a..799a4821ce8e 100644 --- a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt @@ -2,8 +2,10 @@ package dev.kord.core.entity.interaction import dev.kord.common.entity.ApplicationCommandType import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.OptionalSnowflake import dev.kord.common.entity.optional.unwrap import dev.kord.core.Kord +import dev.kord.core.behavior.GuildInteractionBehavior import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.interaction.ApplicationCommandInteractionBehavior @@ -142,7 +144,7 @@ public sealed interface MessageCommandInteraction : ApplicationCommandInteractio public suspend fun getTarget(): Message = supplier.getMessage(channelId, targetId) - public suspend fun getTargetOrNull(): Message? = supplier.getMessageOrNull(channelId, targetId) + public suspend fun getTargetOrNull(): Message? = supplier.getMessageOrNull(channelId, targetId) public val messages: Map get() = resolvedObjects!!.messages!! @@ -214,12 +216,52 @@ public class UnknownApplicationCommandInteraction( * * Check [AutoCompleteInteractionBehavior] for response options */ -public class AutoCompleteInteraction( +public sealed interface AutoCompleteInteraction : AutoCompleteInteractionBehavior, ChatInputCommandInteraction + +internal fun AutoCompleteInteraction( + data: InteractionData, + kord: Kord, + supplier: EntitySupplier = kord.defaultSupplier +): ApplicationCommandInteraction = when (data.guildId) { + is OptionalSnowflake.Value -> GuildAutoCompleteInteraction( + data, kord, supplier + ) + else -> GlobalAutoCompleteInteraction(data, kord, supplier) +} + +/** + * Interaction indicating an auto-complete request from Discord. + * + * **Follow-ups and normals responses don't work on this type** + * + * @see ApplicationCommandInteraction + */ +public class GlobalAutoCompleteInteraction( + override val data: InteractionData, + override val kord: Kord, + override val supplier: EntitySupplier = kord.defaultSupplier +) : AutoCompleteInteraction, GlobalApplicationCommandInteraction { + override fun withStrategy(strategy: EntitySupplyStrategy<*>): GlobalAutoCompleteInteraction = + GlobalAutoCompleteInteraction(data, kord, strategy.supply(kord)) +} + +/** + * Interaction indicating an auto-complete request from Discord on a guild. + * + * **Follow-ups and normals responses don't work on this type** + * + * @see ApplicationCommandInteraction + */ +public class GuildAutoCompleteInteraction( override val data: InteractionData, - override val user: UserBehavior, override val kord: Kord, override val supplier: EntitySupplier = kord.defaultSupplier -) : AutoCompleteInteractionBehavior, ChatInputCommandInteraction { +) : AutoCompleteInteraction, GuildInteractionBehavior { + override val guildId: Snowflake + get() = data.guildId.value!! + + override val user: User get() = User(data.user.value!!, kord) + override fun withStrategy(strategy: EntitySupplyStrategy<*>): Interaction = - AutoCompleteInteraction(data, user, kord, strategy.supply(kord)) + GuildAutoCompleteInteraction(data, kord, strategy.supply(kord)) } diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt index 4c5c4b50e1c6..80c0c8baa523 100644 --- a/core/src/main/kotlin/entity/interaction/Interaction.kt +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -62,10 +62,7 @@ public sealed interface Interaction : InteractionBehavior { ): Interaction { return when { data.type == InteractionType.Component -> ComponentInteraction(data, kord, strategy.supply(kord)) - data.type == InteractionType.AutoComplete -> { - val user = User(data.user.value!!, kord, strategy.supply(kord)) - AutoCompleteInteraction(data, user, kord, strategy.supply(kord)) - } + data.type == InteractionType.AutoComplete -> AutoCompleteInteraction(data, kord, strategy.supply(kord)) data.guildId !is OptionalSnowflake.Missing -> GuildApplicationCommandInteraction( data, kord, diff --git a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt index e1e2fe99d440..8edb0c059cd2 100644 --- a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt +++ b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt @@ -108,9 +108,46 @@ public class GlobalChatInputCommandInteractionCreateEvent( * * @see AutoCompleteInteraction */ -public class AutoCompleteInteractionCreateEvent( - override val interaction: AutoCompleteInteraction, +public sealed interface AutoCompleteInteractionCreateEvent : InteractionCreateEvent, ChatInputCommandInteractionCreateEvent + +internal fun AutoCompleteInteractionCreateEvent( + interaction: AutoCompleteInteraction, + kord: Kord, + shard: Int, + coroutineScope: CoroutineScope = kordCoroutineScope(kord) +): AutoCompleteInteractionCreateEvent = when (interaction) { + is GuildAutoCompleteInteraction -> GuildAutoCompleteInteractionCreateEvent( + kord, shard, interaction, coroutineScope + ) + else -> GlobalAutoCompleteInteractionCreateEvent( + kord, shard, interaction as GlobalAutoCompleteInteraction, coroutineScope + ) +} + +/** + * Interaction received when a users types into an auto-completed option. + * + * Check [AutoCompleteInteractionBehavior] on how to reply. + * + * @see AutoCompleteInteraction + */ +public class GlobalAutoCompleteInteractionCreateEvent( + override val kord: Kord, + override val shard: Int, + override val interaction: GlobalAutoCompleteInteraction, + public val coroutineScope: CoroutineScope = kordCoroutineScope(kord) +) : AutoCompleteInteractionCreateEvent, GlobalApplicationInteractionCreateEvent, CoroutineScope by coroutineScope + +/** + * Interaction received when a users types into an auto-completed option. + * + * Check [AutoCompleteInteractionBehavior] on how to reply. + * + * @see AutoCompleteInteraction + */ +public class GuildAutoCompleteInteractionCreateEvent( override val kord: Kord, override val shard: Int, + override val interaction: GuildAutoCompleteInteraction, public val coroutineScope: CoroutineScope = kordCoroutineScope(kord) -) : InteractionCreateEvent, CoroutineScope by coroutineScope +) : AutoCompleteInteractionCreateEvent, CoroutineScope by coroutineScope diff --git a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt index 87d79b56d2d8..c17ae05b7b5e 100644 --- a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt +++ b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt @@ -69,8 +69,7 @@ public class InteractionEventHandler( private fun handle(event: InteractionCreate, shard: Int, kord: Kord, coroutineScope: CoroutineScope): CoreEvent { val data = InteractionData.from(event.interaction) - val interaction = Interaction.from(data, kord) - val coreEvent = when(interaction) { + val coreEvent = when(val interaction = Interaction.from(data, kord)) { is AutoCompleteInteraction -> AutoCompleteInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalChatInputCommandInteraction -> GlobalChatInputCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalUserCommandInteraction -> GlobalUserCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) From 8e56fb124b045c334c43724d5553cf2ad705e824 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Fri, 29 Oct 2021 17:34:33 +0200 Subject: [PATCH 08/13] Improve documentation and method naming --- .../AutoCompleteInteractionBehavior.kt | 15 +++++++++++---- .../kotlin/builder/interaction/OptionsBuilder.kt | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/behavior/interaction/AutoCompleteInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/AutoCompleteInteractionBehavior.kt index a70e357d5f75..a799c94e0060 100644 --- a/core/src/main/kotlin/behavior/interaction/AutoCompleteInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/AutoCompleteInteractionBehavior.kt @@ -22,10 +22,12 @@ public interface AutoCompleteInteractionBehavior : InteractionBehavior /** * Responds with the int choices specified by [builder]. * + * The provided choices are only suggestions and the user can provide any other input as well. + * * @see IntChoiceBuilder */ @OptIn(ExperimentalContracts::class) -public suspend inline fun AutoCompleteInteractionBehavior.respondInt(builder: IntChoiceBuilder.() -> Unit) { +public suspend inline fun AutoCompleteInteractionBehavior.suggestInt(builder: IntChoiceBuilder.() -> Unit) { contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } @@ -36,10 +38,11 @@ public suspend inline fun AutoCompleteInteractionBehavior.respondInt(builder: In /** * Responds with the number choices specified by [builder]. * + * The provided choices are only suggestions and the user can provide any other input as well. * @see NumberChoiceBuilder */ @OptIn(ExperimentalContracts::class) -public suspend inline fun AutoCompleteInteractionBehavior.respondNumber(builder: NumberChoiceBuilder.() -> Unit) { +public suspend inline fun AutoCompleteInteractionBehavior.suggestNumber(builder: NumberChoiceBuilder.() -> Unit) { contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } @@ -50,10 +53,12 @@ public suspend inline fun AutoCompleteInteractionBehavior.respondNumber(builder: /** * Responds with the string choices specified by [builder]. * + * The provided choices are only suggestions and the user can provide any other input as well. + * * @see StringChoiceBuilder */ @OptIn(ExperimentalContracts::class) -public suspend inline fun AutoCompleteInteractionBehavior.respondString(builder: StringChoiceBuilder.() -> Unit) { +public suspend inline fun AutoCompleteInteractionBehavior.suggestString(builder: StringChoiceBuilder.() -> Unit) { contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } @@ -63,8 +68,10 @@ public suspend inline fun AutoCompleteInteractionBehavior.respondString(builder: /** * Responds with [choices] to this auto-complete request. + * + * The provided choices are only suggestions and the user can provide any other input as well. */ -public suspend inline fun AutoCompleteInteractionBehavior.respond(choices: List>) { +public suspend inline fun AutoCompleteInteractionBehavior.suggest(choices: List>) { kord.rest.interaction.createAutoCompleteInteractionResponse( id, token, diff --git a/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt index 1b93a77fcd3e..6f1411fefdf4 100644 --- a/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt @@ -31,7 +31,7 @@ sealed class OptionsBuilder( /** * Setting this to `true` allows you to dynamically respond with your choices, depending on the user input. * - * **Warning:** This disables all input validation, users can submit values before responding to the AutoComplete request + * This disables all input validation, users can submit values before responding to the AutoComplete request * * **Note:** If you set this to `true` you can't add any other choice * From 89175ab0479ea50af0d05f7708980d71d3f72935 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 17 Nov 2021 16:27:45 +0100 Subject: [PATCH 09/13] Rename Interaction to MessageRespondingInteraction --- .../behavior/GuildInteractionBehavior.kt | 6 +- .../ApplicationCommandInteractionBehavior.kt | 4 +- .../ComponentInteractionBehavior.kt | 2 +- .../EphemeralInteractionResponseBehavior.kt | 3 +- .../interaction/InteractionBehavior.kt | 113 +---- .../InteractionResponseBehavior.kt | 2 +- .../MessageRespondingInteractionBehavior.kt | 111 +++++ .../PublicInteractionResponseBehavior.kt | 2 +- core/src/main/kotlin/entity/Message.kt | 19 +- .../interaction/ComponentInteraction.kt | 2 +- .../entity/interaction/ContextInteraction.kt | 10 +- .../kotlin/entity/interaction/Interaction.kt | 430 +----------------- .../entity/interaction/MessageInteraction.kt | 6 +- .../MessageRespondingInteraction.kt | 423 +++++++++++++++++ .../event/interaction/ApplicationCreate.kt | 14 +- .../event/interaction/InteractionCreate.kt | 4 +- .../exception/EntityNotFoundException.kt | 2 +- .../handler/InteractionEventHandler.kt | 4 +- 18 files changed, 585 insertions(+), 572 deletions(-) create mode 100644 core/src/main/kotlin/behavior/interaction/MessageRespondingInteractionBehavior.kt create mode 100644 core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt diff --git a/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt b/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt index e9348b853413..5f2a01e47a26 100644 --- a/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt @@ -1,13 +1,13 @@ package dev.kord.core.behavior -import dev.kord.core.behavior.interaction.InteractionBehavior import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.interaction.MessageRespondingInteractionBehavior import dev.kord.core.entity.Guild /** - * The behavior of a [dev.kord.core.entity.interaction.Interaction][Interaction] that was invoked in a [Guild] + * The behavior of a [dev.kord.core.entity.interaction.MessageRespondingInteraction][MessageRespondingInteraction] that was invoked in a [Guild] */ -public interface GuildInteractionBehavior : InteractionBehavior { +public interface GuildInteractionBehavior : MessageRespondingInteractionBehavior { public val guildId: Snowflake diff --git a/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt index 05f69dc87853..6f969d665d52 100644 --- a/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt @@ -5,11 +5,11 @@ import dev.kord.core.Kord import dev.kord.core.supplier.EntitySupplier /** - * The behavior of a [Discord Interaction](https://discord.com/developers/docs/interactions/slash-commands#interaction) + * The behavior of a [Discord MessageRespondingInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) * with [Application Command type][dev.kord.common.entity.ApplicationCommandType] */ -public interface ApplicationCommandInteractionBehavior : InteractionBehavior +public interface ApplicationCommandInteractionBehavior : MessageRespondingInteractionBehavior internal fun ApplicationCommandInteractionBehavior( id: Snowflake, diff --git a/core/src/main/kotlin/behavior/interaction/ComponentInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/ComponentInteractionBehavior.kt index cf2dfd7c22a8..912f8e2b46e9 100644 --- a/core/src/main/kotlin/behavior/interaction/ComponentInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/ComponentInteractionBehavior.kt @@ -16,7 +16,7 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract -public interface ComponentInteractionBehavior : InteractionBehavior { +public interface ComponentInteractionBehavior : MessageRespondingInteractionBehavior { /** * Acknowledges a component interaction publicly with the intent of updating it later. diff --git a/core/src/main/kotlin/behavior/interaction/EphemeralInteractionResponseBehavior.kt b/core/src/main/kotlin/behavior/interaction/EphemeralInteractionResponseBehavior.kt index 581a7c016223..01a702e110d5 100644 --- a/core/src/main/kotlin/behavior/interaction/EphemeralInteractionResponseBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/EphemeralInteractionResponseBehavior.kt @@ -1,11 +1,10 @@ package dev.kord.core.behavior.interaction -import dev.kord.common.annotation.KordUnsafe import dev.kord.common.entity.Snowflake import dev.kord.core.Kord /** - * The behavior of a ephemeral [Discord Interaction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) + * The behavior of a ephemeral [Discord MessageRespondingInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) * This response is visible to *only* to the user who made the interaction. */ diff --git a/core/src/main/kotlin/behavior/interaction/InteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/InteractionBehavior.kt index b99eb06592ab..7ce10dfda4a9 100644 --- a/core/src/main/kotlin/behavior/interaction/InteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/InteractionBehavior.kt @@ -1,27 +1,15 @@ package dev.kord.core.behavior.interaction import dev.kord.common.entity.Snowflake -import dev.kord.core.Kord import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.entity.KordEntity -import dev.kord.core.entity.Message import dev.kord.core.entity.Strategizable import dev.kord.core.entity.channel.MessageChannel -import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.core.supplier.getChannelOf import dev.kord.core.supplier.getChannelOfOrNull -import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -/** - * The behavior of a [Discord Interaction](https://discord.com/developers/docs/interactions/slash-commands#interaction) - */ public interface InteractionBehavior : KordEntity, Strategizable { - public val applicationId: Snowflake public val token: String public val channelId: Snowflake @@ -33,107 +21,8 @@ public interface InteractionBehavior : KordEntity, Strategizable { public suspend fun getChannelOrNull(): MessageChannel? = supplier.getChannelOfOrNull(channelId) - public suspend fun getChannel(): MessageChannel = supplier.getChannelOf(channelId) - - /** - * Acknowledges an interaction ephemerally. - * - * @return [EphemeralInteractionResponseBehavior] Ephemeral acknowledgement of the interaction. - */ - public suspend fun acknowledgeEphemeral(): EphemeralInteractionResponseBehavior { - kord.rest.interaction.acknowledge(id, token, true) - return EphemeralInteractionResponseBehavior(applicationId, token, kord) - } - - /** - * Acknowledges an interaction. - * - * @return [PublicInteractionResponseBehavior] public acknowledgement of an interaction. - */ - public suspend fun acknowledgePublic(): PublicInteractionResponseBehavior { - kord.rest.interaction.acknowledge(id, token) - return PublicInteractionResponseBehavior(applicationId, token, kord) - } - - - public suspend fun getOriginalInteractionResponse(): Message? { - return EntitySupplyStrategy.rest.supply(kord).getOriginalInteractionOrNull(applicationId, token) - } - - - override fun withStrategy(strategy: EntitySupplyStrategy<*>): InteractionBehavior = + override fun withStrategy(strategy: EntitySupplyStrategy<*>): MessageRespondingInteractionBehavior = InteractionBehavior(id, channelId, token, applicationId, kord, strategy) - -} - - -/** - * Acknowledges an interaction and responds with [PublicInteractionResponseBehavior]. - * - * @param builder [InteractionResponseCreateBuilder] used to create a public response. - * @return [PublicInteractionResponseBehavior] public response to the interaction. - */ - -@OptIn(ExperimentalContracts::class) -public suspend inline fun InteractionBehavior.respondPublic( - builder: InteractionResponseCreateBuilder.() -> Unit -): PublicInteractionResponseBehavior { - - contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } - - val request = InteractionResponseCreateBuilder().apply(builder).toRequest() - kord.rest.interaction.createInteractionResponse(id, token, request) - return PublicInteractionResponseBehavior(applicationId, token, kord) - -} - - -/** - * Acknowledges an interaction and responds with [EphemeralInteractionResponseBehavior] with ephemeral flag. - * - * @param builder [InteractionResponseCreateBuilder] used to a create an ephemeral response. - * @return [InteractionResponseBehavior] ephemeral response to the interaction. - */ - -@OptIn(ExperimentalContracts::class) -public suspend inline fun InteractionBehavior.respondEphemeral( - builder: InteractionResponseCreateBuilder.() -> Unit -): EphemeralInteractionResponseBehavior { - - contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } - val builder = InteractionResponseCreateBuilder(true).apply(builder) - val request = builder.toRequest() - kord.rest.interaction.createInteractionResponse(id, token, request) - return EphemeralInteractionResponseBehavior(applicationId, token, kord) - -} - -public fun InteractionBehavior( - id: Snowflake, - channelId: Snowflake, - token: String, - applicationId: Snowflake, - kord: Kord, - strategy: EntitySupplyStrategy<*> = kord.resources.defaultStrategy -): InteractionBehavior = object : InteractionBehavior { - override val id: Snowflake - get() = id - - override val token: String - get() = token - - override val applicationId: Snowflake - get() = applicationId - - override val kord: Kord - get() = kord - - override val channelId: Snowflake - get() = channelId - - - override val supplier: EntitySupplier = strategy.supply(kord) - } diff --git a/core/src/main/kotlin/behavior/interaction/InteractionResponseBehavior.kt b/core/src/main/kotlin/behavior/interaction/InteractionResponseBehavior.kt index 13d4fb9d893e..70d97ee95f80 100644 --- a/core/src/main/kotlin/behavior/interaction/InteractionResponseBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/InteractionResponseBehavior.kt @@ -14,7 +14,7 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** - * The behavior of a [Discord Interaction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) + * The behavior of a [Discord MessageRespondingInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) */ public interface InteractionResponseBehavior : KordObject { diff --git a/core/src/main/kotlin/behavior/interaction/MessageRespondingInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/MessageRespondingInteractionBehavior.kt new file mode 100644 index 000000000000..cf4b1232e492 --- /dev/null +++ b/core/src/main/kotlin/behavior/interaction/MessageRespondingInteractionBehavior.kt @@ -0,0 +1,111 @@ +package dev.kord.core.behavior.interaction + +import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord +import dev.kord.core.entity.Message +import dev.kord.core.supplier.EntitySupplier +import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * The behavior of a [Discord MessageRespondingInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) which can respond with a message. + */ +public interface MessageRespondingInteractionBehavior : InteractionBehavior { + + /** + * Acknowledges an interaction ephemerally. + * + * @return [EphemeralInteractionResponseBehavior] Ephemeral acknowledgement of the interaction. + */ + public suspend fun acknowledgeEphemeral(): EphemeralInteractionResponseBehavior { + kord.rest.interaction.acknowledge(id, token, true) + return EphemeralInteractionResponseBehavior(applicationId, token, kord) + } + + /** + * Acknowledges an interaction. + * + * @return [PublicInteractionResponseBehavior] public acknowledgement of an interaction. + */ + public suspend fun acknowledgePublic(): PublicInteractionResponseBehavior { + kord.rest.interaction.acknowledge(id, token) + return PublicInteractionResponseBehavior(applicationId, token, kord) + } + + public suspend fun getOriginalInteractionResponse(): Message? { + return EntitySupplyStrategy.rest.supply(kord).getOriginalInteractionOrNull(applicationId, token) + } +} + + +/** + * Acknowledges an interaction and responds with [PublicInteractionResponseBehavior]. + * + * @param builder [InteractionResponseCreateBuilder] used to create a public response. + * @return [PublicInteractionResponseBehavior] public response to the interaction. + */ + +@OptIn(ExperimentalContracts::class) +public suspend inline fun MessageRespondingInteractionBehavior.respondPublic( + builder: InteractionResponseCreateBuilder.() -> Unit +): PublicInteractionResponseBehavior { + + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + + val request = InteractionResponseCreateBuilder().apply(builder).toRequest() + kord.rest.interaction.createInteractionResponse(id, token, request) + return PublicInteractionResponseBehavior(applicationId, token, kord) + +} + + +/** + * Acknowledges an interaction and responds with [EphemeralInteractionResponseBehavior] with ephemeral flag. + * + * @param builder [InteractionResponseCreateBuilder] used to a create an ephemeral response. + * @return [InteractionResponseBehavior] ephemeral response to the interaction. + */ + +@OptIn(ExperimentalContracts::class) +public suspend inline fun MessageRespondingInteractionBehavior.respondEphemeral( + builder: InteractionResponseCreateBuilder.() -> Unit +): EphemeralInteractionResponseBehavior { + + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + val builder = InteractionResponseCreateBuilder(true).apply(builder) + val request = builder.toRequest() + kord.rest.interaction.createInteractionResponse(id, token, request) + return EphemeralInteractionResponseBehavior(applicationId, token, kord) + +} + +public fun InteractionBehavior( + id: Snowflake, + channelId: Snowflake, + token: String, + applicationId: Snowflake, + kord: Kord, + strategy: EntitySupplyStrategy<*> = kord.resources.defaultStrategy +): MessageRespondingInteractionBehavior = object : MessageRespondingInteractionBehavior { + override val id: Snowflake + get() = id + + override val token: String + get() = token + + override val applicationId: Snowflake + get() = applicationId + + override val kord: Kord + get() = kord + + override val channelId: Snowflake + get() = channelId + + + override val supplier: EntitySupplier = strategy.supply(kord) + +} diff --git a/core/src/main/kotlin/behavior/interaction/PublicInteractionResponseBehavior.kt b/core/src/main/kotlin/behavior/interaction/PublicInteractionResponseBehavior.kt index 6ca64d7c976d..9bb3bdefc71f 100644 --- a/core/src/main/kotlin/behavior/interaction/PublicInteractionResponseBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/PublicInteractionResponseBehavior.kt @@ -6,7 +6,7 @@ import dev.kord.rest.request.RestRequestException /** - * The behavior of a public [Discord Interaction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) + * The behavior of a public [Discord MessageRespondingInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) * This response is visible to all users in the channel. */ diff --git a/core/src/main/kotlin/entity/Message.kt b/core/src/main/kotlin/entity/Message.kt index 20630b351850..13749a77eca6 100644 --- a/core/src/main/kotlin/entity/Message.kt +++ b/core/src/main/kotlin/entity/Message.kt @@ -11,19 +11,26 @@ import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.cache.data.MessageData -import dev.kord.core.entity.channel.* +import dev.kord.core.entity.channel.Channel +import dev.kord.core.entity.channel.GuildChannel +import dev.kord.core.entity.channel.MessageChannel +import dev.kord.core.entity.channel.TopGuildMessageChannel import dev.kord.core.entity.component.Component -import dev.kord.core.entity.interaction.Interaction import dev.kord.core.entity.interaction.MessageInteraction +import dev.kord.core.entity.interaction.MessageRespondingInteraction import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.core.supplier.getChannelOf import dev.kord.core.supplier.getChannelOfOrNull -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import kotlinx.datetime.Instant import kotlinx.datetime.toInstant -import java.util.* +import java.util.Objects /** * An instance of a [Discord Message][https://discord.com/developers/docs/resources/channel#message-object]. @@ -105,7 +112,7 @@ public class Message( public val stickers: List get() = data.stickers.orEmpty().map { MessageSticker(it, kord) } /** - * If the message is a response to an [Interaction], this is the id of the interaction's application + * If the message is a response to an [MessageRespondingInteraction], this is the id of the interaction's application */ public val applicationId: Snowflake? get() = data.application.unwrap { it.id } @@ -182,7 +189,7 @@ public class Message( public val mentionedUserBehaviors: Set get() = data.mentions.map { UserBehavior(it, kord) }.toSet() /** - * The [MessageInteraction] sent on this message object when it is a response to an [dev.kord.core.entity.interaction.Interaction]. + * The [MessageInteraction] sent on this message object when it is a response to an [dev.kord.core.entity.interaction.MessageRespondingInteraction]. */ public val interaction: MessageInteraction? get() = data.interaction.mapNullable { MessageInteraction(it, kord) }.value diff --git a/core/src/main/kotlin/entity/interaction/ComponentInteraction.kt b/core/src/main/kotlin/entity/interaction/ComponentInteraction.kt index e82395792e52..40e3a66d1576 100644 --- a/core/src/main/kotlin/entity/interaction/ComponentInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/ComponentInteraction.kt @@ -22,7 +22,7 @@ import dev.kord.rest.builder.component.SelectMenuBuilder * @see SelectMenuInteraction */ -public sealed interface ComponentInteraction : Interaction, ComponentInteractionBehavior { +public sealed interface ComponentInteraction : MessageRespondingInteraction, ComponentInteractionBehavior { override val user: User get() = User(data.user.value!!, kord) diff --git a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt index 799a4821ce8e..741e8e84a57d 100644 --- a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt @@ -20,7 +20,7 @@ import java.util.Objects /** * Represents an interaction of type [ApplicationCommand][dev.kord.common.entity.InteractionType.ApplicationCommand] */ -public sealed interface ApplicationCommandInteraction : Interaction, ApplicationCommandInteractionBehavior { +public sealed interface ApplicationCommandInteraction : MessageRespondingInteraction, ApplicationCommandInteractionBehavior { public val invokedCommandId: Snowflake get() = data.data.id.value!! public val name: String get() = data.data.name.value!! @@ -210,7 +210,7 @@ public class UnknownApplicationCommandInteraction( } /** - * Interaction indicating an auto-complete request from Discord. + * MessageRespondingInteraction indicating an auto-complete request from Discord. * * **Follow-ups and normals responses don't work on this type** * @@ -230,7 +230,7 @@ internal fun AutoCompleteInteraction( } /** - * Interaction indicating an auto-complete request from Discord. + * MessageRespondingInteraction indicating an auto-complete request from Discord. * * **Follow-ups and normals responses don't work on this type** * @@ -246,7 +246,7 @@ public class GlobalAutoCompleteInteraction( } /** - * Interaction indicating an auto-complete request from Discord on a guild. + * MessageRespondingInteraction indicating an auto-complete request from Discord on a guild. * * **Follow-ups and normals responses don't work on this type** * @@ -262,6 +262,6 @@ public class GuildAutoCompleteInteraction( override val user: User get() = User(data.user.value!!, kord) - override fun withStrategy(strategy: EntitySupplyStrategy<*>): Interaction = + override fun withStrategy(strategy: EntitySupplyStrategy<*>): MessageRespondingInteraction = GuildAutoCompleteInteraction(data, kord, strategy.supply(kord)) } diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt index 80c0c8baa523..3bcfcac7797f 100644 --- a/core/src/main/kotlin/entity/interaction/Interaction.kt +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -1,28 +1,15 @@ package dev.kord.core.entity.interaction -import dev.kord.common.entity.* -import dev.kord.common.entity.optional.* +import dev.kord.common.entity.InteractionType +import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.OptionalSnowflake import dev.kord.core.Kord -import dev.kord.core.KordObject -import dev.kord.core.behavior.* -import dev.kord.core.behavior.channel.GuildMessageChannelBehavior +import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.interaction.InteractionBehavior -import dev.kord.core.cache.data.ApplicationInteractionData import dev.kord.core.cache.data.InteractionData -import dev.kord.core.cache.data.ResolvedObjectsData -import dev.kord.core.entity.* -import dev.kord.core.entity.application.GlobalApplicationCommand -import dev.kord.core.entity.channel.ResolvedChannel -import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy -import dev.kord.rest.service.InteractionService - -/** - * An instance of [Interaction] (https://discord.com/developers/docs/interactions/slash-commands#interaction) - */ - -public sealed interface Interaction : InteractionBehavior { +public interface Interaction : InteractionBehavior { public val data: InteractionData override val id: Snowflake get() = data.id @@ -52,14 +39,14 @@ public sealed interface Interaction : InteractionBehavior { */ public val version: Int get() = data.version - abstract override fun withStrategy(strategy: EntitySupplyStrategy<*>): Interaction + abstract override fun withStrategy(strategy: EntitySupplyStrategy<*>): MessageRespondingInteraction public companion object { public fun from( data: InteractionData, kord: Kord, strategy: EntitySupplyStrategy<*> = kord.resources.defaultStrategy - ): Interaction { + ): MessageRespondingInteraction { return when { data.type == InteractionType.Component -> ComponentInteraction(data, kord, strategy.supply(kord)) data.type == InteractionType.AutoComplete -> AutoCompleteInteraction(data, kord, strategy.supply(kord)) @@ -72,407 +59,4 @@ public sealed interface Interaction : InteractionBehavior { } } } - -} - -/** - * The base interaction for all slash-command related interactions. - * - * @see DmInteraction - * @see GuildApplicationCommandInteraction - */ - -public sealed class CommandInteraction : Interaction { - public val command: InteractionCommand - get() = InteractionCommand(data.data, kord) -} - -/** - * The base command of all commands that can be executed under an interaction event. - */ - -public sealed interface InteractionCommand : KordObject { - /** - * The id of the root command. - */ - public val rootId: Snowflake - - /** - * The root command name - */ - public val rootName: String - - /** - * the values passed to the command. - */ - public val options: Map> - - public val resolved: ResolvedObjects? - - public val strings: Map get() = filterOptions() - - public val integers: Map get() = filterOptions() - - public val numbers: Map get() = filterOptions() - - public val booleans: Map get() = filterOptions() - - public val users: Map get() = filterOptions() - - public val members: Map get() = filterOptions() - - public val channels: Map get() = filterOptions() - - public val roles: Map get() = filterOptions() - - public val mentionables: Map get() = filterOptions() - - private inline fun filterOptions(): Map { - return buildMap { - options.onEach { (key, value) -> - val wrappedValue = value.value - if (wrappedValue is T) put(key, wrappedValue) - } - } - } -} - -public fun InteractionCommand( - data: ApplicationInteractionData, - kord: Kord -): InteractionCommand { - val firstLevelOptions = data.options.orEmpty() - val rootPredicate = firstLevelOptions.isEmpty() || firstLevelOptions.any { it.value.value != null } - val groupPredicate = firstLevelOptions.any { it.subCommands.orEmpty().isNotEmpty() } - val subCommandPredicate = - firstLevelOptions.all { it.value is Optional.Missing && it.subCommands is Optional.Missing } - - return when { - rootPredicate -> RootCommand(data, kord) - groupPredicate -> GroupCommand(data, kord) - subCommandPredicate -> SubCommand(data, kord) - else -> error("The interaction data provided is not an chat input command") - } -} - -/** - * Represents an invocation of a root command. - * - * The root command is the first command defined in a slash-command structure. - */ - -public class RootCommand( - public val data: ApplicationInteractionData, - override val kord: Kord -) : InteractionCommand { - - override val rootId: Snowflake - get() = data.id.value!! - - override val rootName: String get() = data.name.value!! - - override val options: Map> - get() = data.options.orEmpty() - .associate { it.name to OptionValue(it.value.value!!, resolved) } - - override val resolved: ResolvedObjects? - get() = data.resolvedObjectsData.unwrap { ResolvedObjects(it, kord) } - -} - -/** - * Represents an invocation of a sub-command under the [RootCommand] - */ - -public class SubCommand( - public val data: ApplicationInteractionData, - override val kord: Kord -) : InteractionCommand { - - private val subCommandData = data.options.orEmpty().first() - - override val rootName: String get() = data.name.value!! - - override val rootId: Snowflake - get() = data.id.value!! - - /** - * Name of the sub-command executed. - */ - public val name: String get() = subCommandData.name - - override val options: Map> - get() = subCommandData.values.orEmpty() - .associate { it.name to OptionValue(it, resolved) } - - - override val resolved: ResolvedObjects? - get() = data.resolvedObjectsData.unwrap { ResolvedObjects(it, kord) } - - -} - -/** - * Represents an invocation of a sub-command under a group. - */ - -public class GroupCommand( - public val data: ApplicationInteractionData, - override val kord: Kord -) : InteractionCommand { - - private val groupData get() = data.options.orEmpty().first() - private val subCommandData get() = groupData.subCommands.orEmpty().first() - - override val rootId: Snowflake - get() = data.id.value!! - - override val rootName: String get() = data.name.value!! - - /** - * Name of the group of this sub-command. - */ - public val groupName: String get() = groupData.name - - /** - * Name of this sub-command - */ - public val name: String get() = subCommandData.name - - override val options: Map> - get() = subCommandData.options.orEmpty() - .associate { it.name to OptionValue(it, resolved) } - - - override val resolved: ResolvedObjects? - get() = data.resolvedObjectsData.unwrap { ResolvedObjects(it, kord) } - -} - - -public class ResolvedObjects( - public val data: ResolvedObjectsData, - public val kord: Kord, - public val strategy: EntitySupplyStrategy<*> = kord.resources.defaultStrategy -) { - public val channels: Map? - get() = data.channels.mapValues { ResolvedChannel(it.value, kord, strategy) }.value - - public val roles: Map? get() = data.roles.mapValues { Role(it.value, kord) }.value - - public val users: Map? get() = data.users.mapValues { User(it.value, kord) }.value - - public val members: Map? - get() = data.members.mapValues { Member(it.value, users!![it.key]!!.data, kord) }.value - - public val messages: Map? - get() = data.messages.mapValues { Message(it.value, kord) }.value - -} - - -public sealed class OptionValue(public val value: T, public val focused: Boolean) { - - public class RoleOptionValue(value: Role, focused: Boolean) : OptionValue(value, focused) { - override fun toString(): String = "RoleOptionValue(value=$value)" - } - - public open class UserOptionValue(value: User, focused: Boolean) : OptionValue(value, focused) { - override fun toString(): String = "UserOptionValue(value=$value)" - } - - public class MemberOptionValue(value: Member, focused: Boolean) : UserOptionValue(value, focused) { - override fun toString(): String = "MemberOptionValue(value=$value)" - } - - public class ChannelOptionValue(value: ResolvedChannel, focused: Boolean) : - OptionValue(value, focused) { - override fun toString(): String = "ChannelOptionValue(value=$value)" - } - - public class IntOptionValue(value: Long, focused: Boolean) : OptionValue(value, focused) { - override fun toString(): String = "IntOptionValue(value=$value)" - } - - - public class NumberOptionValue(value: Double, focused: Boolean) : OptionValue(value, focused) { - override fun toString(): String = "DoubleOptionValue(value=$value)" - } - - public class StringOptionValue(value: String, focused: Boolean) : OptionValue(value, focused) { - override fun toString(): String = "StringOptionValue(value=$value)" - } - - public class BooleanOptionValue(value: Boolean, focused: Boolean) : OptionValue(value, focused) { - override fun toString(): String = "BooleanOptionValue(value=$value)" - } - - public class MentionableOptionValue(value: Entity, focused: Boolean) : OptionValue(value, focused) { - override fun toString(): String = "MentionableOptionValue(value=$value)" - } - -} - - -public fun OptionValue(value: CommandArgument<*>, resolvedObjects: ResolvedObjects?): OptionValue<*> { - val focused = value.focused.orElse(false) - return when (value) { - is CommandArgument.NumberArgument -> OptionValue.NumberOptionValue(value.value, focused) - is CommandArgument.BooleanArgument -> OptionValue.BooleanOptionValue(value.value, focused) - is CommandArgument.IntegerArgument -> OptionValue.IntOptionValue(value.value, focused) - is CommandArgument.StringArgument -> OptionValue.StringOptionValue(value.value, focused) - is CommandArgument.ChannelArgument -> { - val channel = resolvedObjects?.channels.orEmpty()[value.value] - requireNotNull(channel) { "channel expected for $value but was missing" } - - OptionValue.ChannelOptionValue(channel, focused) - } - - is CommandArgument.MentionableArgument -> { - val channel = resolvedObjects?.channels.orEmpty()[value.value] - val user = resolvedObjects?.users.orEmpty()[value.value] - val member = resolvedObjects?.members.orEmpty()[value.value] - val role = resolvedObjects?.roles.orEmpty()[value.value] - - val entity = channel ?: member ?: user ?: role - requireNotNull(entity) { "user, member, or channel expected for $value but was missing" } - - OptionValue.MentionableOptionValue(entity, focused) - } - - is CommandArgument.RoleArgument -> { - val role = resolvedObjects?.roles.orEmpty()[value.value] - requireNotNull(role) { "role expected for $value but was missing" } - - OptionValue.RoleOptionValue(role, focused) - } - - is CommandArgument.UserArgument -> { - val member = resolvedObjects?.members.orEmpty()[value.value] - - if (member != null) return OptionValue.MemberOptionValue(member, focused) - - val user = resolvedObjects?.users.orEmpty()[value.value] - requireNotNull(user) { "user expected for $value but was missing" } - - OptionValue.UserOptionValue(user, focused) - } - } -} - - -/** - * An [Interaction] that took place in a Global Context with [GlobalApplicationCommand]. - */ - -public sealed interface GlobalApplicationCommandInteraction : ApplicationCommandInteraction, - GlobalApplicationCommandBehavior { - /** - * The user who invoked the interaction. - */ - - override val service: InteractionService - get() = kord.rest.interaction - - override val user: User get() = User(data.user.value!!, kord) - - override fun withStrategy(strategy: EntitySupplyStrategy<*>): GlobalApplicationCommandInteraction = - GlobalApplicationCommandInteraction(data, kord, strategy.supply(kord)) - - override val applicationId: Snowflake - get() = super.applicationId -} - -public fun GlobalApplicationCommandInteraction( - data: InteractionData, - kord: Kord, - supplier: EntitySupplier = kord.defaultSupplier -): GlobalApplicationCommandInteraction { - return when (data.data.type.value) { - ApplicationCommandType.ChatInput -> GlobalChatInputCommandInteraction(data, kord, supplier) - ApplicationCommandType.User -> GlobalUserCommandInteraction(data, kord, supplier) - ApplicationCommandType.Message -> GlobalMessageCommandInteraction(data, kord, supplier) - is ApplicationCommandType.Unknown -> error("Unknown interaction.") - null -> error("No component type was provided") - } -} - -/** - * An [Interaction] that took place in a Global Context with [dev.kord.core.entity.application.GuildApplicationCommand]. - */ - - -public sealed interface GuildApplicationCommandInteraction : ApplicationCommandInteraction, GuildInteractionBehavior { - - override val guildId: Snowflake - get() = data.guildId.value!! - - /** - * Overridden permissions of the interaction invoker in the channel. - */ - public val permissions: Permissions get() = data.permissions.value!! - - - /** - * The invoker of the command as [MemberBehavior]. - */ - public val member: Member - get() = Member( - data.member.value!!, - data.user.value!!, - kord - ) - - override val channel: GuildMessageChannelBehavior - get() = GuildMessageChannelBehavior(guildId, channelId, kord) - - override val user: UserBehavior - get() = UserBehavior(member.id, kord) - - override fun withStrategy(strategy: EntitySupplyStrategy<*>): GuildApplicationCommandInteraction = - GuildApplicationCommandInteraction(data, kord, strategy.supply(kord)) - -} - -public fun GuildApplicationCommandInteraction( - data: InteractionData, - kord: Kord, - supplier: EntitySupplier = kord.defaultSupplier -): GuildApplicationCommandInteraction { - return when (data.data.type.value) { - ApplicationCommandType.ChatInput -> GuildChatInputCommandInteraction(data, kord, supplier) - ApplicationCommandType.User -> GuildUserCommandInteraction(data, kord, supplier) - ApplicationCommandType.Message -> GuildMessageCommandInteraction(data, kord, supplier) - is ApplicationCommandType.Unknown -> error("Unknown interaction.") - null -> error("No interaction type provided.") - } -} - - -public fun OptionValue<*>.user(): User = value as User - - -public fun OptionValue<*>.channel(): ResolvedChannel = value as ResolvedChannel - - -public fun OptionValue<*>.role(): Role = value as Role - - -public fun OptionValue<*>.member(): Member = value as Member - - -public fun OptionValue<*>.string(): String = value.toString() - - -public fun OptionValue<*>.boolean(): Boolean = value as Boolean - - -public fun OptionValue<*>.int(): Long = value as Long - - -public fun OptionValue<*>.number(): Double = value as Double - - -public fun OptionValue<*>.mentionable(): Entity { - return value as Entity } diff --git a/core/src/main/kotlin/entity/interaction/MessageInteraction.kt b/core/src/main/kotlin/entity/interaction/MessageInteraction.kt index bc86eb6f472c..b2199b8f52bd 100644 --- a/core/src/main/kotlin/entity/interaction/MessageInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/MessageInteraction.kt @@ -16,7 +16,7 @@ import dev.kord.core.supplier.EntitySupplyStrategy /** * An instance of [MessageInteraction](https://discord.com/developers/docs/interactions/slash-commands#messageinteraction) - * This is sent on the [Message] object when the message is a response to an [Interaction]. + * This is sent on the [Message] object when the message is a response to an [MessageRespondingInteraction]. */ public class MessageInteraction( @@ -25,7 +25,7 @@ public class MessageInteraction( override val supplier: EntitySupplier = kord.defaultSupplier ) : KordEntity, Strategizable { /** - * [id][Interaction.id] of the [Interaction] this message is responding to. + * [id][MessageRespondingInteraction.id] of the [MessageRespondingInteraction] this message is responding to. */ override val id: Snowflake get() = data.id @@ -35,7 +35,7 @@ public class MessageInteraction( public val name: String get() = data.name /** - * The [UserBehavior] of the [user][Interaction.user] who invoked the [Interaction] + * The [UserBehavior] of the [user][MessageRespondingInteraction.user] who invoked the [MessageRespondingInteraction] */ public val user: UserBehavior get() = UserBehavior(data.user, kord) diff --git a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt b/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt new file mode 100644 index 000000000000..13d55ed4be51 --- /dev/null +++ b/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt @@ -0,0 +1,423 @@ +package dev.kord.core.entity.interaction + +import dev.kord.common.entity.* +import dev.kord.common.entity.optional.* +import dev.kord.core.Kord +import dev.kord.core.KordObject +import dev.kord.core.behavior.* +import dev.kord.core.behavior.channel.GuildMessageChannelBehavior +import dev.kord.core.behavior.interaction.MessageRespondingInteractionBehavior +import dev.kord.core.cache.data.ApplicationInteractionData +import dev.kord.core.cache.data.InteractionData +import dev.kord.core.cache.data.ResolvedObjectsData +import dev.kord.core.entity.* +import dev.kord.core.entity.application.GlobalApplicationCommand +import dev.kord.core.entity.channel.ResolvedChannel +import dev.kord.core.supplier.EntitySupplier +import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kord.rest.service.InteractionService + +/** + * An instance of [MessageRespondingInteraction] (https://discord.com/developers/docs/interactions/slash-commands#interaction) + */ +public sealed interface MessageRespondingInteraction : Interaction, MessageRespondingInteractionBehavior + +/** + * The base interaction for all slash-command related interactions. + * + * @see DmInteraction + * @see GuildApplicationCommandInteraction + */ +public sealed class CommandInteraction : MessageRespondingInteraction { + public val command: InteractionCommand + get() = InteractionCommand(data.data, kord) +} + +/** + * The base command of all commands that can be executed under an interaction event. + */ +public sealed interface InteractionCommand : KordObject { + /** + * The id of the root command. + */ + public val rootId: Snowflake + + /** + * The root command name + */ + public val rootName: String + + /** + * the values passed to the command. + */ + public val options: Map> + + public val resolved: ResolvedObjects? + + public val strings: Map get() = filterOptions() + + public val integers: Map get() = filterOptions() + + public val numbers: Map get() = filterOptions() + + public val booleans: Map get() = filterOptions() + + public val users: Map get() = filterOptions() + + public val members: Map get() = filterOptions() + + public val channels: Map get() = filterOptions() + + public val roles: Map get() = filterOptions() + + public val mentionables: Map get() = filterOptions() + + private inline fun filterOptions(): Map { + return buildMap { + options.onEach { (key, value) -> + val wrappedValue = value.value + if (wrappedValue is T) put(key, wrappedValue) + } + } + } +} + +public fun InteractionCommand( + data: ApplicationInteractionData, + kord: Kord +): InteractionCommand { + val firstLevelOptions = data.options.orEmpty() + val rootPredicate = firstLevelOptions.isEmpty() || firstLevelOptions.any { it.value.value != null } + val groupPredicate = firstLevelOptions.any { it.subCommands.orEmpty().isNotEmpty() } + val subCommandPredicate = + firstLevelOptions.all { it.value is Optional.Missing && it.subCommands is Optional.Missing } + + return when { + rootPredicate -> RootCommand(data, kord) + groupPredicate -> GroupCommand(data, kord) + subCommandPredicate -> SubCommand(data, kord) + else -> error("The interaction data provided is not an chat input command") + } +} + +/** + * Represents an invocation of a root command. + * + * The root command is the first command defined in a slash-command structure. + */ + +public class RootCommand( + public val data: ApplicationInteractionData, + override val kord: Kord +) : InteractionCommand { + + override val rootId: Snowflake + get() = data.id.value!! + + override val rootName: String get() = data.name.value!! + + override val options: Map> + get() = data.options.orEmpty() + .associate { it.name to OptionValue(it.value.value!!, resolved) } + + override val resolved: ResolvedObjects? + get() = data.resolvedObjectsData.unwrap { ResolvedObjects(it, kord) } + +} + +/** + * Represents an invocation of a sub-command under the [RootCommand] + */ + +public class SubCommand( + public val data: ApplicationInteractionData, + override val kord: Kord +) : InteractionCommand { + + private val subCommandData = data.options.orEmpty().first() + + override val rootName: String get() = data.name.value!! + + override val rootId: Snowflake + get() = data.id.value!! + + /** + * Name of the sub-command executed. + */ + public val name: String get() = subCommandData.name + + override val options: Map> + get() = subCommandData.values.orEmpty() + .associate { it.name to OptionValue(it, resolved) } + + + override val resolved: ResolvedObjects? + get() = data.resolvedObjectsData.unwrap { ResolvedObjects(it, kord) } + + +} + +/** + * Represents an invocation of a sub-command under a group. + */ + +public class GroupCommand( + public val data: ApplicationInteractionData, + override val kord: Kord +) : InteractionCommand { + + private val groupData get() = data.options.orEmpty().first() + private val subCommandData get() = groupData.subCommands.orEmpty().first() + + override val rootId: Snowflake + get() = data.id.value!! + + override val rootName: String get() = data.name.value!! + + /** + * Name of the group of this sub-command. + */ + public val groupName: String get() = groupData.name + + /** + * Name of this sub-command + */ + public val name: String get() = subCommandData.name + + override val options: Map> + get() = subCommandData.options.orEmpty() + .associate { it.name to OptionValue(it, resolved) } + + + override val resolved: ResolvedObjects? + get() = data.resolvedObjectsData.unwrap { ResolvedObjects(it, kord) } + +} + + +public class ResolvedObjects( + public val data: ResolvedObjectsData, + public val kord: Kord, + public val strategy: EntitySupplyStrategy<*> = kord.resources.defaultStrategy +) { + public val channels: Map? + get() = data.channels.mapValues { ResolvedChannel(it.value, kord, strategy) }.value + + public val roles: Map? get() = data.roles.mapValues { Role(it.value, kord) }.value + + public val users: Map? get() = data.users.mapValues { User(it.value, kord) }.value + + public val members: Map? + get() = data.members.mapValues { Member(it.value, users!![it.key]!!.data, kord) }.value + + public val messages: Map? + get() = data.messages.mapValues { Message(it.value, kord) }.value + +} + + +public sealed class OptionValue(public val value: T, public val focused: Boolean) { + + public class RoleOptionValue(value: Role, focused: Boolean) : OptionValue(value, focused) { + override fun toString(): String = "RoleOptionValue(value=$value)" + } + + public open class UserOptionValue(value: User, focused: Boolean) : OptionValue(value, focused) { + override fun toString(): String = "UserOptionValue(value=$value)" + } + + public class MemberOptionValue(value: Member, focused: Boolean) : UserOptionValue(value, focused) { + override fun toString(): String = "MemberOptionValue(value=$value)" + } + + public class ChannelOptionValue(value: ResolvedChannel, focused: Boolean) : + OptionValue(value, focused) { + override fun toString(): String = "ChannelOptionValue(value=$value)" + } + + public class IntOptionValue(value: Long, focused: Boolean) : OptionValue(value, focused) { + override fun toString(): String = "IntOptionValue(value=$value)" + } + + + public class NumberOptionValue(value: Double, focused: Boolean) : OptionValue(value, focused) { + override fun toString(): String = "DoubleOptionValue(value=$value)" + } + + public class StringOptionValue(value: String, focused: Boolean) : OptionValue(value, focused) { + override fun toString(): String = "StringOptionValue(value=$value)" + } + + public class BooleanOptionValue(value: Boolean, focused: Boolean) : OptionValue(value, focused) { + override fun toString(): String = "BooleanOptionValue(value=$value)" + } + + public class MentionableOptionValue(value: Entity, focused: Boolean) : OptionValue(value, focused) { + override fun toString(): String = "MentionableOptionValue(value=$value)" + } + +} + + +public fun OptionValue(value: CommandArgument<*>, resolvedObjects: ResolvedObjects?): OptionValue<*> { + val focused = value.focused.orElse(false) + return when (value) { + is CommandArgument.NumberArgument -> OptionValue.NumberOptionValue(value.value, focused) + is CommandArgument.BooleanArgument -> OptionValue.BooleanOptionValue(value.value, focused) + is CommandArgument.IntegerArgument -> OptionValue.IntOptionValue(value.value, focused) + is CommandArgument.StringArgument -> OptionValue.StringOptionValue(value.value, focused) + is CommandArgument.ChannelArgument -> { + val channel = resolvedObjects?.channels.orEmpty()[value.value] + requireNotNull(channel) { "channel expected for $value but was missing" } + + OptionValue.ChannelOptionValue(channel, focused) + } + + is CommandArgument.MentionableArgument -> { + val channel = resolvedObjects?.channels.orEmpty()[value.value] + val user = resolvedObjects?.users.orEmpty()[value.value] + val member = resolvedObjects?.members.orEmpty()[value.value] + val role = resolvedObjects?.roles.orEmpty()[value.value] + + val entity = channel ?: member ?: user ?: role + requireNotNull(entity) { "user, member, or channel expected for $value but was missing" } + + OptionValue.MentionableOptionValue(entity, focused) + } + + is CommandArgument.RoleArgument -> { + val role = resolvedObjects?.roles.orEmpty()[value.value] + requireNotNull(role) { "role expected for $value but was missing" } + + OptionValue.RoleOptionValue(role, focused) + } + + is CommandArgument.UserArgument -> { + val member = resolvedObjects?.members.orEmpty()[value.value] + + if (member != null) return OptionValue.MemberOptionValue(member, focused) + + val user = resolvedObjects?.users.orEmpty()[value.value] + requireNotNull(user) { "user expected for $value but was missing" } + + OptionValue.UserOptionValue(user, focused) + } + } +} + + +/** + * An [MessageRespondingInteraction] that took place in a Global Context with [GlobalApplicationCommand]. + */ + +public sealed interface GlobalApplicationCommandInteraction : ApplicationCommandInteraction, + GlobalApplicationCommandBehavior { + /** + * The user who invoked the interaction. + */ + + override val service: InteractionService + get() = kord.rest.interaction + + override val user: User get() = User(data.user.value!!, kord) + + override fun withStrategy(strategy: EntitySupplyStrategy<*>): GlobalApplicationCommandInteraction = + GlobalApplicationCommandInteraction(data, kord, strategy.supply(kord)) + + override val applicationId: Snowflake + get() = super.applicationId +} + +public fun GlobalApplicationCommandInteraction( + data: InteractionData, + kord: Kord, + supplier: EntitySupplier = kord.defaultSupplier +): GlobalApplicationCommandInteraction { + return when (data.data.type.value) { + ApplicationCommandType.ChatInput -> GlobalChatInputCommandInteraction(data, kord, supplier) + ApplicationCommandType.User -> GlobalUserCommandInteraction(data, kord, supplier) + ApplicationCommandType.Message -> GlobalMessageCommandInteraction(data, kord, supplier) + is ApplicationCommandType.Unknown -> error("Unknown interaction.") + null -> error("No component type was provided") + } +} + +/** + * An [MessageRespondingInteraction] that took place in a Global Context with [dev.kord.core.entity.application.GuildApplicationCommand]. + */ + + +public sealed interface GuildApplicationCommandInteraction : ApplicationCommandInteraction, GuildInteractionBehavior { + + override val guildId: Snowflake + get() = data.guildId.value!! + + /** + * Overridden permissions of the interaction invoker in the channel. + */ + public val permissions: Permissions get() = data.permissions.value!! + + + /** + * The invoker of the command as [MemberBehavior]. + */ + public val member: Member + get() = Member( + data.member.value!!, + data.user.value!!, + kord + ) + + override val channel: GuildMessageChannelBehavior + get() = GuildMessageChannelBehavior(guildId, channelId, kord) + + override val user: UserBehavior + get() = UserBehavior(member.id, kord) + + override fun withStrategy(strategy: EntitySupplyStrategy<*>): GuildApplicationCommandInteraction = + GuildApplicationCommandInteraction(data, kord, strategy.supply(kord)) + +} + +public fun GuildApplicationCommandInteraction( + data: InteractionData, + kord: Kord, + supplier: EntitySupplier = kord.defaultSupplier +): GuildApplicationCommandInteraction { + return when (data.data.type.value) { + ApplicationCommandType.ChatInput -> GuildChatInputCommandInteraction(data, kord, supplier) + ApplicationCommandType.User -> GuildUserCommandInteraction(data, kord, supplier) + ApplicationCommandType.Message -> GuildMessageCommandInteraction(data, kord, supplier) + is ApplicationCommandType.Unknown -> error("Unknown interaction.") + null -> error("No interaction type provided.") + } +} + + +public fun OptionValue<*>.user(): User = value as User + + +public fun OptionValue<*>.channel(): ResolvedChannel = value as ResolvedChannel + + +public fun OptionValue<*>.role(): Role = value as Role + + +public fun OptionValue<*>.member(): Member = value as Member + + +public fun OptionValue<*>.string(): String = value.toString() + + +public fun OptionValue<*>.boolean(): Boolean = value as Boolean + + +public fun OptionValue<*>.int(): Long = value as Long + + +public fun OptionValue<*>.number(): Double = value as Double + + +public fun OptionValue<*>.mentionable(): Entity { + return value as Entity +} diff --git a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt index 8edb0c059cd2..411a0bc719d7 100644 --- a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt +++ b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt @@ -15,10 +15,10 @@ import kotlinx.coroutines.CoroutineScope * [Slash Commands][dev.kord.core.entity.interaction.ApplicationCommand]. * * The event should be acknowledged withing 3 seconds of reception using one of the following methods: - * * [acknowledgeEphemeral][Interaction.acknowledgeEphemeral] - acknowledges an interaction ephemerally. - * * [acknowledgePublic][Interaction.acknowledgePublic] - acknowledges an interaction in public. - * * [respondPublic][Interaction.respondPublic] - same as public acknowledgement, but an immediate result (message) can be supplied. - * * [respondEphemeral][Interaction.respondEphemeral] - same as ephemeral acknowledgement, but an immediate result (message) can be supplied. + * * [acknowledgeEphemeral][MessageRespondingInteraction.acknowledgeEphemeral] - acknowledges an interaction ephemerally. + * * [acknowledgePublic][MessageRespondingInteraction.acknowledgePublic] - acknowledges an interaction in public. + * * [respondPublic][MessageRespondingInteraction.respondPublic] - same as public acknowledgement, but an immediate result (message) can be supplied. + * * [respondEphemeral][MessageRespondingInteraction.respondEphemeral] - same as ephemeral acknowledgement, but an immediate result (message) can be supplied. * * Once an interaction has been acknowledged, * you can use [PublicInteractionResponseBehavior.followUp] or [EphemeralInteractionResponseBehavior.followUp] to display additional messages. @@ -102,7 +102,7 @@ public class GlobalChatInputCommandInteractionCreateEvent( ) : GlobalApplicationInteractionCreateEvent, ChatInputCommandInteractionCreateEvent, CoroutineScope by coroutineScope /** - * Interaction received when a users types into an auto-completed option. + * MessageRespondingInteraction received when a users types into an auto-completed option. * * Check [AutoCompleteInteractionBehavior] on how to reply. * @@ -125,7 +125,7 @@ internal fun AutoCompleteInteractionCreateEvent( } /** - * Interaction received when a users types into an auto-completed option. + * MessageRespondingInteraction received when a users types into an auto-completed option. * * Check [AutoCompleteInteractionBehavior] on how to reply. * @@ -139,7 +139,7 @@ public class GlobalAutoCompleteInteractionCreateEvent( ) : AutoCompleteInteractionCreateEvent, GlobalApplicationInteractionCreateEvent, CoroutineScope by coroutineScope /** - * Interaction received when a users types into an auto-completed option. + * MessageRespondingInteraction received when a users types into an auto-completed option. * * Check [AutoCompleteInteractionBehavior] on how to reply. * diff --git a/core/src/main/kotlin/event/interaction/InteractionCreate.kt b/core/src/main/kotlin/event/interaction/InteractionCreate.kt index c6c382d295f5..c11b27c3c03e 100644 --- a/core/src/main/kotlin/event/interaction/InteractionCreate.kt +++ b/core/src/main/kotlin/event/interaction/InteractionCreate.kt @@ -1,8 +1,8 @@ package dev.kord.core.event.interaction -import dev.kord.core.entity.interaction.Interaction +import dev.kord.core.entity.interaction.MessageRespondingInteraction import dev.kord.core.event.Event public sealed interface InteractionCreateEvent : Event { - public val interaction: Interaction + public val interaction: MessageRespondingInteraction } diff --git a/core/src/main/kotlin/exception/EntityNotFoundException.kt b/core/src/main/kotlin/exception/EntityNotFoundException.kt index 9ba3a7063965..d68407fe89c8 100644 --- a/core/src/main/kotlin/exception/EntityNotFoundException.kt +++ b/core/src/main/kotlin/exception/EntityNotFoundException.kt @@ -74,6 +74,6 @@ public class EntityNotFoundException : Exception { entityNotFound(T::class.simpleName!!, commandId) public inline fun interactionNotFound(token: String): Nothing = - throw EntityNotFoundException("Interaction with token $token was not found.") + throw EntityNotFoundException("MessageRespondingInteraction with token $token was not found.") } } diff --git a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt index c17ae05b7b5e..97a0e7b06f57 100644 --- a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt +++ b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt @@ -23,7 +23,7 @@ import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction import dev.kord.core.entity.interaction.GuildMessageCommandInteraction import dev.kord.core.entity.interaction.GuildSelectMenuInteraction import dev.kord.core.entity.interaction.GuildUserCommandInteraction -import dev.kord.core.entity.interaction.Interaction +import dev.kord.core.entity.interaction.MessageRespondingInteraction import dev.kord.core.entity.interaction.UnknownApplicationCommandInteraction import dev.kord.core.entity.interaction.UnknownComponentInteraction import dev.kord.core.event.interaction.AutoCompleteInteractionCreateEvent @@ -69,7 +69,7 @@ public class InteractionEventHandler( private fun handle(event: InteractionCreate, shard: Int, kord: Kord, coroutineScope: CoroutineScope): CoreEvent { val data = InteractionData.from(event.interaction) - val coreEvent = when(val interaction = Interaction.from(data, kord)) { + val coreEvent = when(val interaction = MessageRespondingInteraction.from(data, kord)) { is AutoCompleteInteraction -> AutoCompleteInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalChatInputCommandInteraction -> GlobalChatInputCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalUserCommandInteraction -> GlobalUserCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) From dea7b554e73034cc46f06123ed40c163249133e8 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 17 Nov 2021 16:55:11 +0100 Subject: [PATCH 10/13] Update underlying interactions accordingly --- .../behavior/GuildInteractionBehavior.kt | 7 ++---- .../ApplicationCommandInteractionBehavior.kt | 1 - .../interaction/InteractionBehavior.kt | 2 +- .../entity/interaction/ContextInteraction.kt | 23 +++++++++++-------- .../kotlin/entity/interaction/Interaction.kt | 6 ++--- .../MessageRespondingInteraction.kt | 11 +++++---- .../event/interaction/ApplicationCreate.kt | 6 ++--- .../event/interaction/InteractionCreate.kt | 4 ++-- .../handler/InteractionEventHandler.kt | 4 ++-- 9 files changed, 33 insertions(+), 31 deletions(-) diff --git a/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt b/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt index 5f2a01e47a26..b6b49711a841 100644 --- a/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt @@ -1,23 +1,20 @@ package dev.kord.core.behavior import dev.kord.common.entity.Snowflake -import dev.kord.core.behavior.interaction.MessageRespondingInteractionBehavior +import dev.kord.core.behavior.interaction.InteractionBehavior import dev.kord.core.entity.Guild /** * The behavior of a [dev.kord.core.entity.interaction.MessageRespondingInteraction][MessageRespondingInteraction] that was invoked in a [Guild] */ -public interface GuildInteractionBehavior : MessageRespondingInteractionBehavior { - +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) diff --git a/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt index 6f969d665d52..93e58c09015e 100644 --- a/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt @@ -8,7 +8,6 @@ import dev.kord.core.supplier.EntitySupplier * The behavior of a [Discord MessageRespondingInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) * with [Application Command type][dev.kord.common.entity.ApplicationCommandType] */ - public interface ApplicationCommandInteractionBehavior : MessageRespondingInteractionBehavior internal fun ApplicationCommandInteractionBehavior( diff --git a/core/src/main/kotlin/behavior/interaction/InteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/InteractionBehavior.kt index 7ce10dfda4a9..fe573022bb6d 100644 --- a/core/src/main/kotlin/behavior/interaction/InteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/InteractionBehavior.kt @@ -23,6 +23,6 @@ public interface InteractionBehavior : KordEntity, Strategizable { public suspend fun getChannel(): MessageChannel = supplier.getChannelOf(channelId) - override fun withStrategy(strategy: EntitySupplyStrategy<*>): MessageRespondingInteractionBehavior = + override fun withStrategy(strategy: EntitySupplyStrategy<*>): InteractionBehavior = InteractionBehavior(id, channelId, token, applicationId, kord, strategy) } diff --git a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt index 741e8e84a57d..4055de8e6b85 100644 --- a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt @@ -20,7 +20,7 @@ import java.util.Objects /** * Represents an interaction of type [ApplicationCommand][dev.kord.common.entity.InteractionType.ApplicationCommand] */ -public sealed interface ApplicationCommandInteraction : MessageRespondingInteraction, ApplicationCommandInteractionBehavior { +public sealed interface ApplicationCommandInteraction : Interaction, ApplicationCommandInteractionBehavior { public val invokedCommandId: Snowflake get() = data.data.id.value!! public val name: String get() = data.data.name.value!! @@ -31,17 +31,22 @@ public sealed interface ApplicationCommandInteraction : MessageRespondingInterac get() = data.data.resolvedObjectsData.unwrap { ResolvedObjects(it, kord) } - } /** - * An [ApplicationCommandInteraction] that's invoked through chat input. + * An [ApplicationCommandInteraction] that contains a [command]. */ -public sealed interface ChatInputCommandInteraction : ApplicationCommandInteraction { +public sealed interface ChatInputCommandInteraction : Interaction { public val command: InteractionCommand get() = InteractionCommand(data.data, kord) } +/** + * An [ApplicationCommandInteraction] that's invoked through chat input. + */ +public sealed interface ChatInputCommandInvocationInteraction : ChatInputCommandInteraction, ApplicationCommandInteraction + + /** * A [ApplicationCommandInteraction] that's invoked through chat input specific to a guild. */ @@ -49,7 +54,7 @@ public class GuildChatInputCommandInteraction( override val data: InteractionData, override val kord: Kord, override val supplier: EntitySupplier -) : ChatInputCommandInteraction, GuildApplicationCommandInteraction { +) : ChatInputCommandInvocationInteraction, GuildApplicationCommandInteraction { override fun equals(other: Any?): Boolean { return if (other !is GuildChatInputCommandInteraction) false else id == other.id @@ -68,7 +73,7 @@ public class GlobalChatInputCommandInteraction( override val data: InteractionData, override val kord: Kord, override val supplier: EntitySupplier -) : ChatInputCommandInteraction, GlobalApplicationCommandInteraction { +) : ChatInputCommandInvocationInteraction, GlobalApplicationCommandInteraction { override fun equals(other: Any?): Boolean { return if (other !is GlobalChatInputCommandInteraction) false else id == other.id @@ -222,7 +227,7 @@ internal fun AutoCompleteInteraction( data: InteractionData, kord: Kord, supplier: EntitySupplier = kord.defaultSupplier -): ApplicationCommandInteraction = when (data.guildId) { +): AutoCompleteInteraction = when (data.guildId) { is OptionalSnowflake.Value -> GuildAutoCompleteInteraction( data, kord, supplier ) @@ -240,7 +245,7 @@ public class GlobalAutoCompleteInteraction( override val data: InteractionData, override val kord: Kord, override val supplier: EntitySupplier = kord.defaultSupplier -) : AutoCompleteInteraction, GlobalApplicationCommandInteraction { +) : AutoCompleteInteraction, GlobalInteraction { override fun withStrategy(strategy: EntitySupplyStrategy<*>): GlobalAutoCompleteInteraction = GlobalAutoCompleteInteraction(data, kord, strategy.supply(kord)) } @@ -262,6 +267,6 @@ public class GuildAutoCompleteInteraction( override val user: User get() = User(data.user.value!!, kord) - override fun withStrategy(strategy: EntitySupplyStrategy<*>): MessageRespondingInteraction = + override fun withStrategy(strategy: EntitySupplyStrategy<*>): Interaction = GuildAutoCompleteInteraction(data, kord, strategy.supply(kord)) } diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt index 3bcfcac7797f..688bd2bce8de 100644 --- a/core/src/main/kotlin/entity/interaction/Interaction.kt +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -9,7 +9,7 @@ import dev.kord.core.behavior.interaction.InteractionBehavior import dev.kord.core.cache.data.InteractionData import dev.kord.core.supplier.EntitySupplyStrategy -public interface Interaction : InteractionBehavior { +public sealed interface Interaction : InteractionBehavior { public val data: InteractionData override val id: Snowflake get() = data.id @@ -39,14 +39,14 @@ public interface Interaction : InteractionBehavior { */ public val version: Int get() = data.version - abstract override fun withStrategy(strategy: EntitySupplyStrategy<*>): MessageRespondingInteraction + abstract override fun withStrategy(strategy: EntitySupplyStrategy<*>): Interaction public companion object { public fun from( data: InteractionData, kord: Kord, strategy: EntitySupplyStrategy<*> = kord.resources.defaultStrategy - ): MessageRespondingInteraction { + ): Interaction { return when { data.type == InteractionType.Component -> ComponentInteraction(data, kord, strategy.supply(kord)) data.type == InteractionType.AutoComplete -> AutoCompleteInteraction(data, kord, strategy.supply(kord)) diff --git a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt b/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt index 13d55ed4be51..85eb74842298 100644 --- a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt @@ -306,12 +306,15 @@ public fun OptionValue(value: CommandArgument<*>, resolvedObjects: ResolvedObjec } +public sealed interface GlobalInteraction : Interaction { + override val user: User get() = User(data.user.value!!, kord) +} + /** * An [MessageRespondingInteraction] that took place in a Global Context with [GlobalApplicationCommand]. */ - public sealed interface GlobalApplicationCommandInteraction : ApplicationCommandInteraction, - GlobalApplicationCommandBehavior { + GlobalApplicationCommandBehavior, GlobalInteraction { /** * The user who invoked the interaction. */ @@ -319,13 +322,11 @@ public sealed interface GlobalApplicationCommandInteraction : ApplicationCommand override val service: InteractionService get() = kord.rest.interaction - override val user: User get() = User(data.user.value!!, kord) - override fun withStrategy(strategy: EntitySupplyStrategy<*>): GlobalApplicationCommandInteraction = GlobalApplicationCommandInteraction(data, kord, strategy.supply(kord)) override val applicationId: Snowflake - get() = super.applicationId + get() = super.applicationId } public fun GlobalApplicationCommandInteraction( diff --git a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt index 411a0bc719d7..371bd25d7720 100644 --- a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt +++ b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt @@ -84,7 +84,7 @@ public class GlobalMessageCommandInteractionCreateEvent( public sealed interface ChatInputCommandInteractionCreateEvent : ApplicationInteractionCreateEvent { - override val interaction: ChatInputCommandInteraction + override val interaction: ChatInputCommandInvocationInteraction } public class GuildChatInputCommandInteractionCreateEvent( @@ -108,7 +108,7 @@ public class GlobalChatInputCommandInteractionCreateEvent( * * @see AutoCompleteInteraction */ -public sealed interface AutoCompleteInteractionCreateEvent : InteractionCreateEvent, ChatInputCommandInteractionCreateEvent +public sealed interface AutoCompleteInteractionCreateEvent : InteractionCreateEvent internal fun AutoCompleteInteractionCreateEvent( interaction: AutoCompleteInteraction, @@ -136,7 +136,7 @@ public class GlobalAutoCompleteInteractionCreateEvent( override val shard: Int, override val interaction: GlobalAutoCompleteInteraction, public val coroutineScope: CoroutineScope = kordCoroutineScope(kord) -) : AutoCompleteInteractionCreateEvent, GlobalApplicationInteractionCreateEvent, CoroutineScope by coroutineScope +) : AutoCompleteInteractionCreateEvent, CoroutineScope by coroutineScope /** * MessageRespondingInteraction received when a users types into an auto-completed option. diff --git a/core/src/main/kotlin/event/interaction/InteractionCreate.kt b/core/src/main/kotlin/event/interaction/InteractionCreate.kt index c11b27c3c03e..c6c382d295f5 100644 --- a/core/src/main/kotlin/event/interaction/InteractionCreate.kt +++ b/core/src/main/kotlin/event/interaction/InteractionCreate.kt @@ -1,8 +1,8 @@ package dev.kord.core.event.interaction -import dev.kord.core.entity.interaction.MessageRespondingInteraction +import dev.kord.core.entity.interaction.Interaction import dev.kord.core.event.Event public sealed interface InteractionCreateEvent : Event { - public val interaction: MessageRespondingInteraction + public val interaction: Interaction } diff --git a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt index 97a0e7b06f57..c17ae05b7b5e 100644 --- a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt +++ b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt @@ -23,7 +23,7 @@ import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction import dev.kord.core.entity.interaction.GuildMessageCommandInteraction import dev.kord.core.entity.interaction.GuildSelectMenuInteraction import dev.kord.core.entity.interaction.GuildUserCommandInteraction -import dev.kord.core.entity.interaction.MessageRespondingInteraction +import dev.kord.core.entity.interaction.Interaction import dev.kord.core.entity.interaction.UnknownApplicationCommandInteraction import dev.kord.core.entity.interaction.UnknownComponentInteraction import dev.kord.core.event.interaction.AutoCompleteInteractionCreateEvent @@ -69,7 +69,7 @@ public class InteractionEventHandler( private fun handle(event: InteractionCreate, shard: Int, kord: Kord, coroutineScope: CoroutineScope): CoreEvent { val data = InteractionData.from(event.interaction) - val coreEvent = when(val interaction = MessageRespondingInteraction.from(data, kord)) { + val coreEvent = when(val interaction = Interaction.from(data, kord)) { is AutoCompleteInteraction -> AutoCompleteInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalChatInputCommandInteraction -> GlobalChatInputCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) is GlobalUserCommandInteraction -> GlobalUserCommandInteractionCreateEvent(interaction, kord, shard, coroutineScope) From c9e595b182d69de8df47ef9673770bc5f700eb25 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 18 Nov 2021 15:41:47 +0100 Subject: [PATCH 11/13] Update docs according --- .../entity/interaction/MessageRespondingInteraction.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt b/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt index 85eb74842298..00805801f78d 100644 --- a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt @@ -15,7 +15,6 @@ import dev.kord.core.entity.application.GlobalApplicationCommand import dev.kord.core.entity.channel.ResolvedChannel import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy -import dev.kord.rest.service.InteractionService /** * An instance of [MessageRespondingInteraction] (https://discord.com/developers/docs/interactions/slash-commands#interaction) @@ -313,15 +312,10 @@ public sealed interface GlobalInteraction : Interaction { /** * An [MessageRespondingInteraction] that took place in a Global Context with [GlobalApplicationCommand]. */ -public sealed interface GlobalApplicationCommandInteraction : ApplicationCommandInteraction, - GlobalApplicationCommandBehavior, GlobalInteraction { +public sealed interface GlobalApplicationCommandInteraction : ApplicationCommandInteraction, GlobalInteraction { /** * The user who invoked the interaction. */ - - override val service: InteractionService - get() = kord.rest.interaction - override fun withStrategy(strategy: EntitySupplyStrategy<*>): GlobalApplicationCommandInteraction = GlobalApplicationCommandInteraction(data, kord, strategy.supply(kord)) From f400e7729269ae188233a260a16a0082e4809c41 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Thu, 18 Nov 2021 15:41:59 +0100 Subject: [PATCH 12/13] Update docs according to requested changes --- .../MessageRespondingInteraction.kt | 21 ++++++++++++++----- .../builder/interaction/OptionsBuilder.kt | 6 ++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt b/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt index 00805801f78d..c3d4df51112e 100644 --- a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt @@ -1,16 +1,28 @@ package dev.kord.core.entity.interaction -import dev.kord.common.entity.* -import dev.kord.common.entity.optional.* +import dev.kord.common.entity.ApplicationCommandType +import dev.kord.common.entity.CommandArgument +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.mapValues +import dev.kord.common.entity.optional.orEmpty +import dev.kord.common.entity.optional.unwrap import dev.kord.core.Kord import dev.kord.core.KordObject -import dev.kord.core.behavior.* +import dev.kord.core.behavior.GuildInteractionBehavior +import dev.kord.core.behavior.MemberBehavior +import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.GuildMessageChannelBehavior import dev.kord.core.behavior.interaction.MessageRespondingInteractionBehavior import dev.kord.core.cache.data.ApplicationInteractionData import dev.kord.core.cache.data.InteractionData import dev.kord.core.cache.data.ResolvedObjectsData -import dev.kord.core.entity.* +import dev.kord.core.entity.Entity +import dev.kord.core.entity.Member +import dev.kord.core.entity.Message +import dev.kord.core.entity.Role +import dev.kord.core.entity.User import dev.kord.core.entity.application.GlobalApplicationCommand import dev.kord.core.entity.channel.ResolvedChannel import dev.kord.core.supplier.EntitySupplier @@ -24,7 +36,6 @@ public sealed interface MessageRespondingInteraction : Interaction, MessageRespo /** * The base interaction for all slash-command related interactions. * - * @see DmInteraction * @see GuildApplicationCommandInteraction */ public sealed class CommandInteraction : MessageRespondingInteraction { diff --git a/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt index 6f1411fefdf4..e41142fbb29c 100644 --- a/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt @@ -31,11 +31,9 @@ sealed class OptionsBuilder( /** * Setting this to `true` allows you to dynamically respond with your choices, depending on the user input. * - * This disables all input validation, users can submit values before responding to the AutoComplete request + * This disables all input validation, users can submit values before responding to the AutoComplete request. * - * **Note:** If you set this to `true` you can't add any other choice - * - * Core users can check the `AutoCompleteInteractionCreateEvent` and `AutoCompleteInteraction` classes. + * Enabling this also means that you cannot add any other option. */ var autocomplete: Boolean? by ::_autocomplete.delegate() From 892bc31eb0ef0f94d9aa0885457166785803ae6a Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 23 Nov 2021 15:58:41 +0100 Subject: [PATCH 13/13] Rename MessageRespondingInteraction to DataInteraction --- .../behavior/GuildInteractionBehavior.kt | 5 ++- ...havior.kt => ActionInteractionBehavior.kt} | 11 ++--- .../ApplicationCommandInteractionBehavior.kt | 4 +- .../ComponentInteractionBehavior.kt | 2 +- .../EphemeralInteractionResponseBehavior.kt | 2 +- .../InteractionResponseBehavior.kt | 2 +- .../PublicInteractionResponseBehavior.kt | 2 +- core/src/main/kotlin/entity/Message.kt | 6 +-- ...ingInteraction.kt => ActionInteraction.kt} | 16 ++++--- .../interaction/ComponentInteraction.kt | 2 +- .../entity/interaction/ContextInteraction.kt | 8 ++-- .../entity/interaction/DataInteraction.kt | 10 +++++ .../kotlin/entity/interaction/Interaction.kt | 6 +++ .../entity/interaction/MessageInteraction.kt | 8 ++-- .../event/interaction/ApplicationCreate.kt | 42 ++++++++++++++----- .../exception/EntityNotFoundException.kt | 2 +- 16 files changed, 86 insertions(+), 42 deletions(-) rename core/src/main/kotlin/behavior/interaction/{MessageRespondingInteractionBehavior.kt => ActionInteractionBehavior.kt} (87%) rename core/src/main/kotlin/entity/interaction/{MessageRespondingInteraction.kt => ActionInteraction.kt} (95%) create mode 100644 core/src/main/kotlin/entity/interaction/DataInteraction.kt diff --git a/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt b/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt index b6b49711a841..a9126d3cece0 100644 --- a/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/GuildInteractionBehavior.kt @@ -3,9 +3,10 @@ package dev.kord.core.behavior 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.MessageRespondingInteraction][MessageRespondingInteraction] 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 @@ -19,6 +20,6 @@ public interface GuildInteractionBehavior : InteractionBehavior { public suspend fun getGuild(): Guild = supplier.getGuild(guildId) - public companion object; + public companion object } diff --git a/core/src/main/kotlin/behavior/interaction/MessageRespondingInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/ActionInteractionBehavior.kt similarity index 87% rename from core/src/main/kotlin/behavior/interaction/MessageRespondingInteractionBehavior.kt rename to core/src/main/kotlin/behavior/interaction/ActionInteractionBehavior.kt index cf4b1232e492..99f705b88627 100644 --- a/core/src/main/kotlin/behavior/interaction/MessageRespondingInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/ActionInteractionBehavior.kt @@ -11,9 +11,10 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** - * The behavior of a [Discord MessageRespondingInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) which can respond with a message. + * The behavior of a [Discord ActionInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) which does perform an action + * (e.g. slash commands and context actions) */ -public interface MessageRespondingInteractionBehavior : InteractionBehavior { +public interface ActionInteractionBehavior : InteractionBehavior { /** * Acknowledges an interaction ephemerally. @@ -49,7 +50,7 @@ public interface MessageRespondingInteractionBehavior : InteractionBehavior { */ @OptIn(ExperimentalContracts::class) -public suspend inline fun MessageRespondingInteractionBehavior.respondPublic( +public suspend inline fun ActionInteractionBehavior.respondPublic( builder: InteractionResponseCreateBuilder.() -> Unit ): PublicInteractionResponseBehavior { @@ -70,7 +71,7 @@ public suspend inline fun MessageRespondingInteractionBehavior.respondPublic( */ @OptIn(ExperimentalContracts::class) -public suspend inline fun MessageRespondingInteractionBehavior.respondEphemeral( +public suspend inline fun ActionInteractionBehavior.respondEphemeral( builder: InteractionResponseCreateBuilder.() -> Unit ): EphemeralInteractionResponseBehavior { @@ -89,7 +90,7 @@ public fun InteractionBehavior( applicationId: Snowflake, kord: Kord, strategy: EntitySupplyStrategy<*> = kord.resources.defaultStrategy -): MessageRespondingInteractionBehavior = object : MessageRespondingInteractionBehavior { +): ActionInteractionBehavior = object : ActionInteractionBehavior { override val id: Snowflake get() = id diff --git a/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt index 93e58c09015e..9cdf5bb872dc 100644 --- a/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/ApplicationCommandInteractionBehavior.kt @@ -5,10 +5,10 @@ import dev.kord.core.Kord import dev.kord.core.supplier.EntitySupplier /** - * The behavior of a [Discord MessageRespondingInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) + * The behavior of a [Discord ActionInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) * with [Application Command type][dev.kord.common.entity.ApplicationCommandType] */ -public interface ApplicationCommandInteractionBehavior : MessageRespondingInteractionBehavior +public interface ApplicationCommandInteractionBehavior : ActionInteractionBehavior internal fun ApplicationCommandInteractionBehavior( id: Snowflake, diff --git a/core/src/main/kotlin/behavior/interaction/ComponentInteractionBehavior.kt b/core/src/main/kotlin/behavior/interaction/ComponentInteractionBehavior.kt index 912f8e2b46e9..81f399dde332 100644 --- a/core/src/main/kotlin/behavior/interaction/ComponentInteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/ComponentInteractionBehavior.kt @@ -16,7 +16,7 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract -public interface ComponentInteractionBehavior : MessageRespondingInteractionBehavior { +public interface ComponentInteractionBehavior : ActionInteractionBehavior { /** * Acknowledges a component interaction publicly with the intent of updating it later. diff --git a/core/src/main/kotlin/behavior/interaction/EphemeralInteractionResponseBehavior.kt b/core/src/main/kotlin/behavior/interaction/EphemeralInteractionResponseBehavior.kt index 01a702e110d5..f28e81ac1072 100644 --- a/core/src/main/kotlin/behavior/interaction/EphemeralInteractionResponseBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/EphemeralInteractionResponseBehavior.kt @@ -4,7 +4,7 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.Kord /** - * The behavior of a ephemeral [Discord MessageRespondingInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) + * The behavior of a ephemeral [Discord ActionInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) * This response is visible to *only* to the user who made the interaction. */ diff --git a/core/src/main/kotlin/behavior/interaction/InteractionResponseBehavior.kt b/core/src/main/kotlin/behavior/interaction/InteractionResponseBehavior.kt index 70d97ee95f80..a335d18e20a5 100644 --- a/core/src/main/kotlin/behavior/interaction/InteractionResponseBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/InteractionResponseBehavior.kt @@ -14,7 +14,7 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** - * The behavior of a [Discord MessageRespondingInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) + * The behavior of a [Discord ActionInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) */ public interface InteractionResponseBehavior : KordObject { diff --git a/core/src/main/kotlin/behavior/interaction/PublicInteractionResponseBehavior.kt b/core/src/main/kotlin/behavior/interaction/PublicInteractionResponseBehavior.kt index 9bb3bdefc71f..10ac9287ba1e 100644 --- a/core/src/main/kotlin/behavior/interaction/PublicInteractionResponseBehavior.kt +++ b/core/src/main/kotlin/behavior/interaction/PublicInteractionResponseBehavior.kt @@ -6,7 +6,7 @@ import dev.kord.rest.request.RestRequestException /** - * The behavior of a public [Discord MessageRespondingInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) + * The behavior of a public [Discord ActionInteraction Response](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) * This response is visible to all users in the channel. */ diff --git a/core/src/main/kotlin/entity/Message.kt b/core/src/main/kotlin/entity/Message.kt index 13749a77eca6..0284f8c01f31 100644 --- a/core/src/main/kotlin/entity/Message.kt +++ b/core/src/main/kotlin/entity/Message.kt @@ -16,8 +16,8 @@ import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.MessageChannel import dev.kord.core.entity.channel.TopGuildMessageChannel import dev.kord.core.entity.component.Component +import dev.kord.core.entity.interaction.ActionInteraction import dev.kord.core.entity.interaction.MessageInteraction -import dev.kord.core.entity.interaction.MessageRespondingInteraction import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy @@ -112,7 +112,7 @@ public class Message( public val stickers: List get() = data.stickers.orEmpty().map { MessageSticker(it, kord) } /** - * If the message is a response to an [MessageRespondingInteraction], this is the id of the interaction's application + * If the message is a response to an [ActionInteraction], this is the id of the interaction's application */ public val applicationId: Snowflake? get() = data.application.unwrap { it.id } @@ -189,7 +189,7 @@ public class Message( public val mentionedUserBehaviors: Set get() = data.mentions.map { UserBehavior(it, kord) }.toSet() /** - * The [MessageInteraction] sent on this message object when it is a response to an [dev.kord.core.entity.interaction.MessageRespondingInteraction]. + * The [MessageInteraction] sent on this message object when it is a response to an [dev.kord.core.entity.interaction.ActionInteraction]. */ public val interaction: MessageInteraction? get() = data.interaction.mapNullable { MessageInteraction(it, kord) }.value diff --git a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt b/core/src/main/kotlin/entity/interaction/ActionInteraction.kt similarity index 95% rename from core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt rename to core/src/main/kotlin/entity/interaction/ActionInteraction.kt index c3d4df51112e..700a2015350e 100644 --- a/core/src/main/kotlin/entity/interaction/MessageRespondingInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/ActionInteraction.kt @@ -14,7 +14,7 @@ import dev.kord.core.behavior.GuildInteractionBehavior import dev.kord.core.behavior.MemberBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.GuildMessageChannelBehavior -import dev.kord.core.behavior.interaction.MessageRespondingInteractionBehavior +import dev.kord.core.behavior.interaction.ActionInteractionBehavior import dev.kord.core.cache.data.ApplicationInteractionData import dev.kord.core.cache.data.InteractionData import dev.kord.core.cache.data.ResolvedObjectsData @@ -29,16 +29,20 @@ import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy /** - * An instance of [MessageRespondingInteraction] (https://discord.com/developers/docs/interactions/slash-commands#interaction) + * An instance of [ActionInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) which does perform an action + * (e.g. slash commands and context actions). + * + * @see DataInteraction + * @see Interaction */ -public sealed interface MessageRespondingInteraction : Interaction, MessageRespondingInteractionBehavior +public sealed interface ActionInteraction : Interaction, ActionInteractionBehavior /** * The base interaction for all slash-command related interactions. * * @see GuildApplicationCommandInteraction */ -public sealed class CommandInteraction : MessageRespondingInteraction { +public sealed class CommandInteraction : ActionInteraction { public val command: InteractionCommand get() = InteractionCommand(data.data, kord) } @@ -321,7 +325,7 @@ public sealed interface GlobalInteraction : Interaction { } /** - * An [MessageRespondingInteraction] that took place in a Global Context with [GlobalApplicationCommand]. + * An [ActionInteraction] that took place in a Global Context with [GlobalApplicationCommand]. */ public sealed interface GlobalApplicationCommandInteraction : ApplicationCommandInteraction, GlobalInteraction { /** @@ -349,7 +353,7 @@ public fun GlobalApplicationCommandInteraction( } /** - * An [MessageRespondingInteraction] that took place in a Global Context with [dev.kord.core.entity.application.GuildApplicationCommand]. + * An [ActionInteraction] that took place in a Global Context with [dev.kord.core.entity.application.GuildApplicationCommand]. */ diff --git a/core/src/main/kotlin/entity/interaction/ComponentInteraction.kt b/core/src/main/kotlin/entity/interaction/ComponentInteraction.kt index 40e3a66d1576..af2df5ee7954 100644 --- a/core/src/main/kotlin/entity/interaction/ComponentInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/ComponentInteraction.kt @@ -22,7 +22,7 @@ import dev.kord.rest.builder.component.SelectMenuBuilder * @see SelectMenuInteraction */ -public sealed interface ComponentInteraction : MessageRespondingInteraction, ComponentInteractionBehavior { +public sealed interface ComponentInteraction : ActionInteraction, ComponentInteractionBehavior { override val user: User get() = User(data.user.value!!, kord) diff --git a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt index 4055de8e6b85..c507b4980efc 100644 --- a/core/src/main/kotlin/entity/interaction/ContextInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/ContextInteraction.kt @@ -215,13 +215,13 @@ public class UnknownApplicationCommandInteraction( } /** - * MessageRespondingInteraction indicating an auto-complete request from Discord. + * ActionInteraction indicating an auto-complete request from Discord. * * **Follow-ups and normals responses don't work on this type** * * Check [AutoCompleteInteractionBehavior] for response options */ -public sealed interface AutoCompleteInteraction : AutoCompleteInteractionBehavior, ChatInputCommandInteraction +public sealed interface AutoCompleteInteraction : AutoCompleteInteractionBehavior, ChatInputCommandInteraction, DataInteraction internal fun AutoCompleteInteraction( data: InteractionData, @@ -235,7 +235,7 @@ internal fun AutoCompleteInteraction( } /** - * MessageRespondingInteraction indicating an auto-complete request from Discord. + * ActionInteraction indicating an auto-complete request from Discord. * * **Follow-ups and normals responses don't work on this type** * @@ -251,7 +251,7 @@ public class GlobalAutoCompleteInteraction( } /** - * MessageRespondingInteraction indicating an auto-complete request from Discord on a guild. + * ActionInteraction indicating an auto-complete request from Discord on a guild. * * **Follow-ups and normals responses don't work on this type** * diff --git a/core/src/main/kotlin/entity/interaction/DataInteraction.kt b/core/src/main/kotlin/entity/interaction/DataInteraction.kt new file mode 100644 index 000000000000..b5b83f53d0e9 --- /dev/null +++ b/core/src/main/kotlin/entity/interaction/DataInteraction.kt @@ -0,0 +1,10 @@ +package dev.kord.core.entity.interaction + +/** + * An instance of [ActionInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction) which does respond to a data request from + * Discord like [AutoCompleteInteractions][AutoCompleteInteraction]. + * + * @see ActionInteraction + * @see Interaction + */ +public sealed interface DataInteraction : Interaction diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt index 688bd2bce8de..dba5f698963e 100644 --- a/core/src/main/kotlin/entity/interaction/Interaction.kt +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -9,6 +9,12 @@ import dev.kord.core.behavior.interaction.InteractionBehavior import dev.kord.core.cache.data.InteractionData import dev.kord.core.supplier.EntitySupplyStrategy +/** + * An instance of [ActionInteraction](https://discord.com/developers/docs/interactions/slash-commands#interaction). + * + * @see ActionInteraction + * @see DataInteraction + */ public sealed interface Interaction : InteractionBehavior { public val data: InteractionData diff --git a/core/src/main/kotlin/entity/interaction/MessageInteraction.kt b/core/src/main/kotlin/entity/interaction/MessageInteraction.kt index b2199b8f52bd..ce55ed959e78 100644 --- a/core/src/main/kotlin/entity/interaction/MessageInteraction.kt +++ b/core/src/main/kotlin/entity/interaction/MessageInteraction.kt @@ -10,13 +10,14 @@ import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Message import dev.kord.core.entity.Strategizable import dev.kord.core.entity.User +import dev.kord.core.entity.application.ApplicationCommand import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy /** * An instance of [MessageInteraction](https://discord.com/developers/docs/interactions/slash-commands#messageinteraction) - * This is sent on the [Message] object when the message is a response to an [MessageRespondingInteraction]. + * This is sent on the [Message] object when the message is a response to an [ActionInteraction]. */ public class MessageInteraction( @@ -25,7 +26,7 @@ public class MessageInteraction( override val supplier: EntitySupplier = kord.defaultSupplier ) : KordEntity, Strategizable { /** - * [id][MessageRespondingInteraction.id] of the [MessageRespondingInteraction] this message is responding to. + * [id][ActionInteraction.id] of the [ActionInteraction] this message is responding to. */ override val id: Snowflake get() = data.id @@ -35,7 +36,7 @@ public class MessageInteraction( public val name: String get() = data.name /** - * The [UserBehavior] of the [user][MessageRespondingInteraction.user] who invoked the [MessageRespondingInteraction] + * The [UserBehavior] of the [user][ActionInteraction.user] who invoked the [ActionInteraction] */ public val user: UserBehavior get() = UserBehavior(data.user, kord) @@ -52,6 +53,7 @@ public class MessageInteraction( */ public suspend fun getUser(): User = supplier.getUser(user.id) + /** * Requests to get the user of this interaction message, * returns null if the [User] isn't present. diff --git a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt index 371bd25d7720..b7f638e84f8c 100644 --- a/core/src/main/kotlin/event/interaction/ApplicationCreate.kt +++ b/core/src/main/kotlin/event/interaction/ApplicationCreate.kt @@ -1,9 +1,29 @@ package dev.kord.core.event.interaction import dev.kord.core.Kord -import dev.kord.core.behavior.interaction.* -import dev.kord.core.entity.application.* -import dev.kord.core.entity.interaction.* +import dev.kord.core.behavior.interaction.AutoCompleteInteractionBehavior +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.followUp +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.entity.application.ApplicationCommand +import dev.kord.core.entity.interaction.ActionInteraction +import dev.kord.core.entity.interaction.ApplicationCommandInteraction +import dev.kord.core.entity.interaction.AutoCompleteInteraction +import dev.kord.core.entity.interaction.ChatInputCommandInvocationInteraction +import dev.kord.core.entity.interaction.GlobalApplicationCommandInteraction +import dev.kord.core.entity.interaction.GlobalAutoCompleteInteraction +import dev.kord.core.entity.interaction.GlobalChatInputCommandInteraction +import dev.kord.core.entity.interaction.GlobalMessageCommandInteraction +import dev.kord.core.entity.interaction.GlobalUserCommandInteraction +import dev.kord.core.entity.interaction.GuildApplicationCommandInteraction +import dev.kord.core.entity.interaction.GuildAutoCompleteInteraction +import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction +import dev.kord.core.entity.interaction.GuildMessageCommandInteraction +import dev.kord.core.entity.interaction.GuildUserCommandInteraction +import dev.kord.core.entity.interaction.MessageCommandInteraction +import dev.kord.core.entity.interaction.UserCommandInteraction import dev.kord.core.event.kordCoroutineScope import kotlinx.coroutines.CoroutineScope @@ -12,13 +32,13 @@ import kotlinx.coroutines.CoroutineScope * * * Discord currently has one type of interaction, - * [Slash Commands][dev.kord.core.entity.interaction.ApplicationCommand]. + * [Slash Commands][ApplicationCommand]. * * The event should be acknowledged withing 3 seconds of reception using one of the following methods: - * * [acknowledgeEphemeral][MessageRespondingInteraction.acknowledgeEphemeral] - acknowledges an interaction ephemerally. - * * [acknowledgePublic][MessageRespondingInteraction.acknowledgePublic] - acknowledges an interaction in public. - * * [respondPublic][MessageRespondingInteraction.respondPublic] - same as public acknowledgement, but an immediate result (message) can be supplied. - * * [respondEphemeral][MessageRespondingInteraction.respondEphemeral] - same as ephemeral acknowledgement, but an immediate result (message) can be supplied. + * * [acknowledgeEphemeral][ActionInteraction.acknowledgeEphemeral] - acknowledges an interaction ephemerally. + * * [acknowledgePublic][ActionInteraction.acknowledgePublic] - acknowledges an interaction in public. + * * [respondPublic][ActionInteraction.respondPublic] - same as public acknowledgement, but an immediate result (message) can be supplied. + * * [respondEphemeral][ActionInteraction.respondEphemeral] - same as ephemeral acknowledgement, but an immediate result (message) can be supplied. * * Once an interaction has been acknowledged, * you can use [PublicInteractionResponseBehavior.followUp] or [EphemeralInteractionResponseBehavior.followUp] to display additional messages. @@ -102,7 +122,7 @@ public class GlobalChatInputCommandInteractionCreateEvent( ) : GlobalApplicationInteractionCreateEvent, ChatInputCommandInteractionCreateEvent, CoroutineScope by coroutineScope /** - * MessageRespondingInteraction received when a users types into an auto-completed option. + * ActionInteraction received when a users types into an auto-completed option. * * Check [AutoCompleteInteractionBehavior] on how to reply. * @@ -125,7 +145,7 @@ internal fun AutoCompleteInteractionCreateEvent( } /** - * MessageRespondingInteraction received when a users types into an auto-completed option. + * ActionInteraction received when a users types into an auto-completed option. * * Check [AutoCompleteInteractionBehavior] on how to reply. * @@ -139,7 +159,7 @@ public class GlobalAutoCompleteInteractionCreateEvent( ) : AutoCompleteInteractionCreateEvent, CoroutineScope by coroutineScope /** - * MessageRespondingInteraction received when a users types into an auto-completed option. + * ActionInteraction received when a users types into an auto-completed option. * * Check [AutoCompleteInteractionBehavior] on how to reply. * diff --git a/core/src/main/kotlin/exception/EntityNotFoundException.kt b/core/src/main/kotlin/exception/EntityNotFoundException.kt index d68407fe89c8..bf29070924df 100644 --- a/core/src/main/kotlin/exception/EntityNotFoundException.kt +++ b/core/src/main/kotlin/exception/EntityNotFoundException.kt @@ -74,6 +74,6 @@ public class EntityNotFoundException : Exception { entityNotFound(T::class.simpleName!!, commandId) public inline fun interactionNotFound(token: String): Nothing = - throw EntityNotFoundException("MessageRespondingInteraction with token $token was not found.") + throw EntityNotFoundException("ActionInteraction with token $token was not found.") } }