Skip to main content

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

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