// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/ChatManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogAdministrator.h" #include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogInviteLinkManager.h" #include "td/telegram/DialogLocation.h" #include "td/telegram/DialogManager.h" #include "td/telegram/DialogParticipantManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/FolderId.h" #include "td/telegram/Global.h" #include "td/telegram/GroupCallManager.h" #include "td/telegram/InputGroupCallId.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/MessageTtl.h" #include "td/telegram/MinChannel.h" #include "td/telegram/misc.h" #include "td/telegram/MissingInvitee.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PeerColor.h" #include "td/telegram/Photo.h" #include "td/telegram/Photo.hpp" #include "td/telegram/PhotoSize.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StoryManager.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserManager.h" #include "td/db/binlog/BinlogEvent.h" #include "td/db/binlog/BinlogHelper.h" #include "td/db/SqliteKeyValue.h" #include "td/db/SqliteKeyValueAsync.h" #include "td/actor/MultiPromise.h" #include "td/actor/SleepActor.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/ScopeGuard.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/StringBuilder.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" #include "td/utils/utf8.h" #include #include namespace td { class CreateChatQuery final : public Td::ResultHandler { Promise> promise_; public: explicit CreateChatQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(vector> &&input_users, const string &title, MessageTtl message_ttl) { int32 flags = telegram_api::messages_createChat::TTL_PERIOD_MASK; send_query(G()->net_query_creator().create( telegram_api::messages_createChat(flags, std::move(input_users), title, message_ttl.get_input_ttl_period()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for CreateChatQuery: " << to_string(ptr); td_->messages_manager_->on_create_new_dialog( std::move(ptr->updates_), MissingInvitees(std::move(ptr->missing_invitees_)), std::move(promise_), Auto()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class CreateChannelQuery final : public Td::ResultHandler { Promise> promise_; public: explicit CreateChannelQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &title, bool is_forum, bool is_megagroup, const string &about, const DialogLocation &location, bool for_import, MessageTtl message_ttl) { int32 flags = telegram_api::channels_createChannel::TTL_PERIOD_MASK; if (is_forum) { flags |= telegram_api::channels_createChannel::FORUM_MASK; } else if (is_megagroup) { flags |= telegram_api::channels_createChannel::MEGAGROUP_MASK; } else { flags |= telegram_api::channels_createChannel::BROADCAST_MASK; } if (!location.empty()) { flags |= telegram_api::channels_createChannel::GEO_POINT_MASK; } if (for_import) { flags |= telegram_api::channels_createChannel::FOR_IMPORT_MASK; } send_query(G()->net_query_creator().create(telegram_api::channels_createChannel( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, title, about, location.get_input_geo_point(), location.get_address(), message_ttl.get_input_ttl_period()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } td_->messages_manager_->on_create_new_dialog(result_ptr.move_as_ok(), MissingInvitees(), Auto(), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UpdateChannelUsernameQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; string username_; public: explicit UpdateChannelUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, const string &username) { channel_id_ = channel_id; username_ = username; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_updateUsername(std::move(input_channel), username), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); LOG(DEBUG) << "Receive result for UpdateChannelUsernameQuery: " << result; if (!result) { return on_error(Status::Error(500, "Supergroup username is not updated")); } td_->chat_manager_->on_update_channel_editable_username(channel_id_, std::move(username_)); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { td_->chat_manager_->on_update_channel_editable_username(channel_id_, std::move(username_)); if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelUsernameQuery"); } promise_.set_error(std::move(status)); } }; class ToggleChannelUsernameQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; string username_; bool is_active_; public: explicit ToggleChannelUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, string &&username, bool is_active) { channel_id_ = channel_id; username_ = std::move(username); is_active_ = is_active; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleUsername(std::move(input_channel), username_, is_active_), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); LOG(DEBUG) << "Receive result for ToggleChannelUsernameQuery: " << result; td_->chat_manager_->on_update_channel_username_is_active(channel_id_, std::move(username_), is_active_, std::move(promise_)); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { td_->chat_manager_->on_update_channel_username_is_active(channel_id_, std::move(username_), is_active_, std::move(promise_)); return; } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelUsernameQuery"); } promise_.set_error(std::move(status)); } }; class DeactivateAllChannelUsernamesQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit DeactivateAllChannelUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_deactivateAllUsernames(std::move(input_channel)), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); LOG(DEBUG) << "Receive result for DeactivateAllChannelUsernamesQuery: " << result; td_->chat_manager_->on_deactivate_channel_usernames(channel_id_, std::move(promise_)); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { td_->chat_manager_->on_deactivate_channel_usernames(channel_id_, std::move(promise_)); return; } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeactivateAllChannelUsernamesQuery"); } promise_.set_error(std::move(status)); } }; class ReorderChannelUsernamesQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; vector usernames_; public: explicit ReorderChannelUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, vector &&usernames) { channel_id_ = channel_id; usernames_ = usernames; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_reorderUsernames(std::move(input_channel), std::move(usernames)), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); LOG(DEBUG) << "Receive result for ReorderChannelUsernamesQuery: " << result; if (!result) { return on_error(Status::Error(500, "Supergroup usernames weren't updated")); } td_->chat_manager_->on_update_channel_active_usernames_order(channel_id_, std::move(usernames_), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { td_->chat_manager_->on_update_channel_active_usernames_order(channel_id_, std::move(usernames_), std::move(promise_)); return; } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReorderChannelUsernamesQuery"); } promise_.set_error(std::move(status)); } }; class UpdateChannelColorQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit UpdateChannelColorQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool for_profile, AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = 0; if (for_profile) { flags |= telegram_api::channels_updateColor::FOR_PROFILE_MASK; } if (accent_color_id.is_valid()) { flags |= telegram_api::channels_updateColor::COLOR_MASK; } if (background_custom_emoji_id.is_valid()) { flags |= telegram_api::channels_updateColor::BACKGROUND_EMOJI_ID_MASK; } send_query(G()->net_query_creator().create( telegram_api::channels_updateColor(flags, false /*ignored*/, std::move(input_channel), accent_color_id.get(), background_custom_emoji_id.get()), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for UpdateChannelColorQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelColorQuery"); } promise_.set_error(std::move(status)); } }; class UpdateChannelEmojiStatusQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit UpdateChannelEmojiStatusQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, const EmojiStatus &emoji_status) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_updateEmojiStatus(std::move(input_channel), emoji_status.get_input_emoji_status()), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for UpdateChannelEmojiStatusQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelEmojiStatusQuery"); get_recent_emoji_statuses(td_, Auto()); } promise_.set_error(std::move(status)); } }; class SetChannelStickerSetQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; StickerSetId sticker_set_id_; public: explicit SetChannelStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, StickerSetId sticker_set_id, telegram_api::object_ptr &&input_sticker_set) { channel_id_ = channel_id; sticker_set_id_ = sticker_set_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_setStickers(std::move(input_channel), std::move(input_sticker_set)), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); LOG(DEBUG) << "Receive result for SetChannelStickerSetQuery: " << result; if (!result) { return on_error(Status::Error(500, "Supergroup sticker set not updated")); } td_->chat_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { td_->chat_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_); if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "SetChannelStickerSetQuery"); } promise_.set_error(std::move(status)); } }; class SetChannelEmojiStickerSetQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; StickerSetId sticker_set_id_; public: explicit SetChannelEmojiStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, StickerSetId sticker_set_id, telegram_api::object_ptr &&input_sticker_set) { channel_id_ = channel_id; sticker_set_id_ = sticker_set_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_setEmojiStickers(std::move(input_channel), std::move(input_sticker_set)), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); LOG(DEBUG) << "Receive result for SetChannelEmojiStickerSetQuery: " << result; if (!result) { return on_error(Status::Error(500, "Supergroup custom emoji sticker set not updated")); } td_->chat_manager_->on_update_channel_emoji_sticker_set(channel_id_, sticker_set_id_); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { td_->chat_manager_->on_update_channel_emoji_sticker_set(channel_id_, sticker_set_id_); if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "SetChannelEmojiStickerSetQuery"); } promise_.set_error(std::move(status)); } }; class SetChannelBoostsToUnblockRestrictionsQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; int32 unrestrict_boost_count_; public: explicit SetChannelBoostsToUnblockRestrictionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, int32 unrestrict_boost_count) { channel_id_ = channel_id; unrestrict_boost_count_ = unrestrict_boost_count; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_setBoostsToUnblockRestrictions(std::move(input_channel), unrestrict_boost_count), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for SetChannelBoostsToUnblockRestrictionsQuery: " << to_string(ptr); td_->chat_manager_->on_update_channel_unrestrict_boost_count(channel_id_, unrestrict_boost_count_); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { td_->chat_manager_->on_update_channel_unrestrict_boost_count(channel_id_, unrestrict_boost_count_); if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "SetChannelBoostsToUnblockRestrictionsQuery"); } promise_.set_error(std::move(status)); } }; class ToggleChannelSignaturesQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit ToggleChannelSignaturesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool sign_messages) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleSignatures(std::move(input_channel), sign_messages), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleChannelSignaturesQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelSignaturesQuery"); } promise_.set_error(std::move(status)); } }; class ToggleChannelJoinToSendQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit ToggleChannelJoinToSendQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool join_to_send) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleJoinToSend(std::move(input_channel), join_to_send), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleChannelJoinToSendQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinToSendQuery"); } promise_.set_error(std::move(status)); } }; class ToggleChannelJoinRequestQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit ToggleChannelJoinRequestQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool join_request) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleJoinRequest(std::move(input_channel), join_request), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleChannelJoinRequestQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinRequestQuery"); } promise_.set_error(std::move(status)); } }; class TogglePrehistoryHiddenQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; bool is_all_history_available_; public: explicit TogglePrehistoryHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool is_all_history_available) { channel_id_ = channel_id; is_all_history_available_ = is_all_history_available; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_togglePreHistoryHidden(std::move(input_channel), !is_all_history_available), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for TogglePrehistoryHiddenQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates( std::move(ptr), PromiseCreator::lambda([actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, is_all_history_available = is_all_history_available_](Unit result) mutable { send_closure(actor_id, &ChatManager::on_update_channel_is_all_history_available, channel_id, is_all_history_available, std::move(promise)); })); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "TogglePrehistoryHiddenQuery"); } promise_.set_error(std::move(status)); } }; class RestrictSponsoredMessagesQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; bool can_have_sponsored_messages_; public: explicit RestrictSponsoredMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool can_have_sponsored_messages) { channel_id_ = channel_id; can_have_sponsored_messages_ = can_have_sponsored_messages; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_restrictSponsoredMessages(std::move(input_channel), !can_have_sponsored_messages), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for RestrictSponsoredMessagesQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates( std::move(ptr), PromiseCreator::lambda([actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, can_have_sponsored_messages = can_have_sponsored_messages_](Unit result) mutable { send_closure(actor_id, &ChatManager::on_update_channel_can_have_sponsored_messages, channel_id, can_have_sponsored_messages, std::move(promise)); })); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "RestrictSponsoredMessagesQuery"); } promise_.set_error(std::move(status)); } }; class ToggleParticipantsHiddenQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; bool has_hidden_participants_; public: explicit ToggleParticipantsHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool has_hidden_participants) { channel_id_ = channel_id; has_hidden_participants_ = has_hidden_participants; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleParticipantsHidden(std::move(input_channel), has_hidden_participants), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleParticipantsHiddenQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates( std::move(ptr), PromiseCreator::lambda([actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, has_hidden_participants = has_hidden_participants_](Unit result) mutable { send_closure(actor_id, &ChatManager::on_update_channel_has_hidden_participants, channel_id, has_hidden_participants, std::move(promise)); })); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleParticipantsHiddenQuery"); } promise_.set_error(std::move(status)); } }; class ToggleAntiSpamQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; bool has_aggressive_anti_spam_enabled_; public: explicit ToggleAntiSpamQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool has_aggressive_anti_spam_enabled) { channel_id_ = channel_id; has_aggressive_anti_spam_enabled_ = has_aggressive_anti_spam_enabled; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleAntiSpam(std::move(input_channel), has_aggressive_anti_spam_enabled), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleAntiSpamQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates( std::move(ptr), PromiseCreator::lambda( [actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled_](Unit result) mutable { send_closure(actor_id, &ChatManager::on_update_channel_has_aggressive_anti_spam_enabled, channel_id, has_aggressive_anti_spam_enabled, std::move(promise)); })); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleAntiSpamQuery"); } promise_.set_error(std::move(status)); } }; class ToggleForumQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit ToggleForumQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool is_forum) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_toggleForum(std::move(input_channel), is_forum), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleForumQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleForumQuery"); } promise_.set_error(std::move(status)); } }; class ConvertToGigagroupQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit ConvertToGigagroupQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_convertToGigagroup(std::move(input_channel)), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ConvertToGigagroupQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { promise_.set_value(Unit()); return; } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ConvertToGigagroupQuery"); } promise_.set_error(std::move(status)); } }; class EditChatAboutQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; string about_; void on_success() { switch (dialog_id_.get_type()) { case DialogType::Chat: return td_->chat_manager_->on_update_chat_description(dialog_id_.get_chat_id(), std::move(about_)); case DialogType::Channel: return td_->chat_manager_->on_update_channel_description(dialog_id_.get_channel_id(), std::move(about_)); case DialogType::User: case DialogType::SecretChat: case DialogType::None: UNREACHABLE(); } } public: explicit EditChatAboutQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, const string &about) { dialog_id_ = dialog_id; about_ = about; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } send_query(G()->net_query_creator().create(telegram_api::messages_editChatAbout(std::move(input_peer), about), {{dialog_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); LOG(DEBUG) << "Receive result for EditChatAboutQuery: " << result; if (!result) { return on_error(Status::Error(500, "Chat description is not updated")); } on_success(); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "CHAT_ABOUT_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { on_success(); if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "EditChatAboutQuery"); } promise_.set_error(std::move(status)); } }; class SetDiscussionGroupQuery final : public Td::ResultHandler { Promise promise_; ChannelId broadcast_channel_id_; ChannelId group_channel_id_; public: explicit SetDiscussionGroupQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId broadcast_channel_id, telegram_api::object_ptr broadcast_input_channel, ChannelId group_channel_id, telegram_api::object_ptr group_input_channel) { broadcast_channel_id_ = broadcast_channel_id; group_channel_id_ = group_channel_id; send_query(G()->net_query_creator().create( telegram_api::channels_setDiscussionGroup(std::move(broadcast_input_channel), std::move(group_input_channel)), {{broadcast_channel_id}, {group_channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.move_as_ok(); LOG_IF(INFO, !result) << "Set discussion group has failed"; td_->chat_manager_->on_update_channel_linked_channel_id(broadcast_channel_id_, group_channel_id_); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "LINK_NOT_MODIFIED") { return promise_.set_value(Unit()); } promise_.set_error(std::move(status)); } }; class EditLocationQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; DialogLocation location_; public: explicit EditLocationQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, const DialogLocation &location) { channel_id_ = channel_id; location_ = location; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_editLocation(std::move(input_channel), location_.get_input_geo_point(), location_.get_address()), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.move_as_ok(); LOG_IF(INFO, !result) << "Edit chat location has failed"; td_->chat_manager_->on_update_channel_location(channel_id_, location_); promise_.set_value(Unit()); } void on_error(Status status) final { td_->chat_manager_->on_get_channel_error(channel_id_, status, "EditLocationQuery"); promise_.set_error(std::move(status)); } }; class ToggleSlowModeQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; int32 slow_mode_delay_ = 0; public: explicit ToggleSlowModeQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, int32 slow_mode_delay) { channel_id_ = channel_id; slow_mode_delay_ = slow_mode_delay; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleSlowMode(std::move(input_channel), slow_mode_delay), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ToggleSlowModeQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates( std::move(ptr), PromiseCreator::lambda([actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, slow_mode_delay = slow_mode_delay_](Unit result) mutable { send_closure(actor_id, &ChatManager::on_update_channel_slow_mode_delay, channel_id, slow_mode_delay, std::move(promise)); })); } void on_error(Status status) final { if (status.message() == "CHAT_NOT_MODIFIED") { td_->chat_manager_->on_update_channel_slow_mode_delay(channel_id_, slow_mode_delay_, Promise()); if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } } else { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleSlowModeQuery"); } promise_.set_error(std::move(status)); } }; class ReportChannelSpamQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; DialogId sender_dialog_id_; public: explicit ReportChannelSpamQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, DialogId sender_dialog_id, const vector &message_ids) { channel_id_ = channel_id; sender_dialog_id_ = sender_dialog_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); auto input_peer = td_->dialog_manager_->get_input_peer(sender_dialog_id, AccessRights::Know); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_reportSpam( std::move(input_channel), std::move(input_peer), MessageId::get_server_message_ids(message_ids)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.move_as_ok(); LOG_IF(INFO, !result) << "Report spam has failed in " << channel_id_; promise_.set_value(Unit()); } void on_error(Status status) final { if (sender_dialog_id_.get_type() != DialogType::Channel) { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReportChannelSpamQuery"); } promise_.set_error(std::move(status)); } }; class ReportChannelAntiSpamFalsePositiveQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit ReportChannelAntiSpamFalsePositiveQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, MessageId message_id) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_reportAntiSpamFalsePositive( std::move(input_channel), message_id.get_server_message_id().get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.move_as_ok(); LOG_IF(INFO, !result) << "Report anti-spam false positive has failed in " << channel_id_; promise_.set_value(Unit()); } void on_error(Status status) final { td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReportChannelAntiSpamFalsePositiveQuery"); promise_.set_error(std::move(status)); } }; class DeleteChatQuery final : public Td::ResultHandler { Promise promise_; public: explicit DeleteChatQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChatId chat_id) { send_query(G()->net_query_creator().create(telegram_api::messages_deleteChat(chat_id.get()), {{chat_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } LOG(INFO) << "Receive result for DeleteChatQuery: " << result_ptr.ok(); td_->updates_manager_->get_difference("DeleteChatQuery"); td_->updates_manager_->on_get_updates(make_tl_object(), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class DeleteChannelQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit DeleteChannelQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_deleteChannel(std::move(input_channel)), {{channel_id}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for DeleteChannelQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelQuery"); promise_.set_error(std::move(status)); } }; class GetCreatedPublicChannelsQuery final : public Td::ResultHandler { Promise promise_; PublicDialogType type_; public: explicit GetCreatedPublicChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(PublicDialogType type, bool check_limit) { type_ = type; int32 flags = 0; if (type_ == PublicDialogType::IsLocationBased) { flags |= telegram_api::channels_getAdminedPublicChannels::BY_LOCATION_MASK; } if (type_ == PublicDialogType::ForPersonalDialog) { CHECK(!check_limit); flags |= telegram_api::channels_getAdminedPublicChannels::FOR_PERSONAL_MASK; } if (check_limit) { flags |= telegram_api::channels_getAdminedPublicChannels::CHECK_LIMIT_MASK; } send_query(G()->net_query_creator().create(telegram_api::channels_getAdminedPublicChannels( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto chats_ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetCreatedPublicChannelsQuery: " << to_string(chats_ptr); switch (chats_ptr->get_id()) { case telegram_api::messages_chats::ID: { auto chats = move_tl_object_as(chats_ptr); td_->chat_manager_->on_get_created_public_channels(type_, std::move(chats->chats_)); break; } case telegram_api::messages_chatsSlice::ID: { auto chats = move_tl_object_as(chats_ptr); LOG(ERROR) << "Receive chatsSlice in result of GetCreatedPublicChannelsQuery"; td_->chat_manager_->on_get_created_public_channels(type_, std::move(chats->chats_)); break; } default: UNREACHABLE(); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetGroupsForDiscussionQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetGroupsForDiscussionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::channels_getGroupsForDiscussion())); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto chats_ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetGroupsForDiscussionQuery: " << to_string(chats_ptr); switch (chats_ptr->get_id()) { case telegram_api::messages_chats::ID: { auto chats = move_tl_object_as(chats_ptr); td_->chat_manager_->on_get_dialogs_for_discussion(std::move(chats->chats_)); break; } case telegram_api::messages_chatsSlice::ID: { auto chats = move_tl_object_as(chats_ptr); LOG(ERROR) << "Receive chatsSlice in result of GetGroupsForDiscussionQuery"; td_->chat_manager_->on_get_dialogs_for_discussion(std::move(chats->chats_)); break; } default: UNREACHABLE(); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetInactiveChannelsQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetInactiveChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::channels_getInactiveChannels())); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetInactiveChannelsQuery: " << to_string(result); // don't need to use result->dates_, because chat.last_message.date is more reliable td_->user_manager_->on_get_users(std::move(result->users_), "GetInactiveChannelsQuery"); td_->chat_manager_->on_get_inactive_channels(std::move(result->chats_), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetChatsQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetChatsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector &&chat_ids) { send_query(G()->net_query_creator().create(telegram_api::messages_getChats(std::move(chat_ids)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto chats_ptr = result_ptr.move_as_ok(); switch (chats_ptr->get_id()) { case telegram_api::messages_chats::ID: { auto chats = move_tl_object_as(chats_ptr); td_->chat_manager_->on_get_chats(std::move(chats->chats_), "GetChatsQuery"); break; } case telegram_api::messages_chatsSlice::ID: { auto chats = move_tl_object_as(chats_ptr); LOG(ERROR) << "Receive chatsSlice in result of GetChatsQuery"; td_->chat_manager_->on_get_chats(std::move(chats->chats_), "GetChatsQuery slice"); break; } default: UNREACHABLE(); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetFullChatQuery final : public Td::ResultHandler { Promise promise_; ChatId chat_id_; public: explicit GetFullChatQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChatId chat_id) { send_query(G()->net_query_creator().create(telegram_api::messages_getFullChat(chat_id.get()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); td_->user_manager_->on_get_users(std::move(ptr->users_), "GetFullChatQuery"); td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetFullChatQuery"); td_->chat_manager_->on_get_chat_full(std::move(ptr->full_chat_), std::move(promise_)); } void on_error(Status status) final { td_->chat_manager_->on_get_chat_full_failed(chat_id_); promise_.set_error(std::move(status)); } }; class GetChannelsQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit GetChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(tl_object_ptr &&input_channel) { CHECK(input_channel != nullptr); if (input_channel->get_id() == telegram_api::inputChannel::ID) { channel_id_ = ChannelId(static_cast(input_channel.get())->channel_id_); } else if (input_channel->get_id() == telegram_api::inputChannelFromMessage::ID) { channel_id_ = ChannelId(static_cast(input_channel.get())->channel_id_); } vector> input_channels; input_channels.push_back(std::move(input_channel)); send_query(G()->net_query_creator().create(telegram_api::channels_getChannels(std::move(input_channels)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } // LOG(INFO) << "Receive result for GetChannelsQuery: " << to_string(result_ptr.ok()); auto chats_ptr = result_ptr.move_as_ok(); switch (chats_ptr->get_id()) { case telegram_api::messages_chats::ID: { auto chats = move_tl_object_as(chats_ptr); td_->chat_manager_->on_get_chats(std::move(chats->chats_), "GetChannelsQuery"); break; } case telegram_api::messages_chatsSlice::ID: { auto chats = move_tl_object_as(chats_ptr); LOG(ERROR) << "Receive chatsSlice in result of GetChannelsQuery"; td_->chat_manager_->on_get_chats(std::move(chats->chats_), "GetChannelsQuery slice"); break; } default: UNREACHABLE(); } promise_.set_value(Unit()); } void on_error(Status status) final { td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelsQuery"); promise_.set_error(std::move(status)); } }; class GetFullChannelQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; public: explicit GetFullChannelQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, tl_object_ptr &&input_channel) { channel_id_ = channel_id; send_query(G()->net_query_creator().create(telegram_api::channels_getFullChannel(std::move(input_channel)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); td_->user_manager_->on_get_users(std::move(ptr->users_), "GetFullChannelQuery"); td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetFullChannelQuery"); td_->chat_manager_->on_get_chat_full(std::move(ptr->full_chat_), std::move(promise_)); } void on_error(Status status) final { td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetFullChannelQuery"); td_->chat_manager_->on_get_channel_full_failed(channel_id_); promise_.set_error(std::move(status)); } }; ChatManager::ChatManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { channel_emoji_status_timeout_.set_callback(on_channel_emoji_status_timeout_callback); channel_emoji_status_timeout_.set_callback_data(static_cast(this)); channel_unban_timeout_.set_callback(on_channel_unban_timeout_callback); channel_unban_timeout_.set_callback_data(static_cast(this)); slow_mode_delay_timeout_.set_callback(on_slow_mode_delay_timeout_callback); slow_mode_delay_timeout_.set_callback_data(static_cast(this)); get_chat_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); td_->create_handler(std::move(promise))->send(std::move(query_ids)); }); get_channel_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(query_ids.size() == 1); auto input_channel = get_input_channel(ChannelId(query_ids[0])); if (input_channel == nullptr) { return promise.set_error(Status::Error(400, "Channel not found")); } td_->create_handler(std::move(promise))->send(std::move(input_channel)); }); } ChatManager::~ChatManager() { Scheduler::instance()->destroy_on_scheduler( G()->get_gc_scheduler_id(), chats_, chats_full_, unknown_chats_, chat_full_file_source_ids_, min_channels_, channels_, channels_full_, unknown_channels_, invalidated_channels_full_, channel_full_file_source_ids_); Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), loaded_from_database_chats_, unavailable_chat_fulls_, loaded_from_database_channels_, unavailable_channel_fulls_, linked_channel_ids_, restricted_channel_ids_); } void ChatManager::tear_down() { parent_.reset(); LOG(DEBUG) << "Have " << chats_.calc_size() << " basic groups and " << channels_.calc_size() << " supergroups to free"; LOG(DEBUG) << "Have " << chats_full_.calc_size() << " full basic groups and " << channels_full_.calc_size() << " full supergroups to free"; } void ChatManager::on_channel_emoji_status_timeout_callback(void *chat_manager_ptr, int64 channel_id_long) { if (G()->close_flag()) { return; } auto chat_manager = static_cast(chat_manager_ptr); send_closure_later(chat_manager->actor_id(chat_manager), &ChatManager::on_channel_emoji_status_timeout, ChannelId(channel_id_long)); } void ChatManager::on_channel_emoji_status_timeout(ChannelId channel_id) { if (G()->close_flag()) { return; } auto c = get_channel(channel_id); CHECK(c != nullptr); CHECK(c->is_update_supergroup_sent); update_channel(c, channel_id); } void ChatManager::on_channel_unban_timeout_callback(void *chat_manager_ptr, int64 channel_id_long) { if (G()->close_flag()) { return; } auto chat_manager = static_cast(chat_manager_ptr); send_closure_later(chat_manager->actor_id(chat_manager), &ChatManager::on_channel_unban_timeout, ChannelId(channel_id_long)); } void ChatManager::on_channel_unban_timeout(ChannelId channel_id) { if (G()->close_flag()) { return; } auto c = get_channel(channel_id); CHECK(c != nullptr); auto old_status = c->status; c->status.update_restrictions(); if (c->status == old_status) { LOG_IF(ERROR, c->status.is_restricted() || c->status.is_banned()) << "Status of " << channel_id << " wasn't updated: " << c->status; } else { c->is_changed = true; } LOG(INFO) << "Update " << channel_id << " status"; c->is_status_changed = true; invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_unban_timeout"); update_channel(c, channel_id); // always call, because in case of failure we need to reactivate timeout } void ChatManager::on_slow_mode_delay_timeout_callback(void *chat_manager_ptr, int64 channel_id_long) { if (G()->close_flag()) { return; } auto chat_manager = static_cast(chat_manager_ptr); send_closure_later(chat_manager->actor_id(chat_manager), &ChatManager::on_slow_mode_delay_timeout, ChannelId(channel_id_long)); } void ChatManager::on_slow_mode_delay_timeout(ChannelId channel_id) { if (G()->close_flag()) { return; } on_update_channel_slow_mode_next_send_date(channel_id, 0); } template void ChatManager::Chat::store(StorerT &storer) const { using td::store; bool has_photo = photo.small_file_id.is_valid(); bool use_new_rights = true; bool has_default_permissions_version = default_permissions_version != -1; bool has_pinned_message_version = pinned_message_version != -1; bool has_cache_version = cache_version != 0; BEGIN_STORE_FLAGS(); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(is_active); STORE_FLAG(has_photo); STORE_FLAG(use_new_rights); STORE_FLAG(has_default_permissions_version); STORE_FLAG(has_pinned_message_version); STORE_FLAG(has_cache_version); STORE_FLAG(noforwards); END_STORE_FLAGS(); store(title, storer); if (has_photo) { store(photo, storer); } store(participant_count, storer); store(date, storer); store(migrated_to_channel_id, storer); store(version, storer); store(status, storer); store(default_permissions, storer); if (has_default_permissions_version) { store(default_permissions_version, storer); } if (has_pinned_message_version) { store(pinned_message_version, storer); } if (has_cache_version) { store(cache_version, storer); } } template void ChatManager::Chat::parse(ParserT &parser) { using td::parse; bool has_photo; bool left; bool kicked; bool is_creator; bool is_administrator; bool everyone_is_administrator; bool can_edit; bool use_new_rights; bool has_default_permissions_version; bool has_pinned_message_version; bool has_cache_version; BEGIN_PARSE_FLAGS(); PARSE_FLAG(left); PARSE_FLAG(kicked); PARSE_FLAG(is_creator); PARSE_FLAG(is_administrator); PARSE_FLAG(everyone_is_administrator); PARSE_FLAG(can_edit); PARSE_FLAG(is_active); PARSE_FLAG(has_photo); PARSE_FLAG(use_new_rights); PARSE_FLAG(has_default_permissions_version); PARSE_FLAG(has_pinned_message_version); PARSE_FLAG(has_cache_version); PARSE_FLAG(noforwards); END_PARSE_FLAGS(); parse(title, parser); if (has_photo) { parse(photo, parser); } parse(participant_count, parser); parse(date, parser); parse(migrated_to_channel_id, parser); parse(version, parser); if (use_new_rights) { parse(status, parser); parse(default_permissions, parser); } else { if (can_edit != (is_creator || is_administrator || everyone_is_administrator)) { LOG(ERROR) << "Have wrong can_edit flag"; } if (kicked || !is_active) { status = DialogParticipantStatus::Banned(0); } else if (left) { status = DialogParticipantStatus::Left(); } else if (is_creator) { status = DialogParticipantStatus::Creator(true, false, string()); } else if (is_administrator && !everyone_is_administrator) { status = DialogParticipantStatus::GroupAdministrator(false); } else { status = DialogParticipantStatus::Member(); } default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, everyone_is_administrator, everyone_is_administrator, everyone_is_administrator, false, ChannelType::Unknown); } if (has_default_permissions_version) { parse(default_permissions_version, parser); } if (has_pinned_message_version) { parse(pinned_message_version, parser); } if (has_cache_version) { parse(cache_version, parser); } if (!check_utf8(title)) { LOG(ERROR) << "Have invalid title \"" << title << '"'; title.clear(); cache_version = 0; } if (status.is_administrator() && !status.is_creator()) { status = DialogParticipantStatus::GroupAdministrator(false); } } template void ChatManager::ChatFull::store(StorerT &storer) const { using td::store; bool has_description = !description.empty(); bool has_legacy_invite_link = false; bool has_photo = !photo.is_empty(); bool has_invite_link = invite_link.is_valid(); bool has_bot_commands = !bot_commands.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_description); STORE_FLAG(has_legacy_invite_link); STORE_FLAG(can_set_username); STORE_FLAG(has_photo); STORE_FLAG(has_invite_link); STORE_FLAG(has_bot_commands); END_STORE_FLAGS(); store(version, storer); store(creator_user_id, storer); store(participants, storer); if (has_description) { store(description, storer); } if (has_photo) { store(photo, storer); } if (has_invite_link) { store(invite_link, storer); } if (has_bot_commands) { store(bot_commands, storer); } } template void ChatManager::ChatFull::parse(ParserT &parser) { using td::parse; bool has_description; bool legacy_has_invite_link; bool has_photo; bool has_invite_link; bool has_bot_commands; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_description); PARSE_FLAG(legacy_has_invite_link); PARSE_FLAG(can_set_username); PARSE_FLAG(has_photo); PARSE_FLAG(has_invite_link); PARSE_FLAG(has_bot_commands); END_PARSE_FLAGS(); parse(version, parser); parse(creator_user_id, parser); parse(participants, parser); if (has_description) { parse(description, parser); } if (legacy_has_invite_link) { string legacy_invite_link; parse(legacy_invite_link, parser); } if (has_photo) { parse(photo, parser); } if (has_invite_link) { parse(invite_link, parser); } if (has_bot_commands) { parse(bot_commands, parser); } } template void ChatManager::Channel::store(StorerT &storer) const { using td::store; bool has_photo = photo.small_file_id.is_valid(); bool legacy_has_username = false; bool use_new_rights = true; bool has_participant_count = participant_count != 0; bool have_default_permissions = true; bool has_cache_version = cache_version != 0; bool has_restriction_reasons = !restriction_reasons.empty(); bool legacy_has_active_group_call = false; bool has_usernames = !usernames.is_empty(); bool has_flags2 = true; bool has_max_active_story_id = max_active_story_id.is_valid(); bool has_max_read_story_id = max_read_story_id.is_valid(); bool has_max_active_story_id_next_reload_time = max_active_story_id_next_reload_time > Time::now(); bool has_accent_color_id = accent_color_id.is_valid(); bool has_background_custom_emoji_id = background_custom_emoji_id.is_valid(); bool has_profile_accent_color_id = profile_accent_color_id.is_valid(); bool has_profile_background_custom_emoji_id = profile_background_custom_emoji_id.is_valid(); bool has_boost_level = boost_level != 0; bool has_emoji_status = !emoji_status.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(sign_messages); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(false); STORE_FLAG(is_megagroup); STORE_FLAG(is_verified); STORE_FLAG(has_photo); STORE_FLAG(legacy_has_username); STORE_FLAG(false); STORE_FLAG(use_new_rights); STORE_FLAG(has_participant_count); STORE_FLAG(have_default_permissions); STORE_FLAG(is_scam); STORE_FLAG(has_cache_version); STORE_FLAG(has_linked_channel); STORE_FLAG(has_location); STORE_FLAG(is_slow_mode_enabled); STORE_FLAG(has_restriction_reasons); STORE_FLAG(legacy_has_active_group_call); STORE_FLAG(is_fake); STORE_FLAG(is_gigagroup); STORE_FLAG(noforwards); STORE_FLAG(can_be_deleted); STORE_FLAG(join_to_send); STORE_FLAG(join_request); STORE_FLAG(has_usernames); STORE_FLAG(has_flags2); END_STORE_FLAGS(); if (has_flags2) { BEGIN_STORE_FLAGS(); STORE_FLAG(is_forum); STORE_FLAG(has_max_active_story_id); STORE_FLAG(has_max_read_story_id); STORE_FLAG(has_max_active_story_id_next_reload_time); STORE_FLAG(stories_hidden); STORE_FLAG(has_accent_color_id); STORE_FLAG(has_background_custom_emoji_id); STORE_FLAG(has_profile_accent_color_id); STORE_FLAG(has_profile_background_custom_emoji_id); STORE_FLAG(has_boost_level); STORE_FLAG(has_emoji_status); END_STORE_FLAGS(); } store(status, storer); store(access_hash, storer); store(title, storer); if (has_photo) { store(photo, storer); } store(date, storer); if (has_restriction_reasons) { store(restriction_reasons, storer); } if (has_participant_count) { store(participant_count, storer); } if (is_megagroup) { store(default_permissions, storer); } if (has_cache_version) { store(cache_version, storer); } if (has_usernames) { store(usernames, storer); } if (has_max_active_story_id) { store(max_active_story_id, storer); } if (has_max_read_story_id) { store(max_read_story_id, storer); } if (has_max_active_story_id_next_reload_time) { store_time(max_active_story_id_next_reload_time, storer); } if (has_accent_color_id) { store(accent_color_id, storer); } if (has_background_custom_emoji_id) { store(background_custom_emoji_id, storer); } if (has_profile_accent_color_id) { store(profile_accent_color_id, storer); } if (has_profile_background_custom_emoji_id) { store(profile_background_custom_emoji_id, storer); } if (has_boost_level) { store(boost_level, storer); } if (has_emoji_status) { store(emoji_status, storer); } } template void ChatManager::Channel::parse(ParserT &parser) { using td::parse; bool has_photo; bool legacy_has_username; bool legacy_is_restricted; bool left; bool kicked; bool is_creator; bool can_edit; bool can_moderate; bool anyone_can_invite; bool use_new_rights; bool has_participant_count; bool have_default_permissions; bool has_cache_version; bool has_restriction_reasons; bool legacy_has_active_group_call; bool has_usernames; bool has_flags2; bool has_max_active_story_id = false; bool has_max_read_story_id = false; bool has_max_active_story_id_next_reload_time = false; bool has_accent_color_id = false; bool has_background_custom_emoji_id = false; bool has_profile_accent_color_id = false; bool has_profile_background_custom_emoji_id = false; bool has_boost_level = false; bool has_emoji_status = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(left); PARSE_FLAG(kicked); PARSE_FLAG(anyone_can_invite); PARSE_FLAG(sign_messages); PARSE_FLAG(is_creator); PARSE_FLAG(can_edit); PARSE_FLAG(can_moderate); PARSE_FLAG(is_megagroup); PARSE_FLAG(is_verified); PARSE_FLAG(has_photo); PARSE_FLAG(legacy_has_username); PARSE_FLAG(legacy_is_restricted); PARSE_FLAG(use_new_rights); PARSE_FLAG(has_participant_count); PARSE_FLAG(have_default_permissions); PARSE_FLAG(is_scam); PARSE_FLAG(has_cache_version); PARSE_FLAG(has_linked_channel); PARSE_FLAG(has_location); PARSE_FLAG(is_slow_mode_enabled); PARSE_FLAG(has_restriction_reasons); PARSE_FLAG(legacy_has_active_group_call); PARSE_FLAG(is_fake); PARSE_FLAG(is_gigagroup); PARSE_FLAG(noforwards); PARSE_FLAG(can_be_deleted); PARSE_FLAG(join_to_send); PARSE_FLAG(join_request); PARSE_FLAG(has_usernames); PARSE_FLAG(has_flags2); END_PARSE_FLAGS(); if (has_flags2) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_forum); PARSE_FLAG(has_max_active_story_id); PARSE_FLAG(has_max_read_story_id); PARSE_FLAG(has_max_active_story_id_next_reload_time); PARSE_FLAG(stories_hidden); PARSE_FLAG(has_accent_color_id); PARSE_FLAG(has_background_custom_emoji_id); PARSE_FLAG(has_profile_accent_color_id); PARSE_FLAG(has_profile_background_custom_emoji_id); PARSE_FLAG(has_boost_level); PARSE_FLAG(has_emoji_status); END_PARSE_FLAGS(); } if (use_new_rights) { parse(status, parser); } else { if (kicked) { status = DialogParticipantStatus::Banned(0); } else if (left) { status = DialogParticipantStatus::Left(); } else if (is_creator) { status = DialogParticipantStatus::Creator(true, false, string()); } else if (can_edit || can_moderate) { status = DialogParticipantStatus::ChannelAdministrator(false, is_megagroup); } else { status = DialogParticipantStatus::Member(); } } parse(access_hash, parser); parse(title, parser); if (has_photo) { parse(photo, parser); } if (legacy_has_username) { if (has_usernames) { parser.set_error("Have invalid channel flags"); return; } string username; parse(username, parser); usernames = Usernames(std::move(username), vector>()); } parse(date, parser); if (legacy_is_restricted) { string restriction_reason; parse(restriction_reason, parser); restriction_reasons = get_restriction_reasons(restriction_reason); } else if (has_restriction_reasons) { parse(restriction_reasons, parser); } if (has_participant_count) { parse(participant_count, parser); } if (is_megagroup) { if (have_default_permissions) { parse(default_permissions, parser); } else { default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, anyone_can_invite, false, false, ChannelType::Megagroup); } } if (has_cache_version) { parse(cache_version, parser); } if (has_usernames) { CHECK(!legacy_has_username); parse(usernames, parser); } if (has_max_active_story_id) { parse(max_active_story_id, parser); } if (has_max_read_story_id) { parse(max_read_story_id, parser); } if (has_max_active_story_id_next_reload_time) { parse_time(max_active_story_id_next_reload_time, parser); } if (has_accent_color_id) { parse(accent_color_id, parser); } if (has_background_custom_emoji_id) { parse(background_custom_emoji_id, parser); } if (has_profile_accent_color_id) { parse(profile_accent_color_id, parser); } if (has_profile_background_custom_emoji_id) { parse(profile_background_custom_emoji_id, parser); } if (has_boost_level) { parse(boost_level, parser); } if (has_emoji_status) { parse(emoji_status, parser); } if (!check_utf8(title)) { LOG(ERROR) << "Have invalid title \"" << title << '"'; title.clear(); cache_version = 0; } if (legacy_has_active_group_call) { cache_version = 0; } if (!is_megagroup && status.is_restricted()) { if (status.is_member()) { status = DialogParticipantStatus::Member(); } else { status = DialogParticipantStatus::Left(); } } } template void ChatManager::ChannelFull::store(StorerT &storer) const { using td::store; bool has_description = !description.empty(); bool has_administrator_count = administrator_count != 0; bool has_restricted_count = restricted_count != 0; bool has_banned_count = banned_count != 0; bool legacy_has_invite_link = false; bool has_sticker_set = sticker_set_id.is_valid(); bool has_linked_channel_id = linked_channel_id.is_valid(); bool has_migrated_from_max_message_id = migrated_from_max_message_id.is_valid(); bool has_migrated_from_chat_id = migrated_from_chat_id.is_valid(); bool has_location = !location.empty(); bool has_bot_user_ids = !bot_user_ids.empty(); bool is_slow_mode_enabled = slow_mode_delay != 0; bool is_slow_mode_delay_active = slow_mode_next_send_date != 0; bool has_stats_dc_id = stats_dc_id.is_exact(); bool has_photo = !photo.is_empty(); bool legacy_has_active_group_call_id = false; bool has_invite_link = invite_link.is_valid(); bool has_bot_commands = !bot_commands.empty(); bool has_flags2 = true; bool has_emoji_sticker_set = emoji_sticker_set_id.is_valid(); bool has_boost_count = boost_count != 0; bool has_unrestrict_boost_count = unrestrict_boost_count != 0; bool has_can_have_sponsored_messages = true; BEGIN_STORE_FLAGS(); STORE_FLAG(has_description); STORE_FLAG(has_administrator_count); STORE_FLAG(has_restricted_count); STORE_FLAG(has_banned_count); STORE_FLAG(legacy_has_invite_link); STORE_FLAG(has_sticker_set); STORE_FLAG(has_linked_channel_id); STORE_FLAG(has_migrated_from_max_message_id); STORE_FLAG(has_migrated_from_chat_id); STORE_FLAG(can_get_participants); STORE_FLAG(can_set_username); STORE_FLAG(can_set_sticker_set); STORE_FLAG(false); // legacy_can_view_statistics STORE_FLAG(is_all_history_available); STORE_FLAG(can_set_location); STORE_FLAG(has_location); STORE_FLAG(has_bot_user_ids); STORE_FLAG(is_slow_mode_enabled); STORE_FLAG(is_slow_mode_delay_active); STORE_FLAG(has_stats_dc_id); STORE_FLAG(has_photo); STORE_FLAG(is_can_view_statistics_inited); STORE_FLAG(can_view_statistics); STORE_FLAG(legacy_has_active_group_call_id); STORE_FLAG(has_invite_link); STORE_FLAG(has_bot_commands); STORE_FLAG(can_be_deleted); STORE_FLAG(has_aggressive_anti_spam_enabled); STORE_FLAG(has_hidden_participants); STORE_FLAG(has_flags2); END_STORE_FLAGS(); if (has_flags2) { BEGIN_STORE_FLAGS(); STORE_FLAG(has_pinned_stories); STORE_FLAG(has_emoji_sticker_set); STORE_FLAG(has_boost_count); STORE_FLAG(has_unrestrict_boost_count); STORE_FLAG(can_have_sponsored_messages); STORE_FLAG(can_view_revenue); STORE_FLAG(has_can_have_sponsored_messages); STORE_FLAG(has_paid_media_allowed); END_STORE_FLAGS(); } if (has_description) { store(description, storer); } store(participant_count, storer); if (has_administrator_count) { store(administrator_count, storer); } if (has_restricted_count) { store(restricted_count, storer); } if (has_banned_count) { store(banned_count, storer); } if (has_sticker_set) { store(sticker_set_id, storer); } if (has_linked_channel_id) { store(linked_channel_id, storer); } if (has_location) { store(location, storer); } if (has_bot_user_ids) { store(bot_user_ids, storer); } if (has_migrated_from_max_message_id) { store(migrated_from_max_message_id, storer); } if (has_migrated_from_chat_id) { store(migrated_from_chat_id, storer); } if (is_slow_mode_enabled) { store(slow_mode_delay, storer); } if (is_slow_mode_delay_active) { store(slow_mode_next_send_date, storer); } store_time(expires_at, storer); if (has_stats_dc_id) { store(stats_dc_id.get_raw_id(), storer); } if (has_photo) { store(photo, storer); } if (has_invite_link) { store(invite_link, storer); } if (has_bot_commands) { store(bot_commands, storer); } if (has_emoji_sticker_set) { store(emoji_sticker_set_id, storer); } if (has_boost_count) { store(boost_count, storer); } if (has_unrestrict_boost_count) { store(unrestrict_boost_count, storer); } } template void ChatManager::ChannelFull::parse(ParserT &parser) { using td::parse; bool has_description; bool has_administrator_count; bool has_restricted_count; bool has_banned_count; bool legacy_has_invite_link; bool has_sticker_set; bool has_linked_channel_id; bool has_migrated_from_max_message_id; bool has_migrated_from_chat_id; bool legacy_can_view_statistics; bool has_location; bool has_bot_user_ids; bool is_slow_mode_enabled; bool is_slow_mode_delay_active; bool has_stats_dc_id; bool has_photo; bool legacy_has_active_group_call_id; bool has_invite_link; bool has_bot_commands; bool has_flags2; bool has_emoji_sticker_set = false; bool has_boost_count = false; bool has_unrestrict_boost_count = false; bool has_can_have_sponsored_messages = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_description); PARSE_FLAG(has_administrator_count); PARSE_FLAG(has_restricted_count); PARSE_FLAG(has_banned_count); PARSE_FLAG(legacy_has_invite_link); PARSE_FLAG(has_sticker_set); PARSE_FLAG(has_linked_channel_id); PARSE_FLAG(has_migrated_from_max_message_id); PARSE_FLAG(has_migrated_from_chat_id); PARSE_FLAG(can_get_participants); PARSE_FLAG(can_set_username); PARSE_FLAG(can_set_sticker_set); PARSE_FLAG(legacy_can_view_statistics); PARSE_FLAG(is_all_history_available); PARSE_FLAG(can_set_location); PARSE_FLAG(has_location); PARSE_FLAG(has_bot_user_ids); PARSE_FLAG(is_slow_mode_enabled); PARSE_FLAG(is_slow_mode_delay_active); PARSE_FLAG(has_stats_dc_id); PARSE_FLAG(has_photo); PARSE_FLAG(is_can_view_statistics_inited); PARSE_FLAG(can_view_statistics); PARSE_FLAG(legacy_has_active_group_call_id); PARSE_FLAG(has_invite_link); PARSE_FLAG(has_bot_commands); PARSE_FLAG(can_be_deleted); PARSE_FLAG(has_aggressive_anti_spam_enabled); PARSE_FLAG(has_hidden_participants); PARSE_FLAG(has_flags2); END_PARSE_FLAGS(); if (has_flags2) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_pinned_stories); PARSE_FLAG(has_emoji_sticker_set); PARSE_FLAG(has_boost_count); PARSE_FLAG(has_unrestrict_boost_count); PARSE_FLAG(can_have_sponsored_messages); PARSE_FLAG(can_view_revenue); PARSE_FLAG(has_can_have_sponsored_messages); PARSE_FLAG(has_paid_media_allowed); END_PARSE_FLAGS(); } if (has_description) { parse(description, parser); } parse(participant_count, parser); if (has_administrator_count) { parse(administrator_count, parser); } if (has_restricted_count) { parse(restricted_count, parser); } if (has_banned_count) { parse(banned_count, parser); } if (legacy_has_invite_link) { string legacy_invite_link; parse(legacy_invite_link, parser); } if (has_sticker_set) { parse(sticker_set_id, parser); } if (has_linked_channel_id) { parse(linked_channel_id, parser); } if (has_location) { parse(location, parser); } if (has_bot_user_ids) { parse(bot_user_ids, parser); } if (has_migrated_from_max_message_id) { parse(migrated_from_max_message_id, parser); } if (has_migrated_from_chat_id) { parse(migrated_from_chat_id, parser); } if (is_slow_mode_enabled) { parse(slow_mode_delay, parser); } if (is_slow_mode_delay_active) { parse(slow_mode_next_send_date, parser); } parse_time(expires_at, parser); if (has_stats_dc_id) { stats_dc_id = DcId::create(parser.fetch_int()); } if (has_photo) { parse(photo, parser); } if (legacy_has_active_group_call_id) { InputGroupCallId input_group_call_id; parse(input_group_call_id, parser); } if (has_invite_link) { parse(invite_link, parser); } if (has_bot_commands) { parse(bot_commands, parser); } if (has_emoji_sticker_set) { parse(emoji_sticker_set_id, parser); } if (has_boost_count) { parse(boost_count, parser); } if (has_unrestrict_boost_count) { parse(unrestrict_boost_count, parser); } if (legacy_can_view_statistics) { LOG(DEBUG) << "Ignore legacy can view statistics flag"; } if (!is_can_view_statistics_inited) { can_view_statistics = stats_dc_id.is_exact(); } if (!has_can_have_sponsored_messages) { can_have_sponsored_messages = true; } } tl_object_ptr ChatManager::get_input_channel(ChannelId channel_id) const { const Channel *c = get_channel(channel_id); if (c == nullptr) { if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { return make_tl_object(channel_id.get(), 0); } auto it = channel_messages_.find(channel_id); if (it != channel_messages_.end()) { CHECK(!it->second.empty()); auto message_full_id = *it->second.begin(); return make_tl_object( get_simple_input_peer(message_full_id.get_dialog_id()), message_full_id.get_message_id().get_server_message_id().get(), channel_id.get()); } return nullptr; } return make_tl_object(channel_id.get(), c->access_hash); } bool ChatManager::have_input_peer_chat(ChatId chat_id, AccessRights access_rights) const { return have_input_peer_chat(get_chat(chat_id), access_rights); } bool ChatManager::have_input_peer_chat(const Chat *c, AccessRights access_rights) { if (c == nullptr) { LOG(DEBUG) << "Have no basic group"; return false; } if (access_rights == AccessRights::Know) { return true; } if (access_rights == AccessRights::Read) { return true; } if (c->status.is_left()) { LOG(DEBUG) << "Have left basic group"; return false; } if (access_rights == AccessRights::Write && !c->is_active) { LOG(DEBUG) << "Have inactive basic group"; return false; } return true; } tl_object_ptr ChatManager::get_input_peer_chat(ChatId chat_id, AccessRights access_rights) const { auto c = get_chat(chat_id); if (!have_input_peer_chat(c, access_rights)) { return nullptr; } return make_tl_object(chat_id.get()); } bool ChatManager::have_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const { const Channel *c = get_channel(channel_id); return have_input_peer_channel(c, channel_id, access_rights); } tl_object_ptr ChatManager::get_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const { const Channel *c = get_channel(channel_id); if (!have_input_peer_channel(c, channel_id, access_rights)) { return nullptr; } if (c == nullptr) { if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { return make_tl_object(channel_id.get(), 0); } auto it = channel_messages_.find(channel_id); CHECK(it != channel_messages_.end()); CHECK(!it->second.empty()); auto message_full_id = *it->second.begin(); return make_tl_object( get_simple_input_peer(message_full_id.get_dialog_id()), message_full_id.get_message_id().get_server_message_id().get(), channel_id.get()); } return make_tl_object(channel_id.get(), c->access_hash); } tl_object_ptr ChatManager::get_simple_input_peer(DialogId dialog_id) const { CHECK(dialog_id.get_type() == DialogType::Channel); auto channel_id = dialog_id.get_channel_id(); const Channel *c = get_channel(channel_id); CHECK(c != nullptr); // if (!have_input_peer_channel(c, channel_id, AccessRights::Read)) { // return nullptr; // } return make_tl_object(channel_id.get(), c->access_hash); } bool ChatManager::have_input_peer_channel(const Channel *c, ChannelId channel_id, AccessRights access_rights, bool from_linked) const { if (c == nullptr) { LOG(DEBUG) << "Have no " << channel_id; if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { return true; } if (channel_messages_.count(channel_id) != 0) { return true; } return false; } if (access_rights == AccessRights::Know) { return true; } if (c->status.is_administrator()) { return true; } if (c->status.is_banned()) { LOG(DEBUG) << "Was banned in " << channel_id; return false; } if (c->status.is_member()) { return true; } bool is_public = is_channel_public(c); if (access_rights == AccessRights::Read) { if (is_public) { return true; } if (!from_linked && c->has_linked_channel) { auto linked_channel_id = get_linked_channel_id(channel_id); if (linked_channel_id.is_valid() && have_channel(linked_channel_id)) { if (have_input_peer_channel(get_channel(linked_channel_id), linked_channel_id, access_rights, true)) { return true; } } else { return true; } } if (!from_linked && td_->dialog_invite_link_manager_->have_dialog_access_by_invite_link(DialogId(channel_id))) { return true; } } else { if (!from_linked && c->is_megagroup && !td_->auth_manager_->is_bot() && c->has_linked_channel) { auto linked_channel_id = get_linked_channel_id(channel_id); if (linked_channel_id.is_valid() && (is_public || have_channel(linked_channel_id))) { return is_public || have_input_peer_channel(get_channel(linked_channel_id), linked_channel_id, AccessRights::Read, true); } else { return true; } } } LOG(DEBUG) << "Have no access to " << channel_id; return false; } bool ChatManager::is_chat_received_from_server(ChatId chat_id) const { const auto *c = get_chat(chat_id); return c != nullptr && c->is_received_from_server; } bool ChatManager::is_channel_received_from_server(ChannelId channel_id) const { const auto *c = get_channel(channel_id); return c != nullptr && c->is_received_from_server; } const DialogPhoto *ChatManager::get_chat_dialog_photo(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return nullptr; } return &c->photo; } const DialogPhoto *ChatManager::get_channel_dialog_photo(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { auto min_channel = get_min_channel(channel_id); if (min_channel != nullptr) { return &min_channel->photo_; } return nullptr; } return &c->photo; } int32 ChatManager::get_chat_accent_color_id_object(ChatId chat_id) const { return td_->theme_manager_->get_accent_color_id_object(AccentColorId(chat_id)); } AccentColorId ChatManager::get_channel_accent_color_id(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { auto min_channel = get_min_channel(channel_id); if (min_channel != nullptr && min_channel->accent_color_id_.is_valid()) { return min_channel->accent_color_id_; } return AccentColorId(channel_id); } if (!c->accent_color_id.is_valid()) { return AccentColorId(channel_id); } return c->accent_color_id; } int32 ChatManager::get_channel_accent_color_id_object(ChannelId channel_id) const { return td_->theme_manager_->get_accent_color_id_object(get_channel_accent_color_id(channel_id), AccentColorId(channel_id)); } CustomEmojiId ChatManager::get_chat_background_custom_emoji_id(ChatId chat_id) const { return CustomEmojiId(); } CustomEmojiId ChatManager::get_channel_background_custom_emoji_id(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return CustomEmojiId(); } return c->background_custom_emoji_id; } int32 ChatManager::get_chat_profile_accent_color_id_object(ChatId chat_id) const { return -1; } int32 ChatManager::get_channel_profile_accent_color_id_object(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return -1; } return td_->theme_manager_->get_profile_accent_color_id_object(c->profile_accent_color_id); } CustomEmojiId ChatManager::get_chat_profile_background_custom_emoji_id(ChatId chat_id) const { return CustomEmojiId(); } CustomEmojiId ChatManager::get_channel_profile_background_custom_emoji_id(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return CustomEmojiId(); } return c->profile_background_custom_emoji_id; } string ChatManager::get_chat_title(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return string(); } return c->title; } string ChatManager::get_channel_title(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { auto min_channel = get_min_channel(channel_id); if (min_channel != nullptr) { return min_channel->title_; } return string(); } return c->title; } RestrictedRights ChatManager::get_chat_default_permissions(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, ChannelType::Unknown); } return c->default_permissions; } RestrictedRights ChatManager::get_channel_default_permissions(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, ChannelType::Unknown); } return c->default_permissions; } td_api::object_ptr ChatManager::get_chat_emoji_status_object(ChatId chat_id) const { return nullptr; } td_api::object_ptr ChatManager::get_channel_emoji_status_object(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return nullptr; } return c->last_sent_emoji_status.get_emoji_status_object(); } bool ChatManager::get_chat_has_protected_content(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return false; } return c->noforwards; } bool ChatManager::get_channel_has_protected_content(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return c->noforwards; } bool ChatManager::get_channel_stories_hidden(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return c->stories_hidden; } bool ChatManager::can_poll_channel_active_stories(ChannelId channel_id) const { const Channel *c = get_channel(channel_id); return need_poll_channel_active_stories(c, channel_id) && Time::now() >= c->max_active_story_id_next_reload_time; } bool ChatManager::can_use_premium_custom_emoji_in_channel(ChannelId channel_id) const { if (!is_megagroup_channel(channel_id)) { return false; } auto channel_full = get_channel_full_const(channel_id); return channel_full == nullptr || channel_full->emoji_sticker_set_id.is_valid(); } string ChatManager::get_chat_about(ChatId chat_id) { auto chat_full = get_chat_full_force(chat_id, "get_chat_about"); if (chat_full != nullptr) { return chat_full->description; } return string(); } string ChatManager::get_channel_about(ChannelId channel_id) { auto channel_full = get_channel_full_force(channel_id, false, "get_channel_about"); if (channel_full != nullptr) { return channel_full->description; } return string(); } string ChatManager::get_channel_search_text(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return get_channel_title(channel_id); } return PSTRING() << c->title << ' ' << implode(c->usernames.get_active_usernames()); } string ChatManager::get_channel_first_username(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return string(); } return c->usernames.get_first_username(); } string ChatManager::get_channel_editable_username(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return string(); } return c->usernames.get_editable_username(); } ChannelId ChatManager::get_unsupported_channel_id() { return ChannelId(static_cast(G()->is_test_dc() ? 10304875 : 1535424647)); } void ChatManager::set_chat_description(ChatId chat_id, const string &description, Promise &&promise) { auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH); auto c = get_chat(chat_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (!get_chat_permissions(c).can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to set chat description")); } td_->create_handler(std::move(promise))->send(DialogId(chat_id), new_description); } void ChatManager::set_channel_username(ChannelId channel_id, const string &username, Promise &&promise) { const auto *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!get_channel_status(c).is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights to change supergroup username")); } if (!username.empty() && !is_allowed_username(username)) { return promise.set_error(Status::Error(400, "Username is invalid")); } td_->create_handler(std::move(promise))->send(channel_id, username); } void ChatManager::toggle_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, Promise &&promise) { const auto *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!get_channel_status(c).is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights to change username")); } if (!c->usernames.can_toggle(username)) { return promise.set_error(Status::Error(400, "Wrong username specified")); } td_->create_handler(std::move(promise))->send(channel_id, std::move(username), is_active); } void ChatManager::disable_all_channel_usernames(ChannelId channel_id, Promise &&promise) { const auto *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!get_channel_status(c).is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights to disable usernames")); } td_->create_handler(std::move(promise))->send(channel_id); } void ChatManager::reorder_channel_usernames(ChannelId channel_id, vector &&usernames, Promise &&promise) { const auto *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!get_channel_status(c).is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights to reorder usernames")); } if (!c->usernames.can_reorder_to(usernames)) { return promise.set_error(Status::Error(400, "Invalid username order specified")); } if (usernames.size() <= 1) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise))->send(channel_id, std::move(usernames)); } void ChatManager::on_update_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, Promise &&promise) { auto *c = get_channel(channel_id); CHECK(c != nullptr); if (!c->usernames.can_toggle(username)) { return reload_channel(channel_id, std::move(promise), "on_update_channel_username_is_active"); } on_update_channel_usernames(c, channel_id, c->usernames.toggle(username, is_active)); update_channel(c, channel_id); promise.set_value(Unit()); } void ChatManager::on_deactivate_channel_usernames(ChannelId channel_id, Promise &&promise) { auto *c = get_channel(channel_id); CHECK(c != nullptr); on_update_channel_usernames(c, channel_id, c->usernames.deactivate_all()); update_channel(c, channel_id); promise.set_value(Unit()); } void ChatManager::on_update_channel_active_usernames_order(ChannelId channel_id, vector &&usernames, Promise &&promise) { auto *c = get_channel(channel_id); CHECK(c != nullptr); if (!c->usernames.can_reorder_to(usernames)) { return reload_channel(channel_id, std::move(promise), "on_update_channel_active_usernames_order"); } on_update_channel_usernames(c, channel_id, c->usernames.reorder_to(std::move(usernames))); update_channel(c, channel_id); promise.set_value(Unit()); } void ChatManager::set_channel_accent_color(ChannelId channel_id, AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, Promise &&promise) { if (!accent_color_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid accent color identifier specified")); } const auto *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (c->is_megagroup) { return promise.set_error(Status::Error(400, "Accent color can be changed only in channel chats")); } if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { return promise.set_error(Status::Error(400, "Not enough rights in the channel")); } td_->create_handler(std::move(promise)) ->send(channel_id, false, accent_color_id, background_custom_emoji_id); } void ChatManager::set_channel_profile_accent_color(ChannelId channel_id, AccentColorId profile_accent_color_id, CustomEmojiId profile_background_custom_emoji_id, Promise &&promise) { const auto *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { return promise.set_error(Status::Error(400, "Not enough rights in the chat")); } td_->create_handler(std::move(promise)) ->send(channel_id, true, profile_accent_color_id, profile_background_custom_emoji_id); } void ChatManager::set_channel_emoji_status(ChannelId channel_id, const EmojiStatus &emoji_status, Promise &&promise) { const auto *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { return promise.set_error(Status::Error(400, "Not enough rights in the chat")); } add_recent_emoji_status(td_, emoji_status); td_->create_handler(std::move(promise))->send(channel_id, emoji_status); } void ChatManager::set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!c->is_megagroup) { return promise.set_error(Status::Error(400, "Chat sticker set can be set only for supergroups")); } if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { return promise.set_error(Status::Error(400, "Not enough rights to change supergroup sticker set")); } telegram_api::object_ptr input_sticker_set; if (!sticker_set_id.is_valid()) { input_sticker_set = telegram_api::make_object(); } else { input_sticker_set = td_->stickers_manager_->get_input_sticker_set(sticker_set_id); if (input_sticker_set == nullptr) { return promise.set_error(Status::Error(400, "Sticker set not found")); } } auto channel_full = get_channel_full(channel_id, false, "set_channel_sticker_set"); if (channel_full != nullptr && !channel_full->can_set_sticker_set) { return promise.set_error(Status::Error(400, "Can't set supergroup sticker set")); } td_->create_handler(std::move(promise)) ->send(channel_id, sticker_set_id, std::move(input_sticker_set)); } void ChatManager::set_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!c->is_megagroup) { return promise.set_error(Status::Error(400, "Cuctom emoji sticker set can be set only for supergroups")); } if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { return promise.set_error( Status::Error(400, "Not enough rights to change custom emoji sticker set in the supergroup")); } telegram_api::object_ptr input_sticker_set; if (!sticker_set_id.is_valid()) { input_sticker_set = telegram_api::make_object(); } else { input_sticker_set = td_->stickers_manager_->get_input_sticker_set(sticker_set_id); if (input_sticker_set == nullptr) { return promise.set_error(Status::Error(400, "Sticker set not found")); } } td_->create_handler(std::move(promise)) ->send(channel_id, sticker_set_id, std::move(input_sticker_set)); } void ChatManager::set_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!c->is_megagroup) { return promise.set_error(Status::Error(400, "Unrestrict boost count can be set only for supergroups")); } if (!get_channel_status(c).can_restrict_members()) { return promise.set_error( Status::Error(400, "Not enough rights to change unrestrict boost count set in the supergroup")); } if (unrestrict_boost_count < 0 || unrestrict_boost_count > 8) { return promise.set_error(Status::Error(400, "Invalid new value for the unrestrict boost count specified")); } td_->create_handler(std::move(promise)) ->send(channel_id, unrestrict_boost_count); } void ChatManager::toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (get_channel_type(c) == ChannelType::Megagroup) { return promise.set_error(Status::Error(400, "Message signatures can't be toggled in supergroups")); } if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to toggle channel sign messages")); } td_->create_handler(std::move(promise))->send(channel_id, sign_messages); } void ChatManager::toggle_channel_join_to_send(ChannelId channel_id, bool join_to_send, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) { return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups")); } if (!get_channel_status(c).can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights")); } td_->create_handler(std::move(promise))->send(channel_id, join_to_send); } void ChatManager::toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) { return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups")); } if (!get_channel_status(c).can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights")); } td_->create_handler(std::move(promise))->send(channel_id, join_request); } void ChatManager::toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to toggle all supergroup history availability")); } if (get_channel_type(c) != ChannelType::Megagroup) { return promise.set_error(Status::Error(400, "Message history can be hidden in supergroups only")); } if (c->is_forum && !is_all_history_available) { return promise.set_error(Status::Error(400, "Message history can't be hidden in forum supergroups")); } if (c->has_linked_channel && !is_all_history_available) { return promise.set_error(Status::Error(400, "Message history can't be hidden in discussion supergroups")); } // it can be toggled in public chats, but will not affect them td_->create_handler(std::move(promise))->send(channel_id, is_all_history_available); } void ChatManager::toggle_channel_can_have_sponsored_messages(ChannelId channel_id, bool can_have_sponsored_messages, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!get_channel_status(c).is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights to disable sponsored messages")); } if (get_channel_type(c) != ChannelType::Broadcast) { return promise.set_error(Status::Error(400, "Sponsored messages can be disabled only in channels")); } td_->create_handler(std::move(promise)) ->send(channel_id, can_have_sponsored_messages); } Status ChatManager::can_hide_chat_participants(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return Status::Error(400, "Basic group not found"); } if (!get_chat_permissions(c).is_creator()) { return Status::Error(400, "Not enough rights to hide group members"); } if (c->participant_count < td_->option_manager_->get_option_integer("hidden_members_group_size_min")) { return Status::Error(400, "The basic group is too small"); } return Status::OK(); } Status ChatManager::can_hide_channel_participants(ChannelId channel_id, const ChannelFull *channel_full) const { auto c = get_channel(channel_id); if (c == nullptr) { return Status::Error(400, "Supergroup not found"); } if (!get_channel_status(c).can_restrict_members()) { return Status::Error(400, "Not enough rights to hide group members"); } if (get_channel_type(c) != ChannelType::Megagroup) { return Status::Error(400, "Group members are hidden by default in channels"); } if (channel_full != nullptr && channel_full->has_hidden_participants) { return Status::OK(); } if (c->participant_count > 0 && c->participant_count < td_->option_manager_->get_option_integer("hidden_members_group_size_min")) { return Status::Error(400, "The supergroup is too small"); } return Status::OK(); } void ChatManager::toggle_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, Promise &&promise) { auto channel_full = get_channel_full_force(channel_id, true, "toggle_channel_has_hidden_participants"); TRY_STATUS_PROMISE(promise, can_hide_channel_participants(channel_id, channel_full)); td_->create_handler(std::move(promise))->send(channel_id, has_hidden_participants); } Status ChatManager::can_toggle_chat_aggressive_anti_spam(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return Status::Error(400, "Basic group not found"); } if (!get_chat_permissions(c).is_creator()) { return Status::Error(400, "Not enough rights to enable aggressive anti-spam checks"); } if (c->participant_count < td_->option_manager_->get_option_integer("aggressive_anti_spam_supergroup_member_count_min")) { return Status::Error(400, "The basic group is too small"); } return Status::OK(); } Status ChatManager::can_toggle_channel_aggressive_anti_spam(ChannelId channel_id, const ChannelFull *channel_full) const { auto c = get_channel(channel_id); if (c == nullptr) { return Status::Error(400, "Supergroup not found"); } if (!get_channel_status(c).can_delete_messages()) { return Status::Error(400, "Not enough rights to enable aggressive anti-spam checks"); } if (get_channel_type(c) != ChannelType::Megagroup) { return Status::Error(400, "Aggressive anti-spam checks can be enabled in supergroups only"); } if (c->is_gigagroup) { return Status::Error(400, "Aggressive anti-spam checks can't be enabled in broadcast supergroups"); } if (channel_full != nullptr && channel_full->has_aggressive_anti_spam_enabled) { return Status::OK(); } if (c->has_location || begins_with(c->usernames.get_editable_username(), "translation_")) { return Status::OK(); } if (c->participant_count > 0 && c->participant_count < td_->option_manager_->get_option_integer( "aggressive_anti_spam_supergroup_member_count_min")) { return Status::Error(400, "The supergroup is too small"); } return Status::OK(); } void ChatManager::toggle_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, bool has_aggressive_anti_spam_enabled, Promise &&promise) { auto channel_full = get_channel_full_force(channel_id, true, "toggle_channel_has_aggressive_anti_spam_enabled"); TRY_STATUS_PROMISE(promise, can_toggle_channel_aggressive_anti_spam(channel_id, channel_full)); td_->create_handler(std::move(promise))->send(channel_id, has_aggressive_anti_spam_enabled); } void ChatManager::toggle_channel_is_forum(ChannelId channel_id, bool is_forum, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (c->is_forum == is_forum) { return promise.set_value(Unit()); } if (!get_channel_status(c).is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights to convert the group to a forum")); } if (get_channel_type(c) != ChannelType::Megagroup) { return promise.set_error(Status::Error(400, "Forums can be enabled in supergroups only")); } td_->create_handler(std::move(promise))->send(channel_id, is_forum); } void ChatManager::convert_channel_to_gigagroup(ChannelId channel_id, Promise &&promise) { if (!can_convert_channel_to_gigagroup(channel_id)) { return promise.set_error(Status::Error(400, "Can't convert the chat to a broadcast group")); } td_->dialog_manager_->remove_dialog_suggested_action( SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); td_->create_handler(std::move(promise))->send(channel_id); } void ChatManager::set_channel_description(ChannelId channel_id, const string &description, Promise &&promise) { auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH); auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to set chat description")); } td_->create_handler(std::move(promise))->send(DialogId(channel_id), new_description); } void ChatManager::set_channel_discussion_group(DialogId dialog_id, DialogId discussion_dialog_id, Promise &&promise) { if (!dialog_id.is_valid() && !discussion_dialog_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid chat identifiers specified")); } ChannelId broadcast_channel_id; telegram_api::object_ptr broadcast_input_channel; if (dialog_id.is_valid()) { if (!td_->dialog_manager_->have_dialog_force(dialog_id, "set_channel_discussion_group 1")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (dialog_id.get_type() != DialogType::Channel) { return promise.set_error(Status::Error(400, "Chat is not a channel")); } broadcast_channel_id = dialog_id.get_channel_id(); const Channel *c = get_channel(broadcast_channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (c->is_megagroup) { return promise.set_error(Status::Error(400, "Chat is not a channel")); } if (!c->status.can_change_info_and_settings_as_administrator()) { return promise.set_error(Status::Error(400, "Not enough rights in the channel")); } broadcast_input_channel = get_input_channel(broadcast_channel_id); CHECK(broadcast_input_channel != nullptr); } else { broadcast_input_channel = telegram_api::make_object(); } ChannelId group_channel_id; telegram_api::object_ptr group_input_channel; if (discussion_dialog_id.is_valid()) { if (!td_->dialog_manager_->have_dialog_force(discussion_dialog_id, "set_channel_discussion_group 2")) { return promise.set_error(Status::Error(400, "Discussion chat not found")); } if (discussion_dialog_id.get_type() != DialogType::Channel) { return promise.set_error(Status::Error(400, "Discussion chat is not a supergroup")); } group_channel_id = discussion_dialog_id.get_channel_id(); const Channel *c = get_channel(group_channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Discussion chat info not found")); } if (!c->is_megagroup) { return promise.set_error(Status::Error(400, "Discussion chat is not a supergroup")); } if (!c->status.is_administrator() || !c->status.can_pin_messages()) { return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); } group_input_channel = get_input_channel(group_channel_id); CHECK(group_input_channel != nullptr); } else { group_input_channel = telegram_api::make_object(); } td_->create_handler(std::move(promise)) ->send(broadcast_channel_id, std::move(broadcast_input_channel), group_channel_id, std::move(group_input_channel)); } void ChatManager::set_channel_location(ChannelId channel_id, const DialogLocation &location, Promise &&promise) { if (location.empty()) { return promise.set_error(Status::Error(400, "Invalid chat location specified")); } const Channel *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (!c->is_megagroup) { return promise.set_error(Status::Error(400, "Chat is not a supergroup")); } if (!c->status.is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); } td_->create_handler(std::move(promise))->send(channel_id, location); } void ChatManager::set_channel_slow_mode_delay(DialogId dialog_id, int32 slow_mode_delay, Promise &&promise) { vector allowed_slow_mode_delays{0, 10, 30, 60, 300, 900, 3600}; if (!td::contains(allowed_slow_mode_delays, slow_mode_delay)) { return promise.set_error(Status::Error(400, "Invalid new value for slow mode delay")); } if (!dialog_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid chat identifier specified")); } if (!td_->dialog_manager_->have_dialog_force(dialog_id, "set_channel_slow_mode_delay")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (dialog_id.get_type() != DialogType::Channel) { return promise.set_error(Status::Error(400, "Chat is not a supergroup")); } auto channel_id = dialog_id.get_channel_id(); const Channel *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (!c->is_megagroup) { return promise.set_error(Status::Error(400, "Chat is not a supergroup")); } if (!get_channel_status(c).can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); } td_->create_handler(std::move(promise))->send(channel_id, slow_mode_delay); } void ChatManager::get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics, Promise &&promise) { if (!dialog_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid chat identifier specified")); } if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_statistics_dc_id")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (dialog_id.get_type() != DialogType::Channel) { return promise.set_error(Status::Error(400, "Chat is not a channel")); } auto channel_id = dialog_id.get_channel_id(); const Channel *c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat info not found")); } auto channel_full = get_channel_full_force(channel_id, false, "get_channel_statistics_dc_id"); if (channel_full == nullptr || !channel_full->stats_dc_id.is_exact() || (for_full_statistics && !channel_full->can_view_statistics)) { auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, for_full_statistics, promise = std::move(promise)](Result result) mutable { send_closure(actor_id, &ChatManager::get_channel_statistics_dc_id_impl, channel_id, for_full_statistics, std::move(promise)); }); send_get_channel_full_query(channel_full, channel_id, std::move(query_promise), "get_channel_statistics_dc_id"); return; } promise.set_value(DcId(channel_full->stats_dc_id)); } void ChatManager::get_channel_statistics_dc_id_impl(ChannelId channel_id, bool for_full_statistics, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto channel_full = get_channel_full(channel_id, false, "get_channel_statistics_dc_id_impl"); if (channel_full == nullptr) { return promise.set_error(Status::Error(400, "Chat full info not found")); } if (!channel_full->stats_dc_id.is_exact() || (for_full_statistics && !channel_full->can_view_statistics)) { return promise.set_error(Status::Error(400, "Chat statistics are not available")); } promise.set_value(DcId(channel_full->stats_dc_id)); } bool ChatManager::can_get_channel_message_statistics(ChannelId channel_id) const { CHECK(!td_->auth_manager_->is_bot()); const Channel *c = get_channel(channel_id); if (c == nullptr || c->is_megagroup) { return false; } auto channel_full = get_channel_full(channel_id); if (channel_full != nullptr) { return channel_full->stats_dc_id.is_exact(); } return c->status.can_post_messages(); } bool ChatManager::can_get_channel_story_statistics(ChannelId channel_id) const { CHECK(!td_->auth_manager_->is_bot()); const Channel *c = get_channel(channel_id); if (c == nullptr || c->is_megagroup) { return false; } auto channel_full = get_channel_full(channel_id); if (channel_full != nullptr) { return channel_full->stats_dc_id.is_exact(); } return c->status.can_post_messages(); } bool ChatManager::can_convert_channel_to_gigagroup(ChannelId channel_id) const { const Channel *c = get_channel(channel_id); return c == nullptr || get_channel_type(c) != ChannelType::Megagroup || !get_channel_status(c).is_creator() || c->is_gigagroup || c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, ChannelType::Unknown); } void ChatManager::report_channel_spam(ChannelId channel_id, const vector &message_ids, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!c->is_megagroup) { return promise.set_error(Status::Error(400, "Spam can be reported only in supergroups")); } if (!c->status.is_administrator()) { return promise.set_error(Status::Error(400, "Spam can be reported only by chat administrators")); } FlatHashMap, DialogIdHash> server_message_ids; for (auto &message_id : message_ids) { if (message_id.is_valid_scheduled()) { return promise.set_error(Status::Error(400, "Can't report scheduled messages")); } if (!message_id.is_valid()) { return promise.set_error(Status::Error(400, "Message not found")); } if (!message_id.is_server()) { continue; } auto sender_dialog_id = td_->messages_manager_->get_dialog_message_sender({DialogId(channel_id), message_id}); CHECK(sender_dialog_id.get_type() != DialogType::SecretChat); if (sender_dialog_id.is_valid() && sender_dialog_id != td_->dialog_manager_->get_my_dialog_id() && td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { server_message_ids[sender_dialog_id].push_back(message_id); } } if (server_message_ids.empty()) { return promise.set_value(Unit()); } MultiPromiseActorSafe mpas{"ReportSupergroupSpamMultiPromiseActor"}; mpas.add_promise(std::move(promise)); auto lock_promise = mpas.get_promise(); for (auto &it : server_message_ids) { td_->create_handler(mpas.get_promise())->send(channel_id, it.first, it.second); } lock_promise.set_value(Unit()); } void ChatManager::report_channel_anti_spam_false_positive(ChannelId channel_id, MessageId message_id, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!c->is_megagroup) { return promise.set_error(Status::Error(400, "The chat is not a supergroup")); } if (!c->status.is_administrator()) { return promise.set_error( Status::Error(400, "Anti-spam checks false positives can be reported only by chat administrators")); } if (!message_id.is_valid() || !message_id.is_server()) { return promise.set_error(Status::Error(400, "Invalid message identifier specified")); } td_->create_handler(std::move(promise))->send(channel_id, message_id); } void ChatManager::delete_chat(ChatId chat_id, Promise &&promise) { auto c = get_chat(chat_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (!get_chat_status(c).is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights to delete the chat")); } if (!c->is_active) { return promise.set_error(Status::Error(400, "Chat is already deactivated")); } td_->create_handler(std::move(promise))->send(chat_id); } void ChatManager::delete_channel(ChannelId channel_id, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (!get_channel_can_be_deleted(c)) { return promise.set_error(Status::Error(400, "The chat can't be deleted")); } td_->create_handler(std::move(promise))->send(channel_id); } vector ChatManager::get_channel_ids(vector> &&chats, const char *source) { vector channel_ids; for (auto &chat : chats) { auto channel_id = get_channel_id(chat); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << " in " << to_string(chat); continue; } on_get_chat(std::move(chat), source); if (have_channel(channel_id)) { channel_ids.push_back(channel_id); } } return channel_ids; } vector ChatManager::get_dialog_ids(vector> &&chats, const char *source) { vector dialog_ids; for (auto &chat : chats) { auto channel_id = get_channel_id(chat); if (!channel_id.is_valid()) { auto chat_id = get_chat_id(chat); if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid chat from " << source << " in " << to_string(chat); } else { dialog_ids.push_back(DialogId(chat_id)); } } else { dialog_ids.push_back(DialogId(channel_id)); } on_get_chat(std::move(chat), source); } return dialog_ids; } void ChatManager::return_created_public_dialogs(Promise> &&promise, const vector &channel_ids) { if (!promise) { return; } auto total_count = narrow_cast(channel_ids.size()); promise.set_value(td_api::make_object( total_count, transform(channel_ids, [](ChannelId channel_id) { return DialogId(channel_id).get(); }))); } bool ChatManager::is_suitable_created_public_channel(PublicDialogType type, const Channel *c) { if (c == nullptr || !c->status.is_creator()) { return false; } switch (type) { case PublicDialogType::HasUsername: return c->usernames.has_editable_username(); case PublicDialogType::IsLocationBased: return c->has_location; case PublicDialogType::ForPersonalDialog: return !c->is_megagroup && c->usernames.has_first_username(); default: UNREACHABLE(); return false; } } void ChatManager::get_created_public_dialogs(PublicDialogType type, Promise> &&promise, bool from_binlog) { auto index = static_cast(type); if (created_public_channels_inited_[index]) { return return_created_public_dialogs(std::move(promise), created_public_channels_[index]); } if (get_created_public_channels_queries_[index].empty() && G()->use_message_database()) { auto pmc_key = PSTRING() << "public_channels" << index; auto str = G()->td_db()->get_binlog_pmc()->get(pmc_key); if (!str.empty()) { auto r_channel_ids = transform(full_split(Slice(str), ','), [](Slice str) -> Result { TRY_RESULT(channel_id_int, to_integer_safe(str)); ChannelId channel_id(channel_id_int); if (!channel_id.is_valid()) { return Status::Error("Have invalid channel ID"); } return channel_id; }); if (any_of(r_channel_ids, [](const auto &r_channel_id) { return r_channel_id.is_error(); })) { LOG(ERROR) << "Can't parse " << str; G()->td_db()->get_binlog_pmc()->erase(pmc_key); } else { Dependencies dependencies; vector channel_ids; for (auto &r_channel_id : r_channel_ids) { auto channel_id = r_channel_id.move_as_ok(); dependencies.add_dialog_and_dependencies(DialogId(channel_id)); channel_ids.push_back(channel_id); } if (!dependencies.resolve_force(td_, "get_created_public_dialogs")) { G()->td_db()->get_binlog_pmc()->erase(pmc_key); } else { for (auto channel_id : channel_ids) { if (is_suitable_created_public_channel(type, get_channel(channel_id))) { created_public_channels_[index].push_back(channel_id); } } created_public_channels_inited_[index] = true; if (from_binlog) { return_created_public_dialogs(std::move(promise), created_public_channels_[index]); promise = {}; } } } } } reload_created_public_dialogs(type, std::move(promise)); } void ChatManager::reload_created_public_dialogs(PublicDialogType type, Promise> &&promise) { auto index = static_cast(type); get_created_public_channels_queries_[index].push_back(std::move(promise)); if (get_created_public_channels_queries_[index].size() == 1) { auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), type](Result &&result) { send_closure(actor_id, &ChatManager::finish_get_created_public_dialogs, type, std::move(result)); }); td_->create_handler(std::move(query_promise))->send(type, false); } } void ChatManager::finish_get_created_public_dialogs(PublicDialogType type, Result &&result) { G()->ignore_result_if_closing(result); auto index = static_cast(type); auto promises = std::move(get_created_public_channels_queries_[index]); reset_to_empty(get_created_public_channels_queries_[index]); if (result.is_error()) { return fail_promises(promises, result.move_as_error()); } CHECK(created_public_channels_inited_[index]); for (auto &promise : promises) { return_created_public_dialogs(std::move(promise), created_public_channels_[index]); } } void ChatManager::update_created_public_channels(Channel *c, ChannelId channel_id) { for (auto type : {PublicDialogType::HasUsername, PublicDialogType::IsLocationBased, PublicDialogType::ForPersonalDialog}) { auto index = static_cast(type); if (!created_public_channels_inited_[index]) { continue; } bool was_changed = false; if (!is_suitable_created_public_channel(type, c)) { was_changed = td::remove(created_public_channels_[index], channel_id); } else { if (!td::contains(created_public_channels_[index], channel_id)) { created_public_channels_[index].push_back(channel_id); was_changed = true; } } if (was_changed) { save_created_public_channels(type); reload_created_public_dialogs(type, Promise>()); } } } void ChatManager::on_get_created_public_channels(PublicDialogType type, vector> &&chats) { auto index = static_cast(type); auto channel_ids = get_channel_ids(std::move(chats), "on_get_created_public_channels"); if (created_public_channels_inited_[index] && created_public_channels_[index] == channel_ids) { return; } created_public_channels_[index].clear(); for (auto channel_id : channel_ids) { td_->dialog_manager_->force_create_dialog(DialogId(channel_id), "on_get_created_public_channels"); if (is_suitable_created_public_channel(type, get_channel(channel_id))) { created_public_channels_[index].push_back(channel_id); } } created_public_channels_inited_[index] = true; save_created_public_channels(type); } void ChatManager::save_created_public_channels(PublicDialogType type) { auto index = static_cast(type); CHECK(created_public_channels_inited_[index]); if (G()->use_message_database()) { G()->td_db()->get_binlog_pmc()->set( PSTRING() << "public_channels" << index, implode( transform(created_public_channels_[index], [](auto channel_id) { return PSTRING() << channel_id.get(); }), ',')); } } void ChatManager::check_created_public_dialogs_limit(PublicDialogType type, Promise &&promise) { td_->create_handler(std::move(promise))->send(type, true); } bool ChatManager::are_created_public_broadcasts_inited() const { return created_public_channels_inited_[2]; } const vector &ChatManager::get_created_public_broadcasts() const { return created_public_channels_[2]; } vector ChatManager::get_dialogs_for_discussion(Promise &&promise) { if (dialogs_for_discussion_inited_) { promise.set_value(Unit()); return transform(dialogs_for_discussion_, [&](DialogId dialog_id) { td_->dialog_manager_->force_create_dialog(dialog_id, "get_dialogs_for_discussion"); return dialog_id; }); } td_->create_handler(std::move(promise))->send(); return {}; } void ChatManager::on_get_dialogs_for_discussion(vector> &&chats) { dialogs_for_discussion_inited_ = true; dialogs_for_discussion_ = get_dialog_ids(std::move(chats), "on_get_dialogs_for_discussion"); } void ChatManager::update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable) { if (!dialogs_for_discussion_inited_) { return; } if (is_suitable) { if (!td::contains(dialogs_for_discussion_, dialog_id)) { LOG(DEBUG) << "Add " << dialog_id << " to list of suitable discussion chats"; dialogs_for_discussion_.insert(dialogs_for_discussion_.begin(), dialog_id); } } else { if (td::remove(dialogs_for_discussion_, dialog_id)) { LOG(DEBUG) << "Remove " << dialog_id << " from list of suitable discussion chats"; } } } vector ChatManager::get_inactive_channels(Promise &&promise) { if (inactive_channel_ids_inited_) { promise.set_value(Unit()); return transform(inactive_channel_ids_, [&](ChannelId channel_id) { return DialogId(channel_id); }); } td_->create_handler(std::move(promise))->send(); return {}; } void ChatManager::on_get_inactive_channels(vector> &&chats, Promise &&promise) { auto channel_ids = get_channel_ids(std::move(chats), "on_get_inactive_channels"); MultiPromiseActorSafe mpas{"GetInactiveChannelsMultiPromiseActor"}; mpas.add_promise( PromiseCreator::lambda([actor_id = actor_id(this), channel_ids, promise = std::move(promise)](Unit) mutable { send_closure(actor_id, &ChatManager::on_create_inactive_channels, std::move(channel_ids), std::move(promise)); })); mpas.set_ignore_errors(true); auto lock_promise = mpas.get_promise(); for (auto channel_id : channel_ids) { td_->messages_manager_->create_dialog(DialogId(channel_id), false, mpas.get_promise()); } lock_promise.set_value(Unit()); } void ChatManager::on_create_inactive_channels(vector &&channel_ids, Promise &&promise) { inactive_channel_ids_inited_ = true; inactive_channel_ids_ = std::move(channel_ids); promise.set_value(Unit()); } void ChatManager::remove_inactive_channel(ChannelId channel_id) { if (inactive_channel_ids_inited_ && td::remove(inactive_channel_ids_, channel_id)) { LOG(DEBUG) << "Remove " << channel_id << " from list of inactive channels"; } } void ChatManager::register_message_channels(MessageFullId message_full_id, vector channel_ids) { auto dialog_id = message_full_id.get_dialog_id(); CHECK(dialog_id.get_type() == DialogType::Channel); if (!have_channel(dialog_id.get_channel_id())) { return; } for (auto channel_id : channel_ids) { CHECK(channel_id.is_valid()); if (!have_channel(channel_id)) { channel_messages_[channel_id].insert(message_full_id); // get info about the channel get_channel_queries_.add_query(channel_id.get(), Promise(), "register_message_channels"); } } } void ChatManager::unregister_message_channels(MessageFullId message_full_id, vector channel_ids) { if (channel_messages_.empty()) { // fast path return; } for (auto channel_id : channel_ids) { auto it = channel_messages_.find(channel_id); if (it != channel_messages_.end()) { it->second.erase(message_full_id); if (it->second.empty()) { channel_messages_.erase(it); } } } } ChatId ChatManager::get_chat_id(const tl_object_ptr &chat) { CHECK(chat != nullptr); switch (chat->get_id()) { case telegram_api::chatEmpty::ID: return ChatId(static_cast(chat.get())->id_); case telegram_api::chat::ID: return ChatId(static_cast(chat.get())->id_); case telegram_api::chatForbidden::ID: return ChatId(static_cast(chat.get())->id_); default: return ChatId(); } } ChannelId ChatManager::get_channel_id(const tl_object_ptr &chat) { CHECK(chat != nullptr); switch (chat->get_id()) { case telegram_api::channel::ID: return ChannelId(static_cast(chat.get())->id_); case telegram_api::channelForbidden::ID: return ChannelId(static_cast(chat.get())->id_); default: return ChannelId(); } } DialogId ChatManager::get_dialog_id(const tl_object_ptr &chat) { auto channel_id = get_channel_id(chat); if (channel_id.is_valid()) { return DialogId(channel_id); } return DialogId(get_chat_id(chat)); } class ChatManager::ChatLogEvent { public: ChatId chat_id; const Chat *c_in = nullptr; unique_ptr c_out; ChatLogEvent() = default; ChatLogEvent(ChatId chat_id, const Chat *c) : chat_id(chat_id), c_in(c) { } template void store(StorerT &storer) const { td::store(chat_id, storer); td::store(*c_in, storer); } template void parse(ParserT &parser) { td::parse(chat_id, parser); td::parse(c_out, parser); } }; void ChatManager::save_chat(Chat *c, ChatId chat_id, bool from_binlog) { if (!G()->use_chat_info_database()) { return; } CHECK(c != nullptr); if (!c->is_saved) { if (!from_binlog) { auto log_event = ChatLogEvent(chat_id, c); auto storer = get_log_event_storer(log_event); if (c->log_event_id == 0) { c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Chats, storer); } else { binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Chats, storer); } } save_chat_to_database(c, chat_id); return; } } void ChatManager::on_binlog_chat_event(BinlogEvent &&event) { if (!G()->use_chat_info_database()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } ChatLogEvent log_event; if (log_event_parse(log_event, event.get_data()).is_error()) { LOG(ERROR) << "Failed to load a basic group from binlog"; binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } auto chat_id = log_event.chat_id; if (have_chat(chat_id) || !chat_id.is_valid()) { LOG(ERROR) << "Skip adding already added " << chat_id; binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } LOG(INFO) << "Add " << chat_id << " from binlog"; chats_.set(chat_id, std::move(log_event.c_out)); Chat *c = get_chat(chat_id); CHECK(c != nullptr); c->log_event_id = event.id_; update_chat(c, chat_id, true, false); } string ChatManager::get_chat_database_key(ChatId chat_id) { return PSTRING() << "gr" << chat_id.get(); } string ChatManager::get_chat_database_value(const Chat *c) { return log_event_store(*c).as_slice().str(); } void ChatManager::save_chat_to_database(Chat *c, ChatId chat_id) { CHECK(c != nullptr); if (c->is_being_saved) { return; } if (loaded_from_database_chats_.count(chat_id)) { save_chat_to_database_impl(c, chat_id, get_chat_database_value(c)); return; } if (load_chat_from_database_queries_.count(chat_id) != 0) { return; } load_chat_from_database_impl(chat_id, Auto()); } void ChatManager::save_chat_to_database_impl(Chat *c, ChatId chat_id, string value) { CHECK(c != nullptr); CHECK(load_chat_from_database_queries_.count(chat_id) == 0); CHECK(!c->is_being_saved); c->is_being_saved = true; c->is_saved = true; LOG(INFO) << "Trying to save to database " << chat_id; G()->td_db()->get_sqlite_pmc()->set( get_chat_database_key(chat_id), std::move(value), PromiseCreator::lambda([chat_id](Result<> result) { send_closure(G()->chat_manager(), &ChatManager::on_save_chat_to_database, chat_id, result.is_ok()); })); } void ChatManager::on_save_chat_to_database(ChatId chat_id, bool success) { if (G()->close_flag()) { return; } Chat *c = get_chat(chat_id); CHECK(c != nullptr); CHECK(c->is_being_saved); CHECK(load_chat_from_database_queries_.count(chat_id) == 0); c->is_being_saved = false; if (!success) { LOG(ERROR) << "Failed to save " << chat_id << " to database"; c->is_saved = false; } else { LOG(INFO) << "Successfully saved " << chat_id << " to database"; } if (c->is_saved) { if (c->log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); c->log_event_id = 0; } } else { save_chat(c, chat_id, c->log_event_id != 0); } } void ChatManager::load_chat_from_database(Chat *c, ChatId chat_id, Promise promise) { if (loaded_from_database_chats_.count(chat_id)) { promise.set_value(Unit()); return; } CHECK(c == nullptr || !c->is_being_saved); load_chat_from_database_impl(chat_id, std::move(promise)); } void ChatManager::load_chat_from_database_impl(ChatId chat_id, Promise promise) { LOG(INFO) << "Load " << chat_id << " from database"; auto &load_chat_queries = load_chat_from_database_queries_[chat_id]; load_chat_queries.push_back(std::move(promise)); if (load_chat_queries.size() == 1u) { G()->td_db()->get_sqlite_pmc()->get(get_chat_database_key(chat_id), PromiseCreator::lambda([chat_id](string value) { send_closure(G()->chat_manager(), &ChatManager::on_load_chat_from_database, chat_id, std::move(value), false); })); } } void ChatManager::on_load_chat_from_database(ChatId chat_id, string value, bool force) { if (G()->close_flag() && !force) { // the chat is in Binlog and will be saved after restart return; } CHECK(chat_id.is_valid()); if (!loaded_from_database_chats_.insert(chat_id).second) { return; } auto it = load_chat_from_database_queries_.find(chat_id); vector> promises; if (it != load_chat_from_database_queries_.end()) { promises = std::move(it->second); CHECK(!promises.empty()); load_chat_from_database_queries_.erase(it); } LOG(INFO) << "Successfully loaded " << chat_id << " of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_chat_database_key(chat_id), Auto()); // return; Chat *c = get_chat(chat_id); if (c == nullptr) { if (!value.empty()) { c = add_chat(chat_id); if (log_event_parse(*c, value).is_error()) { LOG(ERROR) << "Failed to load " << chat_id << " from database"; chats_.erase(chat_id); } else { c->is_saved = true; update_chat(c, chat_id, true, true); } } } else { CHECK(!c->is_saved); // chat can't be saved before load completes CHECK(!c->is_being_saved); auto new_value = get_chat_database_value(c); if (value != new_value) { save_chat_to_database_impl(c, chat_id, std::move(new_value)); } else if (c->log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); c->log_event_id = 0; } } if (c != nullptr && c->migrated_to_channel_id.is_valid() && !have_channel_force(c->migrated_to_channel_id, "on_load_chat_from_database")) { LOG(ERROR) << "Can't find " << c->migrated_to_channel_id << " from " << chat_id; } set_promises(promises); } bool ChatManager::have_chat_force(ChatId chat_id, const char *source) { return get_chat_force(chat_id, source) != nullptr; } ChatManager::Chat *ChatManager::get_chat_force(ChatId chat_id, const char *source) { if (!chat_id.is_valid()) { return nullptr; } Chat *c = get_chat(chat_id); if (c != nullptr) { if (c->migrated_to_channel_id.is_valid() && !have_channel_force(c->migrated_to_channel_id, source)) { LOG(ERROR) << "Can't find " << c->migrated_to_channel_id << " from " << chat_id << " from " << source; } return c; } if (!G()->use_chat_info_database()) { return nullptr; } if (loaded_from_database_chats_.count(chat_id)) { return nullptr; } LOG(INFO) << "Trying to load " << chat_id << " from database from " << source; on_load_chat_from_database(chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_database_key(chat_id)), true); return get_chat(chat_id); } class ChatManager::ChannelLogEvent { public: ChannelId channel_id; const Channel *c_in = nullptr; unique_ptr c_out; ChannelLogEvent() = default; ChannelLogEvent(ChannelId channel_id, const Channel *c) : channel_id(channel_id), c_in(c) { } template void store(StorerT &storer) const { td::store(channel_id, storer); td::store(*c_in, storer); } template void parse(ParserT &parser) { td::parse(channel_id, parser); td::parse(c_out, parser); } }; void ChatManager::save_channel(Channel *c, ChannelId channel_id, bool from_binlog) { if (!G()->use_chat_info_database()) { return; } CHECK(c != nullptr); if (!c->is_saved) { if (!from_binlog) { auto log_event = ChannelLogEvent(channel_id, c); auto storer = get_log_event_storer(log_event); if (c->log_event_id == 0) { c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Channels, storer); } else { binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Channels, storer); } } save_channel_to_database(c, channel_id); return; } } void ChatManager::on_binlog_channel_event(BinlogEvent &&event) { if (!G()->use_chat_info_database()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } ChannelLogEvent log_event; if (log_event_parse(log_event, event.get_data()).is_error()) { LOG(ERROR) << "Failed to load a supergroup from binlog"; binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } auto channel_id = log_event.channel_id; if (have_channel(channel_id) || !channel_id.is_valid()) { LOG(ERROR) << "Skip adding already added " << channel_id; binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } LOG(INFO) << "Add " << channel_id << " from binlog"; channels_.set(channel_id, std::move(log_event.c_out)); Channel *c = get_channel(channel_id); CHECK(c != nullptr); c->log_event_id = event.id_; update_channel(c, channel_id, true, false); } string ChatManager::get_channel_database_key(ChannelId channel_id) { return PSTRING() << "ch" << channel_id.get(); } string ChatManager::get_channel_database_value(const Channel *c) { return log_event_store(*c).as_slice().str(); } void ChatManager::save_channel_to_database(Channel *c, ChannelId channel_id) { CHECK(c != nullptr); if (c->is_being_saved) { return; } if (loaded_from_database_channels_.count(channel_id)) { save_channel_to_database_impl(c, channel_id, get_channel_database_value(c)); return; } if (load_channel_from_database_queries_.count(channel_id) != 0) { return; } load_channel_from_database_impl(channel_id, Auto()); } void ChatManager::save_channel_to_database_impl(Channel *c, ChannelId channel_id, string value) { CHECK(c != nullptr); CHECK(load_channel_from_database_queries_.count(channel_id) == 0); CHECK(!c->is_being_saved); c->is_being_saved = true; c->is_saved = true; LOG(INFO) << "Trying to save to database " << channel_id; G()->td_db()->get_sqlite_pmc()->set( get_channel_database_key(channel_id), std::move(value), PromiseCreator::lambda([channel_id](Result<> result) { send_closure(G()->chat_manager(), &ChatManager::on_save_channel_to_database, channel_id, result.is_ok()); })); } void ChatManager::on_save_channel_to_database(ChannelId channel_id, bool success) { if (G()->close_flag()) { return; } Channel *c = get_channel(channel_id); CHECK(c != nullptr); CHECK(c->is_being_saved); CHECK(load_channel_from_database_queries_.count(channel_id) == 0); c->is_being_saved = false; if (!success) { LOG(ERROR) << "Failed to save " << channel_id << " to database"; c->is_saved = false; } else { LOG(INFO) << "Successfully saved " << channel_id << " to database"; } if (c->is_saved) { if (c->log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); c->log_event_id = 0; } } else { save_channel(c, channel_id, c->log_event_id != 0); } } void ChatManager::load_channel_from_database(Channel *c, ChannelId channel_id, Promise promise) { if (loaded_from_database_channels_.count(channel_id)) { promise.set_value(Unit()); return; } CHECK(c == nullptr || !c->is_being_saved); load_channel_from_database_impl(channel_id, std::move(promise)); } void ChatManager::load_channel_from_database_impl(ChannelId channel_id, Promise promise) { LOG(INFO) << "Load " << channel_id << " from database"; auto &load_channel_queries = load_channel_from_database_queries_[channel_id]; load_channel_queries.push_back(std::move(promise)); if (load_channel_queries.size() == 1u) { G()->td_db()->get_sqlite_pmc()->get(get_channel_database_key(channel_id), PromiseCreator::lambda([channel_id](string value) { send_closure(G()->chat_manager(), &ChatManager::on_load_channel_from_database, channel_id, std::move(value), false); })); } } void ChatManager::on_load_channel_from_database(ChannelId channel_id, string value, bool force) { if (G()->close_flag() && !force) { // the channel is in Binlog and will be saved after restart return; } CHECK(channel_id.is_valid()); if (!loaded_from_database_channels_.insert(channel_id).second) { return; } auto it = load_channel_from_database_queries_.find(channel_id); vector> promises; if (it != load_channel_from_database_queries_.end()) { promises = std::move(it->second); CHECK(!promises.empty()); load_channel_from_database_queries_.erase(it); } LOG(INFO) << "Successfully loaded " << channel_id << " of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_channel_database_key(channel_id), Auto()); // return; Channel *c = get_channel(channel_id); if (c == nullptr) { if (!value.empty()) { c = add_channel(channel_id, "on_load_channel_from_database"); if (log_event_parse(*c, value).is_error()) { LOG(ERROR) << "Failed to load " << channel_id << " from database"; channels_.erase(channel_id); } else { c->is_saved = true; update_channel(c, channel_id, true, true); } } } else { CHECK(!c->is_saved); // channel can't be saved before load completes CHECK(!c->is_being_saved); if (!value.empty()) { Channel temp_c; if (log_event_parse(temp_c, value).is_ok()) { if (c->participant_count == 0 && temp_c.participant_count != 0) { c->participant_count = temp_c.participant_count; CHECK(c->is_update_supergroup_sent); send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, c)); } c->status.update_restrictions(); temp_c.status.update_restrictions(); if (temp_c.status != c->status) { on_channel_status_changed(c, channel_id, temp_c.status, c->status); CHECK(!c->is_being_saved); } if (temp_c.usernames != c->usernames) { on_channel_usernames_changed(c, channel_id, temp_c.usernames, c->usernames); CHECK(!c->is_being_saved); } } } auto new_value = get_channel_database_value(c); if (value != new_value) { save_channel_to_database_impl(c, channel_id, std::move(new_value)); } else if (c->log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); c->log_event_id = 0; } } set_promises(promises); } bool ChatManager::have_channel_force(ChannelId channel_id, const char *source) { return get_channel_force(channel_id, source) != nullptr; } ChatManager::Channel *ChatManager::get_channel_force(ChannelId channel_id, const char *source) { if (!channel_id.is_valid()) { return nullptr; } Channel *c = get_channel(channel_id); if (c != nullptr) { return c; } if (!G()->use_chat_info_database()) { return nullptr; } if (loaded_from_database_channels_.count(channel_id)) { return nullptr; } LOG(INFO) << "Trying to load " << channel_id << " from database from " << source; on_load_channel_from_database(channel_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_database_key(channel_id)), true); return get_channel(channel_id); } void ChatManager::save_chat_full(const ChatFull *chat_full, ChatId chat_id) { if (!G()->use_chat_info_database()) { return; } LOG(INFO) << "Trying to save to database full " << chat_id; CHECK(chat_full != nullptr); G()->td_db()->get_sqlite_pmc()->set(get_chat_full_database_key(chat_id), get_chat_full_database_value(chat_full), Auto()); } string ChatManager::get_chat_full_database_key(ChatId chat_id) { return PSTRING() << "grf" << chat_id.get(); } string ChatManager::get_chat_full_database_value(const ChatFull *chat_full) { return log_event_store(*chat_full).as_slice().str(); } void ChatManager::on_load_chat_full_from_database(ChatId chat_id, string value) { LOG(INFO) << "Successfully loaded full " << chat_id << " of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); // return; if (get_chat_full(chat_id) != nullptr || value.empty()) { return; } ChatFull *chat_full = add_chat_full(chat_id); auto status = log_event_parse(*chat_full, value); if (status.is_error()) { // can't happen unless database is broken LOG(ERROR) << "Repair broken full " << chat_id << ' ' << format::as_hex_dump<4>(Slice(value)); // just clean all known data about the chat and pretend that there was nothing in the database chats_full_.erase(chat_id); G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); return; } Dependencies dependencies; dependencies.add(chat_id); dependencies.add(chat_full->creator_user_id); for (auto &participant : chat_full->participants) { dependencies.add_message_sender_dependencies(participant.dialog_id_); dependencies.add(participant.inviter_user_id_); } dependencies.add(chat_full->invite_link.get_creator_user_id()); if (!dependencies.resolve_force(td_, "on_load_chat_full_from_database")) { chats_full_.erase(chat_id); G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); return; } Chat *c = get_chat(chat_id); CHECK(c != nullptr); bool need_invite_link = c->is_active && c->status.can_manage_invite_links(); bool have_invite_link = chat_full->invite_link.is_valid(); if (need_invite_link != have_invite_link) { if (need_invite_link) { // ignore ChatFull without invite link chats_full_.erase(chat_id); return; } else { chat_full->invite_link = DialogInviteLink(); } } if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { chat_full->photo = Photo(); if (c->photo.small_file_id.is_valid()) { reload_chat_full(chat_id, Auto(), "on_load_chat_full_from_database"); } } auto photo = std::move(chat_full->photo); chat_full->photo = Photo(); on_update_chat_full_photo(chat_full, chat_id, std::move(photo)); td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, false); chat_full->is_update_chat_full_sent = true; update_chat_full(chat_full, chat_id, "on_load_chat_full_from_database", true); } ChatManager::ChatFull *ChatManager::get_chat_full_force(ChatId chat_id, const char *source) { if (!have_chat_force(chat_id, source)) { return nullptr; } ChatFull *chat_full = get_chat_full(chat_id); if (chat_full != nullptr) { return chat_full; } if (!G()->use_chat_info_database()) { return nullptr; } if (!unavailable_chat_fulls_.insert(chat_id).second) { return nullptr; } LOG(INFO) << "Trying to load full " << chat_id << " from database from " << source; on_load_chat_full_from_database(chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_full_database_key(chat_id))); return get_chat_full(chat_id); } void ChatManager::save_channel_full(const ChannelFull *channel_full, ChannelId channel_id) { if (!G()->use_chat_info_database()) { return; } LOG(INFO) << "Trying to save to database full " << channel_id; CHECK(channel_full != nullptr); G()->td_db()->get_sqlite_pmc()->set(get_channel_full_database_key(channel_id), get_channel_full_database_value(channel_full), Auto()); } string ChatManager::get_channel_full_database_key(ChannelId channel_id) { return PSTRING() << "chf" << channel_id.get(); } string ChatManager::get_channel_full_database_value(const ChannelFull *channel_full) { return log_event_store(*channel_full).as_slice().str(); } void ChatManager::on_load_channel_full_from_database(ChannelId channel_id, string value, const char *source) { LOG(INFO) << "Successfully loaded full " << channel_id << " of size " << value.size() << " from database from " << source; // G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); // return; if (get_channel_full(channel_id, true, "on_load_channel_full_from_database") != nullptr || value.empty()) { return; } ChannelFull *channel_full = add_channel_full(channel_id); auto status = log_event_parse(*channel_full, value); if (status.is_error()) { // can't happen unless database is broken LOG(ERROR) << "Repair broken full " << channel_id << ' ' << format::as_hex_dump<4>(Slice(value)); // just clean all known data about the channel and pretend that there was nothing in the database channels_full_.erase(channel_id); G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); return; } Dependencies dependencies; dependencies.add(channel_id); // must not depend on the linked_dialog_id itself, because message database can be disabled // the Dialog will be forcely created in update_channel_full dependencies.add_dialog_dependencies(DialogId(channel_full->linked_channel_id)); dependencies.add(channel_full->migrated_from_chat_id); for (auto bot_user_id : channel_full->bot_user_ids) { dependencies.add(bot_user_id); } dependencies.add(channel_full->invite_link.get_creator_user_id()); if (!dependencies.resolve_force(td_, source)) { channels_full_.erase(channel_id); G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); return; } Channel *c = get_channel(channel_id); CHECK(c != nullptr); bool need_invite_link = c->status.can_manage_invite_links(); bool have_invite_link = channel_full->invite_link.is_valid(); if (need_invite_link != have_invite_link) { if (need_invite_link) { // ignore ChannelFull without invite link channels_full_.erase(channel_id); return; } else { channel_full->invite_link = DialogInviteLink(); } } if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), channel_full->photo, c->photo, false)) { channel_full->photo = Photo(); if (c->photo.small_file_id.is_valid()) { channel_full->expires_at = 0.0; } } auto photo = std::move(channel_full->photo); channel_full->photo = Photo(); on_update_channel_full_photo(channel_full, channel_id, std::move(photo)); if (channel_full->participant_count < channel_full->administrator_count) { channel_full->participant_count = channel_full->administrator_count; } if (c->participant_count != 0 && c->participant_count != channel_full->participant_count) { channel_full->participant_count = c->participant_count; if (channel_full->participant_count < channel_full->administrator_count) { channel_full->participant_count = channel_full->administrator_count; channel_full->expires_at = 0.0; c->participant_count = channel_full->participant_count; c->is_changed = true; } } if (c->can_be_deleted != channel_full->can_be_deleted) { c->can_be_deleted = channel_full->can_be_deleted; c->need_save_to_database = true; } if (invalidated_channels_full_.erase(channel_id) > 0 || (!c->is_slow_mode_enabled && channel_full->slow_mode_delay != 0)) { do_invalidate_channel_full(channel_full, channel_id, !c->is_slow_mode_enabled); } td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, false); send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), channel_full->bot_user_ids, true); update_channel(c, channel_id); channel_full->is_update_channel_full_sent = true; update_channel_full(channel_full, channel_id, "on_load_channel_full_from_database", true); if (channel_full->expires_at == 0.0) { load_channel_full(channel_id, true, Auto(), "on_load_channel_full_from_database"); } } ChatManager::ChannelFull *ChatManager::get_channel_full_force(ChannelId channel_id, bool only_local, const char *source) { if (!have_channel_force(channel_id, source)) { return nullptr; } ChannelFull *channel_full = get_channel_full(channel_id, only_local, source); if (channel_full != nullptr) { return channel_full; } if (!G()->use_chat_info_database()) { return nullptr; } if (!unavailable_channel_fulls_.insert(channel_id).second) { return nullptr; } LOG(INFO) << "Trying to load full " << channel_id << " from database from " << source; on_load_channel_full_from_database( channel_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_full_database_key(channel_id)), source); return get_channel_full(channel_id, only_local, source); } void ChatManager::update_chat(Chat *c, ChatId chat_id, bool from_binlog, bool from_database) { CHECK(c != nullptr); if (c->is_being_updated) { LOG(ERROR) << "Detected recursive update of " << chat_id; } c->is_being_updated = true; SCOPE_EXIT { c->is_being_updated = false; }; bool need_update_chat_full = false; if (c->is_photo_changed) { td_->messages_manager_->on_dialog_photo_updated(DialogId(chat_id)); c->is_photo_changed = false; auto chat_full = get_chat_full(chat_id); // must not load ChatFull if (chat_full != nullptr && !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { on_update_chat_full_photo(chat_full, chat_id, Photo()); if (chat_full->is_update_chat_full_sent) { need_update_chat_full = true; } if (c->photo.small_file_id.is_valid()) { reload_chat_full(chat_id, Auto(), "update_chat"); } } } if (c->is_title_changed) { td_->messages_manager_->on_dialog_title_updated(DialogId(chat_id)); c->is_title_changed = false; } if (c->is_default_permissions_changed) { td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(chat_id)); c->is_default_permissions_changed = false; } if (c->is_is_active_changed) { update_dialogs_for_discussion(DialogId(chat_id), c->is_active && c->status.is_creator()); c->is_is_active_changed = false; } if (c->is_status_changed) { if (!c->status.can_manage_invite_links()) { td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(chat_id)); } if (!from_database) { // if the chat is empty, this can add it to a chat list or remove it from a chat list send_closure_later(G()->messages_manager(), &MessagesManager::try_update_dialog_pos, DialogId(chat_id)); if (c->is_update_basic_group_sent) { // reload the chat to repair its status if it is changed back after receiving of outdated data create_actor( "ReloadChatSleepActor", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), chat_id](Unit) { send_closure(actor_id, &ChatManager::reload_chat, chat_id, Promise(), "ReloadChatSleepActor"); })) .release(); } } c->is_status_changed = false; } if (c->is_noforwards_changed) { td_->messages_manager_->on_dialog_has_protected_content_updated(DialogId(chat_id)); c->is_noforwards_changed = false; } if (need_update_chat_full) { auto chat_full = get_chat_full(chat_id); CHECK(chat_full != nullptr); update_chat_full(chat_full, chat_id, "update_chat"); } LOG(DEBUG) << "Update " << chat_id << ": need_save_to_database = " << c->need_save_to_database << ", is_changed = " << c->is_changed; c->need_save_to_database |= c->is_changed; if (c->need_save_to_database) { if (!from_database) { c->is_saved = false; } c->need_save_to_database = false; } if (c->is_changed) { send_closure(G()->td(), &Td::send_update, get_update_basic_group_object(chat_id, c)); c->is_changed = false; c->is_update_basic_group_sent = true; } if (!from_database) { save_chat(c, chat_id, from_binlog); } if (c->cache_version != Chat::CACHE_VERSION && !c->is_repaired && have_input_peer_chat(c, AccessRights::Read) && !G()->close_flag()) { c->is_repaired = true; LOG(INFO) << "Repairing cache of " << chat_id; reload_chat(chat_id, Promise(), "update_chat"); } } void ChatManager::update_channel(Channel *c, ChannelId channel_id, bool from_binlog, bool from_database) { CHECK(c != nullptr); if (c->is_being_updated) { LOG(ERROR) << "Detected recursive update of " << channel_id; } c->is_being_updated = true; SCOPE_EXIT { c->is_being_updated = false; }; bool need_update_channel_full = false; if (c->is_photo_changed) { td_->messages_manager_->on_dialog_photo_updated(DialogId(channel_id)); c->is_photo_changed = false; auto channel_full = get_channel_full(channel_id, true, "update_channel"); if (channel_full != nullptr && !is_same_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), channel_full->photo, c->photo, false)) { on_update_channel_full_photo(channel_full, channel_id, Photo()); if (channel_full->is_update_channel_full_sent) { need_update_channel_full = true; } if (c->photo.small_file_id.is_valid()) { if (channel_full->expires_at > 0.0) { channel_full->expires_at = 0.0; channel_full->need_save_to_database = true; } send_get_channel_full_query(channel_full, channel_id, Auto(), "update_channel"); } } } if (c->is_accent_color_changed) { td_->messages_manager_->on_dialog_accent_colors_updated(DialogId(channel_id)); c->is_accent_color_changed = false; } if (c->is_title_changed) { td_->messages_manager_->on_dialog_title_updated(DialogId(channel_id)); c->is_title_changed = false; } if (c->is_status_changed) { c->status.update_restrictions(); auto until_date = c->status.get_until_date(); double left_time = 0; if (until_date > 0) { left_time = until_date - G()->server_time() + 2; if (left_time <= 0) { c->status.update_restrictions(); CHECK(c->status.get_until_date() == 0); } } if (left_time > 0 && left_time < 366 * 86400) { channel_unban_timeout_.set_timeout_in(channel_id.get(), left_time); } else { channel_unban_timeout_.cancel_timeout(channel_id.get()); } if (c->is_megagroup) { update_dialogs_for_discussion(DialogId(channel_id), c->status.is_administrator() && c->status.can_pin_messages()); } if (!c->status.is_member()) { remove_inactive_channel(channel_id); } if (!c->status.can_manage_invite_links()) { td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(channel_id)); } if (!from_database && c->is_update_supergroup_sent) { // reload the channel to repair its status if it is changed back after receiving of outdated data create_actor("ReloadChannelSleepActor", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), channel_id](Unit) { send_closure(actor_id, &ChatManager::reload_channel, channel_id, Promise(), "ReloadChannelSleepActor"); })) .release(); } c->is_status_changed = false; } if (c->is_username_changed) { if (c->status.is_creator()) { update_created_public_channels(c, channel_id); } c->is_username_changed = false; } if (c->is_default_permissions_changed) { td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(channel_id)); if (c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, ChannelType::Unknown)) { td_->dialog_manager_->remove_dialog_suggested_action( SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); } c->is_default_permissions_changed = false; } if (c->is_has_location_changed) { if (c->status.is_creator()) { update_created_public_channels(c, channel_id); } c->is_has_location_changed = false; } if (c->is_creator_changed) { update_created_public_channels(c, channel_id); c->is_creator_changed = false; } if (c->is_noforwards_changed) { td_->messages_manager_->on_dialog_has_protected_content_updated(DialogId(channel_id)); c->is_noforwards_changed = false; } if (c->is_stories_hidden_changed) { send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, DialogId(channel_id), "stories_hidden"); c->is_stories_hidden_changed = false; } auto unix_time = G()->unix_time(); auto effective_emoji_status = c->emoji_status.get_effective_emoji_status(true, unix_time); if (effective_emoji_status != c->last_sent_emoji_status) { if (!c->last_sent_emoji_status.is_empty()) { channel_emoji_status_timeout_.cancel_timeout(channel_id.get()); } c->last_sent_emoji_status = effective_emoji_status; if (!c->last_sent_emoji_status.is_empty()) { auto until_date = c->last_sent_emoji_status.get_until_date(); auto left_time = until_date - unix_time; if (left_time >= 0 && left_time < 30 * 86400) { channel_emoji_status_timeout_.set_timeout_in(channel_id.get(), left_time); } } td_->messages_manager_->on_dialog_emoji_status_updated(DialogId(channel_id)); } c->is_emoji_status_changed = false; if (!td_->auth_manager_->is_bot()) { if (c->restriction_reasons.empty()) { restricted_channel_ids_.erase(channel_id); } else { restricted_channel_ids_.insert(channel_id); } } if (from_binlog || from_database) { td_->dialog_manager_->on_dialog_usernames_received(DialogId(channel_id), c->usernames, true); } if (!is_channel_public(c) && !c->has_linked_channel) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id, DialogId(channel_id), DialogId(), false); } if (need_update_channel_full) { auto channel_full = get_channel_full(channel_id, true, "update_channel"); CHECK(channel_full != nullptr); update_channel_full(channel_full, channel_id, "update_channel"); } LOG(DEBUG) << "Update " << channel_id << ": need_save_to_database = " << c->need_save_to_database << ", is_changed = " << c->is_changed; c->need_save_to_database |= c->is_changed; if (c->need_save_to_database) { if (!from_database) { c->is_saved = false; } c->need_save_to_database = false; } if (c->is_changed) { send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, c)); c->is_changed = false; c->is_update_supergroup_sent = true; } if (!from_database) { save_channel(c, channel_id, from_binlog); } bool have_read_access = have_input_peer_channel(c, channel_id, AccessRights::Read); if (c->had_read_access && !have_read_access) { send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id), Promise()); } c->had_read_access = have_read_access; if (c->cache_version != Channel::CACHE_VERSION && !c->is_repaired && have_input_peer_channel(c, channel_id, AccessRights::Read) && !G()->close_flag()) { c->is_repaired = true; LOG(INFO) << "Repairing cache of " << channel_id; reload_channel(channel_id, Promise(), "update_channel"); } } void ChatManager::update_chat_full(ChatFull *chat_full, ChatId chat_id, const char *source, bool from_database) { CHECK(chat_full != nullptr); if (chat_full->is_being_updated) { LOG(ERROR) << "Detected recursive update of full " << chat_id << " from " << source; } chat_full->is_being_updated = true; SCOPE_EXIT { chat_full->is_being_updated = false; }; unavailable_chat_fulls_.erase(chat_id); // don't needed anymore chat_full->need_send_update |= chat_full->is_changed; chat_full->need_save_to_database |= chat_full->is_changed; chat_full->is_changed = false; if (chat_full->need_send_update || chat_full->need_save_to_database) { LOG(INFO) << "Update full " << chat_id << " from " << source; } if (chat_full->need_send_update) { vector administrators; vector bot_user_ids; for (const auto &participant : chat_full->participants) { if (participant.status_.is_administrator() && participant.dialog_id_.get_type() == DialogType::User) { administrators.emplace_back(participant.dialog_id_.get_user_id(), participant.status_.get_rank(), participant.status_.is_creator()); } if (participant.dialog_id_.get_type() == DialogType::User) { auto user_id = participant.dialog_id_.get_user_id(); if (td_->user_manager_->is_user_bot(user_id)) { bot_user_ids.push_back(user_id); } } } td::remove_if(chat_full->bot_commands, [&bot_user_ids](const BotCommands &commands) { return !td::contains(bot_user_ids, commands.get_bot_user_id()); }); td_->dialog_participant_manager_->on_update_dialog_administrators(DialogId(chat_id), std::move(administrators), chat_full->version != -1, from_database); send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(chat_id), std::move(bot_user_ids), from_database); { Chat *c = get_chat(chat_id); CHECK(c == nullptr || c->is_update_basic_group_sent); } if (!chat_full->is_update_chat_full_sent) { LOG(ERROR) << "Send partial updateBasicGroupFullInfo for " << chat_id << " from " << source; chat_full->is_update_chat_full_sent = true; } send_closure( G()->td(), &Td::send_update, make_tl_object(get_basic_group_id_object(chat_id, "update_chat_full"), get_basic_group_full_info_object(chat_id, chat_full))); chat_full->need_send_update = false; } if (chat_full->need_save_to_database) { if (!from_database) { save_chat_full(chat_full, chat_id); } chat_full->need_save_to_database = false; } } void ChatManager::update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source, bool from_database) { CHECK(channel_full != nullptr); if (channel_full->is_being_updated) { LOG(ERROR) << "Detected recursive update of full " << channel_id << " from " << source; } channel_full->is_being_updated = true; SCOPE_EXIT { channel_full->is_being_updated = false; }; unavailable_channel_fulls_.erase(channel_id); // don't needed anymore CHECK(channel_full->participant_count >= channel_full->administrator_count); if (channel_full->is_slow_mode_next_send_date_changed) { auto now = G()->server_time(); if (channel_full->slow_mode_next_send_date > now + 3601) { channel_full->slow_mode_next_send_date = static_cast(now) + 3601; } if (channel_full->slow_mode_next_send_date <= now) { channel_full->slow_mode_next_send_date = 0; } if (channel_full->slow_mode_next_send_date == 0) { slow_mode_delay_timeout_.cancel_timeout(channel_id.get()); } else { slow_mode_delay_timeout_.set_timeout_in(channel_id.get(), channel_full->slow_mode_next_send_date - now + 0.002); } channel_full->is_slow_mode_next_send_date_changed = false; } if (channel_full->need_save_to_database) { channel_full->is_changed |= td::remove_if( channel_full->bot_commands, [bot_user_ids = &channel_full->bot_user_ids](const BotCommands &commands) { return !td::contains(*bot_user_ids, commands.get_bot_user_id()); }); } channel_full->need_send_update |= channel_full->is_changed; channel_full->need_save_to_database |= channel_full->is_changed; channel_full->is_changed = false; if (channel_full->need_send_update || channel_full->need_save_to_database) { LOG(INFO) << "Update full " << channel_id << " from " << source; } if (channel_full->need_send_update) { if (channel_full->linked_channel_id.is_valid()) { td_->dialog_manager_->force_create_dialog(DialogId(channel_full->linked_channel_id), "update_channel_full", true); } { Channel *c = get_channel(channel_id); CHECK(c == nullptr || c->is_update_supergroup_sent); } if (!channel_full->is_update_channel_full_sent) { LOG(ERROR) << "Send partial updateSupergroupFullInfo for " << channel_id << " from " << source; channel_full->is_update_channel_full_sent = true; } send_closure( G()->td(), &Td::send_update, make_tl_object(get_supergroup_id_object(channel_id, "update_channel_full"), get_supergroup_full_info_object(channel_id, channel_full))); channel_full->need_send_update = false; } if (channel_full->need_save_to_database) { if (!from_database) { save_channel_full(channel_full, channel_id); } channel_full->need_save_to_database = false; } } void ChatManager::on_get_chat(tl_object_ptr &&chat, const char *source) { LOG(DEBUG) << "Receive from " << source << ' ' << to_string(chat); switch (chat->get_id()) { case telegram_api::chatEmpty::ID: on_get_chat_empty(static_cast(*chat), source); break; case telegram_api::chat::ID: on_get_chat(static_cast(*chat), source); break; case telegram_api::chatForbidden::ID: on_get_chat_forbidden(static_cast(*chat), source); break; case telegram_api::channel::ID: on_get_channel(static_cast(*chat), source); break; case telegram_api::channelForbidden::ID: on_get_channel_forbidden(static_cast(*chat), source); break; default: UNREACHABLE(); } } void ChatManager::on_get_chats(vector> &&chats, const char *source) { for (auto &chat : chats) { auto constuctor_id = chat->get_id(); if (constuctor_id == telegram_api::channel::ID || constuctor_id == telegram_api::channelForbidden::ID) { // apply info about megagroups before corresponding chats on_get_chat(std::move(chat), source); chat = nullptr; } } for (auto &chat : chats) { if (chat != nullptr) { on_get_chat(std::move(chat), source); chat = nullptr; } } } void ChatManager::on_get_chat_full(tl_object_ptr &&chat_full_ptr, Promise &&promise) { LOG(INFO) << "Receive " << to_string(chat_full_ptr); if (chat_full_ptr->get_id() == telegram_api::chatFull::ID) { auto chat = move_tl_object_as(chat_full_ptr); ChatId chat_id(chat->id_); Chat *c = get_chat(chat_id); if (c == nullptr) { LOG(ERROR) << "Can't find " << chat_id; return promise.set_value(Unit()); } if (c->version >= c->pinned_message_version) { auto pinned_message_id = MessageId(ServerMessageId(chat->pinned_msg_id_)); LOG(INFO) << "Receive pinned " << pinned_message_id << " in " << chat_id << " with version " << c->version << ". Current version is " << c->pinned_message_version; td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(chat_id), pinned_message_id); if (c->version > c->pinned_message_version) { c->pinned_message_version = c->version; c->need_save_to_database = true; update_chat(c, chat_id); } } td_->messages_manager_->on_update_dialog_folder_id(DialogId(chat_id), FolderId(chat->folder_id_)); td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(chat_id), chat->has_scheduled_); { InputGroupCallId input_group_call_id; if (chat->call_ != nullptr) { input_group_call_id = InputGroupCallId(chat->call_); } td_->messages_manager_->on_update_dialog_group_call_id(DialogId(chat_id), input_group_call_id); } { DialogId default_join_group_call_as_dialog_id; if (chat->groupcall_default_join_as_ != nullptr) { default_join_group_call_as_dialog_id = DialogId(chat->groupcall_default_join_as_); } // use send closure later to not create synchronously default_join_group_call_as_dialog_id send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id, DialogId(chat_id), default_join_group_call_as_dialog_id, false); } td_->messages_manager_->on_update_dialog_message_ttl(DialogId(chat_id), MessageTtl(chat->ttl_period_)); td_->messages_manager_->on_update_dialog_is_translatable(DialogId(chat_id), !chat->translations_disabled_); ChatFull *chat_full = add_chat_full(chat_id); on_update_chat_full_invite_link(chat_full, std::move(chat->exported_invite_)); auto photo = get_photo(td_, std::move(chat->chat_photo_), DialogId(chat_id)); // on_update_chat_photo should be a no-op if server sent consistent data on_update_chat_photo(c, chat_id, as_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), 0, photo, false), false); on_update_chat_full_photo(chat_full, chat_id, std::move(photo)); if (chat_full->description != chat->about_) { chat_full->description = std::move(chat->about_); chat_full->is_changed = true; td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, true); } if (chat_full->can_set_username != chat->can_set_username_) { chat_full->can_set_username = chat->can_set_username_; chat_full->need_save_to_database = true; } on_get_chat_participants(std::move(chat->participants_), false); td_->messages_manager_->on_update_dialog_notify_settings(DialogId(chat_id), std::move(chat->notify_settings_), "on_get_chat_full"); td_->messages_manager_->on_update_dialog_available_reactions( DialogId(chat_id), std::move(chat->available_reactions_), chat->reactions_limit_); td_->messages_manager_->on_update_dialog_theme_name(DialogId(chat_id), std::move(chat->theme_emoticon_)); td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(chat_id), chat->requests_pending_, std::move(chat->recent_requesters_)); auto bot_commands = td_->user_manager_->get_bot_commands(std::move(chat->bot_info_), &chat_full->participants); if (chat_full->bot_commands != bot_commands) { chat_full->bot_commands = std::move(bot_commands); chat_full->is_changed = true; } if (c->is_changed) { LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << chat_id; update_chat(c, chat_id); } chat_full->is_update_chat_full_sent = true; update_chat_full(chat_full, chat_id, "on_get_chat_full"); } else { CHECK(chat_full_ptr->get_id() == telegram_api::channelFull::ID); auto channel = move_tl_object_as(chat_full_ptr); ChannelId channel_id(channel->id_); auto c = get_channel(channel_id); if (c == nullptr) { LOG(ERROR) << "Can't find " << channel_id; return promise.set_value(Unit()); } invalidated_channels_full_.erase(channel_id); if (!G()->close_flag()) { auto channel_full = get_channel_full(channel_id, true, "on_get_channel_full"); if (channel_full != nullptr) { if (channel_full->repair_request_version != 0 && channel_full->repair_request_version < channel_full->speculative_version) { LOG(INFO) << "Receive ChannelFull with request version " << channel_full->repair_request_version << ", but current speculative version is " << channel_full->speculative_version; channel_full->repair_request_version = channel_full->speculative_version; auto input_channel = get_input_channel(channel_id); CHECK(input_channel != nullptr); td_->create_handler(std::move(promise))->send(channel_id, std::move(input_channel)); return; } channel_full->repair_request_version = 0; } } td_->messages_manager_->on_update_dialog_notify_settings(DialogId(channel_id), std::move(channel->notify_settings_), "on_get_channel_full"); td_->messages_manager_->on_update_dialog_background(DialogId(channel_id), std::move(channel->wallpaper_)); td_->messages_manager_->on_update_dialog_available_reactions( DialogId(channel_id), std::move(channel->available_reactions_), channel->reactions_limit_); td_->messages_manager_->on_update_dialog_theme_name(DialogId(channel_id), std::move(channel->theme_emoticon_)); td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(channel_id), channel->requests_pending_, std::move(channel->recent_requesters_)); td_->messages_manager_->on_update_dialog_message_ttl(DialogId(channel_id), MessageTtl(channel->ttl_period_)); td_->messages_manager_->on_update_dialog_view_as_messages(DialogId(channel_id), channel->view_forum_as_messages_); td_->messages_manager_->on_update_dialog_is_translatable(DialogId(channel_id), !channel->translations_disabled_); send_closure_later(td_->story_manager_actor_, &StoryManager::on_get_dialog_stories, DialogId(channel_id), std::move(channel->stories_), Promise()); ChannelFull *channel_full = add_channel_full(channel_id); bool have_participant_count = (channel->flags_ & telegram_api::channelFull::PARTICIPANTS_COUNT_MASK) != 0; auto participant_count = have_participant_count ? channel->participants_count_ : channel_full->participant_count; auto administrator_count = 0; if ((channel->flags_ & telegram_api::channelFull::ADMINS_COUNT_MASK) != 0) { administrator_count = channel->admins_count_; } else if (c->is_megagroup || c->status.is_administrator()) { // in megagroups and administered channels don't drop known number of administrators administrator_count = channel_full->administrator_count; } if (participant_count < administrator_count) { participant_count = administrator_count; } auto restricted_count = channel->banned_count_; auto banned_count = channel->kicked_count_; auto can_get_participants = channel->can_view_participants_; auto has_hidden_participants = channel->participants_hidden_; auto can_set_username = channel->can_set_username_; auto can_set_sticker_set = channel->can_set_stickers_; auto can_set_location = channel->can_set_location_; auto is_all_history_available = !channel->hidden_prehistory_; auto can_have_sponsored_messages = !channel->restricted_sponsored_; auto has_aggressive_anti_spam_enabled = channel->antispam_; auto can_view_statistics = channel->can_view_stats_; auto can_view_revenue = channel->can_view_revenue_; auto has_pinned_stories = channel->stories_pinned_available_; auto boost_count = channel->boosts_applied_; auto unrestrict_boost_count = channel->boosts_unrestrict_; auto has_paid_media_allowed = channel->paid_media_allowed_; StickerSetId sticker_set_id; if (channel->stickerset_ != nullptr) { sticker_set_id = td_->stickers_manager_->on_get_sticker_set(std::move(channel->stickerset_), true, "on_get_channel_full"); } StickerSetId emoji_sticker_set_id; if (channel->emojiset_ != nullptr) { emoji_sticker_set_id = td_->stickers_manager_->on_get_sticker_set(std::move(channel->emojiset_), true, "on_get_channel_full"); } DcId stats_dc_id; if ((channel->flags_ & telegram_api::channelFull::STATS_DC_MASK) != 0) { stats_dc_id = DcId::create(channel->stats_dc_); } if (!stats_dc_id.is_exact() && can_view_statistics) { LOG(ERROR) << "Receive can_view_statistics == true, but invalid statistics DC ID in " << channel_id; can_view_statistics = false; } channel_full->repair_request_version = 0; channel_full->expires_at = Time::now() + CHANNEL_FULL_EXPIRE_TIME; if (channel_full->participant_count != participant_count || channel_full->administrator_count != administrator_count || channel_full->restricted_count != restricted_count || channel_full->banned_count != banned_count || channel_full->can_get_participants != can_get_participants || channel_full->can_set_sticker_set != can_set_sticker_set || channel_full->can_set_location != can_set_location || channel_full->can_view_statistics != can_view_statistics || channel_full->stats_dc_id != stats_dc_id || channel_full->sticker_set_id != sticker_set_id || channel_full->emoji_sticker_set_id != emoji_sticker_set_id || channel_full->is_all_history_available != is_all_history_available || channel_full->can_have_sponsored_messages != can_have_sponsored_messages || channel_full->has_aggressive_anti_spam_enabled != has_aggressive_anti_spam_enabled || channel_full->has_hidden_participants != has_hidden_participants || channel_full->has_pinned_stories != has_pinned_stories || channel_full->boost_count != boost_count || channel_full->unrestrict_boost_count != unrestrict_boost_count || channel_full->can_view_revenue != can_view_revenue || channel_full->has_paid_media_allowed != has_paid_media_allowed) { channel_full->participant_count = participant_count; channel_full->administrator_count = administrator_count; channel_full->restricted_count = restricted_count; channel_full->banned_count = banned_count; channel_full->can_get_participants = can_get_participants; channel_full->has_hidden_participants = has_hidden_participants; channel_full->can_set_sticker_set = can_set_sticker_set; channel_full->can_set_location = can_set_location; channel_full->can_view_statistics = can_view_statistics; channel_full->stats_dc_id = stats_dc_id; channel_full->sticker_set_id = sticker_set_id; channel_full->emoji_sticker_set_id = emoji_sticker_set_id; channel_full->is_all_history_available = is_all_history_available; channel_full->can_have_sponsored_messages = can_have_sponsored_messages; channel_full->has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled; channel_full->has_pinned_stories = has_pinned_stories; channel_full->boost_count = boost_count; channel_full->unrestrict_boost_count = unrestrict_boost_count; channel_full->can_view_revenue = can_view_revenue; channel_full->has_paid_media_allowed = has_paid_media_allowed; channel_full->is_changed = true; } if (channel_full->description != channel->about_) { channel_full->description = std::move(channel->about_); channel_full->is_changed = true; td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, true); } if (have_participant_count && c->participant_count != participant_count) { c->participant_count = participant_count; c->is_changed = true; update_channel(c, channel_id); } if (!channel_full->is_can_view_statistics_inited) { channel_full->is_can_view_statistics_inited = true; channel_full->need_save_to_database = true; } if (channel_full->can_set_username != can_set_username) { channel_full->can_set_username = can_set_username; channel_full->need_save_to_database = true; } auto photo = get_photo(td_, std::move(channel->chat_photo_), DialogId(channel_id)); // on_update_channel_photo should be a no-op if server sent consistent data on_update_channel_photo( c, channel_id, as_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), c->access_hash, photo, false), false); on_update_channel_full_photo(channel_full, channel_id, std::move(photo)); td_->messages_manager_->on_read_channel_outbox(channel_id, MessageId(ServerMessageId(channel->read_outbox_max_id_))); if ((channel->flags_ & telegram_api::channelFull::AVAILABLE_MIN_ID_MASK) != 0) { td_->messages_manager_->on_update_channel_max_unavailable_message_id( channel_id, MessageId(ServerMessageId(channel->available_min_id_)), "ChannelFull"); } td_->messages_manager_->on_read_channel_inbox(channel_id, MessageId(ServerMessageId(channel->read_inbox_max_id_)), channel->unread_count_, channel->pts_, "ChannelFull"); on_update_channel_full_invite_link(channel_full, std::move(channel->exported_invite_)); td_->messages_manager_->on_update_dialog_is_blocked(DialogId(channel_id), channel->blocked_, false); td_->messages_manager_->on_update_dialog_last_pinned_message_id( DialogId(channel_id), MessageId(ServerMessageId(channel->pinned_msg_id_))); td_->messages_manager_->on_update_dialog_folder_id(DialogId(channel_id), FolderId(channel->folder_id_)); td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(channel_id), channel->has_scheduled_); { InputGroupCallId input_group_call_id; if (channel->call_ != nullptr) { input_group_call_id = InputGroupCallId(channel->call_); } td_->messages_manager_->on_update_dialog_group_call_id(DialogId(channel_id), input_group_call_id); } { DialogId default_join_group_call_as_dialog_id; if (channel->groupcall_default_join_as_ != nullptr) { default_join_group_call_as_dialog_id = DialogId(channel->groupcall_default_join_as_); } // use send closure later to not create synchronously default_join_group_call_as_dialog_id send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id, DialogId(channel_id), default_join_group_call_as_dialog_id, false); } { DialogId default_send_message_as_dialog_id; if (channel->default_send_as_ != nullptr) { default_send_message_as_dialog_id = DialogId(channel->default_send_as_); } // use send closure later to not create synchronously default_send_message_as_dialog_id send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id, DialogId(channel_id), default_send_message_as_dialog_id, false); } if (participant_count >= 190 || !can_get_participants || has_hidden_participants) { td_->dialog_participant_manager_->on_update_dialog_online_member_count(DialogId(channel_id), channel->online_count_, true); } vector bot_user_ids; for (const auto &bot_info : channel->bot_info_) { UserId user_id(bot_info->user_id_); if (!td_->user_manager_->is_user_bot(user_id)) { continue; } bot_user_ids.push_back(user_id); } on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids)); auto bot_commands = td_->user_manager_->get_bot_commands(std::move(channel->bot_info_), nullptr); if (channel_full->bot_commands != bot_commands) { channel_full->bot_commands = std::move(bot_commands); channel_full->is_changed = true; } ChannelId linked_channel_id; if ((channel->flags_ & telegram_api::channelFull::LINKED_CHAT_ID_MASK) != 0) { linked_channel_id = ChannelId(channel->linked_chat_id_); auto linked_channel = get_channel_force(linked_channel_id, "ChannelFull"); if (linked_channel == nullptr || c->is_megagroup == linked_channel->is_megagroup || channel_id == linked_channel_id) { LOG(ERROR) << "Failed to add a link between " << channel_id << " and " << linked_channel_id; linked_channel_id = ChannelId(); } } on_update_channel_full_linked_channel_id(channel_full, channel_id, linked_channel_id); on_update_channel_full_location(channel_full, channel_id, DialogLocation(td_, std::move(channel->location_))); if (c->is_megagroup) { on_update_channel_full_slow_mode_delay(channel_full, channel_id, channel->slowmode_seconds_, channel->slowmode_next_send_date_); } if (channel_full->can_be_deleted != channel->can_delete_channel_) { channel_full->can_be_deleted = channel->can_delete_channel_; channel_full->need_save_to_database = true; } if (c->can_be_deleted != channel_full->can_be_deleted) { c->can_be_deleted = channel_full->can_be_deleted; c->need_save_to_database = true; } auto migrated_from_chat_id = ChatId(channel->migrated_from_chat_id_); auto migrated_from_max_message_id = MessageId(ServerMessageId(channel->migrated_from_max_id_)); if (channel_full->migrated_from_chat_id != migrated_from_chat_id || channel_full->migrated_from_max_message_id != migrated_from_max_message_id) { channel_full->migrated_from_chat_id = migrated_from_chat_id; channel_full->migrated_from_max_message_id = migrated_from_max_message_id; channel_full->is_changed = true; } if (c->is_changed) { LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << channel_id; update_channel(c, channel_id); } channel_full->is_update_channel_full_sent = true; update_channel_full(channel_full, channel_id, "on_get_channel_full"); if (linked_channel_id.is_valid()) { auto linked_channel_full = get_channel_full_force(linked_channel_id, true, "on_get_channel_full"); on_update_channel_full_linked_channel_id(linked_channel_full, linked_channel_id, channel_id); if (linked_channel_full != nullptr) { update_channel_full(linked_channel_full, linked_channel_id, "on_get_channel_full 2"); } } td_->dialog_manager_->set_dialog_pending_suggestions(DialogId(channel_id), std::move(channel->pending_suggestions_)); } promise.set_value(Unit()); } void ChatManager::on_get_chat_full_failed(ChatId chat_id) { if (G()->close_flag()) { return; } LOG(INFO) << "Failed to get full " << chat_id; } void ChatManager::on_get_channel_full_failed(ChannelId channel_id) { if (G()->close_flag()) { return; } LOG(INFO) << "Failed to get full " << channel_id; auto channel_full = get_channel_full(channel_id, true, "on_get_channel_full"); if (channel_full != nullptr) { channel_full->repair_request_version = 0; } } void ChatManager::on_ignored_restriction_reasons_changed() { restricted_channel_ids_.foreach([&](const ChannelId &channel_id) { send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, get_channel(channel_id))); }); } void ChatManager::update_chat_online_member_count(ChatId chat_id, bool is_from_server) { auto chat_full = get_chat_full(chat_id); if (chat_full != nullptr) { update_chat_online_member_count(chat_full, chat_id, false); } } void ChatManager::update_chat_online_member_count(const ChatFull *chat_full, ChatId chat_id, bool is_from_server) { td_->dialog_participant_manager_->update_dialog_online_member_count(chat_full->participants, DialogId(chat_id), is_from_server); } void ChatManager::on_get_chat_participants(tl_object_ptr &&participants_ptr, bool from_update) { switch (participants_ptr->get_id()) { case telegram_api::chatParticipantsForbidden::ID: { auto participants = move_tl_object_as(participants_ptr); ChatId chat_id(participants->chat_id_); if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return; } if (!have_chat_force(chat_id, "on_get_chat_participants")) { LOG(ERROR) << chat_id << " not found"; return; } if (from_update) { drop_chat_full(chat_id); } break; } case telegram_api::chatParticipants::ID: { auto participants = move_tl_object_as(participants_ptr); ChatId chat_id(participants->chat_id_); if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return; } const Chat *c = get_chat_force(chat_id, "chatParticipants"); if (c == nullptr) { LOG(ERROR) << chat_id << " not found"; return; } ChatFull *chat_full = get_chat_full_force(chat_id, "telegram_api::chatParticipants"); if (chat_full == nullptr) { LOG(INFO) << "Ignore update of members for unknown full " << chat_id; return; } UserId new_creator_user_id; vector new_participants; new_participants.reserve(participants->participants_.size()); for (auto &participant_ptr : participants->participants_) { DialogParticipant dialog_participant(std::move(participant_ptr), c->date, c->status.is_creator()); if (!dialog_participant.is_valid()) { LOG(ERROR) << "Receive invalid " << dialog_participant; continue; } LOG_IF(ERROR, !td_->dialog_manager_->have_dialog_info(dialog_participant.dialog_id_)) << "Have no information about " << dialog_participant.dialog_id_ << " as a member of " << chat_id; LOG_IF(ERROR, !td_->user_manager_->have_user(dialog_participant.inviter_user_id_)) << "Have no information about " << dialog_participant.inviter_user_id_ << " as a member of " << chat_id; if (dialog_participant.joined_date_ < c->date) { LOG_IF(ERROR, dialog_participant.joined_date_ < c->date - 30 && c->date >= 1486000000) << "Wrong join date = " << dialog_participant.joined_date_ << " for " << dialog_participant.dialog_id_ << ", " << chat_id << " was created at " << c->date; dialog_participant.joined_date_ = c->date; } if (dialog_participant.status_.is_creator() && dialog_participant.dialog_id_.get_type() == DialogType::User) { new_creator_user_id = dialog_participant.dialog_id_.get_user_id(); } new_participants.push_back(std::move(dialog_participant)); } if (chat_full->creator_user_id != new_creator_user_id) { if (new_creator_user_id.is_valid() && chat_full->creator_user_id.is_valid()) { LOG(ERROR) << "Group creator has changed from " << chat_full->creator_user_id << " to " << new_creator_user_id << " in " << chat_id; } chat_full->creator_user_id = new_creator_user_id; chat_full->is_changed = true; } on_update_chat_full_participants(chat_full, chat_id, std::move(new_participants), participants->version_, from_update); if (from_update) { update_chat_full(chat_full, chat_id, "on_get_chat_participants"); } break; } default: UNREACHABLE(); } } const DialogParticipant *ChatManager::get_chat_participant(ChatId chat_id, UserId user_id) const { auto chat_full = get_chat_full(chat_id); if (chat_full == nullptr) { return nullptr; } return get_chat_full_participant(chat_full, DialogId(user_id)); } const DialogParticipant *ChatManager::get_chat_full_participant(const ChatFull *chat_full, DialogId dialog_id) { for (const auto &dialog_participant : chat_full->participants) { if (dialog_participant.dialog_id_ == dialog_id) { return &dialog_participant; } } return nullptr; } const vector *ChatManager::get_chat_participants(ChatId chat_id) const { auto chat_full = get_chat_full(chat_id); if (chat_full == nullptr) { return nullptr; } return &chat_full->participants; } tl_object_ptr ChatManager::get_chat_member_object(const DialogParticipant &dialog_participant, const char *source) const { return td_api::make_object( get_message_sender_object(td_, dialog_participant.dialog_id_, source), td_->user_manager_->get_user_id_object(dialog_participant.inviter_user_id_, "chatMember.inviter_user_id"), dialog_participant.joined_date_, dialog_participant.status_.get_chat_member_status_object()); } bool ChatManager::on_get_channel_error(ChannelId channel_id, const Status &status, const char *source) { LOG(INFO) << "Receive " << status << " in " << channel_id << " from " << source; if (status.message() == CSlice("BOT_METHOD_INVALID")) { LOG(ERROR) << "Receive BOT_METHOD_INVALID from " << source; return true; } if (G()->is_expected_error(status)) { return true; } if (status.message() == "CHANNEL_PRIVATE" || status.message() == "CHANNEL_PUBLIC_GROUP_NA") { if (!channel_id.is_valid()) { LOG(ERROR) << "Receive " << status.message() << " in invalid " << channel_id << " from " << source; return false; } auto c = get_channel(channel_id); if (c == nullptr) { if (Slice(source) == Slice("GetChannelDifferenceQuery") || Slice(source) == Slice("GetChannelsQuery")) { // get channel difference after restart // get channel from server by its identifier return true; } LOG(ERROR) << "Receive " << status.message() << " in not found " << channel_id << " from " << source; return false; } auto debug_channel_object = oneline(to_string(get_supergroup_object(channel_id, c))); if (c->status.is_member()) { LOG(INFO) << "Emulate leaving " << channel_id; // TODO we also may try to write to a public channel int32 flags = 0; if (c->is_megagroup) { flags |= CHANNEL_FLAG_IS_MEGAGROUP; } else { flags |= CHANNEL_FLAG_IS_BROADCAST; } telegram_api::channelForbidden channel_forbidden(flags, false /*ignored*/, false /*ignored*/, channel_id.get(), c->access_hash, c->title, 0); on_get_channel_forbidden(channel_forbidden, "CHANNEL_PRIVATE"); } else if (!c->status.is_banned()) { if (!c->usernames.is_empty()) { LOG(INFO) << "Drop usernames of " << channel_id; on_update_channel_usernames(c, channel_id, Usernames()); } on_update_channel_has_location(c, channel_id, false); on_update_channel_linked_channel_id(channel_id, ChannelId()); update_channel(c, channel_id); td_->dialog_invite_link_manager_->remove_dialog_access_by_invite_link(DialogId(channel_id)); } invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, source); LOG_IF(ERROR, have_input_peer_channel(c, channel_id, AccessRights::Read)) << "Have read access to channel after receiving CHANNEL_PRIVATE. Channel state: " << oneline(to_string(get_supergroup_object(channel_id, c))) << ". Previous channel state: " << debug_channel_object; return true; } return false; } bool ChatManager::speculative_add_count(int32 &count, int32 delta_count, int32 min_count) { auto new_count = count + delta_count; if (new_count < min_count) { new_count = min_count; } if (new_count == count) { return false; } count = new_count; return true; } void ChatManager::speculative_add_channel_participants(ChannelId channel_id, const vector &added_user_ids, UserId inviter_user_id, int32 date, bool by_me) { td_->dialog_participant_manager_->add_cached_channel_participants(channel_id, added_user_ids, inviter_user_id, date); auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_participants"); int32 delta_participant_count = 0; for (auto user_id : added_user_ids) { if (!user_id.is_valid()) { continue; } delta_participant_count++; if (channel_full != nullptr && td_->user_manager_->is_user_bot(user_id) && !td::contains(channel_full->bot_user_ids, user_id)) { channel_full->bot_user_ids.push_back(user_id); channel_full->need_save_to_database = true; reload_channel_full(channel_id, Promise(), "speculative_add_channel_participants"); send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), channel_full->bot_user_ids, false); } } if (channel_full != nullptr) { if (channel_full->is_changed) { channel_full->speculative_version++; } update_channel_full(channel_full, channel_id, "speculative_add_channel_participants"); } if (delta_participant_count == 0) { return; } speculative_add_channel_participant_count(channel_id, delta_participant_count, by_me); } void ChatManager::speculative_delete_channel_participant(ChannelId channel_id, UserId deleted_user_id, bool by_me) { if (!deleted_user_id.is_valid()) { return; } td_->dialog_participant_manager_->delete_cached_channel_participant(channel_id, deleted_user_id); if (td_->user_manager_->is_user_bot(deleted_user_id)) { auto channel_full = get_channel_full_force(channel_id, true, "speculative_delete_channel_participant"); if (channel_full != nullptr && td::remove(channel_full->bot_user_ids, deleted_user_id)) { channel_full->need_save_to_database = true; update_channel_full(channel_full, channel_id, "speculative_delete_channel_participant"); send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), channel_full->bot_user_ids, false); } } speculative_add_channel_participant_count(channel_id, -1, by_me); } void ChatManager::speculative_add_channel_participant_count(ChannelId channel_id, int32 delta_participant_count, bool by_me) { if (by_me) { // Currently, ignore all changes made by the current user, because they may have been already counted invalidate_channel_full(channel_id, false, "speculative_add_channel_participant_count"); // just in case return; } auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_participant_count"); auto min_count = channel_full == nullptr ? 0 : channel_full->administrator_count; auto c = get_channel_force(channel_id, "speculative_add_channel_participant_count"); if (c != nullptr && c->participant_count != 0 && speculative_add_count(c->participant_count, delta_participant_count, min_count)) { c->is_changed = true; update_channel(c, channel_id); } if (channel_full == nullptr) { return; } channel_full->is_changed |= speculative_add_count(channel_full->participant_count, delta_participant_count, min_count); if (channel_full->is_changed) { channel_full->speculative_version++; } update_channel_full(channel_full, channel_id, "speculative_add_channel_participant_count"); } void ChatManager::speculative_add_channel_user(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &new_status, const DialogParticipantStatus &old_status) { auto c = get_channel_force(channel_id, "speculative_add_channel_user"); // channel full must be loaded before c->participant_count is updated, because on_load_channel_full_from_database // must copy the initial c->participant_count before it is speculatibely updated auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_user"); int32 min_count = 0; LOG(INFO) << "Speculatively change status of " << user_id << " in " << channel_id << " from " << old_status << " to " << new_status; if (channel_full != nullptr) { channel_full->is_changed |= speculative_add_count( channel_full->administrator_count, new_status.is_administrator_member() - old_status.is_administrator_member()); min_count = channel_full->administrator_count; } if (c != nullptr && c->participant_count != 0 && speculative_add_count(c->participant_count, new_status.is_member() - old_status.is_member(), min_count)) { c->is_changed = true; update_channel(c, channel_id); } td_->dialog_participant_manager_->update_cached_channel_participant_status(channel_id, user_id, new_status); if (channel_full == nullptr) { return; } channel_full->is_changed |= speculative_add_count(channel_full->participant_count, new_status.is_member() - old_status.is_member(), min_count); channel_full->is_changed |= speculative_add_count(channel_full->restricted_count, new_status.is_restricted() - old_status.is_restricted()); channel_full->is_changed |= speculative_add_count(channel_full->banned_count, new_status.is_banned() - old_status.is_banned()); if (channel_full->is_changed) { channel_full->speculative_version++; } if (new_status.is_member() != old_status.is_member() && td_->user_manager_->is_user_bot(user_id)) { if (new_status.is_member()) { if (!td::contains(channel_full->bot_user_ids, user_id)) { channel_full->bot_user_ids.push_back(user_id); channel_full->need_save_to_database = true; reload_channel_full(channel_id, Promise(), "speculative_add_channel_user"); send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), channel_full->bot_user_ids, false); } } else { if (td::remove(channel_full->bot_user_ids, user_id)) { channel_full->need_save_to_database = true; send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), channel_full->bot_user_ids, false); } } } update_channel_full(channel_full, channel_id, "speculative_add_channel_user"); } void ChatManager::invalidate_channel_full(ChannelId channel_id, bool need_drop_slow_mode_delay, const char *source) { LOG(INFO) << "Invalidate supergroup full for " << channel_id << " from " << source; auto channel_full = get_channel_full(channel_id, true, "invalidate_channel_full"); // must not load ChannelFull if (channel_full != nullptr) { do_invalidate_channel_full(channel_full, channel_id, need_drop_slow_mode_delay); update_channel_full(channel_full, channel_id, source); } else if (channel_id.is_valid()) { invalidated_channels_full_.insert(channel_id); } } void ChatManager::do_invalidate_channel_full(ChannelFull *channel_full, ChannelId channel_id, bool need_drop_slow_mode_delay) { CHECK(channel_full != nullptr); td_->dialog_manager_->on_dialog_info_full_invalidated(DialogId(channel_id)); if (channel_full->expires_at >= Time::now()) { channel_full->expires_at = 0.0; channel_full->need_save_to_database = true; } if (need_drop_slow_mode_delay && channel_full->slow_mode_delay != 0) { channel_full->slow_mode_delay = 0; channel_full->slow_mode_next_send_date = 0; channel_full->is_slow_mode_next_send_date_changed = true; channel_full->is_changed = true; } } void ChatManager::on_update_chat_full_photo(ChatFull *chat_full, ChatId chat_id, Photo photo) { CHECK(chat_full != nullptr); if (photo != chat_full->photo) { chat_full->photo = std::move(photo); chat_full->is_changed = true; } auto photo_file_ids = photo_get_file_ids(chat_full->photo); if (chat_full->registered_photo_file_ids == photo_file_ids) { return; } auto &file_source_id = chat_full->file_source_id; if (!file_source_id.is_valid()) { file_source_id = chat_full_file_source_ids_.get(chat_id); if (file_source_id.is_valid()) { VLOG(file_references) << "Move " << file_source_id << " inside of " << chat_id; chat_full_file_source_ids_.erase(chat_id); } else { VLOG(file_references) << "Need to create new file source for full " << chat_id; file_source_id = td_->file_reference_manager_->create_chat_full_file_source(chat_id); } } td_->file_manager_->change_files_source(file_source_id, chat_full->registered_photo_file_ids, photo_file_ids); chat_full->registered_photo_file_ids = std::move(photo_file_ids); } void ChatManager::on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo) { CHECK(channel_full != nullptr); if (photo != channel_full->photo) { channel_full->photo = std::move(photo); channel_full->is_changed = true; } auto photo_file_ids = photo_get_file_ids(channel_full->photo); if (channel_full->registered_photo_file_ids == photo_file_ids) { return; } auto &file_source_id = channel_full->file_source_id; if (!file_source_id.is_valid()) { file_source_id = channel_full_file_source_ids_.get(channel_id); if (file_source_id.is_valid()) { VLOG(file_references) << "Move " << file_source_id << " inside of " << channel_id; channel_full_file_source_ids_.erase(channel_id); } else { VLOG(file_references) << "Need to create new file source for full " << channel_id; file_source_id = td_->file_reference_manager_->create_channel_full_file_source(channel_id); } } td_->file_manager_->change_files_source(file_source_id, channel_full->registered_photo_file_ids, photo_file_ids); channel_full->registered_photo_file_ids = std::move(photo_file_ids); } void ChatManager::on_update_chat_full_invite_link(ChatFull *chat_full, tl_object_ptr &&invite_link) { CHECK(chat_full != nullptr); if (update_permanent_invite_link(chat_full->invite_link, DialogInviteLink(std::move(invite_link), false, "ChatFull"))) { chat_full->is_changed = true; } } void ChatManager::on_update_channel_full_invite_link(ChannelFull *channel_full, tl_object_ptr &&invite_link) { CHECK(channel_full != nullptr); if (update_permanent_invite_link(channel_full->invite_link, DialogInviteLink(std::move(invite_link), false, "ChannelFull"))) { channel_full->is_changed = true; } } void ChatManager::remove_linked_channel_id(ChannelId channel_id) { if (!channel_id.is_valid()) { return; } auto linked_channel_id = linked_channel_ids_.get(channel_id); if (linked_channel_id.is_valid()) { linked_channel_ids_.erase(channel_id); linked_channel_ids_.erase(linked_channel_id); } } ChannelId ChatManager::get_linked_channel_id(ChannelId channel_id) const { auto channel_full = get_channel_full(channel_id); if (channel_full != nullptr) { return channel_full->linked_channel_id; } return linked_channel_ids_.get(channel_id); } void ChatManager::on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id, ChannelId linked_channel_id) { auto old_linked_channel_id = get_linked_channel_id(channel_id); LOG(INFO) << "Uplate linked channel in " << channel_id << " from " << old_linked_channel_id << " to " << linked_channel_id; if (channel_full != nullptr && channel_full->linked_channel_id != linked_channel_id && channel_full->linked_channel_id.is_valid()) { get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 10"); get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 0"); } auto old_linked_linked_channel_id = get_linked_channel_id(linked_channel_id); remove_linked_channel_id(channel_id); remove_linked_channel_id(linked_channel_id); if (channel_id.is_valid() && linked_channel_id.is_valid()) { linked_channel_ids_.set(channel_id, linked_channel_id); linked_channel_ids_.set(linked_channel_id, channel_id); } if (channel_full != nullptr && channel_full->linked_channel_id != linked_channel_id) { if (channel_full->linked_channel_id.is_valid()) { // remove link from a previously linked channel_full auto linked_channel = get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 11"); if (linked_channel != nullptr && linked_channel->has_linked_channel) { linked_channel->has_linked_channel = false; linked_channel->is_changed = true; update_channel(linked_channel, channel_full->linked_channel_id); reload_channel(channel_full->linked_channel_id, Auto(), "on_update_channel_full_linked_channel_id 21"); } auto linked_channel_full = get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 1"); if (linked_channel_full != nullptr && linked_channel_full->linked_channel_id == channel_id) { linked_channel_full->linked_channel_id = ChannelId(); linked_channel_full->is_changed = true; update_channel_full(linked_channel_full, channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 3"); } } channel_full->linked_channel_id = linked_channel_id; channel_full->is_changed = true; if (channel_full->linked_channel_id.is_valid()) { // add link from a newly linked channel_full auto linked_channel = get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 12"); if (linked_channel != nullptr && !linked_channel->has_linked_channel) { linked_channel->has_linked_channel = true; linked_channel->is_changed = true; update_channel(linked_channel, channel_full->linked_channel_id); reload_channel(channel_full->linked_channel_id, Auto(), "on_update_channel_full_linked_channel_id 22"); } auto linked_channel_full = get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 2"); if (linked_channel_full != nullptr && linked_channel_full->linked_channel_id != channel_id) { linked_channel_full->linked_channel_id = channel_id; linked_channel_full->is_changed = true; update_channel_full(linked_channel_full, channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 4"); } } } Channel *c = get_channel(channel_id); CHECK(c != nullptr); if (linked_channel_id.is_valid() != c->has_linked_channel) { c->has_linked_channel = linked_channel_id.is_valid(); c->is_changed = true; update_channel(c, channel_id); } if (old_linked_channel_id != linked_channel_id) { // must be called after the linked channel is changed td_->messages_manager_->on_dialog_linked_channel_updated(DialogId(channel_id), old_linked_channel_id, linked_channel_id); } if (linked_channel_id.is_valid()) { auto new_linked_linked_channel_id = get_linked_channel_id(linked_channel_id); LOG(INFO) << "Uplate linked channel in " << linked_channel_id << " from " << old_linked_linked_channel_id << " to " << new_linked_linked_channel_id; if (old_linked_linked_channel_id != new_linked_linked_channel_id) { // must be called after the linked channel is changed td_->messages_manager_->on_dialog_linked_channel_updated( DialogId(linked_channel_id), old_linked_linked_channel_id, new_linked_linked_channel_id); } } } void ChatManager::on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id, const DialogLocation &location) { if (channel_full->location != location) { channel_full->location = location; channel_full->is_changed = true; } Channel *c = get_channel(channel_id); CHECK(c != nullptr); on_update_channel_has_location(c, channel_id, !location.empty()); update_channel(c, channel_id); } void ChatManager::on_update_channel_full_slow_mode_delay(ChannelFull *channel_full, ChannelId channel_id, int32 slow_mode_delay, int32 slow_mode_next_send_date) { if (slow_mode_delay < 0) { LOG(ERROR) << "Receive slow mode delay " << slow_mode_delay << " in " << channel_id; slow_mode_delay = 0; } if (channel_full->slow_mode_delay != slow_mode_delay) { channel_full->slow_mode_delay = slow_mode_delay; channel_full->is_changed = true; } on_update_channel_full_slow_mode_next_send_date(channel_full, slow_mode_next_send_date); Channel *c = get_channel(channel_id); CHECK(c != nullptr); bool is_slow_mode_enabled = slow_mode_delay != 0; if (is_slow_mode_enabled != c->is_slow_mode_enabled) { c->is_slow_mode_enabled = is_slow_mode_enabled; c->is_changed = true; update_channel(c, channel_id); } } void ChatManager::on_update_channel_full_slow_mode_next_send_date(ChannelFull *channel_full, int32 slow_mode_next_send_date) { if (slow_mode_next_send_date < 0) { LOG(ERROR) << "Receive slow mode next send date " << slow_mode_next_send_date; slow_mode_next_send_date = 0; } if (channel_full->slow_mode_delay == 0 && slow_mode_next_send_date > 0) { LOG(ERROR) << "Slow mode is disabled, but next send date is " << slow_mode_next_send_date; slow_mode_next_send_date = 0; } if (slow_mode_next_send_date != 0) { auto now = G()->unix_time(); if (slow_mode_next_send_date <= now) { slow_mode_next_send_date = 0; } if (slow_mode_next_send_date > now + 3601) { slow_mode_next_send_date = now + 3601; } } if (channel_full->slow_mode_next_send_date != slow_mode_next_send_date) { channel_full->slow_mode_next_send_date = slow_mode_next_send_date; channel_full->is_slow_mode_next_send_date_changed = true; if (channel_full->unrestrict_boost_count == 0 || channel_full->boost_count < channel_full->unrestrict_boost_count) { channel_full->is_changed = true; } else { channel_full->need_save_to_database = true; } } } bool ChatManager::update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link) { if (new_invite_link != invite_link) { if (invite_link.is_valid() && invite_link.get_invite_link() != new_invite_link.get_invite_link()) { // old link was invalidated td_->dialog_invite_link_manager_->invalidate_invite_link_info(invite_link.get_invite_link()); } invite_link = std::move(new_invite_link); return true; } return false; } void ChatManager::repair_chat_participants(ChatId chat_id) { send_get_chat_full_query(chat_id, Auto(), "repair_chat_participants"); } void ChatManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_user_id, UserId user_id, int32 date, int32 version) { if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return; } if (!td_->user_manager_->have_user(user_id)) { LOG(ERROR) << "Can't find " << user_id; return; } if (!td_->user_manager_->have_user(inviter_user_id)) { LOG(ERROR) << "Can't find " << inviter_user_id; return; } LOG(INFO) << "Receive updateChatParticipantAdd to " << chat_id << " with " << user_id << " invited by " << inviter_user_id << " at " << date << " with version " << version; ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_add_user"); if (chat_full == nullptr) { LOG(INFO) << "Ignoring update about members of " << chat_id; return; } auto c = get_chat(chat_id); if (c == nullptr) { LOG(ERROR) << "Receive updateChatParticipantAdd for unknown " << chat_id << ". Couldn't apply it"; repair_chat_participants(chat_id); return; } if (c->status.is_left()) { // possible if updates come out of order LOG(WARNING) << "Receive updateChatParticipantAdd for left " << chat_id << ". Couldn't apply it"; repair_chat_participants(chat_id); // just in case return; } if (on_update_chat_full_participants_short(chat_full, chat_id, version)) { for (auto &participant : chat_full->participants) { if (participant.dialog_id_ == DialogId(user_id)) { if (participant.inviter_user_id_ != inviter_user_id) { LOG(ERROR) << user_id << " was readded to " << chat_id << " by " << inviter_user_id << ", previously invited by " << participant.inviter_user_id_; participant.inviter_user_id_ = inviter_user_id; participant.joined_date_ = date; repair_chat_participants(chat_id); } else { // Possible if update comes twice LOG(INFO) << user_id << " was readded to " << chat_id; } return; } } chat_full->participants.push_back(DialogParticipant{DialogId(user_id), inviter_user_id, date, user_id == chat_full->creator_user_id ? DialogParticipantStatus::Creator(true, false, string()) : DialogParticipantStatus::Member()}); update_chat_online_member_count(chat_full, chat_id, false); chat_full->is_changed = true; update_chat_full(chat_full, chat_id, "on_update_chat_add_user"); // Chat is already updated if (chat_full->version == c->version && narrow_cast(chat_full->participants.size()) != c->participant_count) { LOG(ERROR) << "Number of members in " << chat_id << " with version " << c->version << " is " << c->participant_count << " but there are " << chat_full->participants.size() << " members in the ChatFull"; repair_chat_participants(chat_id); } } } void ChatManager::on_update_chat_edit_administrator(ChatId chat_id, UserId user_id, bool is_administrator, int32 version) { if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return; } if (!td_->user_manager_->have_user(user_id)) { LOG(ERROR) << "Can't find " << user_id; return; } LOG(INFO) << "Receive updateChatParticipantAdmin in " << chat_id << " with " << user_id << ", administrator rights " << (is_administrator ? "enabled" : "disabled") << " with version " << version; auto c = get_chat_force(chat_id, "on_update_chat_edit_administrator"); if (c == nullptr) { LOG(INFO) << "Ignoring update about members of unknown " << chat_id; return; } if (c->status.is_left()) { // possible if updates come out of order LOG(WARNING) << "Receive updateChatParticipantAdmin for left " << chat_id << ". Couldn't apply it"; repair_chat_participants(chat_id); // just in case return; } if (version <= -1) { LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; return; } CHECK(c->version >= 0); auto status = is_administrator ? DialogParticipantStatus::GroupAdministrator(c->status.is_creator()) : DialogParticipantStatus::Member(); if (version > c->version) { if (version != c->version + 1) { LOG(INFO) << "Administrators of " << chat_id << " with version " << c->version << " has changed, but new version is " << version; repair_chat_participants(chat_id); return; } c->version = version; c->need_save_to_database = true; if (user_id == td_->user_manager_->get_my_id() && !c->status.is_creator()) { // if chat with version was already received, then the update is already processed // so we need to call on_update_chat_status only if version > c->version on_update_chat_status(c, chat_id, status); } update_chat(c, chat_id); } ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_edit_administrator"); if (chat_full != nullptr) { if (chat_full->version + 1 == version) { for (auto &participant : chat_full->participants) { if (participant.dialog_id_ == DialogId(user_id)) { participant.status_ = std::move(status); chat_full->is_changed = true; update_chat_full(chat_full, chat_id, "on_update_chat_edit_administrator"); return; } } } // can't find chat member or version have increased too much repair_chat_participants(chat_id); } } void ChatManager::on_update_chat_delete_user(ChatId chat_id, UserId user_id, int32 version) { if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return; } if (!td_->user_manager_->have_user(user_id)) { LOG(ERROR) << "Can't find " << user_id; return; } LOG(INFO) << "Receive updateChatParticipantDelete from " << chat_id << " with " << user_id << " and version " << version; ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_delete_user"); if (chat_full == nullptr) { LOG(INFO) << "Ignoring update about members of " << chat_id; return; } const Chat *c = get_chat_force(chat_id, "on_update_chat_delete_user"); if (c == nullptr) { LOG(ERROR) << "Receive updateChatParticipantDelete for unknown " << chat_id; repair_chat_participants(chat_id); return; } if (user_id == td_->user_manager_->get_my_id()) { LOG_IF(WARNING, c->status.is_member()) << "User was removed from " << chat_id << " but it is not left the group. Possible if updates comes out of order"; return; } if (c->status.is_left()) { // possible if updates come out of order LOG(INFO) << "Receive updateChatParticipantDelete for left " << chat_id; repair_chat_participants(chat_id); return; } if (on_update_chat_full_participants_short(chat_full, chat_id, version)) { for (size_t i = 0; i < chat_full->participants.size(); i++) { if (chat_full->participants[i].dialog_id_ == DialogId(user_id)) { chat_full->participants[i] = chat_full->participants.back(); chat_full->participants.resize(chat_full->participants.size() - 1); chat_full->is_changed = true; update_chat_online_member_count(chat_full, chat_id, false); update_chat_full(chat_full, chat_id, "on_update_chat_delete_user"); if (static_cast(chat_full->participants.size()) != c->participant_count) { repair_chat_participants(chat_id); } return; } } LOG(ERROR) << "Can't find basic group member " << user_id << " in " << chat_id << " to be removed"; repair_chat_participants(chat_id); } } void ChatManager::on_update_chat_status(Chat *c, ChatId chat_id, DialogParticipantStatus status) { if (c->status != status) { LOG(INFO) << "Update " << chat_id << " status from " << c->status << " to " << status; bool need_reload_group_call = c->status.can_manage_calls() != status.can_manage_calls(); bool need_drop_invite_link = c->status.can_manage_invite_links() && !status.can_manage_invite_links(); c->status = std::move(status); c->is_status_changed = true; if (c->status.is_left()) { c->participant_count = 0; c->version = -1; c->default_permissions_version = -1; c->pinned_message_version = -1; drop_chat_full(chat_id); } else if (need_drop_invite_link) { ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_status"); if (chat_full != nullptr) { on_update_chat_full_invite_link(chat_full, nullptr); update_chat_full(chat_full, chat_id, "on_update_chat_status"); } } if (need_reload_group_call) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, DialogId(chat_id)); } c->is_changed = true; } } void ChatManager::on_update_chat_default_permissions(ChatId chat_id, RestrictedRights default_permissions, int32 version) { if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return; } auto c = get_chat_force(chat_id, "on_update_chat_default_permissions"); if (c == nullptr) { LOG(INFO) << "Ignoring update about unknown " << chat_id; return; } LOG(INFO) << "Receive updateChatDefaultBannedRights in " << chat_id << " with " << default_permissions << " and version " << version << ". Current version is " << c->version; if (c->status.is_left()) { // possible if updates come out of order LOG(WARNING) << "Receive updateChatDefaultBannedRights for left " << chat_id << ". Couldn't apply it"; repair_chat_participants(chat_id); // just in case return; } if (version <= -1) { LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; return; } CHECK(c->version >= 0); if (version > c->version) { // this should be unreachable, because version and default permissions must be already updated from // the chat object in on_get_chat if (version != c->version + 1) { LOG(INFO) << "Default permissions of " << chat_id << " with version " << c->version << " has changed, but new version is " << version; repair_chat_participants(chat_id); return; } LOG_IF(ERROR, default_permissions == c->default_permissions) << "Receive updateChatDefaultBannedRights in " << chat_id << " with version " << version << " and default_permissions = " << default_permissions << ", but default_permissions are not changed. Current version is " << c->version; c->version = version; c->need_save_to_database = true; on_update_chat_default_permissions(c, chat_id, default_permissions, version); update_chat(c, chat_id); } } void ChatManager::on_update_chat_default_permissions(Chat *c, ChatId chat_id, RestrictedRights default_permissions, int32 version) { if (c->default_permissions != default_permissions && version >= c->default_permissions_version) { LOG(INFO) << "Update " << chat_id << " default permissions from " << c->default_permissions << " to " << default_permissions << " and version from " << c->default_permissions_version << " to " << version; c->default_permissions = default_permissions; c->default_permissions_version = version; c->is_default_permissions_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_chat_noforwards(Chat *c, ChatId chat_id, bool noforwards) { if (c->noforwards != noforwards) { LOG(INFO) << "Update " << chat_id << " has_protected_content from " << c->noforwards << " to " << noforwards; c->noforwards = noforwards; c->is_noforwards_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_chat_pinned_message(ChatId chat_id, MessageId pinned_message_id, int32 version) { if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return; } auto c = get_chat_force(chat_id, "on_update_chat_pinned_message"); if (c == nullptr) { LOG(INFO) << "Ignoring update about unknown " << chat_id; return; } LOG(INFO) << "Receive updateChatPinnedMessage in " << chat_id << " with " << pinned_message_id << " and version " << version << ". Current version is " << c->version << "/" << c->pinned_message_version; if (c->status.is_left()) { // possible if updates come out of order repair_chat_participants(chat_id); // just in case return; } if (version <= -1) { LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; return; } CHECK(c->version >= 0); if (version >= c->pinned_message_version) { if (version != c->version + 1 && version != c->version) { LOG(INFO) << "Pinned message of " << chat_id << " with version " << c->version << " has changed, but new version is " << version; repair_chat_participants(chat_id); } else if (version == c->version + 1) { c->version = version; c->need_save_to_database = true; } td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(chat_id), pinned_message_id); if (version > c->pinned_message_version) { LOG(INFO) << "Change pinned message version of " << chat_id << " from " << c->pinned_message_version << " to " << version; c->pinned_message_version = version; c->need_save_to_database = true; } update_chat(c, chat_id); } } void ChatManager::on_update_chat_participant_count(Chat *c, ChatId chat_id, int32 participant_count, int32 version, const string &debug_str) { if (version <= -1) { LOG(ERROR) << "Receive wrong version " << version << " in " << chat_id << debug_str; return; } if (version < c->version) { // some outdated data LOG(INFO) << "Receive number of members in " << chat_id << " with version " << version << debug_str << ", but current version is " << c->version; return; } if (c->participant_count != participant_count) { if (version == c->version && participant_count != 0) { // version is not changed when deleted user is removed from the chat LOG_IF(ERROR, c->participant_count != participant_count + 1) << "Number of members in " << chat_id << " has changed from " << c->participant_count << " to " << participant_count << ", but version " << c->version << " remains unchanged" << debug_str; repair_chat_participants(chat_id); } c->participant_count = participant_count; c->version = version; c->is_changed = true; return; } if (version > c->version) { c->version = version; c->need_save_to_database = true; } } void ChatManager::on_update_chat_photo(Chat *c, ChatId chat_id, tl_object_ptr &&chat_photo_ptr) { on_update_chat_photo( c, chat_id, get_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), 0, std::move(chat_photo_ptr)), true); } void ChatManager::on_update_chat_photo(Chat *c, ChatId chat_id, DialogPhoto &&photo, bool invalidate_photo_cache) { if (td_->auth_manager_->is_bot()) { photo.minithumbnail.clear(); } if (need_update_dialog_photo(c->photo, photo)) { c->photo = std::move(photo); c->is_photo_changed = true; c->need_save_to_database = true; if (invalidate_photo_cache) { auto chat_full = get_chat_full(chat_id); // must not load ChatFull if (chat_full != nullptr) { if (!chat_full->photo.is_empty()) { chat_full->photo = Photo(); chat_full->is_changed = true; } if (c->photo.small_file_id.is_valid()) { reload_chat_full(chat_id, Auto(), "on_update_chat_photo"); } update_chat_full(chat_full, chat_id, "on_update_chat_photo"); } } } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) { c->photo.minithumbnail = std::move(photo.minithumbnail); c->is_photo_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_chat_title(Chat *c, ChatId chat_id, string &&title) { if (c->title != title) { c->title = std::move(title); c->is_title_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_chat_active(Chat *c, ChatId chat_id, bool is_active) { if (c->is_active != is_active) { c->is_active = is_active; c->is_is_active_changed = true; c->is_changed = true; } } void ChatManager::on_update_chat_migrated_to_channel_id(Chat *c, ChatId chat_id, ChannelId migrated_to_channel_id) { if (c->migrated_to_channel_id != migrated_to_channel_id && migrated_to_channel_id.is_valid()) { LOG_IF(ERROR, c->migrated_to_channel_id.is_valid()) << "Upgraded supergroup ID for " << chat_id << " has changed from " << c->migrated_to_channel_id << " to " << migrated_to_channel_id; c->migrated_to_channel_id = migrated_to_channel_id; c->is_changed = true; } } void ChatManager::on_update_chat_description(ChatId chat_id, string &&description) { if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return; } auto chat_full = get_chat_full_force(chat_id, "on_update_chat_description"); if (chat_full == nullptr) { return; } if (chat_full->description != description) { chat_full->description = std::move(description); chat_full->is_changed = true; update_chat_full(chat_full, chat_id, "on_update_chat_description"); td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, true); } } bool ChatManager::on_update_chat_full_participants_short(ChatFull *chat_full, ChatId chat_id, int32 version) { if (version <= -1) { LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; return false; } if (chat_full->version == -1) { // chat members are unknown, nothing to update return false; } if (chat_full->version + 1 == version) { chat_full->version = version; return true; } LOG(INFO) << "Number of members in " << chat_id << " with version " << chat_full->version << " has changed, but new version is " << version; repair_chat_participants(chat_id); return false; } void ChatManager::on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id, vector participants, int32 version, bool from_update) { if (version <= -1) { LOG(ERROR) << "Receive members with wrong version " << version << " in " << chat_id; return; } if (version < chat_full->version) { // some outdated data LOG(WARNING) << "Receive members of " << chat_id << " with version " << version << " but current version is " << chat_full->version; return; } if ((chat_full->participants.size() != participants.size() && version == chat_full->version) || (from_update && version != chat_full->version + 1)) { LOG(INFO) << "Members of " << chat_id << " has changed"; // this is possible in very rare situations repair_chat_participants(chat_id); } chat_full->participants = std::move(participants); chat_full->version = version; chat_full->is_changed = true; update_chat_online_member_count(chat_full, chat_id, true); } void ChatManager::drop_chat_full(ChatId chat_id) { ChatFull *chat_full = get_chat_full_force(chat_id, "drop_chat_full"); if (chat_full == nullptr) { return; } LOG(INFO) << "Drop basicGroupFullInfo of " << chat_id; on_update_chat_full_photo(chat_full, chat_id, Photo()); // chat_full->creator_user_id = UserId(); chat_full->participants.clear(); chat_full->bot_commands.clear(); chat_full->version = -1; on_update_chat_full_invite_link(chat_full, nullptr); update_chat_online_member_count(chat_full, chat_id, true); chat_full->is_changed = true; update_chat_full(chat_full, chat_id, "drop_chat_full"); } void ChatManager::on_update_channel_photo(Channel *c, ChannelId channel_id, tl_object_ptr &&chat_photo_ptr) { on_update_channel_photo( c, channel_id, get_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), c->access_hash, std::move(chat_photo_ptr)), true); } void ChatManager::on_update_chat_bot_commands(ChatId chat_id, BotCommands &&bot_commands) { auto chat_full = get_chat_full_force(chat_id, "on_update_chat_bot_commands"); if (chat_full != nullptr && BotCommands::update_all_bot_commands(chat_full->bot_commands, std::move(bot_commands))) { chat_full->is_changed = true; update_chat_full(chat_full, chat_id, "on_update_chat_bot_commands"); } } void ChatManager::on_update_chat_permanent_invite_link(ChatId chat_id, const DialogInviteLink &invite_link) { auto chat_full = get_chat_full_force(chat_id, "on_update_chat_permanent_invite_link"); if (chat_full != nullptr && update_permanent_invite_link(chat_full->invite_link, invite_link)) { chat_full->is_changed = true; update_chat_full(chat_full, chat_id, "on_update_chat_permanent_invite_link"); } } void ChatManager::on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo, bool invalidate_photo_cache) { if (td_->auth_manager_->is_bot()) { photo.minithumbnail.clear(); } if (need_update_dialog_photo(c->photo, photo)) { c->photo = std::move(photo); c->is_photo_changed = true; c->need_save_to_database = true; if (invalidate_photo_cache) { auto channel_full = get_channel_full(channel_id, true, "on_update_channel_photo"); // must not load ChannelFull if (channel_full != nullptr) { if (!channel_full->photo.is_empty()) { channel_full->photo = Photo(); channel_full->is_changed = true; } if (c->photo.small_file_id.is_valid()) { if (channel_full->expires_at > 0.0) { channel_full->expires_at = 0.0; channel_full->need_save_to_database = true; } reload_channel_full(channel_id, Auto(), "on_update_channel_photo"); } update_channel_full(channel_full, channel_id, "on_update_channel_photo"); } } } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) { c->photo.minithumbnail = std::move(photo.minithumbnail); c->is_photo_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_emoji_status(Channel *c, ChannelId channel_id, EmojiStatus emoji_status) { if (c->emoji_status != emoji_status) { LOG(DEBUG) << "Change emoji status of " << channel_id << " from " << c->emoji_status << " to " << emoji_status; c->emoji_status = std::move(emoji_status); c->is_emoji_status_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_accent_color_id(Channel *c, ChannelId channel_id, AccentColorId accent_color_id) { if (accent_color_id == AccentColorId(channel_id) || !accent_color_id.is_valid()) { accent_color_id = AccentColorId(); } if (c->accent_color_id != accent_color_id) { c->accent_color_id = accent_color_id; c->is_accent_color_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_background_custom_emoji_id(Channel *c, ChannelId channel_id, CustomEmojiId background_custom_emoji_id) { if (c->background_custom_emoji_id != background_custom_emoji_id) { c->background_custom_emoji_id = background_custom_emoji_id; c->is_accent_color_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_profile_accent_color_id(Channel *c, ChannelId channel_id, AccentColorId profile_accent_color_id) { if (!profile_accent_color_id.is_valid()) { profile_accent_color_id = AccentColorId(); } if (c->profile_accent_color_id != profile_accent_color_id) { c->profile_accent_color_id = profile_accent_color_id; c->is_accent_color_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_profile_background_custom_emoji_id( Channel *c, ChannelId channel_id, CustomEmojiId profile_background_custom_emoji_id) { if (c->profile_background_custom_emoji_id != profile_background_custom_emoji_id) { c->profile_background_custom_emoji_id = profile_background_custom_emoji_id; c->is_accent_color_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_title(Channel *c, ChannelId channel_id, string &&title) { if (c->title != title) { c->title = std::move(title); c->is_title_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status) { if (c->status != status) { LOG(INFO) << "Update " << channel_id << " status from " << c->status << " to " << status; if (c->is_update_supergroup_sent) { on_channel_status_changed(c, channel_id, c->status, status); } c->status = status; c->is_status_changed = true; c->is_changed = true; } } void ChatManager::on_channel_status_changed(Channel *c, ChannelId channel_id, const DialogParticipantStatus &old_status, const DialogParticipantStatus &new_status) { CHECK(c->is_update_supergroup_sent); bool have_channel_full = get_channel_full(channel_id) != nullptr; if (old_status.can_post_stories() != new_status.can_post_stories()) { td_->story_manager_->update_dialogs_to_send_stories(channel_id, new_status.can_post_stories()); } bool need_reload_group_call = old_status.can_manage_calls() != new_status.can_manage_calls(); if (old_status.can_manage_invite_links() && !new_status.can_manage_invite_links()) { auto channel_full = get_channel_full(channel_id, true, "on_channel_status_changed"); if (channel_full != nullptr) { // otherwise invite_link will be dropped when the channel is loaded on_update_channel_full_invite_link(channel_full, nullptr); do_invalidate_channel_full(channel_full, channel_id, !c->is_slow_mode_enabled); update_channel_full(channel_full, channel_id, "on_channel_status_changed"); } } else { invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_status_changed"); } if (old_status.is_creator() != new_status.is_creator()) { c->is_creator_changed = true; send_get_channel_full_query(nullptr, channel_id, Auto(), "update channel owner"); td_->dialog_participant_manager_->reload_dialog_administrators(DialogId(channel_id), {}, Auto()); td_->dialog_manager_->remove_dialog_suggested_action( SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); } if (old_status.is_member() != new_status.is_member() || new_status.is_banned()) { td_->dialog_invite_link_manager_->remove_dialog_access_by_invite_link(DialogId(channel_id)); if (new_status.is_member() || new_status.is_creator()) { reload_channel_full(channel_id, PromiseCreator::lambda([channel_id](Unit) { LOG(INFO) << "Reloaded full " << channel_id; }), "on_channel_status_changed"); } } if (need_reload_group_call) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, DialogId(channel_id)); } bool is_bot = td_->auth_manager_->is_bot(); if (is_bot && old_status.is_administrator() && !new_status.is_administrator()) { td_->dialog_participant_manager_->drop_channel_participant_cache(channel_id); } if (is_bot && old_status.is_member() && !new_status.is_member() && !G()->use_message_database()) { send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id), Promise()); } if (!is_bot && old_status.is_member() != new_status.is_member()) { DialogId dialog_id(channel_id); if (new_status.is_member()) { send_closure_later(td_->story_manager_actor_, &StoryManager::reload_dialog_expiring_stories, dialog_id); } else { send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, dialog_id, "on_channel_status_changed"); } send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "on_channel_status_changed", true, true); } // must not load ChannelFull, because must not change the Channel CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr)); } void ChatManager::on_update_channel_default_permissions(Channel *c, ChannelId channel_id, RestrictedRights default_permissions) { if (c->is_megagroup && c->default_permissions != default_permissions) { LOG(INFO) << "Update " << channel_id << " default permissions from " << c->default_permissions << " to " << default_permissions; c->default_permissions = default_permissions; c->is_default_permissions_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_has_location(Channel *c, ChannelId channel_id, bool has_location) { if (c->has_location != has_location) { LOG(INFO) << "Update " << channel_id << " has_location from " << c->has_location << " to " << has_location; c->has_location = has_location; c->is_has_location_changed = true; c->is_changed = true; } } void ChatManager::on_update_channel_noforwards(Channel *c, ChannelId channel_id, bool noforwards) { if (c->noforwards != noforwards) { LOG(INFO) << "Update " << channel_id << " has_protected_content from " << c->noforwards << " to " << noforwards; c->noforwards = noforwards; c->is_noforwards_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_story_ids(ChannelId channel_id, StoryId max_active_story_id, StoryId max_read_story_id) { if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; return; } Channel *c = get_channel_force(channel_id, "on_update_channel_story_ids"); if (c != nullptr) { on_update_channel_story_ids_impl(c, channel_id, max_active_story_id, max_read_story_id); update_channel(c, channel_id); } else { LOG(INFO) << "Ignore update channel story identifiers about unknown " << channel_id; } } void ChatManager::on_update_channel_story_ids_impl(Channel *c, ChannelId channel_id, StoryId max_active_story_id, StoryId max_read_story_id) { if (td_->auth_manager_->is_bot()) { return; } if (max_active_story_id != StoryId() && !max_active_story_id.is_server()) { LOG(ERROR) << "Receive max active " << max_active_story_id << " for " << channel_id; return; } if (max_read_story_id != StoryId() && !max_read_story_id.is_server()) { LOG(ERROR) << "Receive max read " << max_read_story_id << " for " << channel_id; return; } auto has_unread_stories = get_channel_has_unread_stories(c); if (c->max_active_story_id != max_active_story_id) { LOG(DEBUG) << "Change last active story of " << channel_id << " from " << c->max_active_story_id << " to " << max_active_story_id; c->max_active_story_id = max_active_story_id; c->need_save_to_database = true; } if (need_poll_channel_active_stories(c, channel_id)) { auto max_active_story_id_next_reload_time = Time::now() + MAX_ACTIVE_STORY_ID_RELOAD_TIME; if (max_active_story_id_next_reload_time > c->max_active_story_id_next_reload_time + MAX_ACTIVE_STORY_ID_RELOAD_TIME / 5) { LOG(DEBUG) << "Change max_active_story_id_next_reload_time of " << channel_id; c->max_active_story_id_next_reload_time = max_active_story_id_next_reload_time; c->need_save_to_database = true; } } if (!max_active_story_id.is_valid()) { CHECK(max_read_story_id == StoryId()); if (c->max_read_story_id != StoryId()) { LOG(DEBUG) << "Drop last read " << c->max_read_story_id << " of " << channel_id; c->max_read_story_id = StoryId(); c->need_save_to_database = true; } } else if (max_read_story_id.get() > c->max_read_story_id.get()) { LOG(DEBUG) << "Change last read story of " << channel_id << " from " << c->max_read_story_id << " to " << max_read_story_id; c->max_read_story_id = max_read_story_id; c->need_save_to_database = true; } if (has_unread_stories != get_channel_has_unread_stories(c)) { LOG(DEBUG) << "Change has_unread_stories of " << channel_id << " to " << !has_unread_stories; c->is_changed = true; } } void ChatManager::on_update_channel_max_read_story_id(ChannelId channel_id, StoryId max_read_story_id) { CHECK(channel_id.is_valid()); Channel *c = get_channel(channel_id); if (c != nullptr) { on_update_channel_max_read_story_id(c, channel_id, max_read_story_id); update_channel(c, channel_id); } } void ChatManager::on_update_channel_max_read_story_id(Channel *c, ChannelId channel_id, StoryId max_read_story_id) { if (td_->auth_manager_->is_bot()) { return; } auto has_unread_stories = get_channel_has_unread_stories(c); if (max_read_story_id.get() > c->max_read_story_id.get()) { LOG(DEBUG) << "Change last read story of " << channel_id << " from " << c->max_read_story_id << " to " << max_read_story_id; c->max_read_story_id = max_read_story_id; c->need_save_to_database = true; } if (has_unread_stories != get_channel_has_unread_stories(c)) { LOG(DEBUG) << "Change has_unread_stories of " << channel_id << " to " << !has_unread_stories; c->is_changed = true; } } void ChatManager::on_update_channel_stories_hidden(ChannelId channel_id, bool stories_hidden) { if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; return; } Channel *c = get_channel_force(channel_id, "on_update_channel_stories_hidden"); if (c != nullptr) { on_update_channel_stories_hidden(c, channel_id, stories_hidden); update_channel(c, channel_id); } else { LOG(INFO) << "Ignore update channel stories are archived about unknown " << channel_id; } } void ChatManager::on_update_channel_stories_hidden(Channel *c, ChannelId channel_id, bool stories_hidden) { if (td_->auth_manager_->is_bot()) { return; } if (c->stories_hidden != stories_hidden) { LOG(DEBUG) << "Change stories are archived of " << channel_id << " to " << stories_hidden; c->stories_hidden = stories_hidden; c->is_stories_hidden_changed = true; c->need_save_to_database = true; } } void ChatManager::on_update_channel_participant_count(ChannelId channel_id, int32 participant_count) { Channel *c = get_channel(channel_id); if (c == nullptr || c->participant_count == participant_count) { return; } c->participant_count = participant_count; c->is_changed = true; update_channel(c, channel_id); auto channel_full = get_channel_full(channel_id, true, "on_update_channel_participant_count"); if (channel_full != nullptr && channel_full->participant_count != participant_count) { if (channel_full->administrator_count > participant_count) { channel_full->administrator_count = participant_count; } channel_full->participant_count = participant_count; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_participant_count"); } } void ChatManager::on_update_channel_editable_username(ChannelId channel_id, string &&username) { Channel *c = get_channel(channel_id); CHECK(c != nullptr); on_update_channel_usernames(c, channel_id, c->usernames.change_editable_username(std::move(username))); update_channel(c, channel_id); } void ChatManager::on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames) { if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; return; } Channel *c = get_channel_force(channel_id, "on_update_channel_usernames"); if (c != nullptr) { on_update_channel_usernames(c, channel_id, std::move(usernames)); update_channel(c, channel_id); } else { LOG(INFO) << "Ignore update channel usernames about unknown " << channel_id; } } void ChatManager::on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames) { if (c->usernames != usernames) { td_->dialog_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); td_->messages_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); if (c->is_update_supergroup_sent) { on_channel_usernames_changed(c, channel_id, c->usernames, usernames); } c->usernames = std::move(usernames); c->is_username_changed = true; c->is_changed = true; } else { td_->dialog_manager_->on_dialog_usernames_received(DialogId(channel_id), usernames, false); } } void ChatManager::on_channel_usernames_changed(const Channel *c, ChannelId channel_id, const Usernames &old_usernames, const Usernames &new_usernames) { bool have_channel_full = get_channel_full(channel_id) != nullptr; if (!old_usernames.has_first_username() || !new_usernames.has_first_username()) { // moving channel from private to public can change availability of chat members invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_usernames_changed"); } // must not load ChannelFull, because must not change the Channel CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr)); } void ChatManager::on_update_channel_description(ChannelId channel_id, string &&description) { CHECK(channel_id.is_valid()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_description"); if (channel_full == nullptr) { return; } if (channel_full->description != description) { channel_full->description = std::move(description); channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_description"); td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, true); } } void ChatManager::on_update_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id) { CHECK(channel_id.is_valid()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_sticker_set"); if (channel_full == nullptr) { return; } if (channel_full->sticker_set_id != sticker_set_id) { channel_full->sticker_set_id = sticker_set_id; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_sticker_set"); } } void ChatManager::on_update_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id) { CHECK(channel_id.is_valid()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_emoji_sticker_set"); if (channel_full == nullptr) { return; } if (channel_full->emoji_sticker_set_id != sticker_set_id) { channel_full->emoji_sticker_set_id = sticker_set_id; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_emoji_sticker_set"); } } void ChatManager::on_update_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count) { CHECK(channel_id.is_valid()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_unrestrict_boost_count"); if (channel_full == nullptr) { return; } if (channel_full->unrestrict_boost_count != unrestrict_boost_count) { channel_full->unrestrict_boost_count = unrestrict_boost_count; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_unrestrict_boost_count"); } } void ChatManager::on_update_channel_linked_channel_id(ChannelId channel_id, ChannelId group_channel_id) { if (channel_id.is_valid()) { auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_linked_channel_id 1"); on_update_channel_full_linked_channel_id(channel_full, channel_id, group_channel_id); if (channel_full != nullptr) { update_channel_full(channel_full, channel_id, "on_update_channel_linked_channel_id 3"); } } if (group_channel_id.is_valid()) { auto channel_full = get_channel_full_force(group_channel_id, true, "on_update_channel_linked_channel_id 2"); on_update_channel_full_linked_channel_id(channel_full, group_channel_id, channel_id); if (channel_full != nullptr) { update_channel_full(channel_full, group_channel_id, "on_update_channel_linked_channel_id 4"); } } } void ChatManager::on_update_channel_location(ChannelId channel_id, const DialogLocation &location) { auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_location"); if (channel_full != nullptr) { on_update_channel_full_location(channel_full, channel_id, location); update_channel_full(channel_full, channel_id, "on_update_channel_location"); } } void ChatManager::on_update_channel_slow_mode_delay(ChannelId channel_id, int32 slow_mode_delay, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_slow_mode_delay"); if (channel_full != nullptr) { on_update_channel_full_slow_mode_delay(channel_full, channel_id, slow_mode_delay, 0); update_channel_full(channel_full, channel_id, "on_update_channel_slow_mode_delay"); } promise.set_value(Unit()); } void ChatManager::on_update_channel_slow_mode_next_send_date(ChannelId channel_id, int32 slow_mode_next_send_date) { auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_slow_mode_next_send_date"); if (channel_full != nullptr) { on_update_channel_full_slow_mode_next_send_date(channel_full, slow_mode_next_send_date); update_channel_full(channel_full, channel_id, "on_update_channel_slow_mode_next_send_date"); } } void ChatManager::on_update_channel_bot_user_ids(ChannelId channel_id, vector &&bot_user_ids) { CHECK(channel_id.is_valid()); if (!have_channel(channel_id)) { LOG(ERROR) << channel_id << " not found"; return; } auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_bot_user_ids"); if (channel_full == nullptr) { send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), std::move(bot_user_ids), false); return; } on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids)); update_channel_full(channel_full, channel_id, "on_update_channel_bot_user_ids"); } void ChatManager::on_update_channel_full_bot_user_ids(ChannelFull *channel_full, ChannelId channel_id, vector &&bot_user_ids) { CHECK(channel_full != nullptr); send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), bot_user_ids, false); if (channel_full->bot_user_ids != bot_user_ids) { channel_full->bot_user_ids = std::move(bot_user_ids); channel_full->need_save_to_database = true; } } void ChatManager::on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(channel_id.is_valid()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_is_all_history_available"); if (channel_full != nullptr && channel_full->is_all_history_available != is_all_history_available) { channel_full->is_all_history_available = is_all_history_available; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_is_all_history_available"); } promise.set_value(Unit()); } void ChatManager::on_update_channel_can_have_sponsored_messages(ChannelId channel_id, bool can_have_sponsored_messages, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(channel_id.is_valid()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_can_have_sponsored_messages"); if (channel_full != nullptr && channel_full->can_have_sponsored_messages != can_have_sponsored_messages) { channel_full->can_have_sponsored_messages = can_have_sponsored_messages; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_can_have_sponsored_messages"); } promise.set_value(Unit()); } void ChatManager::on_update_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(channel_id.is_valid()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_hidden_participants"); if (channel_full != nullptr && channel_full->has_hidden_participants != has_hidden_participants) { channel_full->has_hidden_participants = has_hidden_participants; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_has_hidden_participants"); } promise.set_value(Unit()); } void ChatManager::on_update_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, bool has_aggressive_anti_spam_enabled, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(channel_id.is_valid()); auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_aggressive_anti_spam_enabled"); if (channel_full != nullptr && channel_full->has_aggressive_anti_spam_enabled != has_aggressive_anti_spam_enabled) { channel_full->has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_has_aggressive_anti_spam_enabled"); } promise.set_value(Unit()); } void ChatManager::on_update_channel_has_pinned_stories(ChannelId channel_id, bool has_pinned_stories) { if (td_->auth_manager_->is_bot()) { return; } if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; return; } ChannelFull *channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_pinned_stories"); if (channel_full == nullptr || channel_full->has_pinned_stories == has_pinned_stories) { return; } channel_full->has_pinned_stories = has_pinned_stories; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_has_pinned_stories"); } void ChatManager::on_update_channel_default_permissions(ChannelId channel_id, RestrictedRights default_permissions) { if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; return; } Channel *c = get_channel_force(channel_id, "on_update_channel_default_permissions"); if (c != nullptr) { on_update_channel_default_permissions(c, channel_id, std::move(default_permissions)); update_channel(c, channel_id); } else { LOG(INFO) << "Ignore update channel default permissions about unknown " << channel_id; } } FileSourceId ChatManager::get_chat_full_file_source_id(ChatId chat_id) { if (!chat_id.is_valid()) { return FileSourceId(); } auto chat_full = get_chat_full(chat_id); if (chat_full != nullptr) { VLOG(file_references) << "Don't need to create file source for full " << chat_id; // chat full was already added, source ID was registered and shouldn't be needed return chat_full->is_update_chat_full_sent ? FileSourceId() : chat_full->file_source_id; } auto &source_id = chat_full_file_source_ids_[chat_id]; if (!source_id.is_valid()) { source_id = td_->file_reference_manager_->create_chat_full_file_source(chat_id); } VLOG(file_references) << "Return " << source_id << " for full " << chat_id; return source_id; } FileSourceId ChatManager::get_channel_full_file_source_id(ChannelId channel_id) { if (!channel_id.is_valid()) { return FileSourceId(); } auto channel_full = get_channel_full(channel_id); if (channel_full != nullptr) { VLOG(file_references) << "Don't need to create file source for full " << channel_id; // channel full was already added, source ID was registered and shouldn't be needed return channel_full->is_update_channel_full_sent ? FileSourceId() : channel_full->file_source_id; } auto &source_id = channel_full_file_source_ids_[channel_id]; if (!source_id.is_valid()) { source_id = td_->file_reference_manager_->create_channel_full_file_source(channel_id); } VLOG(file_references) << "Return " << source_id << " for full " << channel_id; return source_id; } void ChatManager::create_new_chat(const vector &user_ids, const string &title, MessageTtl message_ttl, Promise> &&promise) { auto new_title = clean_name(title, MAX_TITLE_LENGTH); if (new_title.empty()) { return promise.set_error(Status::Error(400, "Title must be non-empty")); } vector> input_users; for (auto user_id : user_ids) { TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); input_users.push_back(std::move(input_user)); } td_->create_handler(std::move(promise))->send(std::move(input_users), new_title, message_ttl); } void ChatManager::create_new_channel(const string &title, bool is_forum, bool is_megagroup, const string &description, const DialogLocation &location, bool for_import, MessageTtl message_ttl, Promise> &&promise) { auto new_title = clean_name(title, MAX_TITLE_LENGTH); if (new_title.empty()) { return promise.set_error(Status::Error(400, "Title must be non-empty")); } td_->create_handler(std::move(promise)) ->send(new_title, is_forum, is_megagroup, strip_empty_characters(description, MAX_DESCRIPTION_LENGTH), location, for_import, message_ttl); } bool ChatManager::have_chat(ChatId chat_id) const { return chats_.count(chat_id) > 0; } const ChatManager::Chat *ChatManager::get_chat(ChatId chat_id) const { return chats_.get_pointer(chat_id); } ChatManager::Chat *ChatManager::get_chat(ChatId chat_id) { return chats_.get_pointer(chat_id); } ChatManager::Chat *ChatManager::add_chat(ChatId chat_id) { CHECK(chat_id.is_valid()); auto &chat_ptr = chats_[chat_id]; if (chat_ptr == nullptr) { chat_ptr = make_unique(); } return chat_ptr.get(); } bool ChatManager::get_chat(ChatId chat_id, int left_tries, Promise &&promise) { if (!chat_id.is_valid()) { promise.set_error(Status::Error(400, "Invalid basic group identifier")); return false; } if (!have_chat(chat_id)) { if (left_tries > 2 && G()->use_chat_info_database()) { send_closure_later(actor_id(this), &ChatManager::load_chat_from_database, nullptr, chat_id, std::move(promise)); return false; } if (left_tries > 1) { get_chat_queries_.add_query(chat_id.get(), std::move(promise), "get_chat"); return false; } promise.set_error(Status::Error(400, "Group not found")); return false; } promise.set_value(Unit()); return true; } void ChatManager::reload_chat(ChatId chat_id, Promise &&promise, const char *source) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (!chat_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid basic group identifier")); } get_chat_queries_.add_query(chat_id.get(), std::move(promise), source); } const ChatManager::ChatFull *ChatManager::get_chat_full(ChatId chat_id) const { return chats_full_.get_pointer(chat_id); } ChatManager::ChatFull *ChatManager::get_chat_full(ChatId chat_id) { return chats_full_.get_pointer(chat_id); } ChatManager::ChatFull *ChatManager::add_chat_full(ChatId chat_id) { CHECK(chat_id.is_valid()); auto &chat_full_ptr = chats_full_[chat_id]; if (chat_full_ptr == nullptr) { chat_full_ptr = make_unique(); } return chat_full_ptr.get(); } bool ChatManager::is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id, bool only_participants) const { CHECK(c != nullptr); CHECK(chat_full != nullptr); if (!c->is_active && chat_full->version == -1) { return false; } if (chat_full->version != c->version) { LOG(INFO) << "Have outdated ChatFull " << chat_id << " with current version " << chat_full->version << " and chat version " << c->version; return true; } if (!only_participants && c->is_active && c->status.can_manage_invite_links() && !chat_full->invite_link.is_valid()) { LOG(INFO) << "Have outdated invite link in " << chat_id; return true; } if (!only_participants && !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { LOG(INFO) << "Have outdated chat photo in " << chat_id; return true; } LOG(DEBUG) << "Full " << chat_id << " is up-to-date with version " << chat_full->version << " and photos " << c->photo << '/' << chat_full->photo; return false; } void ChatManager::load_chat_full(ChatId chat_id, bool force, Promise &&promise, const char *source) { auto c = get_chat(chat_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Group not found")); } auto chat_full = get_chat_full_force(chat_id, source); if (chat_full == nullptr) { LOG(INFO) << "Full " << chat_id << " not found"; return send_get_chat_full_query(chat_id, std::move(promise), source); } if (is_chat_full_outdated(chat_full, c, chat_id, false)) { LOG(INFO) << "Have outdated full " << chat_id; if (td_->auth_manager_->is_bot() && !force) { return send_get_chat_full_query(chat_id, std::move(promise), source); } send_get_chat_full_query(chat_id, Auto(), source); } vector participant_dialog_ids; for (const auto &dialog_participant : chat_full->participants) { participant_dialog_ids.push_back(dialog_participant.dialog_id_); } td_->story_manager_->on_view_dialog_active_stories(std::move(participant_dialog_ids)); promise.set_value(Unit()); } void ChatManager::reload_chat_full(ChatId chat_id, Promise &&promise, const char *source) { send_get_chat_full_query(chat_id, std::move(promise), source); } void ChatManager::send_get_chat_full_query(ChatId chat_id, Promise &&promise, const char *source) { LOG(INFO) << "Get full " << chat_id << " from " << source; if (!chat_id.is_valid()) { return promise.set_error(Status::Error(500, "Invalid chat_id")); } auto send_query = PromiseCreator::lambda([td = td_, chat_id](Result> &&promise) { if (promise.is_ok() && !G()->close_flag()) { td->create_handler(promise.move_as_ok())->send(chat_id); } }); get_chat_full_queries_.add_query(DialogId(chat_id).get(), std::move(send_query), std::move(promise)); } int32 ChatManager::get_chat_date(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return 0; } return c->date; } int32 ChatManager::get_chat_participant_count(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return 0; } return c->participant_count; } bool ChatManager::get_chat_is_active(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return false; } return c->is_active; } ChannelId ChatManager::get_chat_migrated_to_channel_id(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return ChannelId(); } return c->migrated_to_channel_id; } DialogParticipantStatus ChatManager::get_chat_status(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return DialogParticipantStatus::Banned(0); } return get_chat_status(c); } DialogParticipantStatus ChatManager::get_chat_status(const Chat *c) { if (!c->is_active) { return DialogParticipantStatus::Banned(0); } return c->status; } DialogParticipantStatus ChatManager::get_chat_permissions(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return DialogParticipantStatus::Banned(0); } return get_chat_permissions(c); } DialogParticipantStatus ChatManager::get_chat_permissions(const Chat *c) const { if (!c->is_active) { return DialogParticipantStatus::Banned(0); } return c->status.apply_restrictions(c->default_permissions, false, td_->auth_manager_->is_bot()); } bool ChatManager::is_appointed_chat_administrator(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return false; } return c->status.is_administrator(); } bool ChatManager::is_channel_public(ChannelId channel_id) const { return is_channel_public(get_channel(channel_id)); } bool ChatManager::is_channel_public(const Channel *c) { return c != nullptr && (c->usernames.has_first_username() || c->has_location); } ChannelType ChatManager::get_channel_type(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { auto min_channel = get_min_channel(channel_id); if (min_channel != nullptr) { return min_channel->is_megagroup_ ? ChannelType::Megagroup : ChannelType::Broadcast; } return ChannelType::Unknown; } return get_channel_type(c); } ChannelType ChatManager::get_channel_type(const Channel *c) { if (c->is_megagroup) { return ChannelType::Megagroup; } return ChannelType::Broadcast; } bool ChatManager::is_broadcast_channel(ChannelId channel_id) const { return get_channel_type(channel_id) == ChannelType::Broadcast; } bool ChatManager::is_megagroup_channel(ChannelId channel_id) const { return get_channel_type(channel_id) == ChannelType::Megagroup; } bool ChatManager::is_forum_channel(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return c->is_forum; } int32 ChatManager::get_channel_date(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return 0; } return c->date; } DialogParticipantStatus ChatManager::get_channel_status(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return DialogParticipantStatus::Banned(0); } return get_channel_status(c); } DialogParticipantStatus ChatManager::get_channel_status(const Channel *c) { c->status.update_restrictions(); return c->status; } DialogParticipantStatus ChatManager::get_channel_permissions(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return DialogParticipantStatus::Banned(0); } return get_channel_permissions(channel_id, c); } DialogParticipantStatus ChatManager::get_channel_permissions(ChannelId channel_id, const Channel *c) const { c->status.update_restrictions(); bool is_booster = false; if (!td_->auth_manager_->is_bot() && c->is_megagroup) { auto channel_full = get_channel_full_const(channel_id); if (channel_full == nullptr || (channel_full->unrestrict_boost_count > 0 && channel_full->boost_count >= channel_full->unrestrict_boost_count)) { is_booster = true; } } return c->status.apply_restrictions(c->default_permissions, is_booster, td_->auth_manager_->is_bot()); } int32 ChatManager::get_channel_participant_count(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return 0; } return c->participant_count; } bool ChatManager::get_channel_is_verified(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return c->is_verified; } bool ChatManager::get_channel_is_scam(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return c->is_scam; } bool ChatManager::get_channel_is_fake(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return c->is_fake; } bool ChatManager::get_channel_sign_messages(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return get_channel_sign_messages(c); } bool ChatManager::get_channel_sign_messages(const Channel *c) { return c->sign_messages; } bool ChatManager::get_channel_has_linked_channel(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return get_channel_has_linked_channel(c); } bool ChatManager::get_channel_has_linked_channel(const Channel *c) { return c->has_linked_channel; } bool ChatManager::get_channel_can_be_deleted(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return get_channel_can_be_deleted(c); } bool ChatManager::get_channel_can_be_deleted(const Channel *c) { return c->can_be_deleted; } bool ChatManager::get_channel_join_to_send(const Channel *c) { return c->join_to_send || !c->is_megagroup || !c->has_linked_channel; } bool ChatManager::get_channel_join_request(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return false; } return get_channel_join_request(c); } bool ChatManager::get_channel_join_request(const Channel *c) { return c->join_request && c->is_megagroup && (is_channel_public(c) || c->has_linked_channel); } ChannelId ChatManager::get_channel_linked_channel_id(ChannelId channel_id, const char *source) { auto channel_full = get_channel_full_const(channel_id); if (channel_full == nullptr) { channel_full = get_channel_full_force(channel_id, true, source); if (channel_full == nullptr) { return ChannelId(); } } return channel_full->linked_channel_id; } int32 ChatManager::get_channel_slow_mode_delay(ChannelId channel_id, const char *source) { auto channel_full = get_channel_full_const(channel_id); if (channel_full == nullptr) { channel_full = get_channel_full_force(channel_id, true, source); if (channel_full == nullptr) { return 0; } } return channel_full->slow_mode_delay; } bool ChatManager::get_channel_effective_has_hidden_participants(ChannelId channel_id, const char *source) { auto c = get_channel_force(channel_id, "get_channel_effective_has_hidden_participants"); if (c == nullptr) { return true; } if (get_channel_status(c).is_administrator()) { return false; } auto channel_full = get_channel_full_const(channel_id); if (channel_full == nullptr) { channel_full = get_channel_full_force(channel_id, true, source); if (channel_full == nullptr) { return true; } } return channel_full->has_hidden_participants || !channel_full->can_get_participants; } int32 ChatManager::get_channel_my_boost_count(ChannelId channel_id) { auto channel_full = get_channel_full_const(channel_id); if (channel_full == nullptr) { channel_full = get_channel_full_force(channel_id, true, "get_channel_my_boost_count"); if (channel_full == nullptr) { return 0; } } return channel_full->boost_count; } bool ChatManager::have_channel(ChannelId channel_id) const { return channels_.count(channel_id) > 0; } bool ChatManager::have_min_channel(ChannelId channel_id) const { return min_channels_.count(channel_id) > 0; } const MinChannel *ChatManager::get_min_channel(ChannelId channel_id) const { return min_channels_.get_pointer(channel_id); } void ChatManager::add_min_channel(ChannelId channel_id, const MinChannel &min_channel) { if (have_channel(channel_id) || have_min_channel(channel_id) || !channel_id.is_valid()) { return; } min_channels_.set(channel_id, td::make_unique(min_channel)); } const ChatManager::Channel *ChatManager::get_channel(ChannelId channel_id) const { return channels_.get_pointer(channel_id); } ChatManager::Channel *ChatManager::get_channel(ChannelId channel_id) { return channels_.get_pointer(channel_id); } ChatManager::Channel *ChatManager::add_channel(ChannelId channel_id, const char *source) { CHECK(channel_id.is_valid()); auto &channel_ptr = channels_[channel_id]; if (channel_ptr == nullptr) { channel_ptr = make_unique(); min_channels_.erase(channel_id); } return channel_ptr.get(); } bool ChatManager::get_channel(ChannelId channel_id, int left_tries, Promise &&promise) { if (!channel_id.is_valid()) { promise.set_error(Status::Error(400, "Invalid supergroup identifier")); return false; } if (!have_channel(channel_id)) { if (left_tries > 2 && G()->use_chat_info_database()) { send_closure_later(actor_id(this), &ChatManager::load_channel_from_database, nullptr, channel_id, std::move(promise)); return false; } if (left_tries > 1 && td_->auth_manager_->is_bot()) { get_channel_queries_.add_query(channel_id.get(), std::move(promise), "get_channel"); return false; } promise.set_error(Status::Error(400, "Supergroup not found")); return false; } promise.set_value(Unit()); return true; } void ChatManager::reload_channel(ChannelId channel_id, Promise &&promise, const char *source) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (!channel_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid supergroup identifier")); } have_channel_force(channel_id, source); auto input_channel = get_input_channel(channel_id); if (input_channel == nullptr) { // requests with 0 access_hash must not be merged td_->create_handler(std::move(promise)) ->send(telegram_api::make_object(channel_id.get(), 0)); return; } get_channel_queries_.add_query(channel_id.get(), std::move(promise), source); } const ChatManager::ChannelFull *ChatManager::get_channel_full_const(ChannelId channel_id) const { return channels_full_.get_pointer(channel_id); } const ChatManager::ChannelFull *ChatManager::get_channel_full(ChannelId channel_id) const { return channels_full_.get_pointer(channel_id); } ChatManager::ChannelFull *ChatManager::get_channel_full(ChannelId channel_id, bool only_local, const char *source) { auto channel_full = channels_full_.get_pointer(channel_id); if (channel_full == nullptr) { return nullptr; } if (!only_local && channel_full->is_expired() && !td_->auth_manager_->is_bot()) { send_get_channel_full_query(channel_full, channel_id, Auto(), source); } return channel_full; } ChatManager::ChannelFull *ChatManager::add_channel_full(ChannelId channel_id) { CHECK(channel_id.is_valid()); auto &channel_full_ptr = channels_full_[channel_id]; if (channel_full_ptr == nullptr) { channel_full_ptr = make_unique(); } return channel_full_ptr.get(); } void ChatManager::load_channel_full(ChannelId channel_id, bool force, Promise &&promise, const char *source) { auto channel_full = get_channel_full_force(channel_id, true, source); if (channel_full == nullptr) { return send_get_channel_full_query(channel_full, channel_id, std::move(promise), source); } if (channel_full->is_expired()) { if (td_->auth_manager_->is_bot() && !force) { return send_get_channel_full_query(channel_full, channel_id, std::move(promise), "load expired channel_full"); } Promise new_promise; if (promise) { new_promise = PromiseCreator::lambda([channel_id](Result result) { if (result.is_error()) { LOG(INFO) << "Failed to reload expired " << channel_id << ": " << result.error(); } else { LOG(INFO) << "Reloaded expired " << channel_id; } }); } send_get_channel_full_query(channel_full, channel_id, std::move(new_promise), "load expired channel_full"); } promise.set_value(Unit()); } void ChatManager::reload_channel_full(ChannelId channel_id, Promise &&promise, const char *source) { send_get_channel_full_query(get_channel_full(channel_id, true, "reload_channel_full"), channel_id, std::move(promise), source); } void ChatManager::send_get_channel_full_query(ChannelFull *channel_full, ChannelId channel_id, Promise &&promise, const char *source) { auto input_channel = get_input_channel(channel_id); if (input_channel == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); } if (!have_input_peer_channel(channel_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (channel_full != nullptr) { if (!promise) { if (channel_full->repair_request_version != 0) { LOG(INFO) << "Skip get full " << channel_id << " request from " << source; return; } channel_full->repair_request_version = channel_full->speculative_version; } else { channel_full->repair_request_version = std::numeric_limits::max(); } } LOG(INFO) << "Get full " << channel_id << " from " << source; auto send_query = PromiseCreator::lambda( [td = td_, channel_id, input_channel = std::move(input_channel)](Result> &&promise) mutable { if (promise.is_ok() && !G()->close_flag()) { td->create_handler(promise.move_as_ok())->send(channel_id, std::move(input_channel)); } }); get_chat_full_queries_.add_query(DialogId(channel_id).get(), std::move(send_query), std::move(promise)); } void ChatManager::get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise) { LOG(INFO) << "Trying to get " << user_id << " as member of " << chat_id; auto c = get_chat(chat_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Group not found")); } if (td_->auth_manager_->is_bot() && user_id == td_->user_manager_->get_my_id()) { // bots don't need inviter information reload_chat(chat_id, Auto(), "get_chat_participant"); return promise.set_value(DialogParticipant{DialogId(user_id), user_id, c->date, c->status}); } auto chat_full = get_chat_full_force(chat_id, "get_chat_participant"); if (chat_full == nullptr || (td_->auth_manager_->is_bot() && is_chat_full_outdated(chat_full, c, chat_id, true))) { auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), chat_id, user_id, promise = std::move(promise)](Result &&result) mutable { TRY_STATUS_PROMISE(promise, std::move(result)); send_closure(actor_id, &ChatManager::finish_get_chat_participant, chat_id, user_id, std::move(promise)); }); send_get_chat_full_query(chat_id, std::move(query_promise), "get_chat_participant"); return; } if (is_chat_full_outdated(chat_full, c, chat_id, true)) { send_get_chat_full_query(chat_id, Auto(), "get_chat_participant lazy"); } finish_get_chat_participant(chat_id, user_id, std::move(promise)); } void ChatManager::finish_get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); const auto *participant = get_chat_participant(chat_id, user_id); if (participant == nullptr) { return promise.set_value(DialogParticipant::left(DialogId(user_id))); } promise.set_value(DialogParticipant(*participant)); } void ChatManager::on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count) { auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_administrator_count"); if (channel_full != nullptr && channel_full->administrator_count != administrator_count) { channel_full->administrator_count = administrator_count; channel_full->is_changed = true; if (channel_full->participant_count < channel_full->administrator_count) { channel_full->participant_count = channel_full->administrator_count; auto c = get_channel(channel_id); if (c != nullptr && c->participant_count != channel_full->participant_count) { c->participant_count = channel_full->participant_count; c->is_changed = true; update_channel(c, channel_id); } } update_channel_full(channel_full, channel_id, "on_update_channel_administrator_count"); } } void ChatManager::on_update_channel_bot_commands(ChannelId channel_id, BotCommands &&bot_commands) { auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_bot_commands"); if (channel_full != nullptr && BotCommands::update_all_bot_commands(channel_full->bot_commands, std::move(bot_commands))) { channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_bot_commands"); } } void ChatManager::on_update_channel_permanent_invite_link(ChannelId channel_id, const DialogInviteLink &invite_link) { auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_permanent_invite_link"); if (channel_full != nullptr && update_permanent_invite_link(channel_full->invite_link, invite_link)) { channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_update_channel_permanent_invite_link"); } } void ChatManager::on_get_chat_empty(telegram_api::chatEmpty &chat, const char *source) { ChatId chat_id(chat.id_); if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id << " from " << source; return; } if (!have_chat(chat_id)) { LOG(ERROR) << "Have no information about " << chat_id << " but received chatEmpty from " << source; } } void ChatManager::on_get_chat(telegram_api::chat &chat, const char *source) { auto debug_str = PSTRING() << " from " << source << " in " << oneline(to_string(chat)); ChatId chat_id(chat.id_); if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id << debug_str; return; } DialogParticipantStatus status = [&] { bool is_creator = 0 != (chat.flags_ & CHAT_FLAG_USER_IS_CREATOR); bool has_left = 0 != (chat.flags_ & CHAT_FLAG_USER_HAS_LEFT); if (is_creator) { return DialogParticipantStatus::Creator(!has_left, false, string()); } else if (chat.admin_rights_ != nullptr) { return DialogParticipantStatus(false, std::move(chat.admin_rights_), string(), ChannelType::Unknown); } else if (has_left) { return DialogParticipantStatus::Left(); } else { return DialogParticipantStatus::Member(); } }(); bool is_active = 0 == (chat.flags_ & CHAT_FLAG_IS_DEACTIVATED); ChannelId migrated_to_channel_id; if (chat.flags_ & CHAT_FLAG_WAS_MIGRATED) { switch (chat.migrated_to_->get_id()) { case telegram_api::inputChannelFromMessage::ID: case telegram_api::inputChannelEmpty::ID: LOG(ERROR) << "Receive invalid information about upgraded supergroup for " << chat_id << debug_str; break; case telegram_api::inputChannel::ID: { auto input_channel = move_tl_object_as(chat.migrated_to_); migrated_to_channel_id = ChannelId(input_channel->channel_id_); if (!have_channel_force(migrated_to_channel_id, source)) { if (!migrated_to_channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << migrated_to_channel_id << debug_str; } else { // temporarily create the channel Channel *c = add_channel(migrated_to_channel_id, "on_get_chat"); c->access_hash = input_channel->access_hash_; c->title = chat.title_; c->status = DialogParticipantStatus::Left(); c->is_megagroup = true; // we definitely need to call update_channel, because client should know about every added channel update_channel(c, migrated_to_channel_id); // get info about the channel get_channel_queries_.add_query(migrated_to_channel_id.get(), Promise(), "on_get_chat"); } } break; } default: UNREACHABLE(); } } Chat *c = get_chat_force(chat_id, source); // to load versions if (c == nullptr) { c = add_chat(chat_id); } on_update_chat_title(c, chat_id, std::move(chat.title_)); if (!status.is_left()) { on_update_chat_participant_count(c, chat_id, chat.participants_count_, chat.version_, debug_str); } else { chat.photo_ = nullptr; } if (c->date != chat.date_) { LOG_IF(ERROR, c->date != 0) << "Chat creation date has changed from " << c->date << " to " << chat.date_ << debug_str; c->date = chat.date_; c->need_save_to_database = true; } on_update_chat_status(c, chat_id, std::move(status)); on_update_chat_default_permissions(c, chat_id, RestrictedRights(chat.default_banned_rights_, ChannelType::Unknown), chat.version_); on_update_chat_photo(c, chat_id, std::move(chat.photo_)); on_update_chat_active(c, chat_id, is_active); on_update_chat_noforwards(c, chat_id, chat.noforwards_); on_update_chat_migrated_to_channel_id(c, chat_id, migrated_to_channel_id); LOG_IF(INFO, !is_active && !migrated_to_channel_id.is_valid()) << chat_id << " is deactivated" << debug_str; if (c->cache_version != Chat::CACHE_VERSION) { c->cache_version = Chat::CACHE_VERSION; c->need_save_to_database = true; } c->is_received_from_server = true; update_chat(c, chat_id); bool has_active_group_call = (chat.flags_ & CHAT_FLAG_HAS_ACTIVE_GROUP_CALL) != 0; bool is_group_call_empty = (chat.flags_ & CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0; td_->messages_manager_->on_update_dialog_group_call(DialogId(chat_id), has_active_group_call, is_group_call_empty, "receive chat"); } void ChatManager::on_get_chat_forbidden(telegram_api::chatForbidden &chat, const char *source) { ChatId chat_id(chat.id_); if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id << " from " << source; return; } bool is_uninited = get_chat_force(chat_id, source) == nullptr; Chat *c = add_chat(chat_id); on_update_chat_title(c, chat_id, std::move(chat.title_)); // chat participant count will be updated in on_update_chat_status on_update_chat_photo(c, chat_id, nullptr); if (c->date != 0) { c->date = 0; // removed in 38-th layer c->need_save_to_database = true; } on_update_chat_status(c, chat_id, DialogParticipantStatus::Banned(0)); if (is_uninited) { on_update_chat_active(c, chat_id, true); on_update_chat_migrated_to_channel_id(c, chat_id, ChannelId()); } else { // leave active and migrated to as is } if (c->cache_version != Chat::CACHE_VERSION) { c->cache_version = Chat::CACHE_VERSION; c->need_save_to_database = true; } c->is_received_from_server = true; update_chat(c, chat_id); } void ChatManager::on_get_channel(telegram_api::channel &channel, const char *source) { ChannelId channel_id(channel.id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << ": " << to_string(channel); return; } if (channel.flags_ == 0 && channel.access_hash_ == 0 && channel.title_.empty()) { Channel *c = get_channel_force(channel_id, source); LOG(ERROR) << "Receive empty " << to_string(channel) << " from " << source << ", have " << to_string(get_supergroup_object(channel_id, c)); if (c == nullptr && !have_min_channel(channel_id)) { min_channels_.set(channel_id, td::make_unique()); } return; } bool is_min = (channel.flags_ & CHANNEL_FLAG_IS_MIN) != 0; bool has_access_hash = (channel.flags_ & CHANNEL_FLAG_HAS_ACCESS_HASH) != 0; auto access_hash = has_access_hash ? channel.access_hash_ : 0; bool has_linked_channel = (channel.flags_ & CHANNEL_FLAG_HAS_LINKED_CHAT) != 0; bool sign_messages = (channel.flags_ & CHANNEL_FLAG_SIGN_MESSAGES) != 0; bool join_to_send = (channel.flags_ & CHANNEL_FLAG_JOIN_TO_SEND) != 0; bool join_request = (channel.flags_ & CHANNEL_FLAG_JOIN_REQUEST) != 0; bool is_slow_mode_enabled = (channel.flags_ & CHANNEL_FLAG_IS_SLOW_MODE_ENABLED) != 0; bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; bool is_verified = (channel.flags_ & CHANNEL_FLAG_IS_VERIFIED) != 0; bool is_scam = (channel.flags_ & CHANNEL_FLAG_IS_SCAM) != 0; bool is_fake = (channel.flags_ & CHANNEL_FLAG_IS_FAKE) != 0; bool is_gigagroup = (channel.flags_ & CHANNEL_FLAG_IS_GIGAGROUP) != 0; bool is_forum = (channel.flags_ & CHANNEL_FLAG_IS_FORUM) != 0; bool have_participant_count = (channel.flags_ & CHANNEL_FLAG_HAS_PARTICIPANT_COUNT) != 0; int32 participant_count = have_participant_count ? channel.participants_count_ : 0; bool stories_available = channel.stories_max_id_ > 0; bool stories_unavailable = channel.stories_unavailable_; auto boost_level = channel.level_; if (have_participant_count) { auto channel_full = get_channel_full_const(channel_id); if (channel_full != nullptr && channel_full->administrator_count > participant_count) { participant_count = channel_full->administrator_count; } } { bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0; LOG_IF(ERROR, is_broadcast == is_megagroup) << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << " from " << source << ": " << oneline(to_string(channel)); } if (is_megagroup) { LOG_IF(ERROR, sign_messages) << "Need to sign messages in the supergroup " << channel_id << " from " << source; sign_messages = true; } else { LOG_IF(ERROR, is_slow_mode_enabled) << "Slow mode enabled in the " << channel_id << " from " << source; LOG_IF(ERROR, is_gigagroup) << "Receive broadcast group as " << channel_id << " from " << source; LOG_IF(ERROR, is_forum) << "Receive broadcast forum as " << channel_id << " from " << source; is_slow_mode_enabled = false; is_gigagroup = false; is_forum = false; } if (is_gigagroup) { td_->dialog_manager_->remove_dialog_suggested_action( SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); } DialogParticipantStatus status = [&] { bool has_left = (channel.flags_ & CHANNEL_FLAG_USER_HAS_LEFT) != 0; bool is_creator = (channel.flags_ & CHANNEL_FLAG_USER_IS_CREATOR) != 0; if (is_creator) { bool is_anonymous = channel.admin_rights_ != nullptr && (channel.admin_rights_->flags_ & telegram_api::chatAdminRights::ANONYMOUS_MASK) != 0; return DialogParticipantStatus::Creator(!has_left, is_anonymous, string()); } else if (channel.admin_rights_ != nullptr) { return DialogParticipantStatus(false, std::move(channel.admin_rights_), string(), is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast); } else if (channel.banned_rights_ != nullptr) { return DialogParticipantStatus(!has_left, std::move(channel.banned_rights_), is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast); } else if (has_left) { return DialogParticipantStatus::Left(); } else { return DialogParticipantStatus::Member(); } }(); if (is_min) { Channel *c = get_channel_force(channel_id, source); if (c != nullptr) { LOG(DEBUG) << "Receive known min " << channel_id; auto old_join_to_send = get_channel_join_to_send(c); auto old_join_request = get_channel_join_request(c); on_update_channel_title(c, channel_id, std::move(channel.title_)); on_update_channel_usernames(c, channel_id, Usernames(std::move(channel.username_), std::move(channel.usernames_))); if (!c->status.is_banned()) { on_update_channel_photo(c, channel_id, std::move(channel.photo_)); } on_update_channel_has_location(c, channel_id, channel.has_geo_); on_update_channel_noforwards(c, channel_id, channel.noforwards_); on_update_channel_emoji_status(c, channel_id, EmojiStatus(std::move(channel.emoji_status_))); if (c->has_linked_channel != has_linked_channel || c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || c->is_scam != is_scam || c->is_fake != is_fake || c->is_gigagroup != is_gigagroup || c->is_forum != is_forum || c->boost_level != boost_level) { c->has_linked_channel = has_linked_channel; c->is_slow_mode_enabled = is_slow_mode_enabled; c->is_megagroup = is_megagroup; c->is_scam = is_scam; c->is_fake = is_fake; c->is_gigagroup = is_gigagroup; if (c->is_forum != is_forum) { c->is_forum = is_forum; send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_is_forum, DialogId(channel_id), is_forum); } c->boost_level = boost_level; c->is_changed = true; invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_min_channel"); } if (!td_->auth_manager_->is_bot()) { auto restriction_reasons = get_restriction_reasons(std::move(channel.restriction_reason_)); if (restriction_reasons != c->restriction_reasons) { c->restriction_reasons = std::move(restriction_reasons); c->is_changed = true; } } if (c->join_to_send != join_to_send || c->join_request != join_request) { c->join_to_send = join_to_send; c->join_request = join_request; c->need_save_to_database = true; } // sign_messages isn't known for min-channels if (c->is_verified != is_verified) { c->is_verified = is_verified; c->is_changed = true; } if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { c->is_changed = true; } // must be after setting of c->is_megagroup on_update_channel_default_permissions(c, channel_id, RestrictedRights(channel.default_banned_rights_, ChannelType::Megagroup)); update_channel(c, channel_id); } else { auto min_channel = td::make_unique(); min_channel->photo_ = get_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), access_hash, std::move(channel.photo_)); if (td_->auth_manager_->is_bot()) { min_channel->photo_.minithumbnail.clear(); } PeerColor peer_color(channel.color_); min_channel->accent_color_id_ = peer_color.accent_color_id_; min_channel->title_ = std::move(channel.title_); min_channel->is_megagroup_ = is_megagroup; min_channels_.set(channel_id, std::move(min_channel)); } return; } if (!has_access_hash) { LOG(ERROR) << "Receive non-min " << channel_id << " without access_hash from " << source; return; } if (status.is_creator()) { // to correctly calculate is_ownership_transferred in on_update_channel_status get_channel_force(channel_id, source); } Channel *c = add_channel(channel_id, "on_get_channel"); auto old_join_to_send = get_channel_join_to_send(c); auto old_join_request = get_channel_join_request(c); if (c->access_hash != access_hash) { c->access_hash = access_hash; c->need_save_to_database = true; } if (c->date != channel.date_) { c->date = channel.date_; c->is_changed = true; } bool need_update_participant_count = have_participant_count && participant_count != c->participant_count; if (need_update_participant_count) { c->participant_count = participant_count; c->is_changed = true; } bool need_invalidate_channel_full = false; if (c->has_linked_channel != has_linked_channel || c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || c->is_scam != is_scam || c->is_fake != is_fake || c->is_gigagroup != is_gigagroup || c->is_forum != is_forum || c->boost_level != boost_level) { c->has_linked_channel = has_linked_channel; c->is_slow_mode_enabled = is_slow_mode_enabled; c->is_megagroup = is_megagroup; c->is_scam = is_scam; c->is_fake = is_fake; c->is_gigagroup = is_gigagroup; if (c->is_forum != is_forum) { c->is_forum = is_forum; send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_is_forum, DialogId(channel_id), is_forum); } c->boost_level = boost_level; c->is_changed = true; need_invalidate_channel_full = true; } if (!td_->auth_manager_->is_bot()) { auto restriction_reasons = get_restriction_reasons(std::move(channel.restriction_reason_)); if (restriction_reasons != c->restriction_reasons) { c->restriction_reasons = std::move(restriction_reasons); c->is_changed = true; } } if (c->join_to_send != join_to_send || c->join_request != join_request) { c->join_to_send = join_to_send; c->join_request = join_request; c->need_save_to_database = true; } if (c->is_verified != is_verified || c->sign_messages != sign_messages) { c->is_verified = is_verified; c->sign_messages = sign_messages; c->is_changed = true; } if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { c->is_changed = true; } on_update_channel_title(c, channel_id, std::move(channel.title_)); on_update_channel_photo(c, channel_id, std::move(channel.photo_)); PeerColor peer_color(channel.color_); on_update_channel_accent_color_id(c, channel_id, peer_color.accent_color_id_); on_update_channel_background_custom_emoji_id(c, channel_id, peer_color.background_custom_emoji_id_); PeerColor profile_peer_color(channel.profile_color_); on_update_channel_profile_accent_color_id(c, channel_id, profile_peer_color.accent_color_id_); on_update_channel_profile_background_custom_emoji_id(c, channel_id, profile_peer_color.background_custom_emoji_id_); on_update_channel_status(c, channel_id, std::move(status)); on_update_channel_usernames( c, channel_id, Usernames(std::move(channel.username_), std::move(channel.usernames_))); // uses status, must be called after on_update_channel_status on_update_channel_has_location(c, channel_id, channel.has_geo_); on_update_channel_noforwards(c, channel_id, channel.noforwards_); on_update_channel_emoji_status(c, channel_id, EmojiStatus(std::move(channel.emoji_status_))); if (!td_->auth_manager_->is_bot() && !channel.stories_hidden_min_) { on_update_channel_stories_hidden(c, channel_id, channel.stories_hidden_); } // must be after setting of c->is_megagroup on_update_channel_default_permissions(c, channel_id, RestrictedRights(channel.default_banned_rights_, ChannelType::Megagroup)); if (!td_->auth_manager_->is_bot() && (stories_available || stories_unavailable)) { // update at the end, because it calls need_poll_channel_active_stories on_update_channel_story_ids_impl(c, channel_id, StoryId(channel.stories_max_id_), StoryId()); } if (c->cache_version != Channel::CACHE_VERSION) { c->cache_version = Channel::CACHE_VERSION; c->need_save_to_database = true; } c->is_received_from_server = true; update_channel(c, channel_id); if (need_update_participant_count) { auto channel_full = get_channel_full(channel_id, true, "on_get_channel"); if (channel_full != nullptr && channel_full->participant_count != participant_count) { channel_full->participant_count = participant_count; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_get_channel"); } } if (need_invalidate_channel_full) { invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_channel"); } bool has_active_group_call = (channel.flags_ & CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL) != 0; bool is_group_call_empty = (channel.flags_ & CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0; td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), has_active_group_call, is_group_call_empty, "receive channel"); } void ChatManager::on_get_channel_forbidden(telegram_api::channelForbidden &channel, const char *source) { ChannelId channel_id(channel.id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << ": " << to_string(channel); return; } if (channel.flags_ == 0 && channel.access_hash_ == 0 && channel.title_.empty()) { Channel *c = get_channel_force(channel_id, source); LOG(ERROR) << "Receive empty " << to_string(channel) << " from " << source << ", have " << to_string(get_supergroup_object(channel_id, c)); if (c == nullptr && !have_min_channel(channel_id)) { min_channels_.set(channel_id, td::make_unique()); } return; } Channel *c = add_channel(channel_id, "on_get_channel_forbidden"); auto old_join_to_send = get_channel_join_to_send(c); auto old_join_request = get_channel_join_request(c); if (c->access_hash != channel.access_hash_) { c->access_hash = channel.access_hash_; c->need_save_to_database = true; } if (c->date != 0) { c->date = 0; c->is_changed = true; } bool sign_messages = false; bool join_to_send = false; bool join_request = false; bool is_slow_mode_enabled = false; bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; bool is_verified = false; bool is_scam = false; bool is_fake = false; { bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0; LOG_IF(ERROR, is_broadcast == is_megagroup) << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << " from " << source << ": " << oneline(to_string(channel)); } if (is_megagroup) { sign_messages = true; } bool need_invalidate_channel_full = false; if (c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || !c->restriction_reasons.empty() || c->is_scam != is_scam || c->is_fake != is_fake || c->join_to_send != join_to_send || c->join_request != join_request) { // c->has_linked_channel = has_linked_channel; c->is_slow_mode_enabled = is_slow_mode_enabled; c->is_megagroup = is_megagroup; c->restriction_reasons.clear(); c->is_scam = is_scam; c->is_fake = is_fake; c->join_to_send = join_to_send; c->join_request = join_request; c->is_changed = true; need_invalidate_channel_full = true; } if (c->join_to_send != join_to_send || c->join_request != join_request) { c->join_to_send = join_to_send; c->join_request = join_request; c->need_save_to_database = true; } if (c->sign_messages != sign_messages || c->is_verified != is_verified) { c->sign_messages = sign_messages; c->is_verified = is_verified; c->is_changed = true; } if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { c->is_changed = true; } on_update_channel_title(c, channel_id, std::move(channel.title_)); on_update_channel_photo(c, channel_id, nullptr); on_update_channel_status(c, channel_id, DialogParticipantStatus::Banned(channel.until_date_)); // on_update_channel_usernames(c, channel_id, Usernames()); // don't know if channel usernames are empty, so don't update it // on_update_channel_has_location(c, channel_id, false); on_update_channel_noforwards(c, channel_id, false); on_update_channel_emoji_status(c, channel_id, EmojiStatus()); td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), false, false, "on_get_channel_forbidden"); // must be after setting of c->is_megagroup tl_object_ptr banned_rights; // == nullptr on_update_channel_default_permissions(c, channel_id, RestrictedRights(banned_rights, ChannelType::Megagroup)); bool need_drop_participant_count = c->participant_count != 0; if (need_drop_participant_count) { c->participant_count = 0; c->is_changed = true; } if (c->cache_version != Channel::CACHE_VERSION) { c->cache_version = Channel::CACHE_VERSION; c->need_save_to_database = true; } c->is_received_from_server = true; update_channel(c, channel_id); if (need_drop_participant_count) { auto channel_full = get_channel_full(channel_id, true, "on_get_channel_forbidden"); if (channel_full != nullptr && channel_full->participant_count != 0) { channel_full->participant_count = 0; channel_full->administrator_count = 0; channel_full->is_changed = true; update_channel_full(channel_full, channel_id, "on_get_channel_forbidden 2"); } } if (need_invalidate_channel_full) { invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_channel_forbidden 3"); } } td_api::object_ptr ChatManager::get_update_basic_group_object(ChatId chat_id, const Chat *c) { if (c == nullptr) { return get_update_unknown_basic_group_object(chat_id); } return td_api::make_object(get_basic_group_object(chat_id, c)); } td_api::object_ptr ChatManager::get_update_unknown_basic_group_object(ChatId chat_id) { return td_api::make_object(td_api::make_object( chat_id.get(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), true, 0)); } int64 ChatManager::get_basic_group_id_object(ChatId chat_id, const char *source) const { if (chat_id.is_valid() && get_chat(chat_id) == nullptr && unknown_chats_.count(chat_id) == 0) { LOG(ERROR) << "Have no information about " << chat_id << " from " << source; unknown_chats_.insert(chat_id); send_closure(G()->td(), &Td::send_update, get_update_unknown_basic_group_object(chat_id)); } return chat_id.get(); } tl_object_ptr ChatManager::get_basic_group_object(ChatId chat_id) { return get_basic_group_object(chat_id, get_chat(chat_id)); } tl_object_ptr ChatManager::get_basic_group_object(ChatId chat_id, const Chat *c) { if (c == nullptr) { return nullptr; } if (c->migrated_to_channel_id.is_valid()) { get_channel_force(c->migrated_to_channel_id, "get_basic_group_object"); } return get_basic_group_object_const(chat_id, c); } tl_object_ptr ChatManager::get_basic_group_object_const(ChatId chat_id, const Chat *c) const { return make_tl_object( chat_id.get(), c->participant_count, get_chat_status(c).get_chat_member_status_object(), c->is_active, get_supergroup_id_object(c->migrated_to_channel_id, "get_basic_group_object")); } tl_object_ptr ChatManager::get_basic_group_full_info_object(ChatId chat_id) const { return get_basic_group_full_info_object(chat_id, get_chat_full(chat_id)); } tl_object_ptr ChatManager::get_basic_group_full_info_object( ChatId chat_id, const ChatFull *chat_full) const { CHECK(chat_full != nullptr); auto bot_commands = transform(chat_full->bot_commands, [td = td_](const BotCommands &commands) { return commands.get_bot_commands_object(td); }); auto members = transform(chat_full->participants, [this](const DialogParticipant &dialog_participant) { return get_chat_member_object(dialog_participant, "get_basic_group_full_info_object"); }); return make_tl_object( get_chat_photo_object(td_->file_manager_.get(), chat_full->photo), chat_full->description, td_->user_manager_->get_user_id_object(chat_full->creator_user_id, "basicGroupFullInfo"), std::move(members), can_hide_chat_participants(chat_id).is_ok(), can_toggle_chat_aggressive_anti_spam(chat_id).is_ok(), chat_full->invite_link.get_chat_invite_link_object(td_->user_manager_.get()), std::move(bot_commands)); } td_api::object_ptr ChatManager::get_update_supergroup_object(ChannelId channel_id, const Channel *c) const { if (c == nullptr) { return get_update_unknown_supergroup_object(channel_id); } return td_api::make_object(get_supergroup_object(channel_id, c)); } td_api::object_ptr ChatManager::get_update_unknown_supergroup_object( ChannelId channel_id) const { auto min_channel = get_min_channel(channel_id); bool is_megagroup = min_channel == nullptr ? false : min_channel->is_megagroup_; return td_api::make_object(td_api::make_object( channel_id.get(), nullptr, 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), 0, 0, false, false, false, !is_megagroup, false, false, !is_megagroup, false, false, false, string(), false, false, false, false)); } int64 ChatManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const { if (channel_id.is_valid() && get_channel(channel_id) == nullptr && unknown_channels_.count(channel_id) == 0) { if (have_min_channel(channel_id)) { LOG(INFO) << "Have only min " << channel_id << " received from " << source; } else { LOG(ERROR) << "Have no information about " << channel_id << " received from " << source; } unknown_channels_.insert(channel_id); send_closure(G()->td(), &Td::send_update, get_update_unknown_supergroup_object(channel_id)); } return channel_id.get(); } bool ChatManager::need_poll_channel_active_stories(const Channel *c, ChannelId channel_id) const { return c != nullptr && !get_channel_status(c).is_member() && have_input_peer_channel(c, channel_id, AccessRights::Read); } bool ChatManager::get_channel_has_unread_stories(const Channel *c) { CHECK(c != nullptr); return c->max_active_story_id.get() > c->max_read_story_id.get(); } tl_object_ptr ChatManager::get_supergroup_object(ChannelId channel_id) const { return get_supergroup_object(channel_id, get_channel(channel_id)); } tl_object_ptr ChatManager::get_supergroup_object(ChannelId channel_id, const Channel *c) { if (c == nullptr) { return nullptr; } return td_api::make_object( channel_id.get(), c->usernames.get_usernames_object(), c->date, get_channel_status(c).get_chat_member_status_object(), c->participant_count, c->boost_level, c->has_linked_channel, c->has_location, c->sign_messages, get_channel_join_to_send(c), get_channel_join_request(c), c->is_slow_mode_enabled, !c->is_megagroup, c->is_gigagroup, c->is_forum, c->is_verified, get_restriction_reason_description(c->restriction_reasons), c->is_scam, c->is_fake, c->max_active_story_id.is_valid(), get_channel_has_unread_stories(c)); } tl_object_ptr ChatManager::get_supergroup_full_info_object(ChannelId channel_id) const { return get_supergroup_full_info_object(channel_id, get_channel_full(channel_id)); } tl_object_ptr ChatManager::get_supergroup_full_info_object( ChannelId channel_id, const ChannelFull *channel_full) const { CHECK(channel_full != nullptr); double slow_mode_delay_expires_in = 0; if (channel_full->slow_mode_next_send_date != 0 && (channel_full->unrestrict_boost_count == 0 || channel_full->boost_count < channel_full->unrestrict_boost_count)) { slow_mode_delay_expires_in = max(channel_full->slow_mode_next_send_date - G()->server_time(), 1e-3); } auto bot_commands = transform(channel_full->bot_commands, [td = td_](const BotCommands &commands) { return commands.get_bot_commands_object(td); }); bool has_hidden_participants = channel_full->has_hidden_participants || !channel_full->can_get_participants; return td_api::make_object( get_chat_photo_object(td_->file_manager_.get(), channel_full->photo), channel_full->description, channel_full->participant_count, channel_full->administrator_count, channel_full->restricted_count, channel_full->banned_count, DialogId(channel_full->linked_channel_id).get(), channel_full->slow_mode_delay, slow_mode_delay_expires_in, channel_full->can_get_participants, has_hidden_participants, can_hide_channel_participants(channel_id, channel_full).is_ok(), channel_full->can_set_sticker_set, channel_full->can_set_location, channel_full->can_view_statistics, channel_full->can_view_revenue, can_toggle_channel_aggressive_anti_spam(channel_id, channel_full).is_ok(), channel_full->is_all_history_available, channel_full->can_have_sponsored_messages, channel_full->has_aggressive_anti_spam_enabled, channel_full->has_paid_media_allowed, channel_full->has_pinned_stories, channel_full->boost_count, channel_full->unrestrict_boost_count, channel_full->sticker_set_id.get(), channel_full->emoji_sticker_set_id.get(), channel_full->location.get_chat_location_object(), channel_full->invite_link.get_chat_invite_link_object(td_->user_manager_.get()), std::move(bot_commands), get_basic_group_id_object(channel_full->migrated_from_chat_id, "get_supergroup_full_info_object"), channel_full->migrated_from_max_message_id.get()); } void ChatManager::get_current_state(vector> &updates) const { for (auto chat_id : unknown_chats_) { if (!have_chat(chat_id)) { updates.push_back(get_update_unknown_basic_group_object(chat_id)); } } for (auto channel_id : unknown_channels_) { if (!have_channel(channel_id)) { updates.push_back(get_update_unknown_supergroup_object(channel_id)); } } channels_.foreach([&](const ChannelId &channel_id, const unique_ptr &channel) { updates.push_back(get_update_supergroup_object(channel_id, channel.get())); }); // chat objects can contain channel_id, so they must be sent after channels chats_.foreach([&](const ChatId &chat_id, const unique_ptr &chat) { updates.push_back(td_api::make_object(get_basic_group_object_const(chat_id, chat.get()))); }); channels_full_.foreach([&](const ChannelId &channel_id, const unique_ptr &channel_full) { updates.push_back(td_api::make_object( channel_id.get(), get_supergroup_full_info_object(channel_id, channel_full.get()))); }); chats_full_.foreach([&](const ChatId &chat_id, const unique_ptr &chat_full) { updates.push_back(td_api::make_object( chat_id.get(), get_basic_group_full_info_object(chat_id, chat_full.get()))); }); } } // namespace td