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