Skip to content

Commit

Permalink
Add Modal Interactions and Input Text (#1657)
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalharp committed Feb 18, 2022
1 parent 57c0826 commit 92fe5bb
Show file tree
Hide file tree
Showing 7 changed files with 589 additions and 3 deletions.
87 changes: 86 additions & 1 deletion src/builder/create_components.rs
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;

use crate::internal::prelude::*;
use crate::model::channel::ReactionType;
use crate::model::interactions::message_component::ButtonStyle;
use crate::model::interactions::message_component::{ButtonStyle, InputTextStyle};
use crate::utils;

/// A builder for creating several [`ActionRow`]s.
Expand Down Expand Up @@ -97,6 +97,29 @@ impl CreateActionRow {
self
}

/// Creates an input text.
pub fn create_input_text<F>(&mut self, f: F) -> &mut Self
where
F: FnOnce(&mut CreateInputText) -> &mut CreateInputText,
{
let mut data = CreateInputText::default();
f(&mut data);

self.add_input_text(data);

self
}

/// Adds an input text.
pub fn add_input_text(&mut self, input_text: CreateInputText) -> &mut Self {
let components = self.0.entry("components").or_insert_with(|| Value::Array(Vec::new()));
let components_array = components.as_array_mut().expect("Must be an array");

components_array.push(input_text.build());

self
}

pub fn build(&mut self) -> Value {
self.0.insert("type", Value::Number(serde_json::Number::from(1_u8)));

Expand Down Expand Up @@ -331,3 +354,65 @@ impl CreateSelectMenuOption {
self
}
}

/// A builder for creating an [`InputText`].
///
/// [`InputText`]: crate::model::interactions::message_component::InputText
#[derive(Clone, Debug, Default)]
pub struct CreateInputText(pub HashMap<&'static str, Value>);

impl CreateInputText {
/// Sets the custom id of the input text, a developer-defined identifier.
pub fn custom_id<D: ToString>(&mut self, id: D) -> &mut Self {
self.0.insert("custom_id", Value::String(id.to_string()));
self
}

/// Sets the style of this input text
pub fn style(&mut self, kind: InputTextStyle) -> &mut Self {
self.0.insert("style", Value::Number(serde_json::Number::from(kind as u8)));
self
}

/// Sets the label of this input text.
pub fn label<D: ToString>(&mut self, label: D) -> &mut Self {
self.0.insert("label", Value::String(label.to_string()));
self
}

/// Sets the placeholder of this input text.
pub fn placeholder<D: ToString>(&mut self, label: D) -> &mut Self {
self.0.insert("placeholder", Value::String(label.to_string()));
self
}

/// Sets the minimum length required for the input text
pub fn min_length(&mut self, min: u64) -> &mut Self {
self.0.insert("min_length", Value::Number(Number::from(min)));
self
}

/// Sets the maximum length required for the input text
pub fn max_length(&mut self, max: u64) -> &mut Self {
self.0.insert("max_length", Value::Number(Number::from(max)));
self
}

/// Sets the value of this input text.
pub fn value<D: ToString>(&mut self, value: D) -> &mut Self {
self.0.insert("value", Value::String(value.to_string()));
self
}

/// Sets if the input text is required
pub fn required(&mut self, required: bool) -> &mut Self {
self.0.insert("required", Value::Bool(required));
self
}

pub fn build(mut self) -> Value {
self.0.insert("type", Value::Number(serde_json::Number::from(4_u8)));

utils::hashmap_to_json_map(self.0.clone()).into()
}
}
12 changes: 12 additions & 0 deletions src/builder/create_interaction_response.rs
Expand Up @@ -148,6 +148,18 @@ impl CreateInteractionResponseData {
self.0.insert("components", Value::Array(components.0));
self
}

/// Sets the custom id for modal interactions
pub fn custom_id<D: ToString>(&mut self, id: D) -> &mut Self {
self.0.insert("custom_id", Value::String(id.to_string()));
self
}

/// Sets the titel for modal interactions
pub fn title<D: ToString>(&mut self, title: D) -> &mut Self {
self.0.insert("title", Value::String(title.to_string()));
self
}
}

#[derive(Clone, Debug)]
Expand Down
1 change: 1 addition & 0 deletions src/builder/mod.rs
Expand Up @@ -86,6 +86,7 @@ pub use self::{
CreateActionRow,
CreateButton,
CreateComponents,
CreateInputText,
CreateSelectMenu,
CreateSelectMenuOption,
CreateSelectMenuOptions,
Expand Down
4 changes: 4 additions & 0 deletions src/model/event.rs
Expand Up @@ -2239,24 +2239,28 @@ macro_rules! with_related_ids_for_event_types {
Interaction::ApplicationCommand(i) => Some(i.user.id),
Interaction::MessageComponent(i) => Some(i.user.id),
Interaction::Autocomplete(i) => Some(i.user.id),
Interaction::ModalSubmit(i) => Some(i.user.id),
},
guild_id: match &e.interaction {
Interaction::Ping(_) => None,
Interaction::ApplicationCommand(i) => i.guild_id.into(),
Interaction::MessageComponent(i) => i.guild_id.into(),
Interaction::Autocomplete(i) => i.guild_id.into(),
Interaction::ModalSubmit(i) => i.guild_id.into(),
},
channel_id: match &e.interaction {
Interaction::Ping(_) => None,
Interaction::ApplicationCommand(i) => Some(i.channel_id),
Interaction::MessageComponent(i) => Some(i.channel_id),
Interaction::Autocomplete(i) => Some(i.channel_id),
Interaction::ModalSubmit(i) => Some(i.channel_id),
},
message_id: match &e.interaction {
Interaction::Ping(_) => None,
Interaction::ApplicationCommand(_) => None,
Interaction::MessageComponent(i) => Some(i.message.id),
Interaction::Autocomplete(i) => None,
Interaction::ModalSubmit(i) => i.message.as_ref().map(|m| m.id).into(),
},
},
#[cfg(feature = "unstable_discord_api")]
Expand Down
50 changes: 49 additions & 1 deletion src/model/interactions/message_component.rs
Expand Up @@ -438,6 +438,7 @@ pub enum Component {
ActionRow(ActionRow),
Button(Button),
SelectMenu(SelectMenu),
InputText(InputText),
}

impl<'de> Deserialize<'de> for Component {
Expand All @@ -460,6 +461,9 @@ impl<'de> Deserialize<'de> for Component {
ComponentType::SelectMenu => serde_json::from_value::<SelectMenu>(Value::Object(map))
.map(Component::SelectMenu)
.map_err(DeError::custom),
ComponentType::InputText => serde_json::from_value::<InputText>(Value::Object(map))
.map(Component::InputText)
.map_err(DeError::custom),
ComponentType::Unknown => Err(DeError::custom("Unknown component type")),
}
}
Expand All @@ -474,6 +478,7 @@ impl Serialize for Component {
Component::ActionRow(c) => ActionRow::serialize(c, serializer),
Component::Button(c) => Button::serialize(c, serializer),
Component::SelectMenu(c) => SelectMenu::serialize(c, serializer),
Component::InputText(c) => InputText::serialize(c, serializer),
}
}
}
Expand All @@ -496,6 +501,12 @@ impl From<SelectMenu> for Component {
}
}

impl From<InputText> for Component {
fn from(component: InputText) -> Self {
Component::InputText(component)
}
}

/// The type of a component
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
Expand All @@ -504,13 +515,15 @@ pub enum ComponentType {
ActionRow = 1,
Button = 2,
SelectMenu = 3,
InputText = 4,
Unknown = !0,
}

enum_number!(ComponentType {
ActionRow,
Button,
SelectMenu
SelectMenu,
InputText
});

/// An action row.
Expand All @@ -530,6 +543,7 @@ pub struct ActionRow {
pub enum ActionRowComponent {
Button(Button),
SelectMenu(SelectMenu),
InputText(InputText),
}

impl<'de> Deserialize<'de> for ActionRowComponent {
Expand All @@ -549,6 +563,9 @@ impl<'de> Deserialize<'de> for ActionRowComponent {
ComponentType::SelectMenu => serde_json::from_value::<SelectMenu>(Value::Object(map))
.map(ActionRowComponent::SelectMenu)
.map_err(DeError::custom),
ComponentType::InputText => serde_json::from_value::<InputText>(Value::Object(map))
.map(ActionRowComponent::InputText)
.map_err(DeError::custom),
_ => Err(DeError::custom("Unknown component type")),
}
}
Expand All @@ -562,6 +579,7 @@ impl Serialize for ActionRowComponent {
match self {
ActionRowComponent::Button(c) => Button::serialize(c, serializer),
ActionRowComponent::SelectMenu(c) => SelectMenu::serialize(c, serializer),
ActionRowComponent::InputText(c) => InputText::serialize(c, serializer),
}
}
}
Expand All @@ -571,6 +589,7 @@ impl From<ActionRowComponent> for Component {
match component {
ActionRowComponent::Button(b) => Component::Button(b),
ActionRowComponent::SelectMenu(s) => Component::SelectMenu(s),
ActionRowComponent::InputText(i) => Component::InputText(i),
}
}
}
Expand All @@ -583,6 +602,7 @@ impl TryFrom<Component> for ActionRowComponent {
Component::ActionRow(_) => Err(Error::Model(ModelError::InvalidComponentType)),
Component::Button(b) => Ok(ActionRowComponent::Button(b)),
Component::SelectMenu(s) => Ok(ActionRowComponent::SelectMenu(s)),
Component::InputText(i) => Ok(ActionRowComponent::InputText(i)),
}
}
}
Expand Down Expand Up @@ -675,3 +695,31 @@ pub struct SelectMenuOption {
#[serde(default)]
pub default: bool,
}

/// An input text component for modal interactions
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct InputText {
/// The component type, it will always be [`ComponentType::InputText`].
#[serde(rename = "type")]
pub kind: ComponentType,
/// An identifier defined by the developer for the select menu.
pub custom_id: String,
/// The input from the user
pub value: String,
}

/// The style of the input text
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
#[repr(u8)]
pub enum InputTextStyle {
Short = 1,
Paragraph = 2,
Unknown = !0,
}

enum_number!(InputTextStyle {
Short,
Paragraph,
Unknown
});
19 changes: 18 additions & 1 deletion src/model/interactions/mod.rs
@@ -1,12 +1,14 @@
pub mod application_command;
pub mod autocomplete;
pub mod message_component;
pub mod modal;
pub mod ping;

use application_command::ApplicationCommandInteraction;
use autocomplete::AutocompleteInteraction;
use bitflags::__impl_bitflags;
use message_component::MessageComponentInteraction;
use modal::ModalSubmitInteraction;
use ping::PingInteraction;
use serde::de::{Deserialize, Deserializer, Error as DeError};
use serde::ser::{Serialize, Serializer};
Expand All @@ -21,6 +23,7 @@ pub enum Interaction {
ApplicationCommand(ApplicationCommandInteraction),
MessageComponent(MessageComponentInteraction),
Autocomplete(AutocompleteInteraction),
ModalSubmit(ModalSubmitInteraction),
}

impl Interaction {
Expand All @@ -31,6 +34,7 @@ impl Interaction {
Interaction::ApplicationCommand(i) => i.id,
Interaction::MessageComponent(i) => i.id,
Interaction::Autocomplete(i) => i.id,
Interaction::ModalSubmit(i) => i.id,
}
}

Expand All @@ -41,6 +45,7 @@ impl Interaction {
Interaction::ApplicationCommand(_) => InteractionType::ApplicationCommand,
Interaction::MessageComponent(_) => InteractionType::MessageComponent,
Interaction::Autocomplete(_) => InteractionType::Autocomplete,
Interaction::ModalSubmit(_) => InteractionType::ModalSubmit,
}
}

Expand All @@ -51,6 +56,7 @@ impl Interaction {
Interaction::ApplicationCommand(i) => i.application_id,
Interaction::MessageComponent(i) => i.application_id,
Interaction::Autocomplete(i) => i.application_id,
Interaction::ModalSubmit(i) => i.application_id,
}
}

Expand All @@ -61,6 +67,7 @@ impl Interaction {
Interaction::ApplicationCommand(i) => i.token.as_str(),
Interaction::MessageComponent(i) => i.token.as_str(),
Interaction::Autocomplete(i) => i.token.as_str(),
Interaction::ModalSubmit(i) => i.token.as_str(),
}
}

Expand All @@ -71,6 +78,7 @@ impl Interaction {
Interaction::ApplicationCommand(i) => i.guild_locale.as_ref().map(String::as_str),
Interaction::MessageComponent(i) => i.guild_locale.as_ref().map(String::as_str),
Interaction::Autocomplete(i) => i.guild_locale.as_ref().map(String::as_str),
Interaction::ModalSubmit(i) => i.guild_locale.as_ref().map(String::as_str),
}
}

Expand Down Expand Up @@ -136,6 +144,11 @@ impl<'de> Deserialize<'de> for Interaction {
.map(Interaction::Autocomplete)
.map_err(DeError::custom)
},
InteractionType::ModalSubmit => {
serde_json::from_value::<ModalSubmitInteraction>(Value::Object(map))
.map(Interaction::ModalSubmit)
.map_err(DeError::custom)
},
InteractionType::Unknown => Err(DeError::custom("Unknown interaction type")),
}
}
Expand All @@ -155,6 +168,7 @@ impl Serialize for Interaction {
MessageComponentInteraction::serialize(i, serializer)
},
Interaction::Autocomplete(i) => AutocompleteInteraction::serialize(i, serializer),
Interaction::ModalSubmit(i) => ModalSubmitInteraction::serialize(i, serializer),
}
}
}
Expand All @@ -168,14 +182,16 @@ pub enum InteractionType {
ApplicationCommand = 2,
MessageComponent = 3,
Autocomplete = 4,
ModalSubmit = 5,
Unknown = !0,
}

enum_number!(InteractionType {
Ping,
MessageComponent,
ApplicationCommand,
Autocomplete
Autocomplete,
ModalSubmit
});

/// The flags for an interaction response.
Expand Down Expand Up @@ -234,4 +250,5 @@ pub enum InteractionResponseType {
DeferredUpdateMessage = 6,
UpdateMessage = 7,
Autocomplete = 8,
Modal = 9,
}

0 comments on commit 92fe5bb

Please sign in to comment.