From aaeba3ad5d6bce185f7e41b898bff4570e49a3f5 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Thu, 17 Dec 2020 21:56:05 +0200 Subject: [PATCH 01/40] Add interaction models --- common/src/main/kotlin/entity/Interactions.kt | 186 ++++++++++++++++++ .../json/request/InteractionsRequests.kt | 44 +++++ 2 files changed, 230 insertions(+) create mode 100644 common/src/main/kotlin/entity/Interactions.kt create mode 100644 rest/src/main/kotlin/json/request/InteractionsRequests.kt diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt new file mode 100644 index 000000000000..5bcebfc5879d --- /dev/null +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -0,0 +1,186 @@ +package dev.kord.common.entity + +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@Serializable +data class ApplicationCommand( + val id: Snowflake, + @SerialName("application_id") + val applicationId: Snowflake, + val name: String, + val description: String, + val options: Optional = Optional.Missing() +) + +@Serializable +class ApplicationCommandOption( + val type: ApplicationCommandOptionType, + val name: String, + val description: String, + val default: OptionalBoolean = OptionalBoolean.Missing, + val required: OptionalBoolean = OptionalBoolean.Missing, + val choices: Optional> = Optional.Missing(), + val options: Optional> = Optional.Missing() +) + +@Serializable(ApplicationCommandOptionType.Serializer::class) +sealed class ApplicationCommandOptionType(val type: Int) { + + + object SubCommand : ApplicationCommandOptionType(1) + object SubCommandGroup : ApplicationCommandOptionType(2) + object String : ApplicationCommandOptionType(3) + object Integer : ApplicationCommandOptionType(4) + object Boolean : ApplicationCommandOptionType(5) + object User : ApplicationCommandOptionType(6) + object Channel : ApplicationCommandOptionType(7) + object Role : ApplicationCommandOptionType(8) + object Unknown : ApplicationCommandOptionType(Int.MAX_VALUE) + + companion object + + object Serializer : KSerializer { + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("ApplicationCommandOptionType", PrimitiveKind.INT) + + override fun deserialize(decoder: Decoder): ApplicationCommandOptionType { + return when (decoder.decodeInt()) { + 1 -> SubCommand + 2 -> SubCommandGroup + 3 -> String + 4 -> Integer + 5 -> Boolean + 6 -> User + 7 -> Channel + 8 -> Role + else -> Unknown + } + } + + override fun serialize(encoder: Encoder, value: ApplicationCommandOptionType) { + encoder.encodeInt(value.type) + } + } + + +} + +@Serializable +data class ApplicationCommandOptionChoice( + val name: String, + val value: String // mixed type int or string +) + +@Serializable +data class Interaction( + val id: Snowflake, + val type: InteractionType, + val data: Optional = Optional.Missing(), + @SerialName("guild_id") + val guildId: Snowflake, + @SerialName("channel_id") + val channelId: Snowflake, + val member: DiscordGuildMember, + val token: String, + val version: Int +) +@Serializable(InteractionType.Serializer::class) +sealed class InteractionType(val type: Int) { + object Ping : InteractionType(1) + object ApplicationCommand : InteractionType(2) + object Unknown : InteractionType(Int.MAX_VALUE) + + companion object + + object Serializer : KSerializer { + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("InteractionType", PrimitiveKind.INT) + + override fun deserialize(decoder: Decoder): InteractionType { + return when (decoder.decodeInt()) { + 1 -> Ping + 2 -> ApplicationCommand + else -> Unknown + } + } + + override fun serialize(encoder: Encoder, value: InteractionType) { + TODO("Not yet implemented") + } + + } +} +@Serializable +data class ApplicationCommandInteractionData( + val id: Snowflake, + val name: String, + val options: Optional> = Optional.Missing() +) + +@Serializable +data class ApplicationCommandInteractionDataOption( + val name: String, + val value: Optional = Optional.Missing(), + val options: Optional> = Optional.Missing() +) + +@Serializable +data class InteractionResponse( + val type: InteractionResponseType, + val data: Optional = Optional.Missing() +) + +@Serializable +sealed class InteractionResponseType(val type: Int) { + object Pong : InteractionResponseType(1) + object Acknowledge : InteractionResponseType(2) + object ChannelMessage : InteractionResponseType(3) + object ChannelMessageWithSource : InteractionResponseType(4) + object ACKWithSource : InteractionResponseType(5) + object Unknown : InteractionResponseType(Int.MAX_VALUE) + + companion object + + object Serializer : KSerializer { + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("InteractionResponseType", PrimitiveKind.INT) + + override fun deserialize(decoder: Decoder): InteractionResponseType { + return when (decoder.decodeInt()) { + 1 -> Pong + 2 -> Acknowledge + 3 -> ChannelMessage + 4 -> ChannelMessageWithSource + 5 -> ACKWithSource + else -> Unknown + } + } + + override fun serialize(encoder: Encoder, value: InteractionResponseType) { + encoder.encodeInt(value.type) + } + + } +} + +@Serializable +class InteractionApplicationCommandCallbackData( + val tts: OptionalBoolean = OptionalBoolean.Missing, + val content: String, + val embeds: Optional> = Optional.Missing(), + val allowedMentions: Optional = Optional.Missing() + +) \ 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 new file mode 100644 index 000000000000..b9668f69d388 --- /dev/null +++ b/rest/src/main/kotlin/json/request/InteractionsRequests.kt @@ -0,0 +1,44 @@ +package dev.kord.rest.json.request + +import dev.kord.common.entity.AllowedMentions +import dev.kord.common.entity.ApplicationCommandOption +import dev.kord.common.entity.DiscordEmbed +import dev.kord.common.entity.optional.Optional +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GlobalApplicationCommandCreateRequest( + val name: String, + val description: String, + val options: Optional> +) + +@Serializable +data class GlobalApplicationCommandModifyRequest( + val name: String, + val description: String, + val options: Optional> +) + +@Serializable +data class GuildApplicationCommandCreateRequest( + val name: String, + val description: String, + val options: Optional> +) + +@Serializable +data class GuildApplicationCommandModifyRequest( + val name: String, + val description: String, + val options: Optional> +) + +@Serializable +data class OriginalInteractionResponseModifyRequest( + val content: String, + val embeds: List, + @SerialName("allowed_mentions") + val allowedMentions: AllowedMentions, +) From eb55b44cd0d8742f668a5a3b527b0912e23e1afb Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Thu, 17 Dec 2020 22:05:01 +0200 Subject: [PATCH 02/40] Add interaction routes --- rest/src/main/kotlin/route/Route.kt | 228 ++++++++++++++++++++++++---- 1 file changed, 197 insertions(+), 31 deletions(-) diff --git a/rest/src/main/kotlin/route/Route.kt b/rest/src/main/kotlin/route/Route.kt index 748e1a02f746..efa323bb3b27 100644 --- a/rest/src/main/kotlin/route/Route.kt +++ b/rest/src/main/kotlin/route/Route.kt @@ -6,10 +6,11 @@ import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.* import dev.kord.rest.json.optional import dev.kord.rest.json.response.* -import io.ktor.http.HttpMethod -import kotlinx.serialization.* +import io.ktor.http.* +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.descriptors.buildSerialDescriptor @@ -20,13 +21,14 @@ internal const val REST_VERSION_PROPERTY_NAME = "dev.kord.rest.version" internal val restVersion get() = System.getenv(REST_VERSION_PROPERTY_NAME) ?: "v8" sealed class Route( - val method: HttpMethod, - val path: String, - val strategy: DeserializationStrategy + val method: HttpMethod, + val path: String, + val strategy: DeserializationStrategy ) { @OptIn(ExperimentalSerializationApi::class) - override fun toString(): String = "Route(method:${method.value},path:$path,strategy:${strategy.descriptor.serialName})" + override fun toString(): String = + "Route(method:${method.value},path:$path,strategy:${strategy.descriptor.serialName})" object GatewayGet : Route(HttpMethod.Get, "/gateway", GatewayResponse.serializer()) @@ -56,13 +58,25 @@ sealed class Route( : Route(HttpMethod.Get, "/channels/$ChannelId/messages/$MessageId", DiscordMessage.serializer()) object MessagesGet - : Route>(HttpMethod.Get, "/channels/$ChannelId/messages", ListSerializer(DiscordMessage.serializer())) + : Route>( + HttpMethod.Get, + "/channels/$ChannelId/messages", + ListSerializer(DiscordMessage.serializer()) + ) object PinsGet - : Route>(HttpMethod.Get, "/channels/$ChannelId/pins", ListSerializer(DiscordMessage.serializer())) + : Route>( + HttpMethod.Get, + "/channels/$ChannelId/pins", + ListSerializer(DiscordMessage.serializer()) + ) object InvitesGet - : Route>(HttpMethod.Get, "/channels/$ChannelId/invites", ListSerializer(DiscordInvite.serializer())) + : Route>( + HttpMethod.Get, + "/channels/$ChannelId/invites", + ListSerializer(DiscordInvite.serializer()) + ) object InvitePost : Route(HttpMethod.Post, "/channels/$ChannelId/invites", DiscordInvite.serializer()) @@ -74,7 +88,8 @@ sealed class Route( : Route(HttpMethod.Delete, "/channels/$ChannelId/messages/$MessageId/reactions/$Emoji/@me", NoStrategy) object ReactionDelete - : Route(HttpMethod.Delete, "/channels/$ChannelId/messages/$MessageId/reactions/$Emoji/$UserId", NoStrategy) + : + Route(HttpMethod.Delete, "/channels/$ChannelId/messages/$MessageId/reactions/$Emoji/$UserId", NoStrategy) object DeleteAllReactionsForEmoji : Route(HttpMethod.Delete, "/channels/$ChannelId/messages/$MessageId/reactions/$Emoji", NoStrategy) @@ -101,7 +116,11 @@ sealed class Route( : Route(HttpMethod.Put, "/channels/$ChannelId/permissions/$OverwriteId", NoStrategy) object ReactionsGet - : Route>(HttpMethod.Get, "/channels/$ChannelId/messages/$MessageId/reactions/$Emoji", ListSerializer(DiscordUser.serializer())) + : Route>( + HttpMethod.Get, + "/channels/$ChannelId/messages/$MessageId/reactions/$Emoji", + ListSerializer(DiscordUser.serializer()) + ) object TypingIndicatorPost : Route(HttpMethod.Post, "/channels/$ChannelId/typing", NoStrategy) @@ -113,7 +132,8 @@ sealed class Route( : Route(HttpMethod.Put, "/channels/$ChannelId/recipients/$UserId", NoStrategy) object EditMessagePatch - : Route(HttpMethod.Patch, "/channels/$ChannelId/messages/$MessageId", DiscordMessage.serializer()) + : + Route(HttpMethod.Patch, "/channels/$ChannelId/messages/$MessageId", DiscordMessage.serializer()) object GuildEmojiGet : Route(HttpMethod.Get, "/guilds/$GuildId/emojis/$EmojiId", EmojiEntity.serializer()) @@ -146,7 +166,11 @@ sealed class Route( : Route(HttpMethod.Get, "/users/$UserId", DiscordUser.serializer()) object CurrentUsersGuildsGet - : Route>(HttpMethod.Get, "/users/@me/guilds", ListSerializer(DiscordPartialGuild.serializer())) + : Route>( + HttpMethod.Get, + "/users/@me/guilds", + ListSerializer(DiscordPartialGuild.serializer()) + ) object GuildLeave : Route(HttpMethod.Delete, "/users/@me/guilds/$GuildId", NoStrategy) @@ -170,7 +194,11 @@ sealed class Route( : Route(HttpMethod.Delete, "/guilds/$GuildId", NoStrategy) object GuildChannelsGet - : Route>(HttpMethod.Get, "/guilds/$GuildId/channels", ListSerializer(DiscordChannel.serializer())) + : Route>( + HttpMethod.Get, + "/guilds/$GuildId/channels", + ListSerializer(DiscordChannel.serializer()) + ) object GuildChannelsPost : Route(HttpMethod.Post, "/guilds/$GuildId/channels", DiscordChannel.serializer()) @@ -182,20 +210,37 @@ sealed class Route( : Route(HttpMethod.Get, "/guilds/$GuildId/members/$UserId", DiscordGuildMember.serializer()) object GuildMembersGet - : Route>(HttpMethod.Get, "/guilds/$GuildId/members", ListSerializer(DiscordGuildMember.serializer())) + : Route>( + HttpMethod.Get, + "/guilds/$GuildId/members", + ListSerializer(DiscordGuildMember.serializer()) + ) @KordExperimental object GuildMembersSearchGet //https://github.com/discord/discord-api-docs/pull/1577 - : Route>(HttpMethod.Get, "/guilds/$GuildId/members/search", ListSerializer(DiscordGuildMember.serializer())) + : Route>( + HttpMethod.Get, + "/guilds/$GuildId/members/search", + ListSerializer(DiscordGuildMember.serializer()) + ) object GuildMemberPut - : Route(HttpMethod.Put, "/guilds/$GuildId/members/$UserId", DiscordGuildMember.serializer().optional) + : Route( + HttpMethod.Put, + "/guilds/$GuildId/members/$UserId", + DiscordGuildMember.serializer().optional + ) object GuildMemberPatch - : Route(HttpMethod.Patch, "/guilds/$GuildId/members/$UserId", DiscordGuildMember.serializer()) + : + Route(HttpMethod.Patch, "/guilds/$GuildId/members/$UserId", DiscordGuildMember.serializer()) object GuildCurrentUserNickPatch - : Route(HttpMethod.Patch, "/guilds/$GuildId/members/@me/nick", CurrentUserNicknameModifyResponse.serializer()) + : Route( + HttpMethod.Patch, + "/guilds/$GuildId/members/@me/nick", + CurrentUserNicknameModifyResponse.serializer() + ) object GuildMemberRolePut : Route(HttpMethod.Put, "/guilds/$GuildId/members/$UserId/roles/$RoleId", NoStrategy) @@ -240,13 +285,25 @@ sealed class Route( : Route(HttpMethod.Post, "/guilds/$GuildId/prune", PruneResponse.serializer()) object GuildVoiceRegionsGet - : Route>(HttpMethod.Get, "/guilds/$GuildId/regions", ListSerializer(DiscordVoiceRegion.serializer())) + : Route>( + HttpMethod.Get, + "/guilds/$GuildId/regions", + ListSerializer(DiscordVoiceRegion.serializer()) + ) object GuildInvitesGet - : Route>(HttpMethod.Get, "/guilds/$GuildId/invites", ListSerializer(DiscordInvite.serializer())) + : Route>( + HttpMethod.Get, + "/guilds/$GuildId/invites", + ListSerializer(DiscordInvite.serializer()) + ) object GuildIntegrationGet - : Route>(HttpMethod.Get, "/guilds/$GuildId/integrations", ListSerializer(DiscordIntegration.serializer())) + : Route>( + HttpMethod.Get, + "/guilds/$GuildId/integrations", + ListSerializer(DiscordIntegration.serializer()) + ) object GuildIntegrationPost : Route(HttpMethod.Post, "/guilds/$GuildId/integrations", NoStrategy) @@ -282,11 +339,19 @@ sealed class Route( @KordPreview object MessageCrosspost - : Route(HttpMethod.Post, "/channels/$ChannelId/messages/$MessageId/crosspost", DiscordMessage.serializer()) + : Route( + HttpMethod.Post, + "/channels/$ChannelId/messages/$MessageId/crosspost", + DiscordMessage.serializer() + ) @KordPreview object NewsChannelFollow - : Route(HttpMethod.Post, "/channels/$ChannelId/followers", FollowedChannelResponse.serializer()) + : Route( + HttpMethod.Post, + "/channels/$ChannelId/followers", + FollowedChannelResponse.serializer() + ) /** * Returns the guild preview object for the given id, even if the user is not in the guild. @@ -297,10 +362,18 @@ sealed class Route( : Route(HttpMethod.Get, "/guilds/${GuildId}/preview", DiscordGuildPreview.serializer()) object ChannelWebhooksGet - : Route>(HttpMethod.Get, "/channels/$ChannelId/webhooks", ListSerializer(DiscordWebhook.serializer())) + : Route>( + HttpMethod.Get, + "/channels/$ChannelId/webhooks", + ListSerializer(DiscordWebhook.serializer()) + ) object GuildWebhooksGet - : Route>(HttpMethod.Get, "/guild/$GuildId/webhooks", ListSerializer(DiscordWebhook.serializer())) + : Route>( + HttpMethod.Get, + "/guild/$GuildId/webhooks", + ListSerializer(DiscordWebhook.serializer()) + ) object WebhookGet : Route(HttpMethod.Get, "/webhooks/$WebhookId", DiscordWebhook.serializer()) @@ -326,7 +399,11 @@ sealed class Route( //TODO Make sure of the return of these routes below object ExecuteWebhookPost - : Route(HttpMethod.Post, "/webhooks/$WebhookId/$WebhookToken", DiscordMessage.serializer().optional) + : Route( + HttpMethod.Post, + "/webhooks/$WebhookId/$WebhookToken", + DiscordMessage.serializer().optional + ) object ExecuteSlackWebhookPost : Route(HttpMethod.Post, "/webhooks/$WebhookId/$WebhookToken/slack", NoStrategy) @@ -335,10 +412,15 @@ sealed class Route( : Route(HttpMethod.Post, "/webhooks/$WebhookId/$WebhookToken", NoStrategy) object VoiceRegionsGet - : Route>(HttpMethod.Get, "/voice/regions", ListSerializer(DiscordVoiceRegion.serializer())) + : Route>( + HttpMethod.Get, + "/voice/regions", + ListSerializer(DiscordVoiceRegion.serializer()) + ) object CurrentApplicationInfo - : Route(HttpMethod.Get, "/oauth2/applications/@me", ApplicationInfoResponse.serializer()) + : + Route(HttpMethod.Get, "/oauth2/applications/@me", ApplicationInfoResponse.serializer()) object TemplateGet : Route(HttpMethod.Get, "guilds/templates/${TemplateCode}", DiscordTemplate.serializer()) @@ -346,6 +428,86 @@ sealed class Route( object TemplatePost : Route(HttpMethod.Post, "guilds/templates/${TemplateCode}", DiscordGuild.serializer()) + object GlobalApplicationCommandsGet + : Route>( + HttpMethod.Get, "/applications/${ApplicationId}/commands", ListSerializer(ApplicationCommand.serializer()) + ) + + object GlobalApplicationCommandCreate : Route( + HttpMethod.Post, + "/applications/${ApplicationId}/commands", + ApplicationCommand.serializer() + ) + + object GlobalApplicationCommandModify : Route( + HttpMethod.Patch, + "/applications/${ApplicationId}/commands/${CommandId}", + ApplicationCommand.serializer() + ) + + object GlobalApplicationCommandDelete + : Route( + HttpMethod.Delete, "/applications/${ApplicationId}/commands/${CommandId}", NoStrategy + ) + + object GuildApplicationCommandsGet + : Route>( + HttpMethod.Get, + "/applications/${ApplicationId}/guilds/${GuildId}/commands", + ListSerializer(ApplicationCommand.serializer()) + ) + + object GuildApplicationCommandCreate : Route( + HttpMethod.Post, + "/applications/${ApplicationId}/guilds/${GuildId}/commands", + ApplicationCommand.serializer() + ) + + object GuildApplicationCommandModify + : Route( + HttpMethod.Patch, + "/applications/${ApplicationId}/guilds/${GuildId}/commands/${CommandId}", + ApplicationCommand.serializer() + ) + + object GuildApplicationCommandDelete + : Route( + HttpMethod.Delete, + "/applications/${ApplicationId}/guilds/{guild.id}/commands/${CommandId}", + NoStrategy + ) + + object InteractionResponseCreate : Route( + HttpMethod.Post, + "/interactions/${InteractionId}/${InteractionToken}/callback", + InteractionResponse.serializer() + ) + + object OriginalInteractionResponseModify : + Route( + HttpMethod.Patch, + "/webhooks/${ApplicationId}/${InteractionToken}/messages/@original", + InteractionResponse.serializer() + ) + + object OriginalInteractionResponseDelete + : Route(HttpMethod.Delete, "/webhooks/${ApplicationId}/${InteractionToken}/messages/@original", NoStrategy) + + object FollowupMessageCreate : Route( + HttpMethod.Post, + "/webhooks/${ApplicationId}/${InteractionToken}", + DiscordMessage.serializer() + ) + + object FollowupMessageModify : Route( + HttpMethod.Patch, + "/webhooks/${ApplicationId}/${InteractionToken}/messages/${MessageId}", + DiscordMessage.serializer() + ) + + object FollowupMessageDelete : + Route(HttpMethod.Delete, "/webhooks/${ApplicationId}/${InteractionToken}/messages/${MessageId}", NoStrategy) + companion object { val baseUrl = "https://discord.com/api/$restVersion" } @@ -366,7 +528,11 @@ sealed class Route( object IntegrationId : Key("{integration.id}") object WebhookId : Key("{webhook.id}", true) object WebhookToken : Key("{webhook.token}") - object TemplateCode: Key("{template.code}") + object TemplateCode : Key("{template.code}") + object ApplicationId : Key("{application.id}") + object CommandId : Key("{command.id}") + object InteractionId : Key("interaction.id") + object InteractionToken : Key("{interaction.token}") } From 0bf62858847a3f4fa8b6bde63ae756a3382c86f6 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Thu, 17 Dec 2020 22:06:42 +0200 Subject: [PATCH 03/40] Add interactions rest service --- .../main/kotlin/service/InteractionService.kt | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 rest/src/main/kotlin/service/InteractionService.kt diff --git a/rest/src/main/kotlin/service/InteractionService.kt b/rest/src/main/kotlin/service/InteractionService.kt new file mode 100644 index 000000000000..803d1e9204db --- /dev/null +++ b/rest/src/main/kotlin/service/InteractionService.kt @@ -0,0 +1,130 @@ +package dev.kord.rest.service + +import dev.kord.common.entity.* +import dev.kord.rest.json.request.* +import dev.kord.rest.request.RequestHandler +import dev.kord.rest.route.Route + +class InteractionService(requestHandler: RequestHandler) : RestService(requestHandler) { + suspend fun getGlobalApplicationCommands(applicationId: Snowflake): List = + call(Route.GlobalApplicationCommandsGet) { + keys[Route.ApplicationId] = applicationId + } + + suspend fun createGlobalApplicationCommand( + applicationId: Snowflake, + request: GlobalApplicationCommandCreateRequest + ): ApplicationCommand = call(Route.GlobalApplicationCommandCreate) { + keys[Route.ApplicationId] = applicationId + body(GlobalApplicationCommandCreateRequest.serializer(), request) + } + + suspend fun modifyGlobalApplicationCommand( + applicationId: Snowflake, + commandId: Snowflake, + request: GlobalApplicationCommandModifyRequest + ) = call(Route.GlobalApplicationCommandModify) { + keys[Route.ApplicationId] = applicationId + keys[Route.CommandId] = commandId + body(GlobalApplicationCommandModifyRequest.serializer(), request) + } + + suspend fun deleteGlobalApplicationCommand(applicationId: Snowflake, commandId: Snowflake) = + call(Route.GlobalApplicationCommandDelete) { + keys[Route.ApplicationId] = applicationId + keys[Route.CommandId] = commandId + } + + suspend fun getGuildApplicationCommands(applicationId: Snowflake, guildId: Snowflake) = + call(Route.GuildApplicationCommandsGet) { + keys[Route.ApplicationId] = applicationId + keys[Route.GuildId] = guildId + } + + suspend fun createGuildApplicationCommands( + applicationId: Snowflake, + guildId: Snowflake, + request: GuildApplicationCommandCreateRequest + ) = + call(Route.GuildApplicationCommandCreate) { + keys[Route.ApplicationId] = applicationId + keys[Route.GuildId] = guildId + body(GuildApplicationCommandCreateRequest.serializer(), request) + } + + suspend fun modifyGuildApplicationCommand( + applicationId: Snowflake, + guildId: Snowflake, + commandId: Snowflake, + request: GuildApplicationCommandModifyRequest + ) = call(Route.GuildApplicationCommandModify) { + keys[Route.ApplicationId] = applicationId + keys[Route.GuildId] = guildId + keys[Route.CommandId] = commandId + body(GuildApplicationCommandModifyRequest.serializer(), request) + } + + suspend fun deleteGuildApplicationCommand(applicationId: Snowflake, guildId: Snowflake, commandId: Snowflake) = + call(Route.GuildApplicationCommandDelete) { + keys[Route.ApplicationId] = applicationId + keys[Route.GuildId] = guildId + keys[Route.CommandId] = commandId + } + + suspend fun createInteractionResponse( + interactionId: Snowflake, + interactionToken: String, + request: InteractionResponse + ) = + call(Route.InteractionResponseCreate) { + keys[Route.InteractionId] = interactionId + keys[Route.InteractionToken] = interactionToken + body(InteractionResponse.serializer(), request) + } + + suspend fun modifyInteractionResponse( + interactionId: Snowflake, + interactionToken: String, + request: InteractionResponse + ) = call(Route.OriginalInteractionResponseModify) { + keys[Route.InteractionId] = interactionId + keys[Route.InteractionToken] = interactionToken + body(InteractionResponse.serializer(), request) + + } + + suspend fun deleteOriginalInteractionResponse(applicationId: Snowflake, interactionToken: String) = + call(Route.OriginalInteractionResponseDelete) { + keys[Route.ApplicationId] = applicationId + keys[Route.InteractionToken] = interactionToken + } + + suspend fun createFollowupMessage( + applicationId: Snowflake, + interactionToken: String, + request: MessageCreateRequest + ) = call(Route.FollowupMessageCreate) { + keys[Route.ApplicationId] = applicationId + keys[Route.InteractionToken] = interactionToken + body(MessageCreateRequest.serializer(), request) + } + + suspend fun deleteFollowupMessage(applicationId: Snowflake, interactionToken: String, messageId: Snowflake) = + call(Route.FollowupMessageDelete) { + keys[Route.ApplicationId] = applicationId + keys[Route.InteractionToken] = interactionToken + keys[Route.MessageId] = messageId + } + + suspend fun modifyFollowupMessage( + applicationId: Snowflake, + interactionToken: String, + messageId: Snowflake, + request: MessageEditPatchRequest + ) = call(Route.EditMessagePatch) { + keys[Route.ApplicationId] = applicationId + keys[Route.InteractionToken] = interactionToken + keys[Route.MessageId] = messageId + body(MessageEditPatchRequest.serializer(), request) + } +} \ No newline at end of file From 74e69c1165549f6d7b019cf751e6e86892698957 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Thu, 17 Dec 2020 22:07:14 +0200 Subject: [PATCH 04/40] Move AllowedMentions to common --- .../src/main/kotlin/entity/DiscordMessage.kt | 31 ++++++++++++++++ .../builder/message/MessageCreateBuilder.kt | 4 +-- .../kotlin/json/request/MessageRequests.kt | 35 +------------------ .../kotlin/json/request/WebhookRequests.kt | 1 + 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/common/src/main/kotlin/entity/DiscordMessage.kt b/common/src/main/kotlin/entity/DiscordMessage.kt index 3db68810842f..28453be4e6fb 100644 --- a/common/src/main/kotlin/entity/DiscordMessage.kt +++ b/common/src/main/kotlin/entity/DiscordMessage.kt @@ -714,4 +714,35 @@ enum class MessageType(val code: Int) { } } } +@Serializable(with = AllowedMentionType.Serializer::class) +sealed class AllowedMentionType(val value: String) { + class Unknown(value: String) : AllowedMentionType(value) + object RoleMentions : AllowedMentionType("roles") + object UserMentions : AllowedMentionType("users") + object EveryoneMentions : AllowedMentionType("everyone") + + internal class Serializer : KSerializer { + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("Kord.DiscordAllowedMentionType", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): AllowedMentionType = when(val value = decoder.decodeString()) { + "roles" -> RoleMentions + "users" -> UserMentions + "everyone" -> EveryoneMentions + else -> Unknown(value) + } + + override fun serialize(encoder: Encoder, value: AllowedMentionType) { + encoder.encodeString(value.value) + } + } +} +@Serializable +data class AllowedMentions( + val parse: List, + val users: List, + val roles: List, + @SerialName("replied_user") + val repliedUser: OptionalBoolean = OptionalBoolean.Missing +) \ No newline at end of file diff --git a/rest/src/main/kotlin/builder/message/MessageCreateBuilder.kt b/rest/src/main/kotlin/builder/message/MessageCreateBuilder.kt index ca724d217452..5a2570e22635 100644 --- a/rest/src/main/kotlin/builder/message/MessageCreateBuilder.kt +++ b/rest/src/main/kotlin/builder/message/MessageCreateBuilder.kt @@ -1,6 +1,8 @@ package dev.kord.rest.builder.message import dev.kord.common.annotation.KordDsl +import dev.kord.common.entity.AllowedMentionType +import dev.kord.common.entity.AllowedMentions import dev.kord.common.entity.DiscordMessageReference import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.Optional @@ -9,8 +11,6 @@ import dev.kord.common.entity.optional.OptionalSnowflake import dev.kord.common.entity.optional.delegate.delegate import dev.kord.common.entity.optional.map import dev.kord.rest.builder.RequestBuilder -import dev.kord.rest.json.request.AllowedMentions -import dev.kord.rest.json.request.AllowedMentionType import dev.kord.rest.json.request.MessageCreateRequest import dev.kord.rest.json.request.MultipartMessageCreateRequest import kotlinx.coroutines.Dispatchers diff --git a/rest/src/main/kotlin/json/request/MessageRequests.kt b/rest/src/main/kotlin/json/request/MessageRequests.kt index e93f092c9255..51f5317a510c 100644 --- a/rest/src/main/kotlin/json/request/MessageRequests.kt +++ b/rest/src/main/kotlin/json/request/MessageRequests.kt @@ -1,9 +1,7 @@ package dev.kord.rest.json.request import dev.kord.common.Color -import dev.kord.common.entity.DiscordMessageReference -import dev.kord.common.entity.Snowflake -import dev.kord.common.entity.UserFlags +import dev.kord.common.entity.* import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.OptionalInt @@ -28,38 +26,7 @@ data class MessageCreateRequest( val messageReference: Optional = Optional.Missing() ) -@Serializable -data class AllowedMentions( - val parse: List, - val users: List, - val roles: List, - @SerialName("replied_user") - val repliedUser: OptionalBoolean = OptionalBoolean.Missing -) - -@Serializable(with = AllowedMentionType.Serializer::class) -sealed class AllowedMentionType(val value: String) { - class Unknown(value: String) : AllowedMentionType(value) - object RoleMentions : AllowedMentionType("roles") - object UserMentions : AllowedMentionType("users") - object EveryoneMentions : AllowedMentionType("everyone") - - internal class Serializer : KSerializer { - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("Kord.DiscordAllowedMentionType", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): AllowedMentionType = when(val value = decoder.decodeString()) { - "roles" -> RoleMentions - "users" -> UserMentions - "everyone" -> EveryoneMentions - else -> Unknown(value) - } - override fun serialize(encoder: Encoder, value: AllowedMentionType) { - encoder.encodeString(value.value) - } - } -} data class MultipartMessageCreateRequest( val request: MessageCreateRequest, diff --git a/rest/src/main/kotlin/json/request/WebhookRequests.kt b/rest/src/main/kotlin/json/request/WebhookRequests.kt index 63ae867dc6b6..f09faf2624f0 100644 --- a/rest/src/main/kotlin/json/request/WebhookRequests.kt +++ b/rest/src/main/kotlin/json/request/WebhookRequests.kt @@ -1,5 +1,6 @@ package dev.kord.rest.json.request +import dev.kord.common.entity.AllowedMentions import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.OptionalSnowflake From 965f566fa7aed9c45d0fc1c303af44fb4bcb593e Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Thu, 17 Dec 2020 22:13:11 +0200 Subject: [PATCH 05/40] Add serialization strategy for interaction type --- common/src/main/kotlin/entity/Interactions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index 5bcebfc5879d..f7002d569ede 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -117,7 +117,7 @@ sealed class InteractionType(val type: Int) { } override fun serialize(encoder: Encoder, value: InteractionType) { - TODO("Not yet implemented") + encoder.encodeInt(value.type) } } From 8c89b6c79a8c914dd04770b66da05fbd31d28bcf Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Fri, 18 Dec 2020 16:05:20 +0200 Subject: [PATCH 06/40] Add interaction create event --- common/src/main/kotlin/entity/Interactions.kt | 8 +- gateway/src/main/kotlin/Event.kt | 469 +++++++++++++----- 2 files changed, 354 insertions(+), 123 deletions(-) diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index f7002d569ede..d9832a14b803 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -13,7 +13,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Serializable -data class ApplicationCommand( +data class DiscordApplicationCommand( val id: Snowflake, @SerialName("application_id") val applicationId: Snowflake, @@ -29,7 +29,7 @@ class ApplicationCommandOption( val description: String, val default: OptionalBoolean = OptionalBoolean.Missing, val required: OptionalBoolean = OptionalBoolean.Missing, - val choices: Optional> = Optional.Missing(), + val choices: Optional> = Optional.Missing(), val options: Optional> = Optional.Missing() ) @@ -77,13 +77,13 @@ sealed class ApplicationCommandOptionType(val type: Int) { } @Serializable -data class ApplicationCommandOptionChoice( +data class DiscordApplicationCommandOptionChoice( val name: String, val value: String // mixed type int or string ) @Serializable -data class Interaction( +data class DiscordInteraction( val id: Snowflake, val type: InteractionType, val data: Optional = Optional.Missing(), diff --git a/gateway/src/main/kotlin/Event.kt b/gateway/src/main/kotlin/Event.kt index 539dae5e62ff..1fec731c693e 100644 --- a/gateway/src/main/kotlin/Event.kt +++ b/gateway/src/main/kotlin/Event.kt @@ -56,7 +56,8 @@ sealed class Event { var eventName: String? = null //this isn't actually unused but seems to be a compiler bug with(decoder.beginStructure(descriptor)) { loop@ while (true) { - when (val index = decodeElementIndex(descriptor)) {//we assume the all fields to be present *before* the data field + when (val index = + decodeElementIndex(descriptor)) {//we assume the all fields to be present *before* the data field CompositeDecoder.DECODE_DONE -> break@loop 0 -> { op = OpCode.deserialize(decoder) @@ -66,7 +67,8 @@ sealed class Event { OpCode.Reconnect -> data = Reconnect } } - 1 -> eventName = decodeNullableSerializableElement(descriptor, index, String.serializer().nullable) + 1 -> eventName = + decodeNullableSerializableElement(descriptor, index, String.serializer().nullable) 2 -> sequence = decodeNullableSerializableElement(descriptor, index, Int.serializer().nullable) 3 -> data = when (op) { OpCode.Dispatch -> getByDispatchEvent(index, this, eventName, sequence) @@ -75,11 +77,19 @@ sealed class Event { this.decodeSerializableElement(descriptor, index, NullDecoder) HeartbeatACK } - OpCode.InvalidSession -> decodeSerializableElement(descriptor, index, InvalidSession.Serializer) + OpCode.InvalidSession -> decodeSerializableElement( + descriptor, + index, + InvalidSession.Serializer + ) OpCode.Hello -> decodeSerializableElement(descriptor, index, Hello.serializer()) //some events contain undocumented data fields, we'll only assume an unknown opcode with no data to be an error else -> if (data == null) { - val element = decodeNullableSerializableElement(descriptor, index, JsonElement.serializer().nullable) + val element = decodeNullableSerializableElement( + descriptor, + index, + JsonElement.serializer().nullable + ) error("Unknown 'd' field for Op code ${op?.code}: $element") } else { decodeNullableSerializableElement(descriptor, index, JsonElement.serializer().nullable) @@ -96,60 +106,272 @@ sealed class Event { @OptIn(ExperimentalSerializationApi::class) - private fun getByDispatchEvent(index: Int, decoder: CompositeDecoder, name: String?, sequence: Int?) = when (name) { - "PRESENCES_REPLACE" -> { - decoder.decodeNullableSerializableElement(descriptor, index, JsonElement.serializer().nullable) - null //https://github.com/kordlib/kord/issues/42 - } - "RESUMED" -> { - decoder.decodeNullableSerializableElement(descriptor, index, JsonElement.serializer().nullable) - Resumed(sequence) - } - "READY" -> Ready(decoder.decodeSerializableElement(descriptor, index, ReadyData.serializer()), sequence) - "CHANNEL_CREATE" -> ChannelCreate(decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), sequence) - "CHANNEL_UPDATE" -> ChannelUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), sequence) - "CHANNEL_DELETE" -> ChannelDelete(decoder.decodeSerializableElement(descriptor, index, DiscordChannel.serializer()), sequence) - "CHANNEL_PINS_UPDATE" -> ChannelPinsUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordPinsUpdateData.serializer()), sequence) - "TYPING_START" -> TypingStart(decoder.decodeSerializableElement(descriptor, index, DiscordTyping.serializer()), sequence) - "GUILD_CREATE" -> GuildCreate(decoder.decodeSerializableElement(descriptor, index, DiscordGuild.serializer()), sequence) - "GUILD_UPDATE" -> GuildUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordGuild.serializer()), sequence) - "GUILD_DELETE" -> GuildDelete(decoder.decodeSerializableElement(descriptor, index, DiscordUnavailableGuild.serializer()), sequence) - "GUILD_BAN_ADD" -> GuildBanAdd(decoder.decodeSerializableElement(descriptor, index, DiscordGuildBan.serializer()), sequence) - "GUILD_BAN_REMOVE" -> GuildBanRemove(decoder.decodeSerializableElement(descriptor, index, DiscordGuildBan.serializer()), sequence) - "GUILD_EMOJIS_UPDATE" -> GuildEmojisUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordUpdatedEmojis.serializer()), sequence) - "GUILD_INTEGRATIONS_UPDATE" -> GuildIntegrationsUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordGuildIntegrations.serializer()), sequence) - "GUILD_MEMBER_ADD" -> GuildMemberAdd(decoder.decodeSerializableElement(descriptor, index, DiscordAddedGuildMember.serializer()), sequence) - "GUILD_MEMBER_REMOVE" -> GuildMemberRemove(decoder.decodeSerializableElement(descriptor, index, DiscordRemovedGuildMember.serializer()), sequence) - "GUILD_MEMBER_UPDATE" -> GuildMemberUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordUpdatedGuildMember.serializer()), sequence) - "GUILD_ROLE_CREATE" -> GuildRoleCreate(decoder.decodeSerializableElement(descriptor, index, DiscordGuildRole.serializer()), sequence) - "GUILD_ROLE_UPDATE" -> GuildRoleUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordGuildRole.serializer()), sequence) - "GUILD_ROLE_DELETE" -> GuildRoleDelete(decoder.decodeSerializableElement(descriptor, index, DiscordDeletedGuildRole.serializer()), sequence) - "GUILD_MEMBERS_CHUNK" -> GuildMembersChunk(decoder.decodeSerializableElement(descriptor, index, GuildMembersChunkData.serializer()), sequence) - - "INVITE_CREATE" -> InviteCreate(decoder.decodeSerializableElement(descriptor, index, DiscordCreatedInvite.serializer()), sequence) - "INVITE_DELETE" -> InviteDelete(decoder.decodeSerializableElement(descriptor, index, DiscordDeletedInvite.serializer()), sequence) - - "MESSAGE_CREATE" -> MessageCreate(decoder.decodeSerializableElement(descriptor, index, DiscordMessage.serializer()), sequence) - "MESSAGE_UPDATE" -> MessageUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordPartialMessage.serializer()), sequence) - "MESSAGE_DELETE" -> MessageDelete(decoder.decodeSerializableElement(descriptor, index, DeletedMessage.serializer()), sequence) - "MESSAGE_DELETE_BULK" -> MessageDeleteBulk(decoder.decodeSerializableElement(descriptor, index, BulkDeleteData.serializer()), sequence) - "MESSAGE_REACTION_ADD" -> MessageReactionAdd(decoder.decodeSerializableElement(descriptor, index, MessageReactionAddData.serializer()), sequence) - "MESSAGE_REACTION_REMOVE" -> MessageReactionRemove(decoder.decodeSerializableElement(descriptor, index, MessageReactionRemoveData.serializer()), sequence) - "MESSAGE_REACTION_REMOVE_EMOJI" -> MessageReactionRemoveEmoji(decoder.decodeSerializableElement(descriptor, index, DiscordRemovedEmoji.serializer()), sequence) - - "MESSAGE_REACTION_REMOVE_ALL" -> MessageReactionRemoveAll(decoder.decodeSerializableElement(descriptor, index, AllRemovedMessageReactions.serializer()), sequence) - "PRESENCE_UPDATE" -> PresenceUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordPresenceUpdate.serializer()), sequence) - "USER_UPDATE" -> UserUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordUser.serializer()), sequence) - "VOICE_STATE_UPDATE" -> VoiceStateUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordVoiceState.serializer()), sequence) - "VOICE_SERVER_UPDATE" -> VoiceServerUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordVoiceServerUpdateData.serializer()), sequence) - "WEBHOOKS_UPDATE" -> WebhooksUpdate(decoder.decodeSerializableElement(descriptor, index, DiscordWebhooksUpdateData.serializer()), sequence) - else -> { - jsonLogger.warn { "unknown gateway event name $name" } - // consume json elements that are unknown to us - decoder.decodeSerializableElement(descriptor, index, JsonElement.serializer().nullable) - null + private fun getByDispatchEvent(index: Int, decoder: CompositeDecoder, name: String?, sequence: Int?) = + when (name) { + "PRESENCES_REPLACE" -> { + decoder.decodeNullableSerializableElement(descriptor, index, JsonElement.serializer().nullable) + null //https://github.com/kordlib/kord/issues/42 + } + "RESUMED" -> { + decoder.decodeNullableSerializableElement(descriptor, index, JsonElement.serializer().nullable) + Resumed(sequence) + } + "READY" -> Ready(decoder.decodeSerializableElement(descriptor, index, ReadyData.serializer()), sequence) + "CHANNEL_CREATE" -> ChannelCreate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordChannel.serializer() + ), sequence + ) + "CHANNEL_UPDATE" -> ChannelUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordChannel.serializer() + ), sequence + ) + "CHANNEL_DELETE" -> ChannelDelete( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordChannel.serializer() + ), sequence + ) + "CHANNEL_PINS_UPDATE" -> ChannelPinsUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordPinsUpdateData.serializer() + ), sequence + ) + "TYPING_START" -> TypingStart( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordTyping.serializer() + ), sequence + ) + "GUILD_CREATE" -> GuildCreate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordGuild.serializer() + ), sequence + ) + "GUILD_UPDATE" -> GuildUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordGuild.serializer() + ), sequence + ) + "GUILD_DELETE" -> GuildDelete( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordUnavailableGuild.serializer() + ), sequence + ) + "GUILD_BAN_ADD" -> GuildBanAdd( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordGuildBan.serializer() + ), sequence + ) + "GUILD_BAN_REMOVE" -> GuildBanRemove( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordGuildBan.serializer() + ), sequence + ) + "GUILD_EMOJIS_UPDATE" -> GuildEmojisUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordUpdatedEmojis.serializer() + ), sequence + ) + "GUILD_INTEGRATIONS_UPDATE" -> GuildIntegrationsUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordGuildIntegrations.serializer() + ), sequence + ) + "GUILD_MEMBER_ADD" -> GuildMemberAdd( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordAddedGuildMember.serializer() + ), sequence + ) + "GUILD_MEMBER_REMOVE" -> GuildMemberRemove( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordRemovedGuildMember.serializer() + ), sequence + ) + "GUILD_MEMBER_UPDATE" -> GuildMemberUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordUpdatedGuildMember.serializer() + ), sequence + ) + "GUILD_ROLE_CREATE" -> GuildRoleCreate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordGuildRole.serializer() + ), sequence + ) + "GUILD_ROLE_UPDATE" -> GuildRoleUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordGuildRole.serializer() + ), sequence + ) + "GUILD_ROLE_DELETE" -> GuildRoleDelete( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordDeletedGuildRole.serializer() + ), sequence + ) + "GUILD_MEMBERS_CHUNK" -> GuildMembersChunk( + decoder.decodeSerializableElement( + descriptor, + index, + GuildMembersChunkData.serializer() + ), sequence + ) + + "INVITE_CREATE" -> InviteCreate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordCreatedInvite.serializer() + ), sequence + ) + "INVITE_DELETE" -> InviteDelete( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordDeletedInvite.serializer() + ), sequence + ) + + "MESSAGE_CREATE" -> MessageCreate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordMessage.serializer() + ), sequence + ) + "MESSAGE_UPDATE" -> MessageUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordPartialMessage.serializer() + ), sequence + ) + "MESSAGE_DELETE" -> MessageDelete( + decoder.decodeSerializableElement( + descriptor, + index, + DeletedMessage.serializer() + ), sequence + ) + "MESSAGE_DELETE_BULK" -> MessageDeleteBulk( + decoder.decodeSerializableElement( + descriptor, + index, + BulkDeleteData.serializer() + ), sequence + ) + "MESSAGE_REACTION_ADD" -> MessageReactionAdd( + decoder.decodeSerializableElement( + descriptor, + index, + MessageReactionAddData.serializer() + ), sequence + ) + "MESSAGE_REACTION_REMOVE" -> MessageReactionRemove( + decoder.decodeSerializableElement( + descriptor, + index, + MessageReactionRemoveData.serializer() + ), sequence + ) + "MESSAGE_REACTION_REMOVE_EMOJI" -> MessageReactionRemoveEmoji( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordRemovedEmoji.serializer() + ), sequence + ) + + "MESSAGE_REACTION_REMOVE_ALL" -> MessageReactionRemoveAll( + decoder.decodeSerializableElement( + descriptor, + index, + AllRemovedMessageReactions.serializer() + ), sequence + ) + "PRESENCE_UPDATE" -> PresenceUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordPresenceUpdate.serializer() + ), sequence + ) + "USER_UPDATE" -> UserUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordUser.serializer() + ), sequence + ) + "VOICE_STATE_UPDATE" -> VoiceStateUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordVoiceState.serializer() + ), sequence + ) + "VOICE_SERVER_UPDATE" -> VoiceServerUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordVoiceServerUpdateData.serializer() + ), sequence + ) + "WEBHOOKS_UPDATE" -> WebhooksUpdate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordWebhooksUpdateData.serializer() + ), sequence + ) + "INTERACTION_CREATE" -> InteractionCreate( + decoder.decodeSerializableElement( + descriptor, + index, + DiscordInteraction.serializer() + ), sequence + ) + else -> { + jsonLogger.warn { "unknown gateway event name $name" } + // consume json elements that are unknown to us + decoder.decodeSerializableElement(descriptor, index, JsonElement.serializer().nullable) + null + } } - } } @@ -208,30 +430,30 @@ object Reconnect : Event() @Serializable data class Hello( - @SerialName("heartbeat_interval") - val heartbeatInterval: Int, + @SerialName("heartbeat_interval") + val heartbeatInterval: Int, ) : Event() data class Ready(val data: ReadyData, override val sequence: Int?) : DispatchEvent() @Serializable data class ReadyData( - @SerialName("v") - val version: Int, - val user: DiscordUser, - @SerialName("private_channels") - val privateChannels: List, - val guilds: List, - @SerialName("session_id") - val sessionId: String, - @SerialName("geo_ordered_rtc_regions") - val geoOrderedRtcRegions: Optional = Optional.Missing(), - @SerialName("guild_hashes") - val guildHashes: Optional = Optional.Missing(), - val application: Optional = Optional.Missing(), - @SerialName("_trace") - val traces: List, - val shard: Optional = Optional.Missing(), + @SerialName("v") + val version: Int, + val user: DiscordUser, + @SerialName("private_channels") + val privateChannels: List, + val guilds: List, + @SerialName("session_id") + val sessionId: String, + @SerialName("geo_ordered_rtc_regions") + val geoOrderedRtcRegions: Optional = Optional.Missing(), + @SerialName("guild_hashes") + val guildHashes: Optional = Optional.Missing(), + val application: Optional = Optional.Missing(), + @SerialName("_trace") + val traces: List, + val shard: Optional = Optional.Missing(), ) @Serializable(with = Heartbeat.Companion::class) @@ -250,7 +472,7 @@ data class Heartbeat(val data: Long) : Event() { @Serializable data class Resumed( - override val sequence: Int?, + override val sequence: Int?, ) : DispatchEvent() @Serializable(with = InvalidSession.Serializer::class) @@ -280,7 +502,9 @@ data class GuildDelete(val guild: DiscordUnavailableGuild, override val sequence data class GuildBanAdd(val ban: DiscordGuildBan, override val sequence: Int?) : DispatchEvent() data class GuildBanRemove(val ban: DiscordGuildBan, override val sequence: Int?) : DispatchEvent() data class GuildEmojisUpdate(val emoji: DiscordUpdatedEmojis, override val sequence: Int?) : DispatchEvent() -data class GuildIntegrationsUpdate(val integrations: DiscordGuildIntegrations, override val sequence: Int?) : DispatchEvent() +data class GuildIntegrationsUpdate(val integrations: DiscordGuildIntegrations, override val sequence: Int?) : + DispatchEvent() + data class GuildMemberAdd(val member: DiscordAddedGuildMember, override val sequence: Int?) : DispatchEvent() data class GuildMemberRemove(val member: DiscordRemovedGuildMember, override val sequence: Int?) : DispatchEvent() data class GuildMemberUpdate(val member: DiscordUpdatedGuildMember, override val sequence: Int?) : DispatchEvent() @@ -301,44 +525,44 @@ data class InviteDelete(val invite: DiscordDeletedInvite, override val sequence: @Serializable data class DiscordDeletedInvite( - @SerialName("channel_id") - val channelId: Snowflake, - @SerialName("guild_id") - val guildId: Snowflake, - val code: String, + @SerialName("channel_id") + val channelId: Snowflake, + @SerialName("guild_id") + val guildId: Snowflake, + val code: String, ) @Serializable data class DiscordCreatedInvite( - @SerialName("channel_id") - val channelId: Snowflake, - val code: String, - @SerialName("created_at") - val createdAt: String, - @SerialName("guild_id") - val guildId: OptionalSnowflake = OptionalSnowflake.Missing, - val inviter: Optional = Optional.Missing(), - @SerialName("max_age") - val maxAge: Int, - @SerialName("max_uses") - val maxUses: Int, - @SerialName("target_user") - val targetUser: Optional = Optional.Missing(), - @SerialName("target_user_type") - val targetUserType: Optional = Optional.Missing(), - val temporary: Boolean, - val uses: Int, + @SerialName("channel_id") + val channelId: Snowflake, + val code: String, + @SerialName("created_at") + val createdAt: String, + @SerialName("guild_id") + val guildId: OptionalSnowflake = OptionalSnowflake.Missing, + val inviter: Optional = Optional.Missing(), + @SerialName("max_age") + val maxAge: Int, + @SerialName("max_uses") + val maxUses: Int, + @SerialName("target_user") + val targetUser: Optional = Optional.Missing(), + @SerialName("target_user_type") + val targetUserType: Optional = Optional.Missing(), + val temporary: Boolean, + val uses: Int, ) @Serializable data class DiscordInviteUser( - val id: Snowflake, - val username: String, - val discriminator: String, - val avatar: String?, - val bot: OptionalBoolean = OptionalBoolean.Missing, - @SerialName("public_flags") - val publicFlags: Optional = Optional.Missing(), + val id: Snowflake, + val username: String, + val discriminator: String, + val avatar: String?, + val bot: OptionalBoolean = OptionalBoolean.Missing, + @SerialName("public_flags") + val publicFlags: Optional = Optional.Missing(), ) data class MessageCreate(val message: DiscordMessage, override val sequence: Int?) : DispatchEvent() @@ -347,28 +571,35 @@ data class MessageDelete(val message: DeletedMessage, override val sequence: Int data class MessageDeleteBulk(val messageBulk: BulkDeleteData, override val sequence: Int?) : DispatchEvent() data class MessageReactionAdd(val reaction: MessageReactionAddData, override val sequence: Int?) : DispatchEvent() data class MessageReactionRemove(val reaction: MessageReactionRemoveData, override val sequence: Int?) : DispatchEvent() -data class MessageReactionRemoveAll(val reactions: AllRemovedMessageReactions, override val sequence: Int?) : DispatchEvent() +data class MessageReactionRemoveAll(val reactions: AllRemovedMessageReactions, override val sequence: Int?) : + DispatchEvent() + data class MessageReactionRemoveEmoji(val reaction: DiscordRemovedEmoji, override val sequence: Int?) : DispatchEvent() @Serializable data class DiscordRemovedEmoji( - @SerialName("channel_id") - val channelId: Snowflake, - @SerialName("guild_id") - val guildId: Snowflake, - @SerialName("message_id") - val messageId: Snowflake, - val emoji: DiscordRemovedReactionEmoji, + @SerialName("channel_id") + val channelId: Snowflake, + @SerialName("guild_id") + val guildId: Snowflake, + @SerialName("message_id") + val messageId: Snowflake, + val emoji: DiscordRemovedReactionEmoji, ) @Serializable data class DiscordRemovedReactionEmoji( - val id: Snowflake?, - val name: String?, + val id: Snowflake?, + val name: String?, ) data class PresenceUpdate(val presence: DiscordPresenceUpdate, override val sequence: Int?) : DispatchEvent() data class UserUpdate(val user: DiscordUser, override val sequence: Int?) : DispatchEvent() data class VoiceStateUpdate(val voiceState: DiscordVoiceState, override val sequence: Int?) : DispatchEvent() -data class VoiceServerUpdate(val voiceServerUpdateData: DiscordVoiceServerUpdateData, override val sequence: Int?) : DispatchEvent() -data class WebhooksUpdate(val webhooksUpdateData: DiscordWebhooksUpdateData, override val sequence: Int?) : DispatchEvent() +data class VoiceServerUpdate(val voiceServerUpdateData: DiscordVoiceServerUpdateData, override val sequence: Int?) : + DispatchEvent() + +data class WebhooksUpdate(val webhooksUpdateData: DiscordWebhooksUpdateData, override val sequence: Int?) : + DispatchEvent() + +data class InteractionCreate(val interaction: DiscordInteraction, override val sequence: Int?) : DispatchEvent() From 2f1b2ea16e1a8430483345054f5199f462eca942 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Fri, 18 Dec 2020 19:33:04 +0200 Subject: [PATCH 07/40] Add interactions to RestClient --- rest/src/main/kotlin/service/RestClient.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/rest/src/main/kotlin/service/RestClient.kt b/rest/src/main/kotlin/service/RestClient.kt index b8f929cdbd39..0023356c7e90 100644 --- a/rest/src/main/kotlin/service/RestClient.kt +++ b/rest/src/main/kotlin/service/RestClient.kt @@ -20,6 +20,7 @@ class RestClient(requestHandler: RequestHandler) : RestService(requestHandler) { val voice: VoiceService = VoiceService(requestHandler) val webhook: WebhookService = WebhookService(requestHandler) val application: ApplicationService = ApplicationService(requestHandler) + val interaction: InteractionService = InteractionService(requestHandler) /** * Sends a request to the given [route]. This function exposes a direct call to the Discord api and allows From 7cc469ebed1702035d6468eaa8522186cdaada6b Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Fri, 18 Dec 2020 19:34:21 +0200 Subject: [PATCH 08/40] Add interactions requests and builders --- .../interaction/InteractionsBuilder.kt | 145 ++++++++++++++++++ .../json/request/InteractionsRequests.kt | 1 + 2 files changed, 146 insertions(+) create mode 100644 rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt diff --git a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt new file mode 100644 index 000000000000..cfa291819047 --- /dev/null +++ b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt @@ -0,0 +1,145 @@ +package dev.kord.rest.builder.interaction + +import dev.kord.common.entity.ApplicationCommandOption +import dev.kord.common.entity.DiscordApplicationCommandOptionChoice +import dev.kord.common.entity.ApplicationCommandOptionType +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean +import dev.kord.common.entity.optional.delegate.delegate +import dev.kord.common.entity.optional.mapList +import dev.kord.rest.builder.RequestBuilder +import dev.kord.rest.json.request.GlobalApplicationCommandCreateRequest +import dev.kord.rest.json.request.GlobalApplicationCommandModifyRequest +import dev.kord.rest.json.request.GuildApplicationCommandCreateRequest +import dev.kord.rest.json.request.GuildApplicationCommandModifyRequest + +class GlobalApplicationCommandCreateBuilder( + val name: String, + val description: String +) : RequestBuilder { + + private var _options: Optional> = Optional.Missing() + private var options: MutableList? by ::_options.delegate() + + //TODO("check if desc can be empty") + fun option( + type: ApplicationCommandOptionType, + name: String, + description: String, + builder: ApplicationCommandOptionBuilder.() -> Unit + ) { + if (options == null) options = mutableListOf() + val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) + options!!.add(option) + } + + override fun toRequest(): GlobalApplicationCommandCreateRequest { + return GlobalApplicationCommandCreateRequest(name, description, _options.mapList { it.toRequest() }) + + } + +} + + +class GlobalApplicationCommandModifyBuilder( + val name: String, + val description: String +) : RequestBuilder { + + private var _options: Optional> = Optional.Missing() + private var options: MutableList? by ::_options.delegate() + + //TODO("check if desc can be empty") + fun option( + type: ApplicationCommandOptionType, + name: String, + description: String, + builder: ApplicationCommandOptionBuilder.() -> Unit + ) { + if (options == null) options = mutableListOf() + val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) + options!!.add(option) + } + + override fun toRequest(): GlobalApplicationCommandModifyRequest { + return GlobalApplicationCommandModifyRequest(name, description, _options.mapList { it.toRequest() }) + + } + +} + +class GuildApplicationCommandCreateBuilder( + val name: String, + val description: String +) : RequestBuilder { + + private var _options: Optional> = Optional.Missing() + private var options: MutableList? by ::_options.delegate() + + //TODO("check if desc can be empty") + fun option( + type: ApplicationCommandOptionType, + name: String, + description: String, + builder: ApplicationCommandOptionBuilder.() -> Unit + ) { + if (options == null) options = mutableListOf() + val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) + options!!.add(option) + } + + override fun toRequest(): GuildApplicationCommandCreateRequest { + return GuildApplicationCommandCreateRequest(name, description, _options.mapList { it.toRequest() }) + + } + +} + + +class GuildApplicationCommandModifyBuilder( + val name: String, + val description: String +) : RequestBuilder { + + private var _options: Optional> = Optional.Missing() + private var options: MutableList? by ::_options.delegate() + + //TODO("check if desc can be empty") + fun option( + type: ApplicationCommandOptionType, + name: String, + description: String, + builder: ApplicationCommandOptionBuilder.() -> Unit + ) { + if (options == null) options = mutableListOf() + val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) + options!!.add(option) + } + + override fun toRequest(): GuildApplicationCommandModifyRequest { + return GuildApplicationCommandModifyRequest(name, description, _options.mapList { it.toRequest() }) + + } + +} +class ApplicationCommandOptionBuilder( + val type: ApplicationCommandOptionType, + val name: String, + val description: String +) { + private var _required: OptionalBoolean = OptionalBoolean.Missing + var required: Boolean? by ::_required.delegate() + + private var _default: OptionalBoolean = OptionalBoolean.Missing + var default: Boolean? by ::_default.delegate() + + private var _choices: Optional> = Optional.Missing() + private var choices: MutableList? by ::_choices.delegate() + + fun choice(name: String, value: () -> String) { + if (choices == null) choices = mutableListOf() + choices!!.add(DiscordApplicationCommandOptionChoice(name, value())) + } + + fun toRequest() = ApplicationCommandOption(type, name, description, _default, _required, _choices) +} \ 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 b9668f69d388..909cb472d1e7 100644 --- a/rest/src/main/kotlin/json/request/InteractionsRequests.kt +++ b/rest/src/main/kotlin/json/request/InteractionsRequests.kt @@ -3,6 +3,7 @@ package dev.kord.rest.json.request import dev.kord.common.entity.AllowedMentions import dev.kord.common.entity.ApplicationCommandOption import dev.kord.common.entity.DiscordEmbed +import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.Optional import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable From df3ad16405f2339ba2c1f8baaef3aa254348d754 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Fri, 18 Dec 2020 19:34:57 +0200 Subject: [PATCH 09/40] Fix errors due to rename --- rest/src/main/kotlin/route/Route.kt | 24 +++++++++---------- .../main/kotlin/service/InteractionService.kt | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/rest/src/main/kotlin/route/Route.kt b/rest/src/main/kotlin/route/Route.kt index efa323bb3b27..ada3334d7ebb 100644 --- a/rest/src/main/kotlin/route/Route.kt +++ b/rest/src/main/kotlin/route/Route.kt @@ -429,20 +429,20 @@ sealed class Route( : Route(HttpMethod.Post, "guilds/templates/${TemplateCode}", DiscordGuild.serializer()) object GlobalApplicationCommandsGet - : Route>( - HttpMethod.Get, "/applications/${ApplicationId}/commands", ListSerializer(ApplicationCommand.serializer()) + : Route>( + HttpMethod.Get, "/applications/${ApplicationId}/commands", ListSerializer(DiscordApplicationCommand.serializer()) ) - object GlobalApplicationCommandCreate : Route( + object GlobalApplicationCommandCreate : Route( HttpMethod.Post, "/applications/${ApplicationId}/commands", - ApplicationCommand.serializer() + DiscordApplicationCommand.serializer() ) - object GlobalApplicationCommandModify : Route( + object GlobalApplicationCommandModify : Route( HttpMethod.Patch, "/applications/${ApplicationId}/commands/${CommandId}", - ApplicationCommand.serializer() + DiscordApplicationCommand.serializer() ) object GlobalApplicationCommandDelete @@ -451,23 +451,23 @@ sealed class Route( ) object GuildApplicationCommandsGet - : Route>( + : Route>( HttpMethod.Get, "/applications/${ApplicationId}/guilds/${GuildId}/commands", - ListSerializer(ApplicationCommand.serializer()) + ListSerializer(DiscordApplicationCommand.serializer()) ) - object GuildApplicationCommandCreate : Route( + object GuildApplicationCommandCreate : Route( HttpMethod.Post, "/applications/${ApplicationId}/guilds/${GuildId}/commands", - ApplicationCommand.serializer() + DiscordApplicationCommand.serializer() ) object GuildApplicationCommandModify - : Route( + : Route( HttpMethod.Patch, "/applications/${ApplicationId}/guilds/${GuildId}/commands/${CommandId}", - ApplicationCommand.serializer() + DiscordApplicationCommand.serializer() ) object GuildApplicationCommandDelete diff --git a/rest/src/main/kotlin/service/InteractionService.kt b/rest/src/main/kotlin/service/InteractionService.kt index 803d1e9204db..de415f5e4775 100644 --- a/rest/src/main/kotlin/service/InteractionService.kt +++ b/rest/src/main/kotlin/service/InteractionService.kt @@ -6,7 +6,7 @@ import dev.kord.rest.request.RequestHandler import dev.kord.rest.route.Route class InteractionService(requestHandler: RequestHandler) : RestService(requestHandler) { - suspend fun getGlobalApplicationCommands(applicationId: Snowflake): List = + suspend fun getGlobalApplicationCommands(applicationId: Snowflake): List = call(Route.GlobalApplicationCommandsGet) { keys[Route.ApplicationId] = applicationId } @@ -14,7 +14,7 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa suspend fun createGlobalApplicationCommand( applicationId: Snowflake, request: GlobalApplicationCommandCreateRequest - ): ApplicationCommand = call(Route.GlobalApplicationCommandCreate) { + ): DiscordApplicationCommand = call(Route.GlobalApplicationCommandCreate) { keys[Route.ApplicationId] = applicationId body(GlobalApplicationCommandCreateRequest.serializer(), request) } From 9f5f282f9f2decd0becd289bcf97f5f6ffd17ce8 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Sat, 19 Dec 2020 11:18:37 +0200 Subject: [PATCH 10/40] introduce a KordEntity type Since interactions don't need a kord instance to exist. --- core/src/main/kotlin/ClientResources.kt | 3 +- core/src/main/kotlin/Kord.kt | 2 +- core/src/main/kotlin/Unsafe.kt | 2 +- core/src/main/kotlin/Util.kt | 8 +-- .../src/main/kotlin/behavior/GuildBehavior.kt | 3 +- .../kotlin/behavior/GuildEmojiBehavior.kt | 4 +- .../main/kotlin/behavior/MemberBehavior.kt | 2 +- .../main/kotlin/behavior/MessageBehavior.kt | 2 +- core/src/main/kotlin/behavior/RoleBehavior.kt | 4 +- core/src/main/kotlin/behavior/UserBehavior.kt | 3 +- .../main/kotlin/behavior/WebhookBehavior.kt | 4 +- .../behavior/channel/ChannelBehavior.kt | 4 +- .../main/kotlin/builder/kord/KordBuilder.kt | 52 ++++++++++--------- .../src/main/kotlin/entity/ApplicationInfo.kt | 3 +- core/src/main/kotlin/entity/Attachment.kt | 2 +- core/src/main/kotlin/entity/Entity.kt | 11 ++-- core/src/main/kotlin/entity/GuildEmoji.kt | 3 +- core/src/main/kotlin/entity/GuildPreview.kt | 2 +- core/src/main/kotlin/entity/Integration.kt | 2 +- core/src/main/kotlin/entity/MessageSticker.kt | 2 +- core/src/main/kotlin/entity/PartialGuild.kt | 2 +- core/src/main/kotlin/entity/Strategizable.kt | 4 +- core/src/main/kotlin/entity/Team.kt | 2 +- .../main/kotlin/entity/channel/Category.kt | 1 + core/src/main/kotlin/live/LiveGuild.kt | 4 +- .../live/{LiveEntity.kt => LiveKordEntity.kt} | 8 +-- core/src/main/kotlin/live/LiveMember.kt | 4 +- core/src/main/kotlin/live/LiveMessage.kt | 4 +- core/src/main/kotlin/live/LiveRole.kt | 4 +- core/src/main/kotlin/live/LiveUser.kt | 4 +- .../main/kotlin/live/channel/LiveCategory.kt | 4 +- .../main/kotlin/live/channel/LiveChannel.kt | 4 +- .../main/kotlin/live/channel/LiveDmChannel.kt | 4 +- .../kotlin/live/channel/LiveGuildChannel.kt | 4 +- .../live/channel/LiveGuildMessageChannel.kt | 4 +- .../kotlin/live/channel/LiveVoiceChannel.kt | 4 +- core/src/test/kotlin/entity/GuildTest.kt | 2 +- core/src/test/kotlin/entity/MemberTest.kt | 2 +- core/src/test/kotlin/entity/MessageTest.kt | 2 +- core/src/test/kotlin/entity/RoleTest.kt | 2 +- core/src/test/kotlin/entity/UserTest.kt | 2 +- core/src/test/kotlin/entity/WebhookTest.kt | 2 +- .../kotlin/equality/BehaviorEqualityTest.kt | 7 ++- .../kotlin/equality/ChannelEqualityTest.kt | 10 ++-- .../kotlin/equality/EntityEqualityTest.kt | 6 +-- .../equality/GuildChannelEqualityTest.kt | 6 +-- .../equality/GuildEntityEqualityTest.kt | 6 +-- .../main/kotlin/service/InteractionService.kt | 2 +- 48 files changed, 115 insertions(+), 113 deletions(-) rename core/src/main/kotlin/live/{LiveEntity.kt => LiveKordEntity.kt} (87%) diff --git a/core/src/main/kotlin/ClientResources.kt b/core/src/main/kotlin/ClientResources.kt index ea2906f60a7b..f4a88273c960 100644 --- a/core/src/main/kotlin/ClientResources.kt +++ b/core/src/main/kotlin/ClientResources.kt @@ -9,7 +9,8 @@ class ClientResources( val shardCount: Int, val httpClient: HttpClient, val defaultStrategy: EntitySupplyStrategy<*>, - val intents: Intents + val intents: Intents, + val applicationId: String? = null ) { override fun toString(): String { return "ClientResources(shardCount=$shardCount, httpClient=$httpClient, defaultStrategy=$defaultStrategy, intents=$intents)" diff --git a/core/src/main/kotlin/Kord.kt b/core/src/main/kotlin/Kord.kt index 634766ad2b6a..4e30be5f580d 100644 --- a/core/src/main/kotlin/Kord.kt +++ b/core/src/main/kotlin/Kord.kt @@ -56,7 +56,7 @@ class Kord( * The default supplier, obtained through Kord's [resources] and configured through [KordBuilder.defaultStrategy]. * By default a strategy from [EntitySupplyStrategy.rest]. * - * All [strategizable][Strategizable] [entities][Entity] created through this instance will use this supplier by default. + * All [strategizable][Strategizable] [entities][KordEntity] created through this instance will use this supplier by default. */ val defaultSupplier: EntitySupplier = resources.defaultStrategy.supply(this) diff --git a/core/src/main/kotlin/Unsafe.kt b/core/src/main/kotlin/Unsafe.kt index e0ebf93ce477..ede502585732 100644 --- a/core/src/main/kotlin/Unsafe.kt +++ b/core/src/main/kotlin/Unsafe.kt @@ -15,7 +15,7 @@ import dev.kord.core.behavior.channel.* * class inherently unsafe. * * If the user is not sure of the correctness of the data being passed along, it is advised - * to use [Entities][dev.kord.core.entity.Entity] generated by [Kord] or other Entities instead. + * to use [Entities][dev.kord.core.entity.KordEntity] generated by [Kord] or other Entities instead. */ @KordUnsafe @KordExperimental diff --git a/core/src/main/kotlin/Util.kt b/core/src/main/kotlin/Util.kt index a21bcf5f5e2e..e59be3663fac 100644 --- a/core/src/main/kotlin/Util.kt +++ b/core/src/main/kotlin/Util.kt @@ -1,7 +1,7 @@ package dev.kord.core import dev.kord.common.entity.Snowflake -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.event.Event import dev.kord.core.event.user.PresenceUpdateEvent import dev.kord.core.event.user.VoiceStateUpdateEvent @@ -72,7 +72,7 @@ internal inline fun catchDiscordError(vararg codes: JsonErrorCode, block: () -fun Flow.sorted(): Flow = flow { +fun Flow.sorted(): Flow = flow { for (entity in toList().sorted()) { emit(entity) } @@ -200,7 +200,7 @@ internal fun , T> paginateForwards(start: Snowflake = Snowflak /** * Selects the [Position.After] the youngest item in the batch. */ -internal fun , T : Entity> paginateForwards(start: Snowflake = Snowflake("0"), batchSize: Int, request: suspend (position: Position) -> C): Flow = +internal fun , T : KordEntity> paginateForwards(start: Snowflake = Snowflake("0"), batchSize: Int, request: suspend (position: Position) -> C): Flow = paginate(start, batchSize, { it.id }, youngestItem { it.id }, Position::After, request) /** @@ -212,7 +212,7 @@ internal fun , T> paginateBackwards(start: Snowflake = Snowfla /** * Selects the [Position.Before] the oldest item in the batch. */ -internal fun , T : Entity> paginateBackwards(start: Snowflake = Snowflake(Long.MAX_VALUE), batchSize: Int, request: suspend (position: Position) -> C): Flow = +internal fun , T : KordEntity> paginateBackwards(start: Snowflake = Snowflake(Long.MAX_VALUE), batchSize: Int, request: suspend (position: Position) -> C): Flow = paginate(start, batchSize, { it.id }, oldestItem { it.id }, Position::Before, request) inline fun Intents.IntentsBuilder.enableEvent() = enableEvent(T::class) diff --git a/core/src/main/kotlin/behavior/GuildBehavior.kt b/core/src/main/kotlin/behavior/GuildBehavior.kt index daef896a69bb..c220369d395a 100644 --- a/core/src/main/kotlin/behavior/GuildBehavior.kt +++ b/core/src/main/kotlin/behavior/GuildBehavior.kt @@ -45,12 +45,11 @@ import java.util.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract -import kotlin.random.Random /** * The behavior of a [Discord Guild](https://discord.com/developers/docs/resources/guild). */ -interface GuildBehavior : Entity, Strategizable { +interface GuildBehavior : KordEntity, Strategizable { /** * Requests to get all present bans for this guild. * diff --git a/core/src/main/kotlin/behavior/GuildEmojiBehavior.kt b/core/src/main/kotlin/behavior/GuildEmojiBehavior.kt index b6e36c9b202a..f545fdb10626 100644 --- a/core/src/main/kotlin/behavior/GuildEmojiBehavior.kt +++ b/core/src/main/kotlin/behavior/GuildEmojiBehavior.kt @@ -3,7 +3,7 @@ package dev.kord.core.behavior import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.cache.data.EmojiData -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.GuildEmoji import dev.kord.core.entity.Strategizable import dev.kord.core.supplier.EntitySupplier @@ -15,7 +15,7 @@ import java.util.* /** * The behavior of a [Discord Emoij](https://discord.com/developers/docs/resources/emoji). */ -interface GuildEmojiBehavior : Entity, Strategizable { +interface GuildEmojiBehavior : KordEntity, Strategizable { /** * The id of the guild this emojis is part of. diff --git a/core/src/main/kotlin/behavior/MemberBehavior.kt b/core/src/main/kotlin/behavior/MemberBehavior.kt index 5116bc30acdb..3c33c75798cf 100644 --- a/core/src/main/kotlin/behavior/MemberBehavior.kt +++ b/core/src/main/kotlin/behavior/MemberBehavior.kt @@ -24,7 +24,7 @@ import kotlin.contracts.contract /** * The behavior of a [Discord Member](https://discord.com/developers/docs/resources/guild#guild-member-object). */ -interface MemberBehavior : Entity, UserBehavior { +interface MemberBehavior : KordEntity, UserBehavior { /** * The id of the guild this channel is associated to. diff --git a/core/src/main/kotlin/behavior/MessageBehavior.kt b/core/src/main/kotlin/behavior/MessageBehavior.kt index d637fd87d5eb..30fc639c62f0 100644 --- a/core/src/main/kotlin/behavior/MessageBehavior.kt +++ b/core/src/main/kotlin/behavior/MessageBehavior.kt @@ -28,7 +28,7 @@ import kotlin.contracts.contract /** * The behavior of a [Discord Message](https://discord.com/developers/docs/resources/channel#message-object). */ -interface MessageBehavior : Entity, Strategizable { +interface MessageBehavior : KordEntity, Strategizable { /** * The channel id this message belongs to. */ diff --git a/core/src/main/kotlin/behavior/RoleBehavior.kt b/core/src/main/kotlin/behavior/RoleBehavior.kt index d4aaf33d99ab..b3f5515c5f87 100644 --- a/core/src/main/kotlin/behavior/RoleBehavior.kt +++ b/core/src/main/kotlin/behavior/RoleBehavior.kt @@ -3,7 +3,7 @@ package dev.kord.core.behavior import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.cache.data.RoleData -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Role import dev.kord.core.entity.Strategizable import dev.kord.core.indexOfFirstOrNull @@ -23,7 +23,7 @@ import kotlin.contracts.contract /** * The behavior of a [Discord Role](https://discord.com/developers/docs/topics/permissions#role-object) associated to a [guild]. */ -interface RoleBehavior : Entity, Strategizable { +interface RoleBehavior : KordEntity, Strategizable { /** * The id of the guild this channel is associated to. */ diff --git a/core/src/main/kotlin/behavior/UserBehavior.kt b/core/src/main/kotlin/behavior/UserBehavior.kt index cc633776c386..ea573ebf2960 100644 --- a/core/src/main/kotlin/behavior/UserBehavior.kt +++ b/core/src/main/kotlin/behavior/UserBehavior.kt @@ -12,7 +12,6 @@ import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.rest.json.JsonErrorCode import dev.kord.rest.json.request.DMCreateRequest -import dev.kord.rest.request.HttpStatus import dev.kord.rest.request.RestRequestException import dev.kord.rest.service.RestClient import io.ktor.http.* @@ -21,7 +20,7 @@ import java.util.* /** * The behavior of a [Discord User](https://discord.com/developers/docs/resources/user) */ -interface UserBehavior : Entity, Strategizable { +interface UserBehavior : KordEntity, Strategizable { val mention: String get() = "<@${id.asString}>" diff --git a/core/src/main/kotlin/behavior/WebhookBehavior.kt b/core/src/main/kotlin/behavior/WebhookBehavior.kt index 8191c53305cc..32c1ab64fe7f 100644 --- a/core/src/main/kotlin/behavior/WebhookBehavior.kt +++ b/core/src/main/kotlin/behavior/WebhookBehavior.kt @@ -4,7 +4,7 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.cache.data.MessageData import dev.kord.core.cache.data.WebhookData -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Message import dev.kord.core.entity.Strategizable import dev.kord.core.entity.Webhook @@ -21,7 +21,7 @@ import kotlin.contracts.contract /** * The behavior of a [Discord Webhook](https://discord.com/developers/docs/resources/webhook). */ -interface WebhookBehavior : Entity, Strategizable { +interface WebhookBehavior : KordEntity, Strategizable { /** * Requests to delete this webhook, this user must be the creator. diff --git a/core/src/main/kotlin/behavior/channel/ChannelBehavior.kt b/core/src/main/kotlin/behavior/channel/ChannelBehavior.kt index 83006eb869b6..a5d6cfb3c351 100644 --- a/core/src/main/kotlin/behavior/channel/ChannelBehavior.kt +++ b/core/src/main/kotlin/behavior/channel/ChannelBehavior.kt @@ -3,7 +3,7 @@ package dev.kord.core.behavior.channel import dev.kord.common.entity.Snowflake import dev.kord.common.exception.RequestException import dev.kord.core.Kord -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Strategizable import dev.kord.core.entity.channel.Channel import dev.kord.core.exception.EntityNotFoundException @@ -15,7 +15,7 @@ import java.util.* /** * The behavior of a [Discord Channel](https://discord.com/developers/docs/resources/channel) */ -interface ChannelBehavior : Entity, Strategizable { +interface ChannelBehavior : KordEntity, Strategizable { /** * This channel [formatted as a mention](https://discord.com/developers/docs/reference#message-formatting) diff --git a/core/src/main/kotlin/builder/kord/KordBuilder.kt b/core/src/main/kotlin/builder/kord/KordBuilder.kt index 447db04f5bdb..f70ae54920ae 100644 --- a/core/src/main/kotlin/builder/kord/KordBuilder.kt +++ b/core/src/main/kotlin/builder/kord/KordBuilder.kt @@ -37,12 +37,12 @@ import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json import mu.KotlinLogging import kotlin.concurrent.thread -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract import kotlin.time.seconds -operator fun DefaultGateway.Companion.invoke(resources: ClientResources, retry: Retry = LinearRetry(2.seconds, 60.seconds, 10)): DefaultGateway { +operator fun DefaultGateway.Companion.invoke( + resources: ClientResources, + retry: Retry = LinearRetry(2.seconds, 60.seconds, 10) +): DefaultGateway { return DefaultGateway { url = "wss://gateway.discord.gg/" client = resources.httpClient @@ -56,18 +56,19 @@ private val logger = KotlinLogging.logger { } class KordBuilder(val token: String) { private var shardRange: (recommended: Int) -> Iterable = { 0 until it } - private var gatewayBuilder: (resources: ClientResources, shards: List) -> List = { resources, shards -> - val rateLimiter = BucketRateLimiter(1, 5.seconds) - shards.map { - DefaultGateway { - client = resources.httpClient - identifyRateLimiter = rateLimiter + private var gatewayBuilder: (resources: ClientResources, shards: List) -> List = + { resources, shards -> + val rateLimiter = BucketRateLimiter(1, 5.seconds) + shards.map { + DefaultGateway { + client = resources.httpClient + identifyRateLimiter = rateLimiter + } } } - } private var handlerBuilder: (resources: ClientResources) -> RequestHandler = - { KtorRequestHandler(it.httpClient, ExclusionRequestRateLimiter()) } + { KtorRequestHandler(it.httpClient, ExclusionRequestRateLimiter()) } private var cacheBuilder: KordCacheBuilder.(resources: ClientResources) -> Unit = {} /** @@ -77,12 +78,12 @@ class KordBuilder(val token: String) { /** * The event flow used by [Kord.eventFlow] to publish [events][Kord.events]. - * + * * * By default a [MutableSharedFlow] with an `extraBufferCapacity` of `Int.MAX_VALUE`. */ var eventFlow: MutableSharedFlow = MutableSharedFlow( - extraBufferCapacity = Int.MAX_VALUE + extraBufferCapacity = Int.MAX_VALUE ) /** @@ -106,6 +107,8 @@ class KordBuilder(val token: String) { */ var intents: Intents = Intents.nonPrivileged + var applicationId: String? = null + /** * Configures the shards this client will connect to, by default `0 until recommended`. * This can be used to break up to client into multiple processes. @@ -143,7 +146,6 @@ class KordBuilder(val token: String) { } - /** * Configures the [RequestHandler] for the [RestClient]. * @@ -218,15 +220,15 @@ class KordBuilder(val token: String) { } } - val resources = ClientResources(token, shards.count(), client, defaultStrategy, intents) + val resources = ClientResources(token, shards.count(), client, defaultStrategy, intents, applicationId) val rest = RestClient(handlerBuilder(resources)) val cache = KordCacheBuilder().apply { cacheBuilder(resources) }.build() cache.registerKordData() val gateway = run { val gateways = buildMap { val gateways = gatewayBuilder(resources, shards) - .map { CachingGateway(cache.createView(), it) } - .onEach { it.registerKordData() } + .map { CachingGateway(cache.createView(), it) } + .onEach { it.registerKordData() } shards.forEachIndexed { index, shard -> put(shard, gateways[index]) @@ -246,13 +248,13 @@ class KordBuilder(val token: String) { } return Kord( - resources, - cache, - gateway, - rest, - self, - eventFlow, - defaultDispatcher + resources, + cache, + gateway, + rest, + self, + eventFlow, + defaultDispatcher ) } diff --git a/core/src/main/kotlin/entity/ApplicationInfo.kt b/core/src/main/kotlin/entity/ApplicationInfo.kt index db540266072b..e579e9ac9c0b 100644 --- a/core/src/main/kotlin/entity/ApplicationInfo.kt +++ b/core/src/main/kotlin/entity/ApplicationInfo.kt @@ -8,7 +8,6 @@ import dev.kord.core.cache.data.ApplicationInfoData import dev.kord.core.cache.data.TeamData import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy -import dev.kord.rest.Image import java.util.* /** @@ -18,7 +17,7 @@ class ApplicationInfo( val data: ApplicationInfoData, override val kord: Kord, override val supplier: EntitySupplier = kord.defaultSupplier, -) : Entity, Strategizable { +) : KordEntity, Strategizable { override val id: Snowflake get() = data.id diff --git a/core/src/main/kotlin/entity/Attachment.kt b/core/src/main/kotlin/entity/Attachment.kt index 4bd99ecd9454..27ee8621fb0a 100644 --- a/core/src/main/kotlin/entity/Attachment.kt +++ b/core/src/main/kotlin/entity/Attachment.kt @@ -11,7 +11,7 @@ import java.util.* * * A file attached to a [Message]. */ -data class Attachment(val data: AttachmentData, override val kord: Kord) : Entity { +data class Attachment(val data: AttachmentData, override val kord: Kord) : KordEntity { override val id: Snowflake get() = data.id diff --git a/core/src/main/kotlin/entity/Entity.kt b/core/src/main/kotlin/entity/Entity.kt index f2ee3efe4053..0dddfa25ee72 100644 --- a/core/src/main/kotlin/entity/Entity.kt +++ b/core/src/main/kotlin/entity/Entity.kt @@ -3,10 +3,7 @@ package dev.kord.core.entity import dev.kord.common.entity.Snowflake import dev.kord.core.KordObject -/** - * An object that is identified by its [id]. - */ -interface Entity : KordObject, Comparable { +interface Entity : Comparable { /** * The unique identifier of this entity. */ @@ -21,3 +18,9 @@ interface Entity : KordObject, Comparable { val comparator = compareBy { it.id } } } + +/** + * An object that is identified by its [id]. + * This object holds a [KordObject] + */ +interface KordEntity : KordObject, Entity diff --git a/core/src/main/kotlin/entity/GuildEmoji.kt b/core/src/main/kotlin/entity/GuildEmoji.kt index e0b8385e4912..1e3a77cf6385 100644 --- a/core/src/main/kotlin/entity/GuildEmoji.kt +++ b/core/src/main/kotlin/entity/GuildEmoji.kt @@ -10,7 +10,6 @@ import dev.kord.core.behavior.UserBehavior import dev.kord.core.cache.data.EmojiData import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy -import dev.kord.core.toSnowflakeOrNull import dev.kord.rest.builder.guild.EmojiModifyBuilder import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @@ -27,7 +26,7 @@ class GuildEmoji( val data: EmojiData, override val kord: Kord, override val supplier: EntitySupplier = kord.defaultSupplier -) : Entity, Strategizable { +) : KordEntity, Strategizable { override val id: Snowflake get() = data.id diff --git a/core/src/main/kotlin/entity/GuildPreview.kt b/core/src/main/kotlin/entity/GuildPreview.kt index 025154e85f5a..5cde46a95191 100644 --- a/core/src/main/kotlin/entity/GuildPreview.kt +++ b/core/src/main/kotlin/entity/GuildPreview.kt @@ -8,7 +8,7 @@ import dev.kord.core.cache.data.GuildPreviewData class GuildPreview( val data: GuildPreviewData, override val kord: Kord -) : Entity { +) : KordEntity { override val id: Snowflake get() = data.id diff --git a/core/src/main/kotlin/entity/Integration.kt b/core/src/main/kotlin/entity/Integration.kt index 353fdf7b41b5..88e7698fc9cc 100644 --- a/core/src/main/kotlin/entity/Integration.kt +++ b/core/src/main/kotlin/entity/Integration.kt @@ -29,7 +29,7 @@ class Integration( val data: IntegrationData, override val kord: Kord, override val supplier: EntitySupplier = kord.defaultSupplier -) : Entity, Strategizable { +) : KordEntity, Strategizable { override val id: Snowflake get() = data.id diff --git a/core/src/main/kotlin/entity/MessageSticker.kt b/core/src/main/kotlin/entity/MessageSticker.kt index 4785cb87f413..09e512e28a03 100644 --- a/core/src/main/kotlin/entity/MessageSticker.kt +++ b/core/src/main/kotlin/entity/MessageSticker.kt @@ -8,7 +8,7 @@ import dev.kord.core.cache.data.MessageStickerData /** * A sticker image that can be used in messages. */ -class MessageSticker(val data: MessageStickerData, override val kord: Kord) : Entity { +class MessageSticker(val data: MessageStickerData, override val kord: Kord) : KordEntity { /** * The id of the sticker. diff --git a/core/src/main/kotlin/entity/PartialGuild.kt b/core/src/main/kotlin/entity/PartialGuild.kt index 37bcce7732f4..47f56adb5a79 100644 --- a/core/src/main/kotlin/entity/PartialGuild.kt +++ b/core/src/main/kotlin/entity/PartialGuild.kt @@ -17,7 +17,7 @@ class PartialGuild( val data: PartialGuildData, override val kord: Kord, override val supplier: EntitySupplier = kord.defaultSupplier -) : Entity, Strategizable { +) : KordEntity, Strategizable { /** * The name of this guild. diff --git a/core/src/main/kotlin/entity/Strategizable.kt b/core/src/main/kotlin/entity/Strategizable.kt index 55ac22e201be..f72e4f172ec0 100644 --- a/core/src/main/kotlin/entity/Strategizable.kt +++ b/core/src/main/kotlin/entity/Strategizable.kt @@ -4,10 +4,10 @@ import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy /** - * A class that will defer the requesting of [Entities][Entity] to a [supplier]. + * A class that will defer the requesting of [Entities][KordEntity] to a [supplier]. * Copies of this class with a different [supplier] can be made through [withStrategy]. * - * Unless stated otherwise, all members that fetch [Entities][Entity] will delegate to the [supplier]. + * Unless stated otherwise, all members that fetch [Entities][KordEntity] will delegate to the [supplier]. */ interface Strategizable { diff --git a/core/src/main/kotlin/entity/Team.kt b/core/src/main/kotlin/entity/Team.kt index 368ef8098a51..93474e4db187 100644 --- a/core/src/main/kotlin/entity/Team.kt +++ b/core/src/main/kotlin/entity/Team.kt @@ -17,7 +17,7 @@ class Team( val data: TeamData, override val kord: Kord, override val supplier: EntitySupplier = kord.defaultSupplier, -) : Entity, Strategizable { +) : KordEntity, Strategizable { /** * The unique ID of this team. */ diff --git a/core/src/main/kotlin/entity/channel/Category.kt b/core/src/main/kotlin/entity/channel/Category.kt index cbf267ea844e..67eb87204006 100644 --- a/core/src/main/kotlin/entity/channel/Category.kt +++ b/core/src/main/kotlin/entity/channel/Category.kt @@ -7,6 +7,7 @@ import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.behavior.channel.GuildChannelBehavior import dev.kord.core.cache.data.ChannelData import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import java.util.* diff --git a/core/src/main/kotlin/live/LiveGuild.kt b/core/src/main/kotlin/live/LiveGuild.kt index 36a99371bec9..a29f54326202 100644 --- a/core/src/main/kotlin/live/LiveGuild.kt +++ b/core/src/main/kotlin/live/LiveGuild.kt @@ -2,7 +2,7 @@ package dev.kord.core.live import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.optional.* -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Guild import dev.kord.core.event.* import dev.kord.core.event.channel.ChannelCreateEvent @@ -20,7 +20,7 @@ import dev.kord.core.event.user.VoiceStateUpdateEvent fun Guild.live(): LiveGuild = LiveGuild(this) @KordPreview -class LiveGuild(guild: Guild) : AbstractLiveEntity(), Entity by guild { +class LiveGuild(guild: Guild) : AbstractLiveKordEntity(), KordEntity by guild { var guild: Guild = guild private set diff --git a/core/src/main/kotlin/live/LiveEntity.kt b/core/src/main/kotlin/live/LiveKordEntity.kt similarity index 87% rename from core/src/main/kotlin/live/LiveEntity.kt rename to core/src/main/kotlin/live/LiveKordEntity.kt index e5eb2e390a1c..024e69578b2b 100644 --- a/core/src/main/kotlin/live/LiveEntity.kt +++ b/core/src/main/kotlin/live/LiveKordEntity.kt @@ -2,7 +2,7 @@ package dev.kord.core.live import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.event.Event import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent @@ -22,14 +22,14 @@ import kotlinx.coroutines.sync.withLock * [reactions][ReactionAddEvent] to that message. */ @KordPreview -interface LiveEntity : Entity { +interface LiveKordEntity : KordEntity { val events: Flow fun shutDown() } @KordPreview -abstract class AbstractLiveEntity : LiveEntity { +abstract class AbstractLiveKordEntity : LiveKordEntity { private val mutex = Mutex() private val running = atomic(true) @@ -51,7 +51,7 @@ abstract class AbstractLiveEntity : LiveEntity { * or [Kord] by default and will not propagate any exceptions. */ @KordPreview -inline fun LiveEntity.on(scope: CoroutineScope = kord, noinline consumer: suspend (T) -> Unit) = +inline fun LiveKordEntity.on(scope: CoroutineScope = kord, noinline consumer: suspend (T) -> Unit) = events.buffer(Channel.UNLIMITED).filterIsInstance().onEach { runCatching { consumer(it) }.onFailure { kordLogger.catching(it) } }.catch { kordLogger.catching(it) }.launchIn(scope) diff --git a/core/src/main/kotlin/live/LiveMember.kt b/core/src/main/kotlin/live/LiveMember.kt index 3589a1b98070..7eae972e473f 100644 --- a/core/src/main/kotlin/live/LiveMember.kt +++ b/core/src/main/kotlin/live/LiveMember.kt @@ -1,7 +1,7 @@ package dev.kord.core.live import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Member import dev.kord.core.event.Event import dev.kord.core.event.guild.BanAddEvent @@ -13,7 +13,7 @@ import dev.kord.core.event.guild.MemberUpdateEvent fun Member.live() = LiveMember(this) @KordPreview -class LiveMember(member: Member) : AbstractLiveEntity(), Entity by member { +class LiveMember(member: Member) : AbstractLiveKordEntity(), KordEntity by member { var member = member private set diff --git a/core/src/main/kotlin/live/LiveMessage.kt b/core/src/main/kotlin/live/LiveMessage.kt index cc0053d983ad..4572520880d5 100644 --- a/core/src/main/kotlin/live/LiveMessage.kt +++ b/core/src/main/kotlin/live/LiveMessage.kt @@ -5,7 +5,7 @@ import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.orEmpty import dev.kord.core.cache.data.ReactionData -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Message import dev.kord.core.entity.ReactionEmoji import dev.kord.core.event.Event @@ -18,7 +18,7 @@ import dev.kord.core.supplier.EntitySupplyStrategy suspend fun Message.live() = LiveMessage(this, withStrategy(EntitySupplyStrategy.cacheWithRestFallback).getGuildOrNull()?.id) @KordPreview -class LiveMessage(message: Message, val guildId: Snowflake?) : AbstractLiveEntity(), Entity by message { +class LiveMessage(message: Message, val guildId: Snowflake?) : AbstractLiveKordEntity(), KordEntity by message { var message: Message = message private set diff --git a/core/src/main/kotlin/live/LiveRole.kt b/core/src/main/kotlin/live/LiveRole.kt index 456cc39d193d..150cbc8f07a7 100644 --- a/core/src/main/kotlin/live/LiveRole.kt +++ b/core/src/main/kotlin/live/LiveRole.kt @@ -1,7 +1,7 @@ package dev.kord.core.live import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Role import dev.kord.core.event.Event import dev.kord.core.event.guild.GuildDeleteEvent @@ -12,7 +12,7 @@ import dev.kord.core.event.role.RoleUpdateEvent fun Role.live() = LiveRole(this) @KordPreview -class LiveRole(role: Role) : AbstractLiveEntity(), Entity by role { +class LiveRole(role: Role) : AbstractLiveKordEntity(), KordEntity by role { var role = role private set diff --git a/core/src/main/kotlin/live/LiveUser.kt b/core/src/main/kotlin/live/LiveUser.kt index 6c9c0f0f9f18..aad37704177c 100644 --- a/core/src/main/kotlin/live/LiveUser.kt +++ b/core/src/main/kotlin/live/LiveUser.kt @@ -1,7 +1,7 @@ package dev.kord.core.live import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.User import dev.kord.core.event.Event import dev.kord.core.event.user.UserUpdateEvent @@ -11,7 +11,7 @@ import dev.kord.core.event.user.UserUpdateEvent fun User.live() = LiveUser(this) @KordPreview -class LiveUser(user: User) : AbstractLiveEntity(), Entity by user { +class LiveUser(user: User) : AbstractLiveKordEntity(), KordEntity by user { var user: User = user private set diff --git a/core/src/main/kotlin/live/channel/LiveCategory.kt b/core/src/main/kotlin/live/channel/LiveCategory.kt index 146ebc7e625c..28cd9cb6250d 100644 --- a/core/src/main/kotlin/live/channel/LiveCategory.kt +++ b/core/src/main/kotlin/live/channel/LiveCategory.kt @@ -1,7 +1,7 @@ package dev.kord.core.live.channel import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.channel.Category import dev.kord.core.event.Event import dev.kord.core.event.channel.CategoryCreateEvent @@ -13,7 +13,7 @@ import dev.kord.core.event.guild.GuildDeleteEvent fun Category.live() = LiveCategory(this) @KordPreview -class LiveCategory(channel: Category) : LiveChannel(), Entity by channel { +class LiveCategory(channel: Category) : LiveChannel(), KordEntity by channel { override var channel: Category = channel private set diff --git a/core/src/main/kotlin/live/channel/LiveChannel.kt b/core/src/main/kotlin/live/channel/LiveChannel.kt index a82a98867f54..11bd3a572cde 100644 --- a/core/src/main/kotlin/live/channel/LiveChannel.kt +++ b/core/src/main/kotlin/live/channel/LiveChannel.kt @@ -11,7 +11,7 @@ import dev.kord.core.event.guild.GuildCreateEvent import dev.kord.core.event.guild.GuildDeleteEvent import dev.kord.core.event.guild.GuildUpdateEvent import dev.kord.core.event.message.* -import dev.kord.core.live.AbstractLiveEntity +import dev.kord.core.live.AbstractLiveKordEntity @KordPreview fun Channel.live() = when (this) { @@ -24,7 +24,7 @@ fun Channel.live() = when (this) { } @KordPreview -abstract class LiveChannel : AbstractLiveEntity() { +abstract class LiveChannel : AbstractLiveKordEntity() { abstract val channel: Channel diff --git a/core/src/main/kotlin/live/channel/LiveDmChannel.kt b/core/src/main/kotlin/live/channel/LiveDmChannel.kt index f1bc2399d293..8d36b200a974 100644 --- a/core/src/main/kotlin/live/channel/LiveDmChannel.kt +++ b/core/src/main/kotlin/live/channel/LiveDmChannel.kt @@ -1,7 +1,7 @@ package dev.kord.core.live.channel import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.channel.DmChannel import dev.kord.core.event.Event import dev.kord.core.event.channel.DMChannelCreateEvent @@ -13,7 +13,7 @@ import dev.kord.core.event.guild.GuildDeleteEvent fun DmChannel.live() = LiveDmChannel(this) @KordPreview -class LiveDmChannel(channel: DmChannel) : LiveChannel(), Entity by channel { +class LiveDmChannel(channel: DmChannel) : LiveChannel(), KordEntity by channel { override var channel: DmChannel = channel private set diff --git a/core/src/main/kotlin/live/channel/LiveGuildChannel.kt b/core/src/main/kotlin/live/channel/LiveGuildChannel.kt index 3bc6d600c2cf..8b81a8267206 100644 --- a/core/src/main/kotlin/live/channel/LiveGuildChannel.kt +++ b/core/src/main/kotlin/live/channel/LiveGuildChannel.kt @@ -1,7 +1,7 @@ package dev.kord.core.live.channel import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.Event @@ -14,7 +14,7 @@ import dev.kord.core.event.guild.GuildDeleteEvent fun GuildChannel.live() = LiveGuildChannel(this) @KordPreview -class LiveGuildChannel(channel: GuildChannel) : LiveChannel(), Entity by channel { +class LiveGuildChannel(channel: GuildChannel) : LiveChannel(), KordEntity by channel { override var channel: GuildChannel = channel private set diff --git a/core/src/main/kotlin/live/channel/LiveGuildMessageChannel.kt b/core/src/main/kotlin/live/channel/LiveGuildMessageChannel.kt index 06cac29469fc..434a100f42fa 100644 --- a/core/src/main/kotlin/live/channel/LiveGuildMessageChannel.kt +++ b/core/src/main/kotlin/live/channel/LiveGuildMessageChannel.kt @@ -1,7 +1,7 @@ package dev.kord.core.live.channel import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.Event import dev.kord.core.event.channel.* @@ -11,7 +11,7 @@ import dev.kord.core.event.guild.GuildDeleteEvent fun GuildMessageChannel.live() = LiveGuildMessageChannel(this) @KordPreview -class LiveGuildMessageChannel(channel: GuildMessageChannel) : LiveChannel(), Entity by channel { +class LiveGuildMessageChannel(channel: GuildMessageChannel) : LiveChannel(), KordEntity by channel { override var channel: GuildMessageChannel = channel private set diff --git a/core/src/main/kotlin/live/channel/LiveVoiceChannel.kt b/core/src/main/kotlin/live/channel/LiveVoiceChannel.kt index e827de1a2a14..31edaebff6f1 100644 --- a/core/src/main/kotlin/live/channel/LiveVoiceChannel.kt +++ b/core/src/main/kotlin/live/channel/LiveVoiceChannel.kt @@ -1,7 +1,7 @@ package dev.kord.core.live.channel import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import dev.kord.core.entity.channel.VoiceChannel import dev.kord.core.event.Event import dev.kord.core.event.channel.VoiceChannelCreateEvent @@ -13,7 +13,7 @@ import dev.kord.core.event.guild.GuildDeleteEvent fun VoiceChannel.live() = LiveVoiceChannel(this) @KordPreview -class LiveVoiceChannel(channel: VoiceChannel) : LiveChannel(), Entity by channel { +class LiveVoiceChannel(channel: VoiceChannel) : LiveChannel(), KordEntity by channel { override var channel: VoiceChannel = channel private set diff --git a/core/src/test/kotlin/entity/GuildTest.kt b/core/src/test/kotlin/entity/GuildTest.kt index 73e24ca7e03a..03fa31e04b31 100644 --- a/core/src/test/kotlin/entity/GuildTest.kt +++ b/core/src/test/kotlin/entity/GuildTest.kt @@ -14,5 +14,5 @@ internal class GuildTest: EntityEqualityTest by EntityEqualityTest({ every { data.id } returns it Guild(data, kord) }), BehaviorEqualityTest { - override fun Guild.behavior(): Entity = GuildBehavior(id, kord) + override fun Guild.behavior(): KordEntity = GuildBehavior(id, kord) } \ No newline at end of file diff --git a/core/src/test/kotlin/entity/MemberTest.kt b/core/src/test/kotlin/entity/MemberTest.kt index f41e5deac8fd..475653d87d3f 100644 --- a/core/src/test/kotlin/entity/MemberTest.kt +++ b/core/src/test/kotlin/entity/MemberTest.kt @@ -23,7 +23,7 @@ internal class MemberTest : GuildEntityEqualityTest by GuildEntityEquali Member(memberData, userData, kord) }), BehaviorEqualityTest { - override fun Member.behavior(): Entity = MemberBehavior(guildId = guildId, id = id, kord = kord) + override fun Member.behavior(): KordEntity = MemberBehavior(guildId = guildId, id = id, kord = kord) @Test fun `members equal users with the same ID`() { diff --git a/core/src/test/kotlin/entity/MessageTest.kt b/core/src/test/kotlin/entity/MessageTest.kt index 8d53ae93950e..5ea9f3af7094 100644 --- a/core/src/test/kotlin/entity/MessageTest.kt +++ b/core/src/test/kotlin/entity/MessageTest.kt @@ -15,5 +15,5 @@ internal class MessageTest : EntityEqualityTest by EntityEqualityTest({ every { data.channelId } returns it Message(data, kord) }), BehaviorEqualityTest { - override fun Message.behavior(): Entity = MessageBehavior(messageId = id, channelId = id, kord = kord) + override fun Message.behavior(): KordEntity = MessageBehavior(messageId = id, channelId = id, kord = kord) } \ No newline at end of file diff --git a/core/src/test/kotlin/entity/RoleTest.kt b/core/src/test/kotlin/entity/RoleTest.kt index 20cb622bc173..f586fa2b2756 100644 --- a/core/src/test/kotlin/entity/RoleTest.kt +++ b/core/src/test/kotlin/entity/RoleTest.kt @@ -15,5 +15,5 @@ internal class RoleTest : GuildEntityEqualityTest by GuildEntityEqualityTe every { data.guildId } returns guildId Role(data, kord) }), BehaviorEqualityTest { - override fun Role.behavior(): Entity = RoleBehavior(guildId = guildId, id = id, kord = kord) + override fun Role.behavior(): KordEntity = RoleBehavior(guildId = guildId, id = id, kord = kord) } \ No newline at end of file diff --git a/core/src/test/kotlin/entity/UserTest.kt b/core/src/test/kotlin/entity/UserTest.kt index 3f163cb66d1a..4b53357739e5 100644 --- a/core/src/test/kotlin/entity/UserTest.kt +++ b/core/src/test/kotlin/entity/UserTest.kt @@ -14,5 +14,5 @@ internal class UserTest : EntityEqualityTest by EntityEqualityTest({ every { data.id } returns it User(data, kord) }), BehaviorEqualityTest { - override fun User.behavior(): Entity = UserBehavior(id = id, kord = kord) + override fun User.behavior(): KordEntity = UserBehavior(id = id, kord = kord) } \ No newline at end of file diff --git a/core/src/test/kotlin/entity/WebhookTest.kt b/core/src/test/kotlin/entity/WebhookTest.kt index 6adef0f43965..076bc5d051e8 100644 --- a/core/src/test/kotlin/entity/WebhookTest.kt +++ b/core/src/test/kotlin/entity/WebhookTest.kt @@ -14,5 +14,5 @@ internal class WebhookTest : EntityEqualityTest by EntityEqualityTest({ every { data.id } returns it Webhook(data, kord) }), BehaviorEqualityTest { - override fun Webhook.behavior(): Entity = WebhookBehavior(id = id, kord = kord) + override fun Webhook.behavior(): KordEntity = WebhookBehavior(id = id, kord = kord) } \ No newline at end of file diff --git a/core/src/test/kotlin/equality/BehaviorEqualityTest.kt b/core/src/test/kotlin/equality/BehaviorEqualityTest.kt index 9909c6d5eaba..ea568cdbc415 100644 --- a/core/src/test/kotlin/equality/BehaviorEqualityTest.kt +++ b/core/src/test/kotlin/equality/BehaviorEqualityTest.kt @@ -1,13 +1,12 @@ package equality -import dev.kord.common.entity.Snowflake -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import kotlin.test.Test import kotlin.test.assertEquals -interface BehaviorEqualityTest : EntityEqualityTest { +interface BehaviorEqualityTest : EntityEqualityTest { - fun T.behavior() : Entity + fun T.behavior() : KordEntity @Test fun `Full entity equals its behavior`(){ diff --git a/core/src/test/kotlin/equality/ChannelEqualityTest.kt b/core/src/test/kotlin/equality/ChannelEqualityTest.kt index 13853bd5c146..abbc4aca40cd 100644 --- a/core/src/test/kotlin/equality/ChannelEqualityTest.kt +++ b/core/src/test/kotlin/equality/ChannelEqualityTest.kt @@ -2,24 +2,24 @@ package equality import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import mockKord import kotlin.test.assertEquals -interface ChannelEqualityTest : EntityEqualityTest { +interface ChannelEqualityTest : EntityEqualityTest { @kotlin.test.Test fun `Channel is equal to Channel with the same id`() { val id = randomId() val kord = mockKord() - val fakeChannel: Entity = ChannelBehavior(id, kord) - val channel: Entity = newEntity(id) + val fakeChannel: KordEntity = ChannelBehavior(id, kord) + val channel: KordEntity = newEntity(id) assertEquals(fakeChannel, channel) } companion object { - operator fun invoke(supplier: (Snowflake) -> T) = object: ChannelEqualityTest { + operator fun invoke(supplier: (Snowflake) -> T) = object: ChannelEqualityTest { override fun newEntity(id: Snowflake): T = supplier(id) } } diff --git a/core/src/test/kotlin/equality/EntityEqualityTest.kt b/core/src/test/kotlin/equality/EntityEqualityTest.kt index d27d45a8c471..4f6d78b4eaf8 100644 --- a/core/src/test/kotlin/equality/EntityEqualityTest.kt +++ b/core/src/test/kotlin/equality/EntityEqualityTest.kt @@ -1,7 +1,7 @@ package equality import dev.kord.common.entity.Snowflake -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals @@ -10,7 +10,7 @@ import kotlin.test.assertNotEquals val ids = generateSequence { Random.nextLong() }.distinct().iterator() fun randomId() = Snowflake(ids.next()) -interface EntityEqualityTest { +interface EntityEqualityTest { fun newEntity(id: Snowflake): T @@ -32,7 +32,7 @@ interface EntityEqualityTest { } companion object { - operator fun invoke(supplier: (Snowflake) -> T) = object : EntityEqualityTest { + operator fun invoke(supplier: (Snowflake) -> T) = object : EntityEqualityTest { override fun newEntity(id: Snowflake): T = supplier(id) } } diff --git a/core/src/test/kotlin/equality/GuildChannelEqualityTest.kt b/core/src/test/kotlin/equality/GuildChannelEqualityTest.kt index 77029e83476e..39951040f031 100644 --- a/core/src/test/kotlin/equality/GuildChannelEqualityTest.kt +++ b/core/src/test/kotlin/equality/GuildChannelEqualityTest.kt @@ -1,14 +1,14 @@ package equality import dev.kord.common.entity.Snowflake -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity -interface GuildChannelEqualityTest : +interface GuildChannelEqualityTest : ChannelEqualityTest, GuildEntityEqualityTest { companion object { - operator fun invoke(supplier: (id: Snowflake, guildId: Snowflake) -> T) = object: GuildChannelEqualityTest { + operator fun invoke(supplier: (id: Snowflake, guildId: Snowflake) -> T) = object: GuildChannelEqualityTest { override fun newEntity(id: Snowflake, guildId: Snowflake): T = supplier(id, guildId) } } diff --git a/core/src/test/kotlin/equality/GuildEntityEqualityTest.kt b/core/src/test/kotlin/equality/GuildEntityEqualityTest.kt index 7c0ad386e722..3dad9ccd1469 100644 --- a/core/src/test/kotlin/equality/GuildEntityEqualityTest.kt +++ b/core/src/test/kotlin/equality/GuildEntityEqualityTest.kt @@ -1,11 +1,11 @@ package equality import dev.kord.common.entity.Snowflake -import dev.kord.core.entity.Entity +import dev.kord.core.entity.KordEntity import kotlin.test.Test import kotlin.test.assertNotEquals -interface GuildEntityEqualityTest : EntityEqualityTest { +interface GuildEntityEqualityTest : EntityEqualityTest { fun newEntity(id: Snowflake, guildId: Snowflake): T @@ -30,7 +30,7 @@ interface GuildEntityEqualityTest : EntityEqualityTest { } companion object { - operator fun invoke(supplier: (id: Snowflake, guildId: Snowflake) -> T) = object: GuildEntityEqualityTest { + operator fun invoke(supplier: (id: Snowflake, guildId: Snowflake) -> T) = object: GuildEntityEqualityTest { override fun newEntity(id: Snowflake, guildId: Snowflake): T = supplier(id, guildId) } } diff --git a/rest/src/main/kotlin/service/InteractionService.kt b/rest/src/main/kotlin/service/InteractionService.kt index de415f5e4775..933def945260 100644 --- a/rest/src/main/kotlin/service/InteractionService.kt +++ b/rest/src/main/kotlin/service/InteractionService.kt @@ -41,7 +41,7 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa keys[Route.GuildId] = guildId } - suspend fun createGuildApplicationCommands( + suspend fun createGuildApplicationCommand( applicationId: Snowflake, guildId: Snowflake, request: GuildApplicationCommandCreateRequest From 290b16dada4fb26dbb0997d63cb4384dd03c9611 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Sat, 19 Dec 2020 11:23:50 +0200 Subject: [PATCH 11/40] Fix incorrect type representation --- common/src/main/kotlin/entity/Interactions.kt | 2 +- .../main/kotlin/builder/interaction/InteractionsBuilder.kt | 4 ++-- 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 d9832a14b803..2b8fe6bc1278 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -19,7 +19,7 @@ data class DiscordApplicationCommand( val applicationId: Snowflake, val name: String, val description: String, - val options: Optional = Optional.Missing() + val options: Optional> = Optional.Missing() ) @Serializable diff --git a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt index cfa291819047..cbf56617939d 100644 --- a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt @@ -135,8 +135,8 @@ class ApplicationCommandOptionBuilder( private var _choices: Optional> = Optional.Missing() private var choices: MutableList? by ::_choices.delegate() - - fun choice(name: String, value: () -> String) { + //TODO("Express types in a convenient way.") + fun choice(name: String, value: () -> String) { if (choices == null) choices = mutableListOf() choices!!.add(DiscordApplicationCommandOptionChoice(name, value())) } From 31ad6f9e157d83063681ee3db55395496461879f Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Sat, 19 Dec 2020 11:24:30 +0200 Subject: [PATCH 12/40] Add slash commands core representation --- core/src/main/kotlin/SlashCommands.kt | 58 +++++++++++++++ .../GlobalApplicationCommandBehavior.kt | 50 +++++++++++++ .../main/kotlin/cache/data/InteractionData.kt | 70 +++++++++++++++++++ .../kotlin/entity/GlobalApplicationCommand.kt | 58 +++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 core/src/main/kotlin/SlashCommands.kt create mode 100644 core/src/main/kotlin/behavior/GlobalApplicationCommandBehavior.kt create mode 100644 core/src/main/kotlin/cache/data/InteractionData.kt create mode 100644 core/src/main/kotlin/entity/GlobalApplicationCommand.kt diff --git a/core/src/main/kotlin/SlashCommands.kt b/core/src/main/kotlin/SlashCommands.kt new file mode 100644 index 000000000000..b4783c1e47c4 --- /dev/null +++ b/core/src/main/kotlin/SlashCommands.kt @@ -0,0 +1,58 @@ +package dev.kord.core + +import dev.kord.common.entity.Snowflake +import dev.kord.core.cache.data.ApplicationCommandData +import dev.kord.core.entity.GlobalApplicationCommand +import dev.kord.core.entity.GuildApplicationCommand +import dev.kord.rest.builder.interaction.GlobalApplicationCommandCreateBuilder +import dev.kord.rest.builder.interaction.GuildApplicationCommandCreateBuilder +import dev.kord.rest.service.InteractionService +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +class SlashCommands( + private val applicationId: Snowflake, + private val service: InteractionService +) { + suspend fun createGlobalApplicationCommand( + name: String, + description: String, + builder: GlobalApplicationCommandCreateBuilder.() -> Unit + ): GlobalApplicationCommand { + val request = GlobalApplicationCommandCreateBuilder(name, description).apply(builder).toRequest() + val response = service.createGlobalApplicationCommand(applicationId, request) + val data = ApplicationCommandData.from(response) + return GlobalApplicationCommand(data, service) + } + + suspend fun createGuildApplicationCommand( + guildId: Snowflake, + name: String, + description: String, + builder: GuildApplicationCommandCreateBuilder.() -> Unit + ): GuildApplicationCommand { + val request = GuildApplicationCommandCreateBuilder(name, description).apply(builder).toRequest() + val response = service.createGuildApplicationCommand(applicationId, guildId, request) + val data = ApplicationCommandData.from(response) + return GuildApplicationCommand(data, guildId, service) + } + + suspend fun getGuildApplicationCommands(guildId: Snowflake): Flow = flow { + for (command in service.getGuildApplicationCommands(applicationId, guildId)) { + val data = ApplicationCommandData.from(command) + emit(GuildApplicationCommand(data, guildId, service)) + } + } + + + suspend fun getGlobalApplicationCommands(guildId: Snowflake): Flow = flow { + for (command in service.getGlobalApplicationCommands(applicationId)) { + val data = ApplicationCommandData.from(command) + emit(GlobalApplicationCommand(data, service)) + } + } +} + +fun Kord.toSlashCommands(applicationId: Snowflake): SlashCommands { + return SlashCommands(applicationId, rest.interaction) +} \ No newline at end of file diff --git a/core/src/main/kotlin/behavior/GlobalApplicationCommandBehavior.kt b/core/src/main/kotlin/behavior/GlobalApplicationCommandBehavior.kt new file mode 100644 index 000000000000..b3ae8cf9977d --- /dev/null +++ b/core/src/main/kotlin/behavior/GlobalApplicationCommandBehavior.kt @@ -0,0 +1,50 @@ +package dev.kord.core.behavior + +import dev.kord.common.entity.Snowflake +import dev.kord.core.cache.data.ApplicationCommandData +import dev.kord.core.entity.Entity +import dev.kord.core.entity.GlobalApplicationCommand +import dev.kord.core.entity.GuildApplicationCommand +import dev.kord.rest.builder.interaction.GlobalApplicationCommandModifyBuilder +import dev.kord.rest.builder.interaction.GuildApplicationCommandModifyBuilder +import dev.kord.rest.service.InteractionService + +interface GlobalApplicationCommandBehavior : Entity { + val applicationId: Snowflake + val service: InteractionService + suspend fun edit( + name: String, + description: String, + builder: GlobalApplicationCommandModifyBuilder.() -> Unit + ): GlobalApplicationCommand { + val request = GlobalApplicationCommandModifyBuilder(name, description).apply(builder).toRequest() + val response = service.modifyGlobalApplicationCommand(applicationId, id, request) + val data = ApplicationCommandData.from(response) + return GlobalApplicationCommand(data, service) + } + + suspend fun delete() { + service.deleteGlobalApplicationCommand(applicationId, id) + } +} + + +interface GuildApplicationCommandBehavior : Entity { + val applicationId: Snowflake + val guildId: Snowflake + val service: InteractionService + suspend fun edit( + name: String, + description: String, + builder: GuildApplicationCommandModifyBuilder.() -> Unit + ): GuildApplicationCommand { + val request = GuildApplicationCommandModifyBuilder(name, description).apply(builder).toRequest() + val response = service.modifyGuildApplicationCommand(applicationId, guildId, id, request) + val data = ApplicationCommandData.from(response) + return GuildApplicationCommand(data, guildId, service) + } + + suspend fun delete() { + service.deleteGuildApplicationCommand(applicationId,guildId, id) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/cache/data/InteractionData.kt b/core/src/main/kotlin/cache/data/InteractionData.kt new file mode 100644 index 000000000000..b994d43d35b2 --- /dev/null +++ b/core/src/main/kotlin/cache/data/InteractionData.kt @@ -0,0 +1,70 @@ +package dev.kord.core.cache.data + +import dev.kord.common.entity.* +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean +import dev.kord.common.entity.optional.mapList +import kotlinx.serialization.Serializable + +@Serializable +data class ApplicationCommandData( + val id: Snowflake, + val applicationId: Snowflake, + val name: String, + val description: String, + val options: Optional> +) { + companion object { + fun from(command: DiscordApplicationCommand): ApplicationCommandData { + return with(command) { + ApplicationCommandData( + id, + applicationId, + name, + description, + options.mapList { ApplicationCommandOptionData.from(it) }) + } + } + } +} + +@Serializable +data class ApplicationCommandOptionData( + val type: ApplicationCommandOptionType, + val name: String, + val description: String, + val default: OptionalBoolean = OptionalBoolean.Missing, + val required: OptionalBoolean = OptionalBoolean.Missing, + val choices: Optional> = Optional.Missing(), + val options: Optional> = Optional.Missing() +) { + companion object { + fun from(data: ApplicationCommandOption): ApplicationCommandOptionData { + return with(data) { + ApplicationCommandOptionData( + type, + name, + description, + default, + required, + choices.mapList { ApplicationCommandOptionChoiceData.from(it) }, + options.mapList { inner -> from(inner) } + ) + } + } + } +} + +@Serializable +data class ApplicationCommandOptionChoiceData( + val name: String, + val value: String +) { + companion object { + fun from(choice: DiscordApplicationCommandOptionChoice): ApplicationCommandOptionChoiceData { + return with(choice) { + ApplicationCommandOptionChoiceData(name, value) + } + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/entity/GlobalApplicationCommand.kt b/core/src/main/kotlin/entity/GlobalApplicationCommand.kt new file mode 100644 index 000000000000..f1d271112d31 --- /dev/null +++ b/core/src/main/kotlin/entity/GlobalApplicationCommand.kt @@ -0,0 +1,58 @@ +package dev.kord.core.entity + +import dev.kord.common.entity.ApplicationCommandOptionType +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.mapList +import dev.kord.common.entity.optional.value +import dev.kord.core.behavior.GlobalApplicationCommandBehavior +import dev.kord.core.behavior.GuildApplicationCommandBehavior +import dev.kord.core.cache.data.ApplicationCommandData +import dev.kord.core.cache.data.ApplicationCommandOptionChoiceData +import dev.kord.core.cache.data.ApplicationCommandOptionData +import dev.kord.rest.service.InteractionService + +class GlobalApplicationCommand(val data: ApplicationCommandData, override val service: InteractionService) : + GlobalApplicationCommandBehavior { + override val id: Snowflake + get() = data.id + + override val applicationId: Snowflake + get() = data.applicationId + + val name get() = data.name + + val description: String get() = data.description + + val options: List? = data.options.mapList { CommandOptions(it) }.value + + +} + +class GuildApplicationCommand(val data: ApplicationCommandData,override val guildId: Snowflake, override val service: InteractionService) : + GuildApplicationCommandBehavior { + override val id: Snowflake + get() = data.id + + override val applicationId: Snowflake + get() = data.applicationId + + val name get() = data.name + + val description: String get() = data.description + + val options: List? = data.options.mapList { CommandOptions(it) }.value + + +} + +data class CommandOptions(val data: ApplicationCommandOptionData) { + val type: ApplicationCommandOptionType get() = data.type + val name: String get() = data.name + val description: String get() = data.description + val default: Boolean? = data.default.value + val required: Boolean? = data.required.value + val choices: Map? = data.choices.value?.associate { it.name to it.value } + val options: List? = data.options.mapList { CommandOptions(it) }.value +} \ No newline at end of file From 7a1c1d73869ab13283c154710554dfbcaa09638a Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Sat, 19 Dec 2020 16:44:51 +0200 Subject: [PATCH 13/40] Add missing interaction requests and fix route returns --- common/src/main/kotlin/entity/Interactions.kt | 18 +-- .../interaction/InteractionsBuilder.kt | 111 ++++++++++++++++-- .../json/request/InteractionsRequests.kt | 44 ++++++- .../kotlin/json/request/WebhookRequests.kt | 2 +- rest/src/main/kotlin/route/Route.kt | 9 +- .../main/kotlin/service/InteractionService.kt | 21 ++-- 6 files changed, 169 insertions(+), 36 deletions(-) diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index 2b8fe6bc1278..53fcba24fb21 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -86,7 +86,7 @@ data class DiscordApplicationCommandOptionChoice( data class DiscordInteraction( val id: Snowflake, val type: InteractionType, - val data: Optional = Optional.Missing(), + val data: Optional = Optional.Missing(), @SerialName("guild_id") val guildId: Snowflake, @SerialName("channel_id") @@ -123,23 +123,23 @@ sealed class InteractionType(val type: Int) { } } @Serializable -data class ApplicationCommandInteractionData( +data class DiscordApplicationCommandInteractionData( val id: Snowflake, val name: String, - val options: Optional> = Optional.Missing() + val options: Optional> = Optional.Missing() ) @Serializable -data class ApplicationCommandInteractionDataOption( +data class DiscordApplicationCommandInteractionDataOption( val name: String, - val value: Optional = Optional.Missing(), - val options: Optional> = Optional.Missing() + val value: Optional = Optional.Missing(), + val options: Optional> = Optional.Missing() ) @Serializable -data class InteractionResponse( +data class DiscordInteractionResponse( val type: InteractionResponseType, - val data: Optional = Optional.Missing() + val data: Optional = Optional.Missing() ) @Serializable @@ -177,7 +177,7 @@ sealed class InteractionResponseType(val type: Int) { } @Serializable -class InteractionApplicationCommandCallbackData( +class DiscordInteractionApplicationCommandCallbackData( val tts: OptionalBoolean = OptionalBoolean.Missing, val content: String, val embeds: Optional> = Optional.Missing(), diff --git a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt index cbf56617939d..a622a11dbec7 100644 --- a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt @@ -1,17 +1,18 @@ package dev.kord.rest.builder.interaction import dev.kord.common.entity.ApplicationCommandOption -import dev.kord.common.entity.DiscordApplicationCommandOptionChoice import dev.kord.common.entity.ApplicationCommandOptionType +import dev.kord.common.entity.DiscordApplicationCommandOptionChoice +import dev.kord.common.entity.InteractionResponseType import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.delegate.delegate +import dev.kord.common.entity.optional.map import dev.kord.common.entity.optional.mapList import dev.kord.rest.builder.RequestBuilder -import dev.kord.rest.json.request.GlobalApplicationCommandCreateRequest -import dev.kord.rest.json.request.GlobalApplicationCommandModifyRequest -import dev.kord.rest.json.request.GuildApplicationCommandCreateRequest -import dev.kord.rest.json.request.GuildApplicationCommandModifyRequest +import dev.kord.rest.builder.message.AllowedMentionsBuilder +import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.json.request.* class GlobalApplicationCommandCreateBuilder( val name: String, @@ -122,6 +123,7 @@ class GuildApplicationCommandModifyBuilder( } } + class ApplicationCommandOptionBuilder( val type: ApplicationCommandOptionType, val name: String, @@ -135,11 +137,104 @@ class ApplicationCommandOptionBuilder( private var _choices: Optional> = Optional.Missing() private var choices: MutableList? by ::_choices.delegate() - //TODO("Express types in a convenient way.") - fun choice(name: String, value: () -> String) { + + //TODO("Express types in a convenient way.") + fun choice(name: String, value: () -> String) { if (choices == null) choices = mutableListOf() choices!!.add(DiscordApplicationCommandOptionChoice(name, value())) } fun toRequest() = ApplicationCommandOption(type, name, description, _default, _required, _choices) -} \ No newline at end of file +} + +class OriginalInteractionResponseModifyBuilder() : + RequestBuilder { + private var _content: Optional = Optional.Missing() + var content: String? by ::_content.delegate() + + private var _embeds: Optional> = Optional.Missing() + var embeds: MutableList? by ::_embeds.delegate() + + private var _allowedMentions: Optional = Optional.Missing() + var allowedMentions: AllowedMentionsBuilder? by ::_allowedMentions.delegate() + + fun embed(builder: EmbedBuilder.() -> Unit) { + if (embeds == null) embeds = mutableListOf() + embeds!! += EmbedBuilder().apply(builder) + } + + + override fun toRequest(): OriginalInteractionResponseModifyRequest { + return OriginalInteractionResponseModifyRequest( + _content, + _embeds.mapList { it.toRequest() }, + _allowedMentions.map { it.build() }) + } +} + +class FollowupMessageModifyBuilder() : + RequestBuilder { + private var _content: Optional = Optional.Missing() + var content: String? by ::_content.delegate() + + private var _embeds: Optional> = Optional.Missing() + var embeds: MutableList? by ::_embeds.delegate() + + private var _allowedMentions: Optional = Optional.Missing() + var allowedMentions: AllowedMentionsBuilder? by ::_allowedMentions.delegate() + + fun embed(builder: EmbedBuilder.() -> Unit) { + if (embeds == null) embeds = mutableListOf() + embeds!! += EmbedBuilder().apply(builder) + } + + + override fun toRequest(): FollowupMessageModifyRequest { + return FollowupMessageModifyRequest( + _content, + _embeds.mapList { it.toRequest() }, + _allowedMentions.map { it.build() }) + } +} + +class InteractionApplicationCommandCallbackDataBuilder(val content: String) { + + private var _tts: OptionalBoolean = OptionalBoolean.Missing + var tts: Boolean? by ::_tts.delegate() + + private var _embeds: Optional> = Optional.Missing() + var embeds: MutableList? by ::_embeds.delegate() + + private var _allowedMentions: Optional = Optional.Missing() + var allowedMentions: AllowedMentionsBuilder? by ::_allowedMentions.delegate() + + fun embed(builder: EmbedBuilder.() -> Unit) { + if (embeds == null) embeds = mutableListOf() + embeds!! += EmbedBuilder().apply(builder) + } + + fun build(): DiscordInteractionApplicationCommandCallbackData { + + return DiscordInteractionApplicationCommandCallbackData( + _tts, + content, + _embeds.mapList { it.toRequest() }, + _allowedMentions.map { it.build() }) + + } +} + +class InteractionResponseCreateBuilder(var type: InteractionResponseType) : RequestBuilder { + private var _data: Optional = Optional.Missing() + private var data: InteractionApplicationCommandCallbackDataBuilder? by ::_data.delegate() + + fun data(content: String, builder: InteractionApplicationCommandCallbackDataBuilder.() -> Unit) { + val data = InteractionApplicationCommandCallbackDataBuilder(content).apply(builder) + } + + override fun toRequest(): DiscordInteractionResponse { + return DiscordInteractionResponse(type, _data.map { it.build() }) + } + +} + diff --git a/rest/src/main/kotlin/json/request/InteractionsRequests.kt b/rest/src/main/kotlin/json/request/InteractionsRequests.kt index 909cb472d1e7..977b46442022 100644 --- a/rest/src/main/kotlin/json/request/InteractionsRequests.kt +++ b/rest/src/main/kotlin/json/request/InteractionsRequests.kt @@ -2,9 +2,9 @@ package dev.kord.rest.json.request import dev.kord.common.entity.AllowedMentions import dev.kord.common.entity.ApplicationCommandOption -import dev.kord.common.entity.DiscordEmbed -import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.InteractionResponseType import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -38,8 +38,42 @@ data class GuildApplicationCommandModifyRequest( @Serializable data class OriginalInteractionResponseModifyRequest( - val content: String, - val embeds: List, + val content: Optional, + val embeds: Optional>, @SerialName("allowed_mentions") - val allowedMentions: AllowedMentions, + val allowedMentions: Optional, +) + + +@Serializable +data class DiscordInteractionResponse( + val type: InteractionResponseType, + val data: Optional = Optional.Missing() ) + +@Serializable +class DiscordInteractionApplicationCommandCallbackData( + val tts: OptionalBoolean = OptionalBoolean.Missing, + val content: String, + val embeds: Optional> = Optional.Missing(), + val allowedMentions: Optional = Optional.Missing() + +) +@Serializable +class FollowupMessageCreateRequest( + val content: Optional = Optional.Missing(), + val username: Optional = Optional.Missing(), + @SerialName("avatar_url") + val avatar: Optional = Optional.Missing(), + val tts: OptionalBoolean = OptionalBoolean.Missing, + val embeds: Optional> = Optional.Missing(), + val allowedMentions: Optional = Optional.Missing() +) + +@Serializable +data class FollowupMessageModifyRequest( + val content: Optional, + val embeds: Optional>, + @SerialName("allowed_mentions") + val allowedMentions: Optional, +) \ No newline at end of file diff --git a/rest/src/main/kotlin/json/request/WebhookRequests.kt b/rest/src/main/kotlin/json/request/WebhookRequests.kt index f09faf2624f0..ec85c438904b 100644 --- a/rest/src/main/kotlin/json/request/WebhookRequests.kt +++ b/rest/src/main/kotlin/json/request/WebhookRequests.kt @@ -5,7 +5,7 @@ import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.OptionalSnowflake import kotlinx.serialization.SerialName -import kotlinx. serialization.Serializable +import kotlinx.serialization.Serializable @Serializable data class WebhookCreateRequest(val name: String, val avatar: Optional = Optional.Missing()) diff --git a/rest/src/main/kotlin/route/Route.kt b/rest/src/main/kotlin/route/Route.kt index ada3334d7ebb..4c39de653c5a 100644 --- a/rest/src/main/kotlin/route/Route.kt +++ b/rest/src/main/kotlin/route/Route.kt @@ -5,6 +5,7 @@ import dev.kord.common.annotation.KordExperimental import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.* import dev.kord.rest.json.optional +import dev.kord.rest.json.request.DiscordInteractionResponse import dev.kord.rest.json.response.* import io.ktor.http.* import kotlinx.serialization.DeserializationStrategy @@ -477,17 +478,17 @@ sealed class Route( NoStrategy ) - object InteractionResponseCreate : Route( + object InteractionResponseCreate : Route( HttpMethod.Post, "/interactions/${InteractionId}/${InteractionToken}/callback", - InteractionResponse.serializer() + DiscordInteractionResponse.serializer() ) object OriginalInteractionResponseModify : - Route( + Route( HttpMethod.Patch, "/webhooks/${ApplicationId}/${InteractionToken}/messages/@original", - InteractionResponse.serializer() + DiscordInteractionResponse.serializer() ) object OriginalInteractionResponseDelete diff --git a/rest/src/main/kotlin/service/InteractionService.kt b/rest/src/main/kotlin/service/InteractionService.kt index 933def945260..4df6bceff702 100644 --- a/rest/src/main/kotlin/service/InteractionService.kt +++ b/rest/src/main/kotlin/service/InteractionService.kt @@ -1,6 +1,7 @@ package dev.kord.rest.service -import dev.kord.common.entity.* +import dev.kord.common.entity.DiscordApplicationCommand +import dev.kord.common.entity.Snowflake import dev.kord.rest.json.request.* import dev.kord.rest.request.RequestHandler import dev.kord.rest.route.Route @@ -74,22 +75,22 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa suspend fun createInteractionResponse( interactionId: Snowflake, interactionToken: String, - request: InteractionResponse + request: DiscordInteractionResponse ) = call(Route.InteractionResponseCreate) { keys[Route.InteractionId] = interactionId keys[Route.InteractionToken] = interactionToken - body(InteractionResponse.serializer(), request) + body(DiscordInteractionResponse.serializer(), request) } suspend fun modifyInteractionResponse( interactionId: Snowflake, interactionToken: String, - request: InteractionResponse + request: OriginalInteractionResponseModifyRequest ) = call(Route.OriginalInteractionResponseModify) { keys[Route.InteractionId] = interactionId keys[Route.InteractionToken] = interactionToken - body(InteractionResponse.serializer(), request) + body(OriginalInteractionResponseModifyRequest.serializer(), request) } @@ -102,11 +103,13 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa suspend fun createFollowupMessage( applicationId: Snowflake, interactionToken: String, - request: MessageCreateRequest + request: FollowupMessageCreateRequest, + wait: Boolean = false ) = call(Route.FollowupMessageCreate) { keys[Route.ApplicationId] = applicationId keys[Route.InteractionToken] = interactionToken - body(MessageCreateRequest.serializer(), request) + parameter("wait", "$wait") + body(FollowupMessageCreateRequest.serializer(), request) } suspend fun deleteFollowupMessage(applicationId: Snowflake, interactionToken: String, messageId: Snowflake) = @@ -120,11 +123,11 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa applicationId: Snowflake, interactionToken: String, messageId: Snowflake, - request: MessageEditPatchRequest + request: FollowupMessageModifyRequest ) = call(Route.EditMessagePatch) { keys[Route.ApplicationId] = applicationId keys[Route.InteractionToken] = interactionToken keys[Route.MessageId] = messageId - body(MessageEditPatchRequest.serializer(), request) + body(FollowupMessageModifyRequest.serializer(), request) } } \ No newline at end of file From 5b5011ed9e00fa0ffa4074a35873673e46db667c Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Sat, 19 Dec 2020 16:46:16 +0200 Subject: [PATCH 14/40] remove redundant applicationId --- core/src/main/kotlin/ClientResources.kt | 1 - .../main/kotlin/builder/kord/KordBuilder.kt | 3 +-- .../ApplicationCommand.kt} | 22 +++++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) rename core/src/main/kotlin/entity/{GlobalApplicationCommand.kt => interaction/ApplicationCommand.kt} (62%) diff --git a/core/src/main/kotlin/ClientResources.kt b/core/src/main/kotlin/ClientResources.kt index f4a88273c960..27ebabcaf854 100644 --- a/core/src/main/kotlin/ClientResources.kt +++ b/core/src/main/kotlin/ClientResources.kt @@ -10,7 +10,6 @@ class ClientResources( val httpClient: HttpClient, val defaultStrategy: EntitySupplyStrategy<*>, val intents: Intents, - val applicationId: String? = null ) { override fun toString(): String { return "ClientResources(shardCount=$shardCount, httpClient=$httpClient, defaultStrategy=$defaultStrategy, intents=$intents)" diff --git a/core/src/main/kotlin/builder/kord/KordBuilder.kt b/core/src/main/kotlin/builder/kord/KordBuilder.kt index f70ae54920ae..131998c78cca 100644 --- a/core/src/main/kotlin/builder/kord/KordBuilder.kt +++ b/core/src/main/kotlin/builder/kord/KordBuilder.kt @@ -107,7 +107,6 @@ class KordBuilder(val token: String) { */ var intents: Intents = Intents.nonPrivileged - var applicationId: String? = null /** * Configures the shards this client will connect to, by default `0 until recommended`. @@ -220,7 +219,7 @@ class KordBuilder(val token: String) { } } - val resources = ClientResources(token, shards.count(), client, defaultStrategy, intents, applicationId) + val resources = ClientResources(token, shards.count(), client, defaultStrategy, intents) val rest = RestClient(handlerBuilder(resources)) val cache = KordCacheBuilder().apply { cacheBuilder(resources) }.build() cache.registerKordData() diff --git a/core/src/main/kotlin/entity/GlobalApplicationCommand.kt b/core/src/main/kotlin/entity/interaction/ApplicationCommand.kt similarity index 62% rename from core/src/main/kotlin/entity/GlobalApplicationCommand.kt rename to core/src/main/kotlin/entity/interaction/ApplicationCommand.kt index f1d271112d31..da3adb69d3bc 100644 --- a/core/src/main/kotlin/entity/GlobalApplicationCommand.kt +++ b/core/src/main/kotlin/entity/interaction/ApplicationCommand.kt @@ -2,14 +2,11 @@ package dev.kord.core.entity import dev.kord.common.entity.ApplicationCommandOptionType 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.mapList import dev.kord.common.entity.optional.value import dev.kord.core.behavior.GlobalApplicationCommandBehavior import dev.kord.core.behavior.GuildApplicationCommandBehavior import dev.kord.core.cache.data.ApplicationCommandData -import dev.kord.core.cache.data.ApplicationCommandOptionChoiceData import dev.kord.core.cache.data.ApplicationCommandOptionData import dev.kord.rest.service.InteractionService @@ -25,13 +22,16 @@ class GlobalApplicationCommand(val data: ApplicationCommandData, override val se val description: String get() = data.description - val options: List? = data.options.mapList { CommandOptions(it) }.value + val options: List? get() = data.options.mapList { CommandOptions(it) }.value } -class GuildApplicationCommand(val data: ApplicationCommandData,override val guildId: Snowflake, override val service: InteractionService) : - GuildApplicationCommandBehavior { +class GuildApplicationCommand( + val data: ApplicationCommandData, + override val guildId: Snowflake, + override val service: InteractionService +) : GuildApplicationCommandBehavior { override val id: Snowflake get() = data.id @@ -42,7 +42,7 @@ class GuildApplicationCommand(val data: ApplicationCommandData,override val guil val description: String get() = data.description - val options: List? = data.options.mapList { CommandOptions(it) }.value + val options: List? get() = data.options.mapList { CommandOptions(it) }.value } @@ -51,8 +51,8 @@ data class CommandOptions(val data: ApplicationCommandOptionData) { val type: ApplicationCommandOptionType get() = data.type val name: String get() = data.name val description: String get() = data.description - val default: Boolean? = data.default.value - val required: Boolean? = data.required.value - val choices: Map? = data.choices.value?.associate { it.name to it.value } - val options: List? = data.options.mapList { CommandOptions(it) }.value + val default: Boolean? get() = data.default.value + val required: Boolean? get() = data.required.value + val choices: Map? get() = data.choices.value?.associate { it.name to it.value } + val options: List? get() = data.options.mapList { CommandOptions(it) }.value } \ No newline at end of file From d26057c14b0144811c756f04e3bda11cadda9556 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Sat, 19 Dec 2020 16:48:37 +0200 Subject: [PATCH 15/40] Move application command data to matching file name --- .../cache/data/{InteractionData.kt => ApplicationCommandData.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/src/main/kotlin/cache/data/{InteractionData.kt => ApplicationCommandData.kt} (100%) diff --git a/core/src/main/kotlin/cache/data/InteractionData.kt b/core/src/main/kotlin/cache/data/ApplicationCommandData.kt similarity index 100% rename from core/src/main/kotlin/cache/data/InteractionData.kt rename to core/src/main/kotlin/cache/data/ApplicationCommandData.kt From 9125b0a85ddcd7ed999ffe79e9f4896e24f16142 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Sun, 20 Dec 2020 17:51:27 +0200 Subject: [PATCH 16/40] Fix undocumented api behaviors --- core/src/main/kotlin/ClientResources.kt | 12 +- .../kotlin/regression/CacheMissRegression.kt | 156 ------------------ .../interaction/InteractionsBuilder.kt | 27 +-- .../json/request/InteractionsRequests.kt | 8 +- rest/src/main/kotlin/route/Route.kt | 9 +- 5 files changed, 33 insertions(+), 179 deletions(-) delete mode 100644 core/src/test/kotlin/regression/CacheMissRegression.kt diff --git a/core/src/main/kotlin/ClientResources.kt b/core/src/main/kotlin/ClientResources.kt index 27ebabcaf854..1f0c06887b46 100644 --- a/core/src/main/kotlin/ClientResources.kt +++ b/core/src/main/kotlin/ClientResources.kt @@ -1,15 +1,17 @@ package dev.kord.core +import dev.kord.common.entity.Snowflake import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.Intents import io.ktor.client.HttpClient class ClientResources( - val token: String, - val shardCount: Int, - val httpClient: HttpClient, - val defaultStrategy: EntitySupplyStrategy<*>, - val intents: Intents, + val token: String, + val shardCount: Int, + val httpClient: HttpClient, + val defaultStrategy: EntitySupplyStrategy<*>, + val intents: Intents, + val applicationId: Snowflake, ) { override fun toString(): String { return "ClientResources(shardCount=$shardCount, httpClient=$httpClient, defaultStrategy=$defaultStrategy, intents=$intents)" diff --git a/core/src/test/kotlin/regression/CacheMissRegression.kt b/core/src/test/kotlin/regression/CacheMissRegression.kt deleted file mode 100644 index 4e1e09f86f62..000000000000 --- a/core/src/test/kotlin/regression/CacheMissRegression.kt +++ /dev/null @@ -1,156 +0,0 @@ -package regression - -import com.gitlab.kordlib.cache.api.put -import com.gitlab.kordlib.cache.map.MapDataCache -import dev.kord.common.entity.ChannelType -import dev.kord.common.entity.Snowflake -import dev.kord.core.ClientResources -import dev.kord.core.Kord -import dev.kord.core.builder.kord.configure -import dev.kord.core.builder.kord.getBotIdFromToken -import dev.kord.core.cache.data.ChannelData -import dev.kord.core.cache.registerKordData -import dev.kord.core.gateway.MasterGateway -import dev.kord.core.supplier.EntitySupplyStrategy -import dev.kord.gateway.* -import dev.kord.rest.request.JsonRequest -import dev.kord.rest.request.MultipartRequest -import dev.kord.rest.request.Request -import dev.kord.rest.request.RequestHandler -import dev.kord.rest.route.Route -import dev.kord.rest.service.RestClient -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.client.request.forms.* -import io.ktor.client.statement.* -import io.ktor.content.* -import io.ktor.http.* -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.channels.BroadcastChannel -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runBlockingTest -import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.json.Json -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.test.BeforeTest -import kotlin.time.Duration -import kotlin.time.ExperimentalTime - - -private val parser = Json { - encodeDefaults = false - allowStructuredMapKeys = true - ignoreUnknownKeys = true - isLenient = true -} - -object FakeGateway : Gateway { - - val deferred = CompletableDeferred() - - override val events: SharedFlow = MutableSharedFlow() - - @ExperimentalTime - override val ping: StateFlow = MutableStateFlow(null) - - override suspend fun detach() {} - - override suspend fun send(command: Command) {} - override suspend fun start(configuration: GatewayConfiguration) { - deferred.await() - } - - override suspend fun stop() { - deferred.complete(Unit) - } - - override val coroutineContext: CoroutineContext = EmptyCoroutineContext + SupervisorJob() -} - -class CrashingHandler(val client: HttpClient) : RequestHandler { - override suspend fun handle(request: Request): R { - if (request.route != Route.CurrentUserGet) throw IllegalStateException("shouldn't do a request") - val response = client.request { - method = request.route.method - headers.appendAll(request.headers) - - url { - url.takeFrom(Route.baseUrl) - encodedPath += request.path - parameters.appendAll(request.parameters) - } - - - request.body?.let { - @Suppress("UNCHECKED_CAST") - when (request) { - is MultipartRequest<*, *> -> { - headers.append("payload_json", parser.encodeToString(it.strategy as SerializationStrategy, it.body)) - this.body = MultiPartFormDataContent(request.data) - } - - is JsonRequest<*, *> -> { - val json = parser.encodeToString(it.strategy as SerializationStrategy, it.body) - this.body = TextContent(json, ContentType.Application.Json) - } - } - } - - - }.execute() - - return parser.decodeFromString(request.route.strategy, response.readText()) - - - } -} - -@EnabledIfEnvironmentVariable(named = "TARGET_BRANCH", matches = "master") -class CacheMissingRegressions { - lateinit var kord: Kord - - @BeforeTest - fun setup() = runBlockingTest { //TODO, move this over to entity supplier tests instead, eventually. - val token = System.getenv("KORD_TEST_TOKEN") - val resources = ClientResources(token, 1, null.configure(token), EntitySupplyStrategy.cacheWithRestFallback, Intents.nonPrivileged) - kord = Kord( - resources, - MapDataCache().also { it.registerKordData() }, - MasterGateway(mapOf(0 to FakeGateway)), - RestClient(CrashingHandler(resources.httpClient)), - getBotIdFromToken(token), - MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE), - Dispatchers.Default - ) - } - - - @Test - fun `if data not in cache explode`() { - val id = 5L - assertThrows { - runBlocking { - kord.getChannel(Snowflake(id)) - } - } - } - - @Test - fun `if data in cache don't fetch from rest`() { - runBlocking { - val id = Snowflake(5L) - kord.cache.put(ChannelData(id, ChannelType.GuildText)) - - kord.getChannel(id) - } - } - -} \ No newline at end of file diff --git a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt index a622a11dbec7..4effd385a351 100644 --- a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt @@ -42,10 +42,13 @@ class GlobalApplicationCommandCreateBuilder( } -class GlobalApplicationCommandModifyBuilder( - val name: String, - val description: String -) : RequestBuilder { +class GlobalApplicationCommandModifyBuilder : RequestBuilder { + private var _name: Optional = Optional.Missing() + var name: String? by ::_name.delegate() + + private var _description: Optional = Optional.Missing() + var description: String? by ::_name.delegate() + private var _options: Optional> = Optional.Missing() private var options: MutableList? by ::_options.delegate() @@ -63,7 +66,7 @@ class GlobalApplicationCommandModifyBuilder( } override fun toRequest(): GlobalApplicationCommandModifyRequest { - return GlobalApplicationCommandModifyRequest(name, description, _options.mapList { it.toRequest() }) + return GlobalApplicationCommandModifyRequest(_name, _description, _options.mapList { it.toRequest() }) } @@ -97,10 +100,14 @@ class GuildApplicationCommandCreateBuilder( } -class GuildApplicationCommandModifyBuilder( - val name: String, - val description: String -) : RequestBuilder { +class GuildApplicationCommandModifyBuilder : RequestBuilder { + + private var _name: Optional = Optional.Missing() + var name: String? by ::_name.delegate() + + private var _description: Optional = Optional.Missing() + var description: String? by ::_name.delegate() + private var _options: Optional> = Optional.Missing() private var options: MutableList? by ::_options.delegate() @@ -118,7 +125,7 @@ class GuildApplicationCommandModifyBuilder( } override fun toRequest(): GuildApplicationCommandModifyRequest { - return GuildApplicationCommandModifyRequest(name, description, _options.mapList { it.toRequest() }) + return GuildApplicationCommandModifyRequest(_name, _description, _options.mapList { it.toRequest() }) } diff --git a/rest/src/main/kotlin/json/request/InteractionsRequests.kt b/rest/src/main/kotlin/json/request/InteractionsRequests.kt index 977b46442022..4ff78c424195 100644 --- a/rest/src/main/kotlin/json/request/InteractionsRequests.kt +++ b/rest/src/main/kotlin/json/request/InteractionsRequests.kt @@ -17,8 +17,8 @@ data class GlobalApplicationCommandCreateRequest( @Serializable data class GlobalApplicationCommandModifyRequest( - val name: String, - val description: String, + val name: Optional, + val description: Optional, val options: Optional> ) @@ -31,8 +31,8 @@ data class GuildApplicationCommandCreateRequest( @Serializable data class GuildApplicationCommandModifyRequest( - val name: String, - val description: String, + val name: Optional, + val description: Optional, val options: Optional> ) diff --git a/rest/src/main/kotlin/route/Route.kt b/rest/src/main/kotlin/route/Route.kt index 4c39de653c5a..f7ed409d0662 100644 --- a/rest/src/main/kotlin/route/Route.kt +++ b/rest/src/main/kotlin/route/Route.kt @@ -6,6 +6,7 @@ import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.* import dev.kord.rest.json.optional import dev.kord.rest.json.request.DiscordInteractionResponse +import dev.kord.rest.json.request.MessageEditPatchRequest import dev.kord.rest.json.response.* import io.ktor.http.* import kotlinx.serialization.DeserializationStrategy @@ -478,17 +479,17 @@ sealed class Route( NoStrategy ) - object InteractionResponseCreate : Route( + object InteractionResponseCreate : Route( HttpMethod.Post, "/interactions/${InteractionId}/${InteractionToken}/callback", - DiscordInteractionResponse.serializer() + NoStrategy ) object OriginalInteractionResponseModify : - Route( + Route( HttpMethod.Patch, "/webhooks/${ApplicationId}/${InteractionToken}/messages/@original", - DiscordInteractionResponse.serializer() + NoStrategy ) object OriginalInteractionResponseDelete From a401feaf1ccd8479940feaad73025b738e6568fa Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Tue, 22 Dec 2020 08:25:44 +0200 Subject: [PATCH 17/40] Add missing applicationId and remove redundant properties --- core/src/main/kotlin/ClientResources.kt | 2 +- .../behavior/GlobalApplicationCommandBehavior.kt | 16 ++++------------ core/src/main/kotlin/builder/kord/KordBuilder.kt | 5 ++++- .../kotlin/builder/kord/KordRestOnlyBuilder.kt | 5 ++++- .../kotlin/supplier/CacheEntitySupplierTest.kt | 3 +-- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/core/src/main/kotlin/ClientResources.kt b/core/src/main/kotlin/ClientResources.kt index 1f0c06887b46..dae4b42e8d2a 100644 --- a/core/src/main/kotlin/ClientResources.kt +++ b/core/src/main/kotlin/ClientResources.kt @@ -11,7 +11,7 @@ class ClientResources( val httpClient: HttpClient, val defaultStrategy: EntitySupplyStrategy<*>, val intents: Intents, - val applicationId: Snowflake, + val applicationId: Snowflake? = null, ) { override fun toString(): String { return "ClientResources(shardCount=$shardCount, httpClient=$httpClient, defaultStrategy=$defaultStrategy, intents=$intents)" diff --git a/core/src/main/kotlin/behavior/GlobalApplicationCommandBehavior.kt b/core/src/main/kotlin/behavior/GlobalApplicationCommandBehavior.kt index b3ae8cf9977d..a036b426c91d 100644 --- a/core/src/main/kotlin/behavior/GlobalApplicationCommandBehavior.kt +++ b/core/src/main/kotlin/behavior/GlobalApplicationCommandBehavior.kt @@ -12,12 +12,8 @@ import dev.kord.rest.service.InteractionService interface GlobalApplicationCommandBehavior : Entity { val applicationId: Snowflake val service: InteractionService - suspend fun edit( - name: String, - description: String, - builder: GlobalApplicationCommandModifyBuilder.() -> Unit - ): GlobalApplicationCommand { - val request = GlobalApplicationCommandModifyBuilder(name, description).apply(builder).toRequest() + suspend fun edit(builder: GlobalApplicationCommandModifyBuilder.() -> Unit): GlobalApplicationCommand { + val request = GlobalApplicationCommandModifyBuilder().apply(builder).toRequest() val response = service.modifyGlobalApplicationCommand(applicationId, id, request) val data = ApplicationCommandData.from(response) return GlobalApplicationCommand(data, service) @@ -33,12 +29,8 @@ interface GuildApplicationCommandBehavior : Entity { val applicationId: Snowflake val guildId: Snowflake val service: InteractionService - suspend fun edit( - name: String, - description: String, - builder: GuildApplicationCommandModifyBuilder.() -> Unit - ): GuildApplicationCommand { - val request = GuildApplicationCommandModifyBuilder(name, description).apply(builder).toRequest() + suspend fun edit(builder: GuildApplicationCommandModifyBuilder.() -> Unit): GuildApplicationCommand { + val request = GuildApplicationCommandModifyBuilder().apply(builder).toRequest() val response = service.modifyGuildApplicationCommand(applicationId, guildId, id, request) val data = ApplicationCommandData.from(response) return GuildApplicationCommand(data, guildId, service) diff --git a/core/src/main/kotlin/builder/kord/KordBuilder.kt b/core/src/main/kotlin/builder/kord/KordBuilder.kt index 131998c78cca..93eb6a46887c 100644 --- a/core/src/main/kotlin/builder/kord/KordBuilder.kt +++ b/core/src/main/kotlin/builder/kord/KordBuilder.kt @@ -3,6 +3,7 @@ package dev.kord.core.builder.kord import com.gitlab.kordlib.cache.api.DataCache +import dev.kord.common.entity.Snowflake import dev.kord.common.ratelimit.BucketRateLimiter import dev.kord.core.ClientResources import dev.kord.core.Kord @@ -107,6 +108,8 @@ class KordBuilder(val token: String) { */ var intents: Intents = Intents.nonPrivileged + var applicationId: Snowflake? = null + /** * Configures the shards this client will connect to, by default `0 until recommended`. @@ -219,7 +222,7 @@ class KordBuilder(val token: String) { } } - val resources = ClientResources(token, shards.count(), client, defaultStrategy, intents) + val resources = ClientResources(token, shards.count(), client, defaultStrategy, intents, applicationId) val rest = RestClient(handlerBuilder(resources)) val cache = KordCacheBuilder().apply { cacheBuilder(resources) }.build() cache.registerKordData() diff --git a/core/src/main/kotlin/builder/kord/KordRestOnlyBuilder.kt b/core/src/main/kotlin/builder/kord/KordRestOnlyBuilder.kt index f6cbbab6baae..c475f78b313e 100644 --- a/core/src/main/kotlin/builder/kord/KordRestOnlyBuilder.kt +++ b/core/src/main/kotlin/builder/kord/KordRestOnlyBuilder.kt @@ -2,6 +2,7 @@ package dev.kord.core.builder.kord import com.gitlab.kordlib.cache.api.DataCache import dev.kord.common.annotation.KordExperimental +import dev.kord.common.entity.Snowflake import dev.kord.core.ClientResources import dev.kord.core.Kord import dev.kord.core.event.Event @@ -38,6 +39,8 @@ class KordRestOnlyBuilder(val token: String) { */ var httpClient: HttpClient? = null + var applicationId: Snowflake? = null + /** * Configures the [RequestHandler] for the [RestClient]. * @@ -58,7 +61,7 @@ class KordRestOnlyBuilder(val token: String) { fun build(): Kord { val client = httpClient.configure(token) - val resources = ClientResources(token, 0, client, EntitySupplyStrategy.rest, Intents.none) + val resources = ClientResources(token, 0, client, EntitySupplyStrategy.rest, Intents.none, applicationId) val rest = RestClient(handlerBuilder(resources)) val selfId = getBotIdFromToken(token) diff --git a/core/src/test/kotlin/supplier/CacheEntitySupplierTest.kt b/core/src/test/kotlin/supplier/CacheEntitySupplierTest.kt index fe5891da8d63..1ad9e37fcb01 100644 --- a/core/src/test/kotlin/supplier/CacheEntitySupplierTest.kt +++ b/core/src/test/kotlin/supplier/CacheEntitySupplierTest.kt @@ -12,7 +12,6 @@ import dev.kord.rest.request.KtorRequestHandler import dev.kord.rest.service.RestClient import io.ktor.client.* import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking @@ -24,7 +23,7 @@ internal class CacheEntitySupplierTest { @OptIn(PrivilegedIntent::class) fun `cache does not throw when accessing unregistered entities`(): Unit = runBlocking { val kord = Kord( - ClientResources("", 0, HttpClient(), EntitySupplyStrategy.cache, Intents.all), + ClientResources("", 0, HttpClient(), EntitySupplyStrategy.cache, Intents.all, null), KordCacheBuilder().build(), MasterGateway(mapOf(0 to Gateway.none())), RestClient(KtorRequestHandler("")), From 45209097159d1d2d420180299ce98151325ccac0 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Tue, 22 Dec 2020 15:57:35 +0200 Subject: [PATCH 18/40] Fix rest endpoints and builders --- common/src/main/kotlin/entity/Interactions.kt | 6 +- .../interaction/InteractionsBuilder.kt | 56 +++++++++++++++---- .../json/request/InteractionsRequests.kt | 12 +++- rest/src/main/kotlin/route/Route.kt | 1 - .../main/kotlin/service/InteractionService.kt | 14 +++-- 5 files changed, 65 insertions(+), 24 deletions(-) diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index 53fcba24fb21..a21f876f3e60 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -86,7 +86,7 @@ data class DiscordApplicationCommandOptionChoice( data class DiscordInteraction( val id: Snowflake, val type: InteractionType, - val data: Optional = Optional.Missing(), + val data: DiscordApplicationCommandInteractionData, @SerialName("guild_id") val guildId: Snowflake, @SerialName("channel_id") @@ -142,7 +142,7 @@ data class DiscordInteractionResponse( val data: Optional = Optional.Missing() ) -@Serializable +@Serializable(InteractionResponseType.Serializer::class) sealed class InteractionResponseType(val type: Int) { object Pong : InteractionResponseType(1) object Acknowledge : InteractionResponseType(2) @@ -151,7 +151,7 @@ sealed class InteractionResponseType(val type: Int) { object ACKWithSource : InteractionResponseType(5) object Unknown : InteractionResponseType(Int.MAX_VALUE) - companion object + companion object; object Serializer : KSerializer { diff --git a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt index 4effd385a351..6e7e5910386a 100644 --- a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt @@ -13,6 +13,13 @@ import dev.kord.rest.builder.RequestBuilder import dev.kord.rest.builder.message.AllowedMentionsBuilder import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.json.request.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.nio.file.Files +import java.nio.file.Path +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract class GlobalApplicationCommandCreateBuilder( val name: String, @@ -179,7 +186,7 @@ class OriginalInteractionResponseModifyBuilder() : } } -class FollowupMessageModifyBuilder() : +class FollowupMessageModifyBuilder : RequestBuilder { private var _content: Optional = Optional.Missing() var content: String? by ::_content.delegate() @@ -204,7 +211,7 @@ class FollowupMessageModifyBuilder() : } } -class InteractionApplicationCommandCallbackDataBuilder(val content: String) { +class InteractionApplicationCommandCallbackDataBuilder(var content: String) { private var _tts: OptionalBoolean = OptionalBoolean.Missing var tts: Boolean? by ::_tts.delegate() @@ -220,9 +227,9 @@ class InteractionApplicationCommandCallbackDataBuilder(val content: String) { embeds!! += EmbedBuilder().apply(builder) } - fun build(): DiscordInteractionApplicationCommandCallbackData { + fun build(): InteractionApplicationCommandCallbackData { - return DiscordInteractionApplicationCommandCallbackData( + return InteractionApplicationCommandCallbackData( _tts, content, _embeds.mapList { it.toRequest() }, @@ -231,17 +238,44 @@ class InteractionApplicationCommandCallbackDataBuilder(val content: String) { } } -class InteractionResponseCreateBuilder(var type: InteractionResponseType) : RequestBuilder { - private var _data: Optional = Optional.Missing() - private var data: InteractionApplicationCommandCallbackDataBuilder? by ::_data.delegate() - fun data(content: String, builder: InteractionApplicationCommandCallbackDataBuilder.() -> Unit) { - val data = InteractionApplicationCommandCallbackDataBuilder(content).apply(builder) +class FollowupMessageCreateBuilder : RequestBuilder { + + private var _content: Optional = Optional.Missing() + var content: String? by ::_content.delegate() + + private var _username: Optional = Optional.Missing() + var username: String? by ::_username.delegate() + + private var _avatarUrl: Optional = Optional.Missing() + var avatarUrl: String? by ::_avatarUrl.delegate() + + private var _tts: OptionalBoolean = OptionalBoolean.Missing + var tts: Boolean? by ::_tts.delegate() + + private var file: Pair? = null + var embeds: MutableList = mutableListOf() + + fun setFile(name: String, content: java.io.InputStream) { + file = name to content } - override fun toRequest(): DiscordInteractionResponse { - return DiscordInteractionResponse(type, _data.map { it.build() }) + suspend fun setFile(path: Path) = withContext(Dispatchers.IO) { + setFile(path.fileName.toString(), Files.newInputStream(path)) } + @OptIn(ExperimentalContracts::class) + inline fun embed(builder: EmbedBuilder.() -> Unit) { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + } + embeds.add(EmbedBuilder().apply(builder).toRequest()) + } + + override fun toRequest(): MultipartFollowupMessageCreateRequest = MultipartFollowupMessageCreateRequest( + FollowupMessageCreateRequest(_content, _username, _avatarUrl, _tts, Optional.missingOnEmpty(embeds)), file + ) + + } diff --git a/rest/src/main/kotlin/json/request/InteractionsRequests.kt b/rest/src/main/kotlin/json/request/InteractionsRequests.kt index 4ff78c424195..e3ea5265416a 100644 --- a/rest/src/main/kotlin/json/request/InteractionsRequests.kt +++ b/rest/src/main/kotlin/json/request/InteractionsRequests.kt @@ -7,6 +7,7 @@ import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import java.io.InputStream @Serializable data class GlobalApplicationCommandCreateRequest( @@ -46,19 +47,24 @@ data class OriginalInteractionResponseModifyRequest( @Serializable -data class DiscordInteractionResponse( +data class InteractionResponseCreateRequest( val type: InteractionResponseType, - val data: Optional = Optional.Missing() + val data: Optional = Optional.Missing() ) @Serializable -class DiscordInteractionApplicationCommandCallbackData( +class InteractionApplicationCommandCallbackData( val tts: OptionalBoolean = OptionalBoolean.Missing, val content: String, val embeds: Optional> = Optional.Missing(), val allowedMentions: Optional = Optional.Missing() ) +data class MultipartFollowupMessageCreateRequest( + val request: FollowupMessageCreateRequest, + val file: Pair? +) + @Serializable class FollowupMessageCreateRequest( val content: Optional = Optional.Missing(), diff --git a/rest/src/main/kotlin/route/Route.kt b/rest/src/main/kotlin/route/Route.kt index f7ed409d0662..e4e0cf9a1ba1 100644 --- a/rest/src/main/kotlin/route/Route.kt +++ b/rest/src/main/kotlin/route/Route.kt @@ -5,7 +5,6 @@ import dev.kord.common.annotation.KordExperimental import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.* import dev.kord.rest.json.optional -import dev.kord.rest.json.request.DiscordInteractionResponse import dev.kord.rest.json.request.MessageEditPatchRequest import dev.kord.rest.json.response.* import io.ktor.http.* diff --git a/rest/src/main/kotlin/service/InteractionService.kt b/rest/src/main/kotlin/service/InteractionService.kt index 4df6bceff702..8512f125bb6f 100644 --- a/rest/src/main/kotlin/service/InteractionService.kt +++ b/rest/src/main/kotlin/service/InteractionService.kt @@ -75,20 +75,20 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa suspend fun createInteractionResponse( interactionId: Snowflake, interactionToken: String, - request: DiscordInteractionResponse + request: InteractionResponseCreateRequest ) = call(Route.InteractionResponseCreate) { keys[Route.InteractionId] = interactionId keys[Route.InteractionToken] = interactionToken - body(DiscordInteractionResponse.serializer(), request) + body(InteractionResponseCreateRequest.serializer(), request) } suspend fun modifyInteractionResponse( - interactionId: Snowflake, + applicationId: Snowflake, interactionToken: String, request: OriginalInteractionResponseModifyRequest ) = call(Route.OriginalInteractionResponseModify) { - keys[Route.InteractionId] = interactionId + keys[Route.ApplicationId] = applicationId keys[Route.InteractionToken] = interactionToken body(OriginalInteractionResponseModifyRequest.serializer(), request) @@ -103,13 +103,15 @@ class InteractionService(requestHandler: RequestHandler) : RestService(requestHa suspend fun createFollowupMessage( applicationId: Snowflake, interactionToken: String, - request: FollowupMessageCreateRequest, + multipart: MultipartFollowupMessageCreateRequest, wait: Boolean = false ) = call(Route.FollowupMessageCreate) { keys[Route.ApplicationId] = applicationId keys[Route.InteractionToken] = interactionToken parameter("wait", "$wait") - body(FollowupMessageCreateRequest.serializer(), request) + body(FollowupMessageCreateRequest.serializer(), multipart.request) + multipart.file?.let { file(it) } + } suspend fun deleteFollowupMessage(applicationId: Snowflake, interactionToken: String, messageId: Snowflake) = From 5c841d94c6e01eab7220b5ac96898a685f646cd2 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Tue, 22 Dec 2020 15:59:24 +0200 Subject: [PATCH 19/40] Add interactions core entities and behaviors --- .../behavior/FollowupMessageBehavior.kt | 51 ++++ .../kotlin/behavior/InteractionBehavior.kt | 103 ++++++++ .../behavior/InteractionResponseBehavior.kt | 44 ++++ .../main/kotlin/cache/data/InteractionData.kt | 71 +++++ .../entity/interaction/FollowupMessage.kt | 246 ++++++++++++++++++ .../kotlin/entity/interaction/Interaction.kt | 49 ++++ .../event/interaction/InteractionCreate.kt | 18 ++ .../handler/GatewayEventInterceptor.kt | 3 +- .../handler/InteractionEventHandler.kt | 35 +++ 9 files changed, 619 insertions(+), 1 deletion(-) create mode 100644 core/src/main/kotlin/behavior/FollowupMessageBehavior.kt create mode 100644 core/src/main/kotlin/behavior/InteractionBehavior.kt create mode 100644 core/src/main/kotlin/behavior/InteractionResponseBehavior.kt create mode 100644 core/src/main/kotlin/cache/data/InteractionData.kt create mode 100644 core/src/main/kotlin/entity/interaction/FollowupMessage.kt create mode 100644 core/src/main/kotlin/entity/interaction/Interaction.kt create mode 100644 core/src/main/kotlin/event/interaction/InteractionCreate.kt create mode 100644 core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt diff --git a/core/src/main/kotlin/behavior/FollowupMessageBehavior.kt b/core/src/main/kotlin/behavior/FollowupMessageBehavior.kt new file mode 100644 index 000000000000..b13509e81622 --- /dev/null +++ b/core/src/main/kotlin/behavior/FollowupMessageBehavior.kt @@ -0,0 +1,51 @@ +package dev.kord.core.behavior + +import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord +import dev.kord.core.cache.data.MessageData +import dev.kord.core.entity.FollowupMessage +import dev.kord.core.entity.KordEntity +import dev.kord.rest.builder.interaction.FollowupMessageModifyBuilder + +interface FollowupMessageBehavior : KordEntity { + val applicationId: Snowflake + val token: String + val channelId: Snowflake + + suspend fun delete() { + kord.rest.interaction.deleteFollowupMessage(applicationId, token, id) + } + + suspend fun edit(builder: FollowupMessageModifyBuilder.() -> Unit): FollowupMessage { + val request = FollowupMessageModifyBuilder().apply(builder).toRequest() + val response = kord.rest.interaction.modifyFollowupMessage(applicationId, token, id, request) + val data = MessageData.from(response) + return FollowupMessage(data, token, applicationId, kord) + } + + companion object { + operator fun invoke( + id: Snowflake, + applicationId: Snowflake, + channelId: Snowflake, + token: String, + kord: Kord + ) = object : FollowupMessageBehavior { + override val id: Snowflake + get() = id + + override val applicationId: Snowflake + get() = applicationId + + override val token: String + get() = token + override val channelId: Snowflake + get() = channelId + + override val kord: Kord + get() = kord + + } + + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/behavior/InteractionBehavior.kt b/core/src/main/kotlin/behavior/InteractionBehavior.kt new file mode 100644 index 000000000000..86f0f074e6ce --- /dev/null +++ b/core/src/main/kotlin/behavior/InteractionBehavior.kt @@ -0,0 +1,103 @@ +package dev.kord.core.behavior + +import dev.kord.common.entity.InteractionResponseType +import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.optional +import dev.kord.core.Kord +import dev.kord.core.entity.KordEntity +import dev.kord.rest.builder.interaction.InteractionApplicationCommandCallbackDataBuilder +import dev.kord.rest.json.request.InteractionResponseCreateRequest + +interface InteractionBehavior : KordEntity { + + //TODO("return full response with full functionality") + + val applicationId: Snowflake + val token: String + + suspend fun acknowledge(source: Boolean = false): InteractionResponseBehavior { + val type = if (source) InteractionResponseType.ACKWithSource + else InteractionResponseType.Acknowledge + val request = InteractionResponseCreateRequest(type) + kord.rest.interaction.createInteractionResponse(id, token, request) + return InteractionResponseBehavior(applicationId, token,kord) + } + + suspend fun pong() { + val request = InteractionResponseCreateRequest(InteractionResponseType.Pong) + kord.rest.interaction.createInteractionResponse(id, token, request) + } + + + suspend fun respond( + content: String, + source: Boolean = false, + builder: InteractionApplicationCommandCallbackDataBuilder.() -> Unit + ): InteractionResponseBehavior { + val type = if (source) InteractionResponseType.ChannelMessageWithSource + else InteractionResponseType.ChannelMessage + + val data = InteractionApplicationCommandCallbackDataBuilder(content).apply(builder).build() + val request = InteractionResponseCreateRequest(type, data.optional()) + kord.rest.interaction.createInteractionResponse(id, token, request) + return InteractionResponseBehavior(applicationId, token,kord) + + } + + + operator fun invoke(id: Snowflake, token: String, applicationId: Snowflake, kord: Kord) = + 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 + } +} + +interface PartialInteractionBehavior : KordEntity { + //TODO("return full response with data") + val token: String + + suspend fun acknowledge(source: Boolean = false) { + val type = if (source) InteractionResponseType.ACKWithSource else InteractionResponseType.Acknowledge + val request = InteractionResponseCreateRequest(type) + kord.rest.interaction.createInteractionResponse(id, token, request) + } + + suspend fun pong() { + val request = InteractionResponseCreateRequest(InteractionResponseType.Pong) + kord.rest.interaction.createInteractionResponse(id, token, request) + } + + + suspend fun respond( + content: String, + source: Boolean = false, + builder: InteractionApplicationCommandCallbackDataBuilder.() -> Unit + ) { + val type = + if (source) InteractionResponseType.ChannelMessageWithSource else InteractionResponseType.ChannelMessage + val data = InteractionApplicationCommandCallbackDataBuilder(content).apply(builder).build() + val request = InteractionResponseCreateRequest(type, data.optional()) + kord.rest.interaction.createInteractionResponse(id, token, request) + } + + + companion object { + operator fun invoke(id: Snowflake, token: String, kord: Kord) = object : PartialInteractionBehavior { + override val id: Snowflake + get() = id + override val token: String + get() = token + override val kord: Kord + get() = kord + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/behavior/InteractionResponseBehavior.kt b/core/src/main/kotlin/behavior/InteractionResponseBehavior.kt new file mode 100644 index 000000000000..ab4bba1ce61a --- /dev/null +++ b/core/src/main/kotlin/behavior/InteractionResponseBehavior.kt @@ -0,0 +1,44 @@ +package dev.kord.core.behavior + +import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord +import dev.kord.core.KordObject +import dev.kord.core.cache.data.MessageData +import dev.kord.core.entity.FollowupMessage +import dev.kord.rest.builder.interaction.FollowupMessageCreateBuilder +import dev.kord.rest.builder.interaction.OriginalInteractionResponseModifyBuilder + +interface InteractionResponseBehavior : KordObject { + val applicationId: Snowflake + val token: String + + suspend fun edit(builder: OriginalInteractionResponseModifyBuilder.() -> Unit) { + val request = OriginalInteractionResponseModifyBuilder().apply(builder).toRequest() + kord.rest.interaction.modifyInteractionResponse(applicationId, token, request) + } + + suspend fun delete() { + kord.rest.interaction.deleteOriginalInteractionResponse(applicationId, token) + } + + suspend fun followUp(builder: FollowupMessageCreateBuilder.() -> Unit): FollowupMessage { + val request = FollowupMessageCreateBuilder().apply(builder).toRequest() + val response = kord.rest.interaction.createFollowupMessage(applicationId, token, request) + val data = MessageData.from(response) + return FollowupMessage(data, token, applicationId, kord) + } + + companion object { + operator fun invoke(applicationId: Snowflake, token: String, kord: Kord) = + object : InteractionResponseBehavior { + override val applicationId: Snowflake + get() = applicationId + + override val token: String + get() = token + + override val kord: Kord + get() = kord + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/cache/data/InteractionData.kt b/core/src/main/kotlin/cache/data/InteractionData.kt new file mode 100644 index 000000000000..f7474cb9fc78 --- /dev/null +++ b/core/src/main/kotlin/cache/data/InteractionData.kt @@ -0,0 +1,71 @@ +package dev.kord.core.cache.data + +import dev.kord.common.entity.DiscordApplicationCommandInteractionData +import dev.kord.common.entity.DiscordApplicationCommandInteractionDataOption +import dev.kord.common.entity.InteractionType +import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.mapList +import dev.kord.gateway.InteractionCreate +import kotlinx.serialization.Serializable + +data class InteractionData( + val id: Snowflake, + val type: InteractionType, + val data: ApplicationCommandInteractionData, + val guildId: Snowflake, + val channelId: Snowflake, + val member: MemberData, + val token: String, + val version: Int +) { + companion object { + fun from(event: InteractionCreate): InteractionData { + return with(event.interaction) { + InteractionData( + id, + type, + ApplicationCommandInteractionData.from(data), + guildId, + channelId, + member.toData(member.user.value!!.id,guildId), + token, + version + ) + } + } + } +} + +@Serializable +data class ApplicationCommandInteractionData( + val id: Snowflake, + val name: String, + val options: Optional> = Optional.Missing() +) { + companion object { + fun from(data: DiscordApplicationCommandInteractionData): ApplicationCommandInteractionData { + return with(data) { + ApplicationCommandInteractionData( + id, + name, + options.mapList { ApplicationCommandInteractionDataOptionData.from(it) }) + } + } + } +} + +@Serializable +data class ApplicationCommandInteractionDataOptionData( + val name: String, + val value: Optional = Optional.Missing(), + val options: Optional> = Optional.Missing() +) { + companion object { + fun from(data: DiscordApplicationCommandInteractionDataOption): ApplicationCommandInteractionDataOptionData { + return with(data) { + ApplicationCommandInteractionDataOptionData(name, value, options.mapList { from(it) }) + } + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/entity/interaction/FollowupMessage.kt b/core/src/main/kotlin/entity/interaction/FollowupMessage.kt new file mode 100644 index 000000000000..858c451851ef --- /dev/null +++ b/core/src/main/kotlin/entity/interaction/FollowupMessage.kt @@ -0,0 +1,246 @@ +package dev.kord.core.entity + +import dev.kord.common.entity.MessageType +import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.orEmpty +import dev.kord.common.exception.RequestException +import dev.kord.core.Kord +import dev.kord.core.behavior.FollowupMessageBehavior +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.Channel +import dev.kord.core.entity.channel.GuildChannel +import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.core.entity.channel.MessageChannel +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 java.time.Instant +import java.time.format.DateTimeFormatter +import java.util.* + +/** + * An instance of a [Discord Message][https://discord.com/developers/docs/resources/channel#message-object]. + */ +class FollowupMessage( + val data: MessageData, + override val token: String, + override val applicationId: Snowflake, + override val kord: Kord, + override val supplier: EntitySupplier = kord.defaultSupplier, +) : FollowupMessageBehavior, Strategizable { + + /** + * The id of this message. + */ + override val id: Snowflake + get() = data.id + + /** + * The id of the [MessageChannel] this message was send in. + */ + override val channelId: Snowflake + get() = data.channelId + + /** + * The files attached to this message. + */ + val attachments: Set get() = data.attachments.asSequence().map { Attachment(it, kord) }.toSet() + + /** + * The author of this message, if it was created by a [User]. + * + * Returns null if the author is not a Discord account, like a [Webhook] or systems message. + */ + val author: User? + get() = if (data.webhookId.value == data.author.id) null + else User(data.author, kord) + + /** + * The content of this message. + */ + val content: String get() = data.content + + /** + * The instant when this message was last edited, if ever. + * + * Returns null if the message was never edited. + */ + val editedTimestamp: Instant? + get() = data.editedTimestamp?.let { + DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(it, Instant::from) + } + + /** + * The embedded content of this message. + * + * This includes automatically embedded [videos][Embed.video] and [urls][Embed.Provider]. + */ + val embeds: List get() = data.embeds.map { Embed(it, kord) } + + /** + * The ids of [Channels][Channel] specifically mentioned in this message. + * + * This collection can only contain values on crossposted messages, channels + * mentioned inside the same guild will not be present. + */ + val mentionedChannelIds: Set get() = data.mentionedChannels.orEmpty().map { it }.toSet() + + /** + * The [Channels][ChannelBehavior] specifically mentioned in this message. + * + * This collection can only contain values on crossposted messages, channels + * mentioned inside the same guild will not be present. + */ + val mentionedChannelBehaviors: Set get() = data.mentionedChannels.orEmpty().map { ChannelBehavior(it, kord) }.toSet() + + /** + * The stickers sent with this message. + */ + val stickers: List get() = data.stickers.orEmpty().map { MessageSticker(it, kord) } + + /** + * The message being replied to. + * + * Absence of this field does **not** mean this message was not a reply. The referenced message + * may not be available (through deletion or other means). + * Compare [type] to [MessageType.Reply] for a consistent way of identifying replies. + */ + val referencedMessage: Message? get() = data.referencedMessage.value?.let { Message(it, kord) } + + /** + * The [Channels][Channel] specifically mentioned in this message. + * + * This property will only emit values on crossposted messages, channels + * mentioned inside the same guild will not be present. + * + * This request uses state [data] to resolve the entities belonging to the flow, + * as such it can't guarantee an up to date representation if the [data] is outdated. + * + * The returned flow is lazily executed, any [RequestException] will be thrown on + * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. + */ + val mentionedChannels: Flow + get() = mentionedChannelIds.asFlow().map { supplier.getChannel(it) } + + /** + * True if this message mentions `@everyone`. + */ + val mentionsEveryone: Boolean get() = data.mentionEveryone + + /** + * The [ids][Role.id] of roles mentioned in this message. + */ + val mentionedRoleIds: Set get() = data.mentionRoles.map { it }.toSet() + + /** + * The [roles][Role] mentioned in this message. + * + * This request uses state [data] to resolve the entities belonging to the flow, + * as such it can't guarantee an up to date representation if the [data] is outdated. + * + * The returned flow is lazily executed, any [RequestException] will be thrown on + * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. + */ + val mentionedRoles: Flow + get() = flow { + if (mentionedRoleIds.isEmpty()) return@flow + + val guild = getGuild() + supplier.getGuildRoles(guild.id).filter { it.id in mentionedRoleIds } + } + + /** + * The [ids][User.id] of users mentioned in this message. + */ + val mentionedUserIds: Set get() = data.mentions.map { it }.toSet() + + /** + * The [Behaviors][UserBehavior] of users mentioned in this message. + */ + val mentionedUserBehaviors: Set get() = data.mentions.map { UserBehavior(it, kord) }.toSet() + + /** + * The [users][User] mentioned in this message. + * + * This request uses state [data] to resolve the entities belonging to the flow, + * as such it can't guarantee an up to date representation if the [data] is outdated. + * + * The returned flow is lazily executed, any [RequestException] will be thrown on + * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. + */ + val mentionedUsers: Flow + get() = data.mentions.asFlow().map { supplier.getUser(it) } + + /** + * Whether the message was pinned in its [channel]. + */ + val isPinned get() = data.pinned + + /** + * The reactions to this message. + */ + val reactions: Set get() = data.reactions.orEmpty().asSequence().map { Reaction(it, kord) }.toSet() + + /** + * The instant when this message was created. + */ + val timestamp: Instant get() = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(data.timestamp, Instant::from) + + /** + * Whether this message was send using `\tts`. + */ + val tts: Boolean get() = data.tts + + /** + * The type of this message. + */ + val type: MessageType get() = data.type + + /** + * The [id][Webhook.id] of the [Webhook] that was used to send this message. + * + * Returns null if this message was not send using a webhook. + */ + val webhookId: Snowflake? get() = data.webhookId.value + + + /** + * Requests to get the guild of this message. + * + * @throws [RequestException] if anything went wrong during the request. + * @throws [EntityNotFoundException] if the [Guild] wasn't present. + * @throws [ClassCastException] if this message wasn't made in a guild. + */ + suspend fun getGuild(): Guild = supplier.getChannelOf(channelId).getGuild() + + /** + * Requests to get the guild of this message, + * returns null if the [Guild] isn't present or this message wasn't made in a guild. + * + * @throws [RequestException] if anything went wrong during the request. + */ + suspend fun getGuildOrNull(): Guild? = supplier.getChannelOfOrNull(channelId)?.getGuildOrNull() + + /** + * Returns a new [Message] with the given [strategy]. + */ + override fun withStrategy(strategy: EntitySupplyStrategy<*>): Message = Message(data, kord, strategy.supply(kord)) + + override fun hashCode(): Int = Objects.hash(id) + + override fun equals(other: Any?): Boolean = when (other) { + is MessageBehavior -> other.id == id && other.channelId == channelId + else -> false + } + + override fun toString(): String { + return "Message(data=$data, kord=$kord, supplier=$supplier)" + } + +} diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt new file mode 100644 index 000000000000..532877494c19 --- /dev/null +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -0,0 +1,49 @@ +package dev.kord.core.entity.interaction + +import dev.kord.common.entity.InteractionType +import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord +import dev.kord.core.behavior.InteractionBehavior +import dev.kord.core.behavior.MemberBehavior +import dev.kord.core.behavior.PartialInteractionBehavior +import dev.kord.core.cache.data.InteractionData +import dev.kord.core.entity.CommandOptions +import dev.kord.core.entity.KordEntity + + class PartialInteraction(val data: InteractionData, override val kord: Kord) : PartialInteractionBehavior { + + override val id: Snowflake get() = data.id + + val channelId: Snowflake get() = data.channelId + + override val token: String get() = data.token + + val guildId: Snowflake get() = data.channelId + + val type: InteractionType get() = data.type + + val member: MemberBehavior get() = MemberBehavior(guildId, data.member.userId, kord) + + val version: Int get() = data.version + +} + +class Interaction(val data: InteractionData, override val applicationId: Snowflake, override val kord: Kord): InteractionBehavior { + + override val id: Snowflake get() = data.id + + val channelId: Snowflake get() = data.channelId + + override val token: String get() = data.token + + val guildId: Snowflake get() = data.channelId + + val type: InteractionType get() = data.type + + val member: MemberBehavior get() = MemberBehavior(guildId, data.member.userId, kord) + + val name: String = data.data.name + + val version: Int get() = data.version + +} \ No newline at end of file diff --git a/core/src/main/kotlin/event/interaction/InteractionCreate.kt b/core/src/main/kotlin/event/interaction/InteractionCreate.kt new file mode 100644 index 000000000000..ebcd66d2d9b4 --- /dev/null +++ b/core/src/main/kotlin/event/interaction/InteractionCreate.kt @@ -0,0 +1,18 @@ +package dev.kord.core.event.interaction + +import dev.kord.core.Kord +import dev.kord.core.entity.interaction.Interaction +import dev.kord.core.entity.interaction.PartialInteraction +import dev.kord.core.event.Event + +class InteractionCreateEvent( + val interaction: Interaction, + override val kord: Kord, + override val shard: Int +) : Event + +class PartialInteractionCreateEvent( + val interaction: PartialInteraction, + override val kord: Kord, + override val shard: Int +) : Event \ No newline at end of file diff --git a/core/src/main/kotlin/gateway/handler/GatewayEventInterceptor.kt b/core/src/main/kotlin/gateway/handler/GatewayEventInterceptor.kt index aeda006961d3..20ddd274c8b0 100644 --- a/core/src/main/kotlin/gateway/handler/GatewayEventInterceptor.kt +++ b/core/src/main/kotlin/gateway/handler/GatewayEventInterceptor.kt @@ -30,7 +30,8 @@ class GatewayEventInterceptor( LifeCycleEventHandler(kord, gateway, cache, coreFlow), UserEventHandler(kord, gateway, cache, coreFlow), VoiceEventHandler(kord, gateway, cache, coreFlow), - WebhookEventHandler(kord, gateway, cache, coreFlow) + WebhookEventHandler(kord, gateway, cache, coreFlow), + InteractionEventHandler(kord,gateway, cache, coreFlow) ) suspend fun start() = gateway.events diff --git a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt new file mode 100644 index 000000000000..337a4c39d488 --- /dev/null +++ b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt @@ -0,0 +1,35 @@ +package dev.kord.core.gateway.handler + +import com.gitlab.kordlib.cache.api.DataCache +import dev.kord.core.Kord +import dev.kord.core.cache.data.InteractionData +import dev.kord.core.entity.interaction.Interaction +import dev.kord.core.entity.interaction.PartialInteraction +import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.PartialInteractionCreateEvent +import dev.kord.core.gateway.MasterGateway +import dev.kord.gateway.Event +import dev.kord.gateway.InteractionCreate +import kotlinx.coroutines.flow.MutableSharedFlow +import dev.kord.core.event.Event as CoreEvent + +class InteractionEventHandler( + kord: Kord, + gateway: MasterGateway, + cache: DataCache, + coreFlow: MutableSharedFlow +) : BaseGatewayEventHandler(kord, gateway, cache, coreFlow) { + override suspend fun handle(event: Event, shard: Int) { + + if(event !is InteractionCreate) return + val data = InteractionData.from(event) + val partial = PartialInteraction(data, kord) + coreFlow.emit(PartialInteractionCreateEvent(partial, kord, shard)) + + if (kord.resources.applicationId != null) { + val interaction = Interaction(data, kord.resources.applicationId, kord) + coreFlow.emit(InteractionCreateEvent(interaction, kord, shard)) + } + + } +} \ No newline at end of file From 8b374454e682b6cc87c6d0355a452e73b7a0642a Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Tue, 22 Dec 2020 17:57:37 +0200 Subject: [PATCH 20/40] Remove pongs from interactions They are not allowed through this endpoint --- core/src/main/kotlin/behavior/InteractionBehavior.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/src/main/kotlin/behavior/InteractionBehavior.kt b/core/src/main/kotlin/behavior/InteractionBehavior.kt index 86f0f074e6ce..c96047f38cc5 100644 --- a/core/src/main/kotlin/behavior/InteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/InteractionBehavior.kt @@ -23,10 +23,6 @@ interface InteractionBehavior : KordEntity { return InteractionResponseBehavior(applicationId, token,kord) } - suspend fun pong() { - val request = InteractionResponseCreateRequest(InteractionResponseType.Pong) - kord.rest.interaction.createInteractionResponse(id, token, request) - } suspend fun respond( @@ -71,10 +67,6 @@ interface PartialInteractionBehavior : KordEntity { kord.rest.interaction.createInteractionResponse(id, token, request) } - suspend fun pong() { - val request = InteractionResponseCreateRequest(InteractionResponseType.Pong) - kord.rest.interaction.createInteractionResponse(id, token, request) - } suspend fun respond( From 6e2d93090e3dc62053162b85b7cdefd138b3a7d4 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Wed, 23 Dec 2020 09:38:47 +0200 Subject: [PATCH 21/40] Add wrapping class for parameters --- .../kotlin/entity/interaction/Interaction.kt | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt index 532877494c19..2884bb2c7bc7 100644 --- a/core/src/main/kotlin/entity/interaction/Interaction.kt +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -2,15 +2,17 @@ package dev.kord.core.entity.interaction import dev.kord.common.entity.InteractionType import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.orEmpty import dev.kord.core.Kord import dev.kord.core.behavior.InteractionBehavior import dev.kord.core.behavior.MemberBehavior import dev.kord.core.behavior.PartialInteractionBehavior +import dev.kord.core.cache.data.ApplicationCommandInteractionData +import dev.kord.core.cache.data.ApplicationCommandInteractionDataOptionData import dev.kord.core.cache.data.InteractionData -import dev.kord.core.entity.CommandOptions -import dev.kord.core.entity.KordEntity +import dev.kord.core.entity.Entity - class PartialInteraction(val data: InteractionData, override val kord: Kord) : PartialInteractionBehavior { +class PartialInteraction(val data: InteractionData, override val kord: Kord) : PartialInteractionBehavior { override val id: Snowflake get() = data.id @@ -28,7 +30,8 @@ import dev.kord.core.entity.KordEntity } -class Interaction(val data: InteractionData, override val applicationId: Snowflake, override val kord: Kord): InteractionBehavior { +class Interaction(val data: InteractionData, override val applicationId: Snowflake, override val kord: Kord) : + InteractionBehavior { override val id: Snowflake get() = data.id @@ -42,8 +45,28 @@ class Interaction(val data: InteractionData, override val applicationId: Snowfla val member: MemberBehavior get() = MemberBehavior(guildId, data.member.userId, kord) - val name: String = data.data.name + val command: Command + get() = Command(data.data) val version: Int get() = data.version +} + +class Command(val data: ApplicationCommandInteractionData) : Entity { + override val id: Snowflake + get() = data.id + + val name = data.name + + val parameters = data.options.orEmpty().map { Parameter(it) } + +} + +class Parameter(val data: ApplicationCommandInteractionDataOptionData) { + val name: String + get() = data.name + val value: String? + get() = data.value.value + + val parameters = data.options.value.orEmpty().map { Parameter(it) } } \ No newline at end of file From d588bafb5bdd9f38dc778b194c14eddca4875137 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Wed, 23 Dec 2020 09:43:35 +0200 Subject: [PATCH 22/40] Allow no-parameter commands in builders --- core/src/main/kotlin/SlashCommands.kt | 6 +++--- .../kotlin/builder/interaction/InteractionsBuilder.kt | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/kotlin/SlashCommands.kt b/core/src/main/kotlin/SlashCommands.kt index b4783c1e47c4..dda7ebcd12eb 100644 --- a/core/src/main/kotlin/SlashCommands.kt +++ b/core/src/main/kotlin/SlashCommands.kt @@ -17,7 +17,7 @@ class SlashCommands( suspend fun createGlobalApplicationCommand( name: String, description: String, - builder: GlobalApplicationCommandCreateBuilder.() -> Unit + builder: GlobalApplicationCommandCreateBuilder.() -> Unit = {} ): GlobalApplicationCommand { val request = GlobalApplicationCommandCreateBuilder(name, description).apply(builder).toRequest() val response = service.createGlobalApplicationCommand(applicationId, request) @@ -29,7 +29,7 @@ class SlashCommands( guildId: Snowflake, name: String, description: String, - builder: GuildApplicationCommandCreateBuilder.() -> Unit + builder: GuildApplicationCommandCreateBuilder.() -> Unit = {} ): GuildApplicationCommand { val request = GuildApplicationCommandCreateBuilder(name, description).apply(builder).toRequest() val response = service.createGuildApplicationCommand(applicationId, guildId, request) @@ -45,7 +45,7 @@ class SlashCommands( } - suspend fun getGlobalApplicationCommands(guildId: Snowflake): Flow = flow { + suspend fun getGlobalApplicationCommands(): Flow = flow { for (command in service.getGlobalApplicationCommands(applicationId)) { val data = ApplicationCommandData.from(command) emit(GlobalApplicationCommand(data, service)) diff --git a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt index 6e7e5910386a..e4986a065263 100644 --- a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt @@ -34,7 +34,7 @@ class GlobalApplicationCommandCreateBuilder( type: ApplicationCommandOptionType, name: String, description: String, - builder: ApplicationCommandOptionBuilder.() -> Unit + builder: ApplicationCommandOptionBuilder.() -> Unit = {} ) { if (options == null) options = mutableListOf() val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) @@ -65,7 +65,7 @@ class GlobalApplicationCommandModifyBuilder : RequestBuilder Unit + builder: ApplicationCommandOptionBuilder.() -> Unit = {} ) { if (options == null) options = mutableListOf() val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) @@ -92,7 +92,7 @@ class GuildApplicationCommandCreateBuilder( type: ApplicationCommandOptionType, name: String, description: String, - builder: ApplicationCommandOptionBuilder.() -> Unit + builder: ApplicationCommandOptionBuilder.() -> Unit = {} ) { if (options == null) options = mutableListOf() val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) @@ -124,7 +124,7 @@ class GuildApplicationCommandModifyBuilder : RequestBuilder Unit + builder: ApplicationCommandOptionBuilder.() -> Unit = {} ) { if (options == null) options = mutableListOf() val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) From 1d243ef74f8485627bf83b5dff3878d368d7d8ed Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Thu, 24 Dec 2020 09:49:05 +0200 Subject: [PATCH 23/40] Add application command gateway events Due to the lack of information regarding the global commands https://github.com/discord/discord-api-docs/pull/2367 I've considered providing a guild application. --- common/src/main/kotlin/entity/Interactions.kt | 3 ++ .../cache/data/ApplicationCommandData.kt | 3 ++ .../interaction/ApplicationCommandCreate.kt | 11 ++++++ .../interaction/ApplicationCommandDelete.kt | 12 ++++++ .../interaction/ApplicationCommandUpdate.kt | 12 ++++++ .../handler/InteractionEventHandler.kt | 38 ++++++++++++++++--- gateway/src/main/kotlin/Event.kt | 13 +++++++ 7 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 core/src/main/kotlin/event/interaction/ApplicationCommandCreate.kt create mode 100644 core/src/main/kotlin/event/interaction/ApplicationCommandDelete.kt create mode 100644 core/src/main/kotlin/event/interaction/ApplicationCommandUpdate.kt diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index a21f876f3e60..d000aa378671 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -2,6 +2,7 @@ package dev.kord.common.entity import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean +import dev.kord.common.entity.optional.OptionalSnowflake import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName @@ -19,6 +20,8 @@ data class DiscordApplicationCommand( val applicationId: Snowflake, val name: String, 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 b994d43d35b2..44a05fe8321b 100644 --- a/core/src/main/kotlin/cache/data/ApplicationCommandData.kt +++ b/core/src/main/kotlin/cache/data/ApplicationCommandData.kt @@ -3,6 +3,7 @@ package dev.kord.core.cache.data import dev.kord.common.entity.* 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 kotlinx.serialization.Serializable @@ -12,6 +13,7 @@ data class ApplicationCommandData( val applicationId: Snowflake, val name: String, val description: String, + val guildId: OptionalSnowflake, val options: Optional> ) { companion object { @@ -22,6 +24,7 @@ data class ApplicationCommandData( applicationId, name, description, + guildId, options.mapList { ApplicationCommandOptionData.from(it) }) } } diff --git a/core/src/main/kotlin/event/interaction/ApplicationCommandCreate.kt b/core/src/main/kotlin/event/interaction/ApplicationCommandCreate.kt new file mode 100644 index 000000000000..1b6bfd24fc2f --- /dev/null +++ b/core/src/main/kotlin/event/interaction/ApplicationCommandCreate.kt @@ -0,0 +1,11 @@ +package dev.kord.core.event.interaction + +import dev.kord.core.Kord +import dev.kord.core.entity.GuildApplicationCommand +import dev.kord.core.event.Event + +class ApplicationCommandCreateEvent( + val command: GuildApplicationCommand, + override val kord: Kord, + override val shard: Int +) : Event diff --git a/core/src/main/kotlin/event/interaction/ApplicationCommandDelete.kt b/core/src/main/kotlin/event/interaction/ApplicationCommandDelete.kt new file mode 100644 index 000000000000..6aef39e78db3 --- /dev/null +++ b/core/src/main/kotlin/event/interaction/ApplicationCommandDelete.kt @@ -0,0 +1,12 @@ +package dev.kord.core.event.interaction + +import dev.kord.core.Kord +import dev.kord.core.entity.GuildApplicationCommand +import dev.kord.core.event.Event + + +class ApplicationCommandDeleteEvent( + val command: GuildApplicationCommand, + override val kord: Kord, + override val shard: Int +) : Event \ No newline at end of file diff --git a/core/src/main/kotlin/event/interaction/ApplicationCommandUpdate.kt b/core/src/main/kotlin/event/interaction/ApplicationCommandUpdate.kt new file mode 100644 index 000000000000..675e13f8631b --- /dev/null +++ b/core/src/main/kotlin/event/interaction/ApplicationCommandUpdate.kt @@ -0,0 +1,12 @@ +package dev.kord.core.event.interaction + +import dev.kord.core.Kord +import dev.kord.core.entity.GuildApplicationCommand +import dev.kord.core.event.Event + +class ApplicationCommandUpdateEvent( + val command: GuildApplicationCommand, + override val kord: Kord, + override val shard: Int +) : Event + diff --git a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt index 337a4c39d488..6432533f8453 100644 --- a/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt +++ b/core/src/main/kotlin/gateway/handler/InteractionEventHandler.kt @@ -2,14 +2,14 @@ package dev.kord.core.gateway.handler import com.gitlab.kordlib.cache.api.DataCache import dev.kord.core.Kord +import dev.kord.core.cache.data.ApplicationCommandData import dev.kord.core.cache.data.InteractionData +import dev.kord.core.entity.GuildApplicationCommand import dev.kord.core.entity.interaction.Interaction import dev.kord.core.entity.interaction.PartialInteraction -import dev.kord.core.event.interaction.InteractionCreateEvent -import dev.kord.core.event.interaction.PartialInteractionCreateEvent +import dev.kord.core.event.interaction.* import dev.kord.core.gateway.MasterGateway -import dev.kord.gateway.Event -import dev.kord.gateway.InteractionCreate +import dev.kord.gateway.* import kotlinx.coroutines.flow.MutableSharedFlow import dev.kord.core.event.Event as CoreEvent @@ -20,8 +20,18 @@ class InteractionEventHandler( coreFlow: MutableSharedFlow ) : BaseGatewayEventHandler(kord, gateway, cache, coreFlow) { override suspend fun handle(event: Event, shard: Int) { + when(event) { + is InteractionCreate -> handle(event, shard) + is ApplicationCommandCreate -> handle(event, shard) + is ApplicationCommandUpdate -> handle(event, shard) + is ApplicationCommandDelete -> handle(event, shard) + else -> Unit + } + + } + + private suspend fun handle(event: InteractionCreate, shard: Int) { - if(event !is InteractionCreate) return val data = InteractionData.from(event) val partial = PartialInteraction(data, kord) coreFlow.emit(PartialInteractionCreateEvent(partial, kord, shard)) @@ -30,6 +40,24 @@ class InteractionEventHandler( val interaction = Interaction(data, kord.resources.applicationId, kord) coreFlow.emit(InteractionCreateEvent(interaction, kord, shard)) } + } + + private suspend fun handle(event: ApplicationCommandCreate, shard: Int) { + val data = ApplicationCommandData.from(event.application) + val application = GuildApplicationCommand(data, data.guildId.value!!, kord.rest.interaction) + coreFlow.emit(ApplicationCommandCreateEvent(application, kord, shard)) + } + + + private suspend fun handle(event: ApplicationCommandUpdate, shard: Int) { + val data = ApplicationCommandData.from(event.application) + val application = GuildApplicationCommand(data, data.guildId.value!!, kord.rest.interaction) + coreFlow.emit(ApplicationCommandUpdateEvent(application, kord, shard)) + } + private suspend fun handle(event: ApplicationCommandDelete, shard: Int) { + val data = ApplicationCommandData.from(event.application) + val application = GuildApplicationCommand(data, data.guildId.value!!, kord.rest.interaction) + coreFlow.emit(ApplicationCommandDeleteEvent(application, kord, shard)) } } \ No newline at end of file diff --git a/gateway/src/main/kotlin/Event.kt b/gateway/src/main/kotlin/Event.kt index 1fec731c693e..2c4d3505b22b 100644 --- a/gateway/src/main/kotlin/Event.kt +++ b/gateway/src/main/kotlin/Event.kt @@ -365,6 +365,16 @@ sealed class Event { DiscordInteraction.serializer() ), sequence ) + "APPLICATION_COMMAND_CREATE" -> ApplicationCommandCreate( + decoder.decodeSerializableElement(descriptor, index, DiscordApplicationCommand.serializer()), sequence) + + "APPLICATION_COMMAND_UPDATE" -> ApplicationCommandUpdate( + decoder.decodeSerializableElement(descriptor, index, DiscordApplicationCommand.serializer()), sequence) + + "APPLICATION_COMMAND_DELETE" -> ApplicationCommandDelete( + decoder.decodeSerializableElement(descriptor, index, DiscordApplicationCommand.serializer()), sequence) + + else -> { jsonLogger.warn { "unknown gateway event name $name" } // consume json elements that are unknown to us @@ -603,3 +613,6 @@ data class WebhooksUpdate(val webhooksUpdateData: DiscordWebhooksUpdateData, ove DispatchEvent() data class InteractionCreate(val interaction: DiscordInteraction, override val sequence: Int?) : DispatchEvent() +data class ApplicationCommandCreate(val application: DiscordApplicationCommand, override val sequence: Int?) : DispatchEvent() +data class ApplicationCommandUpdate(val application: DiscordApplicationCommand, override val sequence: Int?) : DispatchEvent() +data class ApplicationCommandDelete(val application: DiscordApplicationCommand, override val sequence: Int?) : DispatchEvent() From ba566fa54ff8ce24f53402988197c21b724b75dd Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Fri, 25 Dec 2020 22:25:13 +0200 Subject: [PATCH 24/40] Add convenient builders and @KordPreview for slash commands --- common/src/main/kotlin/entity/Interactions.kt | 70 ++++++-- core/src/main/kotlin/SlashCommands.kt | 3 +- core/src/main/kotlin/Util.kt | 3 +- .../kotlin/behavior/InteractionBehavior.kt | 12 +- .../cache/data/ApplicationCommandData.kt | 8 +- .../entity/interaction/ApplicationCommand.kt | 7 +- .../entity/interaction/FollowupMessage.kt | 2 + .../kotlin/entity/interaction/Interaction.kt | 6 +- gateway/src/main/kotlin/Event.kt | 6 +- .../interaction/InteractionsBuilder.kt | 164 ++++++++---------- .../builder/interaction/OptionsBuilder.kt | 117 +++++++++++++ .../json/request/InteractionsRequests.kt | 29 +++- .../main/kotlin/service/InteractionService.kt | 3 +- 13 files changed, 297 insertions(+), 133 deletions(-) create mode 100644 rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index d000aa378671..0057fef5f268 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -3,15 +3,20 @@ package dev.kord.common.entity import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.OptionalSnowflake - import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.encodeStructure +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive @Serializable data class DiscordApplicationCommand( @@ -32,10 +37,17 @@ class ApplicationCommandOption( val description: String, val default: OptionalBoolean = OptionalBoolean.Missing, val required: OptionalBoolean = OptionalBoolean.Missing, - val choices: Optional> = Optional.Missing(), + val choices: Optional>> = Optional.Missing(), val options: Optional> = Optional.Missing() ) +object NotSerializable : KSerializer { + override fun deserialize(decoder: Decoder) = TODO("Not yet implemented") + override val descriptor: SerialDescriptor = String.serializer().descriptor + override fun serialize(encoder: Encoder, value: Any?) = TODO("Not yet implemented") +} + + @Serializable(ApplicationCommandOptionType.Serializer::class) sealed class ApplicationCommandOptionType(val type: Int) { @@ -79,11 +91,47 @@ sealed class ApplicationCommandOptionType(val type: Int) { } -@Serializable -data class DiscordApplicationCommandOptionChoice( - val name: String, - val value: String // mixed type int or string -) +@Serializable(Choice.ChoiceSerializer::class) +sealed class Choice { + abstract val name: String + abstract val value: T + + class IntChoice(override val name: String, override val value: Int) : Choice() + class StringChoice(override val name: String, override val value: String) : Choice() + internal class ChoiceSerializer(serializer: KSerializer) : KSerializer> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Choice") { + element("name") + element("value") + } + + override fun deserialize(decoder: Decoder): Choice<*> { + lateinit var name: String + lateinit var value: JsonPrimitive + val json = decoder as JsonDecoder + with(decoder.beginStructure(descriptor) as JsonDecoder) { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> name = decodeStringElement(descriptor, index) + 1 -> value = decodeJsonElement().jsonPrimitive + + CompositeDecoder.DECODE_DONE -> break + else -> throw SerializationException("unknown index: $index") + } + } + endStructure(descriptor) + } + return if(value.isString) StringChoice(name, value.toString()) else IntChoice(name, value.int) + } + + override fun serialize(encoder: Encoder, value: Choice<*>) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.name) + if (value is IntChoice) encodeIntElement(descriptor, 1, value.value) + else encodeStringElement(descriptor, 1, value.value.toString()) + } + } + } +} @Serializable data class DiscordInteraction( @@ -98,6 +146,7 @@ data class DiscordInteraction( val token: String, val version: Int ) + @Serializable(InteractionType.Serializer::class) sealed class InteractionType(val type: Int) { object Ping : InteractionType(1) @@ -125,6 +174,7 @@ sealed class InteractionType(val type: Int) { } } + @Serializable data class DiscordApplicationCommandInteractionData( val id: Snowflake, diff --git a/core/src/main/kotlin/SlashCommands.kt b/core/src/main/kotlin/SlashCommands.kt index dda7ebcd12eb..06f3f76c7401 100644 --- a/core/src/main/kotlin/SlashCommands.kt +++ b/core/src/main/kotlin/SlashCommands.kt @@ -1,5 +1,6 @@ package dev.kord.core +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.cache.data.ApplicationCommandData import dev.kord.core.entity.GlobalApplicationCommand @@ -9,7 +10,7 @@ import dev.kord.rest.builder.interaction.GuildApplicationCommandCreateBuilder import dev.kord.rest.service.InteractionService import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow - +@KordPreview class SlashCommands( private val applicationId: Snowflake, private val service: InteractionService diff --git a/core/src/main/kotlin/Util.kt b/core/src/main/kotlin/Util.kt index e59be3663fac..5aca9b0bf3c9 100644 --- a/core/src/main/kotlin/Util.kt +++ b/core/src/main/kotlin/Util.kt @@ -1,6 +1,7 @@ package dev.kord.core import dev.kord.common.entity.Snowflake +import dev.kord.core.entity.Entity import dev.kord.core.entity.KordEntity import dev.kord.core.event.Event import dev.kord.core.event.user.PresenceUpdateEvent @@ -72,7 +73,7 @@ internal inline fun catchDiscordError(vararg codes: JsonErrorCode, block: () -fun Flow.sorted(): Flow = flow { +fun Flow.sorted(): Flow = flow { for (entity in toList().sorted()) { emit(entity) } diff --git a/core/src/main/kotlin/behavior/InteractionBehavior.kt b/core/src/main/kotlin/behavior/InteractionBehavior.kt index c96047f38cc5..16447f4238a6 100644 --- a/core/src/main/kotlin/behavior/InteractionBehavior.kt +++ b/core/src/main/kotlin/behavior/InteractionBehavior.kt @@ -1,5 +1,6 @@ package dev.kord.core.behavior +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.InteractionResponseType import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.optional @@ -7,7 +8,7 @@ import dev.kord.core.Kord import dev.kord.core.entity.KordEntity import dev.kord.rest.builder.interaction.InteractionApplicationCommandCallbackDataBuilder import dev.kord.rest.json.request.InteractionResponseCreateRequest - +@KordPreview interface InteractionBehavior : KordEntity { //TODO("return full response with full functionality") @@ -28,7 +29,7 @@ interface InteractionBehavior : KordEntity { suspend fun respond( content: String, source: Boolean = false, - builder: InteractionApplicationCommandCallbackDataBuilder.() -> Unit + builder: InteractionApplicationCommandCallbackDataBuilder.() -> Unit = {} ): InteractionResponseBehavior { val type = if (source) InteractionResponseType.ChannelMessageWithSource else InteractionResponseType.ChannelMessage @@ -56,7 +57,7 @@ interface InteractionBehavior : KordEntity { get() = kord } } - +@KordPreview interface PartialInteractionBehavior : KordEntity { //TODO("return full response with data") val token: String @@ -72,10 +73,9 @@ interface PartialInteractionBehavior : KordEntity { suspend fun respond( content: String, source: Boolean = false, - builder: InteractionApplicationCommandCallbackDataBuilder.() -> Unit + builder: InteractionApplicationCommandCallbackDataBuilder.() -> Unit = {} ) { - val type = - if (source) InteractionResponseType.ChannelMessageWithSource else InteractionResponseType.ChannelMessage + val type = if (source) InteractionResponseType.ChannelMessageWithSource else InteractionResponseType.ChannelMessage val data = InteractionApplicationCommandCallbackDataBuilder(content).apply(builder).build() val request = InteractionResponseCreateRequest(type, data.optional()) kord.rest.interaction.createInteractionResponse(id, token, request) diff --git a/core/src/main/kotlin/cache/data/ApplicationCommandData.kt b/core/src/main/kotlin/cache/data/ApplicationCommandData.kt index 44a05fe8321b..fcecd6826665 100644 --- a/core/src/main/kotlin/cache/data/ApplicationCommandData.kt +++ b/core/src/main/kotlin/cache/data/ApplicationCommandData.kt @@ -1,5 +1,6 @@ package dev.kord.core.cache.data +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.* import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean @@ -8,6 +9,7 @@ import dev.kord.common.entity.optional.mapList import kotlinx.serialization.Serializable @Serializable +@KordPreview data class ApplicationCommandData( val id: Snowflake, val applicationId: Snowflake, @@ -32,6 +34,7 @@ data class ApplicationCommandData( } @Serializable +@KordPreview data class ApplicationCommandOptionData( val type: ApplicationCommandOptionType, val name: String, @@ -59,14 +62,15 @@ data class ApplicationCommandOptionData( } @Serializable +@KordPreview data class ApplicationCommandOptionChoiceData( val name: String, val value: String ) { companion object { - fun from(choice: DiscordApplicationCommandOptionChoice): ApplicationCommandOptionChoiceData { + fun from(choice: Choice<*>): ApplicationCommandOptionChoiceData { return with(choice) { - ApplicationCommandOptionChoiceData(name, value) + ApplicationCommandOptionChoiceData(name, value.toString()) } } } diff --git a/core/src/main/kotlin/entity/interaction/ApplicationCommand.kt b/core/src/main/kotlin/entity/interaction/ApplicationCommand.kt index da3adb69d3bc..d4fece83fdbe 100644 --- a/core/src/main/kotlin/entity/interaction/ApplicationCommand.kt +++ b/core/src/main/kotlin/entity/interaction/ApplicationCommand.kt @@ -1,5 +1,6 @@ package dev.kord.core.entity +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.ApplicationCommandOptionType import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.mapList @@ -9,7 +10,7 @@ import dev.kord.core.behavior.GuildApplicationCommandBehavior import dev.kord.core.cache.data.ApplicationCommandData import dev.kord.core.cache.data.ApplicationCommandOptionData import dev.kord.rest.service.InteractionService - +@KordPreview class GlobalApplicationCommand(val data: ApplicationCommandData, override val service: InteractionService) : GlobalApplicationCommandBehavior { override val id: Snowflake @@ -26,7 +27,7 @@ class GlobalApplicationCommand(val data: ApplicationCommandData, override val se } - +@KordPreview class GuildApplicationCommand( val data: ApplicationCommandData, override val guildId: Snowflake, @@ -46,7 +47,7 @@ class GuildApplicationCommand( } - +@KordPreview data class CommandOptions(val data: ApplicationCommandOptionData) { val type: ApplicationCommandOptionType get() = data.type val name: String get() = data.name diff --git a/core/src/main/kotlin/entity/interaction/FollowupMessage.kt b/core/src/main/kotlin/entity/interaction/FollowupMessage.kt index 858c451851ef..ed82b06e4099 100644 --- a/core/src/main/kotlin/entity/interaction/FollowupMessage.kt +++ b/core/src/main/kotlin/entity/interaction/FollowupMessage.kt @@ -1,5 +1,6 @@ package dev.kord.core.entity +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.MessageType import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.orEmpty @@ -27,6 +28,7 @@ import java.util.* /** * An instance of a [Discord Message][https://discord.com/developers/docs/resources/channel#message-object]. */ +@KordPreview class FollowupMessage( val data: MessageData, override val token: String, diff --git a/core/src/main/kotlin/entity/interaction/Interaction.kt b/core/src/main/kotlin/entity/interaction/Interaction.kt index 2884bb2c7bc7..993029d4c33c 100644 --- a/core/src/main/kotlin/entity/interaction/Interaction.kt +++ b/core/src/main/kotlin/entity/interaction/Interaction.kt @@ -1,5 +1,6 @@ package dev.kord.core.entity.interaction +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.InteractionType import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.orEmpty @@ -11,7 +12,7 @@ import dev.kord.core.cache.data.ApplicationCommandInteractionData import dev.kord.core.cache.data.ApplicationCommandInteractionDataOptionData import dev.kord.core.cache.data.InteractionData import dev.kord.core.entity.Entity - +@KordPreview class PartialInteraction(val data: InteractionData, override val kord: Kord) : PartialInteractionBehavior { override val id: Snowflake get() = data.id @@ -29,7 +30,7 @@ class PartialInteraction(val data: InteractionData, override val kord: Kord) : P val version: Int get() = data.version } - +@KordPreview class Interaction(val data: InteractionData, override val applicationId: Snowflake, override val kord: Kord) : InteractionBehavior { @@ -55,7 +56,6 @@ class Interaction(val data: InteractionData, override val applicationId: Snowfla class Command(val data: ApplicationCommandInteractionData) : Entity { override val id: Snowflake get() = data.id - val name = data.name val parameters = data.options.orEmpty().map { Parameter(it) } diff --git a/gateway/src/main/kotlin/Event.kt b/gateway/src/main/kotlin/Event.kt index 2c4d3505b22b..55bfdd075b70 100644 --- a/gateway/src/main/kotlin/Event.kt +++ b/gateway/src/main/kotlin/Event.kt @@ -1,5 +1,6 @@ package dev.kord.gateway +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.* import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean @@ -611,8 +612,11 @@ data class VoiceServerUpdate(val voiceServerUpdateData: DiscordVoiceServerUpdate data class WebhooksUpdate(val webhooksUpdateData: DiscordWebhooksUpdateData, override val sequence: Int?) : DispatchEvent() - +@KordPreview data class InteractionCreate(val interaction: DiscordInteraction, override val sequence: Int?) : DispatchEvent() +@KordPreview data class ApplicationCommandCreate(val application: DiscordApplicationCommand, override val sequence: Int?) : DispatchEvent() +@KordPreview data class ApplicationCommandUpdate(val application: DiscordApplicationCommand, override val sequence: Int?) : DispatchEvent() +@KordPreview data class ApplicationCommandDelete(val application: DiscordApplicationCommand, override val sequence: Int?) : DispatchEvent() diff --git a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt index e4986a065263..f03c20cbd667 100644 --- a/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt +++ b/rest/src/main/kotlin/builder/interaction/InteractionsBuilder.kt @@ -1,9 +1,6 @@ package dev.kord.rest.builder.interaction -import dev.kord.common.entity.ApplicationCommandOption -import dev.kord.common.entity.ApplicationCommandOptionType -import dev.kord.common.entity.DiscordApplicationCommandOptionChoice -import dev.kord.common.entity.InteractionResponseType +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.delegate.delegate @@ -20,26 +17,14 @@ import java.nio.file.Path import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract - +@KordPreview class GlobalApplicationCommandCreateBuilder( val name: String, val description: String -) : RequestBuilder { - - private var _options: Optional> = Optional.Missing() - private var options: MutableList? by ::_options.delegate() - - //TODO("check if desc can be empty") - fun option( - type: ApplicationCommandOptionType, - name: String, - description: String, - builder: ApplicationCommandOptionBuilder.() -> Unit = {} - ) { - if (options == null) options = mutableListOf() - val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) - options!!.add(option) - } +) : RequestBuilder, BaseApplicationBuilder() { + + private var _options: Optional> = Optional.Missing() + override var options: MutableList? by ::_options.delegate() override fun toRequest(): GlobalApplicationCommandCreateRequest { return GlobalApplicationCommandCreateRequest(name, description, _options.mapList { it.toRequest() }) @@ -48,56 +33,74 @@ class GlobalApplicationCommandCreateBuilder( } - -class GlobalApplicationCommandModifyBuilder : RequestBuilder { +@KordPreview +class GlobalApplicationCommandModifyBuilder : RequestBuilder, + BaseApplicationBuilder() { private var _name: Optional = Optional.Missing() var name: String? by ::_name.delegate() private var _description: Optional = Optional.Missing() var description: String? by ::_name.delegate() + private var _options: Optional> = Optional.Missing() + override var options: MutableList? by ::_options.delegate() + override fun toRequest(): GlobalApplicationCommandModifyRequest { + return GlobalApplicationCommandModifyRequest(_name, _description, _options.mapList { it.toRequest() }) + } + - private var _options: Optional> = Optional.Missing() - private var options: MutableList? by ::_options.delegate() +} +@KordPreview +sealed class BaseApplicationBuilder { + protected abstract var options: MutableList? - //TODO("check if desc can be empty") - fun option( - type: ApplicationCommandOptionType, - name: String, - description: String, - builder: ApplicationCommandOptionBuilder.() -> Unit = {} - ) { + fun boolean(name: String, description: String) { if (options == null) options = mutableListOf() - val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) - options!!.add(option) + options!!.add(BooleanBuilder(name, description)) } - override fun toRequest(): GlobalApplicationCommandModifyRequest { - return GlobalApplicationCommandModifyRequest(_name, _description, _options.mapList { it.toRequest() }) + fun int(name: String, description: String, builder: IntChoiceBuilder.() -> Unit = {}) { + if (options == null) options = mutableListOf() + options!!.add(IntChoiceBuilder(name, description).apply(builder)) + } + fun string(name: String, description: String, builder: StringChoiceBuilder.() -> Unit = {}) { + if (options == null) options = mutableListOf() + options!!.add(StringChoiceBuilder(name, description).apply(builder)) } -} + fun group(name: String, description: String, builder: GroupCommandBuilder.() -> Unit) { + if (options == null) options = mutableListOf() + options!!.add(GroupCommandBuilder(name, description).apply(builder)) + } + + fun subCommand(name: String, description: String, builder: SubCommandBuilder.() -> Unit = {}) { + if (options == null) options = mutableListOf() + options!!.add(SubCommandBuilder(name, description).apply(builder)) + } + + fun role(name: String, description: String) { + if (options == null) options = mutableListOf() + options!!.add(RoleBuilder(name, description)) + } + fun user(name: String, description: String) { + if (options == null) options = mutableListOf() + options!!.add(UserBuilder(name, description)) + } + + fun channel(name: String, description: String) { + if (options == null) options = mutableListOf() + options!!.add(ChannelBuilder(name, description)) + } +} +@KordPreview class GuildApplicationCommandCreateBuilder( val name: String, val description: String -) : RequestBuilder { - - private var _options: Optional> = Optional.Missing() - private var options: MutableList? by ::_options.delegate() - - //TODO("check if desc can be empty") - fun option( - type: ApplicationCommandOptionType, - name: String, - description: String, - builder: ApplicationCommandOptionBuilder.() -> Unit = {} - ) { - if (options == null) options = mutableListOf() - val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) - options!!.add(option) - } +) : RequestBuilder, BaseApplicationBuilder() { + private var _options: Optional> = Optional.Missing() + override var options: MutableList? by ::_options.delegate() override fun toRequest(): GuildApplicationCommandCreateRequest { return GuildApplicationCommandCreateRequest(name, description, _options.mapList { it.toRequest() }) @@ -106,30 +109,21 @@ class GuildApplicationCommandCreateBuilder( } - -class GuildApplicationCommandModifyBuilder : RequestBuilder { +@KordPreview +class GuildApplicationCommandModifyBuilder : BaseApplicationBuilder(), + RequestBuilder { private var _name: Optional = Optional.Missing() var name: String? by ::_name.delegate() + private var _description: Optional = Optional.Missing() var description: String? by ::_name.delegate() - private var _options: Optional> = Optional.Missing() - private var options: MutableList? by ::_options.delegate() + private var _options: Optional> = Optional.Missing() + override var options: MutableList? by ::_options.delegate() - //TODO("check if desc can be empty") - fun option( - type: ApplicationCommandOptionType, - name: String, - description: String, - builder: ApplicationCommandOptionBuilder.() -> Unit = {} - ) { - if (options == null) options = mutableListOf() - val option = ApplicationCommandOptionBuilder(type, name, description).apply(builder) - options!!.add(option) - } override fun toRequest(): GuildApplicationCommandModifyRequest { return GuildApplicationCommandModifyRequest(_name, _description, _options.mapList { it.toRequest() }) @@ -138,30 +132,8 @@ class GuildApplicationCommandModifyBuilder : RequestBuilder> = Optional.Missing() - private var choices: MutableList? by ::_choices.delegate() - - //TODO("Express types in a convenient way.") - fun choice(name: String, value: () -> String) { - if (choices == null) choices = mutableListOf() - choices!!.add(DiscordApplicationCommandOptionChoice(name, value())) - } - - fun toRequest() = ApplicationCommandOption(type, name, description, _default, _required, _choices) -} - -class OriginalInteractionResponseModifyBuilder() : +@KordPreview +class OriginalInteractionResponseModifyBuilder : RequestBuilder { private var _content: Optional = Optional.Missing() var content: String? by ::_content.delegate() @@ -185,7 +157,7 @@ class OriginalInteractionResponseModifyBuilder() : _allowedMentions.map { it.build() }) } } - +@KordPreview class FollowupMessageModifyBuilder : RequestBuilder { private var _content: Optional = Optional.Missing() @@ -210,7 +182,7 @@ class FollowupMessageModifyBuilder : _allowedMentions.map { it.build() }) } } - +@KordPreview class InteractionApplicationCommandCallbackDataBuilder(var content: String) { private var _tts: OptionalBoolean = OptionalBoolean.Missing @@ -227,7 +199,7 @@ class InteractionApplicationCommandCallbackDataBuilder(var content: String) { embeds!! += EmbedBuilder().apply(builder) } - fun build(): InteractionApplicationCommandCallbackData { + fun build(): InteractionApplicationCommandCallbackData { return InteractionApplicationCommandCallbackData( _tts, @@ -238,7 +210,7 @@ class InteractionApplicationCommandCallbackDataBuilder(var content: String) { } } - +@KordPreview class FollowupMessageCreateBuilder : RequestBuilder { private var _content: Optional = Optional.Missing() diff --git a/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt b/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt new file mode 100644 index 000000000000..dbab394645bf --- /dev/null +++ b/rest/src/main/kotlin/builder/interaction/OptionsBuilder.kt @@ -0,0 +1,117 @@ +package dev.kord.rest.builder.interaction + +import dev.kord.common.annotation.KordPreview +import dev.kord.common.entity.ApplicationCommandOption +import dev.kord.common.entity.ApplicationCommandOptionType +import dev.kord.common.entity.Choice +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean +import dev.kord.common.entity.optional.delegate.delegate +import dev.kord.common.entity.optional.mapList +import dev.kord.rest.builder.RequestBuilder +@KordPreview +sealed class OptionsBuilder(val name: String, val description: String, val type: ApplicationCommandOptionType) : + RequestBuilder { + private var _default: OptionalBoolean = OptionalBoolean.Missing + var default: Boolean? by ::_default.delegate() + + private var _required: OptionalBoolean = OptionalBoolean.Missing + var required: Boolean? by ::_required.delegate() + + override fun toRequest(): ApplicationCommandOption { + return ApplicationCommandOption(type, name, description, _default, _required) + } +} + +@KordPreview +sealed class BaseChoiceBuilder(name: String, description: String, type: ApplicationCommandOptionType) : + OptionsBuilder(name, description, type) { + private var _choices: Optional>> = Optional.Missing() + var choices: MutableList>? by ::_choices.delegate() + + abstract fun choice(name: String, value: () -> T) + + override fun toRequest(): ApplicationCommandOption { + return ApplicationCommandOption(type, name, description, choices = _choices) + } + +} +@KordPreview +class IntChoiceBuilder(name: String, description: String) : + BaseChoiceBuilder(name, description, ApplicationCommandOptionType.Integer) { + + override fun choice(name: String, value: () -> Int) { + if (choices == null) choices = mutableListOf() + choices!!.add(Choice.IntChoice(name, value())) + } +} + +@KordPreview +class StringChoiceBuilder(name: String, description: String) : + BaseChoiceBuilder(name, description, ApplicationCommandOptionType.String) { + + override fun choice(name: String, value: () -> String) { + if (choices == null) choices = mutableListOf() + choices!!.add(Choice.StringChoice(name, value())) + } +} +@KordPreview +class BooleanBuilder(name: String, description: String) : + OptionsBuilder(name, description, ApplicationCommandOptionType.Boolean) +@KordPreview +class UserBuilder(name: String, description: String) : + OptionsBuilder(name, description, ApplicationCommandOptionType.User) +@KordPreview +class RoleBuilder(name: String, description: String) : + OptionsBuilder(name, description, ApplicationCommandOptionType.Role) +@KordPreview +class ChannelBuilder(name: String, description: String) : + OptionsBuilder(name, description, ApplicationCommandOptionType.Channel) +@KordPreview +sealed class BaseCommandOptionBuilder(name: String, description: String, type: ApplicationCommandOptionType): OptionsBuilder(name, description,type) { + private var _options: Optional> = Optional.Missing() + protected var options by ::_options.delegate() + + override fun toRequest(): ApplicationCommandOption { + return ApplicationCommandOption(type, name, description, options = _options.mapList { it.toRequest() }) + } +} +@KordPreview +class SubCommandBuilder(name: String, description: String): BaseCommandOptionBuilder(name,description, ApplicationCommandOptionType.SubCommand) { + fun boolean(name: String, description: String) { + if (options == null) options = mutableListOf() + options!!.add(BooleanBuilder(name, description)) + } + + fun int(name: String, description: String, builder: IntChoiceBuilder.() -> Unit = {}) { + if (options == null) options = mutableListOf() + options!!.add(IntChoiceBuilder(name, description).apply(builder)) + } + + fun string(name: String, description: String, builder: StringChoiceBuilder.() -> Unit = {}) { + if (options == null) options = mutableListOf() + options!!.add(StringChoiceBuilder(name, description).apply(builder)) + } + + fun role(name: String, description: String) { + if (options == null) options = mutableListOf() + options!!.add(RoleBuilder(name, description)) + } + + fun user(name: String, description: String) { + if (options == null) options = mutableListOf() + options!!.add(UserBuilder(name, description)) + } + + fun channel(name: String, description: String) { + if (options == null) options = mutableListOf() + options!!.add(ChannelBuilder(name, description)) + } +} +@KordPreview +class GroupCommandBuilder(name: String, description: String): BaseCommandOptionBuilder(name, description, ApplicationCommandOptionType.SubCommandGroup) { + fun subCommand(name: String, description: String, builder: SubCommandBuilder.() -> Unit) { + 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 e3ea5265416a..1aecdddaa3a1 100644 --- a/rest/src/main/kotlin/json/request/InteractionsRequests.kt +++ b/rest/src/main/kotlin/json/request/InteractionsRequests.kt @@ -1,5 +1,6 @@ package dev.kord.rest.json.request +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.AllowedMentions import dev.kord.common.entity.ApplicationCommandOption import dev.kord.common.entity.InteractionResponseType @@ -10,6 +11,7 @@ import kotlinx.serialization.Serializable import java.io.InputStream @Serializable +@KordPreview data class GlobalApplicationCommandCreateRequest( val name: String, val description: String, @@ -17,13 +19,15 @@ data class GlobalApplicationCommandCreateRequest( ) @Serializable +@KordPreview data class GlobalApplicationCommandModifyRequest( - val name: Optional, - val description: Optional, - val options: Optional> + val name: Optional = Optional.Missing(), + val description: Optional = Optional.Missing(), + val options: Optional> = Optional.Missing() ) @Serializable +@KordPreview data class GuildApplicationCommandCreateRequest( val name: String, val description: String, @@ -31,28 +35,32 @@ data class GuildApplicationCommandCreateRequest( ) @Serializable +@KordPreview data class GuildApplicationCommandModifyRequest( - val name: Optional, - val description: Optional, - val options: Optional> + val name: Optional = Optional.Missing(), + val description: Optional = Optional.Missing(), + val options: Optional> = Optional.Missing() ) @Serializable +@KordPreview data class OriginalInteractionResponseModifyRequest( - val content: Optional, - val embeds: Optional>, + val content: Optional = Optional.Missing(), + val embeds: Optional> = Optional.Missing() , @SerialName("allowed_mentions") - val allowedMentions: Optional, + val allowedMentions: Optional = Optional.Missing(), ) @Serializable +@KordPreview data class InteractionResponseCreateRequest( val type: InteractionResponseType, val data: Optional = Optional.Missing() ) @Serializable +@KordPreview class InteractionApplicationCommandCallbackData( val tts: OptionalBoolean = OptionalBoolean.Missing, val content: String, @@ -60,12 +68,14 @@ class InteractionApplicationCommandCallbackData( val allowedMentions: Optional = Optional.Missing() ) +@KordPreview data class MultipartFollowupMessageCreateRequest( val request: FollowupMessageCreateRequest, val file: Pair? ) @Serializable +@KordPreview class FollowupMessageCreateRequest( val content: Optional = Optional.Missing(), val username: Optional = Optional.Missing(), @@ -77,6 +87,7 @@ class FollowupMessageCreateRequest( ) @Serializable +@KordPreview data class FollowupMessageModifyRequest( val content: Optional, val embeds: Optional>, diff --git a/rest/src/main/kotlin/service/InteractionService.kt b/rest/src/main/kotlin/service/InteractionService.kt index 8512f125bb6f..b2a7a265c074 100644 --- a/rest/src/main/kotlin/service/InteractionService.kt +++ b/rest/src/main/kotlin/service/InteractionService.kt @@ -1,11 +1,12 @@ package dev.kord.rest.service +import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.DiscordApplicationCommand import dev.kord.common.entity.Snowflake import dev.kord.rest.json.request.* import dev.kord.rest.request.RequestHandler import dev.kord.rest.route.Route - +@KordPreview class InteractionService(requestHandler: RequestHandler) : RestService(requestHandler) { suspend fun getGlobalApplicationCommands(applicationId: Snowflake): List = call(Route.GlobalApplicationCommandsGet) { From d85db9ca2ed34ad7d02a3391a9dd0fd9269846b5 Mon Sep 17 00:00:00 2001 From: HopeBaron Date: Fri, 1 Jan 2021 15:15:41 +0200 Subject: [PATCH 25/40] [WIP] - Options core representation [skip ci] --- common/src/main/kotlin/entity/Interactions.kt | 186 ++++++++++++++---- .../main/kotlin/entity/optional/Optional.kt | 13 ++ core/src/main/kotlin/SlashCommands.kt | 4 +- .../behavior/FollowupMessageBehavior.kt | 21 +- .../GlobalApplicationCommandBehavior.kt | 4 +- .../kotlin/behavior/InteractionBehavior.kt | 165 ++++++++++++---- .../behavior/InteractionResponseBehavior.kt | 33 ++-- .../main/kotlin/cache/data/InteractionData.kt | 34 ++-- .../entity/interaction/ApplicationCommand.kt | 2 +- .../entity/interaction/FollowupMessage.kt | 3 +- .../kotlin/entity/interaction/Interaction.kt | 46 +++-- .../interaction/ApplicationCommandCreate.kt | 2 +- .../interaction/ApplicationCommandDelete.kt | 2 +- .../interaction/ApplicationCommandUpdate.kt | 2 +- .../handler/InteractionEventHandler.kt | 9 +- .../interaction/InteractionsBuilder.kt | 10 + 16 files changed, 401 insertions(+), 135 deletions(-) diff --git a/common/src/main/kotlin/entity/Interactions.kt b/common/src/main/kotlin/entity/Interactions.kt index 0057fef5f268..3f0e0d5bc94a 100644 --- a/common/src/main/kotlin/entity/Interactions.kt +++ b/common/src/main/kotlin/entity/Interactions.kt @@ -1,22 +1,21 @@ package dev.kord.common.entity -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.* 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.CompositeDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.encodeStructure -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.* +import mu.KotlinLogging + +val kordLogger = KotlinLogging.logger { } @Serializable data class DiscordApplicationCommand( @@ -27,7 +26,7 @@ data class DiscordApplicationCommand( val description: String, @SerialName("guild_id") val guildId: OptionalSnowflake = OptionalSnowflake.Missing, - val options: Optional> = Optional.Missing() + val options: Optional> = Optional.Missing(), ) @Serializable @@ -37,11 +36,15 @@ class ApplicationCommandOption( val description: String, val default: OptionalBoolean = OptionalBoolean.Missing, val required: OptionalBoolean = OptionalBoolean.Missing, + /* + We don't care about serializer type. + [Choice] has it's own [ChoiceSerializer]. [NotSerializable] is a no-op serializer that h + */ val choices: Optional>> = Optional.Missing(), - val options: Optional> = Optional.Missing() + val options: Optional> = Optional.Missing(), ) -object NotSerializable : KSerializer { +internal object NotSerializable : KSerializer { override fun deserialize(decoder: Decoder) = TODO("Not yet implemented") override val descriptor: SerialDescriptor = String.serializer().descriptor override fun serialize(encoder: Encoder, value: Any?) = TODO("Not yet implemented") @@ -60,17 +63,17 @@ sealed class ApplicationCommandOptionType(val type: Int) { object User : ApplicationCommandOptionType(6) object Channel : ApplicationCommandOptionType(7) object Role : ApplicationCommandOptionType(8) - object Unknown : ApplicationCommandOptionType(Int.MAX_VALUE) + class Unknown(type: Int) : ApplicationCommandOptionType(type) - companion object + companion object; - object Serializer : KSerializer { + internal object Serializer : KSerializer { override val descriptor: SerialDescriptor get() = PrimitiveSerialDescriptor("ApplicationCommandOptionType", PrimitiveKind.INT) override fun deserialize(decoder: Decoder): ApplicationCommandOptionType { - return when (decoder.decodeInt()) { + return when (val type = decoder.decodeInt()) { 1 -> SubCommand 2 -> SubCommandGroup 3 -> String @@ -79,7 +82,7 @@ sealed class ApplicationCommandOptionType(val type: Int) { 6 -> User 7 -> Channel 8 -> Role - else -> Unknown + else -> Unknown(type) } } @@ -120,7 +123,7 @@ sealed class Choice { } endStructure(descriptor) } - return if(value.isString) StringChoice(name, value.toString()) else IntChoice(name, value.int) + return if (value.isString) StringChoice(name, value.toString()) else IntChoice(name, value.int) } override fun serialize(encoder: Encoder, value: Choice<*>) { @@ -144,27 +147,26 @@ data class DiscordInteraction( val channelId: Snowflake, val member: DiscordGuildMember, val token: String, - val version: Int + val version: Int, ) @Serializable(InteractionType.Serializer::class) sealed class InteractionType(val type: Int) { object Ping : InteractionType(1) object ApplicationCommand : InteractionType(2) - object Unknown : InteractionType(Int.MAX_VALUE) + class Unknown(type: Int) : InteractionType(type) - companion object - - object Serializer : KSerializer { + companion object; + internal object Serializer : KSerializer { override val descriptor: SerialDescriptor get() = PrimitiveSerialDescriptor("InteractionType", PrimitiveKind.INT) override fun deserialize(decoder: Decoder): InteractionType { - return when (decoder.decodeInt()) { + return when (val type = decoder.decodeInt()) { 1 -> Ping 2 -> ApplicationCommand - else -> Unknown + else -> Unknown(type) } } @@ -179,20 +181,134 @@ sealed class InteractionType(val type: Int) { data class DiscordApplicationCommandInteractionData( val id: Snowflake, val name: String, - val options: Optional> = Optional.Missing() + val options: Optional> = Optional.Missing(), +) { + companion object; + internal object Serializer : KSerializer { + override val descriptor: SerialDescriptor + get() = TODO("Not yet implemented") + + override fun deserialize(decoder: Decoder): DiscordApplicationCommandInteractionData { + TODO("Not yet implemented") + } + + override fun serialize(encoder: Encoder, value: DiscordApplicationCommandInteractionData) { + TODO("Not yet implemented") + } + + } +} + +@Serializable +data class SubCommand(val name: String, val options: Optional> = Optional.Missing()) + +@Serializable +data class SubCommandOption( + val name: String, + @SerialName("value") + val value: Optional> = Optional.Missing(), ) @Serializable -data class DiscordApplicationCommandInteractionDataOption( +data class CommandGroup( val name: String, - val value: Optional = Optional.Missing(), - val options: Optional> = Optional.Missing() + @SerialName("options") + val subCommands: Optional> = Optional.Missing(), ) +@Serializable(OptionValue.OptionValueSerializer::class) +sealed class OptionValue(val value: T) { + class IntValue(value: Int) : OptionValue(value) + class StringValue(value: String) : OptionValue(value) + class BooleanValue(value: Boolean) : OptionValue(value) + + companion object; + internal class OptionValueSerializer(serializer: KSerializer) : KSerializer> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("OptionValue", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): OptionValue<*> { + val value = (decoder as JsonDecoder).decodeJsonElement().jsonPrimitive + return when { + value.isString -> StringValue(value.toString()) + value.booleanOrNull != null -> BooleanValue(value.boolean) + else -> IntValue(value.int) + } + } + + override fun serialize(encoder: Encoder, value: OptionValue<*>) { + when (value) { + is IntValue -> encoder.encodeInt(value.value) + is StringValue -> encoder.encodeString(value.value) + } + } + } +} + +@Serializable(Option.Serializer::class) +data class Option( + val name: String, + val value: Optional> = Optional.Missing(), + val groups: Optional> = Optional.Missing(), + val subCommands: Optional> = Optional.Missing(), +) { + internal object Serializer : KSerializer