1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
use serde_json::{json, Value};
use crate::core::voiceflow::dialog_blocks::enums::VoiceflowButtonsOption;
use crate::core::voiceflow::dialog_blocks::{VoiceflowButtons, VoiceflowCard};
use crate::integrations::utils::ButtonCallbackDataBuilder;
/// `TelegramSerializer` provides methods for serializing various types of messages
/// into JSON format compatible with the Telegram API. These include text, image,
/// button, and card messages, as well as specialized carousel messages.
pub(crate) struct TelegramSerializer;
impl TelegramSerializer {
/// Builds the JSON body for sending a text message via the Telegram API.
///
/// # Parameters
///
/// * `chat_id` - The chat ID of the recipient.
/// * `text` - The text message to send.
///
/// # Returns
///
/// A `Value` containing the JSON body for the request.
pub fn build_text_body(chat_id: &str, text: &str) -> Value {
json!({
"chat_id": chat_id,
"text": text,
})
}
/// Builds the JSON body for sending an image message via the Telegram API.
///
/// # Parameters
///
/// * `chat_id` - The chat ID of the recipient.
/// * `image_url` - The URL of the image to send.
///
/// # Returns
///
/// A `Value` containing the JSON body for the request.
pub fn build_image_body(chat_id: &str, image_url: &str) -> Value {
json!({
"chat_id": chat_id,
"photo": image_url,
})
}
/// Builds the JSON body for sending a message with buttons via the Telegram API.
///
/// # Parameters
///
/// * `chat_id` - The chat ID of the recipient.
/// * `buttons` - The `VoiceflowButtons` to send.
///
/// # Returns
///
/// A `Value` containing the JSON body for the request.
pub fn build_buttons_body(chat_id: &str, buttons: &VoiceflowButtons) -> Value {
let text = match buttons.option() {
VoiceflowButtonsOption::Text(text) => text.message().clone(),
VoiceflowButtonsOption::Empty => String::from("Invalid behavior. Please fix errors in TelegramSender usage")
};
let inline_keyboard: Vec<Vec<Value>> = Self::build_buttons_vec(buttons);
json!({
"chat_id": chat_id,
"text": text,
"reply_markup": {
"inline_keyboard": inline_keyboard,
}
})
}
/// Builds the JSON body for sending a card message via the Telegram API.
///
/// # Parameters
///
/// * `chat_id` - The chat ID of the recipient.
/// * `card` - The `VoiceflowCard` to send.
///
/// # Returns
///
/// A `Value` containing the JSON body for the request.
pub fn build_card_body(chat_id: &str, card: &VoiceflowCard) -> Value {
let title = card.title().clone().unwrap_or(String::new());
let description = card.description().clone().unwrap_or(String::new());
let text = format!("{}\n\n{}", title, description);
let inline_keyboard: Vec<Vec<Value>> = card.buttons().as_ref()
.map(|b| Self::build_buttons_vec(b))
.unwrap_or_else(Vec::new);
Self::build_card_base_body(chat_id, text, card.image_url(), inline_keyboard)
}
/// Builds the JSON body for sending a carousel card message via the Telegram API.
///
/// # Parameters
///
/// * `chat_id` - The chat ID of the recipient.
/// * `card` - The `VoiceflowCard` to send.
/// * `index` - The current position of the card within the carousel (0-based index).
/// * `carousel_length` - The total number of cards in the carousel.
///
/// # Returns
///
/// A `Value` containing the JSON body for the request.
pub fn build_carousel_card_body(chat_id: &str, card: &VoiceflowCard, index: usize, carousel_length: usize) -> Value {
let title = card.title().clone().unwrap_or(String::new());
let description = card.description().clone().unwrap_or(String::new());
let text = format!("{}\n\n{}", title, description);
let inline_keyboard: Vec<Vec<Value>> = Self::build_carousel_card_buttons_vec(card, index, carousel_length);
Self::build_card_base_body(chat_id, text, card.image_url(), inline_keyboard)
}
/// Builds the JSON body for updating a carousel card message via the Telegram API.
///
/// # Parameters
///
/// * `chat_id` - The chat ID of the recipient.
/// * `message_id` - The ID of the message to update.
/// * `card` - The `VoiceflowCard` to update in the carousel.
/// * `has_image` - A boolean indicating if the card has an image.
/// * `index` - The current position of the card within the carousel (0-based index).
/// * `carousel_length` - The total number of cards in the carousel.
///
/// # Returns
///
/// A `Value` containing the JSON body for the request.
pub fn build_carousel_update_card_body(chat_id: &str, message_id: &str, card: &VoiceflowCard, has_image: bool, index: usize, carousel_length: usize) -> Value {
let title = card.title().clone().unwrap_or(String::new());
let description = card.description().clone().unwrap_or(String::new());
let text = format!("{}\n\n{}", title, description);
let inline_keyboard: Vec<Vec<Value>> = Self::build_carousel_card_buttons_vec(card, index, carousel_length);
if has_image {
json!({
"chat_id": chat_id,
"message_id": message_id,
"media": {
"type": "photo",
"media": card.image_url().as_ref().unwrap().clone(),
"caption": text,
},
"reply_markup": {
"inline_keyboard": inline_keyboard,
}
})
} else {
json!({
"chat_id": chat_id,
"message_id": message_id,
"text": text,
"reply_markup": {
"inline_keyboard": inline_keyboard,
}
})
}
}
/// Builds the base JSON body for sending a card or carousel message via the Telegram API.
///
/// # Parameters
///
/// * `chat_id` - The chat ID of the recipient.
/// * `text` - The text content of the message.
/// * `image_url` - An optional URL of the image to send with the card.
/// * `inline_keyboard` - The inline keyboard buttons to include in the message.
///
/// # Returns
///
/// A `Value` containing the JSON body for the request.
fn build_card_base_body(chat_id: &str, text: String, image_url: &Option<String>, inline_keyboard: Vec<Vec<Value>>) -> Value {
match image_url {
None => {
json!({
"chat_id": chat_id,
"text": text,
"reply_markup": {
"inline_keyboard": inline_keyboard,
}
})
}
Some(url) => {
json!({
"chat_id": chat_id,
"photo": url,
"caption": text,
"reply_markup": {
"inline_keyboard": inline_keyboard,
}
})
}
}
}
/// Converts `VoiceflowButtons` to a keyboard layout for Telegram inline keyboard.
///
/// # Parameters
///
/// * `buttons` - The `VoiceflowButtons` to convert.
///
/// # Returns
///
/// A vector of vectors containing the keyboard layout in JSON format.
fn build_buttons_vec(buttons: &VoiceflowButtons) -> Vec<Vec<Value>> {
buttons.iter().enumerate().map(|(index, b)| {
let callback_data = ButtonCallbackDataBuilder::new().index(index).build().to_json_string();
json!({ "text": b.name(), "callback_data": callback_data })
}).map(|key| vec![key]).collect()
}
/// Converts the buttons of a `VoiceflowCard` into a Telegram-compatible inline keyboard,
/// adding navigation buttons for carousel movement.
///
/// This function generates an inline keyboard for a given `VoiceflowCard`, converting its
/// buttons into a format suitable for use in Telegram's API. Additionally, it appends
/// navigation buttons ("<--" and "-->") to allow users to move through a carousel of cards.
/// The navigation buttons are conditionally added based on the card's position in the carousel
/// and the total number of cards.
///
/// # Parameters
///
/// * `card` - A reference to the `VoiceflowCard` whose buttons will be converted.
/// * `index` - The current position of the card within the carousel (0-based index).
/// * `carousel_len` - The total number of cards in the carousel.
///
/// # Returns
///
/// A `Vec<Vec<Value>>` representing the inline keyboard structure for Telegram,
/// including both the card's buttons and any applicable navigation buttons.
fn build_carousel_card_buttons_vec(card: &VoiceflowCard, index: usize, carousel_len: usize) -> Vec<Vec<Value>> {
let mut inline_keyboard: Vec<Vec<Value>> = card.buttons().as_ref()
.map(Self::build_buttons_vec)
.unwrap_or_else(Vec::new);
let mut switch_buttons: Vec<Value> = Vec::new();
// Add a previous button if this is not the first card
if index > 0 {
let carousel_prev = ButtonCallbackDataBuilder::new().direction(false).build().to_json_string();
switch_buttons.push(json!({ "text": "<--", "callback_data": carousel_prev }));
}
// Add a next button if this is not the last card
if index < carousel_len - 1 {
let carousel_next = ButtonCallbackDataBuilder::new().direction(true).build().to_json_string();
switch_buttons.push(json!({ "text": "-->", "callback_data": carousel_next }));
}
inline_keyboard.push(switch_buttons);
inline_keyboard
}
}