Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forms support #531

Merged
merged 33 commits into from
Feb 20, 2022
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4016de0
Initial modals implementation
MrPowerGamerBR Feb 9, 2022
08a7cac
Change the "DiscordComponent" model to multiple classes due to button…
MrPowerGamerBR Feb 9, 2022
2b1f43a
Canonical order
MrPowerGamerBR Feb 9, 2022
89e1e30
Use two ***DiscordComponents classes for everything
MrPowerGamerBR Feb 9, 2022
7c39464
Rename the classes because it looks better this way
MrPowerGamerBR Feb 9, 2022
3856eee
Revert core changes
MrPowerGamerBR Feb 9, 2022
8d03e60
Implement text input component classes in the core module
MrPowerGamerBR Feb 9, 2022
8223e43
Fix TextInput component type deserialization
MrPowerGamerBR Feb 9, 2022
50a837e
improve the interaction tree
HopeBaron Feb 10, 2022
ec873d5
More changes to the tree
HopeBaron Feb 10, 2022
9eccb31
remove unresolved references
HopeBaron Feb 10, 2022
769c8d6
add core events
HopeBaron Feb 10, 2022
660f477
Apply suggestions from code review
HopeBaron Feb 11, 2022
eadce4d
change compontents top level to ActionRowBuilder
HopeBaron Feb 12, 2022
fa8a415
refactor files
HopeBaron Feb 12, 2022
a5a8272
refactor interaction files
HopeBaron Feb 12, 2022
f810972
refactor response files
HopeBaron Feb 12, 2022
992fac9
refactor followup messages
HopeBaron Feb 12, 2022
4b96ef5
add TextInputComponent list retrival property
HopeBaron Feb 12, 2022
edd6ff0
fix withStrategy return type
HopeBaron Feb 12, 2022
5e60f60
Apply suggestions from code review
HopeBaron Feb 12, 2022
81eab28
rename discord modal component to DiscordTextInputComponent
HopeBaron Feb 12, 2022
afab86b
fix interaction response type
HopeBaron Feb 12, 2022
8f705e7
Forms support adjustments (#534)
lukellmann Feb 12, 2022
8ae546f
GuildAutoCompleteInteraction is GuildInteraction (#535)
lukellmann Feb 12, 2022
5d9be2c
required is true by default for text inputs (#537)
lukellmann Feb 15, 2022
39d7608
Merge branch '0.8.x' into feature/forms
HopeBaron Feb 17, 2022
71159e9
rewrite a safer OptionValue implementation
HopeBaron Feb 17, 2022
a63e10f
Apply code review suggestions
HopeBaron Feb 18, 2022
0fb59bc
Yeet out IntOptionValue
HopeBaron Feb 18, 2022
9838aef
Fix interactions for forms feature (#538)
lukellmann Feb 19, 2022
a5bb146
cleanup and consider feedback in codereview
HopeBaron Feb 20, 2022
5f13f84
reintroduce CommandArgument convience methods
HopeBaron Feb 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 132 additions & 20 deletions common/src/main/kotlin/entity/DiscordComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

/**
* Represent a [intractable component within a message sent in Discord](https://discord.com/developers/docs/interactions/message-components#what-are-components).
* Represent a [interactable component within a message sent in Discord](https://discord.com/developers/docs/interactions/message-components#what-are-components).
*
* @property type the [ComponentType] of the component
* @property style the [ButtonStyle] of the component (if it is a button)
* @property style the text that appears on the button (if the component is a button)
* @property emoji an [DiscordPartialEmoji] that appears on the button (if the component is a button)
* @property customId a developer-defined identifier for the button, max 100 characters
* @property url a url for link-style buttons
Expand All @@ -27,36 +31,103 @@ import kotlinx.serialization.encoding.Encoder
* @property placeholder the placeholder text for the select menu
* @property minValues the minimum amount of [options] allowed
* @property maxValues the maximum amount of [options] allowed
* @property minLength the minimum input length for a text input, min 0, max 4000.
* @property maxLength the maximum input length for a text input, min 1, max 4000.
* @property required whether this component is required to be filled, default false.
* @property value a pre-filled value for this component, max 4000 characters.
*/
@Serializable(with = DiscordComponent.Serializer::class)
public sealed class DiscordComponent {
HopeBaron marked this conversation as resolved.
Show resolved Hide resolved
public abstract val type: ComponentType
public abstract val label: Optional<String>
public abstract val emoji: Optional<DiscordPartialEmoji>
@SerialName("custom_id")
public abstract val customId: Optional<String>
public abstract val url: Optional<String>
public abstract val disabled: OptionalBoolean
public abstract val components: Optional<List<DiscordComponent>>
public abstract val options: Optional<List<DiscordSelectOption>>
public abstract val placeholder: Optional<String>
@SerialName("min_values")
public abstract val minValues: OptionalInt
@SerialName("max_values")
public abstract val maxValues: OptionalInt
@SerialName("min_length")
public abstract val minLength: OptionalInt
@SerialName("max_length")
public abstract val maxLength: OptionalInt
public abstract val required: OptionalBoolean
public abstract val value: Optional<String>

internal object Serializer : JsonContentPolymorphicSerializer<DiscordComponent>(DiscordComponent::class) {
override fun selectDeserializer(element: JsonElement): KSerializer<out DiscordComponent> {
val componentType = element.jsonObject["type"]?.jsonPrimitive?.intOrNull ?: error("Missing component type ID!")

return when (componentType) {
ComponentType.TextInput.value -> DiscordTextInputComponent.serializer()
else -> DiscordChatComponent.serializer()
}
}
}
}

@Serializable
public data class DiscordChatComponent(
override val type: ComponentType,
val style: Optional<ButtonStyle> = Optional.Missing(),
override val label: Optional<String> = Optional.Missing(),
override val emoji: Optional<DiscordPartialEmoji> = Optional.Missing(),
@SerialName("custom_id")
override val customId: Optional<String> = Optional.Missing(),
override val url: Optional<String> = Optional.Missing(),
override val disabled: OptionalBoolean = OptionalBoolean.Missing,
override val components: Optional<List<DiscordComponent>> = Optional.Missing(),
override val options: Optional<List<DiscordSelectOption>> = Optional.Missing(),
override val placeholder: Optional<String> = Optional.Missing(),
@SerialName("min_values")
override val minValues: OptionalInt = OptionalInt.Missing,
@SerialName("max_values")
override val maxValues: OptionalInt = OptionalInt.Missing,
@SerialName("min_length")
override val minLength: OptionalInt = OptionalInt.Missing,
@SerialName("max_length")
override val maxLength: OptionalInt = OptionalInt.Missing,
override val required: OptionalBoolean = OptionalBoolean.Missing,
override val value: Optional<String> = Optional.Missing()
) : DiscordComponent()

@Serializable
public data class DiscordComponent(
val type: ComponentType,
val style: Optional<ButtonStyle> = Optional.Missing(),
val label: Optional<String> = Optional.Missing(),
val emoji: Optional<DiscordPartialEmoji> = Optional.Missing(),
public data class DiscordTextInputComponent(
override val type: ComponentType,
public val style: Optional<TextInputStyle> = Optional.Missing(),
override val label: Optional<String> = Optional.Missing(),
override val emoji: Optional<DiscordPartialEmoji> = Optional.Missing(),
@SerialName("custom_id")
val customId: Optional<String> = Optional.Missing(),
val url: Optional<String> = Optional.Missing(),
val disabled: OptionalBoolean = OptionalBoolean.Missing,
val components: Optional<List<DiscordComponent>> = Optional.Missing(),
val options: Optional<List<DiscordSelectOption>> = Optional.Missing(),
val placeholder: Optional<String> = Optional.Missing(),
override val customId: Optional<String> = Optional.Missing(),
override val url: Optional<String> = Optional.Missing(),
override val disabled: OptionalBoolean = OptionalBoolean.Missing,
override val components: Optional<List<DiscordComponent>> = Optional.Missing(),
override val options: Optional<List<DiscordSelectOption>> = Optional.Missing(),
override val placeholder: Optional<String> = Optional.Missing(),
@SerialName("min_values")
val minValues: OptionalInt = OptionalInt.Missing,
override val minValues: OptionalInt = OptionalInt.Missing,
@SerialName("max_values")
val maxValues: OptionalInt = OptionalInt.Missing,
)
override val maxValues: OptionalInt = OptionalInt.Missing,
@SerialName("min_length")
override val minLength: OptionalInt = OptionalInt.Missing,
@SerialName("max_length")
override val maxLength: OptionalInt = OptionalInt.Missing,
override val required: OptionalBoolean = OptionalBoolean.Missing,
override val value: Optional<String> = Optional.Missing()
) : DiscordComponent()

/**
* Representation of different [DiscordComponent] types.
*
* @property value the raw type value used by the Discord API
*/

@Serializable(with = ComponentType.Serializer::class)
public sealed class ComponentType(public val value: Int) {

/**
* Fallback type used for types that haven't been added to Kord yet.
*/
Expand All @@ -77,6 +148,11 @@ public sealed class ComponentType(public val value: Int) {
*/
public object SelectMenu : ComponentType(3)

/**
* A text input object.
*/
public object TextInput : ComponentType(4)

public companion object Serializer : KSerializer<ComponentType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ComponentType", PrimitiveKind.INT)

Expand All @@ -85,6 +161,7 @@ public sealed class ComponentType(public val value: Int) {
1 -> ActionRow
2 -> Button
3 -> SelectMenu
4 -> TextInput
else -> Unknown(value)
}

Expand All @@ -99,7 +176,6 @@ public sealed class ComponentType(public val value: Int) {
*
* @see ComponentType.Button
*/

@Serializable(with = ButtonStyle.Serializer::class)
public sealed class ButtonStyle(public val value: Int) {

Expand Down Expand Up @@ -139,7 +215,7 @@ public sealed class ButtonStyle(public val value: Int) {
public object Link : ButtonStyle(5)

public companion object Serializer : KSerializer<ButtonStyle> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ButtonStyle", PrimitiveKind.INT)
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Button", PrimitiveKind.INT)

override fun deserialize(decoder: Decoder): ButtonStyle =
when (val value = decoder.decodeInt()) {
Expand All @@ -154,3 +230,39 @@ public sealed class ButtonStyle(public val value: Int) {
override fun serialize(encoder: Encoder, value: ButtonStyle): Unit = encoder.encodeInt(value.value)
}
}

/**
* Representation of different TextInputStyles.
*
* @see ComponentType.TextInput
*/
@Serializable(with = TextInputStyle.Serializer::class)
public sealed class TextInputStyle(public val value: Int) {
/**
* A fallback style used for styles that haven't been added to Kord yet.
*/
public class Unknown(value: Int) : TextInputStyle(value)

/**
* A single-line input.
*/
public object Short : TextInputStyle(1)

/**
* A multi-line input.
*/
public object Paragraph : TextInputStyle(2)

internal companion object Serializer : KSerializer<TextInputStyle> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("TextInput", PrimitiveKind.INT)

override fun deserialize(decoder: Decoder): TextInputStyle =
when (val value = decoder.decodeInt()) {
1 -> Short
2 -> Paragraph
else -> Unknown(value)
}

override fun serialize(encoder: Encoder, value: TextInputStyle): Unit = encoder.encodeInt(value.value)
}
}
34 changes: 14 additions & 20 deletions common/src/main/kotlin/entity/Interactions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,15 @@ public sealed class InteractionType(public val type: Int) {
public object Component : InteractionType(3)

public object AutoComplete : InteractionType(4)
public object ModalSubmit : InteractionType(5)
public class Unknown(type: Int) : InteractionType(type)

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

Expand All @@ -278,6 +280,7 @@ public sealed class InteractionType(public val type: Int) {
2 -> ApplicationCommand
3 -> Component
4 -> AutoComplete
5 -> ModalSubmit
else -> Unknown(type)
}
}
Expand All @@ -303,6 +306,7 @@ public data class InteractionCallbackData(
@SerialName("component_type")
val componentType: Optional<ComponentType> = Optional.Missing(),
val values: Optional<List<String>> = Optional.Missing(),
val components: Optional<List<DiscordComponent>> = Optional.Missing()
)

@Serializable(with = Option.Serializer::class)
Expand Down Expand Up @@ -666,26 +670,6 @@ public data class CommandGroup(
get() = ApplicationCommandOptionType.SubCommandGroup
}

public fun CommandArgument<*>.int(): Long {
return value as? Long ?: error("$value wasn't an int.")
}


public fun CommandArgument<*>.string(): String {
return value.toString()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these two are used in tests



public fun CommandArgument<*>.boolean(): Boolean {
return value as? Boolean ?: error("$value wasn't a Boolean.")
}


public fun CommandArgument<*>.snowflake(): Snowflake {
val id = string().toULongOrNull() ?: error("$value wasn't a Snowflake")
return Snowflake(id)
}

@Serializable(InteractionResponseType.Serializer::class)

public sealed class InteractionResponseType(public val type: Int) {
Expand All @@ -695,6 +679,7 @@ public sealed class InteractionResponseType(public val type: Int) {
public object DeferredUpdateMessage : InteractionResponseType(6)
public object UpdateMessage : InteractionResponseType(7)
public object ApplicationCommandAutoCompleteResult : InteractionResponseType(8)
public object Modal : InteractionResponseType(9)
Copy link
Member

@lukellmann lukellmann Feb 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing 9 -> Modal in deserialize of InteractionResponseType.Serializer (but usually it should only be serialized)

public class Unknown(type: Int) : InteractionResponseType(type)

internal object Serializer : KSerializer<InteractionResponseType> {
Expand All @@ -710,6 +695,7 @@ public sealed class InteractionResponseType(public val type: Int) {
6 -> DeferredUpdateMessage
7 -> UpdateMessage
8 -> ApplicationCommandAutoCompleteResult
9 -> Modal
else -> Unknown(type)
}
}
Expand Down Expand Up @@ -771,3 +757,11 @@ public data class DiscordGuildApplicationCommandPermission(
public data class DiscordAutoComplete<T>(
val choices: List<Choice<T>>
)

@Serializable
public data class DiscordModal(
val title: String,
@SerialName("custom_id")
val customId: String,
val components: List<DiscordComponent>,
)
25 changes: 0 additions & 25 deletions core/src/main/kotlin/behavior/GuildInteractionBehavior.kt

This file was deleted.

Loading