serenity_builder/message.rs
1use crate::model::message::{SerenityMessage, SerenityMessageMentionType};
2use serenity::all::CreateMessage;
3use serenity::builder::CreateAllowedMentions as Am;
4
5/// Errors that can occur when converting a custom message struct to a [serenity::all::CreateMessage].
6#[derive(thiserror::Error, Debug)]
7pub enum SerenityMessageConvertError {
8 /**
9 * This occurs when the message content exceeds 2000 characters, which is a limitation imposed by the Discord API.
10 *
11 * Serenity-builder will report an error during conversion.
12 * Serenity does not return an error and leaves the response entirely up to the Discord API.
13 */
14 #[error("The content exceeds the maximum length of 2000 characters.")]
15 TooLongContent,
16 /**
17 * This occurs when there is an error converting an embedded structure.
18 * The specific error details are encapsulated in the [crate::embed::SerenityEmbedConvertError].
19 */
20 #[error(transparent)]
21 EmbedConvertError(#[from] crate::embed::SerenityEmbedConvertError),
22}
23
24impl SerenityMessage {
25 /// Convert the message structure created in Builder into a model usable in Serenity.
26 ///
27 /// ```rs
28 /// let message = SerenityMessage::builder()
29 /// .content("This is a test message.")
30 /// .build();
31 ///
32 /// let serenity_message = message.convert()?; // Result<CreateMessage, SerenityMessageConvertError>
33 /// ```
34 ///
35 /// # How to use
36 ///
37 /// ```rs
38 /// // 1. Create a SerenityMessage using the builder
39 /// let message = SerenityMessage::builder()
40 /// .content("This is a test message.")
41 /// .build(); // Don't forget!: If you forget this, you won't be able to use `convert()`.
42 ///
43 /// // 2. Convert to Serenity's CreateMessage
44 /// let serenity_message = message.convert()?; // Result<CreateMessage, SerenityMessageConvertError>
45 ///
46 /// // 3. Use the converted message in your bot
47 /// if let Err(e) = message.channel_id.send_message(&ctx.http, serenity_message).await {
48 /// tracing::error!("Failed to send preview: {:?}", e);
49 /// }
50 /// ```
51 ///
52 /// # Errors
53 ///
54 /// This function may return the following error:
55 ///
56 /// - [SerenityMessageConvertError::TooLongContent]: The content exceeds the maximum length of 2000 characters.
57 /// - [SerenityMessageConvertError::EmbedConvertError]: Failed to perform internal conversion for embed. (error [crate::embed::SerenityEmbedConvertError] reported by thiserror)
58 pub fn convert(&self) -> Result<CreateMessage, SerenityMessageConvertError> {
59 let mut message = serenity::builder::CreateMessage::default();
60
61 if let Some(content) = &self.content {
62 // Internal string data in the Discord API is handled in UTF-16 code units.
63 if content.encode_utf16().count() > 2000 {
64 return Err(SerenityMessageConvertError::TooLongContent);
65 }
66 message = message.content(content);
67 }
68
69 if let Some(embeds) = &self.embeds {
70 for embed in embeds {
71 let serenity_embed = embed.convert()?;
72 message = message.add_embed(serenity_embed);
73 }
74 }
75
76 if let Some(mention) = &self.mention_type {
77 match mention {
78 SerenityMessageMentionType::Everyone => {
79 message = message.allowed_mentions(Am::new().everyone(true));
80 }
81 SerenityMessageMentionType::Here => {
82 message = message.allowed_mentions(Am::new().all_users(true).all_roles(true));
83 }
84 SerenityMessageMentionType::Users(user_ids) => {
85 message = message.allowed_mentions(Am::new().users(user_ids.clone()));
86 }
87 SerenityMessageMentionType::Roles(role_ids) => {
88 message =
89 message.allowed_mentions(Am::new().all_users(true).roles(role_ids.clone()));
90 }
91 SerenityMessageMentionType::Reply(ref_msg) => {
92 message = message.reference_message(&**ref_msg);
93 message = message.allowed_mentions(Am::new().replied_user(true));
94 }
95 }
96 }
97
98 if let Some(sticker_ids) = &self.sticker_ids {
99 message = message.sticker_ids(sticker_ids);
100 }
101
102 message = message.tts(self.tts);
103 Ok(message)
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 // TODO: Comparison Test with Serenity's CreateMessage.
110
111 use serenity::all::StickerId;
112
113 use super::*;
114 use crate::model::embed::SerenityEmbed;
115
116 static MOCK_TEST: &str = "This is a test message.";
117 static MOCK_STICKER_ID: u64 = 123456789012345678;
118
119 #[test]
120 fn test_message_conversion() {
121 let embed = SerenityEmbed::builder()
122 .title("Test Embed")
123 .description("This is a test embed description.")
124 .build();
125
126 // serenity-builder
127 let mock_message = SerenityMessage::builder()
128 .content(MOCK_TEST)
129 .embeds(vec![embed.clone()])
130 .mention_type(SerenityMessageMentionType::Everyone)
131 .sticker_ids(vec![StickerId::new(MOCK_STICKER_ID)])
132 .build();
133
134 let converted = mock_message.convert();
135 assert!(converted.is_ok());
136 }
137}