#[cfg(feature = "extended-handler")]
use crate::expect_successful_api_response_and_return;
#[cfg(feature = "handler")]
use crate::{expect_specific_api_response, expect_successful_api_response};
use serde::{Deserialize, Serialize};
use serde_with::*;
use serde_repr::*;
use super::application::*;
use super::components::*;
use super::embed::*;
#[cfg(feature = "extended-handler")]
use super::guild::*;
use super::user::*;
#[cfg(feature = "handler")]
use super::HttpError;
use super::Snowflake;
#[cfg(feature = "handler")]
use ::chrono::{DateTime, Utc};
#[cfg(feature = "handler")]
use log::{debug, error};
#[cfg(any(feature = "handler", feature = "extended-handler"))]
use reqwest::{Client, StatusCode};
#[cfg(any(feature = "handler", feature = "extended-handler"))]
#[derive(Clone, Debug)]
pub struct Context {
client: Client,
pub author_id: Option<Snowflake>,
pub interaction: Interaction,
}
#[serde_as]
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct Interaction {
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default)]
pub application_id: Option<Snowflake>,
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default)]
pub id: Option<Snowflake>,
pub r#type: InteractionType,
pub data: Option<ApplicationCommandInteractionData>,
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default)]
pub guild_id: Option<Snowflake>,
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default)]
pub channel_id: Option<Snowflake>,
pub member: Option<Member>,
pub user: Option<User>,
pub token: Option<String>,
pub locale: Option<String>,
pub version: Option<i8>,
}
#[derive(Clone, Serialize_repr, Deserialize_repr, PartialEq, Debug)]
#[repr(u8)]
#[non_exhaustive]
pub enum InteractionType {
Ping = 1,
ApplicationCommand = 2,
MessageComponent = 3,
}
#[serde_as]
#[skip_serializing_none]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct InteractionResponse {
pub r#type: InteractionResponseType,
pub data: Option<InteractionApplicationCommandCallbackData>,
}
#[cfg(feature = "handler")]
#[derive(Clone, Debug)]
pub struct InteractionResponseBuilder {
#[doc(hidden)]
pub r#type: InteractionResponseType,
#[doc(hidden)]
pub data: Option<InteractionApplicationCommandCallbackData>,
}
impl InteractionResponse {
pub fn new(
rtype: InteractionResponseType,
data: Option<InteractionApplicationCommandCallbackData>,
) -> InteractionResponse {
InteractionResponse {
r#type: rtype,
data,
}
}
}
#[cfg(feature = "handler")]
impl Default for InteractionResponseBuilder {
fn default() -> Self {
Self {
r#type: InteractionResponseType::ChannelMessageWithSource,
data: None,
}
}
}
#[cfg(feature = "handler")]
impl InteractionResponseBuilder {
fn ret(self) -> InteractionResponse {
InteractionResponse {
r#type: self.r#type,
data: self.data,
}
}
pub fn pong(mut self) -> Result<InteractionResponse, std::convert::Infallible> {
self.r#type = InteractionResponseType::Pong;
self.data = None;
self.finish()
}
pub fn none(mut self) -> Result<InteractionResponse, std::convert::Infallible> {
self.r#type = InteractionResponseType::None;
self.data = None;
self.finish()
}
pub fn respond_type(mut self, t: InteractionResponseType) -> Self {
self.r#type = t;
self
}
pub fn data(mut self, d: &InteractionApplicationCommandCallbackData) -> InteractionResponse {
self.data = Some(d.clone());
self.ret()
}
pub fn tts(mut self, enable: &bool) -> Self {
if self.data.is_none() {
let mut d = InteractionApplicationCommandCallbackData::new();
d.tts = Some(*enable);
self.data = Some(d);
} else {
self.data.as_mut().unwrap().tts = Some(*enable);
}
self
}
pub fn content(mut self, c: impl ToString) -> Self {
match self.data.as_mut() {
None => {
let mut d = InteractionApplicationCommandCallbackData::new();
d.content = Some(c.to_string());
self.data = Some(d);
}
Some(mut d) => {
d.content = Some(c.to_string());
}
}
self
}
pub fn message(self, c: impl ToString) -> Self {
self.content(c)
}
pub fn is_ephemeral(mut self, e: bool) -> Self {
match self.data.as_mut() {
None => {
let mut d = InteractionApplicationCommandCallbackData::new();
if e {
d.flags = Some(1 << 6);
}
self.data = Some(d);
}
Some(mut d) => {
if let Some(mut flag) = d.flags {
if e {
flag |= 1 << 6;
} else {
flag |= 0 << 6;
}
d.flags = Some(flag);
} else if e {
d.flags = Some(1 << 6);
} else {
d.flags = Some(0);
}
}
}
self
}
pub fn add_embed(mut self, e: &Embed) -> Self {
match self.data.as_mut() {
None => {
let mut d = InteractionApplicationCommandCallbackData::new();
d.embeds = Some(vec![e.clone()]);
self.data = Some(d);
}
Some(mut d) => {
if d.embeds.is_none() {
d.embeds = Some(vec![e.clone()]);
} else {
let v = d.embeds.as_mut().unwrap();
if v.len() <= 9 {
v.push(e.clone());
} else {
error!("Tried to add embed while embed limit (max. 10 embeds) was already reached. Ignoring")
}
}
}
}
self
}
pub fn add_component_row(mut self, comp: impl Into<MessageComponent>) -> Self {
let component = comp.into();
match self.data.as_mut() {
None => {
let mut d = InteractionApplicationCommandCallbackData::new();
d.components = Some(vec![component]);
self.data = Some(d);
}
Some(mut d) => {
if d.components.is_none() {
d.components = Some(vec![component]);
} else {
let comp = d.components.as_mut().unwrap();
comp.push(component);
}
}
}
self
}
pub fn finish(self) -> Result<InteractionResponse, std::convert::Infallible> {
Ok(self.ret())
}
}
#[derive(Clone, Serialize_repr, Deserialize_repr, Debug, PartialEq)]
#[repr(u8)]
pub enum InteractionResponseType {
None = 0,
Pong = 1,
ChannelMessageWithSource = 4,
DefferedChannelMessageWithSource = 5,
DefferedUpdateMessage = 6,
UpdateMessage = 7,
}
#[serde_as]
#[skip_serializing_none]
#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)]
pub struct InteractionApplicationCommandCallbackData {
tts: Option<bool>,
content: Option<String>,
embeds: Option<Vec<Embed>>,
allowed_mentions: Option<AllowedMentions>,
flags: Option<u8>,
components: Option<Vec<MessageComponent>>,
}
impl InteractionApplicationCommandCallbackData {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub enum AllowedMentionTypes {
Roles,
Users,
Everyone,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct AllowedMentions {
parse: Vec<AllowedMentionTypes>,
roles: Vec<Snowflake>,
users: Vec<Snowflake>,
replied_user: bool,
}
#[serde_as]
#[skip_serializing_none]
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
pub struct WebhookMessage {
pub content: Option<String>,
pub embeds: Option<Vec<Embed>>,
pub components: Option<Vec<MessageComponent>>,
pub payload_json: Option<String>,
allowed_mentions: Option<AllowedMentions>,
}
#[cfg(feature = "handler")]
impl WebhookMessage {
pub fn content(mut self, content: impl ToString) -> Self {
self.content = Some(content.to_string());
self
}
pub fn add_embed(mut self, embed: Embed) -> Self {
match self.embeds.as_mut() {
None => {
self.embeds = Some(vec![embed]);
}
Some(e) => {
if e.len() <= 9 {
e.push(embed);
} else {
error!("Tried to add embed while embed limit (max. 10 embeds) was already reached. Ignoring")
}
}
}
self
}
}
impl From<InteractionResponse> for WebhookMessage {
fn from(o: InteractionResponse) -> WebhookMessage {
let data = o.data.unwrap();
WebhookMessage {
content: data.content,
embeds: data.embeds,
components: data.components,
payload_json: None,
allowed_mentions: None,
}
}
}
#[serde_as]
#[skip_serializing_none]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct MessageReference {
#[serde_as(as = "DisplayFromStr")]
message_id: Snowflake,
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default)]
guild_id: Option<Snowflake>,
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default)]
channel_id: Option<Snowflake>,
}
impl MessageReference {
pub fn message_id(&self) -> Snowflake {
self.message_id
}
pub fn guild_id(&self) -> Option<Snowflake> {
self.guild_id
}
pub fn channel_id(&self) -> Option<Snowflake> {
self.channel_id
}
}
#[cfg(feature = "handler")]
#[serde_as]
#[skip_serializing_none]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct FollowupMessage {
#[serde_as(as = "DisplayFromStr")]
id: Snowflake,
r#type: u8,
content: Option<String>,
embeds: Vec<Embed>,
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default)]
channel_id: Option<Snowflake>,
author: Option<User>,
tts: bool,
#[serde_as(as = "DisplayFromStr")]
timestamp: DateTime<Utc>,
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default)]
edited_timestamp: Option<DateTime<Utc>>,
flags: u32,
#[serde_as(as = "DisplayFromStr")]
application_id: Snowflake,
#[serde_as(as = "DisplayFromStr")]
webhook_id: Snowflake,
message_reference: MessageReference,
#[serde(skip)]
interaction_token: String,
#[serde(skip)]
client: Client,
}
#[cfg(feature = "handler")]
impl FollowupMessage {
pub fn id(&self) -> Snowflake {
self.id
}
pub fn get_type(&self) -> u8 {
self.r#type
}
pub fn embeds(&self) -> Vec<Embed> {
self.embeds.clone()
}
pub fn get_content(&self) -> Option<String> {
self.content.clone()
}
pub fn timestamp(&self) -> DateTime<Utc> {
self.timestamp
}
pub fn edited_timestamp(&self) -> Option<DateTime<Utc>> {
self.edited_timestamp
}
pub fn flags(&self) -> u32 {
self.flags
}
pub fn app_id(&self) -> Snowflake {
self.application_id
}
pub fn webhook_id(&self) -> Snowflake {
self.webhook_id
}
pub fn message_reference(&self) -> MessageReference {
self.message_reference.clone()
}
}
#[cfg(feature = "handler")]
impl FollowupMessage {
pub async fn edit_message(&mut self, new_content: &WebhookMessage) -> Result<(), HttpError> {
let url = format!(
"/webhooks/{:?}/{:?}/messages/{:?}",
self.application_id, self.interaction_token, self.id
);
let exec = self.client.post(&url).json(new_content).send().await;
expect_successful_api_response!(exec, {
Ok(())
})
}
pub async fn delete_message(self) -> Result<(), Self> {
let url = format!(
"{}/webhooks/{:?}/{}/messages/{:?}",
crate::BASE_URL,
self.application_id,
self.interaction_token,
self.id
);
let exec = self.client.delete(&url).send().await;
match exec {
Err(e) => {
debug!("Discord API returned an error: {:#?}", e);
Err(self)
}
Ok(r) => {
if r.status() != StatusCode::NO_CONTENT {
let e = format!("{:#?}", r.text().await);
debug!(
"Discord API request did not return {}: {:#?}",
StatusCode::NO_CONTENT,
e
);
Err(self)
} else {
Ok(())
}
}
}
}
}
#[cfg(feature = "handler")]
impl Context {
pub fn new(c: Client, i: Interaction) -> Self {
let mut user_id = None;
if i.user.is_none() {
if let Some(member) = &i.member {
user_id = Some(member.user.id);
}
} else {
if let Some(u) = &i.user {
user_id = Some(u.id);
}
}
Self {
client: c,
interaction: i,
author_id: user_id,
}
}
pub fn respond(&self) -> InteractionResponseBuilder {
let mut b = InteractionResponseBuilder::default();
if self.interaction.r#type == InteractionType::MessageComponent {
b.r#type = InteractionResponseType::UpdateMessage;
}
b
}
pub async fn edit_original(&self, new_content: &WebhookMessage) -> Result<(), HttpError> {
let url = format!(
"{}/webhooks/{:?}/{}/messages/@original",
crate::BASE_URL,
self.interaction.application_id.unwrap(),
self.interaction.token.as_ref().unwrap()
);
let c = self.client.patch(&url).json(new_content).send().await;
expect_successful_api_response!(c, Ok(()))
}
pub async fn delete_original(&self) -> Result<(), HttpError> {
let url = format!(
"{}/webhooks/{:?}/{}/messages/@original",
crate::BASE_URL,
self.interaction.application_id.unwrap(),
self.interaction.token.as_ref().unwrap()
);
let c = self.client.delete(&url).send().await;
expect_specific_api_response!(c, StatusCode::NO_CONTENT, Ok(()))
}
pub async fn create_followup(
&self,
content: &WebhookMessage,
) -> Result<FollowupMessage, HttpError> {
let url = format!(
"{}/webhooks/{:?}/{}?wait=true",
crate::BASE_URL,
self.interaction.application_id.unwrap(),
self.interaction.token.as_ref().unwrap()
);
let c = self.client.post(&url).json(content).send().await;
match c {
Err(e) => {
debug!("Discord API request failed: {:#?}", e);
Err(HttpError {
code: 0,
message: format!("{:#?}", e),
})
}
Ok(r) => {
let st = r.status();
if !st.is_success() {
let e = format!("{:#?}", r.text().await);
debug!("Discord API returned an error: {:#?}", e);
Err(HttpError {
code: st.as_u16(),
message: e,
})
} else {
let a: Result<FollowupMessage, serde_json::Error> =
serde_json::from_str(&r.text().await.unwrap());
match a {
Err(e) => {
debug!("Failed to decode response: {:#?}", e);
Err(HttpError {
code: 500,
message: format!("{:?}", e),
})
}
Ok(mut f) => {
f.interaction_token =
self.interaction.token.as_ref().unwrap().to_string();
Ok(f)
}
}
}
}
}
}
}
#[cfg(feature = "extended-handler")]
impl Context {
pub async fn get_guild<I: Into<Snowflake>>(&self, id: I) -> Result<Guild, HttpError> {
let url = format!(
"{}/guilds/{:?}?with_counts=true",
crate::BASE_URL,
id.into()
);
let r = self.client.get(&url).send().await;
expect_successful_api_response_and_return!(r, Guild, g, Ok(g))
}
pub async fn get_guild_member(
&self,
guild_id: impl Into<Snowflake>,
user_id: impl Into<Snowflake>,
) -> Result<Member, HttpError> {
let url = format!(
"{}/guilds/{:?}/members/{:?}",
crate::BASE_URL,
guild_id.into(),
user_id.into()
);
let r = self.client.get(&url).send().await;
expect_successful_api_response_and_return!(r, Member, m, Ok(m))
}
}