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}