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/BotCommand.h"
8 
9 #include "td/telegram/BotCommandScope.h"
10 #include "td/telegram/ContactsManager.h"
11 #include "td/telegram/Global.h"
12 #include "td/telegram/misc.h"
13 #include "td/telegram/Td.h"
14 
15 #include "td/utils/algorithm.h"
16 #include "td/utils/buffer.h"
17 #include "td/utils/logging.h"
18 #include "td/utils/misc.h"
19 #include "td/utils/SliceBuilder.h"
20 #include "td/utils/Status.h"
21 #include "td/utils/utf8.h"
22 
23 namespace td {
24 
25 class SetBotCommandsQuery final : public Td::ResultHandler {
26   Promise<Unit> promise_;
27 
28  public:
SetBotCommandsQuery(Promise<Unit> && promise)29   explicit SetBotCommandsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
30   }
31 
send(BotCommandScope scope,const string & language_code,vector<BotCommand> && commands)32   void send(BotCommandScope scope, const string &language_code, vector<BotCommand> &&commands) {
33     send_query(G()->net_query_creator().create(telegram_api::bots_setBotCommands(
34         scope.get_input_bot_command_scope(td_), language_code,
35         transform(commands, [](const BotCommand &command) { return command.get_input_bot_command(); }))));
36   }
37 
on_result(BufferSlice packet)38   void on_result(BufferSlice packet) final {
39     auto result_ptr = fetch_result<telegram_api::bots_setBotCommands>(packet);
40     if (result_ptr.is_error()) {
41       return on_error(result_ptr.move_as_error());
42     }
43 
44     if (!result_ptr.ok()) {
45       LOG(ERROR) << "Set bot commands request failed";
46     }
47     promise_.set_value(Unit());
48   }
49 
on_error(Status status)50   void on_error(Status status) final {
51     promise_.set_error(std::move(status));
52   }
53 };
54 
55 class ResetBotCommandsQuery final : public Td::ResultHandler {
56   Promise<Unit> promise_;
57 
58  public:
ResetBotCommandsQuery(Promise<Unit> && promise)59   explicit ResetBotCommandsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
60   }
61 
send(BotCommandScope scope,const string & language_code)62   void send(BotCommandScope scope, const string &language_code) {
63     send_query(G()->net_query_creator().create(
64         telegram_api::bots_resetBotCommands(scope.get_input_bot_command_scope(td_), language_code)));
65   }
66 
on_result(BufferSlice packet)67   void on_result(BufferSlice packet) final {
68     auto result_ptr = fetch_result<telegram_api::bots_resetBotCommands>(packet);
69     if (result_ptr.is_error()) {
70       return on_error(result_ptr.move_as_error());
71     }
72 
73     promise_.set_value(Unit());
74   }
75 
on_error(Status status)76   void on_error(Status status) final {
77     promise_.set_error(std::move(status));
78   }
79 };
80 
81 class GetBotCommandsQuery final : public Td::ResultHandler {
82   Promise<td_api::object_ptr<td_api::botCommands>> promise_;
83 
84  public:
GetBotCommandsQuery(Promise<td_api::object_ptr<td_api::botCommands>> && promise)85   explicit GetBotCommandsQuery(Promise<td_api::object_ptr<td_api::botCommands>> &&promise)
86       : promise_(std::move(promise)) {
87   }
88 
send(BotCommandScope scope,const string & language_code)89   void send(BotCommandScope scope, const string &language_code) {
90     send_query(G()->net_query_creator().create(
91         telegram_api::bots_getBotCommands(scope.get_input_bot_command_scope(td_), language_code)));
92   }
93 
on_result(BufferSlice packet)94   void on_result(BufferSlice packet) final {
95     auto result_ptr = fetch_result<telegram_api::bots_getBotCommands>(packet);
96     if (result_ptr.is_error()) {
97       return on_error(result_ptr.move_as_error());
98     }
99 
100     BotCommands commands(td_->contacts_manager_->get_my_id(), result_ptr.move_as_ok());
101     promise_.set_value(commands.get_bot_commands_object(td_));
102   }
103 
on_error(Status status)104   void on_error(Status status) final {
105     promise_.set_error(std::move(status));
106   }
107 };
108 
BotCommand(telegram_api::object_ptr<telegram_api::botCommand> && bot_command)109 BotCommand::BotCommand(telegram_api::object_ptr<telegram_api::botCommand> &&bot_command) {
110   CHECK(bot_command != nullptr);
111   command_ = std::move(bot_command->command_);
112   description_ = std::move(bot_command->description_);
113 }
114 
get_bot_command_object() const115 td_api::object_ptr<td_api::botCommand> BotCommand::get_bot_command_object() const {
116   return td_api::make_object<td_api::botCommand>(command_, description_);
117 }
118 
get_input_bot_command() const119 telegram_api::object_ptr<telegram_api::botCommand> BotCommand::get_input_bot_command() const {
120   return telegram_api::make_object<telegram_api::botCommand>(command_, description_);
121 }
122 
operator ==(const BotCommand & lhs,const BotCommand & rhs)123 bool operator==(const BotCommand &lhs, const BotCommand &rhs) {
124   return lhs.command_ == rhs.command_ && lhs.description_ == rhs.description_;
125 }
126 
BotCommands(UserId bot_user_id,vector<telegram_api::object_ptr<telegram_api::botCommand>> && bot_commands)127 BotCommands::BotCommands(UserId bot_user_id, vector<telegram_api::object_ptr<telegram_api::botCommand>> &&bot_commands)
128     : bot_user_id_(bot_user_id) {
129   commands_ = transform(std::move(bot_commands), [](telegram_api::object_ptr<telegram_api::botCommand> &&bot_command) {
130     return BotCommand(std::move(bot_command));
131   });
132 }
133 
get_bot_commands_object(Td * td) const134 td_api::object_ptr<td_api::botCommands> BotCommands::get_bot_commands_object(Td *td) const {
135   auto commands = transform(commands_, [](const auto &command) { return command.get_bot_command_object(); });
136   return td_api::make_object<td_api::botCommands>(
137       td->contacts_manager_->get_user_id_object(bot_user_id_, "get_bot_commands_object"), std::move(commands));
138 }
139 
operator ==(const BotCommands & lhs,const BotCommands & rhs)140 bool operator==(const BotCommands &lhs, const BotCommands &rhs) {
141   return lhs.bot_user_id_ == rhs.bot_user_id_ && lhs.commands_ == rhs.commands_;
142 }
143 
is_valid_language_code(const string & language_code)144 static bool is_valid_language_code(const string &language_code) {
145   if (language_code.empty()) {
146     return true;
147   }
148   if (language_code.size() != 2) {
149     return false;
150   }
151   return 'a' <= language_code[0] && language_code[0] <= 'z' && 'a' <= language_code[1] && language_code[1] <= 'z';
152 }
153 
set_commands(Td * td,td_api::object_ptr<td_api::BotCommandScope> && scope_ptr,string && language_code,vector<td_api::object_ptr<td_api::botCommand>> && commands,Promise<Unit> && promise)154 void set_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
155                   vector<td_api::object_ptr<td_api::botCommand>> &&commands, Promise<Unit> &&promise) {
156   TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
157 
158   if (!is_valid_language_code(language_code)) {
159     return promise.set_error(Status::Error(400, "Invalid language code specified"));
160   }
161 
162   vector<BotCommand> new_commands;
163   for (auto &command : commands) {
164     if (command == nullptr) {
165       return promise.set_error(Status::Error(400, "Command must be non-empty"));
166     }
167     if (!clean_input_string(command->command_)) {
168       return promise.set_error(Status::Error(400, "Command must be encoded in UTF-8"));
169     }
170     if (!clean_input_string(command->description_)) {
171       return promise.set_error(Status::Error(400, "Command description must be encoded in UTF-8"));
172     }
173 
174     const size_t MAX_COMMAND_TEXT_LENGTH = 32;
175     command->command_ = trim(command->command_);
176     if (command->command_[0] == '/') {
177       command->command_ = command->command_.substr(1);
178     }
179     if (command->command_.empty()) {
180       return promise.set_error(Status::Error(400, "Command must be non-empty"));
181     }
182     if (utf8_length(command->command_) > MAX_COMMAND_TEXT_LENGTH) {
183       return promise.set_error(
184           Status::Error(400, PSLICE() << "Command length must not exceed " << MAX_COMMAND_TEXT_LENGTH));
185     }
186 
187     const size_t MAX_COMMAND_DESCRIPTION_LENGTH = 256;
188     command->description_ = trim(command->description_);
189     auto description_length = utf8_length(command->description_);
190     if (command->description_.empty()) {
191       return promise.set_error(Status::Error(400, "Command description must be non-empty"));
192     }
193     if (description_length > MAX_COMMAND_DESCRIPTION_LENGTH) {
194       return promise.set_error(Status::Error(
195           400, PSLICE() << "Command description length must not exceed " << MAX_COMMAND_DESCRIPTION_LENGTH));
196     }
197 
198     new_commands.emplace_back(std::move(command->command_), std::move(command->description_));
199   }
200 
201   td->create_handler<SetBotCommandsQuery>(std::move(promise))->send(scope, language_code, std::move(new_commands));
202 }
203 
delete_commands(Td * td,td_api::object_ptr<td_api::BotCommandScope> && scope_ptr,string && language_code,Promise<Unit> && promise)204 void delete_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
205                      Promise<Unit> &&promise) {
206   TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
207 
208   if (!is_valid_language_code(language_code)) {
209     return promise.set_error(Status::Error(400, "Invalid language code specified"));
210   }
211 
212   td->create_handler<ResetBotCommandsQuery>(std::move(promise))->send(scope, language_code);
213 }
214 
get_commands(Td * td,td_api::object_ptr<td_api::BotCommandScope> && scope_ptr,string && language_code,Promise<td_api::object_ptr<td_api::botCommands>> && promise)215 void get_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
216                   Promise<td_api::object_ptr<td_api::botCommands>> &&promise) {
217   TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
218 
219   if (!is_valid_language_code(language_code)) {
220     return promise.set_error(Status::Error(400, "Invalid language code specified"));
221   }
222 
223   td->create_handler<GetBotCommandsQuery>(std::move(promise))->send(scope, language_code);
224 }
225 
226 }  // namespace td
227