Skip to main content

twilight_http/request/channel/thread/create_forum_thread/
message.rs

1use super::{CreateForumThread, ForumThread};
2#[cfg(not(target_os = "wasi"))]
3use crate::response::{Response, ResponseFuture};
4use crate::{
5    Error,
6    request::{Nullable, TryIntoRequest, attachment::PartialAttachment},
7};
8use serde::Serialize;
9use std::{future::IntoFuture, mem};
10use twilight_model::{
11    channel::message::{AllowedMentions, Component, Embed, MessageFlags},
12    http::attachment::Attachment,
13    id::{Id, marker::StickerMarker},
14};
15use twilight_validate::message::{
16    MessageValidationError, attachment_filename as validate_attachment_filename,
17    components as validate_components, content as validate_content, embeds as validate_embeds,
18    sticker_ids as validate_sticker_ids,
19};
20
21/// Contents of the first message in the new forum thread.
22#[derive(Serialize)]
23pub(super) struct CreateForumThreadMessageFields<'a> {
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub(super) allowed_mentions: Option<Nullable<&'a AllowedMentions>>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub(super) attachments: Option<Vec<PartialAttachment<'a>>>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub(super) components: Option<&'a [Component]>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub(super) content: Option<&'a str>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub(super) embeds: Option<&'a [Embed]>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub(super) flags: Option<MessageFlags>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub(super) payload_json: Option<&'a [u8]>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub(super) sticker_ids: Option<&'a [Id<StickerMarker>]>,
40}
41
42#[must_use = "requests must be configured and executed"]
43pub struct CreateForumThreadMessage<'a>(Result<CreateForumThread<'a>, MessageValidationError>);
44
45impl<'a> CreateForumThreadMessage<'a> {
46    pub(super) const fn new(inner: CreateForumThread<'a>) -> Self {
47        Self(Ok(inner))
48    }
49
50    /// Specify the [`AllowedMentions`] for the message.
51    ///
52    /// Unless otherwise called, the request will use the client's default
53    /// allowed mentions. Set to `None` to ignore this default.
54    pub const fn allowed_mentions(mut self, allowed_mentions: Option<&'a AllowedMentions>) -> Self {
55        if let Ok(inner) = self.0.as_mut() {
56            inner.fields.message.allowed_mentions = Some(Nullable(allowed_mentions));
57        }
58
59        self
60    }
61
62    /// Attach multiple files to the message.
63    ///
64    /// Calling this method will clear any previous calls.
65    ///
66    /// # Errors
67    ///
68    /// Returns an error of type [`AttachmentFilename`] if any filename is
69    /// invalid.
70    ///
71    /// [`AttachmentFilename`]: twilight_validate::message::MessageValidationErrorType::AttachmentFilename
72    pub fn attachments(mut self, attachments: &'a [Attachment]) -> Self {
73        if self.0.is_ok() {
74            let validation = attachments
75                .iter()
76                .try_for_each(|attachment| validate_attachment_filename(&attachment.filename));
77
78            if let Err(source) = validation {
79                self.0 = Err(source);
80            } else if let Ok(inner) = self.0.as_mut() {
81                let mut manager = mem::take(&mut inner.attachment_manager);
82                manager = manager.set_files(attachments.iter().collect());
83
84                inner.attachment_manager = manager;
85            }
86        }
87
88        self
89    }
90
91    /// Set the message's list of [`Component`]s.
92    ///
93    /// Calling this method will clear previous calls.
94    ///
95    /// Requires a webhook owned by the application.
96    ///
97    /// # Errors
98    ///
99    /// Refer to the errors section of
100    /// [`twilight_validate::component::component`] for a list of errors that
101    /// may be returned as a result of validating each provided component.
102    pub fn components(mut self, components: &'a [Component]) -> Self {
103        self.0 = self.0.and_then(|mut inner| {
104            validate_components(
105                components,
106                inner
107                    .fields
108                    .message
109                    .flags
110                    .is_some_and(|f| f.contains(MessageFlags::IS_COMPONENTS_V2)),
111            )?;
112            inner.fields.message.components = Some(components);
113
114            Ok(inner)
115        });
116
117        self
118    }
119
120    /// Set the message's content.
121    ///
122    /// The maximum length is 2000 UTF-16 characters.
123    ///
124    /// # Errors
125    ///
126    /// Returns an error of type [`ContentInvalid`] if the content length is too
127    /// long.
128    ///
129    /// [`ContentInvalid`]: twilight_validate::message::MessageValidationErrorType::ContentInvalid
130    pub fn content(mut self, content: &'a str) -> Self {
131        self.0 = self.0.and_then(|mut inner| {
132            validate_content(content)?;
133            inner.fields.message.content = Some(content);
134
135            Ok(inner)
136        });
137
138        self
139    }
140
141    /// Set the message's list of embeds.
142    ///
143    /// Calling this method will clear previous calls.
144    ///
145    /// The amount of embeds must not exceed [`EMBED_COUNT_LIMIT`]. The total
146    /// character length of each embed must not exceed [`EMBED_TOTAL_LENGTH`]
147    /// characters. Additionally, the internal fields also have character
148    /// limits. Refer to [Discord Docs/Embed Limits] for more information.
149    ///
150    /// # Errors
151    ///
152    /// Returns an error of type [`TooManyEmbeds`] if there are too many embeds.
153    ///
154    /// Otherwise, refer to the errors section of
155    /// [`twilight_validate::embed::embed`] for a list of errors that may occur.
156    ///
157    /// [Discord Docs/Embed Limits]: https://discord.com/developers/docs/resources/channel#embed-limits
158    /// [`EMBED_COUNT_LIMIT`]: twilight_validate::message::EMBED_COUNT_LIMIT
159    /// [`EMBED_TOTAL_LENGTH`]: twilight_validate::embed::EMBED_TOTAL_LENGTH
160    /// [`TooManyEmbeds`]: twilight_validate::message::MessageValidationErrorType::TooManyEmbeds
161    pub fn embeds(mut self, embeds: &'a [Embed]) -> Self {
162        self.0 = self.0.and_then(|mut inner| {
163            validate_embeds(embeds)?;
164            inner.fields.message.embeds = Some(embeds);
165
166            Ok(inner)
167        });
168
169        self
170    }
171
172    /// Set the message's flags.
173    ///
174    /// The only supported flags are [`SUPPRESS_EMBEDS`] and
175    /// [`SUPPRESS_NOTIFICATIONS`].
176    ///
177    /// [`SUPPRESS_EMBEDS`]: MessageFlags::SUPPRESS_EMBEDS
178    /// [`SUPPRESS_NOTIFICATIONS`]: MessageFlags::SUPPRESS_NOTIFICATIONS
179    pub const fn flags(mut self, flags: MessageFlags) -> Self {
180        if let Ok(inner) = self.0.as_mut() {
181            inner.fields.message.flags = Some(flags);
182        }
183
184        self
185    }
186
187    /// JSON encoded body of any additional request fields.
188    ///
189    /// If this method is called, all other fields are ignored, except for
190    /// [`attachments`]. See [Discord Docs/Uploading Files].
191    ///
192    /// # Examples
193    ///
194    /// See [`ExecuteWebhook::payload_json`] for examples.
195    ///
196    /// [Discord Docs/Uploading Files]: https://discord.com/developers/docs/reference#uploading-files
197    /// [`ExecuteWebhook::payload_json`]: crate::request::channel::webhook::ExecuteWebhook::payload_json
198    /// [`attachments`]: Self::attachments
199    pub const fn payload_json(mut self, payload_json: &'a [u8]) -> Self {
200        if let Ok(inner) = self.0.as_mut() {
201            inner.fields.message.payload_json = Some(payload_json);
202        }
203
204        self
205    }
206
207    /// Set the IDs of up to 3 guild stickers.
208    ///
209    /// # Errors
210    ///
211    /// Returns an error of type [`StickersInvalid`] if the length is invalid.
212    ///
213    /// [`StickersInvalid`]: twilight_validate::message::MessageValidationErrorType::StickersInvalid
214    pub fn sticker_ids(mut self, sticker_ids: &'a [Id<StickerMarker>]) -> Self {
215        self.0 = self.0.and_then(|mut inner| {
216            validate_sticker_ids(sticker_ids)?;
217            inner.fields.message.sticker_ids = Some(sticker_ids);
218
219            Ok(inner)
220        });
221
222        self
223    }
224}
225
226#[cfg(not(target_os = "wasi"))]
227impl IntoFuture for CreateForumThreadMessage<'_> {
228    type Output = Result<Response<ForumThread>, Error>;
229
230    type IntoFuture = ResponseFuture<ForumThread>;
231
232    fn into_future(self) -> Self::IntoFuture {
233        match self.0 {
234            Ok(inner) => inner.exec(),
235            Err(source) => ResponseFuture::error(Error::validation(source)),
236        }
237    }
238}
239
240impl TryIntoRequest for CreateForumThreadMessage<'_> {
241    fn try_into_request(self) -> Result<crate::request::Request, Error> {
242        self.0
243            .map_err(Error::validation)
244            .and_then(CreateForumThread::try_into_request)
245    }
246}