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