slashook/commands/responder.rs
1// Copyright 2025 slashook Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use crate::structs::{
9 components::{Component, Components},
10 embeds::Embed,
11 interactions::{ApplicationCommandOptionChoice, Attachments, InteractionCallbackData},
12 messages::{AllowedMentions, Attachment, Message, MessageFlags, MessageReference},
13 polls::PollCreateRequest,
14 utils::File, Snowflake,
15};
16use serde::Serialize;
17use crate::tokio::sync::mpsc;
18use crate::rest::{Rest, RestError};
19
20/// Error for when a response failed due to the interaction having been responded to already.
21#[derive(Debug)]
22pub struct InteractionResponseError;
23impl std::fmt::Display for InteractionResponseError {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 write!(f, "Interaction has already been responded to.")
26 }
27}
28impl std::error::Error for InteractionResponseError { }
29
30/// Message that can be sent as a response to a command or other interaction
31///
32/// This struct can be easily constructed from a `str`, `String`, [`Embed`](crate::structs::embeds::Embed), [`Components`](crate::structs::components::Components),
33/// [`File`](crate::structs::utils::File) or [`PollCreateRequest`](crate::structs::polls::PollCreateRequest)
34/// with the `From` trait
35#[derive(Serialize, Clone, Debug)]
36pub struct MessageResponse {
37 /// Whether the response is TTS
38 pub tts: Option<bool>,
39 /// Message content (up to 2000 characters)
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub content: Option<String>,
42 /// Up to 10 embeds (up to 6000 characters)
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub embeds: Option<Vec<Embed>>,
45 /// [Allowed mentions](AllowedMentions) object
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub allowed_mentions: Option<AllowedMentions>,
48 /// [Message flags](MessageFlags) combined as a bitfield
49 /// (only [SUPPRESS_EMBEDS](MessageFlags::SUPPRESS_EMBEDS), [EPHEMERAL](MessageFlags::EPHEMERAL), and [SUPPRESS_NOTIFICATIONS](MessageFlags::SUPPRESS_NOTIFICATIONS) can be set for interaction responses)
50 pub flags: Option<MessageFlags>,
51 /// Message components
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub components: Option<Vec<Component>>,
54 /// Attachment objects with filename and description.\
55 /// Use `files` if you want to upload an attachment.\
56 /// This is for partial attachment objects indicating which to keep when editing.
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub attachments: Option<Vec<Attachment>>,
59 /// Details about the poll
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub poll: Option<PollCreateRequest>,
62 /// Include to make your message a reply or a forward (not available for interaction responses)
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub message_reference: Option<MessageReference>,
65 /// IDs of up to 3 stickers in the server to send in the message
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub sticker_ids: Option<Vec<Snowflake>>,
68 /// Up to 10 files to send with the response
69 #[serde(skip_serializing)]
70 pub files: Option<Vec<File>>,
71}
72
73impl MessageResponse {
74 /// Set the value of tts for the message
75 /// ```
76 /// # use slashook::commands::MessageResponse;
77 /// let response = MessageResponse::from("This message is text to speech")
78 /// .set_tts(true);
79 /// assert_eq!(response.tts, Some(true));
80 /// ```
81 pub fn set_tts(mut self, tts: bool) -> Self {
82 self.tts = Some(tts);
83 self
84 }
85
86 /// Set the content of the message
87 /// ```
88 /// # use slashook::commands::MessageResponse;
89 /// let response = MessageResponse::from("This content will be replaced")
90 /// .set_content("I rule the world!");
91 /// assert_eq!(response.content, Some(String::from("I rule the world!")));
92 /// ```
93 pub fn set_content<T: ToString>(mut self, content: T) -> Self {
94 self.content = Some(content.to_string());
95 self
96 }
97
98 /// Add an embed to the message
99 /// ```
100 /// # use slashook::commands::MessageResponse;
101 /// # use slashook::structs::embeds::Embed;
102 /// let embed = Embed::new().set_description("This is an embed");
103 /// let response = MessageResponse::from("Look at my embed:")
104 /// .add_embed(embed);
105 /// assert_eq!(response.embeds.unwrap()[0].description, Some(String::from("This is an embed")));
106 /// ```
107 pub fn add_embed(mut self, embed: Embed) -> Self {
108 let mut embeds = self.embeds.unwrap_or_default();
109 embeds.push(embed);
110 self.embeds = Some(embeds);
111 self
112 }
113
114 /// Clear embeds from the message. Sets embeds to an empty Vec which also clears embeds when editing.
115 /// ```
116 /// # use slashook::commands::MessageResponse;
117 /// let response = MessageResponse::from("Embeds cleared")
118 /// .clear_embeds();
119 /// assert_eq!(response.embeds.unwrap().len(), 0);
120 /// ```
121 pub fn clear_embeds(mut self) -> Self {
122 self.embeds = Some(Vec::new());
123 self
124 }
125
126 /// Set the allowed mentions for the message
127 /// ```
128 /// # use slashook::commands::MessageResponse;
129 /// # use slashook::structs::messages::{AllowedMentions, AllowedMentionType};
130 /// let allowed_mentions = AllowedMentions::new().add_parse(AllowedMentionType::USERS);
131 /// let response = MessageResponse::from("<@1234> Get pinged. Not @everyone or <@&1235> tho.")
132 /// .set_allowed_mentions(allowed_mentions);
133 /// ```
134 pub fn set_allowed_mentions(mut self, allowed_mentions: AllowedMentions) -> Self {
135 self.allowed_mentions = Some(allowed_mentions);
136 self
137 }
138
139 /// Set the ephemeralness of the message
140 /// ```
141 /// # use slashook::commands::MessageResponse;
142 /// # use slashook::structs::messages::MessageFlags;
143 /// let response = MessageResponse::from("This is for your eyes only!")
144 /// .set_ephemeral(true);
145 /// assert_eq!(response.flags.unwrap().contains(MessageFlags::EPHEMERAL), true);
146 /// ```
147 pub fn set_ephemeral(mut self, ephemeral: bool) -> Self {
148 let mut flags = self.flags.unwrap_or_else(MessageFlags::empty);
149 flags.set(MessageFlags::EPHEMERAL, ephemeral);
150 self.flags = Some(flags);
151 self
152 }
153
154 /// Set suppress embeds flag
155 /// ```
156 /// # use slashook::commands::MessageResponse;
157 /// # use slashook::structs::messages::MessageFlags;
158 /// let response = MessageResponse::from("No embeds here")
159 /// .set_suppress_embeds(true);
160 /// assert_eq!(response.flags.unwrap().contains(MessageFlags::SUPPRESS_EMBEDS), true);
161 /// ```
162 pub fn set_suppress_embeds(mut self, suppress: bool) -> Self {
163 let mut flags = self.flags.unwrap_or_else(MessageFlags::empty);
164 flags.set(MessageFlags::SUPPRESS_EMBEDS, suppress);
165 self.flags = Some(flags);
166 self
167 }
168
169 /// Set suppress notifications flag
170 /// ```
171 /// # use slashook::commands::MessageResponse;
172 /// # use slashook::structs::messages::MessageFlags;
173 /// let response = MessageResponse::from("No notifications @here")
174 /// .set_suppress_notifications(true);
175 /// assert_eq!(response.flags.unwrap().contains(MessageFlags::SUPPRESS_NOTIFICATIONS), true);
176 /// ```
177 pub fn set_suppress_notifications(mut self, suppress: bool) -> Self {
178 let mut flags = self.flags.unwrap_or_else(MessageFlags::empty);
179 flags.set(MessageFlags::SUPPRESS_NOTIFICATIONS, suppress);
180 self.flags = Some(flags);
181 self
182 }
183
184 /// Set voice message flag
185 /// ```no_run
186 /// # use slashook::commands::{MessageResponse, CmdResult};
187 /// # use slashook::structs::{messages::MessageFlags, utils::File};
188 /// # use slashook::tokio::fs::File as TokioFile;
189 /// # #[slashook::main]
190 /// # async fn main() -> CmdResult {
191 /// let file = TokioFile::open("audio.ogg").await?;
192 /// let audio_file = File::from_file("audio.ogg", file).await?
193 /// .set_duration_secs(1.1799999475479126)
194 /// .set_waveform("AAM1YAAAAAAAAAA=");
195 /// let response = MessageResponse::from(audio_file).set_as_voice_message(true);
196 /// assert_eq!(response.flags.unwrap().contains(MessageFlags::IS_VOICE_MESSAGE), true);
197 /// # Ok(())
198 /// # }
199 /// ```
200 pub fn set_as_voice_message(mut self, is_voice_message: bool) -> Self {
201 let mut flags = self.flags.unwrap_or_else(MessageFlags::empty);
202 flags.set(MessageFlags::IS_VOICE_MESSAGE, is_voice_message);
203 self.flags = Some(flags);
204 self
205 }
206
207 /// Set the components on the message
208 /// ```
209 /// # use slashook::commands::MessageResponse;
210 /// # use slashook::structs::components::{Components, Button, ButtonStyle};
211 /// let button = Button::new()
212 /// .set_style(ButtonStyle::DANGER)
213 /// .set_label("Do not press!")
214 /// .set_id("example_button", "danger");
215 /// let components = Components::new().add_button(button);
216 /// let response = MessageResponse::from("Ooh! A big red button!")
217 /// .set_components(components);
218 /// ```
219 pub fn set_components(mut self, components: Components) -> Self {
220 self.components = Some(components.0);
221 self
222 }
223
224 /// Add a file to be sent with the message
225 /// ```no_run
226 /// # use slashook::commands::{MessageResponse, CmdResult};
227 /// # use slashook::structs::utils::File;
228 /// use slashook::tokio::fs::File as TokioFile;
229 /// # #[slashook::main]
230 /// # async fn main() -> CmdResult {
231 /// let file = TokioFile::open("cat.png").await?;
232 /// let msg_file = File::from_file("cat.png", file).await?
233 /// .set_description("Picture of my cute cat!");
234 /// let response = MessageResponse::from("Here's a picture of my cat")
235 /// .add_file(msg_file);
236 /// # Ok(())
237 /// # }
238 /// ```
239 pub fn add_file(mut self, file: File) -> Self {
240 let mut files = self.files.unwrap_or_default();
241 files.push(file);
242 self.files = Some(files);
243 self
244 }
245
246 /// Keep an existing attachment when editing
247 /// ```
248 /// # #[macro_use] extern crate slashook;
249 /// # use slashook::commands::{CommandInput, CommandResponder};
250 /// # use slashook::commands::MessageResponse;
251 /// # use slashook::structs::utils::File;
252 /// # use slashook::tokio::fs::File as TokioFile;
253 /// # #[command(name = "example", description = "An example command")]
254 /// # fn example(input: CommandInput, res: CommandResponder) {
255 /// let msg_file = File::from_file("cat.png", TokioFile::open("cat.png").await?).await?;
256 /// let msg_file2 = File::from_file("cat2.png", TokioFile::open("cat2.png").await?).await?;
257 ///
258 /// res.defer(false).await?;
259 ///
260 /// let response = MessageResponse::from("Here's a picture of my cat")
261 /// .add_file(msg_file);
262 /// let msg = res.send_followup_message(response).await?;
263 ///
264 /// let edit_response = MessageResponse::from("And I added the other cat too!")
265 /// .keep_attachment(&msg.attachments.get(0).unwrap().id)
266 /// .add_file(msg_file2);
267 /// res.edit_original_message(edit_response).await?;
268 /// # }
269 /// ```
270 pub fn keep_attachment<T: ToString>(mut self, attachment_id: T) -> Self {
271 let mut attachments = self.attachments.unwrap_or_default();
272 attachments.push(Attachment::keep_with_id(attachment_id));
273 self.attachments = Some(attachments);
274 self
275 }
276
277 /// Clear attachments from the message. Sets attachments to an empty Vec which also deletes attachments when editing.
278 /// ```
279 /// # use slashook::commands::MessageResponse;
280 /// let response = MessageResponse::from("Attachments deleted")
281 /// .clear_attachments();
282 /// assert_eq!(response.attachments.unwrap().len(), 0);
283 /// ```
284 pub fn clear_attachments(mut self) -> Self {
285 self.attachments = Some(Vec::new());
286 self
287 }
288
289 /// Add a poll to the message
290 /// ```
291 /// # use slashook::commands::MessageResponse;
292 /// # use slashook::structs::{polls::{PollCreateRequest, PollAnswer}, Emoji};
293 /// let response = MessageResponse::from("This message will contain a poll!")
294 /// .set_poll(PollCreateRequest::new("Is this a good poll?")
295 /// .add_answer(PollAnswer::new().set_text("Yes").set_emoji(Emoji::new_standard_emoji("✅")))
296 /// .add_answer(PollAnswer::from("No").set_emoji(Emoji::new_custom_emoji("567088349484023818", "redtick", false)))
297 /// .add_answer("Maybe")
298 /// .set_duration(1)
299 /// );
300 /// ```
301 pub fn set_poll(mut self, poll: PollCreateRequest) -> Self {
302 self.poll = Some(poll);
303 self
304 }
305
306 /// Set a reply or forward
307 /// ```
308 /// # use slashook::commands::MessageResponse;
309 /// # use slashook::structs::messages::MessageReference;
310 /// let response = MessageResponse::from("This is a reply")
311 /// .set_message_reference(MessageReference::new_reply("916413462467465246"));
312 /// assert_eq!(response.message_reference.unwrap().message_id, Some(String::from("916413462467465246")));
313 /// ```
314 pub fn set_message_reference(mut self, message_reference: MessageReference) -> Self {
315 self.message_reference = Some(message_reference);
316 self
317 }
318
319 /// Add a sticker to the message
320 /// ```
321 /// # use slashook::commands::MessageResponse;
322 /// let response = MessageResponse::from(":)")
323 /// .add_sticker("749044136589393960");
324 /// assert_eq!(response.sticker_ids.unwrap().remove(0), String::from("749044136589393960"));
325 /// ```
326 pub fn add_sticker<T: ToString>(mut self, sticker_id: T) -> Self {
327 let mut sticker_ids = self.sticker_ids.unwrap_or_default();
328 sticker_ids.push(sticker_id.to_string());
329 self.sticker_ids = Some(sticker_ids);
330 self
331 }
332}
333
334/// A modal that can be opened for user input
335#[derive(Clone, Debug)]
336pub struct Modal {
337 /// a developer-defined identifier for the component, max 100 characters
338 pub custom_id: String,
339 /// The title of the popup modal
340 pub title: String,
341 /// The components that make up the modal
342 pub components: Vec<Component>
343}
344
345impl Modal {
346 /// Creates a new modal.\
347 /// The command argument is used by the library to choose which command to run when the modal is submitted.
348 /// The custom_id is formatted as `command/id`
349 /// ```
350 /// # use slashook::commands::Modal;
351 /// let modal = Modal::new("example_command", "modal1", "Please fill this form");
352 /// ```
353 pub fn new<T: ToString, U: ToString, V: ToString>(command: T, id: U, title: V) -> Self {
354 Self {
355 custom_id: format!("{}/{}", command.to_string(), id.to_string()),
356 title: title.to_string(),
357 components: Vec::new()
358 }
359 }
360
361 /// Set the components on the modal
362 /// ```
363 /// # use slashook::commands::Modal;
364 /// # use slashook::structs::components::{Components, TextInput};
365 /// let text_input = TextInput::new()
366 /// .set_label("Tell us something")
367 /// .set_id("input");
368 /// let components = Components::new().add_text_input(text_input);
369 /// let modal = Modal::new("example_command", "modal1", "Please fill this form")
370 /// .set_components(components);
371 /// ```
372 pub fn set_components(mut self, components: Components) -> Self {
373 self.components = components.0;
374 self
375 }
376}
377
378#[derive(Debug)]
379pub enum CommandResponse {
380 DeferMessage(MessageFlags),
381 SendMessage(MessageResponse),
382 DeferUpdate,
383 UpdateMessage(MessageResponse),
384 AutocompleteResult(Vec<ApplicationCommandOptionChoice>),
385 Modal(Modal),
386 LaunchActivity,
387}
388
389/// Struct with methods for responding to interactions
390#[derive(Debug)]
391pub struct CommandResponder {
392 pub(crate) tx: mpsc::UnboundedSender<CommandResponse>,
393 pub(crate) id: String,
394 pub(crate) token: String,
395 pub(crate) rest: Rest
396}
397
398impl CommandResponder {
399 /// Respond to an interaction with a message.\
400 /// If interaction has already been responded to, this function will call [`send_followup_message`](CommandResponder::send_followup_message) instead and a message can only be returned in this case.
401 /// ```
402 /// # #[macro_use] extern crate slashook;
403 /// # use slashook::commands::{CommandInput, CommandResponder};
404 /// ##[command(name = "example", description = "An example command")]
405 /// fn example(input: CommandInput, res: CommandResponder) {
406 /// res.send_message("Hello!").await?;
407 /// }
408 /// ```
409 pub async fn send_message<T: Into<MessageResponse>>(&self, response: T) -> Result<Option<Message>, RestError> {
410 let response = response.into();
411 match self.tx.send(CommandResponse::SendMessage(response)) {
412 Ok(_) => {
413 self.tx.closed().await;
414 Ok(None)
415 },
416 Err(err) => {
417 if let CommandResponse::SendMessage(response) = err.0 {
418 return self.send_followup_message(response).await.map(Some);
419 }
420 Ok(None)
421 }
422 }
423 }
424
425 /// Respond to an interaction by editing the original message.\
426 /// If interaction has already been responded to, this function will call [`edit_original_message`](CommandResponder::edit_original_message) instead and a message can only be returned in this case.
427 /// ```
428 /// # #[macro_use] extern crate slashook;
429 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse};
430 /// # use slashook::structs::components::{Components, Button};
431 /// ##[command(name = "example_button", ignore = true)]
432 /// fn example(input: CommandInput, res: CommandResponder) {
433 /// res.update_message("Button was clicked!").await?;
434 /// }
435 /// ```
436 pub async fn update_message<T: Into<MessageResponse>>(&self, response: T) -> Result<Option<Message>, RestError> {
437 let response = response.into();
438 match self.tx.send(CommandResponse::UpdateMessage(response)) {
439 Ok(_) => {
440 self.tx.closed().await;
441 Ok(None)
442 },
443 Err(err) => {
444 if let CommandResponse::UpdateMessage(response) = err.0 {
445 return self.edit_original_message(response).await.map(Some);
446 }
447 Ok(None)
448 }
449 }
450 }
451
452 /// Give yourself more execution time.\
453 /// If you don't respond within 3 seconds, Discord will disconnect and tell the user the interaction failed to run.
454 /// By deferring, Discord will tell the user your bot is "thinking" and allow you to take your time. You can use the `send_followup_message` or `edit_original_message` methods to send the response.\
455 /// The ephemeralness set here will be passed on to your first follow-up, no matter what ephemeralness you set there.
456 /// ```
457 /// # #[macro_use] extern crate slashook;
458 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse};
459 /// ##[command(name = "example", description = "An example command")]
460 /// fn example(input: CommandInput, res: CommandResponder) {
461 /// res.defer(false).await?;
462 /// // Do something that takes longer than 3s
463 /// res.send_followup_message("Thank you for your patience!").await?;
464 /// }
465 /// ```
466 pub async fn defer(&self, ephemeral: bool) -> Result<(), InteractionResponseError> {
467 let mut flags = MessageFlags::empty();
468 flags.set(MessageFlags::EPHEMERAL, ephemeral);
469 self.tx.send(CommandResponse::DeferMessage(flags)).map_err(|_| InteractionResponseError)?;
470 self.tx.closed().await;
471 Ok(())
472 }
473
474 /// Much like `defer` but for component interactions and it shows nothing visibly to the user.
475 /// ```
476 /// # #[macro_use] extern crate slashook;
477 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse};
478 /// ##[command(name = "example_button", ignore = true)]
479 /// fn example(input: CommandInput, res: CommandResponder) {
480 /// res.defer_update().await?;
481 /// // Do something that takes longer than 3s
482 /// res.edit_original_message("Finally it changed!").await?;
483 /// }
484 /// ```
485 pub async fn defer_update(&self) -> Result<(), InteractionResponseError> {
486 self.tx.send(CommandResponse::DeferUpdate).map_err(|_| InteractionResponseError)?;
487 self.tx.closed().await;
488 Ok(())
489 }
490
491 /// Respond to an autocomplete interaction with autocomplete choices
492 /// ```
493 /// # #[macro_use] extern crate slashook;
494 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse};
495 /// # use slashook::structs::interactions::{ApplicationCommandOptionChoice, InteractionOptionType};
496 /// ##[command(name = "example", description = "An example command", options = [{
497 /// name = "choice", description = "Choose an option",
498 /// autocomplete = true, option_type = InteractionOptionType::STRING
499 /// }])]
500 /// fn example(input: CommandInput, res: CommandResponder) {
501 /// if input.is_autocomplete() {
502 /// let search = input.args.get(&input.focused.unwrap()).unwrap().as_string().unwrap();
503 /// // Use the current input to fetch or filter choices
504 /// let choices = vec![
505 /// ApplicationCommandOptionChoice::new("An autocompleted choice", "autocomplete1"),
506 /// ApplicationCommandOptionChoice::new("Another autocompleted choice", "autocomplete2")
507 /// ];
508 /// return res.autocomplete(choices).await?;
509 /// }
510 /// }
511 /// ```
512 pub async fn autocomplete(&self, results: Vec<ApplicationCommandOptionChoice>) -> Result<(), InteractionResponseError> {
513 self.tx.send(CommandResponse::AutocompleteResult(results)).map_err(|_| InteractionResponseError)?;
514 self.tx.closed().await;
515 Ok(())
516 }
517
518 /// Respond to an interaction with a modal
519 /// ```
520 /// # #[macro_use] extern crate slashook;
521 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse, Modal};
522 /// # use slashook::structs::components::{Components, TextInput};
523 /// ##[command(name = "example", description = "An example command")]
524 /// fn example(input: CommandInput, res: CommandResponder) {
525 /// let text_input = TextInput::new()
526 /// .set_label("Tell us something")
527 /// .set_id("input");
528 /// let components = Components::new().add_text_input(text_input);
529 /// let modal = Modal::new("example_command", "modal1", "Please fill this form")
530 /// .set_components(components);
531 /// return res.open_modal(modal).await?;
532 /// }
533 /// ```
534 pub async fn open_modal(&self, modal: Modal) -> Result<(), InteractionResponseError> {
535 self.tx.send(CommandResponse::Modal(modal)).map_err(|_| InteractionResponseError)?;
536 self.tx.closed().await;
537 Ok(())
538 }
539
540 /// Respond to an interaction by launching the activity associated with the app.
541 /// ```
542 /// # #[macro_use] extern crate slashook;
543 /// # use slashook::commands::{CommandInput, CommandResponder};
544 /// # use slashook::structs::interactions::{ApplicationCommandType, ApplicationCommandHandlerType};
545 /// ##[command(
546 /// name = "launch",
547 /// command_type = ApplicationCommandType::PRIMARY_ENTRY_POINT,
548 /// handler = ApplicationCommandHandlerType::APP_HANDLER
549 /// )]
550 /// fn launch(input: CommandInput, res: CommandResponder) {
551 /// return res.launch_activity().await?;
552 /// }
553 /// ```
554 pub async fn launch_activity(&self) -> Result<(), InteractionResponseError> {
555 self.tx.send(CommandResponse::LaunchActivity).map_err(|_| InteractionResponseError)?;
556 self.tx.closed().await;
557 Ok(())
558 }
559
560 /// Send more messages after the initial response
561 /// ```
562 /// # #[macro_use] extern crate slashook;
563 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse};
564 /// ##[command(name = "example", description = "An example command")]
565 /// fn example(input: CommandInput, res: CommandResponder) {
566 /// res.send_message("First message!").await?;
567 /// res.send_followup_message("Second message!").await?;
568 /// }
569 /// ```
570 pub async fn send_followup_message<T: Into<MessageResponse>>(&self, response: T) -> Result<Message, RestError> {
571 let mut response = response.into();
572 let files = response.files.take();
573 let msg: InteractionCallbackData = response.into();
574 let path = format!("webhooks/{}/{}", self.id, self.token);
575 if let Some(files) = files {
576 self.rest.post_files(path, msg, files).await
577 } else {
578 self.rest.post(path, msg).await
579 }
580 }
581
582 /// Edits a follow-up message
583 /// ```
584 /// # #[macro_use] extern crate slashook;
585 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse};
586 /// ##[command(name = "example", description = "An example command")]
587 /// fn example(input: CommandInput, res: CommandResponder) {
588 /// res.send_message("First message!").await?;
589 /// let msg = res.send_followup_message("Second message!").await?;
590 /// res.edit_followup_message(msg.id.unwrap(), "Second message but edited!").await?;
591 /// }
592 /// ```
593 pub async fn edit_followup_message<T: Into<MessageResponse>>(&self, id: String, response: T) -> Result<Message, RestError> {
594 let mut response = response.into();
595 let files = response.files.take();
596 let msg: InteractionCallbackData = response.into();
597 let path = format!("webhooks/{}/{}/messages/{}", self.id, self.token, id);
598 if let Some(files) = files {
599 self.rest.patch_files(path, msg, files).await
600 } else {
601 self.rest.patch(path, msg).await
602 }
603 }
604
605 /// Edits the original message\
606 /// Same as running `edit_followup_message` with id of `@original`
607 pub async fn edit_original_message<T: Into<MessageResponse>>(&self, response: T) -> Result<Message, RestError> {
608 self.edit_followup_message(String::from("@original"), response).await
609 }
610
611 /// Gets a follow-up message
612 pub async fn get_followup_message(&self, id: String) -> Result<Message, RestError> {
613 self.rest.get(format!("webhooks/{}/{}/messages/{}", self.id, self.token, id)).await
614 }
615
616 /// Gets the original message\
617 /// Same as running `get_followup_message` with id of `@original`
618 /// ```
619 /// # #[macro_use] extern crate slashook;
620 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse};
621 /// ##[command(name = "example", description = "An example command")]
622 /// fn example(input: CommandInput, res: CommandResponder) {
623 /// res.send_message("First message!").await?;
624 /// let msg = res.get_original_message().await?;
625 /// println!("I responded with {}", msg.content);
626 /// }
627 /// ```
628 pub async fn get_original_message(&self) -> Result<Message, RestError> {
629 self.get_followup_message(String::from("@original")).await
630 }
631
632 /// Deletes a follow-up message
633 /// ```
634 /// # #[macro_use] extern crate slashook;
635 /// # use slashook::commands::{CommandInput, CommandResponder, MessageResponse};
636 /// ##[command(name = "example", description = "An example command")]
637 /// fn example(input: CommandInput, res: CommandResponder) {
638 /// res.send_message("First message!").await?;
639 /// let msg = res.send_followup_message("If you see me say hi").await?;
640 /// res.delete_followup_message(msg.id.unwrap()).await?;
641 /// }
642 /// ```
643 pub async fn delete_followup_message(&self, id: String) -> Result<(), RestError> {
644 self.rest.delete(format!("webhooks/{}/{}/messages/{}", self.id, self.token, id)).await
645 }
646
647 /// Deletes the original message\
648 /// Same as running `delete_followup_message` with id of `@original`
649 pub async fn delete_original_message(&self) -> Result<(), RestError> {
650 self.delete_followup_message(String::from("@original")).await
651 }
652}
653
654impl From<&str> for MessageResponse {
655 fn from(s: &str) -> MessageResponse {
656 MessageResponse {
657 tts: Some(false),
658 content: Some(String::from(s)),
659 embeds: None,
660 allowed_mentions: None,
661 flags: None,
662 components: None,
663 attachments: None,
664 poll: None,
665 message_reference: None,
666 sticker_ids: None,
667 files: None,
668 }
669 }
670}
671
672impl From<String> for MessageResponse {
673 fn from(s: String) -> MessageResponse {
674 MessageResponse {
675 tts: Some(false),
676 content: Some(s),
677 embeds: None,
678 allowed_mentions: None,
679 flags: None,
680 components: None,
681 attachments: None,
682 poll: None,
683 message_reference: None,
684 sticker_ids: None,
685 files: None,
686 }
687 }
688}
689
690impl From<Embed> for MessageResponse {
691 fn from(e: Embed) -> MessageResponse {
692 MessageResponse {
693 tts: Some(false),
694 content: None,
695 embeds: Some(vec![e]),
696 allowed_mentions: None,
697 flags: None,
698 components: None,
699 attachments: None,
700 poll: None,
701 message_reference: None,
702 sticker_ids: None,
703 files: None,
704 }
705 }
706}
707
708impl From<Vec<Embed>> for MessageResponse {
709 fn from(e: Vec<Embed>) -> MessageResponse {
710 MessageResponse {
711 tts: Some(false),
712 content: None,
713 embeds: Some(e),
714 allowed_mentions: None,
715 flags: None,
716 components: None,
717 attachments: None,
718 poll: None,
719 message_reference: None,
720 sticker_ids: None,
721 files: None,
722 }
723 }
724}
725
726impl From<Components> for MessageResponse {
727 fn from(c: Components) -> MessageResponse {
728 MessageResponse {
729 tts: Some(false),
730 content: None,
731 embeds: None,
732 allowed_mentions: None,
733 flags: None,
734 components: Some(c.0),
735 attachments: None,
736 poll: None,
737 message_reference: None,
738 sticker_ids: None,
739 files: None,
740 }
741 }
742}
743
744impl From<File> for MessageResponse {
745 fn from(f: File) -> MessageResponse {
746 MessageResponse {
747 tts: Some(false),
748 content: None,
749 embeds: None,
750 allowed_mentions: None,
751 flags: None,
752 components: None,
753 attachments: None,
754 poll: None,
755 message_reference: None,
756 sticker_ids: None,
757 files: Some(vec![f]),
758 }
759 }
760}
761
762impl From<Vec<File>> for MessageResponse {
763 fn from(f: Vec<File>) -> MessageResponse {
764 MessageResponse {
765 tts: Some(false),
766 content: None,
767 embeds: None,
768 allowed_mentions: None,
769 flags: None,
770 components: None,
771 attachments: None,
772 poll: None,
773 message_reference: None,
774 sticker_ids: None,
775 files: Some(f),
776 }
777 }
778}
779
780impl From<PollCreateRequest> for MessageResponse {
781 fn from(poll: PollCreateRequest) -> MessageResponse {
782 MessageResponse {
783 tts: Some(false),
784 content: None,
785 embeds: None,
786 allowed_mentions: None,
787 flags: None,
788 components: None,
789 attachments: None,
790 poll: Some(poll),
791 message_reference: None,
792 sticker_ids: None,
793 files: None,
794 }
795 }
796}
797
798impl From<MessageReference> for MessageResponse {
799 fn from(reference: MessageReference) -> MessageResponse {
800 MessageResponse {
801 tts: Some(false),
802 content: None,
803 embeds: None,
804 allowed_mentions: None,
805 flags: None,
806 components: None,
807 attachments: None,
808 poll: None,
809 message_reference: Some(reference),
810 sticker_ids: None,
811 files: None,
812 }
813 }
814}
815
816impl Attachments for MessageResponse {
817 fn take_attachments(&mut self) -> Vec<Attachment> {
818 self.attachments.take().unwrap_or_default()
819 }
820
821 fn set_attachments(&mut self, attachments: Vec<Attachment>) -> &mut Self {
822 self.attachments = Some(attachments);
823 self
824 }
825}