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