mirror of
https://github.com/tdlight-team/tdlight-telegram-bot-api.git
synced 2024-11-30 07:52:55 +01:00
fix conflict
This commit is contained in:
commit
a1510e45bb
@ -1,6 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
|
||||
|
||||
project(TelegramBotApi VERSION 5.2 LANGUAGES CXX)
|
||||
if (POLICY CMP0065)
|
||||
# do not export symbols from executables
|
||||
# affects compiler checks in project(), so must be set before it
|
||||
cmake_policy(SET CMP0065 NEW)
|
||||
endif()
|
||||
|
||||
project(TelegramBotApi VERSION 5.3 LANGUAGES CXX)
|
||||
|
||||
add_subdirectory(td EXCLUDE_FROM_ALL)
|
||||
|
||||
|
@ -121,7 +121,7 @@ function onLoad(initial) {
|
||||
function onOsChanged(initial) {
|
||||
var os = document.getElementById('osSelect').value;
|
||||
if (os.includes('Choose ')) {
|
||||
if (history.state != '') {
|
||||
if (history.state !== '' && history.state !== null) {
|
||||
history.pushState('', '', 'build.html');
|
||||
}
|
||||
|
||||
|
2
td
2
td
@ -1 +1 @@
|
||||
Subproject commit 49fd5cbd874601da8a9ff0083894d7104a4e8680
|
||||
Subproject commit 1ea79d273976c22655742d4031ebbe62dba6cd7c
|
@ -25,6 +25,7 @@
|
||||
#include "td/utils/port/Stat.h"
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Span.h"
|
||||
#include "td/utils/StackAllocator.h"
|
||||
#include "td/utils/Status.h"
|
||||
@ -75,6 +76,10 @@ void Client::fail_query_with_error(PromisedQueryPtr query, int32 error_code, Sli
|
||||
int32 real_error_code = error_code;
|
||||
Slice real_error_message = error_message;
|
||||
if (error_code < 300 || error_code == 404) {
|
||||
if (error_code <= 0) {
|
||||
LOG(ERROR) << "Receive error \"" << real_error_message << "\" with code " << error_code << " from " << *query;
|
||||
}
|
||||
|
||||
error_code = 400;
|
||||
}
|
||||
if (error_code == 400) {
|
||||
@ -197,6 +202,7 @@ bool Client::init_methods() {
|
||||
methods_.emplace("getme", &Client::process_get_me_query);
|
||||
methods_.emplace("getmycommands", &Client::process_get_my_commands_query);
|
||||
methods_.emplace("setmycommands", &Client::process_set_my_commands_query);
|
||||
methods_.emplace("deletemycommands", &Client::process_delete_my_commands_query);
|
||||
methods_.emplace("getuserprofilephotos", &Client::process_get_user_profile_photos_query);
|
||||
methods_.emplace("sendmessage", &Client::process_send_message_query);
|
||||
methods_.emplace("sendanimation", &Client::process_send_animation_query);
|
||||
@ -336,7 +342,7 @@ class Client::JsonDatedFile : public Jsonable {
|
||||
|
||||
class Client::JsonDatedFiles : public Jsonable {
|
||||
public:
|
||||
JsonDatedFiles(const td::vector<td_api::object_ptr<td_api::datedFile>> &files, const Client *client)
|
||||
JsonDatedFiles(const td::vector<object_ptr<td_api::datedFile>> &files, const Client *client)
|
||||
: files_(files), client_(client) {
|
||||
}
|
||||
void store(JsonValueScope *scope) const {
|
||||
@ -347,7 +353,7 @@ class Client::JsonDatedFiles : public Jsonable {
|
||||
}
|
||||
|
||||
private:
|
||||
const td::vector<td_api::object_ptr<td_api::datedFile>> &files_;
|
||||
const td::vector<object_ptr<td_api::datedFile>> &files_;
|
||||
const Client *client_;
|
||||
};
|
||||
|
||||
@ -1592,7 +1598,7 @@ class Client::JsonInlineKeyboardButton : public Jsonable {
|
||||
case td_api::inlineKeyboardButtonTypeCallbackWithPassword::ID: {
|
||||
auto data = get_callback_data(button_->type_);
|
||||
if (!td::check_utf8(data)) {
|
||||
object("callback_data", td::JsonRawString(data));
|
||||
object("callback_data", "INVALID");
|
||||
} else {
|
||||
object("callback_data", data);
|
||||
}
|
||||
@ -2398,6 +2404,7 @@ class Client::JsonChatMembers : public Jsonable {
|
||||
continue;
|
||||
}
|
||||
auto user_id = static_cast<const td_api::messageSenderUser *>(member->member_id_.get())->user_id_;
|
||||
/*
|
||||
bool is_member_bot = member->bot_info_ != nullptr;
|
||||
if (!is_member_bot) {
|
||||
// bot info may be unknown
|
||||
@ -2406,7 +2413,7 @@ class Client::JsonChatMembers : public Jsonable {
|
||||
is_member_bot = true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
if (is_member_bot && user_id != client_->my_id_) {
|
||||
continue;
|
||||
}
|
||||
@ -3470,13 +3477,9 @@ class Client::TdOnGetMyCommandsCallback : public TdQueryCallback {
|
||||
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result));
|
||||
}
|
||||
|
||||
CHECK(result->get_id() == td_api::userFullInfo::ID);
|
||||
auto user_full_info = move_object_as<td_api::userFullInfo>(result);
|
||||
td::vector<object_ptr<td_api::botCommand>> commands;
|
||||
if (user_full_info->bot_info_ != nullptr) {
|
||||
commands = std::move(user_full_info->bot_info_->commands_);
|
||||
}
|
||||
answer_query(td::json_array(commands, [](auto &command) { return JsonBotCommand(command.get()); }),
|
||||
CHECK(result->get_id() == td_api::botCommands::ID);
|
||||
auto bot_commands = move_object_as<td_api::botCommands>(result);
|
||||
answer_query(td::json_array(bot_commands->commands_, [](auto &command) { return JsonBotCommand(command.get()); }),
|
||||
std::move(query_));
|
||||
}
|
||||
|
||||
@ -3960,18 +3963,14 @@ void Client::start_up() {
|
||||
CHECK(r_absolute_dir.is_ok());
|
||||
absolute_dir_ = r_absolute_dir.move_as_ok();
|
||||
auto suff = bot_token_with_dc_ + TD_DIR_SLASH;
|
||||
#if TD_PORT_WINDOWS
|
||||
for (auto &c : suff) {
|
||||
if (c == ':') {
|
||||
c = '~';
|
||||
if (!parameters_->allow_colon_in_filenames_) {
|
||||
for (auto &c : suff) {
|
||||
if (c == ':') {
|
||||
c = '~';
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
dir_ = td::string(".") + TD_DIR_SLASH + suff;
|
||||
if (absolute_dir_.back() != TD_DIR_SLASH) {
|
||||
absolute_dir_ += TD_DIR_SLASH;
|
||||
}
|
||||
absolute_dir_ += suff;
|
||||
dir_ = parameters_->working_directory_ + suff;
|
||||
|
||||
class TdCallback : public td::TdCallback {
|
||||
public:
|
||||
@ -3995,10 +3994,7 @@ void Client::start_up() {
|
||||
|
||||
void Client::send(PromisedQueryPtr query) {
|
||||
if (!query->is_internal()) {
|
||||
send_closure(
|
||||
stat_actor_, &BotStatActor::add_event<ServerBotStat::Request>,
|
||||
ServerBotStat::Request{query->query_size(), query->file_count(), query->files_size(), query->files_max_size()},
|
||||
td::Time::now());
|
||||
query->set_stat_actor(stat_actor_);
|
||||
}
|
||||
cmd_queue_.emplace(std::move(query));
|
||||
loop();
|
||||
@ -4174,6 +4170,10 @@ void Client::check_chat_access(int64 chat_id, AccessRights access_rights, const
|
||||
break;
|
||||
}
|
||||
case ChatInfo::Type::Group: {
|
||||
if (access_rights == AccessRights::ReadMembers) { // member list is inaccessible in deactivated groups
|
||||
need_write_access = true;
|
||||
need_edit_access = true;
|
||||
}
|
||||
auto group_info = get_group_info(chat_info->group_id);
|
||||
CHECK(group_info != nullptr);
|
||||
if (!group_info->is_active && need_write_access) {
|
||||
@ -4184,9 +4184,7 @@ void Client::check_chat_access(int64 chat_id, AccessRights access_rights, const
|
||||
return fail_query(400, "Bad Request: group chat was upgraded to a supergroup chat", std::move(query),
|
||||
std::move(parameters));
|
||||
} else {
|
||||
LOG(WARNING) << "Group chat " << chat_info->group_id << " with " << group_info->member_count
|
||||
<< " members and title \"" << chat_info->title << "\" is deactivated";
|
||||
return fail_query(400, "Bad Request: group chat was deactivated", std::move(query));
|
||||
return fail_query(403, "Forbidden: the group chat was deleted", std::move(query));
|
||||
}
|
||||
}
|
||||
if (group_info->is_active && group_info->kicked && need_edit_access) {
|
||||
@ -4247,6 +4245,36 @@ void Client::check_chat(Slice chat_id_str, AccessRights access_rights, PromisedQ
|
||||
std::move(on_success)));
|
||||
}
|
||||
|
||||
template <class OnSuccess>
|
||||
void Client::check_bot_command_scope(BotCommandScope &&scope, PromisedQueryPtr query, OnSuccess on_success) {
|
||||
CHECK(scope.scope_ != nullptr);
|
||||
if (scope.chat_id_.empty()) {
|
||||
on_success(std::move(scope.scope_), std::move(query));
|
||||
return;
|
||||
}
|
||||
check_chat(scope.chat_id_, AccessRights::ReadMembers, std::move(query),
|
||||
[this, user_id = scope.user_id_, scope_id = scope.scope_->get_id(), on_success = std::move(on_success)](
|
||||
int64 chat_id, PromisedQueryPtr query) mutable {
|
||||
switch (scope_id) {
|
||||
case td_api::botCommandScopeChat::ID:
|
||||
on_success(make_object<td_api::botCommandScopeChat>(chat_id), std::move(query));
|
||||
break;
|
||||
case td_api::botCommandScopeChatAdministrators::ID:
|
||||
on_success(make_object<td_api::botCommandScopeChatAdministrators>(chat_id), std::move(query));
|
||||
break;
|
||||
case td_api::botCommandScopeChatMember::ID:
|
||||
check_user_no_fail(
|
||||
user_id, std::move(query),
|
||||
[chat_id, user_id, on_success = std::move(on_success)](PromisedQueryPtr query) mutable {
|
||||
on_success(make_object<td_api::botCommandScopeChatMember>(chat_id, user_id), std::move(query));
|
||||
});
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <class OnSuccess>
|
||||
void Client::check_remote_file_id(td::string file_id, PromisedQueryPtr query, OnSuccess on_success) {
|
||||
if (file_id.empty()) {
|
||||
@ -4577,6 +4605,8 @@ void Client::on_update_authorization_state() {
|
||||
case td_api::authorizationStateWaitEncryptionKey::ID:
|
||||
return send_request(make_object<td_api::checkDatabaseEncryptionKey>(), std::make_unique<TdOnInitCallback>(this));
|
||||
case td_api::authorizationStateWaitPhoneNumber::ID:
|
||||
send_request(make_object<td_api::setOption>("online", make_object<td_api::optionValueBoolean>(true)),
|
||||
std::make_unique<TdOnOkCallback>());
|
||||
if (is_user_) {
|
||||
waiting_for_auth_input_ = true;
|
||||
return loop();
|
||||
@ -5009,8 +5039,8 @@ void Client::on_closed() {
|
||||
td::ActorId<Client> parent_;
|
||||
|
||||
void start_up() override {
|
||||
CHECK(!dir_.empty());
|
||||
CHECK(dir_[0] == '.');
|
||||
CHECK(dir_.size() >= 24);
|
||||
CHECK(dir_.back() == TD_DIR_SLASH);
|
||||
td::rmrf(dir_).ignore();
|
||||
stop();
|
||||
}
|
||||
@ -5214,6 +5244,7 @@ td::Result<td_api::object_ptr<td_api::ReplyMarkup>> Client::get_reply_markup(con
|
||||
td::Result<td_api::object_ptr<td_api::ReplyMarkup>> Client::get_reply_markup(JsonValue &&value) {
|
||||
td::vector<td::vector<object_ptr<td_api::keyboardButton>>> rows;
|
||||
td::vector<td::vector<object_ptr<td_api::inlineKeyboardButton>>> inline_rows;
|
||||
Slice input_field_placeholder;
|
||||
bool resize = false;
|
||||
bool one_time = false;
|
||||
bool remove = false;
|
||||
@ -5289,18 +5320,24 @@ td::Result<td_api::object_ptr<td_api::ReplyMarkup>> Client::get_reply_markup(Jso
|
||||
return Status::Error(400, "Field \"force_reply\" of the reply markup must be of the type Boolean");
|
||||
}
|
||||
force_reply = field_value.second.get_boolean();
|
||||
} else if (field_value.first == "input_field_placeholder") {
|
||||
if (field_value.second.type() != JsonValue::Type::String) {
|
||||
return Status::Error(400, "Field \"input_field_placeholder\" of the reply markup must be of the type String");
|
||||
}
|
||||
input_field_placeholder = field_value.second.get_string();
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<td_api::ReplyMarkup> result;
|
||||
if (!rows.empty()) {
|
||||
result = make_object<td_api::replyMarkupShowKeyboard>(std::move(rows), resize, one_time, is_personal);
|
||||
result = make_object<td_api::replyMarkupShowKeyboard>(std::move(rows), resize, one_time, is_personal,
|
||||
input_field_placeholder.str());
|
||||
} else if (!inline_rows.empty()) {
|
||||
result = make_object<td_api::replyMarkupInlineKeyboard>(std::move(inline_rows));
|
||||
} else if (remove) {
|
||||
result = make_object<td_api::replyMarkupRemoveKeyboard>(is_personal);
|
||||
} else if (force_reply) {
|
||||
result = make_object<td_api::replyMarkupForceReply>(is_personal);
|
||||
result = make_object<td_api::replyMarkupForceReply>(is_personal, input_field_placeholder.str());
|
||||
}
|
||||
if (result == nullptr || result->get_id() != td_api::replyMarkupInlineKeyboard::ID) {
|
||||
unresolved_bot_usernames_.clear();
|
||||
@ -5970,6 +6007,69 @@ td::Result<td_api::object_ptr<td_api::InputInlineQueryResult>> Client::get_inlin
|
||||
return Status::Error(400, PSLICE() << "type \"" << type << "\" is unsupported for the inline query result");
|
||||
}
|
||||
|
||||
td::Result<Client::BotCommandScope> Client::get_bot_command_scope(JsonValue &&value) {
|
||||
if (value.type() != JsonValue::Type::Object) {
|
||||
return Status::Error(400, "BotCommandScope must be an Object");
|
||||
}
|
||||
|
||||
auto &object = value.get_object();
|
||||
|
||||
TRY_RESULT(type, get_json_object_string_field(object, "type", false));
|
||||
if (type == "default") {
|
||||
return BotCommandScope(make_object<td_api::botCommandScopeDefault>());
|
||||
}
|
||||
if (type == "all_private_chats") {
|
||||
return BotCommandScope(make_object<td_api::botCommandScopeAllPrivateChats>());
|
||||
}
|
||||
if (type == "all_group_chats") {
|
||||
return BotCommandScope(make_object<td_api::botCommandScopeAllGroupChats>());
|
||||
}
|
||||
if (type == "all_chat_administrators") {
|
||||
return BotCommandScope(make_object<td_api::botCommandScopeAllChatAdministrators>());
|
||||
}
|
||||
if (type != "chat" && type != "chat_administrators" && type != "chat_member") {
|
||||
return Status::Error(400, "Unsupported type specified");
|
||||
}
|
||||
|
||||
TRY_RESULT(chat_id, get_json_object_string_field(object, "chat_id", false));
|
||||
if (chat_id.empty()) {
|
||||
return Status::Error(400, "Empty chat_id specified");
|
||||
}
|
||||
if (type == "chat") {
|
||||
return BotCommandScope(make_object<td_api::botCommandScopeChat>(0), std::move(chat_id));
|
||||
}
|
||||
if (type == "chat_administrators") {
|
||||
return BotCommandScope(make_object<td_api::botCommandScopeChatAdministrators>(0), std::move(chat_id));
|
||||
}
|
||||
|
||||
TRY_RESULT(user_id, get_json_object_int_field(object, "user_id", false));
|
||||
if (user_id <= 0) {
|
||||
return Status::Error(400, "Invalid user_id specified");
|
||||
}
|
||||
CHECK(type == "chat_member");
|
||||
return BotCommandScope(make_object<td_api::botCommandScopeChatMember>(0, user_id), std::move(chat_id), user_id);
|
||||
}
|
||||
|
||||
td::Result<Client::BotCommandScope> Client::get_bot_command_scope(const Query *query) {
|
||||
auto scope = query->arg("scope");
|
||||
if (scope.empty()) {
|
||||
return BotCommandScope(make_object<td_api::botCommandScopeDefault>());
|
||||
}
|
||||
|
||||
LOG(INFO) << "Parsing JSON object: " << scope;
|
||||
auto r_value = json_decode(scope);
|
||||
if (r_value.is_error()) {
|
||||
LOG(INFO) << "Can't parse JSON object: " << r_value.error();
|
||||
return Status::Error(400, "Can't parse BotCommandScope JSON object");
|
||||
}
|
||||
|
||||
auto r_scope = get_bot_command_scope(r_value.move_as_ok());
|
||||
if (r_scope.is_error()) {
|
||||
return Status::Error(400, PSLICE() << "Can't parse BotCommandScope: " << r_scope.error().message());
|
||||
}
|
||||
return r_scope.move_as_ok();
|
||||
}
|
||||
|
||||
td::Result<td_api::object_ptr<td_api::botCommand>> Client::get_bot_command(JsonValue &&value) {
|
||||
if (value.type() != JsonValue::Type::Object) {
|
||||
return Status::Error(400, "expected an Object");
|
||||
@ -6604,11 +6704,8 @@ td::Result<td::vector<td::string>> Client::get_poll_options(const Query *query)
|
||||
td::int32 Client::get_integer_arg(const Query *query, Slice field_name, int32 default_value, int32 min_value,
|
||||
int32 max_value) {
|
||||
auto s_arg = query->arg(field_name);
|
||||
if (s_arg.empty()) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
return td::clamp(td::to_integer<int32>(s_arg), min_value, max_value);
|
||||
auto value = s_arg.empty() ? default_value : td::to_integer<int32>(s_arg);
|
||||
return td::clamp(value, min_value, max_value);
|
||||
}
|
||||
|
||||
td::Result<td::MutableSlice> Client::get_required_string_arg(const Query *query, Slice field_name) {
|
||||
@ -6922,16 +7019,42 @@ td::Status Client::process_get_me_query(PromisedQueryPtr &query) {
|
||||
}
|
||||
|
||||
td::Status Client::process_get_my_commands_query(PromisedQueryPtr &query) {
|
||||
send_request(make_object<td_api::getUserFullInfo>(my_id_),
|
||||
std::make_unique<TdOnGetMyCommandsCallback>(std::move(query)));
|
||||
TRY_RESULT(scope, get_bot_command_scope(query.get()));
|
||||
|
||||
check_bot_command_scope(std::move(scope), std::move(query),
|
||||
[this](object_ptr<td_api::BotCommandScope> &&scope, PromisedQueryPtr query) mutable {
|
||||
auto language_code = query->arg("language_code").str();
|
||||
send_request(make_object<td_api::getCommands>(std::move(scope), language_code),
|
||||
std::make_unique<TdOnGetMyCommandsCallback>(std::move(query)));
|
||||
});
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
td::Status Client::process_set_my_commands_query(PromisedQueryPtr &query) {
|
||||
CHECK_IS_BOT();
|
||||
TRY_RESULT(bot_commands, get_bot_commands(query.get()));
|
||||
send_request(make_object<td_api::setCommands>(std::move(bot_commands)),
|
||||
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
||||
TRY_RESULT(scope, get_bot_command_scope(query.get()));
|
||||
|
||||
check_bot_command_scope(
|
||||
std::move(scope), std::move(query),
|
||||
[this, bot_commands = std::move(bot_commands)](object_ptr<td_api::BotCommandScope> &&scope,
|
||||
PromisedQueryPtr query) mutable {
|
||||
auto language_code = query->arg("language_code").str();
|
||||
send_request(make_object<td_api::setCommands>(std::move(scope), language_code, std::move(bot_commands)),
|
||||
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
||||
});
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
td::Status Client::process_delete_my_commands_query(PromisedQueryPtr &query) {
|
||||
TRY_RESULT(scope, get_bot_command_scope(query.get()));
|
||||
|
||||
check_bot_command_scope(std::move(scope), std::move(query),
|
||||
[this](object_ptr<td_api::BotCommandScope> &&scope, PromisedQueryPtr query) mutable {
|
||||
auto language_code = query->arg("language_code").str();
|
||||
send_request(make_object<td_api::deleteCommands>(std::move(scope), language_code),
|
||||
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
||||
});
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@ -7258,7 +7381,7 @@ td::Status Client::process_copy_message_query(PromisedQueryPtr &query) {
|
||||
TRY_RESULT(from_chat_id, get_required_string_arg(query.get(), "from_chat_id"));
|
||||
auto message_id = get_message_id(query.get());
|
||||
bool replace_caption = query->has_arg("caption");
|
||||
td_api::object_ptr<td_api::formattedText> caption;
|
||||
object_ptr<td_api::formattedText> caption;
|
||||
if (replace_caption) {
|
||||
TRY_RESULT_ASSIGN(caption, get_caption(query.get()));
|
||||
}
|
||||
@ -7887,31 +8010,29 @@ td::Status Client::process_get_chat_member_query(PromisedQueryPtr &query) {
|
||||
auto chat_id = query->arg("chat_id");
|
||||
TRY_RESULT(user_id, get_user_id(query.get()));
|
||||
|
||||
check_chat(chat_id, AccessRights::Read, std::move(query), [this, user_id](int64 chat_id, PromisedQueryPtr query) {
|
||||
get_chat_member(chat_id, user_id, std::move(query),
|
||||
[this, chat_type = get_chat_type(chat_id)](td_api::object_ptr<td_api::chatMember> &&chat_member,
|
||||
PromisedQueryPtr query) {
|
||||
answer_query(JsonChatMember(chat_member.get(), chat_type, this), std::move(query));
|
||||
});
|
||||
});
|
||||
check_chat(chat_id, AccessRights::ReadMembers, std::move(query),
|
||||
[this, user_id](int64 chat_id, PromisedQueryPtr query) {
|
||||
get_chat_member(chat_id, user_id, std::move(query),
|
||||
[this, chat_type = get_chat_type(chat_id)](object_ptr<td_api::chatMember> &&chat_member,
|
||||
PromisedQueryPtr query) {
|
||||
answer_query(JsonChatMember(chat_member.get(), chat_type, this), std::move(query));
|
||||
});
|
||||
});
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
td::Status Client::process_get_chat_administrators_query(PromisedQueryPtr &query) {
|
||||
auto chat_id = query->arg("chat_id");
|
||||
|
||||
check_chat(chat_id, AccessRights::Read, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
||||
check_chat(chat_id, AccessRights::ReadMembers, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
||||
auto chat_info = get_chat(chat_id);
|
||||
CHECK(chat_info != nullptr);
|
||||
switch (chat_info->type) {
|
||||
case ChatInfo::Type::Private:
|
||||
return fail_query(400, "Bad Request: there are no administrators in the private chat", std::move(query));
|
||||
case ChatInfo::Type::Group: {
|
||||
auto group_info = get_group_info(chat_info->group_id);
|
||||
CHECK(group_info != nullptr);
|
||||
case ChatInfo::Type::Group:
|
||||
return send_request(make_object<td_api::getBasicGroupFullInfo>(chat_info->group_id),
|
||||
std::make_unique<TdOnGetGroupMembersCallback>(this, true, std::move(query)));
|
||||
}
|
||||
case ChatInfo::Type::Supergroup:
|
||||
return send_request(
|
||||
make_object<td_api::getSupergroupMembers>(
|
||||
@ -7928,7 +8049,7 @@ td::Status Client::process_get_chat_administrators_query(PromisedQueryPtr &query
|
||||
td::Status Client::process_get_chat_member_count_query(PromisedQueryPtr &query) {
|
||||
auto chat_id = query->arg("chat_id");
|
||||
|
||||
check_chat(chat_id, AccessRights::Read, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
||||
check_chat(chat_id, AccessRights::ReadMembers, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) {
|
||||
auto chat_info = get_chat(chat_id);
|
||||
CHECK(chat_info != nullptr);
|
||||
switch (chat_info->type) {
|
||||
@ -7937,9 +8058,6 @@ td::Status Client::process_get_chat_member_count_query(PromisedQueryPtr &query)
|
||||
case ChatInfo::Type::Group: {
|
||||
auto group_info = get_group_info(chat_info->group_id);
|
||||
CHECK(group_info != nullptr);
|
||||
if (group_info->member_count == 0) {
|
||||
return fail_query(403, "Forbidden: bot is not a member of the group chat", std::move(query));
|
||||
}
|
||||
return answer_query(td::VirtuallyJsonableInt(group_info->member_count), std::move(query));
|
||||
}
|
||||
case ChatInfo::Type::Supergroup:
|
||||
@ -8016,8 +8134,8 @@ td::Status Client::process_promote_chat_member_query(PromisedQueryPtr &query) {
|
||||
|
||||
get_chat_member(
|
||||
chat_id, user_id, std::move(query),
|
||||
[this, chat_id, user_id, status = std::move(status)](
|
||||
td_api::object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) mutable {
|
||||
[this, chat_id, user_id, status = std::move(status)](object_ptr<td_api::chatMember> &&chat_member,
|
||||
PromisedQueryPtr query) mutable {
|
||||
if (chat_member->status_->get_id() == td_api::chatMemberStatusAdministrator::ID) {
|
||||
auto administrator =
|
||||
static_cast<const td_api::chatMemberStatusAdministrator *>(chat_member->status_.get());
|
||||
@ -8044,7 +8162,7 @@ td::Status Client::process_set_chat_administrator_custom_title_query(PromisedQue
|
||||
|
||||
get_chat_member(
|
||||
chat_id, user_id, std::move(query),
|
||||
[this, chat_id, user_id](td_api::object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) {
|
||||
[this, chat_id, user_id](object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) {
|
||||
if (chat_member->status_->get_id() == td_api::chatMemberStatusCreator::ID) {
|
||||
return fail_query(400, "Bad Request: only creator can edit their custom title", std::move(query));
|
||||
}
|
||||
@ -8102,7 +8220,7 @@ td::Status Client::process_restrict_chat_member_query(PromisedQueryPtr &query) {
|
||||
get_chat_member(
|
||||
chat_id, user_id, std::move(query),
|
||||
[this, chat_id, user_id, until_date, is_legacy, permissions = std::move(permissions)](
|
||||
td_api::object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) mutable {
|
||||
object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) mutable {
|
||||
if (is_legacy && chat_member->status_->get_id() == td_api::chatMemberStatusRestricted::ID) {
|
||||
auto restricted =
|
||||
static_cast<const td_api::chatMemberStatusRestricted *>(chat_member->status_.get());
|
||||
@ -8138,18 +8256,18 @@ td::Status Client::process_unban_chat_member_query(PromisedQueryPtr &query) {
|
||||
}
|
||||
|
||||
if (only_if_banned) {
|
||||
get_chat_member(chat_id, user_id, std::move(query),
|
||||
[this, chat_id, user_id](td_api::object_ptr<td_api::chatMember> &&chat_member,
|
||||
PromisedQueryPtr query) {
|
||||
if (chat_member->status_->get_id() != td_api::chatMemberStatusBanned::ID) {
|
||||
return answer_query(td::JsonTrue(), std::move(query));
|
||||
}
|
||||
get_chat_member(
|
||||
chat_id, user_id, std::move(query),
|
||||
[this, chat_id, user_id](object_ptr<td_api::chatMember> &&chat_member, PromisedQueryPtr query) {
|
||||
if (chat_member->status_->get_id() != td_api::chatMemberStatusBanned::ID) {
|
||||
return answer_query(td::JsonTrue(), std::move(query));
|
||||
}
|
||||
|
||||
send_request(make_object<td_api::setChatMemberStatus>(
|
||||
chat_id, td_api::make_object<td_api::messageSenderUser>(user_id),
|
||||
make_object<td_api::chatMemberStatusLeft>()),
|
||||
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
||||
});
|
||||
send_request(make_object<td_api::setChatMemberStatus>(
|
||||
chat_id, td_api::make_object<td_api::messageSenderUser>(user_id),
|
||||
make_object<td_api::chatMemberStatusLeft>()),
|
||||
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
|
||||
});
|
||||
} else {
|
||||
check_user_no_fail(user_id, std::move(query), [this, chat_id, user_id](PromisedQueryPtr query) {
|
||||
send_request(make_object<td_api::setChatMemberStatus>(
|
||||
@ -8181,7 +8299,8 @@ td::Status Client::process_upload_sticker_file_query(PromisedQueryPtr &query) {
|
||||
|
||||
check_user(user_id, std::move(query),
|
||||
[this, user_id, png_sticker = std::move(png_sticker)](PromisedQueryPtr query) mutable {
|
||||
send_request(make_object<td_api::uploadStickerFile>(user_id, std::move(png_sticker)),
|
||||
send_request(make_object<td_api::uploadStickerFile>(
|
||||
user_id, make_object<td_api::inputStickerStatic>(std::move(png_sticker), "", nullptr)),
|
||||
std::make_unique<TdOnReturnFileCallback>(this, std::move(query)));
|
||||
});
|
||||
return Status::OK();
|
||||
@ -8198,7 +8317,7 @@ td::Status Client::process_create_new_sticker_set_query(PromisedQueryPtr &query)
|
||||
check_user(user_id, std::move(query),
|
||||
[this, user_id, title, name, is_masks, stickers = std::move(stickers)](PromisedQueryPtr query) mutable {
|
||||
send_request(make_object<td_api::createNewStickerSet>(user_id, title.str(), name.str(), is_masks,
|
||||
std::move(stickers)),
|
||||
std::move(stickers), PSTRING() << "bot" << my_id_),
|
||||
std::make_unique<TdOnReturnStickerSetCallback>(this, false, std::move(query)));
|
||||
});
|
||||
return Status::OK();
|
||||
@ -8317,7 +8436,7 @@ td::Status Client::process_set_webhook_query(PromisedQueryPtr &query) {
|
||||
}
|
||||
|
||||
auto now = td::Time::now_cached();
|
||||
if (!new_url.empty()) {
|
||||
if (!new_url.empty() && !query->is_internal()) {
|
||||
if (now < next_allowed_set_webhook_time_) {
|
||||
query->set_retry_after_error(1);
|
||||
return Status::OK();
|
||||
@ -8808,7 +8927,7 @@ void Client::do_get_file(object_ptr<td_api::file> file, PromisedQueryPtr query)
|
||||
auto file_id = file->id_;
|
||||
file_download_listeners_[file_id].push_back(std::move(query));
|
||||
if (file->local_->is_downloading_completed_) {
|
||||
Slice relative_path = td::PathView::relative(file->local_->path_, absolute_dir_, true);
|
||||
Slice relative_path = td::PathView::relative(file->local_->path_, dir_, true);
|
||||
if (!relative_path.empty()) {
|
||||
auto r_stat = td::stat(file->local_->path_);
|
||||
if (r_stat.is_ok() && r_stat.ok().is_reg_ && r_stat.ok().size_ == file->size_) {
|
||||
@ -9463,7 +9582,7 @@ void Client::json_store_file(td::JsonObjectScope &object, const td_api::file *fi
|
||||
object("file_path", td::JsonRawString(file->local_->path_));
|
||||
}
|
||||
} else {
|
||||
Slice relative_path = td::PathView::relative(file->local_->path_, absolute_dir_, true);
|
||||
Slice relative_path = td::PathView::relative(file->local_->path_, dir_, true);
|
||||
if (!relative_path.empty() && (parameters_->local_mode_ || parameters_->no_file_limit_ || file->local_->downloaded_size_ <= MAX_DOWNLOAD_FILE_SIZE)) {
|
||||
object("file_path", relative_path);
|
||||
}
|
||||
|
@ -237,8 +237,9 @@ class Client : public WebhookActor::Callback {
|
||||
|
||||
struct UserInfo;
|
||||
struct ChatInfo;
|
||||
struct BotCommandScope;
|
||||
|
||||
enum class AccessRights { Read, Edit, Write };
|
||||
enum class AccessRights { Read, ReadMembers, Edit, Write };
|
||||
|
||||
template <class OnSuccess>
|
||||
class TdOnCheckUserCallback;
|
||||
@ -288,6 +289,9 @@ class Client : public WebhookActor::Callback {
|
||||
|
||||
void enable_internet_connection(PromisedQueryPtr query);
|
||||
|
||||
template <class OnSuccess>
|
||||
void check_bot_command_scope(BotCommandScope &&scope, PromisedQueryPtr query, OnSuccess on_success);
|
||||
|
||||
template <class OnSuccess>
|
||||
void check_remote_file_id(td::string file_id, PromisedQueryPtr query, OnSuccess on_success);
|
||||
|
||||
@ -370,6 +374,21 @@ class Client : public WebhookActor::Callback {
|
||||
|
||||
td::Result<td::vector<object_ptr<td_api::InputInlineQueryResult>>> get_inline_query_results(td::JsonValue &&value);
|
||||
|
||||
struct BotCommandScope {
|
||||
object_ptr<td_api::BotCommandScope> scope_;
|
||||
td::string chat_id_;
|
||||
td::int32 user_id_ = 0;
|
||||
|
||||
explicit BotCommandScope(object_ptr<td_api::BotCommandScope> scope, td::string chat_id = td::string(),
|
||||
td::int32 user_id = 0)
|
||||
: scope_(std::move(scope)), chat_id_(std::move(chat_id)), user_id_(user_id) {
|
||||
}
|
||||
};
|
||||
|
||||
static td::Result<BotCommandScope> get_bot_command_scope(const Query *query);
|
||||
|
||||
static td::Result<BotCommandScope> get_bot_command_scope(td::JsonValue &&value);
|
||||
|
||||
static td::Result<object_ptr<td_api::botCommand>> get_bot_command(td::JsonValue &&value);
|
||||
|
||||
static td::Result<td::vector<object_ptr<td_api::botCommand>>> get_bot_commands(const Query *query);
|
||||
@ -474,6 +493,7 @@ class Client : public WebhookActor::Callback {
|
||||
Status process_get_me_query(PromisedQueryPtr &query);
|
||||
Status process_get_my_commands_query(PromisedQueryPtr &query);
|
||||
Status process_set_my_commands_query(PromisedQueryPtr &query);
|
||||
Status process_delete_my_commands_query(PromisedQueryPtr &query);
|
||||
Status process_get_user_profile_photos_query(PromisedQueryPtr &query);
|
||||
Status process_send_message_query(PromisedQueryPtr &query);
|
||||
Status process_send_animation_query(PromisedQueryPtr &query);
|
||||
@ -1017,7 +1037,6 @@ class Client : public WebhookActor::Callback {
|
||||
int64 current_bot_resolve_query_id_ = 1;
|
||||
|
||||
td::string dir_;
|
||||
td::string absolute_dir_;
|
||||
td::ActorOwn<td::ClientActor> td_client_;
|
||||
td::ActorContext context_;
|
||||
std::queue<PromisedQueryPtr> cmd_queue_;
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
#include "td/utils/port/Stat.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/StackAllocator.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
#include "td/utils/Time.h"
|
||||
@ -94,29 +95,24 @@ void ClientManager::send(PromisedQueryPtr query) {
|
||||
}
|
||||
auto id = clients_.create(ClientInfo{BotStatActor(stat_.actor_id(&stat_)), token, td::ActorOwn<Client>()});
|
||||
auto *client_info = clients_.get(id);
|
||||
auto stat_actor = client_info->stat_.actor_id(&client_info->stat_);
|
||||
auto client_id = td::create_actor<Client>(
|
||||
PSLICE() << "Client/" << token, actor_shared(this, id), query->token().str(), query->is_user(),
|
||||
query->is_test_dc(), get_tqueue_id(r_user_id.ok(), query->is_test_dc()), parameters_, std::move(stat_actor));
|
||||
client_info->client_ =
|
||||
td::create_actor<Client>(PSLICE() << "Client/" << token, actor_shared(this, id), query->token().str(), query->is_user(),
|
||||
query->is_test_dc(), get_tqueue_id(r_user_id.ok(), query->is_test_dc()), parameters_,
|
||||
client_info->stat_.actor_id(&client_info->stat_));
|
||||
|
||||
auto method = query->method();
|
||||
if (method != "deletewebhook" && method != "setwebhook") {
|
||||
auto webhook_info = parameters_->shared_data_->webhook_db_->get(bot_token_with_dc);
|
||||
if (!webhook_info.empty()) {
|
||||
send_closure(client_id, &Client::send,
|
||||
get_webhook_restore_query(bot_token_with_dc, query->is_user(), webhook_info, parameters_->shared_data_));
|
||||
send_closure(client_info->client_, &Client::send,
|
||||
get_webhook_restore_query(bot_token_with_dc, query->is_user() webhook_info, parameters_->shared_data_));
|
||||
}
|
||||
}
|
||||
|
||||
clients_.get(id)->client_ = std::move(client_id);
|
||||
std::tie(id_it, std::ignore) = token_to_id_.emplace(token, id);
|
||||
}
|
||||
auto *client_info = clients_.get(id_it->second);
|
||||
|
||||
if (!query->is_internal()) {
|
||||
query->set_stat_actor(client_info->stat_.actor_id(&client_info->stat_));
|
||||
}
|
||||
send_closure(client_info->client_, &Client::send, std::move(query)); // will send 429 if the client is already closed
|
||||
send_closure(clients_.get(id_it->second)->client_, &Client::send,
|
||||
std::move(query)); // will send 429 if the client is already closed
|
||||
}
|
||||
|
||||
void ClientManager::user_login(PromisedQueryPtr query) {
|
||||
@ -407,7 +403,7 @@ void ClientManager::start_up() {
|
||||
td::vector<td::uint64> failed_to_replay_log_event_ids;
|
||||
td::int64 loaded_event_count = 0;
|
||||
binlog
|
||||
->init("tqueue.binlog",
|
||||
->init(parameters_->working_directory_ + "tqueue.binlog",
|
||||
[&](const td::BinlogEvent &event) {
|
||||
if (tqueue_binlog->replay(event, *tqueue).is_error()) {
|
||||
failed_to_replay_log_event_ids.push_back(event.id_);
|
||||
@ -436,7 +432,8 @@ void ClientManager::start_up() {
|
||||
|
||||
// init webhook_db and user_db
|
||||
auto concurrent_webhook_db = td::make_unique<td::BinlogKeyValue<td::ConcurrentBinlog>>();
|
||||
auto status = concurrent_webhook_db->init("webhooks_db.binlog", td::DbKey::empty(), scheduler_id);
|
||||
auto status = concurrent_webhook_db->init(parameters_->working_directory_ + "webhooks_db.binlog", td::DbKey::empty(),
|
||||
scheduler_id);
|
||||
LOG_IF(FATAL, status.is_error()) << "Can't open webhooks_db.binlog " << status.error();
|
||||
parameters_->shared_data_->webhook_db_ = std::move(concurrent_webhook_db);
|
||||
|
||||
@ -510,8 +507,7 @@ PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, bool
|
||||
const auto method = add_string("setwebhook");
|
||||
auto query = std::make_unique<Query>(std::move(containers), token, is_user, is_test_dc, method, std::move(args),
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>>(),
|
||||
td::vector<td::HttpFile>(), std::move(shared_data), td::IPAddress());
|
||||
query->set_internal(true);
|
||||
td::vector<td::HttpFile>(), std::move(shared_data), td::IPAddress(), true);
|
||||
return PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor<td::unique_ptr<Query>>()));
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,9 @@ struct SharedData {
|
||||
};
|
||||
|
||||
struct ClientParameters {
|
||||
td::string working_directory_;
|
||||
bool allow_colon_in_filenames_ = true;
|
||||
|
||||
bool local_mode_ = false;
|
||||
bool allow_http_ = false;
|
||||
bool use_relative_path_ = false;
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "td/utils/JsonBuilder.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/Parser.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
@ -60,7 +61,7 @@ void HttpConnection::handle(td::unique_ptr<td::HttpQuery> http_query,
|
||||
|
||||
auto query = std::make_unique<Query>(std::move(http_query->container_), token, is_user, is_test_dc, method,
|
||||
std::move(http_query->args_), std::move(http_query->headers_),
|
||||
std::move(http_query->files_), shared_data_, http_query->peer_address_);
|
||||
std::move(http_query->files_), shared_data_, http_query->peer_address_, false);
|
||||
|
||||
td::PromiseActor<td::unique_ptr<Query>> promise;
|
||||
td::FutureActor<td::unique_ptr<Query>> future;
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "td/utils/format.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/port/SocketFd.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <functional>
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <numeric>
|
||||
@ -25,7 +26,7 @@ std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> empty_par
|
||||
Query::Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_user, bool is_test_dc, td::MutableSlice method,
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&args,
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&headers, td::vector<td::HttpFile> &&files,
|
||||
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address)
|
||||
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address, bool is_internal)
|
||||
: state_(State::Query)
|
||||
, shared_data_(shared_data)
|
||||
, peer_address_(peer_address)
|
||||
@ -36,7 +37,8 @@ Query::Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_u
|
||||
, method_(method)
|
||||
, args_(std::move(args))
|
||||
, headers_(std::move(headers))
|
||||
, files_(std::move(files)) {
|
||||
, files_(std::move(files))
|
||||
, is_internal_(is_internal) {
|
||||
if (method_.empty()) {
|
||||
method_ = arg("method");
|
||||
}
|
||||
@ -67,6 +69,11 @@ td::int64 Query::files_max_size() const {
|
||||
[](td::int64 acc, const td::HttpFile &file) { return td::max(acc, file.size); });
|
||||
}
|
||||
|
||||
void Query::set_stat_actor(td::ActorId<BotStatActor> stat_actor) {
|
||||
stat_actor_ = stat_actor;
|
||||
send_request_stat();
|
||||
}
|
||||
|
||||
void Query::set_ok(td::BufferSlice result) {
|
||||
CHECK(state_ == State::Query);
|
||||
LOG(INFO) << "QUERY: got ok " << td::tag("ptr", this) << td::tag("text", result.as_slice());
|
||||
@ -109,12 +116,26 @@ td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query) {
|
||||
return sb;
|
||||
}
|
||||
|
||||
void Query::send_response_stat() {
|
||||
void Query::send_request_stat() const {
|
||||
if (stat_actor_.empty()) {
|
||||
return;
|
||||
}
|
||||
send_closure(stat_actor_, &BotStatActor::add_event<ServerBotStat::Request>,
|
||||
ServerBotStat::Request{query_size(), file_count(), files_size(), files_max_size()}, td::Time::now());
|
||||
}
|
||||
|
||||
void Query::send_response_stat() const {
|
||||
auto now = td::Time::now();
|
||||
if (now - start_timestamp_ >= 100.0) {
|
||||
LOG(WARNING) << "Answer too old query with code " << http_status_code_ << " and answer size " << answer_.size()
|
||||
<< ": " << *this;
|
||||
}
|
||||
|
||||
if (stat_actor_.empty()) {
|
||||
return;
|
||||
}
|
||||
send_closure(stat_actor_, &BotStatActor::add_event<ServerBotStat::Response>,
|
||||
ServerBotStat::Response{is_ok(), answer().size()}, td::Time::now());
|
||||
ServerBotStat::Response{state_ == State::OK, answer_.size()}, now);
|
||||
}
|
||||
|
||||
} // namespace telegram_bot_api
|
||||
|
@ -76,17 +76,6 @@ class Query : public td::ListNode {
|
||||
return peer_address_;
|
||||
}
|
||||
|
||||
// for stats
|
||||
td::int32 file_count() const {
|
||||
return static_cast<td::int32>(files_.size());
|
||||
}
|
||||
|
||||
td::int64 query_size() const;
|
||||
|
||||
td::int64 files_size() const;
|
||||
|
||||
td::int64 files_max_size() const;
|
||||
|
||||
td::BufferSlice &answer() {
|
||||
return answer_;
|
||||
}
|
||||
@ -109,26 +98,14 @@ class Query : public td::ListNode {
|
||||
return state_ != State::Query;
|
||||
}
|
||||
|
||||
bool is_error() const {
|
||||
return state_ == State::Error;
|
||||
}
|
||||
|
||||
bool is_ok() const {
|
||||
return state_ == State::OK;
|
||||
}
|
||||
|
||||
bool is_internal() const {
|
||||
return is_internal_;
|
||||
}
|
||||
|
||||
void set_internal(bool is_internal) {
|
||||
is_internal_ = is_internal;
|
||||
}
|
||||
|
||||
Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_user, bool is_test_dc, td::MutableSlice method,
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&args,
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&headers, td::vector<td::HttpFile> &&files,
|
||||
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address);
|
||||
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address, bool is_internal);
|
||||
Query(const Query &) = delete;
|
||||
Query &operator=(const Query &) = delete;
|
||||
Query(Query &&) = delete;
|
||||
@ -143,10 +120,7 @@ class Query : public td::ListNode {
|
||||
return start_timestamp_;
|
||||
}
|
||||
|
||||
void set_stat_actor(td::ActorId<BotStatActor> stat_actor) {
|
||||
stat_actor_ = stat_actor;
|
||||
}
|
||||
void send_response_stat();
|
||||
void set_stat_actor(td::ActorId<BotStatActor> stat_actor);
|
||||
|
||||
private:
|
||||
State state_;
|
||||
@ -170,6 +144,21 @@ class Query : public td::ListNode {
|
||||
td::BufferSlice answer_;
|
||||
int http_status_code_ = 0;
|
||||
int retry_after_ = 0;
|
||||
|
||||
// for stats
|
||||
td::int32 file_count() const {
|
||||
return static_cast<td::int32>(files_.size());
|
||||
}
|
||||
|
||||
td::int64 query_size() const;
|
||||
|
||||
td::int64 files_size() const;
|
||||
|
||||
td::int64 files_max_size() const;
|
||||
|
||||
void send_request_stat() const;
|
||||
|
||||
void send_response_stat() const;
|
||||
};
|
||||
|
||||
td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query);
|
||||
|
@ -7,8 +7,8 @@
|
||||
#include "telegram-bot-api/Stats.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "td/utils/port/SocketFd.h"
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Span.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
@ -165,7 +166,7 @@ td::Status WebhookActor::create_connection() {
|
||||
Callback &operator=(Callback &&) = delete;
|
||||
~Callback() {
|
||||
if (!actor_.empty()) {
|
||||
send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, td::Status::Error("Cancelled"), id_);
|
||||
send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, td::Status::Error("Canceled"), id_);
|
||||
}
|
||||
}
|
||||
void on_connected() override {
|
||||
@ -585,10 +586,10 @@ void WebhookActor::handle(td::unique_ptr<td::HttpQuery> response) {
|
||||
if (!method.empty() && method != "deletewebhook" && method != "setwebhook" && method != "close" &&
|
||||
method != "logout" && !td::begins_with(method, "get")) {
|
||||
VLOG(webhook) << "Receive request " << method << " in response to webhook";
|
||||
auto query =
|
||||
std::make_unique<Query>(std::move(response->container_), td::MutableSlice(), false, false, td::MutableSlice(),
|
||||
std::move(response->args_), std::move(response->headers_),
|
||||
std::move(response->files_), parameters_->shared_data_, response->peer_address_);
|
||||
auto query = std::make_unique<Query>(std::move(response->container_), td::MutableSlice(), false, false,
|
||||
td::MutableSlice(), std::move(response->args_),
|
||||
std::move(response->headers_), std::move(response->files_),
|
||||
parameters_->shared_data_, response->peer_address_, false);
|
||||
auto promised_query =
|
||||
PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor<td::unique_ptr<Query>>()));
|
||||
send_closure(callback_, &Callback::send, std::move(promised_query));
|
||||
|
@ -24,8 +24,8 @@
|
||||
#include "td/actor/ConcurrentScheduler.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/utils/algorithm.h"
|
||||
#include "td/utils/buffer.h"
|
||||
#include "td/utils/CombinedLog.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/crypto.h"
|
||||
#include "td/utils/ExitGuard.h"
|
||||
@ -36,15 +36,19 @@
|
||||
#include "td/utils/MemoryLog.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/OptionParser.h"
|
||||
#include "td/utils/PathView.h"
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
#include "td/utils/port/path.h"
|
||||
#include "td/utils/port/rlimit.h"
|
||||
#include "td/utils/port/signals.h"
|
||||
#include "td/utils/port/stacktrace.h"
|
||||
#include "td/utils/port/Stat.h"
|
||||
#include "td/utils/port/user.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Status.h"
|
||||
#include "td/utils/Time.h"
|
||||
#include "td/utils/TsLog.h"
|
||||
|
||||
#include "memprof/memprof.h"
|
||||
|
||||
@ -56,10 +60,10 @@
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
static std::atomic_flag need_rotate_log;
|
||||
static std::atomic_flag need_reopen_log;
|
||||
|
||||
static void rotate_log_signal_handler(int sig) {
|
||||
need_rotate_log.clear();
|
||||
static void after_log_rotation_signal_handler(int sig) {
|
||||
need_reopen_log.clear();
|
||||
}
|
||||
|
||||
static std::atomic_flag need_quit;
|
||||
@ -106,11 +110,65 @@ static void sigsegv_signal_handler(int signum, void *addr) {
|
||||
fail_signal_handler(signum);
|
||||
}
|
||||
|
||||
static void dump_statistics(const std::shared_ptr<SharedData> &shared_data,
|
||||
const std::shared_ptr<td::NetQueryStats> &net_query_stats) {
|
||||
if (is_memprof_on()) {
|
||||
LOG(WARNING) << "Memory dump:";
|
||||
td::vector<AllocInfo> v;
|
||||
dump_alloc([&](const AllocInfo &info) { v.push_back(info); });
|
||||
std::sort(v.begin(), v.end(), [](const AllocInfo &a, const AllocInfo &b) { return a.size > b.size; });
|
||||
size_t total_size = 0;
|
||||
size_t other_size = 0;
|
||||
int count = 0;
|
||||
for (auto &info : v) {
|
||||
if (count++ < 50) {
|
||||
LOG(WARNING) << td::format::as_size(info.size) << td::format::as_array(info.backtrace);
|
||||
} else {
|
||||
other_size += info.size;
|
||||
}
|
||||
total_size += info.size;
|
||||
}
|
||||
LOG(WARNING) << td::tag("other", td::format::as_size(other_size));
|
||||
LOG(WARNING) << td::tag("total size", td::format::as_size(total_size));
|
||||
LOG(WARNING) << td::tag("total traces", get_ht_size());
|
||||
LOG(WARNING) << td::tag("fast_backtrace_success_rate", get_fast_backtrace_success_rate());
|
||||
}
|
||||
auto r_mem_stat = td::mem_stat();
|
||||
if (r_mem_stat.is_ok()) {
|
||||
auto mem_stat = r_mem_stat.move_as_ok();
|
||||
LOG(WARNING) << td::tag("rss", td::format::as_size(mem_stat.resident_size_));
|
||||
LOG(WARNING) << td::tag("vm", td::format::as_size(mem_stat.virtual_size_));
|
||||
LOG(WARNING) << td::tag("rss_peak", td::format::as_size(mem_stat.resident_size_peak_));
|
||||
LOG(WARNING) << td::tag("vm_peak", td::format::as_size(mem_stat.virtual_size_peak_));
|
||||
}
|
||||
LOG(WARNING) << td::tag("buffer_mem", td::format::as_size(td::BufferAllocator::get_buffer_mem()));
|
||||
LOG(WARNING) << td::tag("buffer_slice_size", td::format::as_size(td::BufferAllocator::get_buffer_slice_size()));
|
||||
|
||||
auto query_count = shared_data->query_count_.load();
|
||||
LOG(WARNING) << td::tag("pending queries", query_count);
|
||||
|
||||
td::uint64 i = 0;
|
||||
bool was_gap = false;
|
||||
for (auto end = &shared_data->query_list_, cur = end->prev; cur != end; cur = cur->prev, i++) {
|
||||
if (i < 20 || i > query_count - 20 || i % (query_count / 50 + 1) == 0) {
|
||||
if (was_gap) {
|
||||
LOG(WARNING) << "...";
|
||||
was_gap = false;
|
||||
}
|
||||
LOG(WARNING) << static_cast<const Query &>(*cur);
|
||||
} else {
|
||||
was_gap = true;
|
||||
}
|
||||
}
|
||||
|
||||
td::dump_pending_network_queries(*net_query_stats);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL));
|
||||
td::ExitGuard exit_guard;
|
||||
|
||||
need_rotate_log.test_and_set();
|
||||
need_reopen_log.test_and_set();
|
||||
need_quit.test_and_set();
|
||||
need_change_verbosity_level.test_and_set();
|
||||
need_dump_log.test_and_set();
|
||||
@ -118,7 +176,7 @@ int main(int argc, char *argv[]) {
|
||||
td::Stacktrace::init();
|
||||
|
||||
td::setup_signals_alt_stack().ensure();
|
||||
td::set_signal_handler(td::SignalType::User, rotate_log_signal_handler).ensure();
|
||||
td::set_signal_handler(td::SignalType::User, after_log_rotation_signal_handler).ensure();
|
||||
td::ignore_signal(td::SignalType::HangUp).ensure();
|
||||
td::ignore_signal(td::SignalType::Pipe).ensure();
|
||||
td::set_signal_handler(td::SignalType::Quit, quit_signal_handler).ensure();
|
||||
@ -134,7 +192,7 @@ int main(int argc, char *argv[]) {
|
||||
auto start_time = td::Time::now();
|
||||
auto shared_data = std::make_shared<SharedData>();
|
||||
auto parameters = std::make_unique<ClientParameters>();
|
||||
parameters->version_ = "5.2";
|
||||
parameters->version_ = "5.3";
|
||||
parameters->shared_data_ = shared_data;
|
||||
parameters->start_time_ = start_time;
|
||||
auto net_query_stats = td::create_net_query_stats();
|
||||
@ -142,6 +200,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
td::OptionParser options;
|
||||
bool need_print_usage = false;
|
||||
bool need_print_version = false;
|
||||
int http_port = 8081;
|
||||
int http_stat_port = 0;
|
||||
td::string http_ip_address = "0.0.0.0";
|
||||
@ -150,7 +209,7 @@ int main(int argc, char *argv[]) {
|
||||
int default_verbosity_level = 0;
|
||||
int memory_verbosity_level = VERBOSITY_NAME(INFO);
|
||||
td::int64 log_max_file_size = 2000000000;
|
||||
td::string working_directory;
|
||||
td::string working_directory = PSTRING() << "." << TD_DIR_SLASH;
|
||||
td::string temporary_directory;
|
||||
td::string username;
|
||||
td::string groupname;
|
||||
@ -173,7 +232,8 @@ int main(int argc, char *argv[]) {
|
||||
options.set_usage(td::Slice(argv[0]), "--api-id=<arg> --api-hash=<arg> [--local] [OPTION]...");
|
||||
options.set_description("Telegram Bot API server");
|
||||
options.add_option('h', "help", "display this help text and exit", [&] { need_print_usage = true; });
|
||||
options.add_option('\0', "local", "allow the Bot API server to serve local requests and disables the file limits",
|
||||
options.add_option('\0', "version", "display version number and exit", [&] { need_print_version = true; });
|
||||
options.add_option('\0', "local", "allow the Bot API server to serve local requests",
|
||||
[&] { parameters->local_mode_ = true; });
|
||||
options.add_option('\0', "no-file-limit", "disable the file limits",
|
||||
[&] { parameters->no_file_limit_ = true; });
|
||||
@ -288,77 +348,17 @@ int main(int argc, char *argv[]) {
|
||||
LOG(PLAIN) << options;
|
||||
return 0;
|
||||
}
|
||||
if (need_print_version) {
|
||||
LOG(PLAIN) << "Bot API " << parameters->version_;
|
||||
return 0;
|
||||
}
|
||||
if (r_non_options.is_error()) {
|
||||
LOG(PLAIN) << argv[0] << ": " << r_non_options.error();
|
||||
LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
|
||||
LOG(PLAIN) << options;
|
||||
return 1;
|
||||
}
|
||||
|
||||
class CombineLog : public td::LogInterface {
|
||||
public:
|
||||
void append(td::CSlice slice, int log_level) override {
|
||||
if (first_ && log_level <= first_verbosity_level_) {
|
||||
if (log_level == VERBOSITY_NAME(FATAL) && second_ && VERBOSITY_NAME(FATAL) <= second_verbosity_level_) {
|
||||
second_->append(slice, VERBOSITY_NAME(ERROR));
|
||||
}
|
||||
first_->append(slice, log_level);
|
||||
}
|
||||
if (second_ && log_level <= second_verbosity_level_) {
|
||||
second_->append(slice, log_level);
|
||||
}
|
||||
}
|
||||
|
||||
void set_first(LogInterface *first) {
|
||||
first_ = first;
|
||||
}
|
||||
|
||||
void set_second(LogInterface *second) {
|
||||
second_ = second;
|
||||
}
|
||||
|
||||
void set_first_verbosity_level(int verbosity_level) {
|
||||
first_verbosity_level_ = verbosity_level;
|
||||
}
|
||||
|
||||
void set_second_verbosity_level(int verbosity_level) {
|
||||
second_verbosity_level_ = verbosity_level;
|
||||
}
|
||||
|
||||
int get_first_verbosity_level() const {
|
||||
return first_verbosity_level_;
|
||||
}
|
||||
|
||||
int get_second_verbosity_level() const {
|
||||
return second_verbosity_level_;
|
||||
}
|
||||
|
||||
void rotate() override {
|
||||
if (first_) {
|
||||
first_->rotate();
|
||||
}
|
||||
if (second_) {
|
||||
second_->rotate();
|
||||
}
|
||||
}
|
||||
|
||||
td::vector<td::string> get_file_paths() override {
|
||||
td::vector<td::string> result;
|
||||
if (first_) {
|
||||
td::append(result, first_->get_file_paths());
|
||||
}
|
||||
if (second_) {
|
||||
td::append(result, second_->get_file_paths());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
LogInterface *first_ = nullptr;
|
||||
int first_verbosity_level_ = VERBOSITY_NAME(FATAL);
|
||||
LogInterface *second_ = nullptr;
|
||||
int second_verbosity_level_ = VERBOSITY_NAME(FATAL);
|
||||
};
|
||||
CombineLog log;
|
||||
td::CombinedLog log;
|
||||
log.set_first(td::default_log_interface);
|
||||
log.set_second(&memory_log);
|
||||
td::log_interface = &log;
|
||||
@ -376,15 +376,65 @@ int main(int argc, char *argv[]) {
|
||||
TRY_STATUS_PREFIX(td::change_user(username, groupname), "Can't change effective user: ");
|
||||
}
|
||||
|
||||
if (!working_directory.empty()) {
|
||||
TRY_STATUS_PREFIX(td::chdir(working_directory), "Can't set working directory: ");
|
||||
{
|
||||
TRY_RESULT_PREFIX_ASSIGN(working_directory, td::realpath(working_directory, true),
|
||||
"Invalid working directory specified: ");
|
||||
if (working_directory.empty()) {
|
||||
return td::Status::Error("Working directory can't be empty");
|
||||
}
|
||||
if (working_directory.back() != TD_DIR_SLASH) {
|
||||
working_directory += TD_DIR_SLASH;
|
||||
}
|
||||
|
||||
TRY_STATUS_PREFIX(td::mkpath(working_directory, 0750), "Failed to create working directory: ");
|
||||
|
||||
auto r_temp_file = td::mkstemp(working_directory);
|
||||
if (r_temp_file.is_error()) {
|
||||
return td::Status::Error(PSLICE() << "Can't create files in the directory \"" << working_directory
|
||||
<< "\". Use --dir option to specify a writable working directory");
|
||||
}
|
||||
r_temp_file.ok_ref().first.close();
|
||||
td::unlink(r_temp_file.ok().second).ensure();
|
||||
|
||||
auto r_temp_dir = td::mkdtemp(working_directory, "1:a");
|
||||
if (r_temp_dir.is_error()) {
|
||||
parameters->allow_colon_in_filenames_ = false;
|
||||
r_temp_dir = td::mkdtemp(working_directory, "1~a");
|
||||
if (r_temp_dir.is_error()) {
|
||||
return td::Status::Error(PSLICE() << "Can't create directories in the directory \"" << working_directory
|
||||
<< "\". Use --dir option to specify a writable working directory");
|
||||
}
|
||||
}
|
||||
td::rmdir(r_temp_dir.ok()).ensure();
|
||||
}
|
||||
|
||||
if (!temporary_directory.empty()) {
|
||||
if (td::PathView(temporary_directory).is_relative()) {
|
||||
temporary_directory = working_directory + temporary_directory;
|
||||
}
|
||||
TRY_STATUS_PREFIX(td::set_temporary_dir(temporary_directory), "Can't set temporary directory: ");
|
||||
}
|
||||
|
||||
{ // check temporary directory
|
||||
auto temp_dir = td::get_temporary_dir();
|
||||
if (temp_dir.empty()) {
|
||||
return td::Status::Error("Can't find directory for temporary files. Use --temp-dir option to specify it");
|
||||
}
|
||||
|
||||
auto r_temp_file = td::mkstemp(temp_dir);
|
||||
if (r_temp_file.is_error()) {
|
||||
return td::Status::Error(PSLICE()
|
||||
<< "Can't create files in the directory \"" << temp_dir
|
||||
<< "\". Use --temp-dir option to specify another directory for temporary files");
|
||||
}
|
||||
r_temp_file.ok_ref().first.close();
|
||||
td::unlink(r_temp_file.ok().second).ensure();
|
||||
}
|
||||
|
||||
if (!log_file_path.empty()) {
|
||||
if (td::PathView(log_file_path).is_relative()) {
|
||||
log_file_path = working_directory + log_file_path;
|
||||
}
|
||||
TRY_STATUS_PREFIX(file_log.init(log_file_path, log_max_file_size), "Can't open log file: ");
|
||||
log.set_first(&ts_log);
|
||||
}
|
||||
@ -392,11 +442,13 @@ int main(int argc, char *argv[]) {
|
||||
return td::Status::OK();
|
||||
}();
|
||||
if (init_status.is_error()) {
|
||||
LOG(PLAIN) << init_status.error();
|
||||
LOG(PLAIN) << init_status.error().message();
|
||||
LOG(PLAIN) << options;
|
||||
return 1;
|
||||
}
|
||||
|
||||
parameters->working_directory_ = std::move(working_directory);
|
||||
|
||||
if (parameters->default_max_webhook_connections_ <= 0) {
|
||||
parameters->default_max_webhook_connections_ = parameters->local_mode_ ? 100 : 40;
|
||||
}
|
||||
@ -458,8 +510,8 @@ int main(int argc, char *argv[]) {
|
||||
while (true) {
|
||||
sched.run_main(next_cron_time - td::Time::now());
|
||||
|
||||
if (!need_rotate_log.test_and_set()) {
|
||||
td::log_interface->rotate();
|
||||
if (!need_reopen_log.test_and_set()) {
|
||||
td::log_interface->after_rotation();
|
||||
}
|
||||
|
||||
if (!need_quit.test_and_set()) {
|
||||
@ -468,7 +520,8 @@ int main(int argc, char *argv[]) {
|
||||
std::_Exit(0);
|
||||
}
|
||||
|
||||
LOG(WARNING) << "Stopping engine by a signal";
|
||||
LOG(WARNING) << "Stopping engine with uptime " << (td::Time::now() - start_time) << " seconds by a signal";
|
||||
dump_statistics(shared_data, net_query_stats);
|
||||
close_flag = true;
|
||||
auto guard = sched.get_main_guard();
|
||||
send_closure(client_manager, &ClientManager::close, td::PromiseCreator::lambda([&can_quit](td::Unit) {
|
||||
@ -497,6 +550,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if (!need_dump_log.test_and_set()) {
|
||||
print_log();
|
||||
dump_statistics(shared_data, net_query_stats);
|
||||
}
|
||||
|
||||
double now = td::Time::now();
|
||||
@ -525,48 +579,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if (now > last_dump_time + 300.0) {
|
||||
last_dump_time = now;
|
||||
if (is_memprof_on()) {
|
||||
LOG(WARNING) << "Memory dump:";
|
||||
td::vector<AllocInfo> v;
|
||||
dump_alloc([&](const AllocInfo &info) { v.push_back(info); });
|
||||
std::sort(v.begin(), v.end(), [](const AllocInfo &a, const AllocInfo &b) { return a.size > b.size; });
|
||||
size_t total_size = 0;
|
||||
size_t other_size = 0;
|
||||
int count = 0;
|
||||
for (auto &info : v) {
|
||||
if (count++ < 50) {
|
||||
LOG(WARNING) << td::format::as_size(info.size) << td::format::as_array(info.backtrace);
|
||||
} else {
|
||||
other_size += info.size;
|
||||
}
|
||||
total_size += info.size;
|
||||
}
|
||||
LOG(WARNING) << td::tag("other", td::format::as_size(other_size));
|
||||
LOG(WARNING) << td::tag("total", td::format::as_size(total_size));
|
||||
LOG(WARNING) << td::tag("total traces", get_ht_size());
|
||||
LOG(WARNING) << td::tag("fast_backtrace_success_rate", get_fast_backtrace_success_rate());
|
||||
}
|
||||
LOG(WARNING) << td::tag("buffer_mem", td::format::as_size(td::BufferAllocator::get_buffer_mem()));
|
||||
LOG(WARNING) << td::tag("buffer_slice_size", td::format::as_size(td::BufferAllocator::get_buffer_slice_size()));
|
||||
|
||||
auto query_count = shared_data->query_count_.load();
|
||||
LOG(WARNING) << td::tag("pending queries", query_count);
|
||||
|
||||
td::uint64 i = 0;
|
||||
bool was_gap = false;
|
||||
for (auto end = &shared_data->query_list_, cur = end->prev; cur != end; cur = cur->prev, i++) {
|
||||
if (i < 20 || i > query_count - 20 || i % (query_count / 50 + 1) == 0) {
|
||||
if (was_gap) {
|
||||
LOG(WARNING) << "...";
|
||||
was_gap = false;
|
||||
}
|
||||
LOG(WARNING) << static_cast<Query &>(*cur);
|
||||
} else {
|
||||
was_gap = true;
|
||||
}
|
||||
}
|
||||
|
||||
td::dump_pending_network_queries(*net_query_stats);
|
||||
dump_statistics(shared_data, net_query_stats);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user