twilight_http/request/application/interaction/
create_followup.rs1use crate::{
2 client::Client,
3 error::Error,
4 request::{
5 attachment::{AttachmentManager, PartialAttachment},
6 Nullable, Request, TryIntoRequest,
7 },
8 response::{Response, ResponseFuture},
9 routing::Route,
10};
11use serde::Serialize;
12use std::future::IntoFuture;
13use twilight_model::{
14 channel::message::{AllowedMentions, Component, Embed, Message, MessageFlags},
15 http::attachment::Attachment,
16 id::{marker::ApplicationMarker, Id},
17 poll::Poll,
18};
19use twilight_validate::message::{
20 attachment as validate_attachment, components as validate_components,
21 content as validate_content, embeds as validate_embeds, MessageValidationError,
22};
23
24#[derive(Serialize)]
25struct CreateFollowupFields<'a> {
26 #[serde(skip_serializing_if = "Option::is_none")]
27 allowed_mentions: Option<Nullable<&'a AllowedMentions>>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 attachments: Option<Vec<PartialAttachment<'a>>>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 components: Option<&'a [Component]>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 content: Option<&'a str>,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 embeds: Option<&'a [Embed]>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 payload_json: Option<&'a [u8]>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 tts: Option<bool>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 flags: Option<MessageFlags>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 poll: Option<Poll>,
44}
45
46#[must_use = "requests must be configured and executed"]
77pub struct CreateFollowup<'a> {
78 application_id: Id<ApplicationMarker>,
79 attachment_manager: AttachmentManager<'a>,
80 fields: Result<CreateFollowupFields<'a>, MessageValidationError>,
81 http: &'a Client,
82 token: &'a str,
83}
84
85impl<'a> CreateFollowup<'a> {
86 pub(crate) const fn new(
87 http: &'a Client,
88 application_id: Id<ApplicationMarker>,
89 token: &'a str,
90 ) -> Self {
91 Self {
92 application_id,
93 attachment_manager: AttachmentManager::new(),
94 fields: Ok(CreateFollowupFields {
95 allowed_mentions: None,
96 attachments: None,
97 components: None,
98 content: None,
99 embeds: None,
100 payload_json: None,
101 tts: None,
102 flags: None,
103 poll: None,
104 }),
105 http,
106 token,
107 }
108 }
109
110 pub fn allowed_mentions(mut self, allowed_mentions: Option<&'a AllowedMentions>) -> Self {
115 if let Ok(fields) = self.fields.as_mut() {
116 fields.allowed_mentions = Some(Nullable(allowed_mentions));
117 }
118
119 self
120 }
121
122 pub fn attachments(mut self, attachments: &'a [Attachment]) -> Self {
137 if self.fields.is_ok() {
138 if let Err(source) = attachments.iter().try_for_each(validate_attachment) {
139 self.fields = Err(source);
140 } else {
141 self.attachment_manager = self
142 .attachment_manager
143 .set_files(attachments.iter().collect());
144 }
145 }
146
147 self
148 }
149
150 pub fn components(mut self, components: &'a [Component]) -> Self {
160 self.fields = self.fields.and_then(|mut fields| {
161 validate_components(
162 components,
163 fields
164 .flags
165 .is_some_and(|flags| flags.contains(MessageFlags::IS_COMPONENTS_V2)),
166 )?;
167 fields.components = Some(components);
168
169 Ok(fields)
170 });
171
172 self
173 }
174
175 pub fn content(mut self, content: &'a str) -> Self {
186 self.fields = self.fields.and_then(|mut fields| {
187 validate_content(content)?;
188 fields.content = Some(content);
189
190 Ok(fields)
191 });
192
193 self
194 }
195
196 pub fn embeds(mut self, embeds: &'a [Embed]) -> Self {
217 self.fields = self.fields.and_then(|mut fields| {
218 validate_embeds(embeds)?;
219 fields.embeds = Some(embeds);
220
221 Ok(fields)
222 });
223
224 self
225 }
226
227 pub fn flags(mut self, flags: MessageFlags) -> Self {
235 if let Ok(fields) = self.fields.as_mut() {
236 fields.flags = Some(flags);
237 }
238
239 self
240 }
241
242 pub fn payload_json(mut self, payload_json: &'a [u8]) -> Self {
255 if let Ok(fields) = self.fields.as_mut() {
256 fields.payload_json = Some(payload_json);
257 }
258
259 self
260 }
261
262 pub fn tts(mut self, tts: bool) -> Self {
264 if let Ok(fields) = self.fields.as_mut() {
265 fields.tts = Some(tts);
266 }
267
268 self
269 }
270
271 pub fn poll(mut self, poll: Poll) -> Self {
273 if let Ok(fields) = self.fields.as_mut() {
274 fields.poll = Some(poll);
275 }
276
277 self
278 }
279}
280
281impl IntoFuture for CreateFollowup<'_> {
282 type Output = Result<Response<Message>, Error>;
283
284 type IntoFuture = ResponseFuture<Message>;
285
286 fn into_future(self) -> Self::IntoFuture {
287 let http = self.http;
288
289 match self.try_into_request() {
290 Ok(request) => http.request(request),
291 Err(source) => ResponseFuture::error(source),
292 }
293 }
294}
295
296impl TryIntoRequest for CreateFollowup<'_> {
297 fn try_into_request(self) -> Result<Request, Error> {
298 let mut fields = self.fields.map_err(Error::validation)?;
299 let mut request = Request::builder(&Route::ExecuteWebhook {
300 thread_id: None,
301 token: self.token,
302 wait: None,
303 with_components: Some(
304 fields
305 .components
306 .is_some_and(|components| !components.is_empty()),
307 ),
308 webhook_id: self.application_id.get(),
309 });
310
311 request = request.use_authorization_token(false);
314
315 if fields.allowed_mentions.is_none() {
317 if let Some(allowed_mentions) = self.http.default_allowed_mentions() {
318 fields.allowed_mentions = Some(Nullable(Some(allowed_mentions)));
319 }
320 }
321
322 if !self.attachment_manager.is_empty() {
325 let form = if let Some(payload_json) = fields.payload_json {
326 self.attachment_manager.build_form(payload_json)
327 } else {
328 fields.attachments = Some(self.attachment_manager.get_partial_attachments());
329
330 let fields = crate::json::to_vec(&fields).map_err(Error::json)?;
331
332 self.attachment_manager.build_form(fields.as_ref())
333 };
334
335 request = request.form(form);
336 } else if let Some(payload_json) = fields.payload_json {
337 request = request.body(payload_json.to_vec());
338 } else {
339 request = request.json(&fields);
340 }
341
342 request.build()
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use crate::{client::Client, request::TryIntoRequest};
349 use std::error::Error;
350 use twilight_http_ratelimiting::Path;
351 use twilight_model::id::Id;
352
353 #[test]
354 fn create_followup_message() -> Result<(), Box<dyn Error>> {
355 let application_id = Id::new(1);
356 let token = "foo".to_owned();
357
358 let client = Client::new(String::new());
359 let req = client
360 .interaction(application_id)
361 .create_followup(&token)
362 .content("test")
363 .try_into_request()?;
364
365 assert!(!req.use_authorization_token());
366 assert_eq!(
367 &Path::WebhooksIdToken(application_id.get(), token),
368 req.ratelimit_path()
369 );
370
371 Ok(())
372 }
373}