use crate::{
client::Client,
error::Error as HttpError,
request::{multipart::Form, validate, Pending, Request},
routing::Route,
};
use serde::Serialize;
use std::{
error::Error,
fmt::{Display, Formatter, Result as FmtResult},
};
use twilight_model::{
channel::{
embed::Embed,
message::{AllowedMentions, MessageReference},
Message,
},
id::{ChannelId, MessageId},
};
#[derive(Debug)]
pub struct CreateMessageError {
kind: CreateMessageErrorType,
source: Option<Box<dyn Error + Send + Sync>>,
}
impl CreateMessageError {
#[must_use = "retrieving the type has no effect if left unused"]
pub const fn kind(&self) -> &CreateMessageErrorType {
&self.kind
}
#[must_use = "consuming the error and retrieving the source has no effect if left unused"]
pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
self.source
}
#[must_use = "consuming the error into its parts has no effect if left unused"]
pub fn into_parts(self) -> (CreateMessageErrorType, Option<Box<dyn Error + Send + Sync>>) {
(self.kind, self.source)
}
}
impl Display for CreateMessageError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match &self.kind {
CreateMessageErrorType::ContentInvalid { .. } => {
f.write_str("the message content is invalid")
}
CreateMessageErrorType::EmbedTooLarge { .. } => {
f.write_str("the embed's contents are too long")
}
}
}
}
impl Error for CreateMessageError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source
.as_ref()
.map(|source| &**source as &(dyn Error + 'static))
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum CreateMessageErrorType {
ContentInvalid {
content: String,
},
EmbedTooLarge {
embed: Box<Embed>,
},
}
#[derive(Default, Serialize)]
pub(crate) struct CreateMessageFields {
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
embed: Option<Embed>,
#[serde(skip_serializing_if = "Option::is_none")]
message_reference: Option<MessageReference>,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
payload_json: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) allowed_mentions: Option<AllowedMentions>,
#[serde(skip_serializing_if = "Option::is_none")]
tts: Option<bool>,
}
pub struct CreateMessage<'a> {
channel_id: ChannelId,
pub(crate) fields: CreateMessageFields,
files: Vec<(String, Vec<u8>)>,
fut: Option<Pending<'a, Message>>,
http: &'a Client,
}
impl<'a> CreateMessage<'a> {
pub(crate) fn new(http: &'a Client, channel_id: ChannelId) -> Self {
Self {
channel_id,
fields: CreateMessageFields {
allowed_mentions: http.default_allowed_mentions(),
..CreateMessageFields::default()
},
files: Vec::new(),
fut: None,
http,
}
}
pub fn allowed_mentions(mut self, allowed_mentions: AllowedMentions) -> Self {
self.fields.allowed_mentions.replace(allowed_mentions);
self
}
pub fn content(self, content: impl Into<String>) -> Result<Self, CreateMessageError> {
self._content(content.into())
}
fn _content(mut self, content: String) -> Result<Self, CreateMessageError> {
if !validate::content_limit(&content) {
return Err(CreateMessageError {
kind: CreateMessageErrorType::ContentInvalid { content },
source: None,
});
}
self.fields.content.replace(content);
Ok(self)
}
pub fn embed(mut self, embed: Embed) -> Result<Self, CreateMessageError> {
if let Err(source) = validate::embed(&embed) {
return Err(CreateMessageError {
kind: CreateMessageErrorType::EmbedTooLarge {
embed: Box::new(embed),
},
source: Some(Box::new(source)),
});
}
self.fields.embed.replace(embed);
Ok(self)
}
pub fn fail_if_not_exists(mut self) -> Self {
self.fields.message_reference = Some(self.fields.message_reference.map_or_else(
|| MessageReference {
channel_id: None,
guild_id: None,
message_id: None,
fail_if_not_exists: Some(true),
},
|message_reference| MessageReference {
fail_if_not_exists: Some(true),
..message_reference
},
));
self
}
pub fn file(mut self, name: impl Into<String>, file: impl Into<Vec<u8>>) -> Self {
self.files.push((name.into(), file.into()));
self
}
pub fn files<N: Into<String>, F: Into<Vec<u8>>>(
mut self,
attachments: impl IntoIterator<Item = (N, F)>,
) -> Self {
for (name, file) in attachments {
self = self.file(name, file);
}
self
}
pub fn nonce(mut self, nonce: u64) -> Self {
self.fields.nonce.replace(nonce);
self
}
pub fn payload_json(mut self, payload_json: impl Into<Vec<u8>>) -> Self {
self.fields.payload_json.replace(payload_json.into());
self
}
pub fn reply(mut self, other: MessageId) -> Self {
let channel_id = self.channel_id;
self.fields.message_reference = Some(self.fields.message_reference.map_or_else(
|| MessageReference {
channel_id: Some(channel_id),
guild_id: None,
message_id: Some(other),
fail_if_not_exists: None,
},
|message_reference| MessageReference {
channel_id: Some(channel_id),
message_id: Some(other),
..message_reference
},
));
self
}
pub fn tts(mut self, tts: bool) -> Self {
self.fields.tts.replace(tts);
self
}
fn start(&mut self) -> Result<(), HttpError> {
let mut request = Request::builder(Route::CreateMessage {
channel_id: self.channel_id.0,
});
if !self.files.is_empty() || self.fields.payload_json.is_some() {
let mut form = Form::new();
for (index, (name, file)) in self.files.drain(..).enumerate() {
form.file(format!("{}", index).as_bytes(), name.as_bytes(), &file);
}
if let Some(payload_json) = &self.fields.payload_json {
form.payload_json(&payload_json);
} else {
let body = crate::json::to_vec(&self.fields).map_err(HttpError::json)?;
form.payload_json(&body);
}
request = request.form(form);
} else {
request = request.json(&self.fields)?;
}
self.fut
.replace(Box::pin(self.http.request(request.build())));
Ok(())
}
}
poll_req!(CreateMessage<'_>, Message);