use std::collections::HashMap;
use serde::de::{Deserializer, Error as DeError};
use serde::ser::{Error as _, Serializer};
use serde::{Deserialize, Serialize};
use super::{AuthorizingIntegrationOwners, InteractionContext};
#[cfg(feature = "model")]
use crate::builder::{
Builder,
CreateInteractionResponse,
CreateInteractionResponseFollowup,
CreateInteractionResponseMessage,
EditInteractionResponse,
};
#[cfg(feature = "collector")]
use crate::client::Context;
#[cfg(feature = "model")]
use crate::http::{CacheHttp, Http};
use crate::internal::prelude::*;
use crate::json::{self, JsonError};
use crate::model::application::{CommandOptionType, CommandType};
use crate::model::channel::{Attachment, Message, PartialChannel};
use crate::model::guild::{Member, PartialMember, Role};
use crate::model::id::{
ApplicationId,
AttachmentId,
ChannelId,
CommandId,
GenericId,
GuildId,
InteractionId,
MessageId,
RoleId,
TargetId,
UserId,
};
use crate::model::monetization::Entitlement;
use crate::model::user::User;
use crate::model::Permissions;
#[cfg(all(feature = "collector", feature = "utils"))]
use crate::utils::{CreateQuickModal, QuickModalResponse};
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(remote = "Self")]
#[non_exhaustive]
pub struct CommandInteraction {
pub id: InteractionId,
pub application_id: ApplicationId,
pub data: CommandData,
#[serde(skip_serializing_if = "Option::is_none")]
pub guild_id: Option<GuildId>,
pub channel: Option<PartialChannel>,
pub channel_id: ChannelId,
#[serde(skip_serializing_if = "Option::is_none")]
pub member: Option<Box<Member>>,
#[serde(default)]
pub user: User,
pub token: String,
pub version: u8,
pub app_permissions: Option<Permissions>,
pub locale: String,
pub guild_locale: Option<String>,
pub entitlements: Vec<Entitlement>,
#[serde(default)]
pub authorizing_integration_owners: AuthorizingIntegrationOwners,
pub context: Option<InteractionContext>,
pub attachment_size_limit: u32,
}
#[cfg(feature = "model")]
impl CommandInteraction {
pub async fn get_response(&self, http: impl AsRef<Http>) -> Result<Message> {
http.as_ref().get_original_interaction_response(&self.token).await
}
pub async fn create_response(
&self,
cache_http: impl CacheHttp,
builder: CreateInteractionResponse,
) -> Result<()> {
builder.execute(cache_http, (self.id, &self.token)).await
}
pub async fn edit_response(
&self,
cache_http: impl CacheHttp,
builder: EditInteractionResponse,
) -> Result<Message> {
builder.execute(cache_http, &self.token).await
}
pub async fn delete_response(&self, http: impl AsRef<Http>) -> Result<()> {
http.as_ref().delete_original_interaction_response(&self.token).await
}
pub async fn create_followup(
&self,
cache_http: impl CacheHttp,
builder: CreateInteractionResponseFollowup,
) -> Result<Message> {
builder.execute(cache_http, (None, &self.token)).await
}
pub async fn edit_followup(
&self,
cache_http: impl CacheHttp,
message_id: impl Into<MessageId>,
builder: CreateInteractionResponseFollowup,
) -> Result<Message> {
builder.execute(cache_http, (Some(message_id.into()), &self.token)).await
}
pub async fn delete_followup<M: Into<MessageId>>(
&self,
http: impl AsRef<Http>,
message_id: M,
) -> Result<()> {
http.as_ref().delete_followup_message(&self.token, message_id.into()).await
}
pub async fn get_followup<M: Into<MessageId>>(
&self,
http: impl AsRef<Http>,
message_id: M,
) -> Result<Message> {
http.as_ref().get_followup_message(&self.token, message_id.into()).await
}
pub async fn defer(&self, cache_http: impl CacheHttp) -> Result<()> {
let builder = CreateInteractionResponse::Defer(CreateInteractionResponseMessage::default());
self.create_response(cache_http, builder).await
}
pub async fn defer_ephemeral(&self, cache_http: impl CacheHttp) -> Result<()> {
let builder = CreateInteractionResponse::Defer(
CreateInteractionResponseMessage::new().ephemeral(true),
);
self.create_response(cache_http, builder).await
}
#[cfg(all(feature = "collector", feature = "utils"))]
pub async fn quick_modal(
&self,
ctx: &Context,
builder: CreateQuickModal,
) -> Result<Option<QuickModalResponse>> {
builder.execute(ctx, self.id, &self.token).await
}
}
impl<'de> Deserialize<'de> for CommandInteraction {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
let mut interaction = Self::deserialize(deserializer)?;
if let Some(guild_id) = interaction.guild_id {
if let Some(member) = &mut interaction.member {
member.guild_id = guild_id;
interaction.user = member.user.clone();
}
interaction.data.resolved.roles.values_mut().for_each(|r| r.guild_id = guild_id);
}
Ok(interaction)
}
}
impl Serialize for CommandInteraction {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
Self::serialize(self, serializer)
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct CommandData {
pub id: CommandId,
pub name: String,
#[serde(rename = "type")]
pub kind: CommandType,
#[serde(default)]
pub resolved: CommandDataResolved,
#[serde(default)]
pub options: Vec<CommandDataOption>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guild_id: Option<GuildId>,
pub target_id: Option<TargetId>,
}
impl CommandData {
#[must_use]
pub fn autocomplete(&self) -> Option<AutocompleteOption<'_>> {
fn find_option(opts: &[CommandDataOption]) -> Option<AutocompleteOption<'_>> {
for opt in opts {
match &opt.value {
CommandDataOptionValue::SubCommand(opts)
| CommandDataOptionValue::SubCommandGroup(opts) => {
return find_option(opts);
},
CommandDataOptionValue::Autocomplete {
kind,
value,
} => {
return Some(AutocompleteOption {
name: &opt.name,
kind: *kind,
value,
});
},
_ => {},
}
}
None
}
find_option(&self.options)
}
#[must_use]
pub fn options(&self) -> Vec<ResolvedOption<'_>> {
fn resolve_options<'a>(
opts: &'a [CommandDataOption],
resolved: &'a CommandDataResolved,
) -> Vec<ResolvedOption<'a>> {
let mut options = Vec::new();
for opt in opts {
let value = match &opt.value {
CommandDataOptionValue::SubCommand(opts) => {
ResolvedValue::SubCommand(resolve_options(opts, resolved))
},
CommandDataOptionValue::SubCommandGroup(opts) => {
ResolvedValue::SubCommandGroup(resolve_options(opts, resolved))
},
CommandDataOptionValue::Autocomplete {
kind,
value,
} => ResolvedValue::Autocomplete {
kind: *kind,
value,
},
CommandDataOptionValue::Boolean(v) => ResolvedValue::Boolean(*v),
CommandDataOptionValue::Integer(v) => ResolvedValue::Integer(*v),
CommandDataOptionValue::Number(v) => ResolvedValue::Number(*v),
CommandDataOptionValue::String(v) => ResolvedValue::String(v),
CommandDataOptionValue::Attachment(id) => resolved.attachments.get(id).map_or(
ResolvedValue::Unresolved(Unresolved::Attachment(*id)),
ResolvedValue::Attachment,
),
CommandDataOptionValue::Channel(id) => resolved.channels.get(id).map_or(
ResolvedValue::Unresolved(Unresolved::Channel(*id)),
ResolvedValue::Channel,
),
CommandDataOptionValue::Mentionable(id) => {
let user_id = UserId::new(id.get());
let value = if let Some(user) = resolved.users.get(&user_id) {
Some(ResolvedValue::User(user, resolved.members.get(&user_id)))
} else {
resolved.roles.get(&RoleId::new(id.get())).map(ResolvedValue::Role)
};
value.unwrap_or(ResolvedValue::Unresolved(Unresolved::Mentionable(*id)))
},
CommandDataOptionValue::User(id) => resolved
.users
.get(id)
.map(|u| ResolvedValue::User(u, resolved.members.get(id)))
.unwrap_or(ResolvedValue::Unresolved(Unresolved::User(*id))),
CommandDataOptionValue::Role(id) => resolved.roles.get(id).map_or(
ResolvedValue::Unresolved(Unresolved::RoleId(*id)),
ResolvedValue::Role,
),
CommandDataOptionValue::Unknown(unknown) => {
ResolvedValue::Unresolved(Unresolved::Unknown(*unknown))
},
};
options.push(ResolvedOption {
name: &opt.name,
value,
});
}
options
}
resolve_options(&self.options, &self.resolved)
}
#[must_use]
pub fn target(&self) -> Option<ResolvedTarget<'_>> {
match (self.kind, self.target_id) {
(CommandType::User, Some(id)) => {
let user_id = id.to_user_id();
let user = self.resolved.users.get(&user_id)?;
let member = self.resolved.members.get(&user_id);
Some(ResolvedTarget::User(user, member))
},
(CommandType::Message, Some(id)) => {
let message_id = id.to_message_id();
let message = self.resolved.messages.get(&message_id)?;
Some(ResolvedTarget::Message(message))
},
_ => None,
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct AutocompleteOption<'a> {
pub name: &'a str,
pub kind: CommandOptionType,
pub value: &'a str,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ResolvedOption<'a> {
pub name: &'a str,
pub value: ResolvedValue<'a>,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ResolvedValue<'a> {
Autocomplete { kind: CommandOptionType, value: &'a str },
Boolean(bool),
Integer(i64),
Number(f64),
String(&'a str),
SubCommand(Vec<ResolvedOption<'a>>),
SubCommandGroup(Vec<ResolvedOption<'a>>),
Attachment(&'a Attachment),
Channel(&'a PartialChannel),
Role(&'a Role),
User(&'a User, Option<&'a PartialMember>),
Unresolved(Unresolved),
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Unresolved {
Attachment(AttachmentId),
Channel(ChannelId),
Mentionable(GenericId),
RoleId(RoleId),
User(UserId),
Unknown(u8),
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ResolvedTarget<'a> {
User(&'a User, Option<&'a PartialMember>),
Message(&'a Message),
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct CommandDataResolved {
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub users: HashMap<UserId, User>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub members: HashMap<UserId, PartialMember>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub roles: HashMap<RoleId, Role>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub channels: HashMap<ChannelId, PartialChannel>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub messages: HashMap<MessageId, Message>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub attachments: HashMap<AttachmentId, Attachment>,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct CommandDataOption {
pub name: String,
pub value: CommandDataOptionValue,
}
impl CommandDataOption {
#[must_use]
pub fn kind(&self) -> CommandOptionType {
self.value.kind()
}
}
#[derive(Deserialize, Serialize)]
struct RawCommandDataOption {
name: String,
#[serde(rename = "type")]
kind: CommandOptionType,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
options: Option<Vec<RawCommandDataOption>>,
#[serde(skip_serializing_if = "Option::is_none")]
focused: Option<bool>,
}
fn option_from_raw(raw: RawCommandDataOption) -> Result<CommandDataOption> {
macro_rules! value {
() => {{
json::from_value(
raw.value.ok_or_else::<JsonError, _>(|| DeError::missing_field("value"))?,
)?
}};
}
let value = match raw.kind {
_ if raw.focused == Some(true) => CommandDataOptionValue::Autocomplete {
kind: raw.kind,
value: value!(),
},
CommandOptionType::Boolean => CommandDataOptionValue::Boolean(value!()),
CommandOptionType::Integer => CommandDataOptionValue::Integer(value!()),
CommandOptionType::Number => CommandDataOptionValue::Number(value!()),
CommandOptionType::String => CommandDataOptionValue::String(value!()),
CommandOptionType::SubCommand => {
let options =
raw.options.ok_or_else::<JsonError, _>(|| DeError::missing_field("options"))?;
let options = options.into_iter().map(option_from_raw).collect::<Result<_>>()?;
CommandDataOptionValue::SubCommand(options)
},
CommandOptionType::SubCommandGroup => {
let options =
raw.options.ok_or_else::<JsonError, _>(|| DeError::missing_field("options"))?;
let options = options.into_iter().map(option_from_raw).collect::<Result<_>>()?;
CommandDataOptionValue::SubCommandGroup(options)
},
CommandOptionType::Attachment => CommandDataOptionValue::Attachment(value!()),
CommandOptionType::Channel => CommandDataOptionValue::Channel(value!()),
CommandOptionType::Mentionable => CommandDataOptionValue::Mentionable(value!()),
CommandOptionType::Role => CommandDataOptionValue::Role(value!()),
CommandOptionType::User => CommandDataOptionValue::User(value!()),
CommandOptionType::Unknown(unknown) => CommandDataOptionValue::Unknown(unknown),
};
Ok(CommandDataOption {
name: raw.name,
value,
})
}
fn option_to_raw(option: &CommandDataOption) -> Result<RawCommandDataOption> {
let mut raw = RawCommandDataOption {
name: option.name.clone(),
kind: option.kind(),
value: None,
options: None,
focused: None,
};
match &option.value {
CommandDataOptionValue::Autocomplete {
kind: _,
value,
} => {
raw.value = Some(json::to_value(value)?);
raw.focused = Some(true);
},
CommandDataOptionValue::Boolean(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::Integer(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::Number(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::String(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::SubCommand(o) | CommandDataOptionValue::SubCommandGroup(o) => {
raw.options = Some(o.iter().map(option_to_raw).collect::<Result<_>>()?);
},
CommandDataOptionValue::Attachment(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::Channel(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::Mentionable(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::Role(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::User(v) => raw.value = Some(json::to_value(v)?),
CommandDataOptionValue::Unknown(_) => {},
}
Ok(raw)
}
impl<'de> Deserialize<'de> for CommandDataOption {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
option_from_raw(RawCommandDataOption::deserialize(deserializer)?).map_err(D::Error::custom)
}
}
impl Serialize for CommandDataOption {
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
option_to_raw(self).map_err(S::Error::custom)?.serialize(serializer)
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum CommandDataOptionValue {
Autocomplete { kind: CommandOptionType, value: String },
Boolean(bool),
Integer(i64),
Number(f64),
String(String),
SubCommand(Vec<CommandDataOption>),
SubCommandGroup(Vec<CommandDataOption>),
Attachment(AttachmentId),
Channel(ChannelId),
Mentionable(GenericId),
Role(RoleId),
User(UserId),
Unknown(u8),
}
impl CommandDataOptionValue {
#[must_use]
pub fn kind(&self) -> CommandOptionType {
match self {
Self::Autocomplete {
kind, ..
} => *kind,
Self::Boolean(_) => CommandOptionType::Boolean,
Self::Integer(_) => CommandOptionType::Integer,
Self::Number(_) => CommandOptionType::Number,
Self::String(_) => CommandOptionType::String,
Self::SubCommand(_) => CommandOptionType::SubCommand,
Self::SubCommandGroup(_) => CommandOptionType::SubCommandGroup,
Self::Attachment(_) => CommandOptionType::Attachment,
Self::Channel(_) => CommandOptionType::Channel,
Self::Mentionable(_) => CommandOptionType::Mentionable,
Self::Role(_) => CommandOptionType::Role,
Self::User(_) => CommandOptionType::User,
Self::Unknown(unknown) => CommandOptionType::Unknown(*unknown),
}
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
match *self {
Self::Boolean(b) => Some(b),
_ => None,
}
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
match *self {
Self::Integer(v) => Some(v),
_ => None,
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
match *self {
Self::Number(v) => Some(v),
_ => None,
}
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
Self::Autocomplete {
value, ..
} => Some(value),
_ => None,
}
}
#[must_use]
pub fn as_attachment_id(&self) -> Option<AttachmentId> {
match self {
Self::Attachment(id) => Some(*id),
_ => None,
}
}
#[must_use]
pub fn as_channel_id(&self) -> Option<ChannelId> {
match self {
Self::Channel(id) => Some(*id),
_ => None,
}
}
#[must_use]
pub fn as_mentionable(&self) -> Option<GenericId> {
match self {
Self::Mentionable(id) => Some(*id),
_ => None,
}
}
#[must_use]
pub fn as_user_id(&self) -> Option<UserId> {
match self {
Self::User(id) => Some(*id),
_ => None,
}
}
#[must_use]
pub fn as_role_id(&self) -> Option<RoleId> {
match self {
Self::Role(id) => Some(*id),
_ => None,
}
}
}
impl TargetId {
#[must_use]
pub fn to_user_id(self) -> UserId {
self.get().into()
}
#[must_use]
pub fn to_message_id(self) -> MessageId {
self.get().into()
}
}
impl From<MessageId> for TargetId {
fn from(id: MessageId) -> Self {
Self::new(id.into())
}
}
impl From<UserId> for TargetId {
fn from(id: UserId) -> Self {
Self::new(id.into())
}
}
impl From<TargetId> for MessageId {
fn from(id: TargetId) -> Self {
Self::new(id.into())
}
}
impl From<TargetId> for UserId {
fn from(id: TargetId) -> Self {
Self::new(id.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::json::{assert_json, json};
#[test]
fn nested_options() {
let value = CommandDataOption {
name: "subcommand_group".into(),
value: CommandDataOptionValue::SubCommandGroup(vec![CommandDataOption {
name: "subcommand".into(),
value: CommandDataOptionValue::SubCommand(vec![CommandDataOption {
name: "channel".into(),
value: CommandDataOptionValue::Channel(ChannelId::new(3)),
}]),
}]),
};
assert_json(
&value,
json!({
"name": "subcommand_group",
"type": 2,
"options": [{
"name": "subcommand",
"type": 1,
"options": [{"name": "channel", "type": 7, "value": "3"}],
}]
}),
);
}
#[test]
fn mixed_options() {
let value = vec![
CommandDataOption {
name: "boolean".into(),
value: CommandDataOptionValue::Boolean(true),
},
CommandDataOption {
name: "integer".into(),
value: CommandDataOptionValue::Integer(1),
},
CommandDataOption {
name: "number".into(),
value: CommandDataOptionValue::Number(2.0),
},
CommandDataOption {
name: "string".into(),
value: CommandDataOptionValue::String("foobar".into()),
},
CommandDataOption {
name: "empty_subcommand".into(),
value: CommandDataOptionValue::SubCommand(vec![]),
},
CommandDataOption {
name: "autocomplete".into(),
value: CommandDataOptionValue::Autocomplete {
kind: CommandOptionType::Integer,
value: "not an integer".into(),
},
},
];
assert_json(
&value,
json!([
{"name": "boolean", "type": 5, "value": true},
{"name": "integer", "type": 4, "value": 1},
{"name": "number", "type": 10, "value": 2.0},
{"name": "string", "type": 3, "value": "foobar"},
{"name": "empty_subcommand", "type": 1, "options": []},
{"name": "autocomplete", "type": 4, "value": "not an integer", "focused": true},
]),
);
}
}