1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/telegram/ReplyMarkup.h"
8
9 #include "td/telegram/ContactsManager.h"
10 #include "td/telegram/Dependencies.h"
11 #include "td/telegram/Global.h"
12 #include "td/telegram/LinkManager.h"
13 #include "td/telegram/misc.h"
14 #include "td/telegram/Td.h"
15 #include "td/telegram/td_api.h"
16 #include "td/telegram/telegram_api.h"
17
18 #include "td/utils/buffer.h"
19 #include "td/utils/format.h"
20 #include "td/utils/logging.h"
21 #include "td/utils/SliceBuilder.h"
22
23 #include <limits>
24
25 namespace td {
26
27 static constexpr int32 REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD = 1 << 0;
28 static constexpr int32 REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD = 1 << 1;
29 static constexpr int32 REPLY_MARKUP_FLAG_IS_PERSONAL = 1 << 2;
30 static constexpr int32 REPLY_MARKUP_FLAG_HAS_PLACEHOLDER = 1 << 3;
31
operator ==(const KeyboardButton & lhs,const KeyboardButton & rhs)32 static bool operator==(const KeyboardButton &lhs, const KeyboardButton &rhs) {
33 return lhs.type == rhs.type && lhs.text == rhs.text;
34 }
35
operator <<(StringBuilder & string_builder,const KeyboardButton & keyboard_button)36 static StringBuilder &operator<<(StringBuilder &string_builder, const KeyboardButton &keyboard_button) {
37 string_builder << "Button[";
38 switch (keyboard_button.type) {
39 case KeyboardButton::Type::Text:
40 string_builder << "Text";
41 break;
42 case KeyboardButton::Type::RequestPhoneNumber:
43 string_builder << "RequestPhoneNumber";
44 break;
45 case KeyboardButton::Type::RequestLocation:
46 string_builder << "RequestLocation";
47 break;
48 case KeyboardButton::Type::RequestPoll:
49 string_builder << "RequestPoll";
50 break;
51 case KeyboardButton::Type::RequestPollQuiz:
52 string_builder << "RequestPollQuiz";
53 break;
54 case KeyboardButton::Type::RequestPollRegular:
55 string_builder << "RequestPollRegular";
56 break;
57 default:
58 UNREACHABLE();
59 }
60 return string_builder << ", " << keyboard_button.text << "]";
61 }
62
operator ==(const InlineKeyboardButton & lhs,const InlineKeyboardButton & rhs)63 static bool operator==(const InlineKeyboardButton &lhs, const InlineKeyboardButton &rhs) {
64 return lhs.type == rhs.type && lhs.text == rhs.text && lhs.data == rhs.data && lhs.id == rhs.id;
65 }
66
operator <<(StringBuilder & string_builder,const InlineKeyboardButton & keyboard_button)67 static StringBuilder &operator<<(StringBuilder &string_builder, const InlineKeyboardButton &keyboard_button) {
68 string_builder << "Button[";
69 switch (keyboard_button.type) {
70 case InlineKeyboardButton::Type::Url:
71 string_builder << "Url";
72 break;
73 case InlineKeyboardButton::Type::Callback:
74 string_builder << "Callback";
75 break;
76 case InlineKeyboardButton::Type::CallbackGame:
77 string_builder << "CallbackGame";
78 break;
79 case InlineKeyboardButton::Type::SwitchInline:
80 string_builder << "SwitchInline";
81 break;
82 case InlineKeyboardButton::Type::SwitchInlineCurrentDialog:
83 string_builder << "SwitchInlineCurrentChat";
84 break;
85 case InlineKeyboardButton::Type::Buy:
86 string_builder << "Buy";
87 break;
88 case InlineKeyboardButton::Type::UrlAuth:
89 string_builder << "UrlAuth, ID = " << keyboard_button.id;
90 break;
91 case InlineKeyboardButton::Type::CallbackWithPassword:
92 string_builder << "CallbackWithPassword";
93 break;
94 case InlineKeyboardButton::Type::User:
95 string_builder << "User " << keyboard_button.user_id.get();
96 break;
97 default:
98 UNREACHABLE();
99 }
100 return string_builder << ", text = " << keyboard_button.text << ", " << keyboard_button.data << "]";
101 }
102
operator ==(const ReplyMarkup & lhs,const ReplyMarkup & rhs)103 bool operator==(const ReplyMarkup &lhs, const ReplyMarkup &rhs) {
104 if (lhs.type != rhs.type) {
105 return false;
106 }
107 if (lhs.type == ReplyMarkup::Type::InlineKeyboard) {
108 return lhs.inline_keyboard == rhs.inline_keyboard;
109 }
110
111 if (lhs.is_personal != rhs.is_personal) {
112 return false;
113 }
114 if (lhs.placeholder != rhs.placeholder) {
115 return false;
116 }
117 if (lhs.type != ReplyMarkup::Type::ShowKeyboard) {
118 return true;
119 }
120 return lhs.need_resize_keyboard == rhs.need_resize_keyboard && lhs.is_one_time_keyboard == rhs.is_one_time_keyboard &&
121 lhs.keyboard == rhs.keyboard;
122 }
123
operator !=(const ReplyMarkup & lhs,const ReplyMarkup & rhs)124 bool operator!=(const ReplyMarkup &lhs, const ReplyMarkup &rhs) {
125 return !(lhs == rhs);
126 }
127
print(StringBuilder & string_builder) const128 StringBuilder &ReplyMarkup::print(StringBuilder &string_builder) const {
129 string_builder << "ReplyMarkup[";
130 switch (type) {
131 case ReplyMarkup::Type::InlineKeyboard:
132 string_builder << "InlineKeyboard";
133 break;
134 case ReplyMarkup::Type::ShowKeyboard:
135 string_builder << "ShowKeyboard";
136 break;
137 case ReplyMarkup::Type::RemoveKeyboard:
138 string_builder << "RemoveKeyboard";
139 break;
140 case ReplyMarkup::Type::ForceReply:
141 string_builder << "ForceReply";
142 break;
143 default:
144 UNREACHABLE();
145 }
146 if (is_personal) {
147 string_builder << ", personal";
148 }
149 if (!placeholder.empty()) {
150 string_builder << ", placeholder \"" << placeholder << '"';
151 }
152
153 if (type == ReplyMarkup::Type::ShowKeyboard) {
154 if (need_resize_keyboard) {
155 string_builder << ", need resize";
156 }
157 if (is_one_time_keyboard) {
158 string_builder << ", one time";
159 }
160 }
161 if (type == ReplyMarkup::Type::InlineKeyboard) {
162 for (auto &row : inline_keyboard) {
163 string_builder << ", " << format::as_array(row);
164 }
165 }
166 if (type == ReplyMarkup::Type::ShowKeyboard) {
167 for (auto &row : keyboard) {
168 string_builder << ", " << format::as_array(row);
169 }
170 }
171
172 string_builder << "]";
173 return string_builder;
174 }
175
operator <<(StringBuilder & string_builder,const ReplyMarkup & reply_markup)176 StringBuilder &operator<<(StringBuilder &string_builder, const ReplyMarkup &reply_markup) {
177 return reply_markup.print(string_builder);
178 }
179
get_keyboard_button(tl_object_ptr<telegram_api::KeyboardButton> && keyboard_button_ptr)180 static KeyboardButton get_keyboard_button(tl_object_ptr<telegram_api::KeyboardButton> &&keyboard_button_ptr) {
181 CHECK(keyboard_button_ptr != nullptr);
182
183 KeyboardButton button;
184 switch (keyboard_button_ptr->get_id()) {
185 case telegram_api::keyboardButton::ID: {
186 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButton>(keyboard_button_ptr);
187 button.type = KeyboardButton::Type::Text;
188 button.text = std::move(keyboard_button->text_);
189 break;
190 }
191 case telegram_api::keyboardButtonRequestPhone::ID: {
192 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonRequestPhone>(keyboard_button_ptr);
193 button.type = KeyboardButton::Type::RequestPhoneNumber;
194 button.text = std::move(keyboard_button->text_);
195 break;
196 }
197 case telegram_api::keyboardButtonRequestGeoLocation::ID: {
198 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonRequestGeoLocation>(keyboard_button_ptr);
199 button.type = KeyboardButton::Type::RequestLocation;
200 button.text = std::move(keyboard_button->text_);
201 break;
202 }
203 case telegram_api::keyboardButtonRequestPoll::ID: {
204 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonRequestPoll>(keyboard_button_ptr);
205 if (keyboard_button->flags_ & telegram_api::keyboardButtonRequestPoll::QUIZ_MASK) {
206 if (keyboard_button->quiz_) {
207 button.type = KeyboardButton::Type::RequestPollQuiz;
208 } else {
209 button.type = KeyboardButton::Type::RequestPollRegular;
210 }
211 } else {
212 button.type = KeyboardButton::Type::RequestPoll;
213 }
214 button.text = std::move(keyboard_button->text_);
215 break;
216 }
217 default:
218 LOG(ERROR) << "Unsupported keyboard button: " << to_string(keyboard_button_ptr);
219 }
220 return button;
221 }
222
get_inline_keyboard_button(tl_object_ptr<telegram_api::KeyboardButton> && keyboard_button_ptr)223 static InlineKeyboardButton get_inline_keyboard_button(
224 tl_object_ptr<telegram_api::KeyboardButton> &&keyboard_button_ptr) {
225 CHECK(keyboard_button_ptr != nullptr);
226
227 InlineKeyboardButton button;
228 switch (keyboard_button_ptr->get_id()) {
229 case telegram_api::keyboardButtonUrl::ID: {
230 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonUrl>(keyboard_button_ptr);
231 button.type = InlineKeyboardButton::Type::Url;
232 button.text = std::move(keyboard_button->text_);
233 button.data = std::move(keyboard_button->url_);
234 break;
235 }
236 case telegram_api::keyboardButtonCallback::ID: {
237 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonCallback>(keyboard_button_ptr);
238 button.type = keyboard_button->requires_password_ ? InlineKeyboardButton::Type::CallbackWithPassword
239 : InlineKeyboardButton::Type::Callback;
240 button.text = std::move(keyboard_button->text_);
241 button.data = keyboard_button->data_.as_slice().str();
242 break;
243 }
244 case telegram_api::keyboardButtonGame::ID: {
245 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonGame>(keyboard_button_ptr);
246 button.type = InlineKeyboardButton::Type::CallbackGame;
247 button.text = std::move(keyboard_button->text_);
248 break;
249 }
250 case telegram_api::keyboardButtonSwitchInline::ID: {
251 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonSwitchInline>(keyboard_button_ptr);
252 button.type = (keyboard_button->flags_ & telegram_api::keyboardButtonSwitchInline::SAME_PEER_MASK) != 0
253 ? InlineKeyboardButton::Type::SwitchInlineCurrentDialog
254 : InlineKeyboardButton::Type::SwitchInline;
255 button.text = std::move(keyboard_button->text_);
256 button.data = std::move(keyboard_button->query_);
257 break;
258 }
259 case telegram_api::keyboardButtonBuy::ID: {
260 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonBuy>(keyboard_button_ptr);
261 button.type = InlineKeyboardButton::Type::Buy;
262 button.text = std::move(keyboard_button->text_);
263 break;
264 }
265 case telegram_api::keyboardButtonUrlAuth::ID: {
266 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonUrlAuth>(keyboard_button_ptr);
267 button.type = InlineKeyboardButton::Type::UrlAuth;
268 button.id = keyboard_button->button_id_;
269 button.text = std::move(keyboard_button->text_);
270 button.data = std::move(keyboard_button->url_);
271 button.forward_text = std::move(keyboard_button->fwd_text_);
272 break;
273 }
274 case telegram_api::keyboardButtonUserProfile::ID: {
275 auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonUserProfile>(keyboard_button_ptr);
276 button.type = InlineKeyboardButton::Type::User;
277 button.text = std::move(keyboard_button->text_);
278 button.user_id = UserId(keyboard_button->user_id_);
279 break;
280 }
281 default:
282 LOG(ERROR) << "Unsupported inline keyboard button: " << to_string(keyboard_button_ptr);
283 }
284 return button;
285 }
286
get_reply_markup(tl_object_ptr<telegram_api::ReplyMarkup> && reply_markup_ptr,bool is_bot,bool only_inline_keyboard,bool message_contains_mention)287 unique_ptr<ReplyMarkup> get_reply_markup(tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup_ptr, bool is_bot,
288 bool only_inline_keyboard, bool message_contains_mention) {
289 if (reply_markup_ptr == nullptr) {
290 return nullptr;
291 }
292
293 auto reply_markup = make_unique<ReplyMarkup>();
294 auto constructor_id = reply_markup_ptr->get_id();
295 if (only_inline_keyboard && constructor_id != telegram_api::replyInlineMarkup::ID) {
296 LOG(ERROR) << "Inline keyboard expected";
297 return nullptr;
298 }
299 switch (constructor_id) {
300 case telegram_api::replyInlineMarkup::ID: {
301 auto inline_markup = move_tl_object_as<telegram_api::replyInlineMarkup>(reply_markup_ptr);
302 reply_markup->type = ReplyMarkup::Type::InlineKeyboard;
303 reply_markup->inline_keyboard.reserve(inline_markup->rows_.size());
304 for (auto &row : inline_markup->rows_) {
305 vector<InlineKeyboardButton> buttons;
306 buttons.reserve(row->buttons_.size());
307 for (auto &button : row->buttons_) {
308 buttons.push_back(get_inline_keyboard_button(std::move(button)));
309 if (buttons.back().text.empty()) {
310 buttons.pop_back();
311 }
312 }
313 if (!buttons.empty()) {
314 reply_markup->inline_keyboard.push_back(std::move(buttons));
315 }
316 }
317 if (reply_markup->inline_keyboard.empty()) {
318 return nullptr;
319 }
320 break;
321 }
322 case telegram_api::replyKeyboardMarkup::ID: {
323 auto keyboard_markup = move_tl_object_as<telegram_api::replyKeyboardMarkup>(reply_markup_ptr);
324 reply_markup->type = ReplyMarkup::Type::ShowKeyboard;
325 reply_markup->need_resize_keyboard = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD) != 0;
326 reply_markup->is_one_time_keyboard = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD) != 0;
327 reply_markup->is_personal = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_IS_PERSONAL) != 0;
328 reply_markup->placeholder = std::move(keyboard_markup->placeholder_);
329 reply_markup->keyboard.reserve(keyboard_markup->rows_.size());
330 for (auto &row : keyboard_markup->rows_) {
331 vector<KeyboardButton> buttons;
332 buttons.reserve(row->buttons_.size());
333 for (auto &button : row->buttons_) {
334 buttons.push_back(get_keyboard_button(std::move(button)));
335 if (buttons.back().text.empty()) {
336 buttons.pop_back();
337 }
338 }
339 if (!buttons.empty()) {
340 reply_markup->keyboard.push_back(std::move(buttons));
341 }
342 }
343 if (reply_markup->keyboard.empty()) {
344 return nullptr;
345 }
346 break;
347 }
348 case telegram_api::replyKeyboardHide::ID: {
349 auto hide_keyboard_markup = move_tl_object_as<telegram_api::replyKeyboardHide>(reply_markup_ptr);
350 reply_markup->type = ReplyMarkup::Type::RemoveKeyboard;
351 reply_markup->is_personal = (hide_keyboard_markup->flags_ & REPLY_MARKUP_FLAG_IS_PERSONAL) != 0;
352 break;
353 }
354 case telegram_api::replyKeyboardForceReply::ID: {
355 auto force_reply_markup = move_tl_object_as<telegram_api::replyKeyboardForceReply>(reply_markup_ptr);
356 reply_markup->type = ReplyMarkup::Type::ForceReply;
357 reply_markup->is_personal = (force_reply_markup->flags_ & REPLY_MARKUP_FLAG_IS_PERSONAL) != 0;
358 reply_markup->placeholder = std::move(force_reply_markup->placeholder_);
359 break;
360 }
361 default:
362 UNREACHABLE();
363 return nullptr;
364 }
365
366 if (!is_bot && reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
367 // incoming keyboard
368 if (reply_markup->is_personal) {
369 reply_markup->is_personal = message_contains_mention;
370 } else {
371 reply_markup->is_personal = true;
372 }
373 }
374
375 return reply_markup;
376 }
377
get_keyboard_button(tl_object_ptr<td_api::keyboardButton> && button,bool request_buttons_allowed)378 static Result<KeyboardButton> get_keyboard_button(tl_object_ptr<td_api::keyboardButton> &&button,
379 bool request_buttons_allowed) {
380 CHECK(button != nullptr);
381
382 if (!clean_input_string(button->text_)) {
383 return Status::Error(400, "Keyboard button text must be encoded in UTF-8");
384 }
385
386 KeyboardButton current_button;
387 current_button.text = std::move(button->text_);
388
389 int32 button_type_id = button->type_ == nullptr ? td_api::keyboardButtonTypeText::ID : button->type_->get_id();
390 switch (button_type_id) {
391 case td_api::keyboardButtonTypeText::ID:
392 current_button.type = KeyboardButton::Type::Text;
393 break;
394 case td_api::keyboardButtonTypeRequestPhoneNumber::ID:
395 if (!request_buttons_allowed) {
396 return Status::Error(400, "Phone number can be requested in private chats only");
397 }
398 current_button.type = KeyboardButton::Type::RequestPhoneNumber;
399 break;
400 case td_api::keyboardButtonTypeRequestLocation::ID:
401 if (!request_buttons_allowed) {
402 return Status::Error(400, "Location can be requested in private chats only");
403 }
404 current_button.type = KeyboardButton::Type::RequestLocation;
405 break;
406 case td_api::keyboardButtonTypeRequestPoll::ID: {
407 if (!request_buttons_allowed) {
408 return Status::Error(400, "Poll can be requested in private chats only");
409 }
410 auto *request_poll = static_cast<const td_api::keyboardButtonTypeRequestPoll *>(button->type_.get());
411 if (request_poll->force_quiz_ && request_poll->force_regular_) {
412 return Status::Error(400, "Can't force quiz mode and regular poll simultaneously");
413 }
414 if (request_poll->force_quiz_) {
415 current_button.type = KeyboardButton::Type::RequestPollQuiz;
416 } else if (request_poll->force_regular_) {
417 current_button.type = KeyboardButton::Type::RequestPollRegular;
418 } else {
419 current_button.type = KeyboardButton::Type::RequestPoll;
420 }
421 break;
422 }
423 default:
424 UNREACHABLE();
425 }
426 return current_button;
427 }
428
get_inline_keyboard_button(tl_object_ptr<td_api::inlineKeyboardButton> && button,bool switch_inline_buttons_allowed)429 static Result<InlineKeyboardButton> get_inline_keyboard_button(tl_object_ptr<td_api::inlineKeyboardButton> &&button,
430 bool switch_inline_buttons_allowed) {
431 CHECK(button != nullptr);
432 if (!clean_input_string(button->text_)) {
433 return Status::Error(400, "Keyboard button text must be encoded in UTF-8");
434 }
435
436 InlineKeyboardButton current_button;
437 current_button.text = std::move(button->text_);
438
439 if (button->type_ == nullptr) {
440 return Status::Error(400, "Inline keyboard button type can't be empty");
441 }
442
443 int32 button_type_id = button->type_->get_id();
444 switch (button_type_id) {
445 case td_api::inlineKeyboardButtonTypeUrl::ID: {
446 auto button_type = move_tl_object_as<td_api::inlineKeyboardButtonTypeUrl>(button->type_);
447 auto user_id = LinkManager::get_link_user_id(button_type->url_);
448 if (user_id.is_valid()) {
449 current_button.type = InlineKeyboardButton::Type::User;
450 current_button.user_id = user_id;
451 break;
452 }
453 auto r_url = LinkManager::check_link(button_type->url_);
454 if (r_url.is_error()) {
455 return Status::Error(400, "Inline keyboard button URL is invalid");
456 }
457 current_button.type = InlineKeyboardButton::Type::Url;
458 current_button.data = r_url.move_as_ok();
459 if (!clean_input_string(current_button.data)) {
460 return Status::Error(400, "Inline keyboard button URL must be encoded in UTF-8");
461 }
462 break;
463 }
464 case td_api::inlineKeyboardButtonTypeCallback::ID: {
465 auto button_type = move_tl_object_as<td_api::inlineKeyboardButtonTypeCallback>(button->type_);
466 current_button.type = InlineKeyboardButton::Type::Callback;
467 current_button.data = std::move(button_type->data_);
468 break;
469 }
470 case td_api::inlineKeyboardButtonTypeCallbackGame::ID:
471 current_button.type = InlineKeyboardButton::Type::CallbackGame;
472 break;
473 case td_api::inlineKeyboardButtonTypeCallbackWithPassword::ID:
474 return Status::Error(400, "Can't use CallbackWithPassword inline button");
475 case td_api::inlineKeyboardButtonTypeSwitchInline::ID: {
476 auto button_type = move_tl_object_as<td_api::inlineKeyboardButtonTypeSwitchInline>(button->type_);
477 if (!switch_inline_buttons_allowed) {
478 const char *button_name =
479 button_type->in_current_chat_ ? "switch_inline_query_current_chat" : "switch_inline_query";
480 return Status::Error(400, PSLICE() << "Can't use " << button_name
481 << " in a channel chat, because a user will not be able to use the button "
482 "without knowing bot's username");
483 }
484
485 current_button.type = button_type->in_current_chat_ ? InlineKeyboardButton::Type::SwitchInlineCurrentDialog
486 : InlineKeyboardButton::Type::SwitchInline;
487 current_button.data = std::move(button_type->query_);
488 if (!clean_input_string(current_button.data)) {
489 return Status::Error(400, "Inline keyboard button switch inline query must be encoded in UTF-8");
490 }
491 break;
492 }
493 case td_api::inlineKeyboardButtonTypeBuy::ID:
494 current_button.type = InlineKeyboardButton::Type::Buy;
495 break;
496 case td_api::inlineKeyboardButtonTypeLoginUrl::ID: {
497 auto button_type = td_api::move_object_as<td_api::inlineKeyboardButtonTypeLoginUrl>(button->type_);
498 auto user_id = LinkManager::get_link_user_id(button_type->url_);
499 if (user_id.is_valid()) {
500 return Status::Error(400, "Link to a user can't be used in login URL buttons");
501 }
502 auto r_url = LinkManager::check_link(button_type->url_);
503 if (r_url.is_error()) {
504 return Status::Error(400, "Inline keyboard button login URL is invalid");
505 }
506 current_button.type = InlineKeyboardButton::Type::UrlAuth;
507 current_button.data = r_url.move_as_ok();
508 current_button.forward_text = std::move(button_type->forward_text_);
509 if (!clean_input_string(current_button.data)) {
510 return Status::Error(400, "Inline keyboard button login URL must be encoded in UTF-8");
511 }
512 if (!clean_input_string(current_button.forward_text)) {
513 return Status::Error(400, "Inline keyboard button forward text must be encoded in UTF-8");
514 }
515 current_button.id = button_type->id_;
516 if (current_button.id == std::numeric_limits<int64>::min() ||
517 !UserId(current_button.id >= 0 ? current_button.id : -current_button.id).is_valid()) {
518 return Status::Error(400, "Invalid bot_user_id specified");
519 }
520 break;
521 }
522 case td_api::inlineKeyboardButtonTypeUser::ID: {
523 auto button_type = td_api::move_object_as<td_api::inlineKeyboardButtonTypeUser>(button->type_);
524 current_button.type = InlineKeyboardButton::Type::User;
525 current_button.user_id = UserId(button_type->user_id_);
526 if (!current_button.user_id.is_valid()) {
527 return Status::Error(400, "Invalid user_id specified");
528 }
529 break;
530 }
531 default:
532 UNREACHABLE();
533 }
534
535 return current_button;
536 }
537
get_reply_markup(tl_object_ptr<td_api::ReplyMarkup> && reply_markup_ptr,bool is_bot,bool only_inline_keyboard,bool request_buttons_allowed,bool switch_inline_buttons_allowed)538 Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMarkup> &&reply_markup_ptr, bool is_bot,
539 bool only_inline_keyboard, bool request_buttons_allowed,
540 bool switch_inline_buttons_allowed) {
541 CHECK(!only_inline_keyboard || !request_buttons_allowed);
542 if (reply_markup_ptr == nullptr || !is_bot) {
543 return nullptr;
544 }
545
546 auto reply_markup = make_unique<ReplyMarkup>();
547 auto constructor_id = reply_markup_ptr->get_id();
548 if (only_inline_keyboard && constructor_id != td_api::replyMarkupInlineKeyboard::ID) {
549 return Status::Error(400, "Inline keyboard expected");
550 }
551
552 switch (constructor_id) {
553 case td_api::replyMarkupShowKeyboard::ID: {
554 auto show_keyboard_markup = move_tl_object_as<td_api::replyMarkupShowKeyboard>(reply_markup_ptr);
555 reply_markup->type = ReplyMarkup::Type::ShowKeyboard;
556 reply_markup->need_resize_keyboard = show_keyboard_markup->resize_keyboard_;
557 reply_markup->is_one_time_keyboard = show_keyboard_markup->one_time_;
558 reply_markup->is_personal = show_keyboard_markup->is_personal_;
559 reply_markup->placeholder = std::move(show_keyboard_markup->input_field_placeholder_);
560
561 reply_markup->keyboard.reserve(show_keyboard_markup->rows_.size());
562 int32 total_button_count = 0;
563 for (auto &row : show_keyboard_markup->rows_) {
564 vector<KeyboardButton> row_buttons;
565 row_buttons.reserve(row.size());
566
567 int32 row_button_count = 0;
568 for (auto &button : row) {
569 if (button->text_.empty()) {
570 continue;
571 }
572
573 TRY_RESULT(current_button, get_keyboard_button(std::move(button), request_buttons_allowed));
574
575 row_buttons.push_back(std::move(current_button));
576 row_button_count++;
577 total_button_count++;
578 if (row_button_count >= 12 || total_button_count >= 300) {
579 break;
580 }
581 }
582 if (!row_buttons.empty()) {
583 reply_markup->keyboard.push_back(row_buttons);
584 }
585 if (total_button_count >= 300) {
586 break;
587 }
588 }
589 if (reply_markup->keyboard.empty()) {
590 return nullptr;
591 }
592 break;
593 }
594 case td_api::replyMarkupInlineKeyboard::ID: {
595 auto inline_keyboard_markup = move_tl_object_as<td_api::replyMarkupInlineKeyboard>(reply_markup_ptr);
596 reply_markup->type = ReplyMarkup::Type::InlineKeyboard;
597
598 reply_markup->inline_keyboard.reserve(inline_keyboard_markup->rows_.size());
599 int32 total_button_count = 0;
600 for (auto &row : inline_keyboard_markup->rows_) {
601 vector<InlineKeyboardButton> row_buttons;
602 row_buttons.reserve(row.size());
603
604 int32 row_button_count = 0;
605 for (auto &button : row) {
606 if (button->text_.empty()) {
607 continue;
608 }
609
610 TRY_RESULT(current_button, get_inline_keyboard_button(std::move(button), switch_inline_buttons_allowed));
611
612 row_buttons.push_back(std::move(current_button));
613 row_button_count++;
614 total_button_count++;
615 if (row_button_count >= 12 || total_button_count >= 300) {
616 break;
617 }
618 }
619 if (!row_buttons.empty()) {
620 reply_markup->inline_keyboard.push_back(row_buttons);
621 }
622 if (total_button_count >= 300) {
623 break;
624 }
625 }
626 if (reply_markup->inline_keyboard.empty()) {
627 return nullptr;
628 }
629 break;
630 }
631 case td_api::replyMarkupRemoveKeyboard::ID: {
632 auto remove_keyboard_markup = move_tl_object_as<td_api::replyMarkupRemoveKeyboard>(reply_markup_ptr);
633 reply_markup->type = ReplyMarkup::Type::RemoveKeyboard;
634 reply_markup->is_personal = remove_keyboard_markup->is_personal_;
635 break;
636 }
637 case td_api::replyMarkupForceReply::ID: {
638 auto force_reply_markup = move_tl_object_as<td_api::replyMarkupForceReply>(reply_markup_ptr);
639 reply_markup->type = ReplyMarkup::Type::ForceReply;
640 reply_markup->is_personal = force_reply_markup->is_personal_;
641 reply_markup->placeholder = std::move(force_reply_markup->input_field_placeholder_);
642 break;
643 }
644 default:
645 UNREACHABLE();
646 }
647
648 return std::move(reply_markup);
649 }
650
get_keyboard_button(const KeyboardButton & keyboard_button)651 static tl_object_ptr<telegram_api::KeyboardButton> get_keyboard_button(const KeyboardButton &keyboard_button) {
652 switch (keyboard_button.type) {
653 case KeyboardButton::Type::Text:
654 return make_tl_object<telegram_api::keyboardButton>(keyboard_button.text);
655 case KeyboardButton::Type::RequestPhoneNumber:
656 return make_tl_object<telegram_api::keyboardButtonRequestPhone>(keyboard_button.text);
657 case KeyboardButton::Type::RequestLocation:
658 return make_tl_object<telegram_api::keyboardButtonRequestGeoLocation>(keyboard_button.text);
659 case KeyboardButton::Type::RequestPoll:
660 return make_tl_object<telegram_api::keyboardButtonRequestPoll>(0, false, keyboard_button.text);
661 case KeyboardButton::Type::RequestPollQuiz:
662 return make_tl_object<telegram_api::keyboardButtonRequestPoll>(1, true, keyboard_button.text);
663 case KeyboardButton::Type::RequestPollRegular:
664 return make_tl_object<telegram_api::keyboardButtonRequestPoll>(1, false, keyboard_button.text);
665 default:
666 UNREACHABLE();
667 return nullptr;
668 }
669 }
670
get_inline_keyboard_button(const InlineKeyboardButton & keyboard_button)671 static tl_object_ptr<telegram_api::KeyboardButton> get_inline_keyboard_button(
672 const InlineKeyboardButton &keyboard_button) {
673 switch (keyboard_button.type) {
674 case InlineKeyboardButton::Type::Url:
675 return make_tl_object<telegram_api::keyboardButtonUrl>(keyboard_button.text, keyboard_button.data);
676 case InlineKeyboardButton::Type::Callback:
677 return make_tl_object<telegram_api::keyboardButtonCallback>(0, false /*ignored*/, keyboard_button.text,
678 BufferSlice(keyboard_button.data));
679 case InlineKeyboardButton::Type::CallbackGame:
680 return make_tl_object<telegram_api::keyboardButtonGame>(keyboard_button.text);
681 case InlineKeyboardButton::Type::SwitchInline:
682 case InlineKeyboardButton::Type::SwitchInlineCurrentDialog: {
683 int32 flags = 0;
684 if (keyboard_button.type == InlineKeyboardButton::Type::SwitchInlineCurrentDialog) {
685 flags |= telegram_api::keyboardButtonSwitchInline::SAME_PEER_MASK;
686 }
687 return make_tl_object<telegram_api::keyboardButtonSwitchInline>(flags, false /*ignored*/, keyboard_button.text,
688 keyboard_button.data);
689 }
690 case InlineKeyboardButton::Type::Buy:
691 return make_tl_object<telegram_api::keyboardButtonBuy>(keyboard_button.text);
692 case InlineKeyboardButton::Type::UrlAuth: {
693 int32 flags = 0;
694 int64 bot_user_id = keyboard_button.id;
695 if (bot_user_id > 0) {
696 flags |= telegram_api::inputKeyboardButtonUrlAuth::REQUEST_WRITE_ACCESS_MASK;
697 } else {
698 bot_user_id = -bot_user_id;
699 }
700 if (!keyboard_button.forward_text.empty()) {
701 flags |= telegram_api::inputKeyboardButtonUrlAuth::FWD_TEXT_MASK;
702 }
703 auto input_user = G()->td().get_actor_unsafe()->contacts_manager_->get_input_user(UserId(bot_user_id));
704 if (input_user == nullptr) {
705 LOG(ERROR) << "Failed to get InputUser for " << bot_user_id;
706 return make_tl_object<telegram_api::keyboardButtonUrl>(keyboard_button.text, keyboard_button.data);
707 }
708 return make_tl_object<telegram_api::inputKeyboardButtonUrlAuth>(flags, false /*ignored*/, keyboard_button.text,
709 keyboard_button.forward_text,
710 keyboard_button.data, std::move(input_user));
711 }
712 case InlineKeyboardButton::Type::CallbackWithPassword:
713 UNREACHABLE();
714 break;
715 case InlineKeyboardButton::Type::User: {
716 auto input_user = G()->td().get_actor_unsafe()->contacts_manager_->get_input_user(keyboard_button.user_id);
717 if (input_user == nullptr) {
718 LOG(ERROR) << "Failed to get InputUser for " << keyboard_button.user_id;
719 input_user = make_tl_object<telegram_api::inputUserEmpty>();
720 }
721 return make_tl_object<telegram_api::inputKeyboardButtonUserProfile>(keyboard_button.text, std::move(input_user));
722 }
723 default:
724 UNREACHABLE();
725 return nullptr;
726 }
727 }
728
get_input_reply_markup() const729 tl_object_ptr<telegram_api::ReplyMarkup> ReplyMarkup::get_input_reply_markup() const {
730 LOG(DEBUG) << "Send " << *this;
731 switch (type) {
732 case ReplyMarkup::Type::InlineKeyboard: {
733 vector<tl_object_ptr<telegram_api::keyboardButtonRow>> rows;
734 rows.reserve(inline_keyboard.size());
735 for (auto &row : inline_keyboard) {
736 vector<tl_object_ptr<telegram_api::KeyboardButton>> buttons;
737 buttons.reserve(row.size());
738 for (auto &button : row) {
739 buttons.push_back(get_inline_keyboard_button(button));
740 }
741 rows.push_back(make_tl_object<telegram_api::keyboardButtonRow>(std::move(buttons)));
742 }
743 LOG(DEBUG) << "Return inlineKeyboardMarkup to send it";
744 return make_tl_object<telegram_api::replyInlineMarkup>(std::move(rows));
745 }
746 case ReplyMarkup::Type::ShowKeyboard: {
747 vector<tl_object_ptr<telegram_api::keyboardButtonRow>> rows;
748 rows.reserve(keyboard.size());
749 for (auto &row : keyboard) {
750 vector<tl_object_ptr<telegram_api::KeyboardButton>> buttons;
751 buttons.reserve(row.size());
752 for (auto &button : row) {
753 buttons.push_back(get_keyboard_button(button));
754 }
755 rows.push_back(make_tl_object<telegram_api::keyboardButtonRow>(std::move(buttons)));
756 }
757 LOG(DEBUG) << "Return replyKeyboardMarkup to send it";
758 return make_tl_object<telegram_api::replyKeyboardMarkup>(
759 need_resize_keyboard * REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD +
760 is_one_time_keyboard * REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD +
761 is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL + (!placeholder.empty()) * REPLY_MARKUP_FLAG_HAS_PLACEHOLDER,
762 false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(rows), placeholder);
763 }
764 case ReplyMarkup::Type::ForceReply:
765 LOG(DEBUG) << "Return replyKeyboardForceReply to send it";
766 return make_tl_object<telegram_api::replyKeyboardForceReply>(
767 is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL + (!placeholder.empty()) * REPLY_MARKUP_FLAG_HAS_PLACEHOLDER,
768 false /*ignored*/, false /*ignored*/, placeholder);
769 case ReplyMarkup::Type::RemoveKeyboard:
770 LOG(DEBUG) << "Return replyKeyboardHide to send it";
771 return make_tl_object<telegram_api::replyKeyboardHide>(is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL,
772 false /*ignored*/);
773 default:
774 UNREACHABLE();
775 return nullptr;
776 }
777 }
778
get_keyboard_button_object(const KeyboardButton & keyboard_button)779 static tl_object_ptr<td_api::keyboardButton> get_keyboard_button_object(const KeyboardButton &keyboard_button) {
780 tl_object_ptr<td_api::KeyboardButtonType> type;
781 switch (keyboard_button.type) {
782 case KeyboardButton::Type::Text:
783 type = make_tl_object<td_api::keyboardButtonTypeText>();
784 break;
785 case KeyboardButton::Type::RequestPhoneNumber:
786 type = make_tl_object<td_api::keyboardButtonTypeRequestPhoneNumber>();
787 break;
788 case KeyboardButton::Type::RequestLocation:
789 type = make_tl_object<td_api::keyboardButtonTypeRequestLocation>();
790 break;
791 case KeyboardButton::Type::RequestPoll:
792 type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(false, false);
793 break;
794 case KeyboardButton::Type::RequestPollQuiz:
795 type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(false, true);
796 break;
797 case KeyboardButton::Type::RequestPollRegular:
798 type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(true, false);
799 break;
800 default:
801 UNREACHABLE();
802 return nullptr;
803 }
804 return make_tl_object<td_api::keyboardButton>(keyboard_button.text, std::move(type));
805 }
806
get_inline_keyboard_button_object(const InlineKeyboardButton & keyboard_button)807 static tl_object_ptr<td_api::inlineKeyboardButton> get_inline_keyboard_button_object(
808 const InlineKeyboardButton &keyboard_button) {
809 tl_object_ptr<td_api::InlineKeyboardButtonType> type;
810 switch (keyboard_button.type) {
811 case InlineKeyboardButton::Type::Url:
812 type = make_tl_object<td_api::inlineKeyboardButtonTypeUrl>(keyboard_button.data);
813 break;
814 case InlineKeyboardButton::Type::Callback:
815 type = make_tl_object<td_api::inlineKeyboardButtonTypeCallback>(keyboard_button.data);
816 break;
817 case InlineKeyboardButton::Type::CallbackGame:
818 type = make_tl_object<td_api::inlineKeyboardButtonTypeCallbackGame>();
819 break;
820 case InlineKeyboardButton::Type::SwitchInline:
821 type = make_tl_object<td_api::inlineKeyboardButtonTypeSwitchInline>(keyboard_button.data, false);
822 break;
823 case InlineKeyboardButton::Type::SwitchInlineCurrentDialog:
824 type = make_tl_object<td_api::inlineKeyboardButtonTypeSwitchInline>(keyboard_button.data, true);
825 break;
826 case InlineKeyboardButton::Type::Buy:
827 type = make_tl_object<td_api::inlineKeyboardButtonTypeBuy>();
828 break;
829 case InlineKeyboardButton::Type::UrlAuth:
830 type = make_tl_object<td_api::inlineKeyboardButtonTypeLoginUrl>(keyboard_button.data, keyboard_button.id,
831 keyboard_button.forward_text);
832 break;
833 case InlineKeyboardButton::Type::CallbackWithPassword:
834 type = make_tl_object<td_api::inlineKeyboardButtonTypeCallbackWithPassword>(keyboard_button.data);
835 break;
836 case InlineKeyboardButton::Type::User:
837 type = make_tl_object<td_api::inlineKeyboardButtonTypeUser>(
838 G()->td().get_actor_unsafe()->contacts_manager_->get_user_id_object(keyboard_button.user_id,
839 "get_inline_keyboard_button_object"));
840 break;
841 default:
842 UNREACHABLE();
843 return nullptr;
844 }
845 return make_tl_object<td_api::inlineKeyboardButton>(keyboard_button.text, std::move(type));
846 }
847
get_reply_markup_object() const848 tl_object_ptr<td_api::ReplyMarkup> ReplyMarkup::get_reply_markup_object() const {
849 switch (type) {
850 case ReplyMarkup::Type::InlineKeyboard: {
851 vector<vector<tl_object_ptr<td_api::inlineKeyboardButton>>> rows;
852 rows.reserve(inline_keyboard.size());
853 for (auto &row : inline_keyboard) {
854 vector<tl_object_ptr<td_api::inlineKeyboardButton>> buttons;
855 buttons.reserve(row.size());
856 for (auto &button : row) {
857 buttons.push_back(get_inline_keyboard_button_object(button));
858 }
859 rows.push_back(std::move(buttons));
860 }
861
862 return make_tl_object<td_api::replyMarkupInlineKeyboard>(std::move(rows));
863 }
864 case ReplyMarkup::Type::ShowKeyboard: {
865 vector<vector<tl_object_ptr<td_api::keyboardButton>>> rows;
866 rows.reserve(keyboard.size());
867 for (auto &row : keyboard) {
868 vector<tl_object_ptr<td_api::keyboardButton>> buttons;
869 buttons.reserve(row.size());
870 for (auto &button : row) {
871 buttons.push_back(get_keyboard_button_object(button));
872 }
873 rows.push_back(std::move(buttons));
874 }
875
876 return make_tl_object<td_api::replyMarkupShowKeyboard>(std::move(rows), need_resize_keyboard,
877 is_one_time_keyboard, is_personal, placeholder);
878 }
879 case ReplyMarkup::Type::RemoveKeyboard:
880 return make_tl_object<td_api::replyMarkupRemoveKeyboard>(is_personal);
881 case ReplyMarkup::Type::ForceReply:
882 return make_tl_object<td_api::replyMarkupForceReply>(is_personal, placeholder);
883 default:
884 UNREACHABLE();
885 return nullptr;
886 }
887 }
888
get_input_reply_markup(const unique_ptr<ReplyMarkup> & reply_markup)889 tl_object_ptr<telegram_api::ReplyMarkup> get_input_reply_markup(const unique_ptr<ReplyMarkup> &reply_markup) {
890 if (reply_markup == nullptr) {
891 return nullptr;
892 }
893
894 return reply_markup->get_input_reply_markup();
895 }
896
get_reply_markup_object(const unique_ptr<ReplyMarkup> & reply_markup)897 tl_object_ptr<td_api::ReplyMarkup> get_reply_markup_object(const unique_ptr<ReplyMarkup> &reply_markup) {
898 if (reply_markup == nullptr) {
899 return nullptr;
900 }
901
902 return reply_markup->get_reply_markup_object();
903 }
904
add_reply_markup_dependencies(Dependencies & dependencies,const ReplyMarkup * reply_markup)905 void add_reply_markup_dependencies(Dependencies &dependencies, const ReplyMarkup *reply_markup) {
906 if (reply_markup == nullptr) {
907 return;
908 }
909 for (auto &row : reply_markup->inline_keyboard) {
910 for (auto &button : row) {
911 if (button.user_id.is_valid()) {
912 dependencies.user_ids.insert(button.user_id);
913 }
914 }
915 }
916 }
917
918 } // namespace td
919