Skip to main content

vk_bot_api/
keyboard.rs

1use serde::{Deserialize, Serialize};
2use serde_json::{Value, json};
3use std::fmt;
4
5/// Button action type
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(tag = "type")]
8pub enum ButtonAction {
9    /// Text button
10    #[serde(rename = "text")]
11    Text {
12        /// Button label
13        label: String,
14        /// Button payload
15        #[serde(skip_serializing_if = "Option::is_none")]
16        payload: Option<Value>,
17    },
18    /// Callback button
19    #[serde(rename = "callback")]
20    Callback {
21        /// Button label
22        label: String,
23        /// Button payload
24        payload: Value,
25    },
26    /// Link button
27    #[serde(rename = "open_link")]
28    OpenLink {
29        /// Link URL
30        link: String,
31        /// Button label
32        label: String,
33        /// Button payload
34        #[serde(skip_serializing_if = "Option::is_none")]
35        payload: Option<Value>,
36    },
37    /// Location button
38    #[serde(rename = "location")]
39    Location {
40        /// Button payload
41        #[serde(skip_serializing_if = "Option::is_none")]
42        payload: Option<Value>,
43    },
44    /// VK Pay button
45    #[serde(rename = "vkpay")]
46    VkPay {
47        /// Payment hash
48        hash: String,
49        /// Button payload
50        #[serde(skip_serializing_if = "Option::is_none")]
51        payload: Option<Value>,
52    },
53    /// Open app button
54    #[serde(rename = "open_app")]
55    OpenApp {
56        /// App ID
57        app_id: i64,
58        /// App owner ID
59        owner_id: i64,
60        /// Button label
61        label: String,
62        /// App hash
63        hash: String,
64        /// Button payload
65        #[serde(skip_serializing_if = "Option::is_none")]
66        payload: Option<Value>,
67    },
68}
69
70/// Button color
71#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
72#[serde(rename_all = "snake_case")]
73pub enum ButtonColor {
74    /// Primary color (blue)
75    Primary,
76    /// Secondary color (white)
77    Secondary,
78    /// Negative color (red)
79    Negative,
80    /// Positive color (green)
81    Positive,
82}
83
84impl fmt::Display for ButtonColor {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        match self {
87            ButtonColor::Primary => write!(f, "primary"),
88            ButtonColor::Secondary => write!(f, "secondary"),
89            ButtonColor::Negative => write!(f, "negative"),
90            ButtonColor::Positive => write!(f, "positive"),
91        }
92    }
93}
94
95/// Keyboard button
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct KeyboardButton {
98    /// Button action
99    pub action: ButtonAction,
100    /// Button color
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub color: Option<ButtonColor>,
103}
104
105/// Keyboard structure
106#[derive(Debug, Clone)]
107pub struct Keyboard {
108    buttons: Vec<Vec<KeyboardButton>>,
109    one_time: bool,
110    inline: bool,
111}
112
113impl Keyboard {
114    /// Create new empty keyboard
115    pub fn new() -> Self {
116        Self {
117            buttons: Vec::new(),
118            one_time: false,
119            inline: false,
120        }
121    }
122
123    /// Create new inline keyboard
124    pub fn new_inline() -> Self {
125        Self {
126            buttons: Vec::new(),
127            one_time: false,
128            inline: true,
129        }
130    }
131
132    /// Create new one-time keyboard
133    pub fn new_one_time() -> Self {
134        Self {
135            buttons: Vec::new(),
136            one_time: true,
137            inline: false,
138        }
139    }
140
141    /// Add row of buttons
142    pub fn add_row(mut self, row: Vec<KeyboardButton>) -> Self {
143        self.buttons.push(row);
144        self
145    }
146
147    /// Add text button to last row
148    pub fn add_text_button(
149        mut self,
150        label: &str,
151        payload: Option<Value>,
152        color: Option<ButtonColor>,
153    ) -> Self {
154        if self.buttons.is_empty() {
155            self.buttons.push(Vec::new());
156        }
157
158        let last_row = self.buttons.last_mut().unwrap();
159        last_row.push(KeyboardButton {
160            action: ButtonAction::Text {
161                label: label.to_string(),
162                payload,
163            },
164            color,
165        });
166
167        self
168    }
169
170    /// Add callback button to last row
171    pub fn add_callback_button(
172        mut self,
173        label: &str,
174        payload: Value,
175        color: Option<ButtonColor>,
176    ) -> Self {
177        if self.buttons.is_empty() {
178            self.buttons.push(Vec::new());
179        }
180
181        let last_row = self.buttons.last_mut().unwrap();
182        last_row.push(KeyboardButton {
183            action: ButtonAction::Callback {
184                label: label.to_string(),
185                payload,
186            },
187            color,
188        });
189
190        self
191    }
192
193    /// Add command button to last row
194    pub fn add_command_button(
195        mut self,
196        label: &str,
197        payload: Value,
198        color: Option<ButtonColor>,
199    ) -> Self {
200        if self.buttons.is_empty() {
201            self.buttons.push(Vec::new());
202        }
203
204        let last_row = self.buttons.last_mut().unwrap();
205        last_row.push(KeyboardButton {
206            action: ButtonAction::Callback {
207                label: label.to_owned(),
208                payload: json!({"command": payload}),
209            },
210            color,
211        });
212
213        self
214    }
215
216    /// Add link button to last row
217    pub fn add_link_button(
218        mut self,
219        label: &str,
220        link: &str,
221        payload: Option<Value>,
222        color: Option<ButtonColor>,
223    ) -> Self {
224        if self.buttons.is_empty() {
225            self.buttons.push(Vec::new());
226        }
227
228        let last_row = self.buttons.last_mut().unwrap();
229        last_row.push(KeyboardButton {
230            action: ButtonAction::OpenLink {
231                link: link.to_owned(),
232                label: label.to_owned(),
233                payload,
234            },
235            color,
236        });
237
238        self
239    }
240
241    /// Convert to JSON value
242    pub fn to_json(&self) -> Value {
243        let buttons_json: Vec<Value> = self
244            .buttons
245            .iter()
246            .map(|row| {
247                let row_json: Vec<Value> = row
248                    .iter()
249                    .map(|button| {
250                        let mut button_json = json!({
251                            "action": button.action,
252                        });
253
254                        if let Some(color) = &button.color {
255                            button_json["color"] = json!(color.to_string());
256                        }
257
258                        button_json
259                    })
260                    .collect();
261
262                json!(row_json)
263            })
264            .collect();
265
266        json!({
267            "one_time": self.one_time,
268            "inline": self.inline,
269            "buttons": buttons_json,
270        })
271    }
272
273    /// Convert to JSON string
274    pub fn to_json_string(&self) -> String {
275        self.to_json().to_string()
276    }
277
278    /// Create default menu keyboard
279    pub fn create_menu() -> Self {
280        Keyboard::new_inline()
281            .add_text_button(
282                "📋 Help",
283                Some(json!({"command": "help"})),
284                Some(ButtonColor::Primary),
285            )
286            .add_text_button(
287                "â„šī¸ Info",
288                Some(json!({"command": "info"})),
289                Some(ButtonColor::Secondary),
290            )
291            .add_row(Vec::new()) // New row
292            .add_text_button(
293                "🎲 Random",
294                Some(json!({"command": "random"})),
295                Some(ButtonColor::Positive),
296            )
297            .add_text_button(
298                "🕒 Time",
299                Some(json!({"command": "time"})),
300                Some(ButtonColor::Negative),
301            )
302    }
303
304    /// Create admin menu keyboard
305    pub fn create_admin_menu() -> Self {
306        Keyboard::new_inline()
307            .add_text_button(
308                "📊 Stats",
309                Some(json!({"command": "stats"})),
310                Some(ButtonColor::Primary),
311            )
312            .add_text_button(
313                "đŸ“ĸ Broadcast",
314                Some(json!({"command": "broadcast"})),
315                Some(ButtonColor::Secondary),
316            )
317            .add_row(Vec::new())
318            .add_text_button(
319                "đŸšĢ Ban",
320                Some(json!({"command": "ban"})),
321                Some(ButtonColor::Negative),
322            )
323            .add_text_button(
324                "✅ Unban",
325                Some(json!({"command": "unban"})),
326                Some(ButtonColor::Positive),
327            )
328    }
329}
330
331impl Default for Keyboard {
332    fn default() -> Self {
333        Self::new()
334    }
335}