diff --git a/CMakeLists.txt b/CMakeLists.txt index 76ef36a..45d5df6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TelegramBotApi VERSION 6.1 LANGUAGES CXX) +project(TelegramBotApi VERSION 6.2 LANGUAGES CXX) if (POLICY CMP0069) option(TELEGRAM_BOT_API_ENABLE_LTO "Use \"ON\" to enable Link Time Optimization.") diff --git a/build.html b/build.html index d24505d..6bb77c8 100644 --- a/build.html +++ b/build.html @@ -181,7 +181,7 @@
-

Choose an operating system, on which you want to use the Telegram Bot API server:

+

Choose an operating system on which you want to use the Telegram Bot API server:

@@ -207,6 +207,7 @@ +

@@ -493,6 +494,8 @@ function onOptionsChanged() { return '-6.0'; case 'Ubuntu 20': return '-10'; + case 'Ubuntu 22': + return '-14'; default: return ''; // use default version } @@ -534,6 +537,7 @@ function onOptionsChanged() { case 'Ubuntu 16': case 'Ubuntu 18': case 'Ubuntu 20': + case 'Ubuntu 22': if (linux_distro.includes('Debian') && !use_root) { commands.push('su -'); } @@ -550,7 +554,7 @@ function onOptionsChanged() { } if (use_clang) { packages += ' clang' + getClangVersionSuffix() + ' libc++-dev'; - if (linux_distro === 'Debian 10+' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20') { + if (linux_distro === 'Debian 10+' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20' || linux_distro === 'Ubuntu 22') { packages += ' libc++abi-dev'; } } else { diff --git a/td b/td index b393215..d9cfcf8 160000 --- a/td +++ b/td @@ -1 +1 @@ -Subproject commit b393215d6671863b6baf2a589d343cff9474f6ba +Subproject commit d9cfcf88fe4ad06dae1716ce8f66bbeb7f9491d9 diff --git a/telegram-bot-api/Client.cpp b/telegram-bot-api/Client.cpp index 85204f6..1effe2a 100644 --- a/telegram-bot-api/Client.cpp +++ b/telegram-bot-api/Client.cpp @@ -10,7 +10,7 @@ #include "td/db/TQueue.h" -#include "td/actor/PromiseFuture.h" +#include "td/actor/MultiPromise.h" #include "td/actor/SleepActor.h" #include "td/utils/algorithm.h" @@ -22,7 +22,6 @@ #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/port/path.h" -#include "td/utils/port/Stat.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Span.h" @@ -207,6 +206,12 @@ Client::Client(td::ActorShared<> parent, const td::string &bot_token, bool is_us CHECK(is_inited); } +Client::~Client() { + td::Scheduler::instance()->destroy_on_scheduler(get_file_gc_scheduler_id(), messages_, users_, groups_, supergroups_, + chats_, reply_message_ids_, yet_unsent_reply_message_ids_, + sticker_set_names_); +} + bool Client::init_methods() { methods_.emplace("getme", &Client::process_get_me_query); methods_.emplace("getmycommands", &Client::process_get_my_commands_query); @@ -285,6 +290,7 @@ bool Client::init_methods() { methods_.emplace("approvechatjoinrequest", &Client::process_approve_chat_join_request_query); methods_.emplace("declinechatjoinrequest", &Client::process_decline_chat_join_request_query); methods_.emplace("getstickerset", &Client::process_get_sticker_set_query); + methods_.emplace("getcustomemojistickers", &Client::process_get_custom_emoji_stickers_query); methods_.emplace("uploadstickerfile", &Client::process_upload_sticker_file_query); methods_.emplace("createnewstickerset", &Client::process_create_new_sticker_set_query); methods_.emplace("addstickertoset", &Client::process_add_sticker_to_set_query); @@ -526,6 +532,12 @@ class Client::JsonEntity final : public Jsonable { object("user", JsonUser(entity->user_id_, client_)); break; } + case td_api::textEntityTypeCustomEmoji::ID: { + auto entity = static_cast(entity_->type_.get()); + object("type", "custom_emoji"); + object("custom_emoji_id", td::to_string(entity->custom_emoji_id_)); + break; + } default: UNREACHABLE(); } @@ -738,6 +750,9 @@ class Client::JsonChat final : public Jsonable { if (user_info->has_private_forwards) { object("has_private_forwards", td::JsonTrue()); } + if (user_info->has_restricted_voice_and_video_messages) { + object("has_restricted_voice_and_video_messages", td::JsonTrue()); + } } photo = user_info->photo.get(); break; @@ -1117,14 +1132,20 @@ class Client::JsonSticker final : public Jsonable { if (!set_name.empty()) { object("set_name", set_name); } - auto type = sticker_->type_->get_id(); - object("is_animated", td::JsonBool(type == td_api::stickerTypeAnimated::ID)); - object("is_video", td::JsonBool(type == td_api::stickerTypeVideo::ID)); - if (type == td_api::stickerTypeMask::ID) { - const auto &mask_position = static_cast(sticker_->type_.get())->mask_position_; - if (mask_position != nullptr) { - object("mask_position", JsonMaskPosition(mask_position.get())); - } + + auto format = sticker_->format_->get_id(); + object("is_animated", td::JsonBool(format == td_api::stickerFormatTgs::ID)); + object("is_video", td::JsonBool(format == td_api::stickerFormatWebm::ID)); + + object("type", Client::get_sticker_type(sticker_->type_)); + + if (sticker_->custom_emoji_id_ != 0) { + object("custom_emoji_id", td::to_string(sticker_->custom_emoji_id_)); + } + + const auto &mask_position = sticker_->mask_position_; + if (mask_position != nullptr) { + object("mask_position", JsonMaskPosition(mask_position.get())); } if (sticker_->premium_animation_ != nullptr) { object("premium_animation", JsonFile(sticker_->premium_animation_.get(), client_, false)); @@ -2136,6 +2157,8 @@ void Client::JsonMessage::store(JsonValueScope *scope) const { object("web_app_data", JsonWebAppData(content)); break; } + case td_api::messageGiftedPremium::ID: + break; default: UNREACHABLE(); } @@ -2751,10 +2774,15 @@ class Client::JsonStickerSet final : public Jsonable { if (sticker_set_->thumbnail_ != nullptr) { client_->json_store_thumbnail(object, sticker_set_->thumbnail_.get()); } - auto type = sticker_set_->sticker_type_->get_id(); - object("is_animated", td::JsonBool(type == td_api::stickerTypeAnimated::ID)); - object("is_video", td::JsonBool(type == td_api::stickerTypeVideo::ID)); - object("contains_masks", td::JsonBool(type == td_api::stickerTypeMask::ID)); + + auto format = sticker_set_->sticker_format_->get_id(); + object("is_animated", td::JsonBool(format == td_api::stickerFormatTgs::ID)); + object("is_video", td::JsonBool(format == td_api::stickerFormatWebm::ID)); + + auto type = Client::get_sticker_type(sticker_set_->sticker_type_); + object("sticker_type", type); + object("contains_masks", td::JsonBool(type == "mask")); + object("stickers", JsonStickers(sticker_set_->stickers_, client_)); } @@ -4075,6 +4103,68 @@ class Client::TdOnReturnStickerSetCallback final : public TdQueryCallback { PromisedQueryPtr query_; }; +class Client::TdOnGetStickerSetPromiseCallback final : public TdQueryCallback { + public: + TdOnGetStickerSetPromiseCallback(Client *client, td::Promise &&promise) + : client_(client), promise_(std::move(promise)) { + } + + void on_result(object_ptr result) final { + if (result->get_id() == td_api::error::ID) { + auto error = move_object_as(result); + return promise_.set_error(Status::Error(error->code_, error->message_)); + } + + CHECK(result->get_id() == td_api::stickerSet::ID); + auto sticker_set = move_object_as(result); + client_->on_get_sticker_set_name(sticker_set->id_, sticker_set->name_); + promise_.set_value(td::Unit()); + } + + private: + Client *client_; + td::Promise promise_; +}; + +class Client::TdOnGetStickersCallback final : public TdQueryCallback { + public: + TdOnGetStickersCallback(Client *client, PromisedQueryPtr query) : client_(client), query_(std::move(query)) { + } + + void on_result(object_ptr result) final { + if (result->get_id() == td_api::error::ID) { + return fail_query_with_error(std::move(query_), move_object_as(result)); + } + + CHECK(result->get_id() == td_api::stickers::ID); + auto stickers = move_object_as(result); + td::FlatHashSet sticker_set_ids; + for (const auto &sticker : stickers->stickers_) { + if (sticker->set_id_ != 0 && client_->get_sticker_set_name(sticker->set_id_).empty()) { + sticker_set_ids.insert(sticker->set_id_); + } + } + + td::MultiPromiseActorSafe mpas("GetStickerSetsMultiPromiseActor"); + mpas.add_promise(td::PromiseCreator::lambda([actor_id = client_->actor_id(client_), stickers = std::move(stickers), + query = std::move(query_)](td::Unit) mutable { + send_closure(actor_id, &Client::return_stickers, std::move(stickers), std::move(query)); + })); + mpas.set_ignore_errors(true); + + auto lock = mpas.get_promise(); + for (auto sticker_set_id : sticker_set_ids) { + client_->send_request(make_object(sticker_set_id), + td::make_unique(client_, mpas.get_promise())); + } + lock.set_value(td::Unit()); + } + + private: + Client *client_; + PromisedQueryPtr query_; +}; + class Client::TdOnSendCustomRequestCallback final : public TdQueryCallback { public: explicit TdOnSendCustomRequestCallback(PromisedQueryPtr query) : query_(std::move(query)) { @@ -5308,6 +5398,8 @@ void Client::on_update(object_ptr result) { set_user_bio(user_id, std::move(full_info->bio_->text_)); } set_user_has_private_forwards(user_id, full_info->has_private_forwards_); + set_user_has_restricted_voice_and_video_messages(user_id, + full_info->has_restricted_voice_and_video_note_messages_); break; } case td_api::updateUserStatus::ID: { @@ -5532,30 +5624,13 @@ void Client::on_closed() { parameters_->shared_data_->webhook_db_->erase(bot_token_with_dc_); parameters_->shared_data_->user_db_->erase(bot_token_with_dc_); - class RmWorker final : public td::Actor { - public: - RmWorker(td::string dir, td::ActorId parent) : dir_(std::move(dir)), parent_(std::move(parent)) { - } - - private: - td::string dir_; - td::ActorId parent_; - - void start_up() final { - CHECK(dir_.size() >= 24); - CHECK(dir_.back() == TD_DIR_SLASH); - td::rmrf(dir_).ignore(); - stop(); - } - void tear_down() final { - send_closure(parent_, &Client::finish_closing); - } - }; - // NB: the same scheduler as for database in Td - auto current_scheduler_id = td::Scheduler::instance()->sched_id(); - auto scheduler_count = td::Scheduler::instance()->sched_count(); - auto scheduler_id = td::min(current_scheduler_id + 1, scheduler_count - 1); - td::create_actor_on_scheduler("RmWorker", scheduler_id, dir_, actor_id(this)).release(); + td::Scheduler::instance()->run_on_scheduler(get_file_gc_scheduler_id(), + [actor_id = actor_id(this), dir = dir_](td::Unit) { + CHECK(dir.size() >= 24); + CHECK(dir.back() == TD_DIR_SLASH); + td::rmrf(dir).ignore(); + send_closure(actor_id, &Client::finish_closing); + }); return; } @@ -5575,6 +5650,20 @@ void Client::timeout_expired() { stop(); } +td::int32 Client::get_database_scheduler_id() { + // the same scheduler as for database in Td + auto current_scheduler_id = td::Scheduler::instance()->sched_id(); + auto scheduler_count = td::Scheduler::instance()->sched_count(); + return td::min(current_scheduler_id + 1, scheduler_count - 1); +} + +td::int32 Client::get_file_gc_scheduler_id() { + // the same scheduler as for file GC in Td + auto current_scheduler_id = td::Scheduler::instance()->sched_id(); + auto scheduler_count = td::Scheduler::instance()->sched_count(); + return td::min(current_scheduler_id + 2, scheduler_count - 1); +} + void Client::clear_tqueue() { CHECK(webhook_id_.empty()); auto &tqueue = parameters_->shared_data_->tqueue_; @@ -6818,27 +6907,23 @@ td::Result> Client::get_mask_position(c return r_mask_position.move_as_ok(); } -td::Result>> Client::get_input_stickers(const Query *query, - bool is_masks) const { +td::Result>> Client::get_input_stickers(const Query *query) const { auto emojis = query->arg("emojis"); auto sticker = get_input_file(query, "png_sticker"); - object_ptr sticker_type; + object_ptr sticker_format; + object_ptr mask_position; if (sticker != nullptr) { - if (is_masks) { - TRY_RESULT(mask_position, get_mask_position(query, "mask_position")); - sticker_type = make_object(std::move(mask_position)); - } else { - sticker_type = make_object(); - } + sticker_format = make_object(); + TRY_RESULT_ASSIGN(mask_position, get_mask_position(query, "mask_position")); } else { sticker = get_input_file(query, "tgs_sticker", true); if (sticker != nullptr) { - sticker_type = make_object(); + sticker_format = make_object(); } else { sticker = get_input_file(query, "webm_sticker", true); if (sticker != nullptr) { - sticker_type = make_object(); + sticker_format = make_object(); } else { if (!query->arg("tgs_sticker").empty()) { return Status::Error(400, "Bad Request: animated sticker must be uploaded as an InputFile"); @@ -6852,7 +6937,8 @@ td::Result>> Client::get_inp } td::vector> stickers; - stickers.push_back(make_object(std::move(sticker), emojis.str(), std::move(sticker_type))); + stickers.push_back(make_object(std::move(sticker), emojis.str(), std::move(sticker_format), + std::move(mask_position))); return std::move(stickers); } @@ -7028,6 +7114,10 @@ td::Result> Client::get_text_entity_t TRY_RESULT(user_id, get_json_object_long_field(user.get_object(), "id", false)); return make_object(user_id); } + if (type == "custom_emoji") { + TRY_RESULT(custom_emoji_id, get_json_object_long_field(object, "custom_emoji_id", false)); + return make_object(custom_emoji_id); + } if (type == "mention" || type == "hashtag" || type == "cashtag" || type == "bot_command" || type == "url" || type == "email" || type == "phone_number" || type == "bank_card_number") { return nullptr; @@ -9111,18 +9201,48 @@ td::Status Client::process_get_sticker_set_query(PromisedQueryPtr &query) { return Status::OK(); } +td::Status Client::process_get_custom_emoji_stickers_query(PromisedQueryPtr &query) { + TRY_RESULT(custom_emoji_ids_json, get_required_string_arg(query.get(), "custom_emoji_ids")); + + LOG(INFO) << "Parsing JSON object: " << custom_emoji_ids_json; + auto r_value = json_decode(custom_emoji_ids_json); + if (r_value.is_error()) { + return Status::Error(400, "Can't parse custom emoji identifiers JSON object"); + } + auto value = r_value.move_as_ok(); + if (value.type() != JsonValue::Type::Array) { + return Status::Error(400, "Expected an Array of custom emoji identifiers"); + } + + td::vector custom_emoji_ids; + for (auto &custom_emoji_id : value.get_array()) { + if (custom_emoji_id.type() != JsonValue::Type::String) { + return Status::Error(400, "Custom emoji identifier must be of type String"); + } + auto parsed_id = td::to_integer_safe(custom_emoji_id.get_string()); + if (parsed_id.is_error()) { + return Status::Error(400, "Invalid custom emoji identifier specified"); + } + custom_emoji_ids.push_back(parsed_id.ok()); + } + + send_request(make_object(std::move(custom_emoji_ids)), + td::make_unique(this, std::move(query))); + return Status::OK(); +} + td::Status Client::process_upload_sticker_file_query(PromisedQueryPtr &query) { CHECK_IS_BOT(); TRY_RESULT(user_id, get_user_id(query.get())); auto png_sticker = get_input_file(query.get(), "png_sticker"); - check_user(user_id, std::move(query), - [this, user_id, png_sticker = std::move(png_sticker)](PromisedQueryPtr query) mutable { - send_request(make_object( - user_id, make_object(std::move(png_sticker), "", - make_object())), - td::make_unique(this, std::move(query))); - }); + check_user( + user_id, std::move(query), [this, user_id, png_sticker = std::move(png_sticker)](PromisedQueryPtr query) mutable { + send_request(make_object( + user_id, make_object(std::move(png_sticker), "", + make_object(), nullptr)), + td::make_unique(this, std::move(query))); + }); return Status::OK(); } @@ -9131,14 +9251,20 @@ td::Status Client::process_create_new_sticker_set_query(PromisedQueryPtr &query) TRY_RESULT(user_id, get_user_id(query.get())); auto name = query->arg("name"); auto title = query->arg("title"); - auto is_masks = to_bool(query->arg("contains_masks")); - TRY_RESULT(stickers, get_input_stickers(query.get(), is_masks)); + TRY_RESULT(stickers, get_input_stickers(query.get())); + + TRY_RESULT(sticker_type, get_sticker_type(query->arg("sticker_type"))); + if (to_bool(query->arg("contains_masks"))) { + sticker_type = make_object(); + } check_user(user_id, std::move(query), - [this, user_id, title, name, stickers = std::move(stickers)](PromisedQueryPtr query) mutable { - send_request(make_object(user_id, title.str(), name.str(), - std::move(stickers), PSTRING() << "bot" << my_id_), - td::make_unique(this, false, std::move(query))); + [this, user_id, title, name, sticker_type = std::move(sticker_type), + stickers = std::move(stickers)](PromisedQueryPtr query) mutable { + send_request( + make_object(user_id, title.str(), name.str(), std::move(sticker_type), + std::move(stickers), PSTRING() << "bot" << my_id_), + td::make_unique(this, false, std::move(query))); }); return Status::OK(); } @@ -9147,7 +9273,7 @@ td::Status Client::process_add_sticker_to_set_query(PromisedQueryPtr &query) { CHECK_IS_BOT(); TRY_RESULT(user_id, get_user_id(query.get())); auto name = query->arg("name"); - TRY_RESULT(stickers, get_input_stickers(query.get(), true)); + TRY_RESULT(stickers, get_input_stickers(query.get())); CHECK(!stickers.empty()); check_user(user_id, std::move(query), @@ -9818,16 +9944,6 @@ void Client::do_get_file(object_ptr 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_, 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_) { - return on_file_download(file_id, std::move(file)); - } - } - } - send_request(make_object(file_id, 1, 0, 0, false), td::make_unique(this, file_id)); } @@ -9854,6 +9970,10 @@ void Client::on_file_download(int32 file_id, td::Result } } +void Client::return_stickers(object_ptr stickers, PromisedQueryPtr query) { + answer_query(JsonStickers(stickers->stickers_, this), std::move(query)); +} + void Client::webhook_verified(td::string cached_ip_address) { if (get_link_token() != webhook_generation_) { return; @@ -9916,14 +10036,25 @@ void Client::webhook_error(Status status) { } void Client::webhook_closed(Status status) { + if (has_webhook_certificate_) { + td::Scheduler::instance()->run_on_scheduler(get_database_scheduler_id(), + [actor_id = actor_id(this), path = get_webhook_certificate_path(), + status = std::move(status)](td::Unit) mutable { + LOG(INFO) << "Unlink certificate " << path; + td::unlink(path).ignore(); + send_closure(actor_id, &Client::on_webhook_closed, std::move(status)); + }); + return; + } + on_webhook_closed(std::move(status)); +} + +void Client::on_webhook_closed(Status status) { LOG(WARNING) << "Webhook closed: " << status << ", webhook_query_type = " << (webhook_query_type_ == WebhookQueryType::Verify ? "verify" : "change"); webhook_id_.release(); webhook_url_ = td::string(); - if (has_webhook_certificate_) { - td::unlink(get_webhook_certificate_path()).ignore(); - has_webhook_certificate_ = false; - } + has_webhook_certificate_ = false; webhook_max_connections_ = 0; webhook_ip_address_ = td::string(); webhook_fix_ip_address_ = false; @@ -10216,7 +10347,7 @@ void Client::do_get_updates(int32 offset, int32 limit, int32 timeout, PromisedQu CHECK(total_size >= updates.size()); total_size -= updates.size(); - bool need_warning = false; + bool need_warning = total_size > 0 && (query->start_timestamp() - previous_get_updates_finish_time_ > 10.0); if (total_size <= MIN_PENDING_UPDATES_WARNING / 2) { if (last_pending_update_count_ > MIN_PENDING_UPDATES_WARNING) { need_warning = true; @@ -10228,7 +10359,7 @@ void Client::do_get_updates(int32 offset, int32 limit, int32 timeout, PromisedQu last_pending_update_count_ *= 2; } } - if (need_warning) { + if (need_warning && previous_get_updates_finish_time_ > 0) { LOG(WARNING) << "Found " << updates.size() << " updates out of " << (total_size + updates.size()) << " after last getUpdates call " << (query->start_timestamp() - previous_get_updates_finish_time_) << " seconds ago in " << (td::Time::now() - query->start_timestamp()) << " seconds"; @@ -10351,6 +10482,11 @@ void Client::set_user_has_private_forwards(int64 user_id, bool has_private_forwa add_user_info(user_id)->has_private_forwards = has_private_forwards; } +void Client::set_user_has_restricted_voice_and_video_messages(int64 user_id, + bool has_restricted_voice_and_video_messages) { + add_user_info(user_id)->has_restricted_voice_and_video_messages = has_restricted_voice_and_video_messages; +} + void Client::set_user_status(int64 user_id, object_ptr &&status) { add_user_info(user_id)->status = std::move(status); } @@ -11113,6 +11249,8 @@ bool Client::need_skip_update_message(int64 chat_id, const object_ptrreply_to_message_id = reply_to_message_id; } +td::Slice Client::get_sticker_type(const object_ptr &type) { + CHECK(type != nullptr); + switch (type->get_id()) { + case td_api::stickerTypeRegular::ID: + return Slice("regular"); + case td_api::stickerTypeMask::ID: + return Slice("mask"); + case td_api::stickerTypeCustomEmoji::ID: + return Slice("custom_emoji"); + default: + UNREACHABLE(); + return Slice(); + } +} + +td::Result> Client::get_sticker_type(Slice type) { + if (type.empty() || type == "regular") { + return make_object(); + } + if (type == "mask") { + return make_object(); + } + if (type == "custom_emoji") { + return make_object(); + } + return Status::Error(400, "Unsupported sticker type specified"); +} + td::CSlice Client::get_callback_data(const object_ptr &type) { CHECK(type != nullptr); switch (type->get_id()) { diff --git a/telegram-bot-api/Client.h b/telegram-bot-api/Client.h index dd7d988..92bc674 100644 --- a/telegram-bot-api/Client.h +++ b/telegram-bot-api/Client.h @@ -16,7 +16,6 @@ #include "td/net/HttpFile.h" #include "td/actor/actor.h" -#include "td/actor/PromiseFuture.h" #include "td/actor/SignalSlot.h" #include "td/utils/common.h" @@ -24,6 +23,7 @@ #include "td/utils/FlatHashMap.h" #include "td/utils/FlatHashSet.h" #include "td/utils/JsonBuilder.h" +#include "td/utils/Promise.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" @@ -42,6 +42,11 @@ class Client final : public WebhookActor::Callback { public: Client(td::ActorShared<> parent, const td::string &bot_token, bool is_user, bool is_test_dc, td::int64 tqueue_id, std::shared_ptr parameters, td::ActorId stat_actor); + Client(const Client &) = delete; + Client &operator=(const Client &) = delete; + Client(Client &&) = delete; + Client &operator=(Client &&) = delete; + ~Client(); void send(PromisedQueryPtr query) final; @@ -212,6 +217,8 @@ class Client final : public WebhookActor::Callback { class TdOnAnswerWebAppQueryCallback; class TdOnReturnFileCallback; class TdOnReturnStickerSetCallback; + class TdOnGetStickerSetPromiseCallback; + class TdOnGetStickersCallback; class TdOnDownloadFileCallback; class TdOnCancelDownloadFileCallback; class TdOnSendCustomRequestCallback; @@ -346,6 +353,10 @@ class Client final : public WebhookActor::Callback { void on_closed(); void finish_closing(); + static int32 get_database_scheduler_id(); + + static int32 get_file_gc_scheduler_id(); + void clear_tqueue(); bool allow_update_before_authorization(const td_api::Object *update) const; @@ -432,7 +443,7 @@ class Client final : public WebhookActor::Callback { static object_ptr mask_index_to_point(int32 index); - td::Result>> get_input_stickers(const Query *query, bool is_masks) const; + td::Result>> get_input_stickers(const Query *query) const; static td::Result get_passport_element_hash(Slice encoded_hash); @@ -599,6 +610,7 @@ class Client final : public WebhookActor::Callback { Status process_approve_chat_join_request_query(PromisedQueryPtr &query); Status process_decline_chat_join_request_query(PromisedQueryPtr &query); Status process_get_sticker_set_query(PromisedQueryPtr &query); + Status process_get_custom_emoji_stickers_query(PromisedQueryPtr &query); Status process_upload_sticker_file_query(PromisedQueryPtr &query); Status process_create_new_sticker_set_query(PromisedQueryPtr &query); Status process_add_sticker_to_set_query(PromisedQueryPtr &query); @@ -663,6 +675,8 @@ class Client final : public WebhookActor::Callback { void save_webhook() const; td::string get_webhook_certificate_path() const; + void on_webhook_closed(Status status); + void do_send_message(object_ptr input_message_content, PromisedQueryPtr query); int64 get_send_message_query_id(PromisedQueryPtr query, bool is_multisend); @@ -674,6 +688,8 @@ class Client final : public WebhookActor::Callback { bool is_file_being_downloaded(int32 file_id) const; void on_file_download(int32 file_id, td::Result> r_file); + void return_stickers(object_ptr stickers, PromisedQueryPtr query); + void fix_reply_markup_bot_user_ids(object_ptr &reply_markup) const; void fix_inline_query_results_bot_user_ids(td::vector> &results) const; @@ -726,6 +742,7 @@ class Client final : public WebhookActor::Callback { bool can_read_all_group_messages = false; bool is_inline_bot = false; bool has_private_forwards = false; + bool has_restricted_voice_and_video_messages = false; bool is_premium = false; bool added_to_attachment_menu = false; }; @@ -733,7 +750,10 @@ class Client final : public WebhookActor::Callback { void set_user_photo(int64 user_id, object_ptr &&photo); void set_user_bio(int64 user_id, td::string &&bio); void set_user_has_private_forwards(int64 user_id, bool has_private_forwards); + void set_user_has_restricted_voice_and_video_messages(int64 user_id, bool has_restricted_voice_and_video_messages); + void set_user_status(int64 user_id, object_ptr &&status); + UserInfo *add_user_info(int64 user_id); const UserInfo *get_user_info(int64 user_id) const; @@ -851,6 +871,10 @@ class Client final : public WebhookActor::Callback { void set_message_reply_to_message_id(MessageInfo *message_info, int64 reply_to_message_id); + static Slice get_sticker_type(const object_ptr &type); + + static td::Result> get_sticker_type(Slice type); + static td::CSlice get_callback_data(const object_ptr &type); static bool are_equal_inline_keyboard_buttons(const td_api::inlineKeyboardButton *lhs, diff --git a/telegram-bot-api/ClientManager.cpp b/telegram-bot-api/ClientManager.cpp index 6350a33..a7bfa95 100644 --- a/telegram-bot-api/ClientManager.cpp +++ b/telegram-bot-api/ClientManager.cpp @@ -99,8 +99,8 @@ void ClientManager::send(PromisedQueryPtr query) { return; } - auto tqueue_id = get_tqueue_id(r_user_id.ok(), query->is_test_dc()); - if (active_client_count_.find(tqueue_id) != active_client_count_.end()) { + auto tqueue_id = get_tqueue_id(user_id, query->is_test_dc()); + if (active_client_count_.count(tqueue_id) != 0) { // return query->set_retry_after_error(1); } @@ -163,7 +163,7 @@ void ClientManager::user_login(PromisedQueryPtr query) { parameters_, std::move(stat_actor)); clients_.get(id)->client_ = std::move(client_id); - auto id_it = token_to_id_.end(); + auto id_it = token_to_id_.find(user_token); std::tie(id_it, std::ignore) = token_to_id_.emplace(user_token, id); send_closure(client_info->client_, &Client::send, std::move(query)); // will send 429 if the client is already closed @@ -216,7 +216,7 @@ bool ClientManager::check_flood_limits(PromisedQueryPtr &query, bool is_user_log return true; } -void ClientManager::get_stats(td::PromiseActor promise, +void ClientManager::get_stats(td::Promise promise, td::vector> args, bool as_json) { if (close_flag_) { @@ -544,7 +544,7 @@ PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, bool auto query = td::make_unique(std::move(containers), token, is_user, is_test_dc, method, std::move(args), td::vector>(), td::vector(), std::move(shared_data), td::IPAddress(), true); - return PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor>())); + return PromisedQueryPtr(query.release(), PromiseDeleter(td::Promise>())); } void ClientManager::raw_event(const td::Event::Raw &event) { diff --git a/telegram-bot-api/ClientManager.h b/telegram-bot-api/ClientManager.h index ec1771e..1fc2aeb 100644 --- a/telegram-bot-api/ClientManager.h +++ b/telegram-bot-api/ClientManager.h @@ -11,13 +11,13 @@ #include "telegram-bot-api/Stats.h" #include "td/actor/actor.h" -#include "td/actor/PromiseFuture.h" #include "td/utils/buffer.h" #include "td/utils/common.h" #include "td/utils/Container.h" #include "td/utils/FlatHashMap.h" #include "td/utils/FloodControlFast.h" +#include "td/utils/Promise.h" #include "td/utils/Slice.h" #include @@ -46,7 +46,7 @@ class ClientManager final : public td::Actor { bool check_flood_limits(PromisedQueryPtr &query, bool is_user_login=false); - void get_stats(td::PromiseActor promise, td::vector> args, bool as_json); + void get_stats(td::Promise promise, td::vector> args, bool as_json); void close(td::Promise &&promise); diff --git a/telegram-bot-api/HttpConnection.cpp b/telegram-bot-api/HttpConnection.cpp index 73265b1..6c2c4e6 100644 --- a/telegram-bot-api/HttpConnection.cpp +++ b/telegram-bot-api/HttpConnection.cpp @@ -14,6 +14,7 @@ #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/Parser.h" +#include "td/utils/Promise.h" #include "td/utils/SliceBuilder.h" namespace telegram_bot_api { @@ -63,26 +64,21 @@ void HttpConnection::handle(td::unique_ptr http_query, std::move(http_query->args_), std::move(http_query->headers_), std::move(http_query->files_), shared_data_, http_query->peer_address_, false); - td::PromiseActor> promise; - td::FutureActor> future; - td::init_promise_future(&promise, &future); - future.set_event(td::EventCreator::yield(actor_id())); + auto promise = td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result> r_query) { + send_closure(actor_id, &HttpConnection::on_query_finished, std::move(r_query)); + }); auto promised_query = PromisedQueryPtr(query.release(), PromiseDeleter(std::move(promise))); if (is_login) { send_closure(client_manager_, &ClientManager::user_login, std::move(promised_query)); } else { send_closure(client_manager_, &ClientManager::send, std::move(promised_query)); } - result_ = std::move(future); } -void HttpConnection::wakeup() { - if (result_.empty()) { - return; - } - LOG_CHECK(result_.is_ok()) << result_.move_as_error(); +void HttpConnection::on_query_finished(td::Result> r_query) { + LOG_CHECK(r_query.is_ok()) << r_query.error(); - auto query = result_.move_as_ok(); + auto query = r_query.move_as_ok(); send_response(query->http_status_code(), std::move(query->answer()), query->retry_after()); } diff --git a/telegram-bot-api/HttpConnection.h b/telegram-bot-api/HttpConnection.h index d631244..d0d245c 100644 --- a/telegram-bot-api/HttpConnection.h +++ b/telegram-bot-api/HttpConnection.h @@ -13,10 +13,10 @@ #include "td/net/HttpQuery.h" #include "td/actor/actor.h" -#include "td/actor/PromiseFuture.h" #include "td/utils/buffer.h" #include "td/utils/Slice.h" +#include "td/utils/Status.h" #include @@ -32,10 +32,7 @@ class HttpConnection final : public td::HttpInboundConnection::Callback { void handle(td::unique_ptr http_query, td::ActorOwn connection) final; - void wakeup() final; - private: - td::FutureActor> result_; td::ActorId client_manager_; td::ActorOwn connection_; std::shared_ptr shared_data_; @@ -45,6 +42,8 @@ class HttpConnection final : public td::HttpInboundConnection::Callback { stop(); } + void on_query_finished(td::Result> r_query); + void send_response(int http_status_code, td::BufferSlice &&content, int retry_after); void send_http_error(int http_status_code, td::Slice description); diff --git a/telegram-bot-api/HttpStatConnection.cpp b/telegram-bot-api/HttpStatConnection.cpp index 922f80e..02506cb 100644 --- a/telegram-bot-api/HttpStatConnection.cpp +++ b/telegram-bot-api/HttpStatConnection.cpp @@ -9,7 +9,7 @@ #include "td/net/HttpHeaderCreator.h" #include "td/utils/common.h" -#include "td/utils/logging.h" +#include "td/utils/Promise.h" namespace telegram_bot_api { @@ -17,25 +17,23 @@ void HttpStatConnection::handle(td::unique_ptr http_query, td::ActorOwn connection) { CHECK(connection_->empty()); connection_ = std::move(connection); - - td::PromiseActor promise; - td::FutureActor future; - init_promise_future(&promise, &future); - future.set_event(td::EventCreator::yield(actor_id())); - LOG(DEBUG) << "SEND"; td::Parser url_path_parser(http_query->url_path_); as_json_ = url_path_parser.try_skip("/json"); + + auto promise = td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result result) { + send_closure(actor_id, &HttpStatConnection::on_result, std::move(result)); + }); send_closure(client_manager_, &ClientManager::get_stats, std::move(promise), http_query->get_args(), as_json_); - result_ = std::move(future); } -void HttpStatConnection::wakeup() { - if (result_.empty()) { +void HttpStatConnection::on_result(td::Result result) { + if (result.is_error()) { + send_closure(connection_.release(), &td::HttpInboundConnection::write_error, + td::Status::Error(500, "Internal Server Error: closing")); return; } - LOG_CHECK(result_.is_ok()) << result_.move_as_error(); - auto content = result_.move_as_ok(); + auto content = result.move_as_ok(); td::HttpHeaderCreator hc; hc.init_status_line(200); hc.set_keep_alive(); diff --git a/telegram-bot-api/HttpStatConnection.h b/telegram-bot-api/HttpStatConnection.h index f5cb5c1..013db83 100644 --- a/telegram-bot-api/HttpStatConnection.h +++ b/telegram-bot-api/HttpStatConnection.h @@ -12,9 +12,9 @@ #include "td/net/HttpQuery.h" #include "td/actor/actor.h" -#include "td/actor/PromiseFuture.h" #include "td/utils/buffer.h" +#include "td/utils/Status.h" namespace telegram_bot_api { @@ -22,16 +22,16 @@ class HttpStatConnection final : public td::HttpInboundConnection::Callback { public: explicit HttpStatConnection(td::ActorId client_manager) : client_manager_(client_manager) { } - void handle(td::unique_ptr http_query, td::ActorOwn connection) final; - void wakeup() final; + void handle(td::unique_ptr http_query, td::ActorOwn connection) final; private: bool as_json_; - td::FutureActor result_; td::ActorId client_manager_; td::ActorOwn connection_; + void on_result(td::Result result); + void hangup() final { connection_.release(); stop(); diff --git a/telegram-bot-api/Query.h b/telegram-bot-api/Query.h index 43aecbb..7df19fc 100644 --- a/telegram-bot-api/Query.h +++ b/telegram-bot-api/Query.h @@ -11,7 +11,6 @@ #include "td/net/HttpFile.h" #include "td/actor/actor.h" -#include "td/actor/PromiseFuture.h" #include "td/utils/buffer.h" #include "td/utils/common.h" @@ -19,6 +18,7 @@ #include "td/utils/JsonBuilder.h" #include "td/utils/List.h" #include "td/utils/port/IPAddress.h" +#include "td/utils/Promise.h" #include "td/utils/Slice.h" #include "td/utils/StringBuilder.h" @@ -231,7 +231,7 @@ class JsonQueryError final : public td::Jsonable { class PromiseDeleter { public: - explicit PromiseDeleter(td::PromiseActor> &&promise) : promise_(std::move(promise)) { + explicit PromiseDeleter(td::Promise> &&promise) : promise_(std::move(promise)) { } PromiseDeleter() = default; PromiseDeleter(const PromiseDeleter &) = delete; @@ -240,7 +240,7 @@ class PromiseDeleter { PromiseDeleter &operator=(PromiseDeleter &&) = default; void operator()(Query *raw_ptr) { td::unique_ptr query(raw_ptr); // now I cannot forget to delete this pointer - if (!promise_.empty_promise()) { + if (promise_) { if (!query->is_ready()) { query->set_retry_after_error(5); } @@ -249,11 +249,11 @@ class PromiseDeleter { } } ~PromiseDeleter() { - CHECK(promise_.empty()); + CHECK(!promise_); } private: - td::PromiseActor> promise_; + td::Promise> promise_; }; using PromisedQueryPtr = std::unique_ptr; diff --git a/telegram-bot-api/WebhookActor.cpp b/telegram-bot-api/WebhookActor.cpp index d6cd7fe..e6b7727 100644 --- a/telegram-bot-api/WebhookActor.cpp +++ b/telegram-bot-api/WebhookActor.cpp @@ -15,7 +15,6 @@ #include "td/net/TransparentProxy.h" #include "td/actor/actor.h" -#include "td/actor/PromiseFuture.h" #include "td/utils/base64.h" #include "td/utils/buffer.h" @@ -26,6 +25,7 @@ #include "td/utils/misc.h" #include "td/utils/port/IPAddress.h" #include "td/utils/port/SocketFd.h" +#include "td/utils/Promise.h" #include "td/utils/Random.h" #include "td/utils/ScopeGuard.h" #include "td/utils/SliceBuilder.h" @@ -82,7 +82,7 @@ void WebhookActor::relax_wakeup_at(double wakeup_at, const char *source) { } void WebhookActor::resolve_ip_address() { - if (fix_ip_address_) { + if (fix_ip_address_ || is_ip_address_being_resolved_) { return; } if (td::Time::now() < next_ip_address_resolve_time_) { @@ -90,43 +90,42 @@ void WebhookActor::resolve_ip_address() { return; } - bool future_created = false; - if (future_ip_address_.empty()) { - td::PromiseActor promise; - init_promise_future(&promise, &future_ip_address_); - future_created = true; - send_closure(parameters_->get_host_by_name_actor_id_, &td::GetHostByNameActor::run, url_.host_, url_.port_, false, - td::PromiseCreator::from_promise_actor(std::move(promise))); + is_ip_address_being_resolved_ = true; + auto promise = td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result r_ip_address) { + send_closure(actor_id, &WebhookActor::on_resolved_ip_address, std::move(r_ip_address)); + }); + send_closure(parameters_->get_host_by_name_actor_id_, &td::GetHostByNameActor::run, url_.host_, url_.port_, false, + std::move(promise)); +} + +void WebhookActor::on_resolved_ip_address(td::Result r_ip_address) { + CHECK(is_ip_address_being_resolved_); + is_ip_address_being_resolved_ = false; + + next_ip_address_resolve_time_ = + td::Time::now() + IP_ADDRESS_CACHE_TIME + td::Random::fast(0, IP_ADDRESS_CACHE_TIME / 10); + relax_wakeup_at(next_ip_address_resolve_time_, "on_resolved_ip_address"); + + SCOPE_EXIT { + loop(); + }; + + if (r_ip_address.is_error()) { + return on_error(r_ip_address.move_as_error()); } - - if (future_ip_address_.is_ready()) { - next_ip_address_resolve_time_ = - td::Time::now() + IP_ADDRESS_CACHE_TIME + td::Random::fast(0, IP_ADDRESS_CACHE_TIME / 10); - relax_wakeup_at(next_ip_address_resolve_time_, "resolve_ip_address"); - - auto r_ip_address = future_ip_address_.move_as_result(); - if (r_ip_address.is_error()) { - CHECK(!(r_ip_address.error() == td::Status::Error::HANGUP_ERROR_CODE>())); - return on_error(r_ip_address.move_as_error()); - } - auto new_ip_address = r_ip_address.move_as_ok(); - if (!check_ip_address(new_ip_address)) { - return on_error(td::Status::Error(PSLICE() << "IP address " << new_ip_address.get_ip_str() << " is reserved")); - } - if (!(ip_address_ == new_ip_address)) { - VLOG(webhook) << "IP address has changed: " << ip_address_ << " --> " << new_ip_address; - ip_address_ = new_ip_address; - ip_generation_++; - if (was_checked_) { - on_webhook_verified(); - } - } - VLOG(webhook) << "IP address was verified"; - } else { - if (future_created) { - future_ip_address_.set_event(td::EventCreator::yield(actor_id())); + auto new_ip_address = r_ip_address.move_as_ok(); + if (!check_ip_address(new_ip_address)) { + return on_error(td::Status::Error(PSLICE() << "IP address " << new_ip_address.get_ip_str() << " is reserved")); + } + if (!(ip_address_ == new_ip_address)) { + VLOG(webhook) << "IP address has changed: " << ip_address_ << " --> " << new_ip_address; + ip_address_ = new_ip_address; + ip_generation_++; + if (was_checked_) { + on_webhook_verified(); } } + VLOG(webhook) << "IP address was verified"; } td::Status WebhookActor::create_connection() { @@ -602,11 +601,10 @@ void WebhookActor::handle(td::unique_ptr response) { method != "logout" && !td::begins_with(method, "get")) { VLOG(webhook) << "Receive request " << method << " in response to webhook"; auto query = td::make_unique(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::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::Promise>())); send_closure(callback_, &Callback::send, std::move(promised_query)); } first_error_410_time_ = 0; diff --git a/telegram-bot-api/WebhookActor.h b/telegram-bot-api/WebhookActor.h index d5e2499..28f5989 100644 --- a/telegram-bot-api/WebhookActor.h +++ b/telegram-bot-api/WebhookActor.h @@ -15,7 +15,6 @@ #include "td/net/SslStream.h" #include "td/actor/actor.h" -#include "td/actor/PromiseFuture.h" #include "td/utils/BufferedFd.h" #include "td/utils/common.h" @@ -137,7 +136,7 @@ class WebhookActor final : public td::HttpOutboundConnection::Callback { td::IPAddress ip_address_; td::int32 ip_generation_ = 0; double next_ip_address_resolve_time_ = 0; - td::FutureActor future_ip_address_; + bool is_ip_address_being_resolved_ = false; class Connection final : public td::ListNode { public: @@ -175,6 +174,7 @@ class WebhookActor final : public td::HttpOutboundConnection::Callback { void relax_wakeup_at(double wakeup_at, const char *source); void resolve_ip_address(); + void on_resolved_ip_address(td::Result r_ip_address); td::Result create_ssl_stream(); td::Status create_connection() TD_WARN_UNUSED_RESULT; diff --git a/telegram-bot-api/telegram-bot-api.cpp b/telegram-bot-api/telegram-bot-api.cpp index 8c159a9..4c72ae7 100644 --- a/telegram-bot-api/telegram-bot-api.cpp +++ b/telegram-bot-api/telegram-bot-api.cpp @@ -22,7 +22,6 @@ #include "td/actor/actor.h" #include "td/actor/ConcurrentScheduler.h" -#include "td/actor/PromiseFuture.h" #include "td/utils/buffer.h" #include "td/utils/CombinedLog.h" @@ -45,6 +44,7 @@ #include "td/utils/port/stacktrace.h" #include "td/utils/port/Stat.h" #include "td/utils/port/user.h" +#include "td/utils/Promise.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" @@ -200,7 +200,7 @@ int main(int argc, char *argv[]) { auto start_time = td::Time::now(); auto shared_data = std::make_shared(); auto parameters = std::make_unique(); - parameters->version_ = "6.1"; + parameters->version_ = "6.2"; parameters->shared_data_ = shared_data; parameters->start_time_ = start_time; auto net_query_stats = td::create_net_query_stats();