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/CallbackQueriesManager.h"
8 
9 #include "td/telegram/AccessRights.h"
10 #include "td/telegram/AuthManager.h"
11 #include "td/telegram/ContactsManager.h"
12 #include "td/telegram/Global.h"
13 #include "td/telegram/InlineQueriesManager.h"
14 #include "td/telegram/MessagesManager.h"
15 #include "td/telegram/PasswordManager.h"
16 #include "td/telegram/Td.h"
17 #include "td/telegram/td_api.h"
18 #include "td/telegram/telegram_api.h"
19 
20 #include "td/actor/actor.h"
21 #include "td/actor/PromiseFuture.h"
22 
23 #include "td/utils/common.h"
24 #include "td/utils/logging.h"
25 #include "td/utils/Status.h"
26 
27 namespace td {
28 
29 class GetBotCallbackAnswerQuery final : public Td::ResultHandler {
30   Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> promise_;
31   DialogId dialog_id_;
32   MessageId message_id_;
33 
34  public:
GetBotCallbackAnswerQuery(Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> && promise)35   explicit GetBotCallbackAnswerQuery(Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> &&promise)
36       : promise_(std::move(promise)) {
37   }
38 
send(DialogId dialog_id,MessageId message_id,const tl_object_ptr<td_api::CallbackQueryPayload> & payload,tl_object_ptr<telegram_api::InputCheckPasswordSRP> && password)39   void send(DialogId dialog_id, MessageId message_id, const tl_object_ptr<td_api::CallbackQueryPayload> &payload,
40             tl_object_ptr<telegram_api::InputCheckPasswordSRP> &&password) {
41     dialog_id_ = dialog_id;
42     message_id_ = message_id;
43 
44     auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
45     CHECK(input_peer != nullptr);
46 
47     int32 flags = 0;
48     BufferSlice data;
49     CHECK(payload != nullptr);
50     switch (payload->get_id()) {
51       case td_api::callbackQueryPayloadData::ID:
52         flags = telegram_api::messages_getBotCallbackAnswer::DATA_MASK;
53         data = BufferSlice(static_cast<const td_api::callbackQueryPayloadData *>(payload.get())->data_);
54         break;
55       case td_api::callbackQueryPayloadDataWithPassword::ID:
56         CHECK(password != nullptr);
57         flags = telegram_api::messages_getBotCallbackAnswer::DATA_MASK |
58                 telegram_api::messages_getBotCallbackAnswer::PASSWORD_MASK;
59         data = BufferSlice(static_cast<const td_api::callbackQueryPayloadDataWithPassword *>(payload.get())->data_);
60         break;
61       case td_api::callbackQueryPayloadGame::ID:
62         flags = telegram_api::messages_getBotCallbackAnswer::GAME_MASK;
63         break;
64       default:
65         UNREACHABLE();
66     }
67 
68     auto net_query = G()->net_query_creator().create(telegram_api::messages_getBotCallbackAnswer(
69         flags, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(), std::move(data),
70         std::move(password)));
71     net_query->need_resend_on_503_ = false;
72     send_query(std::move(net_query));
73   }
74 
on_result(BufferSlice packet)75   void on_result(BufferSlice packet) final {
76     auto result_ptr = fetch_result<telegram_api::messages_getBotCallbackAnswer>(packet);
77     if (result_ptr.is_error()) {
78       return on_error(result_ptr.move_as_error());
79     }
80 
81     auto answer = result_ptr.move_as_ok();
82     promise_.set_value(
83         td_api::make_object<td_api::callbackQueryAnswer>(answer->message_, answer->alert_, answer->url_));
84   }
85 
on_error(Status status)86   void on_error(Status status) final {
87     if (status.message() == "DATA_INVALID" || status.message() == "MESSAGE_ID_INVALID") {
88       td_->messages_manager_->get_message_from_server({dialog_id_, message_id_}, Auto(), "GetBotCallbackAnswerQuery");
89     } else if (status.message() == "BOT_RESPONSE_TIMEOUT") {
90       status = Status::Error(502, "The bot is not responding");
91     }
92     if (status.code() == 502 && td_->messages_manager_->is_message_edited_recently({dialog_id_, message_id_}, 31)) {
93       return promise_.set_value(td_api::make_object<td_api::callbackQueryAnswer>());
94     }
95 
96     td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetBotCallbackAnswerQuery");
97     promise_.set_error(std::move(status));
98   }
99 };
100 
101 class SetBotCallbackAnswerQuery final : public Td::ResultHandler {
102   Promise<Unit> promise_;
103 
104  public:
SetBotCallbackAnswerQuery(Promise<Unit> && promise)105   explicit SetBotCallbackAnswerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
106   }
107 
send(int32 flags,int64 callback_query_id,const string & text,const string & url,int32 cache_time)108   void send(int32 flags, int64 callback_query_id, const string &text, const string &url, int32 cache_time) {
109     send_query(G()->net_query_creator().create(telegram_api::messages_setBotCallbackAnswer(
110         flags, false /*ignored*/, callback_query_id, text, url, cache_time)));
111   }
112 
on_result(BufferSlice packet)113   void on_result(BufferSlice packet) final {
114     auto result_ptr = fetch_result<telegram_api::messages_setBotCallbackAnswer>(packet);
115     if (result_ptr.is_error()) {
116       return on_error(result_ptr.move_as_error());
117     }
118 
119     bool result = result_ptr.ok();
120     if (!result) {
121       LOG(INFO) << "Sending answer to a callback query has failed";
122     }
123     promise_.set_value(Unit());
124   }
125 
on_error(Status status)126   void on_error(Status status) final {
127     promise_.set_error(std::move(status));
128   }
129 };
130 
CallbackQueriesManager(Td * td)131 CallbackQueriesManager::CallbackQueriesManager(Td *td) : td_(td) {
132 }
133 
answer_callback_query(int64 callback_query_id,const string & text,bool show_alert,const string & url,int32 cache_time,Promise<Unit> && promise) const134 void CallbackQueriesManager::answer_callback_query(int64 callback_query_id, const string &text, bool show_alert,
135                                                    const string &url, int32 cache_time, Promise<Unit> &&promise) const {
136   int32 flags = 0;
137   if (!text.empty()) {
138     flags |= BOT_CALLBACK_ANSWER_FLAG_HAS_MESSAGE;
139   }
140   if (show_alert) {
141     flags |= BOT_CALLBACK_ANSWER_FLAG_NEED_SHOW_ALERT;
142   }
143   if (!url.empty()) {
144     flags |= BOT_CALLBACK_ANSWER_FLAG_HAS_URL;
145   }
146   td_->create_handler<SetBotCallbackAnswerQuery>(std::move(promise))
147       ->send(flags, callback_query_id, text, url, cache_time);
148 }
149 
get_query_payload(int32 flags,BufferSlice && data,string && game_short_name)150 tl_object_ptr<td_api::CallbackQueryPayload> CallbackQueriesManager::get_query_payload(int32 flags, BufferSlice &&data,
151                                                                                       string &&game_short_name) {
152   bool has_data = (flags & telegram_api::updateBotCallbackQuery::DATA_MASK) != 0;
153   bool has_game = (flags & telegram_api::updateBotCallbackQuery::GAME_SHORT_NAME_MASK) != 0;
154   if (has_data == has_game) {
155     LOG(ERROR) << "Receive wrong flags " << flags << " in a callback query";
156     return nullptr;
157   }
158 
159   if (has_data) {
160     return make_tl_object<td_api::callbackQueryPayloadData>(data.as_slice().str());
161   }
162   if (has_game) {
163     return make_tl_object<td_api::callbackQueryPayloadGame>(game_short_name);
164   }
165   UNREACHABLE();
166   return nullptr;
167 }
168 
on_new_query(int32 flags,int64 callback_query_id,UserId sender_user_id,DialogId dialog_id,MessageId message_id,BufferSlice && data,int64 chat_instance,string && game_short_name)169 void CallbackQueriesManager::on_new_query(int32 flags, int64 callback_query_id, UserId sender_user_id,
170                                           DialogId dialog_id, MessageId message_id, BufferSlice &&data,
171                                           int64 chat_instance, string &&game_short_name) {
172   if (!dialog_id.is_valid()) {
173     LOG(ERROR) << "Receive new callback query in invalid " << dialog_id;
174     return;
175   }
176   if (!sender_user_id.is_valid()) {
177     LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id << " in " << dialog_id;
178     return;
179   }
180   LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Have no info about " << sender_user_id;
181   if (!td_->auth_manager_->is_bot()) {
182     LOG(ERROR) << "Receive new callback query";
183     return;
184   }
185   if (!message_id.is_valid()) {
186     LOG(ERROR) << "Receive new callback query from " << message_id << " in " << dialog_id << " sent by "
187                << sender_user_id;
188     return;
189   }
190 
191   auto payload = get_query_payload(flags, std::move(data), std::move(game_short_name));
192   if (payload == nullptr) {
193     return;
194   }
195 
196   td_->messages_manager_->force_create_dialog(dialog_id, "on_new_callback_query", true);
197   send_closure(
198       G()->td(), &Td::send_update,
199       make_tl_object<td_api::updateNewCallbackQuery>(
200           callback_query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewCallbackQuery"),
201           dialog_id.get(), message_id.get(), chat_instance, std::move(payload)));
202 }
203 
on_new_inline_query(int32 flags,int64 callback_query_id,UserId sender_user_id,tl_object_ptr<telegram_api::InputBotInlineMessageID> && inline_message_id,BufferSlice && data,int64 chat_instance,string && game_short_name)204 void CallbackQueriesManager::on_new_inline_query(
205     int32 flags, int64 callback_query_id, UserId sender_user_id,
206     tl_object_ptr<telegram_api::InputBotInlineMessageID> &&inline_message_id, BufferSlice &&data, int64 chat_instance,
207     string &&game_short_name) {
208   if (!sender_user_id.is_valid()) {
209     LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id;
210     return;
211   }
212   LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Have no info about " << sender_user_id;
213   if (!td_->auth_manager_->is_bot()) {
214     LOG(ERROR) << "Receive new callback query";
215     return;
216   }
217   CHECK(inline_message_id != nullptr);
218 
219   auto payload = get_query_payload(flags, std::move(data), std::move(game_short_name));
220   if (payload == nullptr) {
221     return;
222   }
223   send_closure(
224       G()->td(), &Td::send_update,
225       make_tl_object<td_api::updateNewInlineCallbackQuery>(
226           callback_query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewInlineCallbackQuery"),
227           InlineQueriesManager::get_inline_message_id(std::move(inline_message_id)), chat_instance,
228           std::move(payload)));
229 }
230 
send_callback_query(FullMessageId full_message_id,tl_object_ptr<td_api::CallbackQueryPayload> && payload,Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> && promise)231 void CallbackQueriesManager::send_callback_query(FullMessageId full_message_id,
232                                                  tl_object_ptr<td_api::CallbackQueryPayload> &&payload,
233                                                  Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> &&promise) {
234   if (td_->auth_manager_->is_bot()) {
235     return promise.set_error(Status::Error(400, "Bot can't send callback queries to other bot"));
236   }
237 
238   if (payload == nullptr) {
239     return promise.set_error(Status::Error(400, "Payload must be non-empty"));
240   }
241 
242   auto dialog_id = full_message_id.get_dialog_id();
243   td_->messages_manager_->have_dialog_force(dialog_id, "send_callback_query");
244   if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
245     return promise.set_error(Status::Error(400, "Can't access the chat"));
246   }
247 
248   if (!td_->messages_manager_->have_message_force(full_message_id, "send_callback_query")) {
249     return promise.set_error(Status::Error(400, "Message not found"));
250   }
251   if (full_message_id.get_message_id().is_valid_scheduled()) {
252     return promise.set_error(Status::Error(400, "Can't send callback queries from scheduled messages"));
253   }
254   if (!full_message_id.get_message_id().is_server()) {
255     return promise.set_error(Status::Error(400, "Bad message identifier"));
256   }
257 
258   if (payload->get_id() == td_api::callbackQueryPayloadDataWithPassword::ID) {
259     auto password = static_cast<const td_api::callbackQueryPayloadDataWithPassword *>(payload.get())->password_;
260     send_closure(
261         td_->password_manager_, &PasswordManager::get_input_check_password_srp, std::move(password),
262         PromiseCreator::lambda([this, full_message_id, payload = std::move(payload), promise = std::move(promise)](
263                                    Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> result) mutable {
264           if (result.is_error()) {
265             return promise.set_error(result.move_as_error());
266           }
267           send_get_callback_answer_query(full_message_id, std::move(payload), result.move_as_ok(), std::move(promise));
268         }));
269   } else {
270     send_get_callback_answer_query(full_message_id, std::move(payload), nullptr, std::move(promise));
271   }
272 }
273 
send_get_callback_answer_query(FullMessageId full_message_id,tl_object_ptr<td_api::CallbackQueryPayload> && payload,tl_object_ptr<telegram_api::InputCheckPasswordSRP> && password,Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> && promise)274 void CallbackQueriesManager::send_get_callback_answer_query(
275     FullMessageId full_message_id, tl_object_ptr<td_api::CallbackQueryPayload> &&payload,
276     tl_object_ptr<telegram_api::InputCheckPasswordSRP> &&password,
277     Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> &&promise) {
278   TRY_STATUS_PROMISE(promise, G()->close_status());
279 
280   auto dialog_id = full_message_id.get_dialog_id();
281   if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
282     return promise.set_error(Status::Error(400, "Can't access the chat"));
283   }
284   if (!td_->messages_manager_->have_message_force(full_message_id, "send_callback_query")) {
285     return promise.set_error(Status::Error(400, "Message not found"));
286   }
287 
288   td_->create_handler<GetBotCallbackAnswerQuery>(std::move(promise))
289       ->send(dialog_id, full_message_id.get_message_id(), payload, std::move(password));
290 }
291 
292 }  // namespace td
293