Skip to main content

botkit_core/
response.rs

1use crate::types::component::Component;
2use crate::types::embed::Embed;
3
4/// Unified bot response builder
5///
6/// Represents a response that can be sent back to any platform.
7/// Each platform adapter converts this to platform-specific format.
8pub struct Response {
9    kind: ResponseKind,
10}
11
12enum ResponseKind {
13    /// Empty response (no reply)
14    Empty,
15    /// Text message
16    Text(TextResponse),
17    /// Acknowledge without visible response (for deferred responses)
18    Acknowledge,
19    /// File attachment
20    File(FileResponse),
21}
22
23struct TextResponse {
24    content: String,
25    embeds: Vec<Embed>,
26    components: Vec<Component>,
27    ephemeral: bool,
28}
29
30/// File response data
31pub struct FileResponse {
32    /// The file to send
33    pub file: async_fs::File,
34    /// Optional filename (used for content-disposition)
35    pub filename: Option<String>,
36    /// Optional caption to accompany the file
37    pub caption: Option<String>,
38}
39
40impl Response {
41    /// Create an empty response (no reply)
42    pub fn empty() -> Self {
43        Self {
44            kind: ResponseKind::Empty,
45        }
46    }
47
48    /// Create a text response
49    pub fn text(content: impl Into<String>) -> Self {
50        Self {
51            kind: ResponseKind::Text(TextResponse {
52                content: content.into(),
53                embeds: Vec::new(),
54                components: Vec::new(),
55                ephemeral: false,
56            }),
57        }
58    }
59
60    /// Create an acknowledgement response (deferred)
61    pub fn acknowledge() -> Self {
62        Self {
63            kind: ResponseKind::Acknowledge,
64        }
65    }
66
67    /// Create a response with an embed
68    pub fn embed(embed: Embed) -> Self {
69        Self {
70            kind: ResponseKind::Text(TextResponse {
71                content: String::new(),
72                embeds: vec![embed],
73                components: Vec::new(),
74                ephemeral: false,
75            }),
76        }
77    }
78
79    /// Add an embed to this response
80    pub fn with_embed(mut self, embed: Embed) -> Self {
81        if let ResponseKind::Text(ref mut text) = self.kind {
82            text.embeds.push(embed);
83        }
84        self
85    }
86
87    /// Add components (buttons, select menus) to this response
88    pub fn with_components(mut self, components: Vec<Component>) -> Self {
89        if let ResponseKind::Text(ref mut text) = self.kind {
90            text.components = components;
91        }
92        self
93    }
94
95    /// Make this response ephemeral (only visible to the user)
96    pub fn ephemeral(mut self) -> Self {
97        if let ResponseKind::Text(ref mut text) = self.kind {
98            text.ephemeral = true;
99        }
100        self
101    }
102
103    /// Create a file response
104    pub fn file(file: async_fs::File) -> Self {
105        Self {
106            kind: ResponseKind::File(FileResponse {
107                file,
108                filename: None,
109                caption: None,
110            }),
111        }
112    }
113
114    /// Set the filename for a file response
115    pub fn with_filename(mut self, name: impl Into<String>) -> Self {
116        if let ResponseKind::File(ref mut f) = self.kind {
117            f.filename = Some(name.into());
118        }
119        self
120    }
121
122    /// Set the caption for a file response
123    pub fn with_caption(mut self, caption: impl Into<String>) -> Self {
124        if let ResponseKind::File(ref mut f) = self.kind {
125            f.caption = Some(caption.into());
126        }
127        self
128    }
129
130    /// Check if this is an empty response
131    pub fn is_empty(&self) -> bool {
132        matches!(self.kind, ResponseKind::Empty)
133    }
134
135    /// Check if this is an acknowledge response
136    pub fn is_acknowledge(&self) -> bool {
137        matches!(self.kind, ResponseKind::Acknowledge)
138    }
139
140    /// Get response content if this is a text response
141    pub fn content(&self) -> Option<&str> {
142        match &self.kind {
143            ResponseKind::Text(t) => Some(&t.content),
144            _ => None,
145        }
146    }
147
148    /// Get embeds if this is a text response
149    pub fn embeds(&self) -> &[Embed] {
150        match &self.kind {
151            ResponseKind::Text(t) => &t.embeds,
152            _ => &[],
153        }
154    }
155
156    /// Get components if this is a text response
157    pub fn components(&self) -> &[Component] {
158        match &self.kind {
159            ResponseKind::Text(t) => &t.components,
160            _ => &[],
161        }
162    }
163
164    /// Check if this response is ephemeral
165    pub fn is_ephemeral(&self) -> bool {
166        match &self.kind {
167            ResponseKind::Text(t) => t.ephemeral,
168            _ => false,
169        }
170    }
171
172    /// Check if this is a file response
173    pub fn is_file(&self) -> bool {
174        matches!(self.kind, ResponseKind::File(_))
175    }
176
177    /// Take the file response data, leaving Empty in its place
178    ///
179    /// This consumes the file data, so it can only be called once.
180    pub fn take_file(&mut self) -> Option<FileResponse> {
181        match std::mem::replace(&mut self.kind, ResponseKind::Empty) {
182            ResponseKind::File(f) => Some(f),
183            other => {
184                self.kind = other;
185                None
186            }
187        }
188    }
189}