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