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/GameManager.h"
8 
9 #include "td/telegram/AccessRights.h"
10 #include "td/telegram/AuthManager.h"
11 #include "td/telegram/ContactsManager.h"
12 #include "td/telegram/DialogId.h"
13 #include "td/telegram/Global.h"
14 #include "td/telegram/InlineQueriesManager.h"
15 #include "td/telegram/MessageContentType.h"
16 #include "td/telegram/MessageId.h"
17 #include "td/telegram/MessagesManager.h"
18 #include "td/telegram/net/DcId.h"
19 #include "td/telegram/net/NetActor.h"
20 #include "td/telegram/net/NetQueryCreator.h"
21 #include "td/telegram/SequenceDispatcher.h"
22 #include "td/telegram/Td.h"
23 #include "td/telegram/UpdatesManager.h"
24 
25 #include "td/utils/buffer.h"
26 #include "td/utils/logging.h"
27 #include "td/utils/Status.h"
28 
29 namespace td {
30 
31 class SetGameScoreActor final : public NetActorOnce {
32   Promise<Unit> promise_;
33   DialogId dialog_id_;
34 
35  public:
SetGameScoreActor(Promise<Unit> && promise)36   explicit SetGameScoreActor(Promise<Unit> &&promise) : promise_(std::move(promise)) {
37   }
38 
send(DialogId dialog_id,MessageId message_id,bool edit_message,tl_object_ptr<telegram_api::InputUser> input_user,int32 score,bool force,uint64 sequence_dispatcher_id)39   void send(DialogId dialog_id, MessageId message_id, bool edit_message,
40             tl_object_ptr<telegram_api::InputUser> input_user, int32 score, bool force, uint64 sequence_dispatcher_id) {
41     int32 flags = 0;
42     if (edit_message) {
43       flags |= telegram_api::messages_setGameScore::EDIT_MESSAGE_MASK;
44     }
45     if (force) {
46       flags |= telegram_api::messages_setGameScore::FORCE_MASK;
47     }
48 
49     dialog_id_ = dialog_id;
50 
51     auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
52     if (input_peer == nullptr) {
53       on_error(Status::Error(400, "Can't access the chat"));
54       stop();
55       return;
56     }
57 
58     CHECK(input_user != nullptr);
59     auto query = G()->net_query_creator().create(
60         telegram_api::messages_setGameScore(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer),
61                                             message_id.get_server_message_id().get(), std::move(input_user), score));
62 
63     query->debug("send to MultiSequenceDispatcher");
64     send_closure(td_->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
65                  std::move(query), actor_shared(this), sequence_dispatcher_id);
66   }
67 
on_result(BufferSlice packet)68   void on_result(BufferSlice packet) final {
69     auto result_ptr = fetch_result<telegram_api::messages_setGameScore>(packet);
70     if (result_ptr.is_error()) {
71       return on_error(result_ptr.move_as_error());
72     }
73 
74     auto ptr = result_ptr.move_as_ok();
75     LOG(INFO) << "Receive result for SetGameScore: " << to_string(ptr);
76     td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
77   }
78 
on_error(Status status)79   void on_error(Status status) final {
80     LOG(INFO) << "Receive error for SetGameScore: " << status;
81     td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetGameScoreActor");
82     promise_.set_error(std::move(status));
83   }
84 };
85 
86 class SetInlineGameScoreQuery final : public Td::ResultHandler {
87   Promise<Unit> promise_;
88 
89  public:
SetInlineGameScoreQuery(Promise<Unit> && promise)90   explicit SetInlineGameScoreQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
91   }
92 
send(tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id,bool edit_message,tl_object_ptr<telegram_api::InputUser> input_user,int32 score,bool force)93   void send(tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id, bool edit_message,
94             tl_object_ptr<telegram_api::InputUser> input_user, int32 score, bool force) {
95     CHECK(input_bot_inline_message_id != nullptr);
96     CHECK(input_user != nullptr);
97 
98     int32 flags = 0;
99     if (edit_message) {
100       flags |= telegram_api::messages_setInlineGameScore::EDIT_MESSAGE_MASK;
101     }
102     if (force) {
103       flags |= telegram_api::messages_setInlineGameScore::FORCE_MASK;
104     }
105 
106     auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id));
107     send_query(G()->net_query_creator().create(
108         telegram_api::messages_setInlineGameScore(flags, false /*ignored*/, false /*ignored*/,
109                                                   std::move(input_bot_inline_message_id), std::move(input_user), score),
110         dc_id));
111   }
112 
on_result(BufferSlice packet)113   void on_result(BufferSlice packet) final {
114     auto result_ptr = fetch_result<telegram_api::messages_setInlineGameScore>(packet);
115     if (result_ptr.is_error()) {
116       return on_error(result_ptr.move_as_error());
117     }
118 
119     LOG_IF(ERROR, !result_ptr.ok()) << "Receive false in result of setInlineGameScore";
120 
121     promise_.set_value(Unit());
122   }
123 
on_error(Status status)124   void on_error(Status status) final {
125     LOG(INFO) << "Receive error for SetInlineGameScoreQuery: " << status;
126     promise_.set_error(std::move(status));
127   }
128 };
129 
130 class GetGameHighScoresQuery final : public Td::ResultHandler {
131   Promise<td_api::object_ptr<td_api::gameHighScores>> promise_;
132   DialogId dialog_id_;
133 
134  public:
GetGameHighScoresQuery(Promise<td_api::object_ptr<td_api::gameHighScores>> && promise)135   explicit GetGameHighScoresQuery(Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise)
136       : promise_(std::move(promise)) {
137   }
138 
send(DialogId dialog_id,MessageId message_id,tl_object_ptr<telegram_api::InputUser> input_user)139   void send(DialogId dialog_id, MessageId message_id, tl_object_ptr<telegram_api::InputUser> input_user) {
140     dialog_id_ = dialog_id;
141 
142     auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
143     CHECK(input_peer != nullptr);
144 
145     CHECK(input_user != nullptr);
146     send_query(G()->net_query_creator().create(telegram_api::messages_getGameHighScores(
147         std::move(input_peer), message_id.get_server_message_id().get(), std::move(input_user))));
148   }
149 
on_result(BufferSlice packet)150   void on_result(BufferSlice packet) final {
151     auto result_ptr = fetch_result<telegram_api::messages_getGameHighScores>(packet);
152     if (result_ptr.is_error()) {
153       return on_error(result_ptr.move_as_error());
154     }
155 
156     promise_.set_value(td_->game_manager_->get_game_high_scores_object(result_ptr.move_as_ok()));
157   }
158 
on_error(Status status)159   void on_error(Status status) final {
160     td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetGameHighScoresQuery");
161     promise_.set_error(std::move(status));
162   }
163 };
164 
165 class GetInlineGameHighScoresQuery final : public Td::ResultHandler {
166   Promise<td_api::object_ptr<td_api::gameHighScores>> promise_;
167 
168  public:
GetInlineGameHighScoresQuery(Promise<td_api::object_ptr<td_api::gameHighScores>> && promise)169   explicit GetInlineGameHighScoresQuery(Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise)
170       : promise_(std::move(promise)) {
171   }
172 
send(tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id,tl_object_ptr<telegram_api::InputUser> input_user)173   void send(tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id,
174             tl_object_ptr<telegram_api::InputUser> input_user) {
175     CHECK(input_bot_inline_message_id != nullptr);
176     CHECK(input_user != nullptr);
177 
178     auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id));
179     send_query(G()->net_query_creator().create(
180         telegram_api::messages_getInlineGameHighScores(std::move(input_bot_inline_message_id), std::move(input_user)),
181         dc_id));
182   }
183 
on_result(BufferSlice packet)184   void on_result(BufferSlice packet) final {
185     auto result_ptr = fetch_result<telegram_api::messages_getInlineGameHighScores>(packet);
186     if (result_ptr.is_error()) {
187       return on_error(result_ptr.move_as_error());
188     }
189 
190     promise_.set_value(td_->game_manager_->get_game_high_scores_object(result_ptr.move_as_ok()));
191   }
192 
on_error(Status status)193   void on_error(Status status) final {
194     promise_.set_error(std::move(status));
195   }
196 };
197 
GameManager(Td * td,ActorShared<> parent)198 GameManager::GameManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
199 }
200 
201 GameManager::~GameManager() = default;
202 
tear_down()203 void GameManager::tear_down() {
204   parent_.reset();
205 }
206 
set_game_score(FullMessageId full_message_id,bool edit_message,UserId user_id,int32 score,bool force,Promise<td_api::object_ptr<td_api::message>> && promise)207 void GameManager::set_game_score(FullMessageId full_message_id, bool edit_message, UserId user_id, int32 score,
208                                  bool force, Promise<td_api::object_ptr<td_api::message>> &&promise) {
209   CHECK(td_->auth_manager_->is_bot());
210 
211   if (!td_->messages_manager_->have_message_force(full_message_id, "set_game_score")) {
212     return promise.set_error(Status::Error(400, "Message not found"));
213   }
214 
215   auto dialog_id = full_message_id.get_dialog_id();
216   if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Edit)) {
217     return promise.set_error(Status::Error(400, "Can't access the chat"));
218   }
219 
220   auto input_user = td_->contacts_manager_->get_input_user(user_id);
221   if (input_user == nullptr) {
222     return promise.set_error(Status::Error(400, "Invalid user identifier specified"));
223   }
224 
225   if (!td_->messages_manager_->can_set_game_score(full_message_id)) {
226     return promise.set_error(Status::Error(400, "Game score can't be set"));
227   }
228 
229   auto query_promise = PromiseCreator::lambda(
230       [actor_id = actor_id(this), full_message_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
231         if (result.is_error()) {
232           return promise.set_error(result.move_as_error());
233         }
234         send_closure(actor_id, &GameManager::on_set_game_score, full_message_id, std::move(promise));
235       });
236   send_closure(td_->create_net_actor<SetGameScoreActor>(std::move(query_promise)), &SetGameScoreActor::send, dialog_id,
237                full_message_id.get_message_id(), edit_message, std::move(input_user), score, force,
238                MessagesManager::get_sequence_dispatcher_id(dialog_id, MessageContentType::None));
239 }
240 
on_set_game_score(FullMessageId full_message_id,Promise<td_api::object_ptr<td_api::message>> && promise)241 void GameManager::on_set_game_score(FullMessageId full_message_id,
242                                     Promise<td_api::object_ptr<td_api::message>> &&promise) {
243   promise.set_value(td_->messages_manager_->get_message_object(full_message_id, "on_set_game_score"));
244 }
245 
set_inline_game_score(const string & inline_message_id,bool edit_message,UserId user_id,int32 score,bool force,Promise<Unit> && promise)246 void GameManager::set_inline_game_score(const string &inline_message_id, bool edit_message, UserId user_id, int32 score,
247                                         bool force, Promise<Unit> &&promise) {
248   CHECK(td_->auth_manager_->is_bot());
249 
250   auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
251   if (input_bot_inline_message_id == nullptr) {
252     return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
253   }
254 
255   auto input_user = td_->contacts_manager_->get_input_user(user_id);
256   if (input_user == nullptr) {
257     return promise.set_error(Status::Error(400, "Wrong user identifier specified"));
258   }
259 
260   td_->create_handler<SetInlineGameScoreQuery>(std::move(promise))
261       ->send(std::move(input_bot_inline_message_id), edit_message, std::move(input_user), score, force);
262 }
263 
get_game_high_scores(FullMessageId full_message_id,UserId user_id,Promise<td_api::object_ptr<td_api::gameHighScores>> && promise)264 void GameManager::get_game_high_scores(FullMessageId full_message_id, UserId user_id,
265                                        Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise) {
266   CHECK(td_->auth_manager_->is_bot());
267 
268   if (!td_->messages_manager_->have_message_force(full_message_id, "get_game_high_scores")) {
269     return promise.set_error(Status::Error(400, "Message not found"));
270   }
271 
272   auto dialog_id = full_message_id.get_dialog_id();
273   if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
274     return promise.set_error(Status::Error(400, "Can't access the chat"));
275   }
276   auto message_id = full_message_id.get_message_id();
277   if (message_id.is_scheduled() || !message_id.is_server()) {
278     return promise.set_error(Status::Error(400, "Wrong message identifier specified"));
279   }
280 
281   auto input_user = td_->contacts_manager_->get_input_user(user_id);
282   if (input_user == nullptr) {
283     return promise.set_error(Status::Error(400, "Wrong user identifier specified"));
284   }
285 
286   td_->create_handler<GetGameHighScoresQuery>(std::move(promise))->send(dialog_id, message_id, std::move(input_user));
287 }
288 
get_inline_game_high_scores(const string & inline_message_id,UserId user_id,Promise<td_api::object_ptr<td_api::gameHighScores>> && promise)289 void GameManager::get_inline_game_high_scores(const string &inline_message_id, UserId user_id,
290                                               Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise) {
291   CHECK(td_->auth_manager_->is_bot());
292 
293   auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
294   if (input_bot_inline_message_id == nullptr) {
295     return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
296   }
297 
298   auto input_user = td_->contacts_manager_->get_input_user(user_id);
299   if (input_user == nullptr) {
300     return promise.set_error(Status::Error(400, "Wrong user identifier specified"));
301   }
302 
303   td_->create_handler<GetInlineGameHighScoresQuery>(std::move(promise))
304       ->send(std::move(input_bot_inline_message_id), std::move(input_user));
305 }
306 
get_game_high_scores_object(telegram_api::object_ptr<telegram_api::messages_highScores> && high_scores)307 td_api::object_ptr<td_api::gameHighScores> GameManager::get_game_high_scores_object(
308     telegram_api::object_ptr<telegram_api::messages_highScores> &&high_scores) {
309   td_->contacts_manager_->on_get_users(std::move(high_scores->users_), "get_game_high_scores_object");
310 
311   auto result = td_api::make_object<td_api::gameHighScores>();
312   for (const auto &high_score : high_scores->scores_) {
313     int32 position = high_score->pos_;
314     UserId user_id(high_score->user_id_);
315     int32 score = high_score->score_;
316     if (position <= 0 || !user_id.is_valid() || score < 0) {
317       LOG(ERROR) << "Receive wrong " << to_string(high_score);
318       continue;
319     }
320     result->scores_.push_back(make_tl_object<td_api::gameHighScore>(
321         position, td_->contacts_manager_->get_user_id_object(user_id, "get_game_high_scores_object"), score));
322   }
323   return result;
324 }
325 
326 }  // namespace td
327