// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 // // 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/UserManager.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/Birthdate.hpp" #include "td/telegram/BlockListId.h" #include "td/telegram/BotMenuButton.h" #include "td/telegram/BusinessAwayMessage.h" #include "td/telegram/BusinessGreetingMessage.h" #include "td/telegram/BusinessInfo.h" #include "td/telegram/BusinessInfo.hpp" #include "td/telegram/BusinessIntro.h" #include "td/telegram/BusinessWorkHours.h" #include "td/telegram/ChannelType.h" #include "td/telegram/CommonDialogManager.h" #include "td/telegram/ConfigManager.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogLocation.h" #include "td/telegram/DialogManager.h" #include "td/telegram/DialogParticipantManager.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/FolderId.h" #include "td/telegram/Global.h" #include "td/telegram/GroupCallManager.h" #include "td/telegram/InlineQueriesManager.h" #include "td/telegram/LinkManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/MessageTtl.h" #include "td/telegram/misc.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/NotificationManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PeerColor.h" #include "td/telegram/PeopleNearbyManager.h" #include "td/telegram/Photo.h" #include "td/telegram/Photo.hpp" #include "td/telegram/PhotoSize.h" #include "td/telegram/PremiumGiftOption.hpp" #include "td/telegram/ReactionListType.h" #include "td/telegram/ReactionManager.h" #include "td/telegram/SecretChatLayer.h" #include "td/telegram/SecretChatsManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StickerPhotoSize.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/Version.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/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/Random.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 #include #include #include #include namespace td { class GetContactsQuery final : public Td::ResultHandler { public: void send(int64 hash) { send_query(G()->net_query_creator().create(telegram_api::contacts_getContacts(hash))); } 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 GetContactsQuery: " << to_string(ptr); td_->user_manager_->on_get_contacts(std::move(ptr)); } void on_error(Status status) final { td_->user_manager_->on_get_contacts_failed(std::move(status)); } }; class GetContactsBirthdaysQuery final : public Td::ResultHandler { public: void send() { send_query(G()->net_query_creator().create(telegram_api::contacts_getBirthdays())); } 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 GetContactsBirthdaysQuery: " << to_string(ptr); td_->user_manager_->on_get_contact_birthdates(std::move(ptr)); } void on_error(Status status) final { td_->user_manager_->on_get_contact_birthdates(nullptr); } }; class GetContactsStatusesQuery final : public Td::ResultHandler { public: void send() { send_query(G()->net_query_creator().create(telegram_api::contacts_getStatuses())); } 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_->user_manager_->on_get_contacts_statuses(result_ptr.move_as_ok()); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for GetContactsStatusesQuery: " << status; } } }; class AddContactQuery final : public Td::ResultHandler { Promise promise_; UserId user_id_; public: explicit AddContactQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId user_id, telegram_api::object_ptr &&input_user, const Contact &contact, bool share_phone_number) { user_id_ = user_id; int32 flags = 0; if (share_phone_number) { flags |= telegram_api::contacts_addContact::ADD_PHONE_PRIVACY_EXCEPTION_MASK; } send_query(G()->net_query_creator().create( telegram_api::contacts_addContact(flags, false /*ignored*/, std::move(input_user), contact.get_first_name(), contact.get_last_name(), contact.get_phone_number()), {{DialogId(user_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 AddContactQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); td_->user_manager_->reload_contacts(true); td_->messages_manager_->reget_dialog_action_bar(DialogId(user_id_), "AddContactQuery"); } }; class EditCloseFriendsQuery final : public Td::ResultHandler { Promise promise_; vector user_ids_; public: explicit EditCloseFriendsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector user_ids) { user_ids_ = std::move(user_ids); send_query(G()->net_query_creator().create( telegram_api::contacts_editCloseFriends(UserId::get_input_user_ids(user_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()); } td_->user_manager_->on_set_close_friends(user_ids_, std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class ResolvePhoneQuery final : public Td::ResultHandler { Promise promise_; string phone_number_; public: explicit ResolvePhoneQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &phone_number) { phone_number_ = phone_number; send_query(G()->net_query_creator().create(telegram_api::contacts_resolvePhone(phone_number))); } 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 ResolvePhoneQuery: " << to_string(ptr); td_->user_manager_->on_get_users(std::move(ptr->users_), "ResolvePhoneQuery"); // on_get_chats(std::move(ptr->chats_), "ResolvePhoneQuery"); DialogId dialog_id(ptr->peer_); if (dialog_id.get_type() != DialogType::User) { LOG(ERROR) << "Receive " << dialog_id << " by " << phone_number_; return on_error(Status::Error(500, "Receive invalid response")); } td_->user_manager_->on_resolved_phone_number(phone_number_, dialog_id.get_user_id()); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == Slice("PHONE_NOT_OCCUPIED")) { td_->user_manager_->on_resolved_phone_number(phone_number_, UserId()); return promise_.set_value(Unit()); } promise_.set_error(std::move(status)); } }; class AcceptContactQuery final : public Td::ResultHandler { Promise promise_; UserId user_id_; public: explicit AcceptContactQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId user_id, telegram_api::object_ptr &&input_user) { user_id_ = user_id; send_query(G()->net_query_creator().create(telegram_api::contacts_acceptContact(std::move(input_user)))); } 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 AcceptContactQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); td_->user_manager_->reload_contacts(true); td_->messages_manager_->reget_dialog_action_bar(DialogId(user_id_), "AcceptContactQuery"); } }; class ImportContactsQuery final : public Td::ResultHandler { int64 random_id_ = 0; size_t sent_size_ = 0; public: void send(vector> &&input_phone_contacts, int64 random_id) { random_id_ = random_id; sent_size_ = input_phone_contacts.size(); send_query(G()->net_query_creator().create(telegram_api::contacts_importContacts(std::move(input_phone_contacts)))); } 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 ImportContactsQuery: " << to_string(ptr); if (sent_size_ == ptr->retry_contacts_.size()) { return on_error(Status::Error(429, "Too Many Requests: retry after 3600")); } td_->user_manager_->on_imported_contacts(random_id_, std::move(ptr)); } void on_error(Status status) final { td_->user_manager_->on_imported_contacts(random_id_, std::move(status)); } }; class DeleteContactsQuery final : public Td::ResultHandler { Promise promise_; public: explicit DeleteContactsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector> &&input_users) { send_query(G()->net_query_creator().create(telegram_api::contacts_deleteContacts(std::move(input_users)))); } 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 DeleteContactsQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); td_->user_manager_->reload_contacts(true); } }; class DeleteContactsByPhoneNumberQuery final : public Td::ResultHandler { Promise promise_; vector user_ids_; public: explicit DeleteContactsByPhoneNumberQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector &&user_phone_numbers, vector &&user_ids) { if (user_phone_numbers.empty()) { return promise_.set_value(Unit()); } user_ids_ = std::move(user_ids); send_query(G()->net_query_creator().create(telegram_api::contacts_deleteByPhones(std::move(user_phone_numbers)))); } 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(); if (!result) { return on_error(Status::Error(500, "Some contacts can't be deleted")); } td_->user_manager_->on_deleted_contacts(user_ids_); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); td_->user_manager_->reload_contacts(true); } }; class ResetContactsQuery final : public Td::ResultHandler { Promise promise_; public: explicit ResetContactsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::contacts_resetSaved())); } 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(); if (!result) { LOG(ERROR) << "Failed to delete imported contacts"; td_->user_manager_->reload_contacts(true); } else { td_->user_manager_->on_update_contacts_reset(); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); td_->user_manager_->reload_contacts(true); } }; class UploadProfilePhotoQuery final : public Td::ResultHandler { Promise promise_; UserId user_id_; FileId file_id_; bool is_fallback_; bool only_suggest_; public: explicit UploadProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId user_id, FileId file_id, telegram_api::object_ptr &&input_file, bool is_fallback, bool only_suggest, bool is_animation, double main_frame_timestamp) { CHECK(input_file != nullptr); CHECK(file_id.is_valid()); user_id_ = user_id; file_id_ = file_id; is_fallback_ = is_fallback; only_suggest_ = only_suggest; static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::VIDEO_MASK) == static_cast(telegram_api::photos_uploadContactProfilePhoto::VIDEO_MASK), ""); static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::VIDEO_START_TS_MASK) == static_cast(telegram_api::photos_uploadContactProfilePhoto::VIDEO_START_TS_MASK), ""); static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::FILE_MASK) == static_cast(telegram_api::photos_uploadContactProfilePhoto::FILE_MASK), ""); int32 flags = 0; telegram_api::object_ptr photo_input_file; telegram_api::object_ptr video_input_file; if (is_animation) { flags |= telegram_api::photos_uploadProfilePhoto::VIDEO_MASK; video_input_file = std::move(input_file); if (main_frame_timestamp != 0.0) { flags |= telegram_api::photos_uploadProfilePhoto::VIDEO_START_TS_MASK; } } else { flags |= telegram_api::photos_uploadProfilePhoto::FILE_MASK; photo_input_file = std::move(input_file); } if (td_->user_manager_->is_user_bot(user_id)) { auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } flags |= telegram_api::photos_uploadProfilePhoto::BOT_MASK; send_query(G()->net_query_creator().create( telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), std::move(photo_input_file), std::move(video_input_file), main_frame_timestamp, nullptr), {{user_id}})); } else if (user_id == td_->user_manager_->get_my_id()) { if (is_fallback) { flags |= telegram_api::photos_uploadProfilePhoto::FALLBACK_MASK; } send_query(G()->net_query_creator().create( telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, nullptr, std::move(photo_input_file), std::move(video_input_file), main_frame_timestamp, nullptr), {{"me"}})); } else { if (only_suggest) { flags |= telegram_api::photos_uploadContactProfilePhoto::SUGGEST_MASK; } else { flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; } auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } send_query(G()->net_query_creator().create( telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, r_input_user.move_as_ok(), std::move(photo_input_file), std::move(video_input_file), main_frame_timestamp, nullptr), {{user_id}})); } } void send(UserId user_id, unique_ptr sticker_photo_size, bool is_fallback, bool only_suggest) { CHECK(sticker_photo_size != nullptr); user_id_ = user_id; file_id_ = FileId(); is_fallback_ = is_fallback; only_suggest_ = only_suggest; if (td_->user_manager_->is_user_bot(user_id)) { auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } int32 flags = telegram_api::photos_uploadProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; flags |= telegram_api::photos_uploadProfilePhoto::BOT_MASK; send_query(G()->net_query_creator().create( telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), nullptr, nullptr, 0.0, sticker_photo_size->get_input_video_size_object(td_)), {{user_id}})); } else if (user_id == td_->user_manager_->get_my_id()) { int32 flags = telegram_api::photos_uploadProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; if (is_fallback) { flags |= telegram_api::photos_uploadProfilePhoto::FALLBACK_MASK; } send_query(G()->net_query_creator().create( telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, nullptr, nullptr, nullptr, 0.0, sticker_photo_size->get_input_video_size_object(td_)), {{"me"}})); } else { int32 flags = telegram_api::photos_uploadContactProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; if (only_suggest) { flags |= telegram_api::photos_uploadContactProfilePhoto::SUGGEST_MASK; } else { flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; } auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } send_query(G()->net_query_creator().create( telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, r_input_user.move_as_ok(), nullptr, nullptr, 0.0, sticker_photo_size->get_input_video_size_object(td_)), {{user_id}})); } } void on_result(BufferSlice packet) final { static_assert(std::is_same::value, ""); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } if (!only_suggest_) { td_->user_manager_->on_set_profile_photo(user_id_, result_ptr.move_as_ok(), is_fallback_, 0, std::move(promise_)); } else { promise_.set_value(Unit()); } if (file_id_.is_valid()) { td_->file_manager_->delete_partial_remote_location(file_id_); } } void on_error(Status status) final { if (file_id_.is_valid()) { td_->file_manager_->delete_partial_remote_location(file_id_); } promise_.set_error(std::move(status)); } }; class UpdateProfilePhotoQuery final : public Td::ResultHandler { Promise promise_; UserId user_id_; FileId file_id_; int64 old_photo_id_; bool is_fallback_; string file_reference_; public: explicit UpdateProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId user_id, FileId file_id, int64 old_photo_id, bool is_fallback, telegram_api::object_ptr &&input_photo) { CHECK(input_photo != nullptr); user_id_ = user_id; file_id_ = file_id; old_photo_id_ = old_photo_id; is_fallback_ = is_fallback; file_reference_ = FileManager::extract_file_reference(input_photo); int32 flags = 0; if (is_fallback) { flags |= telegram_api::photos_updateProfilePhoto::FALLBACK_MASK; } if (td_->user_manager_->is_user_bot(user_id)) { auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } flags |= telegram_api::photos_updateProfilePhoto::BOT_MASK; send_query(G()->net_query_creator().create( telegram_api::photos_updateProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), std::move(input_photo)), {{user_id}})); } else { send_query(G()->net_query_creator().create( telegram_api::photos_updateProfilePhoto(flags, false /*ignored*/, nullptr, std::move(input_photo)), {{"me"}})); } } 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_->user_manager_->on_set_profile_photo(user_id_, result_ptr.move_as_ok(), is_fallback_, old_photo_id_, std::move(promise_)); } void on_error(Status status) final { if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { if (file_id_.is_valid()) { VLOG(file_references) << "Receive " << status << " for " << file_id_; td_->file_manager_->delete_file_reference(file_id_, file_reference_); td_->file_reference_manager_->repair_file_reference( file_id_, PromiseCreator::lambda([user_id = user_id_, file_id = file_id_, is_fallback = is_fallback_, old_photo_id = old_photo_id_, promise = std::move(promise_)](Result result) mutable { if (result.is_error()) { return promise.set_error(Status::Error(400, "Can't find the photo")); } send_closure(G()->user_manager(), &UserManager::send_update_profile_photo_query, user_id, file_id, old_photo_id, is_fallback, std::move(promise)); })); return; } else { LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_; } } promise_.set_error(std::move(status)); } }; class DeleteContactProfilePhotoQuery final : public Td::ResultHandler { Promise promise_; UserId user_id_; public: explicit DeleteContactProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId user_id, telegram_api::object_ptr &&input_user) { CHECK(input_user != nullptr); user_id_ = user_id; int32 flags = 0; flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; send_query(G()->net_query_creator().create( telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, std::move(input_user), nullptr, nullptr, 0, nullptr), {{user_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(); ptr->photo_ = nullptr; td_->user_manager_->on_set_profile_photo(user_id_, std::move(ptr), false, 0, std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class DeleteProfilePhotoQuery final : public Td::ResultHandler { Promise promise_; int64 profile_photo_id_; public: explicit DeleteProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(int64 profile_photo_id) { profile_photo_id_ = profile_photo_id; vector> input_photo_ids; input_photo_ids.push_back(telegram_api::make_object(profile_photo_id, 0, BufferSlice())); send_query(G()->net_query_creator().create(telegram_api::photos_deletePhotos(std::move(input_photo_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 result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for DeleteProfilePhotoQuery: " << format::as_array(result); if (result.size() != 1u) { LOG(WARNING) << "Photo can't be deleted"; return on_error(Status::Error(400, "Photo can't be deleted")); } td_->user_manager_->on_delete_profile_photo(profile_photo_id_, std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UpdateColorQuery final : public Td::ResultHandler { Promise promise_; bool for_profile_; AccentColorId accent_color_id_; CustomEmojiId background_custom_emoji_id_; public: explicit UpdateColorQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(bool for_profile, AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id) { for_profile_ = for_profile; accent_color_id_ = accent_color_id; background_custom_emoji_id_ = background_custom_emoji_id; int32 flags = 0; if (for_profile) { flags |= telegram_api::account_updateColor::FOR_PROFILE_MASK; } if (accent_color_id.is_valid()) { flags |= telegram_api::account_updateColor::COLOR_MASK; } if (background_custom_emoji_id.is_valid()) { flags |= telegram_api::account_updateColor::BACKGROUND_EMOJI_ID_MASK; } send_query(G()->net_query_creator().create( telegram_api::account_updateColor(flags, false /*ignored*/, accent_color_id.get(), background_custom_emoji_id.get()), {{"me"}})); } 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(DEBUG) << "Receive result for UpdateColorQuery: " << result_ptr.ok(); td_->user_manager_->on_update_accent_color_success(for_profile_, accent_color_id_, background_custom_emoji_id_); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UpdateProfileQuery final : public Td::ResultHandler { Promise promise_; int32 flags_; string first_name_; string last_name_; string about_; public: explicit UpdateProfileQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(int32 flags, const string &first_name, const string &last_name, const string &about) { flags_ = flags; first_name_ = first_name; last_name_ = last_name; about_ = about; send_query(G()->net_query_creator().create(telegram_api::account_updateProfile(flags, first_name, last_name, about), {{"me"}})); } 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(DEBUG) << "Receive result for UpdateProfileQuery: " << to_string(result_ptr.ok()); td_->user_manager_->on_get_user(result_ptr.move_as_ok(), "UpdateProfileQuery"); td_->user_manager_->on_update_profile_success(flags_, first_name_, last_name_, about_); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UpdateUsernameQuery final : public Td::ResultHandler { Promise promise_; public: explicit UpdateUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &username) { send_query(G()->net_query_creator().create(telegram_api::account_updateUsername(username), {{"me"}})); } 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(DEBUG) << "Receive result for UpdateUsernameQuery: " << to_string(result_ptr.ok()); td_->user_manager_->on_get_user(result_ptr.move_as_ok(), "UpdateUsernameQuery"); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED" && !td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; } promise_.set_error(std::move(status)); } }; class ToggleUsernameQuery final : public Td::ResultHandler { Promise promise_; string username_; bool is_active_; public: explicit ToggleUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(string &&username, bool is_active) { username_ = std::move(username); is_active_ = is_active; send_query(G()->net_query_creator().create(telegram_api::account_toggleUsername(username_, is_active_), {{"me"}})); } 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 ToggleUsernameQuery: " << result; td_->user_manager_->on_update_username_is_active(td_->user_manager_->get_my_id(), std::move(username_), is_active_, std::move(promise_)); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED") { td_->user_manager_->on_update_username_is_active(td_->user_manager_->get_my_id(), std::move(username_), is_active_, std::move(promise_)); return; } promise_.set_error(std::move(status)); } }; class ReorderUsernamesQuery final : public Td::ResultHandler { Promise promise_; vector usernames_; public: explicit ReorderUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector &&usernames) { usernames_ = usernames; send_query(G()->net_query_creator().create(telegram_api::account_reorderUsernames(std::move(usernames)), {{"me"}})); } 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 ReorderUsernamesQuery: " << result; if (!result) { return on_error(Status::Error(500, "Usernames weren't updated")); } td_->user_manager_->on_update_active_usernames_order(td_->user_manager_->get_my_id(), std::move(usernames_), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED") { td_->user_manager_->on_update_active_usernames_order(td_->user_manager_->get_my_id(), std::move(usernames_), std::move(promise_)); return; } promise_.set_error(std::move(status)); } }; class ToggleBotUsernameQuery final : public Td::ResultHandler { Promise promise_; UserId bot_user_id_; string username_; bool is_active_; public: explicit ToggleBotUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id, string &&username, bool is_active) { bot_user_id_ = bot_user_id; username_ = std::move(username); is_active_ = is_active; auto r_input_user = td_->user_manager_->get_input_user(bot_user_id_); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } send_query(G()->net_query_creator().create( telegram_api::bots_toggleUsername(r_input_user.move_as_ok(), username_, is_active_), {{bot_user_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 ToggleBotUsernameQuery: " << result; td_->user_manager_->on_update_username_is_active(bot_user_id_, std::move(username_), is_active_, std::move(promise_)); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED") { td_->user_manager_->on_update_username_is_active(bot_user_id_, std::move(username_), is_active_, std::move(promise_)); return; } promise_.set_error(std::move(status)); } }; class ReorderBotUsernamesQuery final : public Td::ResultHandler { Promise promise_; UserId bot_user_id_; vector usernames_; public: explicit ReorderBotUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId bot_user_id, vector &&usernames) { bot_user_id_ = bot_user_id; usernames_ = usernames; auto r_input_user = td_->user_manager_->get_input_user(bot_user_id_); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } send_query(G()->net_query_creator().create( telegram_api::bots_reorderUsernames(r_input_user.move_as_ok(), std::move(usernames)), {{bot_user_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 ReorderBotUsernamesQuery: " << result; if (!result) { return on_error(Status::Error(500, "Usernames weren't updated")); } td_->user_manager_->on_update_active_usernames_order(bot_user_id_, std::move(usernames_), std::move(promise_)); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED") { td_->user_manager_->on_update_active_usernames_order(bot_user_id_, std::move(usernames_), std::move(promise_)); return; } promise_.set_error(std::move(status)); } }; class UpdateBirthdayQuery final : public Td::ResultHandler { Promise promise_; public: explicit UpdateBirthdayQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const Birthdate &birthdate) { int32 flags = 0; if (!birthdate.is_empty()) { flags |= telegram_api::account_updateBirthday::BIRTHDAY_MASK; } send_query(G()->net_query_creator().create( telegram_api::account_updateBirthday(flags, birthdate.get_input_birthday()), {{"me"}})); } 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(DEBUG) << "Receive result for UpdateBirthdayQuery: " << result_ptr.ok(); if (result_ptr.ok()) { promise_.set_value(Unit()); } else { promise_.set_error(Status::Error(400, "Failed to change birthdate")); } } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UpdatePersonalChannelQuery final : public Td::ResultHandler { Promise promise_; public: explicit UpdatePersonalChannelQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id) { telegram_api::object_ptr input_channel; if (channel_id == ChannelId()) { input_channel = telegram_api::make_object(); } else { input_channel = td_->contacts_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); } send_query(G()->net_query_creator().create(telegram_api::account_updatePersonalChannel(std::move(input_channel)), {{"me"}})); } 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(DEBUG) << "Receive result for UpdatePersonalChannelQuery: " << result_ptr.ok(); if (result_ptr.ok()) { promise_.set_value(Unit()); } else { promise_.set_error(Status::Error(400, "Failed to change personal chat")); } } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UpdateEmojiStatusQuery final : public Td::ResultHandler { Promise promise_; public: explicit UpdateEmojiStatusQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const EmojiStatus &emoji_status) { send_query(G()->net_query_creator().create( telegram_api::account_updateEmojiStatus(emoji_status.get_input_emoji_status()), {{"me"}})); } 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(DEBUG) << "Receive result for UpdateEmojiStatusQuery: " << result_ptr.ok(); if (result_ptr.ok()) { promise_.set_value(Unit()); } else { promise_.set_error(Status::Error(400, "Failed to change Premium badge")); } } void on_error(Status status) final { get_recent_emoji_statuses(td_, Auto()); promise_.set_error(std::move(status)); } }; class GetUsersQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetUsersQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector> &&input_users) { send_query(G()->net_query_creator().create(telegram_api::users_getUsers(std::move(input_users)))); } 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_->user_manager_->on_get_users(result_ptr.move_as_ok(), "GetUsersQuery"); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetFullUserQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetFullUserQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(telegram_api::object_ptr &&input_user) { send_query(G()->net_query_creator().create(telegram_api::users_getFullUser(std::move(input_user)))); } 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 GetFullUserQuery: " << to_string(ptr); td_->user_manager_->on_get_users(std::move(ptr->users_), "GetFullUserQuery"); td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetFullUserQuery"); td_->user_manager_->on_get_user_full(std::move(ptr->full_user_)); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetUserPhotosQuery final : public Td::ResultHandler { Promise promise_; UserId user_id_; int32 offset_; int32 limit_; public: explicit GetUserPhotosQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(UserId user_id, telegram_api::object_ptr &&input_user, int32 offset, int32 limit, int64 photo_id) { user_id_ = user_id; offset_ = offset; limit_ = limit; send_query(G()->net_query_creator().create( telegram_api::photos_getUserPhotos(std::move(input_user), offset, photo_id, limit))); } 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 GetUserPhotosQuery: " << to_string(ptr); int32 constructor_id = ptr->get_id(); if (constructor_id == telegram_api::photos_photos::ID) { auto photos = move_tl_object_as(ptr); td_->user_manager_->on_get_users(std::move(photos->users_), "GetUserPhotosQuery"); auto photos_size = narrow_cast(photos->photos_.size()); td_->user_manager_->on_get_user_photos(user_id_, offset_, limit_, photos_size, std::move(photos->photos_)); } else { CHECK(constructor_id == telegram_api::photos_photosSlice::ID); auto photos = move_tl_object_as(ptr); td_->user_manager_->on_get_users(std::move(photos->users_), "GetUserPhotosQuery slice"); td_->user_manager_->on_get_user_photos(user_id_, offset_, limit_, photos->count_, std::move(photos->photos_)); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetSupportUserQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetSupportUserQuery(Promise &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::help_getSupport())); } 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 GetSupportUserQuery: " << to_string(ptr); auto user_id = UserManager::get_user_id(ptr->user_); td_->user_manager_->on_get_user(std::move(ptr->user_), "GetSupportUserQuery"); promise_.set_value(std::move(user_id)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetIsPremiumRequiredToContactQuery final : public Td::ResultHandler { Promise promise_; vector user_ids_; public: explicit GetIsPremiumRequiredToContactQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector &&user_ids, vector> &&input_users) { user_ids_ = std::move(user_ids); send_query( G()->net_query_creator().create(telegram_api::users_getIsPremiumRequiredToContact(std::move(input_users)))); } 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_->user_manager_->on_get_is_premium_required_to_contact_users(std::move(user_ids_), result_ptr.move_as_ok(), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; template void UserManager::User::store(StorerT &storer) const { using td::store; bool has_last_name = !last_name.empty(); bool legacy_has_username = false; bool has_photo = photo.small_file_id.is_valid(); bool has_language_code = !language_code.empty(); bool have_access_hash = access_hash != -1; bool has_cache_version = cache_version != 0; bool has_is_contact = true; bool has_restriction_reasons = !restriction_reasons.empty(); bool has_emoji_status = !emoji_status.is_empty(); 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(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_received); STORE_FLAG(is_verified); STORE_FLAG(is_deleted); STORE_FLAG(is_bot); STORE_FLAG(can_join_groups); STORE_FLAG(can_read_all_group_messages); STORE_FLAG(is_inline_bot); STORE_FLAG(need_location_bot); STORE_FLAG(has_last_name); STORE_FLAG(legacy_has_username); STORE_FLAG(has_photo); STORE_FLAG(false); // legacy is_restricted STORE_FLAG(has_language_code); STORE_FLAG(have_access_hash); STORE_FLAG(is_support); STORE_FLAG(is_min_access_hash); STORE_FLAG(is_scam); STORE_FLAG(has_cache_version); STORE_FLAG(has_is_contact); STORE_FLAG(is_contact); STORE_FLAG(is_mutual_contact); STORE_FLAG(has_restriction_reasons); STORE_FLAG(need_apply_min_photo); STORE_FLAG(is_fake); STORE_FLAG(can_be_added_to_attach_menu); STORE_FLAG(is_premium); STORE_FLAG(attach_menu_enabled); STORE_FLAG(has_emoji_status); STORE_FLAG(has_usernames); STORE_FLAG(can_be_edited_bot); END_STORE_FLAGS(); if (has_flags2) { BEGIN_STORE_FLAGS(); STORE_FLAG(is_close_friend); STORE_FLAG(stories_hidden); STORE_FLAG(false); 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(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(contact_require_premium); STORE_FLAG(is_business_bot); END_STORE_FLAGS(); } store(first_name, storer); if (has_last_name) { store(last_name, storer); } store(phone_number, storer); if (have_access_hash) { store(access_hash, storer); } if (has_photo) { store(photo, storer); } store(was_online, storer); if (has_restriction_reasons) { store(restriction_reasons, storer); } if (is_inline_bot) { store(inline_query_placeholder, storer); } if (is_bot) { store(bot_info_version, storer); } if (has_language_code) { store(language_code, storer); } if (has_cache_version) { store(cache_version, storer); } if (has_emoji_status) { store(emoji_status, 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); } } template void UserManager::User::parse(ParserT &parser) { using td::parse; bool has_last_name; bool legacy_has_username; bool has_photo; bool legacy_is_restricted; bool has_language_code; bool have_access_hash; bool has_cache_version; bool has_is_contact; bool has_restriction_reasons; bool has_emoji_status; bool has_usernames; bool has_flags2 = parser.version() >= static_cast(Version::AddUserFlags2); bool legacy_has_stories = false; 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; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_received); PARSE_FLAG(is_verified); PARSE_FLAG(is_deleted); PARSE_FLAG(is_bot); PARSE_FLAG(can_join_groups); PARSE_FLAG(can_read_all_group_messages); PARSE_FLAG(is_inline_bot); PARSE_FLAG(need_location_bot); PARSE_FLAG(has_last_name); PARSE_FLAG(legacy_has_username); PARSE_FLAG(has_photo); PARSE_FLAG(legacy_is_restricted); PARSE_FLAG(has_language_code); PARSE_FLAG(have_access_hash); PARSE_FLAG(is_support); PARSE_FLAG(is_min_access_hash); PARSE_FLAG(is_scam); PARSE_FLAG(has_cache_version); PARSE_FLAG(has_is_contact); PARSE_FLAG(is_contact); PARSE_FLAG(is_mutual_contact); PARSE_FLAG(has_restriction_reasons); PARSE_FLAG(need_apply_min_photo); PARSE_FLAG(is_fake); PARSE_FLAG(can_be_added_to_attach_menu); PARSE_FLAG(is_premium); PARSE_FLAG(attach_menu_enabled); PARSE_FLAG(has_emoji_status); PARSE_FLAG(has_usernames); PARSE_FLAG(can_be_edited_bot); END_PARSE_FLAGS(); if (has_flags2) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_close_friend); PARSE_FLAG(stories_hidden); PARSE_FLAG(legacy_has_stories); 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(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(contact_require_premium); PARSE_FLAG(is_business_bot); END_PARSE_FLAGS(); } parse(first_name, parser); if (has_last_name) { parse(last_name, parser); } if (legacy_has_username) { CHECK(!has_usernames); string username; parse(username, parser); usernames = Usernames(std::move(username), vector>()); } parse(phone_number, parser); if (parser.version() < static_cast(Version::FixMinUsers)) { have_access_hash = is_received; } if (have_access_hash) { parse(access_hash, parser); } else { is_min_access_hash = true; } if (has_photo) { parse(photo, parser); } if (!has_is_contact) { // enum class LinkState : uint8 { Unknown, None, KnowsPhoneNumber, Contact }; uint32 link_state_inbound; uint32 link_state_outbound; parse(link_state_inbound, parser); parse(link_state_outbound, parser); is_contact = link_state_outbound == 3; is_mutual_contact = is_contact && link_state_inbound == 3; is_close_friend = false; } parse(was_online, 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 (is_inline_bot) { parse(inline_query_placeholder, parser); } if (is_bot) { parse(bot_info_version, parser); } if (has_language_code) { parse(language_code, parser); } if (has_cache_version) { parse(cache_version, parser); } if (has_emoji_status) { parse(emoji_status, 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 (!check_utf8(first_name)) { LOG(ERROR) << "Have invalid first name \"" << first_name << '"'; first_name.clear(); cache_version = 0; } if (!check_utf8(last_name)) { LOG(ERROR) << "Have invalid last name \"" << last_name << '"'; last_name.clear(); cache_version = 0; } clean_phone_number(phone_number); if (first_name.empty() && last_name.empty()) { first_name = phone_number; } if (!is_contact && is_mutual_contact) { LOG(ERROR) << "Have invalid flag is_mutual_contact"; is_mutual_contact = false; cache_version = 0; } if (!is_contact && is_close_friend) { LOG(ERROR) << "Have invalid flag is_close_friend"; is_close_friend = false; cache_version = 0; } } template void UserManager::UserFull::store(StorerT &storer) const { using td::store; bool has_about = !about.empty(); bool has_photo = !photo.is_empty(); bool has_description = !description.empty(); bool has_commands = !commands.empty(); bool has_private_forward_name = !private_forward_name.empty(); bool has_group_administrator_rights = group_administrator_rights != AdministratorRights(); bool has_broadcast_administrator_rights = broadcast_administrator_rights != AdministratorRights(); bool has_menu_button = menu_button != nullptr; bool has_description_photo = !description_photo.is_empty(); bool has_description_animation = description_animation_file_id.is_valid(); bool has_premium_gift_options = !premium_gift_options.empty(); bool has_personal_photo = !personal_photo.is_empty(); bool has_fallback_photo = !fallback_photo.is_empty(); bool has_business_info = business_info != nullptr && !business_info->is_empty(); bool has_birthdate = !birthdate.is_empty(); bool has_personal_channel_id = personal_channel_id.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_about); STORE_FLAG(is_blocked); STORE_FLAG(can_be_called); STORE_FLAG(has_private_calls); STORE_FLAG(can_pin_messages); STORE_FLAG(need_phone_number_privacy_exception); // 5 STORE_FLAG(has_photo); STORE_FLAG(supports_video_calls); STORE_FLAG(has_description); STORE_FLAG(has_commands); STORE_FLAG(has_private_forward_name); // 10 STORE_FLAG(has_group_administrator_rights); STORE_FLAG(has_broadcast_administrator_rights); STORE_FLAG(has_menu_button); STORE_FLAG(has_description_photo); STORE_FLAG(has_description_animation); // 15 STORE_FLAG(has_premium_gift_options); STORE_FLAG(voice_messages_forbidden); STORE_FLAG(has_personal_photo); STORE_FLAG(has_fallback_photo); STORE_FLAG(has_pinned_stories); // 20 STORE_FLAG(is_blocked_for_stories); STORE_FLAG(wallpaper_overridden); STORE_FLAG(read_dates_private); STORE_FLAG(contact_require_premium); STORE_FLAG(has_business_info); // 25 STORE_FLAG(has_birthdate); STORE_FLAG(has_personal_channel_id); END_STORE_FLAGS(); if (has_about) { store(about, storer); } store(common_chat_count, storer); store_time(expires_at, storer); if (has_photo) { store(photo, storer); } if (has_description) { store(description, storer); } if (has_commands) { store(commands, storer); } if (has_private_forward_name) { store(private_forward_name, storer); } if (has_group_administrator_rights) { store(group_administrator_rights, storer); } if (has_broadcast_administrator_rights) { store(broadcast_administrator_rights, storer); } if (has_menu_button) { store(menu_button, storer); } if (has_description_photo) { store(description_photo, storer); } if (has_description_animation) { storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(description_animation_file_id, storer); } if (has_premium_gift_options) { store(premium_gift_options, storer); } if (has_personal_photo) { store(personal_photo, storer); } if (has_fallback_photo) { store(fallback_photo, storer); } if (has_business_info) { store(business_info, storer); } if (has_birthdate) { store(birthdate, storer); } if (has_personal_channel_id) { store(personal_channel_id, storer); } } template void UserManager::UserFull::parse(ParserT &parser) { using td::parse; bool has_about; bool has_photo; bool has_description; bool has_commands; bool has_private_forward_name; bool has_group_administrator_rights; bool has_broadcast_administrator_rights; bool has_menu_button; bool has_description_photo; bool has_description_animation; bool has_premium_gift_options; bool has_personal_photo; bool has_fallback_photo; bool has_business_info; bool has_birthdate; bool has_personal_channel_id; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_about); PARSE_FLAG(is_blocked); PARSE_FLAG(can_be_called); PARSE_FLAG(has_private_calls); PARSE_FLAG(can_pin_messages); PARSE_FLAG(need_phone_number_privacy_exception); PARSE_FLAG(has_photo); PARSE_FLAG(supports_video_calls); PARSE_FLAG(has_description); PARSE_FLAG(has_commands); PARSE_FLAG(has_private_forward_name); PARSE_FLAG(has_group_administrator_rights); PARSE_FLAG(has_broadcast_administrator_rights); PARSE_FLAG(has_menu_button); PARSE_FLAG(has_description_photo); PARSE_FLAG(has_description_animation); PARSE_FLAG(has_premium_gift_options); PARSE_FLAG(voice_messages_forbidden); PARSE_FLAG(has_personal_photo); PARSE_FLAG(has_fallback_photo); PARSE_FLAG(has_pinned_stories); PARSE_FLAG(is_blocked_for_stories); PARSE_FLAG(wallpaper_overridden); PARSE_FLAG(read_dates_private); PARSE_FLAG(contact_require_premium); PARSE_FLAG(has_business_info); PARSE_FLAG(has_birthdate); PARSE_FLAG(has_personal_channel_id); END_PARSE_FLAGS(); if (has_about) { parse(about, parser); } parse(common_chat_count, parser); parse_time(expires_at, parser); if (has_photo) { parse(photo, parser); } if (has_description) { parse(description, parser); } if (has_commands) { parse(commands, parser); } if (has_private_forward_name) { parse(private_forward_name, parser); } if (has_group_administrator_rights) { parse(group_administrator_rights, parser); } if (has_broadcast_administrator_rights) { parse(broadcast_administrator_rights, parser); } if (has_menu_button) { parse(menu_button, parser); } if (has_description_photo) { parse(description_photo, parser); } if (has_description_animation) { description_animation_file_id = parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser); } if (has_premium_gift_options) { parse(premium_gift_options, parser); } if (has_personal_photo) { parse(personal_photo, parser); } if (has_fallback_photo) { parse(fallback_photo, parser); } if (has_business_info) { parse(business_info, parser); } if (has_birthdate) { parse(birthdate, parser); } if (has_personal_channel_id) { parse(personal_channel_id, parser); } } template void UserManager::SecretChat::store(StorerT &storer) const { using td::store; bool has_layer = layer > static_cast(SecretChatLayer::Default); bool has_initial_folder_id = initial_folder_id != FolderId(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_outbound); STORE_FLAG(has_layer); STORE_FLAG(has_initial_folder_id); END_STORE_FLAGS(); store(access_hash, storer); store(user_id, storer); store(state, storer); store(ttl, storer); store(date, storer); store(key_hash, storer); if (has_layer) { store(layer, storer); } if (has_initial_folder_id) { store(initial_folder_id, storer); } } template void UserManager::SecretChat::parse(ParserT &parser) { using td::parse; bool has_layer; bool has_initial_folder_id; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_outbound); PARSE_FLAG(has_layer); PARSE_FLAG(has_initial_folder_id); END_PARSE_FLAGS(); if (parser.version() >= static_cast(Version::AddAccessHashToSecretChat)) { parse(access_hash, parser); } parse(user_id, parser); parse(state, parser); parse(ttl, parser); parse(date, parser); if (parser.version() >= static_cast(Version::AddKeyHashToSecretChat)) { parse(key_hash, parser); } if (has_layer) { parse(layer, parser); } else { layer = static_cast(SecretChatLayer::Default); } if (has_initial_folder_id) { parse(initial_folder_id, parser); } } class UserManager::UserLogEvent { public: UserId user_id; const User *u_in = nullptr; unique_ptr u_out; UserLogEvent() = default; UserLogEvent(UserId user_id, const User *u) : user_id(user_id), u_in(u) { } template void store(StorerT &storer) const { td::store(user_id, storer); td::store(*u_in, storer); } template void parse(ParserT &parser) { td::parse(user_id, parser); td::parse(u_out, parser); } }; class UserManager::SecretChatLogEvent { public: SecretChatId secret_chat_id; const SecretChat *c_in = nullptr; unique_ptr c_out; SecretChatLogEvent() = default; SecretChatLogEvent(SecretChatId secret_chat_id, const SecretChat *c) : secret_chat_id(secret_chat_id), c_in(c) { } template void store(StorerT &storer) const { td::store(secret_chat_id, storer); td::store(*c_in, storer); } template void parse(ParserT &parser) { td::parse(secret_chat_id, parser); td::parse(c_out, parser); } }; class UserManager::UploadProfilePhotoCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { send_closure_later(G()->user_manager(), &UserManager::on_upload_profile_photo, file_id, std::move(input_file)); } void on_upload_encrypted_ok(FileId file_id, telegram_api::object_ptr input_file) final { UNREACHABLE(); } void on_upload_secure_ok(FileId file_id, telegram_api::object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->user_manager(), &UserManager::on_upload_profile_photo_error, file_id, std::move(error)); } }; UserManager::UserManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { upload_profile_photo_callback_ = std::make_shared(); my_id_ = load_my_id(); if (G()->use_chat_info_database()) { auto next_contacts_sync_date_string = G()->td_db()->get_binlog_pmc()->get("next_contacts_sync_date"); if (!next_contacts_sync_date_string.empty()) { next_contacts_sync_date_ = min(to_integer(next_contacts_sync_date_string), G()->unix_time() + 100000); } auto saved_contact_count_string = G()->td_db()->get_binlog_pmc()->get("saved_contact_count"); if (!saved_contact_count_string.empty()) { saved_contact_count_ = to_integer(saved_contact_count_string); } } else if (!td_->auth_manager_->is_bot()) { G()->td_db()->get_binlog_pmc()->erase("next_contacts_sync_date"); G()->td_db()->get_binlog_pmc()->erase("saved_contact_count"); } if (G()->use_sqlite_pmc()) { G()->td_db()->get_sqlite_pmc()->erase_by_prefix("us_bot_info", Auto()); } if (!td_->auth_manager_->is_bot()) { was_online_local_ = to_integer(G()->td_db()->get_binlog_pmc()->get("my_was_online_local")); was_online_remote_ = to_integer(G()->td_db()->get_binlog_pmc()->get("my_was_online_remote")); auto unix_time = G()->unix_time(); if (was_online_local_ >= unix_time && !td_->is_online()) { was_online_local_ = unix_time - 1; } } user_online_timeout_.set_callback(on_user_online_timeout_callback); user_online_timeout_.set_callback_data(static_cast(this)); user_emoji_status_timeout_.set_callback(on_user_emoji_status_timeout_callback); user_emoji_status_timeout_.set_callback_data(static_cast(this)); get_user_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto input_users = transform(query_ids, [this](int64 query_id) { return get_input_user_force(UserId(query_id)); }); td_->create_handler(std::move(promise))->send(std::move(input_users)); }); get_is_premium_required_to_contact_queries_.set_merge_function( [this](vector query_ids, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto user_ids = UserId::get_user_ids(query_ids); auto input_users = transform(user_ids, [this](UserId user_id) { return get_input_user_force(user_id); }); td_->create_handler(std::move(promise)) ->send(std::move(user_ids), std::move(input_users)); }); } UserManager::~UserManager() { Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), users_, users_full_, user_photos_, unknown_users_, pending_user_photos_, user_profile_photo_file_source_ids_, my_photo_file_id_, user_full_file_source_ids_, secret_chats_, unknown_secret_chats_, secret_chats_with_user_); Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), loaded_from_database_users_, unavailable_user_fulls_, loaded_from_database_secret_chats_, resolved_phone_numbers_, all_imported_contacts_, restricted_user_ids_); } void UserManager::tear_down() { parent_.reset(); LOG(DEBUG) << "Have " << users_.calc_size() << " users and " << secret_chats_.calc_size() << " secret chats to free"; LOG(DEBUG) << "Have " << users_full_.calc_size() << " full users to free"; } void UserManager::on_user_online_timeout_callback(void *user_manager_ptr, int64 user_id_long) { if (G()->close_flag()) { return; } auto user_manager = static_cast(user_manager_ptr); send_closure_later(user_manager->actor_id(user_manager), &UserManager::on_user_online_timeout, UserId(user_id_long)); } void UserManager::on_user_online_timeout(UserId user_id) { if (G()->close_flag()) { return; } auto u = get_user(user_id); CHECK(u != nullptr); CHECK(u->is_update_user_sent); LOG(INFO) << "Update " << user_id << " online status to offline"; send_closure(G()->td(), &Td::send_update, td_api::make_object(user_id.get(), get_user_status_object(user_id, u, G()->unix_time()))); td_->dialog_participant_manager_->update_user_online_member_count(user_id); } void UserManager::on_user_emoji_status_timeout_callback(void *user_manager_ptr, int64 user_id_long) { if (G()->close_flag()) { return; } auto user_manager = static_cast(user_manager_ptr); send_closure_later(user_manager->actor_id(user_manager), &UserManager::on_user_emoji_status_timeout, UserId(user_id_long)); } void UserManager::on_user_emoji_status_timeout(UserId user_id) { if (G()->close_flag()) { return; } auto u = get_user(user_id); CHECK(u != nullptr); CHECK(u->is_update_user_sent); update_user(u, user_id); } UserId UserManager::get_user_id(const telegram_api::object_ptr &user) { CHECK(user != nullptr); switch (user->get_id()) { case telegram_api::userEmpty::ID: return UserId(static_cast(user.get())->id_); case telegram_api::user::ID: return UserId(static_cast(user.get())->id_); default: UNREACHABLE(); return UserId(); } } UserId UserManager::load_my_id() { auto id_string = G()->td_db()->get_binlog_pmc()->get("my_id"); if (!id_string.empty()) { UserId my_id(to_integer(id_string)); if (my_id.is_valid()) { return my_id; } my_id = UserId(to_integer(Slice(id_string).substr(5))); if (my_id.is_valid()) { G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get())); return my_id; } LOG(ERROR) << "Wrong my ID = \"" << id_string << "\" stored in database"; } return UserId(); } UserId UserManager::get_my_id() const { LOG_IF(ERROR, !my_id_.is_valid()) << "Wrong or unknown my ID returned"; return my_id_; } void UserManager::set_my_id(UserId my_id) { UserId my_old_id = my_id_; if (my_old_id.is_valid() && my_old_id != my_id) { LOG(ERROR) << "Already know that me is " << my_old_id << " but received userSelf with " << my_id; return; } if (!my_id.is_valid()) { LOG(ERROR) << "Receive invalid my ID " << my_id; return; } if (my_old_id != my_id) { my_id_ = my_id; G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get())); td_->option_manager_->set_option_integer("my_id", my_id_.get()); if (!td_->auth_manager_->is_bot()) { G()->td_db()->get_binlog_pmc()->force_sync(Promise(), "set_my_id"); } } } UserId UserManager::get_service_notifications_user_id() { return UserId(static_cast(777000)); } UserId UserManager::add_service_notifications_user() { auto user_id = get_service_notifications_user_id(); if (!have_user_force(user_id, "add_service_notifications_user")) { LOG(FATAL) << "Failed to load service notification user"; } return user_id; } UserId UserManager::get_replies_bot_user_id() { return UserId(static_cast(G()->is_test_dc() ? 708513 : 1271266957)); } UserId UserManager::get_anonymous_bot_user_id() { return UserId(static_cast(G()->is_test_dc() ? 552888 : 1087968824)); } UserId UserManager::get_channel_bot_user_id() { return UserId(static_cast(G()->is_test_dc() ? 936174 : 136817688)); } UserId UserManager::get_anti_spam_bot_user_id() { return UserId(static_cast(G()->is_test_dc() ? 2200583762ll : 5434988373ll)); } UserId UserManager::add_anonymous_bot_user() { auto user_id = get_anonymous_bot_user_id(); if (!have_user_force(user_id, "add_anonymous_bot_user")) { LOG(FATAL) << "Failed to load anonymous bot user"; } return user_id; } UserId UserManager::add_channel_bot_user() { auto user_id = get_channel_bot_user_id(); if (!have_user_force(user_id, "add_channel_bot_user")) { LOG(FATAL) << "Failed to load channel bot user"; } return user_id; } UserManager::MyOnlineStatusInfo UserManager::get_my_online_status() const { MyOnlineStatusInfo status_info; status_info.is_online_local = td_->is_online(); status_info.is_online_remote = was_online_remote_ > G()->unix_time(); status_info.was_online_local = was_online_local_; status_info.was_online_remote = was_online_remote_; return status_info; } void UserManager::set_my_online_status(bool is_online, bool send_update, bool is_local) { if (td_->auth_manager_->is_bot()) { return; // just in case } auto my_id = get_my_id(); User *u = get_user_force(my_id, "set_my_online_status"); if (u != nullptr) { int32 new_online; int32 unix_time = G()->unix_time(); if (is_online) { new_online = unix_time + 300; } else { new_online = unix_time - 1; } auto old_was_online = get_user_was_online(u, my_id, unix_time); if (is_local) { LOG(INFO) << "Update my local online from " << my_was_online_local_ << " to " << new_online; if (!is_online) { new_online = min(new_online, u->was_online); } if (new_online != my_was_online_local_) { my_was_online_local_ = new_online; } } else { if (my_was_online_local_ != 0 || new_online != u->was_online) { LOG(INFO) << "Update my online from " << u->was_online << " to " << new_online; my_was_online_local_ = 0; u->was_online = new_online; u->need_save_to_database = true; } } if (old_was_online != get_user_was_online(u, my_id, unix_time)) { u->is_status_changed = true; u->is_online_status_changed = true; } if (was_online_local_ != new_online) { was_online_local_ = new_online; VLOG(notifications) << "Set was_online_local to " << was_online_local_; G()->td_db()->get_binlog_pmc()->set("my_was_online_local", to_string(was_online_local_)); } if (send_update) { update_user(u, my_id); } } } void UserManager::on_get_user(telegram_api::object_ptr &&user_ptr, const char *source) { LOG(DEBUG) << "Receive from " << source << ' ' << to_string(user_ptr); int32 constructor_id = user_ptr->get_id(); if (constructor_id == telegram_api::userEmpty::ID) { auto user = move_tl_object_as(user_ptr); UserId user_id(user->id_); if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id << " from " << source; return; } LOG(INFO) << "Receive empty " << user_id << " from " << source; User *u = get_user_force(user_id, source); if (u == nullptr && Slice(source) != Slice("GetUsersQuery")) { // userEmpty should be received only through getUsers for nonexistent users LOG(ERROR) << "Have no information about " << user_id << ", but received userEmpty from " << source; } return; } CHECK(constructor_id == telegram_api::user::ID); auto user = move_tl_object_as(user_ptr); UserId user_id(user->id_); if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } int32 flags = user->flags_; int32 flags2 = user->flags2_; LOG(INFO) << "Receive " << user_id << " with flags " << flags << ' ' << flags2 << " from " << source; // the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used bool is_bot = (flags & USER_FLAG_IS_BOT) != 0; if (flags & USER_FLAG_IS_ME) { set_my_id(user_id); if (!is_bot) { td_->option_manager_->set_option_string("my_phone_number", user->phone_); } } bool have_access_hash = (flags & USER_FLAG_HAS_ACCESS_HASH) != 0; bool is_received = (flags & USER_FLAG_IS_INACCESSIBLE) == 0; bool is_contact = (flags & USER_FLAG_IS_CONTACT) != 0; User *u = get_user(user_id); if (u == nullptr) { if (!is_received) { // we must preload received inaccessible users from database in order to not save // the min-user to the database and to not override access_hash and other data u = get_user_force(user_id, "on_get_user 2"); if (u == nullptr) { LOG(INFO) << "Receive inaccessible " << user_id; u = add_user(user_id); } } else if (is_contact && !are_contacts_loaded_) { // preload contact users from database to know that is_contact didn't changed // and the list of contacts doesn't need to be saved to the database u = get_user_force(user_id, "on_get_user 3"); if (u == nullptr) { LOG(INFO) << "Receive contact " << user_id << " for the first time"; u = add_user(user_id); } } else { u = add_user(user_id); } CHECK(u != nullptr); } if (have_access_hash) { // access_hash must be updated before photo auto access_hash = user->access_hash_; bool is_min_access_hash = !is_received && !((flags & USER_FLAG_HAS_PHONE_NUMBER) != 0 && user->phone_.empty()); if (u->access_hash != access_hash && (!is_min_access_hash || u->is_min_access_hash || u->access_hash == -1)) { LOG(DEBUG) << "Access hash has changed for " << user_id << " from " << u->access_hash << "/" << u->is_min_access_hash << " to " << access_hash << "/" << is_min_access_hash; u->access_hash = access_hash; u->is_min_access_hash = is_min_access_hash; u->need_save_to_database = true; } } bool is_verified = (flags & USER_FLAG_IS_VERIFIED) != 0; bool is_premium = (flags & USER_FLAG_IS_PREMIUM) != 0; bool is_support = (flags & USER_FLAG_IS_SUPPORT) != 0; bool is_deleted = (flags & USER_FLAG_IS_DELETED) != 0; bool can_join_groups = (flags & USER_FLAG_IS_PRIVATE_BOT) == 0; bool can_read_all_group_messages = (flags & USER_FLAG_IS_BOT_WITH_PRIVACY_DISABLED) != 0; bool can_be_added_to_attach_menu = (flags & USER_FLAG_IS_ATTACH_MENU_BOT) != 0; bool attach_menu_enabled = (flags & USER_FLAG_ATTACH_MENU_ENABLED) != 0; bool is_scam = (flags & USER_FLAG_IS_SCAM) != 0; bool can_be_edited_bot = (flags2 & USER_FLAG_CAN_BE_EDITED_BOT) != 0; bool is_inline_bot = (flags & USER_FLAG_IS_INLINE_BOT) != 0; bool is_business_bot = user->bot_business_; string inline_query_placeholder = std::move(user->bot_inline_placeholder_); bool need_location_bot = (flags & USER_FLAG_NEED_LOCATION_BOT) != 0; bool has_bot_info_version = (flags & USER_FLAG_HAS_BOT_INFO_VERSION) != 0; bool need_apply_min_photo = (flags & USER_FLAG_NEED_APPLY_MIN_PHOTO) != 0; bool is_fake = (flags & USER_FLAG_IS_FAKE) != 0; bool stories_available = user->stories_max_id_ > 0; bool stories_unavailable = user->stories_unavailable_; bool stories_hidden = user->stories_hidden_; bool contact_require_premium = user->contact_require_premium_; if (!is_bot && (!can_join_groups || can_read_all_group_messages || can_be_added_to_attach_menu || can_be_edited_bot || is_inline_bot || is_business_bot)) { LOG(ERROR) << "Receive not bot " << user_id << " with bot properties from " << source; can_join_groups = true; can_read_all_group_messages = false; can_be_added_to_attach_menu = false; can_be_edited_bot = false; is_inline_bot = false; is_business_bot = false; } if (need_location_bot && !is_inline_bot) { LOG(ERROR) << "Receive not inline bot " << user_id << " which needs user location from " << source; need_location_bot = false; } if (is_deleted) { // just in case is_verified = false; is_premium = false; is_support = false; is_bot = false; can_join_groups = false; can_read_all_group_messages = false; can_be_added_to_attach_menu = false; can_be_edited_bot = false; is_inline_bot = false; is_business_bot = false; inline_query_placeholder = string(); need_location_bot = false; has_bot_info_version = false; need_apply_min_photo = false; } LOG_IF(ERROR, has_bot_info_version && !is_bot) << "Receive not bot " << user_id << " which has bot info version from " << source; int32 bot_info_version = has_bot_info_version ? user->bot_info_version_ : -1; if (is_verified != u->is_verified || is_support != u->is_support || is_bot != u->is_bot || can_join_groups != u->can_join_groups || can_read_all_group_messages != u->can_read_all_group_messages || is_scam != u->is_scam || is_fake != u->is_fake || is_inline_bot != u->is_inline_bot || is_business_bot != u->is_business_bot || inline_query_placeholder != u->inline_query_placeholder || need_location_bot != u->need_location_bot || can_be_added_to_attach_menu != u->can_be_added_to_attach_menu) { if (is_bot != u->is_bot) { LOG_IF(ERROR, !is_deleted && !u->is_deleted && u->is_received) << "User.is_bot has changed for " << user_id << "/" << u->usernames << " from " << source << " from " << u->is_bot << " to " << is_bot; u->is_full_info_changed = true; } u->is_verified = is_verified; u->is_support = is_support; u->is_bot = is_bot; u->can_join_groups = can_join_groups; u->can_read_all_group_messages = can_read_all_group_messages; u->is_scam = is_scam; u->is_fake = is_fake; u->is_inline_bot = is_inline_bot; u->is_business_bot = is_business_bot; u->inline_query_placeholder = std::move(inline_query_placeholder); u->need_location_bot = need_location_bot; u->can_be_added_to_attach_menu = can_be_added_to_attach_menu; LOG(DEBUG) << "Info has changed for " << user_id; u->is_changed = true; } if (u->contact_require_premium != contact_require_premium) { u->contact_require_premium = contact_require_premium; u->is_changed = true; user_full_contact_require_premium_.erase(user_id); } if (is_received && attach_menu_enabled != u->attach_menu_enabled) { u->attach_menu_enabled = attach_menu_enabled; u->is_changed = true; } if (is_premium != u->is_premium) { u->is_premium = is_premium; u->is_is_premium_changed = true; u->is_changed = true; u->is_full_info_changed = true; } if (is_received && can_be_edited_bot != u->can_be_edited_bot) { u->can_be_edited_bot = can_be_edited_bot; u->is_changed = true; u->is_full_info_changed = true; } if (u->bot_info_version != bot_info_version) { u->bot_info_version = bot_info_version; LOG(DEBUG) << "Bot info version has changed for " << user_id; u->need_save_to_database = true; } if (is_received && u->need_apply_min_photo != need_apply_min_photo) { LOG(DEBUG) << "Need apply min photo has changed for " << user_id; u->need_apply_min_photo = need_apply_min_photo; u->need_save_to_database = true; } if (is_received && !u->is_received) { u->is_received = true; LOG(DEBUG) << "Receive " << user_id; u->is_changed = true; } if (is_deleted != u->is_deleted) { u->is_deleted = is_deleted; LOG(DEBUG) << "User.is_deleted has changed for " << user_id << " to " << u->is_deleted; u->is_is_deleted_changed = true; u->is_changed = true; } bool has_language_code = (flags & USER_FLAG_HAS_LANGUAGE_CODE) != 0; LOG_IF(ERROR, has_language_code && !td_->auth_manager_->is_bot()) << "Receive language code for " << user_id << " from " << source; if (u->language_code != user->lang_code_ && !user->lang_code_.empty()) { u->language_code = user->lang_code_; LOG(DEBUG) << "Language code has changed for " << user_id << " to " << u->language_code; u->is_changed = true; } bool is_me_regular_user = !td_->auth_manager_->is_bot(); if (is_received || u->need_apply_min_photo || !u->is_received) { on_update_user_photo(u, user_id, std::move(user->photo_), source); } if (is_me_regular_user) { if (is_received || !u->is_received) { on_update_user_phone_number(u, user_id, std::move(user->phone_)); } if (is_received || !u->is_received || u->was_online == 0) { on_update_user_online(u, user_id, std::move(user->status_)); } if (is_received) { auto is_mutual_contact = (flags & USER_FLAG_IS_MUTUAL_CONTACT) != 0; auto is_close_friend = (flags2 & USER_FLAG_IS_CLOSE_FRIEND) != 0; on_update_user_is_contact(u, user_id, is_contact, is_mutual_contact, is_close_friend); } } if (is_received || !u->is_received) { on_update_user_name(u, user_id, std::move(user->first_name_), std::move(user->last_name_)); on_update_user_usernames(u, user_id, Usernames{std::move(user->username_), std::move(user->usernames_)}); } on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(user->emoji_status_))); PeerColor peer_color(user->color_); on_update_user_accent_color_id(u, user_id, peer_color.accent_color_id_); on_update_user_background_custom_emoji_id(u, user_id, peer_color.background_custom_emoji_id_); PeerColor profile_peer_color(user->profile_color_); on_update_user_profile_accent_color_id(u, user_id, profile_peer_color.accent_color_id_); on_update_user_profile_background_custom_emoji_id(u, user_id, profile_peer_color.background_custom_emoji_id_); if (is_me_regular_user) { if (is_received) { on_update_user_stories_hidden(u, user_id, stories_hidden); } if (stories_available || stories_unavailable) { // update at the end, because it calls need_poll_user_active_stories on_update_user_story_ids_impl(u, user_id, StoryId(user->stories_max_id_), StoryId()); } auto restriction_reasons = get_restriction_reasons(std::move(user->restriction_reason_)); if (restriction_reasons != u->restriction_reasons) { u->restriction_reasons = std::move(restriction_reasons); u->is_changed = true; } } if (u->cache_version != User::CACHE_VERSION && u->is_received) { u->cache_version = User::CACHE_VERSION; u->need_save_to_database = true; } u->is_received_from_server = true; update_user(u, user_id); } void UserManager::on_get_users(vector> &&users, const char *source) { for (auto &user : users) { on_get_user(std::move(user), source); } } void UserManager::on_binlog_user_event(BinlogEvent &&event) { if (!G()->use_chat_info_database()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } UserLogEvent log_event; if (log_event_parse(log_event, event.get_data()).is_error()) { LOG(ERROR) << "Failed to load a user from binlog"; binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } auto user_id = log_event.user_id; if (have_min_user(user_id) || !user_id.is_valid()) { LOG(ERROR) << "Skip adding already added " << user_id; binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } LOG(INFO) << "Add " << user_id << " from binlog"; users_.set(user_id, std::move(log_event.u_out)); User *u = get_user(user_id); CHECK(u != nullptr); u->log_event_id = event.id_; update_user(u, user_id, true, false); } void UserManager::on_binlog_secret_chat_event(BinlogEvent &&event) { if (!G()->use_chat_info_database()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } SecretChatLogEvent log_event; if (log_event_parse(log_event, event.get_data()).is_error()) { LOG(ERROR) << "Failed to load a secret chat from binlog"; binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } auto secret_chat_id = log_event.secret_chat_id; if (have_secret_chat(secret_chat_id) || !secret_chat_id.is_valid()) { LOG(ERROR) << "Skip adding already added " << secret_chat_id; binlog_erase(G()->td_db()->get_binlog(), event.id_); return; } LOG(INFO) << "Add " << secret_chat_id << " from binlog"; secret_chats_.set(secret_chat_id, std::move(log_event.c_out)); SecretChat *c = get_secret_chat(secret_chat_id); CHECK(c != nullptr); c->log_event_id = event.id_; update_secret_chat(c, secret_chat_id, true, false); } void UserManager::on_update_user_name(UserId user_id, string &&first_name, string &&last_name, Usernames &&usernames) { if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } User *u = get_user_force(user_id, "on_update_user_name"); if (u != nullptr) { on_update_user_name(u, user_id, std::move(first_name), std::move(last_name)); on_update_user_usernames(u, user_id, std::move(usernames)); update_user(u, user_id); } else { LOG(INFO) << "Ignore update user name about unknown " << user_id; } } void UserManager::on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name) { if (first_name.empty() && last_name.empty()) { first_name = u->phone_number; } if (u->first_name != first_name || u->last_name != last_name) { u->first_name = std::move(first_name); u->last_name = std::move(last_name); u->is_name_changed = true; LOG(DEBUG) << "Name has changed for " << user_id; u->is_changed = true; } } void UserManager::on_update_user_usernames(User *u, UserId user_id, Usernames &&usernames) { if (u->usernames != usernames) { td_->dialog_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); td_->messages_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); if (u->can_be_edited_bot && u->usernames.get_editable_username() != usernames.get_editable_username()) { u->is_full_info_changed = true; } u->usernames = std::move(usernames); u->is_username_changed = true; LOG(DEBUG) << "Usernames have changed for " << user_id; u->is_changed = true; } else if (u->is_bot || !td_->auth_manager_->is_bot()) { td_->dialog_manager_->on_dialog_usernames_received(DialogId(user_id), usernames, false); } } void UserManager::on_update_user_phone_number(UserId user_id, string &&phone_number) { if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } User *u = get_user_force(user_id, "on_update_user_phone_number"); if (u != nullptr) { on_update_user_phone_number(u, user_id, std::move(phone_number)); update_user(u, user_id); } else { LOG(INFO) << "Ignore update user phone number about unknown " << user_id; } } void UserManager::on_update_user_phone_number(User *u, UserId user_id, string &&phone_number) { if (td_->auth_manager_->is_bot()) { return; } clean_phone_number(phone_number); if (u->phone_number != phone_number) { if (!u->phone_number.empty()) { auto it = resolved_phone_numbers_.find(u->phone_number); if (it != resolved_phone_numbers_.end() && it->second == user_id) { resolved_phone_numbers_.erase(it); } } u->phone_number = std::move(phone_number); u->is_phone_number_changed = true; LOG(DEBUG) << "Phone number has changed for " << user_id; u->is_changed = true; } } void UserManager::on_update_user_photo(User *u, UserId user_id, telegram_api::object_ptr &&photo, const char *source) { if (td_->auth_manager_->is_bot() && !G()->use_chat_info_database()) { if (!u->is_photo_inited) { auto new_photo_id = get_profile_photo_id(photo); auto &old_photo = pending_user_photos_[user_id]; if (new_photo_id == get_profile_photo_id(old_photo)) { return; } if (photo != nullptr && photo->get_id() == telegram_api::userProfilePhoto::ID) { auto *profile_photo = static_cast(photo.get()); if ((profile_photo->flags_ & telegram_api::userProfilePhoto::STRIPPED_THUMB_MASK) != 0) { profile_photo->flags_ -= telegram_api::userProfilePhoto::STRIPPED_THUMB_MASK; profile_photo->stripped_thumb_ = BufferSlice(); } } old_photo = std::move(photo); drop_user_photos(user_id, new_photo_id == 0, "on_update_user_photo"); auto user_full = get_user_full(user_id); // must not load UserFull if (user_full != nullptr && new_photo_id != get_user_full_profile_photo_id(user_full)) { // we didn't sent updateUser yet, so we must not sent updateUserFull with new_photo_id yet drop_user_full_photos(user_full, user_id, 0, "on_update_user_photo"); } return; } if (u->is_received) { auto new_photo_id = get_profile_photo_id(photo); if (new_photo_id == u->photo.id) { return; } } } do_update_user_photo(u, user_id, std::move(photo), source); } void UserManager::do_update_user_photo(User *u, UserId user_id, telegram_api::object_ptr &&photo, const char *source) { ProfilePhoto new_photo = get_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, std::move(photo)); if (td_->auth_manager_->is_bot()) { new_photo.minithumbnail.clear(); } do_update_user_photo(u, user_id, std::move(new_photo), true, source); } void UserManager::do_update_user_photo(User *u, UserId user_id, ProfilePhoto &&new_photo, bool invalidate_photo_cache, const char *source) { u->is_photo_inited = true; if (need_update_profile_photo(u->photo, new_photo)) { LOG_IF(ERROR, u->access_hash == -1 && new_photo.small_file_id.is_valid()) << "Update profile photo of " << user_id << " without access hash from " << source; u->photo = new_photo; u->is_photo_changed = true; LOG(DEBUG) << "Photo has changed for " << user_id << " to " << u->photo << ", invalidate_photo_cache = " << invalidate_photo_cache << " from " << source; u->is_changed = true; if (invalidate_photo_cache) { drop_user_photos(user_id, u->photo.id == 0, source); } auto user_full = get_user_full(user_id); // must not load UserFull if (user_full != nullptr && u->photo.id != get_user_full_profile_photo_id(user_full)) { // we didn't sent updateUser yet, so we must not sent updateUserFull with u->photo.id yet drop_user_full_photos(user_full, user_id, 0, "do_update_user_photo"); } } else if (need_update_dialog_photo_minithumbnail(u->photo.minithumbnail, new_photo.minithumbnail)) { LOG(DEBUG) << "Photo minithumbnail has changed for " << user_id << " from " << source; u->photo.minithumbnail = std::move(new_photo.minithumbnail); u->is_photo_changed = true; u->is_changed = true; } } void UserManager::register_suggested_profile_photo(const Photo &photo) { auto photo_file_ids = photo_get_file_ids(photo); if (photo.is_empty() || photo_file_ids.empty()) { return; } auto first_file_id = photo_file_ids[0]; auto file_type = td_->file_manager_->get_file_view(first_file_id).get_type(); if (file_type == FileType::ProfilePhoto) { return; } CHECK(file_type == FileType::Photo); auto photo_id = photo.id.get(); if (photo_id != 0) { my_photo_file_id_[photo_id] = first_file_id; } } void UserManager::register_user_photo(User *u, UserId user_id, const Photo &photo) { auto photo_file_ids = photo_get_file_ids(photo); if (photo.is_empty() || photo_file_ids.empty()) { return; } auto first_file_id = photo_file_ids[0]; auto file_type = td_->file_manager_->get_file_view(first_file_id).get_type(); if (file_type == FileType::ProfilePhoto) { return; } CHECK(file_type == FileType::Photo); CHECK(u != nullptr); auto photo_id = photo.id.get(); if (photo_id != 0 && u->photo_ids.emplace(photo_id).second) { VLOG(file_references) << "Register photo " << photo_id << " of " << user_id; if (user_id == get_my_id()) { my_photo_file_id_[photo_id] = first_file_id; } auto file_source_id = user_profile_photo_file_source_ids_.get(std::make_pair(user_id, photo_id)); if (file_source_id.is_valid()) { VLOG(file_references) << "Move " << file_source_id << " inside of " << user_id; user_profile_photo_file_source_ids_.erase(std::make_pair(user_id, photo_id)); } else { VLOG(file_references) << "Need to create new file source for photo " << photo_id << " of " << user_id; file_source_id = td_->file_reference_manager_->create_user_photo_file_source(user_id, photo_id); } for (auto &file_id : photo_file_ids) { td_->file_manager_->add_file_source(file_id, file_source_id); } } } void UserManager::on_update_user_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id) { if (accent_color_id == AccentColorId(user_id) || !accent_color_id.is_valid()) { accent_color_id = AccentColorId(); } if (u->accent_color_id != accent_color_id) { u->accent_color_id = accent_color_id; u->is_accent_color_changed = true; u->is_changed = true; } } void UserManager::on_update_user_background_custom_emoji_id(User *u, UserId user_id, CustomEmojiId background_custom_emoji_id) { if (u->background_custom_emoji_id != background_custom_emoji_id) { u->background_custom_emoji_id = background_custom_emoji_id; u->is_accent_color_changed = true; u->is_changed = true; } } void UserManager::on_update_user_profile_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id) { if (!accent_color_id.is_valid()) { accent_color_id = AccentColorId(); } if (u->profile_accent_color_id != accent_color_id) { u->profile_accent_color_id = accent_color_id; u->is_accent_color_changed = true; u->is_changed = true; } } void UserManager::on_update_user_profile_background_custom_emoji_id(User *u, UserId user_id, CustomEmojiId background_custom_emoji_id) { if (u->profile_background_custom_emoji_id != background_custom_emoji_id) { u->profile_background_custom_emoji_id = background_custom_emoji_id; u->is_accent_color_changed = true; u->is_changed = true; } } void UserManager::on_update_user_emoji_status(UserId user_id, telegram_api::object_ptr &&emoji_status) { if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } User *u = get_user_force(user_id, "on_update_user_emoji_status"); if (u != nullptr) { on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(emoji_status))); update_user(u, user_id); } else { LOG(INFO) << "Ignore update user emoji status about unknown " << user_id; } } void UserManager::on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status) { if (u->emoji_status != emoji_status) { LOG(DEBUG) << "Change emoji status of " << user_id << " from " << u->emoji_status << " to " << emoji_status; u->emoji_status = std::move(emoji_status); u->is_emoji_status_changed = true; // effective emoji status might not be changed; checked in update_user // u->is_changed = true; } } void UserManager::on_update_user_story_ids(UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id) { if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } User *u = get_user_force(user_id, "on_update_user_story_ids"); if (u != nullptr) { on_update_user_story_ids_impl(u, user_id, max_active_story_id, max_read_story_id); update_user(u, user_id); } else { LOG(INFO) << "Ignore update user story identifiers about unknown " << user_id; } } void UserManager::on_update_user_story_ids_impl(User *u, UserId user_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 " << user_id; return; } if (max_read_story_id != StoryId() && !max_read_story_id.is_server()) { LOG(ERROR) << "Receive max read " << max_read_story_id << " for " << user_id; return; } auto has_unread_stories = get_user_has_unread_stories(u); if (u->max_active_story_id != max_active_story_id) { LOG(DEBUG) << "Change last active story of " << user_id << " from " << u->max_active_story_id << " to " << max_active_story_id; u->max_active_story_id = max_active_story_id; u->need_save_to_database = true; } if (need_poll_user_active_stories(u, user_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 > u->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 " << user_id; u->max_active_story_id_next_reload_time = max_active_story_id_next_reload_time; u->need_save_to_database = true; } } if (!max_active_story_id.is_valid()) { CHECK(max_read_story_id == StoryId()); if (u->max_read_story_id != StoryId()) { LOG(DEBUG) << "Drop last read " << u->max_read_story_id << " of " << user_id; u->max_read_story_id = StoryId(); u->need_save_to_database = true; } } else if (max_read_story_id.get() > u->max_read_story_id.get()) { LOG(DEBUG) << "Change last read story of " << user_id << " from " << u->max_read_story_id << " to " << max_read_story_id; u->max_read_story_id = max_read_story_id; u->need_save_to_database = true; } if (has_unread_stories != get_user_has_unread_stories(u)) { LOG(DEBUG) << "Change has_unread_stories of " << user_id << " to " << !has_unread_stories; u->is_changed = true; } } void UserManager::on_update_user_max_read_story_id(UserId user_id, StoryId max_read_story_id) { CHECK(user_id.is_valid()); User *u = get_user(user_id); if (u != nullptr) { on_update_user_max_read_story_id(u, user_id, max_read_story_id); update_user(u, user_id); } } void UserManager::on_update_user_max_read_story_id(User *u, UserId user_id, StoryId max_read_story_id) { if (td_->auth_manager_->is_bot() || !u->is_received) { return; } auto has_unread_stories = get_user_has_unread_stories(u); if (max_read_story_id.get() > u->max_read_story_id.get()) { LOG(DEBUG) << "Change last read story of " << user_id << " from " << u->max_read_story_id << " to " << max_read_story_id; u->max_read_story_id = max_read_story_id; u->need_save_to_database = true; } if (has_unread_stories != get_user_has_unread_stories(u)) { LOG(DEBUG) << "Change has_unread_stories of " << user_id << " to " << !has_unread_stories; u->is_changed = true; } } void UserManager::on_update_user_stories_hidden(UserId user_id, bool stories_hidden) { if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } User *u = get_user_force(user_id, "on_update_user_stories_hidden"); if (u != nullptr) { on_update_user_stories_hidden(u, user_id, stories_hidden); update_user(u, user_id); } else { LOG(INFO) << "Ignore update user stories are archived about unknown " << user_id; } } void UserManager::on_update_user_stories_hidden(User *u, UserId user_id, bool stories_hidden) { if (td_->auth_manager_->is_bot()) { return; } if (u->stories_hidden != stories_hidden) { LOG(DEBUG) << "Change stories are archived of " << user_id << " to " << stories_hidden; u->stories_hidden = stories_hidden; u->is_stories_hidden_changed = true; u->need_save_to_database = true; } } void UserManager::on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact, bool is_close_friend) { if (td_->auth_manager_->is_bot()) { return; } UserId my_id = get_my_id(); if (user_id == my_id) { is_mutual_contact = is_contact; is_close_friend = false; } if (!is_contact && (is_mutual_contact || is_close_friend)) { LOG(ERROR) << "Receive is_mutual_contact = " << is_mutual_contact << ", and is_close_friend = " << is_close_friend << " for non-contact " << user_id; is_mutual_contact = false; is_close_friend = false; } if (u->is_contact != is_contact || u->is_mutual_contact != is_mutual_contact || u->is_close_friend != is_close_friend) { LOG(DEBUG) << "Update " << user_id << " is_contact from (" << u->is_contact << ", " << u->is_mutual_contact << ", " << u->is_close_friend << ") to (" << is_contact << ", " << is_mutual_contact << ", " << is_close_friend << ")"; if (u->is_contact != is_contact) { u->is_contact = is_contact; u->is_is_contact_changed = true; reload_contact_birthdates(true); } if (u->is_mutual_contact != is_mutual_contact) { u->is_mutual_contact = is_mutual_contact; u->is_is_mutual_contact_changed = true; } u->is_close_friend = is_close_friend; u->is_changed = true; } } void UserManager::on_update_user_online(UserId user_id, telegram_api::object_ptr &&status) { if (td_->auth_manager_->is_bot()) { return; } if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } User *u = get_user_force(user_id, "on_update_user_online"); if (u != nullptr) { if (u->is_bot) { LOG(ERROR) << "Receive updateUserStatus about bot " << user_id; return; } on_update_user_online(u, user_id, std::move(status)); update_user(u, user_id); if (user_id == get_my_id() && was_online_remote_ != u->was_online) { // only update was_online_remote_ from updateUserStatus was_online_remote_ = u->was_online; VLOG(notifications) << "Set was_online_remote to " << was_online_remote_; G()->td_db()->get_binlog_pmc()->set("my_was_online_remote", to_string(was_online_remote_)); } } else { LOG(INFO) << "Ignore update user online about unknown " << user_id; } } void UserManager::on_update_user_online(User *u, UserId user_id, telegram_api::object_ptr &&status) { if (td_->auth_manager_->is_bot()) { return; } int32 id = status == nullptr ? telegram_api::userStatusEmpty::ID : status->get_id(); int32 new_online; bool is_offline = false; if (id == telegram_api::userStatusOnline::ID) { int32 now = G()->unix_time(); auto st = move_tl_object_as(status); new_online = st->expires_; LOG_IF(ERROR, new_online < now - 86400) << "Receive userStatusOnline expired more than one day in past " << new_online; } else if (id == telegram_api::userStatusOffline::ID) { int32 now = G()->unix_time(); auto st = move_tl_object_as(status); new_online = st->was_online_; if (new_online >= now) { LOG_IF(ERROR, new_online > now + 10) << "Receive userStatusOffline but was online points to future time " << new_online << ", now is " << now; new_online = now - 1; } is_offline = true; } else if (id == telegram_api::userStatusRecently::ID) { auto st = telegram_api::move_object_as(status); new_online = st->by_me_ ? -4 : -1; } else if (id == telegram_api::userStatusLastWeek::ID) { auto st = telegram_api::move_object_as(status); new_online = st->by_me_ ? -5 : -2; } else if (id == telegram_api::userStatusLastMonth::ID) { auto st = telegram_api::move_object_as(status); new_online = st->by_me_ ? -6 : -3; } else { CHECK(id == telegram_api::userStatusEmpty::ID); new_online = 0; } if (new_online != u->was_online && !(new_online < 0 && user_id == get_my_id())) { LOG(DEBUG) << "Update " << user_id << " online from " << u->was_online << " to " << new_online; auto unix_time = G()->unix_time(); bool old_is_online = u->was_online > unix_time; bool new_is_online = new_online > unix_time; u->was_online = new_online; u->is_status_changed = true; if (u->was_online > 0) { u->local_was_online = 0; } if (user_id == get_my_id()) { if (my_was_online_local_ != 0 || old_is_online != new_is_online) { my_was_online_local_ = 0; u->is_online_status_changed = true; } if (is_offline) { td_->on_online_updated(false, false); } } else if (old_is_online != new_is_online) { u->is_online_status_changed = true; } } } void UserManager::on_update_user_local_was_online(UserId user_id, int32 local_was_online) { CHECK(user_id.is_valid()); if (td_->auth_manager_->is_bot()) { return; } User *u = get_user_force(user_id, "on_update_user_local_was_online"); if (u == nullptr) { return; } on_update_user_local_was_online(u, user_id, local_was_online); update_user(u, user_id); } void UserManager::on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online) { CHECK(u != nullptr); if (u->is_deleted || u->is_bot || u->is_support || user_id == get_my_id()) { return; } int32 unix_time = G()->unix_time(); if (u->was_online > unix_time) { // if user is currently online, ignore local online return; } // bring users online for 30 seconds local_was_online += 30; if (local_was_online < unix_time + 2 || local_was_online <= u->local_was_online || local_was_online <= u->was_online) { return; } LOG(DEBUG) << "Update " << user_id << " local online from " << u->local_was_online << " to " << local_was_online; bool old_is_online = u->local_was_online > unix_time; u->local_was_online = local_was_online; u->is_status_changed = true; if (!old_is_online) { u->is_online_status_changed = true; } } void UserManager::on_update_user_is_blocked(UserId user_id, bool is_blocked, bool is_blocked_for_stories) { if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_is_blocked"); if (user_full == nullptr) { return; } on_update_user_full_is_blocked(user_full, user_id, is_blocked, is_blocked_for_stories); update_user_full(user_full, user_id, "on_update_user_is_blocked"); } void UserManager::on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked, bool is_blocked_for_stories) { CHECK(user_full != nullptr); if (user_full->is_blocked != is_blocked || user_full->is_blocked_for_stories != is_blocked_for_stories) { LOG(INFO) << "Receive update user full is blocked with " << user_id << " and is_blocked = " << is_blocked << '/' << is_blocked_for_stories; user_full->is_blocked = is_blocked; user_full->is_blocked_for_stories = is_blocked_for_stories; user_full->is_changed = true; } } void UserManager::on_update_user_has_pinned_stories(UserId user_id, bool has_pinned_stories) { if (td_->auth_manager_->is_bot()) { return; } if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_has_pinned_stories"); if (user_full == nullptr || user_full->has_pinned_stories == has_pinned_stories) { return; } user_full->has_pinned_stories = has_pinned_stories; user_full->is_changed = true; update_user_full(user_full, user_id, "on_update_user_has_pinned_stories"); } void UserManager::on_update_user_common_chat_count(UserId user_id, int32 common_chat_count) { LOG(INFO) << "Receive " << common_chat_count << " common chat count with " << user_id; if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_common_chat_count"); if (user_full == nullptr) { return; } on_update_user_full_common_chat_count(user_full, user_id, common_chat_count); update_user_full(user_full, user_id, "on_update_user_common_chat_count"); } void UserManager::on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, int32 common_chat_count) { CHECK(user_full != nullptr); if (common_chat_count < 0) { LOG(ERROR) << "Receive " << common_chat_count << " as common group count with " << user_id; common_chat_count = 0; } if (user_full->common_chat_count != common_chat_count) { user_full->common_chat_count = common_chat_count; user_full->is_common_chat_count_changed = true; user_full->is_changed = true; } } void UserManager::on_update_user_location(UserId user_id, DialogLocation &&location) { LOG(INFO) << "Receive " << location << " for " << user_id; if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_location"); if (user_full == nullptr) { return; } on_update_user_full_location(user_full, user_id, std::move(location)); update_user_full(user_full, user_id, "on_update_user_location"); } void UserManager::on_update_user_full_location(UserFull *user_full, UserId user_id, DialogLocation &&location) { CHECK(user_full != nullptr); if (BusinessInfo::set_location(user_full->business_info, std::move(location))) { user_full->is_changed = true; } } void UserManager::on_update_user_work_hours(UserId user_id, BusinessWorkHours &&work_hours) { LOG(INFO) << "Receive " << work_hours << " for " << user_id; if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_work_hours"); if (user_full == nullptr) { return; } on_update_user_full_work_hours(user_full, user_id, std::move(work_hours)); update_user_full(user_full, user_id, "on_update_user_work_hours"); } void UserManager::on_update_user_full_work_hours(UserFull *user_full, UserId user_id, BusinessWorkHours &&work_hours) { CHECK(user_full != nullptr); if (BusinessInfo::set_work_hours(user_full->business_info, std::move(work_hours))) { user_full->is_changed = true; } } void UserManager::on_update_user_away_message(UserId user_id, BusinessAwayMessage &&away_message) { LOG(INFO) << "Receive " << away_message << " for " << user_id; if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_away_message"); if (user_full == nullptr) { return; } on_update_user_full_away_message(user_full, user_id, std::move(away_message)); update_user_full(user_full, user_id, "on_update_user_away_message"); } void UserManager::on_update_user_full_away_message(UserFull *user_full, UserId user_id, BusinessAwayMessage &&away_message) const { CHECK(user_full != nullptr); if (away_message.is_valid() && user_id != get_my_id()) { LOG(ERROR) << "Receive " << away_message << " for " << user_id; return; } if (BusinessInfo::set_away_message(user_full->business_info, std::move(away_message))) { user_full->is_changed = true; } } void UserManager::on_update_user_greeting_message(UserId user_id, BusinessGreetingMessage &&greeting_message) { LOG(INFO) << "Receive " << greeting_message << " for " << user_id; if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_greeting_message"); if (user_full == nullptr) { return; } on_update_user_full_greeting_message(user_full, user_id, std::move(greeting_message)); update_user_full(user_full, user_id, "on_update_user_greeting_message"); } void UserManager::on_update_user_full_greeting_message(UserFull *user_full, UserId user_id, BusinessGreetingMessage &&greeting_message) const { CHECK(user_full != nullptr); if (greeting_message.is_valid() && user_id != get_my_id()) { LOG(ERROR) << "Receive " << greeting_message << " for " << user_id; return; } if (BusinessInfo::set_greeting_message(user_full->business_info, std::move(greeting_message))) { user_full->is_changed = true; } } void UserManager::on_update_user_intro(UserId user_id, BusinessIntro &&intro) { LOG(INFO) << "Receive " << intro << " for " << user_id; if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_intro"); if (user_full == nullptr) { return; } on_update_user_full_intro(user_full, user_id, std::move(intro)); update_user_full(user_full, user_id, "on_update_user_intro"); } void UserManager::on_update_user_full_intro(UserFull *user_full, UserId user_id, BusinessIntro &&intro) { CHECK(user_full != nullptr); if (BusinessInfo::set_intro(user_full->business_info, std::move(intro))) { user_full->is_changed = true; } } void UserManager::on_update_user_commands(UserId user_id, vector> &&bot_commands) { UserFull *user_full = get_user_full_force(user_id, "on_update_user_commands"); if (user_full != nullptr) { on_update_user_full_commands(user_full, user_id, std::move(bot_commands)); update_user_full(user_full, user_id, "on_update_user_commands"); } } void UserManager::on_update_user_full_commands( UserFull *user_full, UserId user_id, vector> &&bot_commands) { CHECK(user_full != nullptr); auto commands = transform(std::move(bot_commands), [](telegram_api::object_ptr &&bot_command) { return BotCommand(std::move(bot_command)); }); if (user_full->commands != commands) { user_full->commands = std::move(commands); user_full->is_changed = true; } } void UserManager::on_update_user_need_phone_number_privacy_exception(UserId user_id, bool need_phone_number_privacy_exception) { LOG(INFO) << "Receive " << need_phone_number_privacy_exception << " need phone number privacy exception with " << user_id; if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_need_phone_number_privacy_exception"); if (user_full == nullptr) { return; } on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, need_phone_number_privacy_exception); update_user_full(user_full, user_id, "on_update_user_need_phone_number_privacy_exception"); } void UserManager::on_update_user_full_need_phone_number_privacy_exception( UserFull *user_full, UserId user_id, bool need_phone_number_privacy_exception) const { CHECK(user_full != nullptr); if (need_phone_number_privacy_exception) { const User *u = get_user(user_id); if (u == nullptr || u->is_contact || user_id == get_my_id()) { need_phone_number_privacy_exception = false; } } if (user_full->need_phone_number_privacy_exception != need_phone_number_privacy_exception) { user_full->need_phone_number_privacy_exception = need_phone_number_privacy_exception; user_full->is_changed = true; } } void UserManager::on_update_user_wallpaper_overridden(UserId user_id, bool wallpaper_overridden) { LOG(INFO) << "Receive " << wallpaper_overridden << " set chat background for " << user_id; if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id, "on_update_user_wallpaper_overridden"); if (user_full == nullptr) { return; } on_update_user_full_wallpaper_overridden(user_full, user_id, wallpaper_overridden); update_user_full(user_full, user_id, "on_update_user_wallpaper_overridden"); } void UserManager::on_update_user_full_wallpaper_overridden(UserFull *user_full, UserId user_id, bool wallpaper_overridden) const { CHECK(user_full != nullptr); if (user_full->wallpaper_overridden != wallpaper_overridden) { user_full->wallpaper_overridden = wallpaper_overridden; user_full->is_changed = true; } } void UserManager::on_update_bot_menu_button(UserId bot_user_id, telegram_api::object_ptr &&bot_menu_button) { if (!bot_user_id.is_valid()) { LOG(ERROR) << "Receive updateBotMenuButton about invalid " << bot_user_id; return; } if (!have_user_force(bot_user_id, "on_update_bot_menu_button") || !is_user_bot(bot_user_id)) { return; } if (td_->auth_manager_->is_bot()) { return; } auto user_full = get_user_full_force(bot_user_id, "on_update_bot_menu_button"); if (user_full != nullptr) { on_update_user_full_menu_button(user_full, bot_user_id, std::move(bot_menu_button)); update_user_full(user_full, bot_user_id, "on_update_bot_menu_button"); } } void UserManager::on_update_user_full_menu_button( UserFull *user_full, UserId user_id, telegram_api::object_ptr &&bot_menu_button) { CHECK(user_full != nullptr); auto new_button = get_bot_menu_button(std::move(bot_menu_button)); bool is_changed; if (user_full->menu_button == nullptr) { is_changed = (new_button != nullptr); } else { is_changed = (new_button == nullptr || *user_full->menu_button != *new_button); } if (is_changed) { user_full->menu_button = std::move(new_button); user_full->is_changed = true; } } void UserManager::on_update_secret_chat(SecretChatId secret_chat_id, int64 access_hash, UserId user_id, SecretChatState state, bool is_outbound, int32 ttl, int32 date, string key_hash, int32 layer, FolderId initial_folder_id) { LOG(INFO) << "Update " << secret_chat_id << " with " << user_id << " and access_hash " << access_hash; auto *secret_chat = add_secret_chat(secret_chat_id); if (access_hash != secret_chat->access_hash) { secret_chat->access_hash = access_hash; secret_chat->need_save_to_database = true; } if (user_id.is_valid() && user_id != secret_chat->user_id) { if (secret_chat->user_id.is_valid()) { LOG(ERROR) << "Secret chat user has changed from " << secret_chat->user_id << " to " << user_id; auto &old_secret_chat_ids = secret_chats_with_user_[secret_chat->user_id]; td::remove(old_secret_chat_ids, secret_chat_id); } secret_chat->user_id = user_id; secret_chats_with_user_[secret_chat->user_id].push_back(secret_chat_id); secret_chat->is_changed = true; } if (state != SecretChatState::Unknown && state != secret_chat->state) { secret_chat->state = state; secret_chat->is_changed = true; secret_chat->is_state_changed = true; } if (is_outbound != secret_chat->is_outbound) { secret_chat->is_outbound = is_outbound; secret_chat->is_changed = true; } if (ttl != -1 && ttl != secret_chat->ttl) { secret_chat->ttl = ttl; secret_chat->need_save_to_database = true; secret_chat->is_ttl_changed = true; } if (date != 0 && date != secret_chat->date) { secret_chat->date = date; secret_chat->need_save_to_database = true; } if (!key_hash.empty() && key_hash != secret_chat->key_hash) { secret_chat->key_hash = std::move(key_hash); secret_chat->is_changed = true; } if (layer != 0 && layer != secret_chat->layer) { secret_chat->layer = layer; secret_chat->is_changed = true; } if (initial_folder_id != FolderId() && initial_folder_id != secret_chat->initial_folder_id) { secret_chat->initial_folder_id = initial_folder_id; secret_chat->is_changed = true; } update_secret_chat(secret_chat, secret_chat_id); } void UserManager::on_update_online_status_privacy() { td_->create_handler()->send(); } void UserManager::on_update_phone_number_privacy() { // all UserFull.need_phone_number_privacy_exception can be outdated now, // so mark all of them as expired users_full_.foreach([&](const UserId &user_id, unique_ptr &user_full) { user_full->expires_at = 0.0; }); } void UserManager::on_ignored_restriction_reasons_changed() { restricted_user_ids_.foreach([&](const UserId &user_id) { send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, get_user(user_id))); }); } void UserManager::invalidate_user_full(UserId user_id) { auto user_full = get_user_full_force(user_id, "invalidate_user_full"); if (user_full != nullptr) { td_->dialog_manager_->on_dialog_info_full_invalidated(DialogId(user_id)); if (!user_full->is_expired()) { user_full->expires_at = 0.0; user_full->need_save_to_database = true; update_user_full(user_full, user_id, "invalidate_user_full"); } } } bool UserManager::have_user(UserId user_id) const { auto u = get_user(user_id); return u != nullptr && u->is_received; } bool UserManager::have_min_user(UserId user_id) const { return users_.count(user_id) > 0; } const UserManager::User *UserManager::get_user(UserId user_id) const { return users_.get_pointer(user_id); } UserManager::User *UserManager::get_user(UserId user_id) { return users_.get_pointer(user_id); } UserManager::User *UserManager::add_user(UserId user_id) { CHECK(user_id.is_valid()); auto &user_ptr = users_[user_id]; if (user_ptr == nullptr) { user_ptr = make_unique(); } return user_ptr.get(); } void UserManager::save_user(User *u, UserId user_id, bool from_binlog) { if (!G()->use_chat_info_database()) { return; } CHECK(u != nullptr); if (!u->is_saved || !u->is_status_saved) { // TODO more effective handling of !u->is_status_saved if (!from_binlog) { auto log_event = UserLogEvent(user_id, u); auto storer = get_log_event_storer(log_event); if (u->log_event_id == 0) { u->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Users, storer); } else { binlog_rewrite(G()->td_db()->get_binlog(), u->log_event_id, LogEvent::HandlerType::Users, storer); } } save_user_to_database(u, user_id); } } string UserManager::get_user_database_key(UserId user_id) { return PSTRING() << "us" << user_id.get(); } string UserManager::get_user_database_value(const User *u) { return log_event_store(*u).as_slice().str(); } void UserManager::save_user_to_database(User *u, UserId user_id) { CHECK(u != nullptr); if (u->is_being_saved) { return; } if (loaded_from_database_users_.count(user_id)) { save_user_to_database_impl(u, user_id, get_user_database_value(u)); return; } if (load_user_from_database_queries_.count(user_id) != 0) { return; } load_user_from_database_impl(user_id, Auto()); } void UserManager::save_user_to_database_impl(User *u, UserId user_id, string value) { CHECK(u != nullptr); CHECK(load_user_from_database_queries_.count(user_id) == 0); CHECK(!u->is_being_saved); u->is_being_saved = true; u->is_saved = true; u->is_status_saved = true; LOG(INFO) << "Trying to save to database " << user_id; G()->td_db()->get_sqlite_pmc()->set( get_user_database_key(user_id), std::move(value), PromiseCreator::lambda([user_id](Result<> result) { send_closure(G()->user_manager(), &UserManager::on_save_user_to_database, user_id, result.is_ok()); })); } void UserManager::on_save_user_to_database(UserId user_id, bool success) { if (G()->close_flag()) { return; } User *u = get_user(user_id); CHECK(u != nullptr); LOG_CHECK(u->is_being_saved) << success << ' ' << user_id << ' ' << u->is_saved << ' ' << u->is_status_saved << ' ' << load_user_from_database_queries_.count(user_id) << ' ' << u->is_received << ' ' << u->is_deleted << ' ' << u->is_bot << ' ' << u->need_save_to_database << ' ' << u->is_changed << ' ' << u->is_status_changed << ' ' << u->is_name_changed << ' ' << u->is_username_changed << ' ' << u->is_photo_changed << ' ' << u->is_is_contact_changed << ' ' << u->is_is_deleted_changed << ' ' << u->is_stories_hidden_changed << ' ' << u->log_event_id; CHECK(load_user_from_database_queries_.count(user_id) == 0); u->is_being_saved = false; if (!success) { LOG(ERROR) << "Failed to save " << user_id << " to database"; u->is_saved = false; u->is_status_saved = false; } else { LOG(INFO) << "Successfully saved " << user_id << " to database"; } if (u->is_saved && u->is_status_saved) { if (u->log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), u->log_event_id); u->log_event_id = 0; } } else { save_user(u, user_id, u->log_event_id != 0); } } void UserManager::load_user_from_database(User *u, UserId user_id, Promise promise) { if (loaded_from_database_users_.count(user_id)) { promise.set_value(Unit()); return; } CHECK(u == nullptr || !u->is_being_saved); load_user_from_database_impl(user_id, std::move(promise)); } void UserManager::load_user_from_database_impl(UserId user_id, Promise promise) { LOG(INFO) << "Load " << user_id << " from database"; auto &load_user_queries = load_user_from_database_queries_[user_id]; load_user_queries.push_back(std::move(promise)); if (load_user_queries.size() == 1u) { G()->td_db()->get_sqlite_pmc()->get(get_user_database_key(user_id), PromiseCreator::lambda([user_id](string value) { send_closure(G()->user_manager(), &UserManager::on_load_user_from_database, user_id, std::move(value), false); })); } } void UserManager::on_load_user_from_database(UserId user_id, string value, bool force) { if (G()->close_flag() && !force) { // the user is in Binlog and will be saved after restart return; } CHECK(user_id.is_valid()); if (!loaded_from_database_users_.insert(user_id).second) { return; } auto it = load_user_from_database_queries_.find(user_id); vector> promises; if (it != load_user_from_database_queries_.end()) { promises = std::move(it->second); CHECK(!promises.empty()); load_user_from_database_queries_.erase(it); } LOG(INFO) << "Successfully loaded " << user_id << " of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_user_database_key(user_id), Auto()); // return; User *u = get_user(user_id); if (u == nullptr) { if (!value.empty()) { u = add_user(user_id); if (log_event_parse(*u, value).is_error()) { LOG(ERROR) << "Failed to load " << user_id << " from database"; users_.erase(user_id); } else { u->is_saved = true; u->is_status_saved = true; update_user(u, user_id, true, true); } } } else { CHECK(!u->is_saved); // user can't be saved before load completes CHECK(!u->is_being_saved); auto new_value = get_user_database_value(u); if (value != new_value) { save_user_to_database_impl(u, user_id, std::move(new_value)); } else if (u->log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), u->log_event_id); u->log_event_id = 0; } } set_promises(promises); } bool UserManager::have_user_force(UserId user_id, const char *source) { return get_user_force(user_id, source) != nullptr; } UserManager::User *UserManager::get_user_force(UserId user_id, const char *source) { auto u = get_user_force_impl(user_id, source); if ((u == nullptr || !u->is_received) && (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() || user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id() || user_id == get_anti_spam_bot_user_id())) { int32 flags = USER_FLAG_HAS_ACCESS_HASH | USER_FLAG_HAS_FIRST_NAME | USER_FLAG_NEED_APPLY_MIN_PHOTO; int64 profile_photo_id = 0; int32 profile_photo_dc_id = 1; string first_name; string last_name; string username; string phone_number; int32 bot_info_version = 0; if (user_id == get_service_notifications_user_id()) { flags |= USER_FLAG_HAS_PHONE_NUMBER | USER_FLAG_IS_VERIFIED | USER_FLAG_IS_SUPPORT; first_name = "Telegram"; if (G()->is_test_dc()) { flags |= USER_FLAG_HAS_LAST_NAME; last_name = "Notifications"; } phone_number = "42777"; profile_photo_id = 3337190045231023; } else if (user_id == get_replies_bot_user_id()) { flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; if (!G()->is_test_dc()) { flags |= USER_FLAG_IS_PRIVATE_BOT; } first_name = "Replies"; username = "replies"; bot_info_version = G()->is_test_dc() ? 1 : 3; } else if (user_id == get_anonymous_bot_user_id()) { flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; if (!G()->is_test_dc()) { flags |= USER_FLAG_IS_PRIVATE_BOT; } first_name = "Group"; username = G()->is_test_dc() ? "izgroupbot" : "GroupAnonymousBot"; bot_info_version = G()->is_test_dc() ? 1 : 3; profile_photo_id = 5159307831025969322; } else if (user_id == get_channel_bot_user_id()) { flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; if (!G()->is_test_dc()) { flags |= USER_FLAG_IS_PRIVATE_BOT; } first_name = G()->is_test_dc() ? "Channels" : "Channel"; username = G()->is_test_dc() ? "channelsbot" : "Channel_Bot"; bot_info_version = G()->is_test_dc() ? 1 : 4; profile_photo_id = 587627495930570665; } else if (user_id == get_service_notifications_user_id()) { flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; if (G()->is_test_dc()) { first_name = "antispambot"; username = "tantispambot"; } else { flags |= USER_FLAG_IS_VERIFIED; first_name = "Telegram Anti-Spam"; username = "tgsantispambot"; profile_photo_id = 5170408289966598902; } } telegram_api::object_ptr profile_photo; if (!G()->is_test_dc() && profile_photo_id != 0) { profile_photo = telegram_api::make_object(0, false, false, profile_photo_id, BufferSlice(), profile_photo_dc_id); } auto user = telegram_api::make_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, user_id.get(), 1, first_name, string(), username, phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), string(), string(), nullptr, vector>(), 0, nullptr, nullptr); on_get_user(std::move(user), "get_user_force"); u = get_user(user_id); CHECK(u != nullptr && u->is_received); reload_user(user_id, Promise(), "get_user_force"); } return u; } UserManager::User *UserManager::get_user_force_impl(UserId user_id, const char *source) { if (!user_id.is_valid()) { return nullptr; } User *u = get_user(user_id); if (u != nullptr) { return u; } if (!G()->use_chat_info_database()) { return nullptr; } if (loaded_from_database_users_.count(user_id)) { return nullptr; } LOG(INFO) << "Trying to load " << user_id << " from database from " << source; on_load_user_from_database(user_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_user_database_key(user_id)), true); return get_user(user_id); } void UserManager::send_get_me_query(Td *td, Promise &&promise) { vector> users; users.push_back(telegram_api::make_object()); td->create_handler(std::move(promise))->send(std::move(users)); } UserId UserManager::get_me(Promise &&promise) { auto my_id = get_my_id(); if (!have_user_force(my_id, "get_me")) { get_user_queries_.add_query(my_id.get(), std::move(promise), "get_me"); return UserId(); } promise.set_value(Unit()); return my_id; } bool UserManager::get_user(UserId user_id, int left_tries, Promise &&promise) { if (!user_id.is_valid()) { promise.set_error(Status::Error(400, "Invalid user identifier")); return false; } if (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() || user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id() || user_id == get_anti_spam_bot_user_id()) { get_user_force(user_id, "get_user"); } if (td_->auth_manager_->is_bot() ? !have_user(user_id) : !have_min_user(user_id)) { if (left_tries > 2 && G()->use_chat_info_database()) { send_closure_later(actor_id(this), &UserManager::load_user_from_database, nullptr, user_id, std::move(promise)); return false; } auto r_input_user = get_input_user(user_id); if (left_tries == 1 || r_input_user.is_error()) { if (r_input_user.is_error()) { promise.set_error(r_input_user.move_as_error()); } else { promise.set_error(Status::Error(400, "User not found")); } return false; } get_user_queries_.add_query(user_id.get(), std::move(promise), "get_user"); return false; } promise.set_value(Unit()); return true; } void UserManager::reload_user(UserId user_id, Promise &&promise, const char *source) { if (!user_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid user identifier")); } have_user_force(user_id, source); TRY_STATUS_PROMISE(promise, get_input_user(user_id)); get_user_queries_.add_query(user_id.get(), std::move(promise), source); } Result> UserManager::get_input_user(UserId user_id) const { if (user_id == get_my_id()) { return telegram_api::make_object(); } const User *u = get_user(user_id); if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { if (td_->auth_manager_->is_bot() && user_id.is_valid()) { return telegram_api::make_object(user_id.get(), 0); } auto it = user_messages_.find(user_id); if (it != user_messages_.end()) { CHECK(!it->second.empty()); auto message_full_id = *it->second.begin(); return telegram_api::make_object( td_->contacts_manager_->get_simple_input_peer(message_full_id.get_dialog_id()), message_full_id.get_message_id().get_server_message_id().get(), user_id.get()); } if (u == nullptr) { return Status::Error(400, "User not found"); } else { return Status::Error(400, "Have no access to the user"); } } return telegram_api::make_object(user_id.get(), u->access_hash); } telegram_api::object_ptr UserManager::get_input_user_force(UserId user_id) const { auto r_input_user = get_input_user(user_id); if (r_input_user.is_error()) { CHECK(user_id.is_valid()); return telegram_api::make_object(user_id.get(), 0); } return r_input_user.move_as_ok(); } bool UserManager::have_input_peer_user(UserId user_id, AccessRights access_rights) const { if (user_id == get_my_id()) { return true; } return have_input_peer_user(get_user(user_id), user_id, access_rights); } bool UserManager::have_input_peer_user(const User *u, UserId user_id, AccessRights access_rights) const { if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { if (u == nullptr) { LOG(DEBUG) << "Have no user"; } else { LOG(DEBUG) << "Have user without access hash"; } if (td_->auth_manager_->is_bot() && user_id.is_valid()) { return true; } if (user_messages_.count(user_id) != 0) { return true; } return false; } if (access_rights == AccessRights::Know) { return true; } if (access_rights == AccessRights::Read) { return true; } if (u->is_deleted) { LOG(DEBUG) << "Have a deleted user"; return false; } return true; } telegram_api::object_ptr UserManager::get_input_peer_user(UserId user_id, AccessRights access_rights) const { if (user_id == get_my_id()) { return telegram_api::make_object(); } const User *u = get_user(user_id); if (!have_input_peer_user(u, user_id, access_rights)) { return nullptr; } if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { if (td_->auth_manager_->is_bot() && user_id.is_valid()) { return telegram_api::make_object(user_id.get(), 0); } auto it = user_messages_.find(user_id); CHECK(it != user_messages_.end()); CHECK(!it->second.empty()); auto message_full_id = *it->second.begin(); return telegram_api::make_object( td_->contacts_manager_->get_simple_input_peer(message_full_id.get_dialog_id()), message_full_id.get_message_id().get_server_message_id().get(), user_id.get()); } return telegram_api::make_object(user_id.get(), u->access_hash); } bool UserManager::have_input_encrypted_peer(SecretChatId secret_chat_id, AccessRights access_rights) const { return have_input_encrypted_peer(get_secret_chat(secret_chat_id), access_rights); } bool UserManager::have_input_encrypted_peer(const SecretChat *secret_chat, AccessRights access_rights) { if (secret_chat == nullptr) { LOG(DEBUG) << "Have no secret chat"; return false; } if (access_rights == AccessRights::Know) { return true; } if (access_rights == AccessRights::Read) { return true; } return secret_chat->state == SecretChatState::Active; } telegram_api::object_ptr UserManager::get_input_encrypted_chat( SecretChatId secret_chat_id, AccessRights access_rights) const { auto sc = get_secret_chat(secret_chat_id); if (!have_input_encrypted_peer(sc, access_rights)) { return nullptr; } return telegram_api::make_object(secret_chat_id.get(), sc->access_hash); } bool UserManager::is_user_contact(UserId user_id, bool is_mutual) const { return is_user_contact(get_user(user_id), user_id, is_mutual); } bool UserManager::is_user_contact(const User *u, UserId user_id, bool is_mutual) const { return u != nullptr && (is_mutual ? u->is_mutual_contact : u->is_contact) && user_id != get_my_id(); } bool UserManager::is_user_premium(UserId user_id) const { return is_user_premium(get_user(user_id)); } bool UserManager::is_user_premium(const User *u) { return u != nullptr && u->is_premium; } bool UserManager::is_user_deleted(UserId user_id) const { return is_user_deleted(get_user(user_id)); } bool UserManager::is_user_deleted(const User *u) { return u == nullptr || u->is_deleted; } bool UserManager::is_user_support(UserId user_id) const { return is_user_support(get_user(user_id)); } bool UserManager::is_user_support(const User *u) { return u != nullptr && !u->is_deleted && u->is_support; } bool UserManager::is_user_bot(UserId user_id) const { return is_user_bot(get_user(user_id)); } bool UserManager::is_user_bot(const User *u) { return u != nullptr && !u->is_deleted && u->is_bot; } Result UserManager::get_bot_data(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { return Status::Error(400, "Bot not found"); } if (!u->is_bot) { return Status::Error(400, "User is not a bot"); } if (u->is_deleted) { return Status::Error(400, "Bot is deleted"); } if (!u->is_received) { return Status::Error(400, "Bot is inaccessible"); } BotData bot_data; bot_data.username = u->usernames.get_first_username(); bot_data.can_be_edited = u->can_be_edited_bot; bot_data.can_join_groups = u->can_join_groups; bot_data.can_read_all_group_messages = u->can_read_all_group_messages; bot_data.is_inline = u->is_inline_bot; bot_data.is_business = u->is_business_bot; bot_data.need_location = u->need_location_bot; bot_data.can_be_added_to_attach_menu = u->can_be_added_to_attach_menu; return bot_data; } bool UserManager::is_user_online(UserId user_id, int32 tolerance, int32 unix_time) const { if (unix_time <= 0) { unix_time = G()->unix_time(); } int32 was_online = get_user_was_online(get_user(user_id), user_id, unix_time); return was_online > unix_time - tolerance; } int32 UserManager::get_user_was_online(UserId user_id, int32 unix_time) const { if (unix_time <= 0) { unix_time = G()->unix_time(); } return get_user_was_online(get_user(user_id), user_id, unix_time); } int32 UserManager::get_user_was_online(const User *u, UserId user_id, int32 unix_time) const { if (u == nullptr || u->is_deleted) { return 0; } int32 was_online = u->was_online; if (user_id == get_my_id()) { if (my_was_online_local_ != 0) { was_online = my_was_online_local_; } } else { if (u->local_was_online > 0 && u->local_was_online > was_online && u->local_was_online > unix_time) { was_online = u->local_was_online; } } return was_online; } bool UserManager::is_user_status_exact(UserId user_id) const { auto u = get_user(user_id); return u != nullptr && !u->is_deleted && !u->is_bot && u->was_online > 0; } bool UserManager::is_user_received_from_server(UserId user_id) const { const auto *u = get_user(user_id); return u != nullptr && u->is_received_from_server; } bool UserManager::can_report_user(UserId user_id) const { auto u = get_user(user_id); return u != nullptr && !u->is_deleted && !u->is_support && (u->is_bot || td_->people_nearby_manager_->is_user_nearby(user_id)); } const DialogPhoto *UserManager::get_user_dialog_photo(UserId user_id) { auto u = get_user(user_id); if (u == nullptr) { return nullptr; } apply_pending_user_photo(u, user_id); return &u->photo; } const DialogPhoto *UserManager::get_secret_chat_dialog_photo(SecretChatId secret_chat_id) { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return nullptr; } return get_user_dialog_photo(c->user_id); } int32 UserManager::get_user_accent_color_id_object(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr || !u->accent_color_id.is_valid()) { return td_->theme_manager_->get_accent_color_id_object(AccentColorId(user_id)); } return td_->theme_manager_->get_accent_color_id_object(u->accent_color_id, AccentColorId(user_id)); } int32 UserManager::get_secret_chat_accent_color_id_object(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return 5; } return get_user_accent_color_id_object(c->user_id); } CustomEmojiId UserManager::get_user_background_custom_emoji_id(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { return CustomEmojiId(); } return u->background_custom_emoji_id; } CustomEmojiId UserManager::get_secret_chat_background_custom_emoji_id(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return CustomEmojiId(); } return get_user_background_custom_emoji_id(c->user_id); } int32 UserManager::get_user_profile_accent_color_id_object(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { return -1; } return td_->theme_manager_->get_profile_accent_color_id_object(u->profile_accent_color_id); } int32 UserManager::get_secret_chat_profile_accent_color_id_object(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return -1; } return get_user_profile_accent_color_id_object(c->user_id); } CustomEmojiId UserManager::get_user_profile_background_custom_emoji_id(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { return CustomEmojiId(); } return u->profile_background_custom_emoji_id; } CustomEmojiId UserManager::get_secret_chat_profile_background_custom_emoji_id(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return CustomEmojiId(); } return get_user_profile_background_custom_emoji_id(c->user_id); } string UserManager::get_user_title(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { return string(); } if (u->last_name.empty()) { return u->first_name; } if (u->first_name.empty()) { return u->last_name; } return PSTRING() << u->first_name << ' ' << u->last_name; } string UserManager::get_secret_chat_title(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return string(); } return get_user_title(c->user_id); } RestrictedRights UserManager::get_user_default_permissions(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr || user_id == get_replies_bot_user_id()) { return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, u != nullptr, false, ChannelType::Unknown); } return RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, true, false, ChannelType::Unknown); } RestrictedRights UserManager::get_secret_chat_default_permissions(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_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 RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, ChannelType::Unknown); } td_api::object_ptr UserManager::get_user_emoji_status_object(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { return nullptr; } return u->last_sent_emoji_status.get_emoji_status_object(); } td_api::object_ptr UserManager::get_secret_chat_emoji_status_object( SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return nullptr; } return get_user_emoji_status_object(c->user_id); } bool UserManager::get_user_stories_hidden(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { return false; } return u->stories_hidden; } bool UserManager::can_poll_user_active_stories(UserId user_id) const { const User *u = get_user(user_id); return need_poll_user_active_stories(u, user_id) && Time::now() >= u->max_active_story_id_next_reload_time; } bool UserManager::need_poll_user_active_stories(const User *u, UserId user_id) const { return u != nullptr && user_id != get_my_id() && !is_user_contact(u, user_id, false) && !is_user_bot(u) && !is_user_support(u) && !is_user_deleted(u) && u->was_online != 0; } string UserManager::get_user_about(UserId user_id) { auto user_full = get_user_full_force(user_id, "get_user_about"); if (user_full != nullptr) { return user_full->about; } return string(); } string UserManager::get_secret_chat_about(SecretChatId secret_chat_id) { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return string(); } return get_user_about(c->user_id); } string UserManager::get_user_private_forward_name(UserId user_id) { auto user_full = get_user_full_force(user_id, "get_user_private_forward_name"); if (user_full != nullptr) { return user_full->private_forward_name; } return string(); } bool UserManager::get_user_voice_messages_forbidden(UserId user_id) const { if (!is_user_premium(user_id)) { return false; } auto user_full = get_user_full(user_id); if (user_full != nullptr) { return user_full->voice_messages_forbidden; } return false; } bool UserManager::get_user_read_dates_private(UserId user_id) { auto user_full = get_user_full_force(user_id, "get_user_read_dates_private"); if (user_full != nullptr) { return user_full->read_dates_private; } return false; } string UserManager::get_user_search_text(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { return string(); } return get_user_search_text(u); } string UserManager::get_user_search_text(const User *u) { CHECK(u != nullptr); return PSTRING() << u->first_name << ' ' << u->last_name << ' ' << implode(u->usernames.get_active_usernames()); } void UserManager::for_each_secret_chat_with_user(UserId user_id, const std::function &f) { auto it = secret_chats_with_user_.find(user_id); if (it != secret_chats_with_user_.end()) { for (auto secret_chat_id : it->second) { f(secret_chat_id); } } } string UserManager::get_user_first_username(UserId user_id) const { if (!user_id.is_valid()) { return string(); } auto u = get_user(user_id); if (u == nullptr) { return string(); } return u->usernames.get_first_username(); } int32 UserManager::get_secret_chat_date(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return 0; } return c->date; } int32 UserManager::get_secret_chat_ttl(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return 0; } return c->ttl; } UserId UserManager::get_secret_chat_user_id(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return UserId(); } return c->user_id; } bool UserManager::get_secret_chat_is_outbound(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return false; } return c->is_outbound; } SecretChatState UserManager::get_secret_chat_state(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return SecretChatState::Unknown; } return c->state; } int32 UserManager::get_secret_chat_layer(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return 0; } return c->layer; } FolderId UserManager::get_secret_chat_initial_folder_id(SecretChatId secret_chat_id) const { auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return FolderId::main(); } return c->initial_folder_id; } vector UserManager::get_bot_commands(vector> &&bot_infos, const vector *participants) { vector result; if (td_->auth_manager_->is_bot()) { return result; } for (auto &bot_info : bot_infos) { if (bot_info->commands_.empty()) { continue; } auto user_id = UserId(bot_info->user_id_); const User *u = get_user_force(user_id, "get_bot_commands"); if (u == nullptr) { LOG(ERROR) << "Receive unknown " << user_id; continue; } if (!is_user_bot(u)) { if (!is_user_deleted(u)) { LOG(ERROR) << "Receive non-bot " << user_id; } continue; } if (participants != nullptr) { bool is_participant = false; for (auto &participant : *participants) { if (participant.dialog_id_ == DialogId(user_id)) { is_participant = true; break; } } if (!is_participant) { LOG(ERROR) << "Skip commands of non-member bot " << user_id; continue; } } result.emplace_back(user_id, std::move(bot_info->commands_)); } return result; } void UserManager::set_name(const string &first_name, const string &last_name, Promise &&promise) { auto new_first_name = clean_name(first_name, MAX_NAME_LENGTH); auto new_last_name = clean_name(last_name, MAX_NAME_LENGTH); if (new_first_name.empty()) { return promise.set_error(Status::Error(400, "First name must be non-empty")); } const User *u = get_user(get_my_id()); int32 flags = 0; // TODO we can already send request for changing first_name and last_name and wanting to set initial values // TODO need to be rewritten using invoke after and cancelling previous request if (u == nullptr || u->first_name != new_first_name) { flags |= ACCOUNT_UPDATE_FIRST_NAME; } if (u == nullptr || u->last_name != new_last_name) { flags |= ACCOUNT_UPDATE_LAST_NAME; } if (flags == 0) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise))->send(flags, new_first_name, new_last_name, ""); } void UserManager::set_bio(const string &bio, Promise &&promise) { auto max_bio_length = static_cast(td_->option_manager_->get_option_integer("bio_length_max")); auto new_bio = strip_empty_characters(bio, max_bio_length); for (auto &c : new_bio) { if (c == '\n') { c = ' '; } } const UserFull *user_full = get_user_full(get_my_id()); int32 flags = 0; // TODO we can already send request for changing bio and wanting to set initial values // TODO need to be rewritten using invoke after and cancelling previous request if (user_full == nullptr || user_full->about != new_bio) { flags |= ACCOUNT_UPDATE_ABOUT; } if (flags == 0) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise))->send(flags, "", "", new_bio); } void UserManager::on_update_profile_success(int32 flags, const string &first_name, const string &last_name, const string &about) { CHECK(flags != 0); auto my_user_id = get_my_id(); const User *u = get_user(my_user_id); if (u == nullptr) { LOG(ERROR) << "Doesn't receive info about me during update profile"; return; } LOG_IF(ERROR, (flags & ACCOUNT_UPDATE_FIRST_NAME) != 0 && u->first_name != first_name) << "Wrong first name \"" << u->first_name << "\", expected \"" << first_name << '"'; LOG_IF(ERROR, (flags & ACCOUNT_UPDATE_LAST_NAME) != 0 && u->last_name != last_name) << "Wrong last name \"" << u->last_name << "\", expected \"" << last_name << '"'; if ((flags & ACCOUNT_UPDATE_ABOUT) != 0) { UserFull *user_full = get_user_full_force(my_user_id, "on_update_profile_success"); if (user_full != nullptr) { user_full->about = about; user_full->is_changed = true; update_user_full(user_full, my_user_id, "on_update_profile_success"); td_->group_call_manager_->on_update_dialog_about(DialogId(my_user_id), user_full->about, true); } } } FileId UserManager::get_profile_photo_file_id(int64 photo_id) const { auto it = my_photo_file_id_.find(photo_id); if (it == my_photo_file_id_.end()) { return FileId(); } return it->second; } void UserManager::set_bot_profile_photo(UserId bot_user_id, const td_api::object_ptr &input_photo, Promise &&promise) { if (td_->auth_manager_->is_bot()) { if (bot_user_id != UserId() && bot_user_id != get_my_id()) { return promise.set_error(Status::Error(400, "Invalid bot user identifier specified")); } bot_user_id = get_my_id(); } else { TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); if (!bot_data.can_be_edited) { return promise.set_error(Status::Error(400, "The bot can't be edited")); } } if (input_photo == nullptr) { td_->create_handler(std::move(promise)) ->send(bot_user_id, FileId(), 0, false, telegram_api::make_object()); return; } set_profile_photo_impl(bot_user_id, input_photo, false, false, std::move(promise)); } void UserManager::set_profile_photo(const td_api::object_ptr &input_photo, bool is_fallback, Promise &&promise) { set_profile_photo_impl(get_my_id(), input_photo, is_fallback, false, std::move(promise)); } void UserManager::set_profile_photo_impl(UserId user_id, const td_api::object_ptr &input_photo, bool is_fallback, bool only_suggest, Promise &&promise) { if (input_photo == nullptr) { return promise.set_error(Status::Error(400, "New profile photo must be non-empty")); } const td_api::object_ptr *input_file = nullptr; double main_frame_timestamp = 0.0; bool is_animation = false; switch (input_photo->get_id()) { case td_api::inputChatPhotoPrevious::ID: { if (user_id != get_my_id() || td_->auth_manager_->is_bot()) { return promise.set_error(Status::Error(400, "Can't use inputChatPhotoPrevious")); } auto photo = static_cast(input_photo.get()); auto photo_id = photo->chat_photo_id_; auto *u = get_user(user_id); if (u != nullptr && u->photo.id > 0 && photo_id == u->photo.id) { // it is possible that u->photo.is_fallback != is_fallback, so we need to set the photo anyway // return promise.set_value(Unit()); } auto file_id = get_profile_photo_file_id(photo_id); if (!file_id.is_valid()) { return promise.set_error(Status::Error(400, "Unknown profile photo ID specified")); } return send_update_profile_photo_query(user_id, td_->file_manager_->dup_file_id(file_id, "set_profile_photo_impl"), photo_id, is_fallback, std::move(promise)); } case td_api::inputChatPhotoStatic::ID: { auto photo = static_cast(input_photo.get()); input_file = &photo->photo_; break; } case td_api::inputChatPhotoAnimation::ID: { auto photo = static_cast(input_photo.get()); input_file = &photo->animation_; main_frame_timestamp = photo->main_frame_timestamp_; is_animation = true; break; } case td_api::inputChatPhotoSticker::ID: { auto photo = static_cast(input_photo.get()); TRY_RESULT_PROMISE(promise, sticker_photo_size, StickerPhotoSize::get_sticker_photo_size(td_, photo->sticker_)); td_->create_handler(std::move(promise)) ->send(user_id, std::move(sticker_photo_size), is_fallback, only_suggest); return; } default: UNREACHABLE(); break; } const double MAX_ANIMATION_DURATION = 10.0; if (main_frame_timestamp < 0.0 || main_frame_timestamp > MAX_ANIMATION_DURATION) { return promise.set_error(Status::Error(400, "Wrong main frame timestamp specified")); } auto file_type = is_animation ? FileType::Animation : FileType::Photo; TRY_RESULT_PROMISE(promise, file_id, td_->file_manager_->get_input_file_id(file_type, *input_file, DialogId(user_id), false, false)); CHECK(file_id.is_valid()); upload_profile_photo(user_id, td_->file_manager_->dup_file_id(file_id, "set_profile_photo_impl"), is_fallback, only_suggest, is_animation, main_frame_timestamp, std::move(promise)); } void UserManager::set_user_profile_photo(UserId user_id, const td_api::object_ptr &input_photo, bool only_suggest, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); if (!only_suggest && !is_user_contact(user_id)) { return promise.set_error(Status::Error(400, "User isn't a contact")); } if (user_id == get_my_id()) { return promise.set_error(Status::Error(400, "Can't set personal or suggest photo to self")); } if (is_user_bot(user_id)) { return promise.set_error(Status::Error(400, "Can't set personal or suggest photo to bots")); } if (input_photo == nullptr) { td_->create_handler(std::move(promise))->send(user_id, std::move(input_user)); return; } set_profile_photo_impl(user_id, input_photo, false, only_suggest, std::move(promise)); } void UserManager::send_update_profile_photo_query(UserId user_id, FileId file_id, int64 old_photo_id, bool is_fallback, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); FileView file_view = td_->file_manager_->get_file_view(file_id); td_->create_handler(std::move(promise)) ->send(user_id, file_id, old_photo_id, is_fallback, file_view.main_remote_location().as_input_photo()); } void UserManager::upload_profile_photo(UserId user_id, FileId file_id, bool is_fallback, bool only_suggest, bool is_animation, double main_frame_timestamp, Promise &&promise, int reupload_count, vector bad_parts) { CHECK(file_id.is_valid()); bool is_inserted = uploaded_profile_photos_ .emplace(file_id, UploadedProfilePhoto{user_id, is_fallback, only_suggest, main_frame_timestamp, is_animation, reupload_count, std::move(promise)}) .second; CHECK(is_inserted); LOG(INFO) << "Ask to upload " << (is_animation ? "animated" : "static") << " profile photo " << file_id << " for user " << user_id << " with bad parts " << bad_parts; // TODO use force_reupload if reupload_count >= 1, replace reupload_count with is_reupload td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_profile_photo_callback_, 32, 0); } void UserManager::on_upload_profile_photo(FileId file_id, telegram_api::object_ptr input_file) { auto it = uploaded_profile_photos_.find(file_id); CHECK(it != uploaded_profile_photos_.end()); UserId user_id = it->second.user_id; bool is_fallback = it->second.is_fallback; bool only_suggest = it->second.only_suggest; double main_frame_timestamp = it->second.main_frame_timestamp; bool is_animation = it->second.is_animation; int32 reupload_count = it->second.reupload_count; auto promise = std::move(it->second.promise); uploaded_profile_photos_.erase(it); LOG(INFO) << "Uploaded " << (is_animation ? "animated" : "static") << " profile photo " << file_id << " for " << user_id << " with reupload_count = " << reupload_count; FileView file_view = td_->file_manager_->get_file_view(file_id); if (file_view.has_remote_location() && input_file == nullptr) { if (file_view.main_remote_location().is_web()) { return promise.set_error(Status::Error(400, "Can't use web photo as profile photo")); } if (reupload_count == 3) { // upload, ForceReupload repair file reference, reupload return promise.set_error(Status::Error(400, "Failed to reupload the file")); } // delete file reference and forcely reupload the file if (is_animation) { CHECK(file_view.get_type() == FileType::Animation); LOG_CHECK(file_view.main_remote_location().is_common()) << file_view.main_remote_location(); } else { CHECK(file_view.get_type() == FileType::Photo); LOG_CHECK(file_view.main_remote_location().is_photo()) << file_view.main_remote_location(); } auto file_reference = is_animation ? FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()) : FileManager::extract_file_reference(file_view.main_remote_location().as_input_photo()); td_->file_manager_->delete_file_reference(file_id, file_reference); upload_profile_photo(user_id, file_id, is_fallback, only_suggest, is_animation, main_frame_timestamp, std::move(promise), reupload_count + 1, {-1}); return; } CHECK(input_file != nullptr); td_->create_handler(std::move(promise)) ->send(user_id, file_id, std::move(input_file), is_fallback, only_suggest, is_animation, main_frame_timestamp); } void UserManager::on_upload_profile_photo_error(FileId file_id, Status status) { LOG(INFO) << "File " << file_id << " has upload error " << status; CHECK(status.is_error()); auto it = uploaded_profile_photos_.find(file_id); CHECK(it != uploaded_profile_photos_.end()); auto promise = std::move(it->second.promise); uploaded_profile_photos_.erase(it); promise.set_error(std::move(status)); // TODO check that status has valid error code } void UserManager::on_set_profile_photo(UserId user_id, telegram_api::object_ptr &&photo, bool is_fallback, int64 old_photo_id, Promise &&promise) { LOG(INFO) << "Changed profile photo to " << to_string(photo); bool is_bot = is_user_bot(user_id); bool is_my = (user_id == get_my_id()); if (is_my && !is_fallback) { delete_my_profile_photo_from_cache(old_photo_id); } bool have_user = false; for (const auto &user : photo->users_) { if (get_user_id(user) == user_id) { have_user = true; } } on_get_users(std::move(photo->users_), "on_set_profile_photo"); if (!is_bot) { add_set_profile_photo_to_cache(user_id, get_photo(td_, std::move(photo->photo_), DialogId(user_id)), is_fallback); } if (have_user) { promise.set_value(Unit()); } else { reload_user(user_id, std::move(promise), "on_set_profile_photo"); } } void UserManager::add_set_profile_photo_to_cache(UserId user_id, Photo &&photo, bool is_fallback) { // we have subsequence of user photos in user_photos_ // ProfilePhoto in User and Photo in UserFull User *u = get_user_force(user_id, "add_set_profile_photo_to_cache"); if (u == nullptr) { return; } LOG(INFO) << "Add profile photo " << photo.id.get() << " to cache"; bool is_me = user_id == get_my_id(); // update photo list auto user_photos = user_photos_.get_pointer(user_id); if (is_me && !is_fallback && user_photos != nullptr && user_photos->count != -1 && !photo.is_empty()) { if (user_photos->offset == 0) { if (user_photos->photos.empty() || user_photos->photos[0].id.get() != photo.id.get()) { user_photos->photos.insert(user_photos->photos.begin(), photo); user_photos->count++; register_user_photo(u, user_id, user_photos->photos[0]); } } else { user_photos->count++; user_photos->offset++; } } // update ProfilePhoto in User if ((!is_fallback || u->photo.id == 0) && !photo.is_empty()) { do_update_user_photo(u, user_id, as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, photo, !is_me), false, "add_set_profile_photo_to_cache"); update_user(u, user_id); } // update Photo in UserFull auto user_full = get_user_full_force(user_id, "add_set_profile_photo_to_cache"); if (user_full != nullptr) { Photo *current_photo = nullptr; // don't update the changed photo if other photos aren't known to avoid having only some photos known bool need_apply = get_user_full_profile_photo_id(user_full) > 0; if (!is_me) { current_photo = &user_full->personal_photo; if (photo.is_empty()) { // always can apply empty personal photo need_apply = true; } } else if (!is_fallback) { current_photo = &user_full->photo; if (photo.is_empty()) { // never can apply empty photo need_apply = false; } } else { current_photo = &user_full->fallback_photo; if (photo.is_empty()) { // always can apply empty fallback photo need_apply = true; } } if (*current_photo != photo && need_apply) { LOG(INFO) << "Update full photo of " << user_id << " to " << photo; *current_photo = photo; user_full->is_changed = true; if (is_me && !photo.is_empty()) { if (!is_fallback) { register_user_photo(u, user_id, photo); } else { register_suggested_profile_photo(photo); } } drop_user_full_photos(user_full, user_id, u->photo.id, "add_set_profile_photo_to_cache"); // just in case } if (user_full->expires_at > 0.0) { user_full->expires_at = 0.0; user_full->need_save_to_database = true; } update_user_full(user_full, user_id, "add_set_profile_photo_to_cache"); reload_user_full(user_id, Auto(), "add_set_profile_photo_to_cache"); } } bool UserManager::delete_my_profile_photo_from_cache(int64 profile_photo_id) { if (profile_photo_id == 0 || profile_photo_id == -2) { return false; } // we have subsequence of user photos in user_photos_ // ProfilePhoto in User and Photo in UserFull LOG(INFO) << "Delete profile photo " << profile_photo_id << " from cache"; auto user_id = get_my_id(); User *u = get_user_force(user_id, "delete_my_profile_photo_from_cache"); bool is_main_photo_deleted = u != nullptr && u->photo.id == profile_photo_id; // update photo list auto user_photos = user_photos_.get_pointer(user_id); if (user_photos != nullptr && user_photos->count > 0) { auto old_size = user_photos->photos.size(); if (td::remove_if(user_photos->photos, [profile_photo_id](const auto &photo) { return photo.id.get() == profile_photo_id; })) { auto removed_photos = old_size - user_photos->photos.size(); CHECK(removed_photos > 0); LOG_IF(ERROR, removed_photos != 1) << "Had " << removed_photos << " photos with ID " << profile_photo_id; user_photos->count -= narrow_cast(removed_photos); // offset was not changed CHECK(user_photos->count >= 0); } else { // failed to find photo to remove from cache // don't know how to adjust user_photos->offset, so drop photos cache LOG(INFO) << "Drop photos of " << user_id; user_photos->photos.clear(); user_photos->count = -1; user_photos->offset = -1; } } bool have_new_photo = user_photos != nullptr && user_photos->count != -1 && user_photos->offset == 0 && !user_photos->photos.empty(); auto user_full = get_user_full_force(user_id, "delete_my_profile_photo_from_cache"); // update ProfilePhoto in User bool need_reget_user = false; if (is_main_photo_deleted) { if (have_new_photo) { do_update_user_photo( u, user_id, as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, user_photos->photos[0], false), false, "delete_my_profile_photo_from_cache"); } else { do_update_user_photo(u, user_id, ProfilePhoto(), false, "delete_my_profile_photo_from_cache 2"); need_reget_user = user_photos == nullptr || user_photos->count != 0; } update_user(u, user_id); // update Photo in UserFull if (user_full != nullptr) { if (user_full->fallback_photo.id.get() == profile_photo_id) { LOG(INFO) << "Drop full public photo of " << user_id; user_full->photo = Photo(); user_full->is_changed = true; } else if (have_new_photo) { if (user_full->photo.id.get() == profile_photo_id && user_photos->photos[0] != user_full->photo) { LOG(INFO) << "Update full photo of " << user_id << " to " << user_photos->photos[0]; user_full->photo = user_photos->photos[0]; user_full->is_changed = true; } } else { // repair UserFull photo if (!user_full->photo.is_empty()) { user_full->photo = Photo(); user_full->is_changed = true; } if (!user_full->fallback_photo.is_empty()) { user_full->fallback_photo = Photo(); user_full->is_changed = true; } } if (user_full->expires_at > 0.0) { user_full->expires_at = 0.0; user_full->need_save_to_database = true; } reload_user_full(user_id, Auto(), "delete_my_profile_photo_from_cache"); update_user_full(user_full, user_id, "delete_my_profile_photo_from_cache"); } } return need_reget_user; } void UserManager::delete_profile_photo(int64 profile_photo_id, bool is_recursive, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); const UserFull *user_full = get_user_full_force(get_my_id(), "delete_profile_photo"); if (user_full == nullptr) { // must load UserFull first, because fallback photo can't be deleted via DeleteProfilePhotoQuery if (is_recursive) { return promise.set_error(Status::Error(500, "Failed to load UserFullInfo")); } auto reload_promise = PromiseCreator::lambda( [actor_id = actor_id(this), profile_photo_id, promise = std::move(promise)](Result result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &UserManager::delete_profile_photo, profile_photo_id, true, std::move(promise)); }); reload_user_full(get_my_id(), std::move(reload_promise), "delete_profile_photo"); return; } if (user_full->photo.id.get() == profile_photo_id || user_full->fallback_photo.id.get() == profile_photo_id) { td_->create_handler(std::move(promise)) ->send(get_my_id(), FileId(), profile_photo_id, user_full->fallback_photo.id.get() == profile_photo_id, telegram_api::make_object()); return; } td_->create_handler(std::move(promise))->send(profile_photo_id); } void UserManager::on_delete_profile_photo(int64 profile_photo_id, Promise promise) { bool need_reget_user = delete_my_profile_photo_from_cache(profile_photo_id); if (need_reget_user && !G()->close_flag()) { return reload_user(get_my_id(), std::move(promise), "on_delete_profile_photo"); } promise.set_value(Unit()); } void UserManager::set_username(const string &username, Promise &&promise) { if (!username.empty() && !is_allowed_username(username)) { return promise.set_error(Status::Error(400, "Username is invalid")); } td_->create_handler(std::move(promise))->send(username); } void UserManager::toggle_username_is_active(string &&username, bool is_active, Promise &&promise) { get_me(PromiseCreator::lambda([actor_id = actor_id(this), username = std::move(username), is_active, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &UserManager::toggle_username_is_active_impl, std::move(username), is_active, std::move(promise)); } })); } void UserManager::toggle_username_is_active_impl(string &&username, bool is_active, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); const User *u = get_user(get_my_id()); CHECK(u != nullptr); if (!u->usernames.can_toggle(username)) { return promise.set_error(Status::Error(400, "Wrong username specified")); } td_->create_handler(std::move(promise))->send(std::move(username), is_active); } void UserManager::reorder_usernames(vector &&usernames, Promise &&promise) { get_me(PromiseCreator::lambda([actor_id = actor_id(this), usernames = std::move(usernames), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &UserManager::reorder_usernames_impl, std::move(usernames), std::move(promise)); } })); } void UserManager::reorder_usernames_impl(vector &&usernames, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); const User *u = get_user(get_my_id()); CHECK(u != nullptr); if (!u->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(std::move(usernames)); } void UserManager::on_update_username_is_active(UserId user_id, string &&username, bool is_active, Promise &&promise) { User *u = get_user(user_id); CHECK(u != nullptr); if (!u->usernames.can_toggle(username)) { return reload_user(user_id, std::move(promise), "on_update_username_is_active"); } on_update_user_usernames(u, user_id, u->usernames.toggle(username, is_active)); update_user(u, user_id); promise.set_value(Unit()); } void UserManager::on_update_active_usernames_order(UserId user_id, vector &&usernames, Promise &&promise) { User *u = get_user(user_id); CHECK(u != nullptr); if (!u->usernames.can_reorder_to(usernames)) { return reload_user(user_id, std::move(promise), "on_update_active_usernames_order"); } on_update_user_usernames(u, user_id, u->usernames.reorder_to(std::move(usernames))); update_user(u, user_id); promise.set_value(Unit()); } void UserManager::toggle_bot_username_is_active(UserId bot_user_id, string &&username, bool is_active, Promise &&promise) { TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); if (!bot_data.can_be_edited) { return promise.set_error(Status::Error(400, "The bot can't be edited")); } const User *u = get_user(bot_user_id); CHECK(u != nullptr); if (!u->usernames.can_toggle(username)) { return promise.set_error(Status::Error(400, "Wrong username specified")); } td_->create_handler(std::move(promise))->send(bot_user_id, std::move(username), is_active); } void UserManager::reorder_bot_usernames(UserId bot_user_id, vector &&usernames, Promise &&promise) { TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); if (!bot_data.can_be_edited) { return promise.set_error(Status::Error(400, "The bot can't be edited")); } const User *u = get_user(bot_user_id); CHECK(u != nullptr); if (!u->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(bot_user_id, std::move(usernames)); } void UserManager::set_accent_color(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")); } if (accent_color_id == AccentColorId(get_my_id())) { accent_color_id = AccentColorId(); } td_->create_handler(std::move(promise))->send(false, accent_color_id, background_custom_emoji_id); } void UserManager::set_profile_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, Promise &&promise) { td_->create_handler(std::move(promise))->send(true, accent_color_id, background_custom_emoji_id); } void UserManager::on_update_accent_color_success(bool for_profile, AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id) { auto user_id = get_my_id(); User *u = get_user_force(user_id, "on_update_accent_color_success"); if (u == nullptr) { return; } if (for_profile) { on_update_user_profile_accent_color_id(u, user_id, accent_color_id); on_update_user_profile_background_custom_emoji_id(u, user_id, background_custom_emoji_id); } else { on_update_user_accent_color_id(u, user_id, accent_color_id); on_update_user_background_custom_emoji_id(u, user_id, background_custom_emoji_id); } update_user(u, user_id); } void UserManager::set_birthdate(Birthdate &&birthdate, Promise &&promise) { dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::BirthdaySetup}, Promise()); auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), birthdate, promise = std::move(promise)](Result result) mutable { if (result.is_ok()) { send_closure(actor_id, &UserManager::on_set_birthdate, birthdate, std::move(promise)); } else { promise.set_error(result.move_as_error()); } }); td_->create_handler(std::move(query_promise))->send(birthdate); } void UserManager::on_set_birthdate(Birthdate birthdate, Promise &&promise) { auto my_user_id = get_my_id(); UserFull *user_full = get_user_full_force(my_user_id, "on_set_birthdate"); if (user_full != nullptr && user_full->birthdate != birthdate) { user_full->birthdate = std::move(birthdate); user_full->is_changed = true; update_user_full(user_full, my_user_id, "on_set_birthdate"); } promise.set_value(Unit()); } void UserManager::set_personal_channel(DialogId dialog_id, Promise &&promise) { ChannelId channel_id; if (dialog_id != DialogId() && !td_->dialog_manager_->have_dialog_force(dialog_id, "set_personal_channel")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (dialog_id != DialogId() && dialog_id.get_type() != DialogType::Channel) { return promise.set_error(Status::Error(400, "Chat can't be set as a personal chat")); } auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), channel_id, promise = std::move(promise)](Result result) mutable { if (result.is_ok()) { send_closure(actor_id, &UserManager::on_set_personal_channel, channel_id, std::move(promise)); } else { promise.set_error(result.move_as_error()); } }); td_->create_handler(std::move(query_promise))->send(channel_id); } void UserManager::on_set_personal_channel(ChannelId channel_id, Promise &&promise) { auto my_user_id = get_my_id(); UserFull *user_full = get_user_full_force(my_user_id, "on_set_personal_channel"); if (user_full != nullptr && user_full->personal_channel_id != channel_id) { user_full->personal_channel_id = channel_id; user_full->is_changed = true; update_user_full(user_full, my_user_id, "on_set_personal_channel"); } promise.set_value(Unit()); } void UserManager::set_emoji_status(const EmojiStatus &emoji_status, Promise &&promise) { if (!td_->option_manager_->get_option_boolean("is_premium")) { return promise.set_error(Status::Error(400, "The method is available only to Telegram Premium users")); } add_recent_emoji_status(td_, emoji_status); auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), emoji_status, promise = std::move(promise)](Result result) mutable { if (result.is_ok()) { send_closure(actor_id, &UserManager::on_set_emoji_status, emoji_status, std::move(promise)); } else { promise.set_error(result.move_as_error()); } }); td_->create_handler(std::move(query_promise))->send(emoji_status); } void UserManager::on_set_emoji_status(EmojiStatus emoji_status, Promise &&promise) { auto user_id = get_my_id(); User *u = get_user(user_id); if (u != nullptr) { on_update_user_emoji_status(u, user_id, emoji_status); update_user(u, user_id); } promise.set_value(Unit()); } void UserManager::get_support_user(Promise> &&promise) { if (support_user_id_.is_valid()) { return promise.set_value(get_user_object(support_user_id_)); } auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &UserManager::on_get_support_user, result.move_as_ok(), std::move(promise)); } }); td_->create_handler(std::move(query_promise))->send(); } void UserManager::on_get_support_user(UserId user_id, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); const User *u = get_user(user_id); if (u == nullptr) { return promise.set_error(Status::Error(500, "Can't find support user")); } if (!u->is_support) { LOG(ERROR) << "Receive non-support " << user_id << ", but expected a support user"; } support_user_id_ = user_id; promise.set_value(get_user_object(user_id, u)); } void UserManager::get_user_profile_photos(UserId user_id, int32 offset, int32 limit, Promise> &&promise) { if (offset < 0) { return promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); } if (limit <= 0) { return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } if (limit > MAX_GET_PROFILE_PHOTOS) { limit = MAX_GET_PROFILE_PHOTOS; } TRY_STATUS_PROMISE(promise, get_input_user(user_id)); auto *u = get_user(user_id); if (u == nullptr) { return promise.set_error(Status::Error(400, "User not found")); } apply_pending_user_photo(u, user_id); auto user_photos = add_user_photos(user_id); if (user_photos->count != -1) { // know photo count CHECK(user_photos->offset != -1); LOG(INFO) << "Have " << user_photos->count << " cached user profile photos at offset " << user_photos->offset; vector> photo_objects; if (offset >= user_photos->count) { // offset if too big return promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); } if (limit > user_photos->count - offset) { limit = user_photos->count - offset; } int32 cache_begin = user_photos->offset; int32 cache_end = cache_begin + narrow_cast(user_photos->photos.size()); if (cache_begin <= offset && offset + limit <= cache_end) { // answer query from cache for (int i = 0; i < limit; i++) { photo_objects.push_back( get_chat_photo_object(td_->file_manager_.get(), user_photos->photos[i + offset - cache_begin])); } return promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); } } PendingGetPhotoRequest pending_request; pending_request.offset = offset; pending_request.limit = limit; pending_request.promise = std::move(promise); user_photos->pending_requests.push_back(std::move(pending_request)); if (user_photos->pending_requests.size() != 1u) { return; } send_get_user_photos_query(user_id, user_photos); } void UserManager::send_get_user_photos_query(UserId user_id, const UserPhotos *user_photos) { CHECK(!user_photos->pending_requests.empty()); auto offset = user_photos->pending_requests[0].offset; auto limit = user_photos->pending_requests[0].limit; if (user_photos->count != -1 && offset >= user_photos->offset) { int32 cache_end = user_photos->offset + narrow_cast(user_photos->photos.size()); if (offset < cache_end) { // adjust offset to the end of cache CHECK(offset + limit > cache_end); // otherwise the request has already been answered limit = offset + limit - cache_end; offset = cache_end; } } auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), user_id](Result &&result) { send_closure(actor_id, &UserManager::on_get_user_profile_photos, user_id, std::move(result)); }); td_->create_handler(std::move(query_promise)) ->send(user_id, get_input_user_force(user_id), offset, max(limit, MAX_GET_PROFILE_PHOTOS / 5), 0); } void UserManager::on_get_user_profile_photos(UserId user_id, Result &&result) { G()->ignore_result_if_closing(result); auto user_photos = add_user_photos(user_id); auto pending_requests = std::move(user_photos->pending_requests); CHECK(!pending_requests.empty()); if (result.is_error()) { for (auto &request : pending_requests) { request.promise.set_error(result.error().clone()); } return; } if (user_photos->count == -1) { CHECK(have_user(user_id)); // received result has just been dropped; resend request if (++pending_requests[0].retry_count >= 3) { pending_requests[0].promise.set_error(Status::Error(500, "Failed to return profile photos")); pending_requests.erase(pending_requests.begin()); if (pending_requests.empty()) { return; } } user_photos->pending_requests = std::move(pending_requests); return send_get_user_photos_query(user_id, user_photos); } CHECK(user_photos->offset != -1); LOG(INFO) << "Have " << user_photos->count << " cached user profile photos at offset " << user_photos->offset; vector left_requests; for (size_t request_index = 0; request_index < pending_requests.size(); request_index++) { auto &request = pending_requests[request_index]; vector> photo_objects; if (request.offset >= user_photos->count) { // offset if too big request.promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); continue; } if (request.limit > user_photos->count - request.offset) { request.limit = user_photos->count - request.offset; } int32 cache_begin = user_photos->offset; int32 cache_end = cache_begin + narrow_cast(user_photos->photos.size()); if (cache_begin <= request.offset && request.offset + request.limit <= cache_end) { // answer query from cache for (int i = 0; i < request.limit; i++) { photo_objects.push_back( get_chat_photo_object(td_->file_manager_.get(), user_photos->photos[i + request.offset - cache_begin])); } request.promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); continue; } if (request_index == 0 && ++request.retry_count >= 3) { request.promise.set_error(Status::Error(500, "Failed to get profile photos")); continue; } left_requests.push_back(std::move(request)); } if (!left_requests.empty()) { bool need_send = user_photos->pending_requests.empty(); append(user_photos->pending_requests, std::move(left_requests)); if (need_send) { send_get_user_photos_query(user_id, user_photos); } } } void UserManager::reload_user_profile_photo(UserId user_id, int64 photo_id, Promise &&promise) { get_user_force(user_id, "reload_user_profile_photo"); TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); // this request will be needed only to download the photo, // so there is no reason to combine different requests for a photo into one request td_->create_handler(std::move(promise))->send(user_id, std::move(input_user), -1, 1, photo_id); } FileSourceId UserManager::get_user_profile_photo_file_source_id(UserId user_id, int64 photo_id) { if (!user_id.is_valid()) { return FileSourceId(); } auto u = get_user(user_id); if (u != nullptr && u->photo_ids.count(photo_id) != 0) { VLOG(file_references) << "Don't need to create file source for photo " << photo_id << " of " << user_id; // photo was already added, source ID was registered and shouldn't be needed return FileSourceId(); } auto &source_id = user_profile_photo_file_source_ids_[std::make_pair(user_id, photo_id)]; if (!source_id.is_valid()) { source_id = td_->file_reference_manager_->create_user_photo_file_source(user_id, photo_id); } VLOG(file_references) << "Return " << source_id << " for photo " << photo_id << " of " << user_id; return source_id; } UserManager::UserPhotos *UserManager::add_user_photos(UserId user_id) { CHECK(user_id.is_valid()); auto &user_photos_ptr = user_photos_[user_id]; if (user_photos_ptr == nullptr) { user_photos_ptr = make_unique(); } return user_photos_ptr.get(); } void UserManager::on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count, vector> photos) { auto photo_count = narrow_cast(photos.size()); int32 min_total_count = (offset >= 0 && photo_count > 0 ? offset : 0) + photo_count; if (total_count < min_total_count) { LOG(ERROR) << "Receive wrong photos total_count " << total_count << " for user " << user_id << ": receive " << photo_count << " photos with offset " << offset; total_count = min_total_count; } LOG_IF(ERROR, limit < photo_count) << "Requested not more than " << limit << " photos, but " << photo_count << " received"; User *u = get_user(user_id); if (u == nullptr) { LOG(ERROR) << "Can't find " << user_id; return; } if (offset == -1) { // from reload_user_profile_photo CHECK(limit == 1); for (auto &photo_ptr : photos) { if (photo_ptr->get_id() == telegram_api::photo::ID) { auto server_photo = telegram_api::move_object_as(photo_ptr); if (server_photo->id_ == u->photo.id) { auto profile_photo = convert_photo_to_profile_photo(server_photo, u->photo.is_personal); if (profile_photo) { LOG_IF(ERROR, u->access_hash == -1) << "Receive profile photo of " << user_id << " without access hash"; get_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, std::move(profile_photo)); } else { LOG(ERROR) << "Failed to get profile photo from " << to_string(server_photo); } } auto photo = get_photo(td_, std::move(server_photo), DialogId(user_id)); register_user_photo(u, user_id, photo); } } return; } LOG(INFO) << "Receive " << photo_count << " photos of " << user_id << " out of " << total_count << " with offset " << offset << " and limit " << limit; UserPhotos *user_photos = add_user_photos(user_id); user_photos->count = total_count; CHECK(!user_photos->pending_requests.empty()); if (user_photos->offset == -1) { user_photos->offset = 0; CHECK(user_photos->photos.empty()); } if (offset != narrow_cast(user_photos->photos.size()) + user_photos->offset) { LOG(INFO) << "Inappropriate offset to append " << user_id << " profile photos to cache: offset = " << offset << ", current_offset = " << user_photos->offset << ", photo_count = " << user_photos->photos.size(); user_photos->photos.clear(); user_photos->offset = offset; } for (auto &photo : photos) { auto user_photo = get_photo(td_, std::move(photo), DialogId(user_id)); if (user_photo.is_empty()) { LOG(ERROR) << "Receive empty profile photo in getUserPhotos request for " << user_id << " with offset " << offset << " and limit " << limit << ". Receive " << photo_count << " photos out of " << total_count << " photos"; user_photos->count--; CHECK(user_photos->count >= 0); continue; } user_photos->photos.push_back(std::move(user_photo)); register_user_photo(u, user_id, user_photos->photos.back()); } if (user_photos->offset > user_photos->count) { user_photos->offset = user_photos->count; user_photos->photos.clear(); } auto known_photo_count = narrow_cast(user_photos->photos.size()); if (user_photos->offset + known_photo_count > user_photos->count) { user_photos->photos.resize(user_photos->count - user_photos->offset); } } void UserManager::apply_pending_user_photo(User *u, UserId user_id) { if (u == nullptr || u->is_photo_inited) { return; } if (pending_user_photos_.count(user_id) > 0) { do_update_user_photo(u, user_id, std::move(pending_user_photos_[user_id]), "apply_pending_user_photo"); pending_user_photos_.erase(user_id); update_user(u, user_id); } } void UserManager::register_message_users(MessageFullId message_full_id, vector user_ids) { auto dialog_id = message_full_id.get_dialog_id(); CHECK(dialog_id.get_type() == DialogType::Channel); if (!td_->contacts_manager_->have_channel(dialog_id.get_channel_id())) { return; } for (auto user_id : user_ids) { CHECK(user_id.is_valid()); const User *u = get_user(user_id); if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { auto &user_messages = user_messages_[user_id]; auto need_update = user_messages.empty(); user_messages.insert(message_full_id); if (need_update) { send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); } } } } void UserManager::unregister_message_users(MessageFullId message_full_id, vector user_ids) { if (user_messages_.empty()) { // fast path return; } for (auto user_id : user_ids) { auto it = user_messages_.find(user_id); if (it != user_messages_.end()) { it->second.erase(message_full_id); if (it->second.empty()) { user_messages_.erase(it); const User *u = get_user(user_id); if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); } } } } } void UserManager::can_send_message_to_user(UserId user_id, bool force, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (user_id == get_my_id()) { return promise.set_value(td_api::make_object()); } const auto *u = get_user(user_id); if (!have_input_peer_user(u, user_id, AccessRights::Write)) { return promise.set_value(td_api::make_object()); } CHECK(user_id.is_valid()); if ((u != nullptr && (!u->contact_require_premium || u->is_mutual_contact)) || td_->option_manager_->get_option_boolean("is_premium")) { return promise.set_value(td_api::make_object()); } auto user_full = get_user_full_force(user_id, "can_send_message_to_user"); if (user_full != nullptr) { if (!user_full->contact_require_premium) { return promise.set_value(td_api::make_object()); } return promise.set_value(td_api::make_object()); } auto it = user_full_contact_require_premium_.find(user_id); if (it != user_full_contact_require_premium_.end()) { if (!it->second) { return promise.set_value(td_api::make_object()); } return promise.set_value(td_api::make_object()); } if (force) { return promise.set_value(td_api::make_object()); } auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), user_id, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &UserManager::can_send_message_to_user, user_id, true, std::move(promise)); }); get_is_premium_required_to_contact_queries_.add_query(user_id.get(), std::move(query_promise), "can_send_message_to_user"); } void UserManager::on_get_is_premium_required_to_contact_users(vector &&user_ids, vector &&is_premium_required, Promise &&promise) { if (user_ids.size() != is_premium_required.size()) { LOG(ERROR) << "Receive " << is_premium_required.size() << " flags instead of " << user_ids.size(); return promise.set_error(Status::Error(500, "Receive invalid response")); } for (size_t i = 0; i < user_ids.size(); i++) { auto user_id = user_ids[i]; CHECK(user_id.is_valid()); if (get_user_full(user_id) == nullptr) { user_full_contact_require_premium_[user_id] = is_premium_required[i]; } } promise.set_value(Unit()); } void UserManager::allow_send_message_to_user(UserId user_id) { if (get_user_full(user_id) == nullptr) { CHECK(user_id.is_valid()); user_full_contact_require_premium_[user_id] = true; } } void UserManager::share_phone_number(UserId user_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (!are_contacts_loaded_) { load_contacts(PromiseCreator::lambda( [actor_id = actor_id(this), user_id, promise = std::move(promise)](Result &&) mutable { send_closure(actor_id, &UserManager::share_phone_number, user_id, std::move(promise)); })); return; } LOG(INFO) << "Share phone number with " << user_id; TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); td_->messages_manager_->hide_dialog_action_bar(DialogId(user_id)); td_->create_handler(std::move(promise))->send(user_id, std::move(input_user)); } void UserManager::load_contacts(Promise &&promise) { if (td_->auth_manager_->is_bot()) { are_contacts_loaded_ = true; saved_contact_count_ = 0; } if (are_contacts_loaded_ && saved_contact_count_ != -1) { LOG(INFO) << "Contacts are already loaded"; return promise.set_value(Unit()); } load_contacts_queries_.push_back(std::move(promise)); if (load_contacts_queries_.size() == 1u) { if (G()->use_chat_info_database() && next_contacts_sync_date_ > 0 && saved_contact_count_ != -1) { LOG(INFO) << "Load contacts from database"; G()->td_db()->get_sqlite_pmc()->get( "user_contacts", PromiseCreator::lambda([](string value) { send_closure(G()->user_manager(), &UserManager::on_load_contacts_from_database, std::move(value)); })); } else { LOG(INFO) << "Load contacts from server"; reload_contacts(true); } } else { LOG(INFO) << "Load contacts request has already been sent"; } } int64 UserManager::get_contacts_hash() { if (!are_contacts_loaded_) { return 0; } vector user_ids = contacts_hints_.search_empty(100000).second; CHECK(std::is_sorted(user_ids.begin(), user_ids.end())); auto my_id = get_my_id(); const User *u = get_user_force(my_id, "get_contacts_hash"); if (u != nullptr && u->is_contact) { user_ids.insert(std::upper_bound(user_ids.begin(), user_ids.end(), my_id.get()), my_id.get()); } vector numbers; numbers.reserve(user_ids.size() + 1); numbers.push_back(saved_contact_count_); for (auto user_id : user_ids) { numbers.push_back(user_id); } return get_vector_hash(numbers); } void UserManager::reload_contacts(bool force) { if (!G()->close_flag() && !td_->auth_manager_->is_bot() && next_contacts_sync_date_ != std::numeric_limits::max() && (next_contacts_sync_date_ < G()->unix_time() || force)) { next_contacts_sync_date_ = std::numeric_limits::max(); td_->create_handler()->send(get_contacts_hash()); } } void UserManager::save_next_contacts_sync_date() { if (G()->close_flag()) { return; } if (!G()->use_chat_info_database()) { return; } G()->td_db()->get_binlog_pmc()->set("next_contacts_sync_date", to_string(next_contacts_sync_date_)); } void UserManager::save_contacts_to_database() { if (!G()->use_chat_info_database() || !are_contacts_loaded_) { return; } LOG(INFO) << "Schedule save contacts to database"; vector user_ids = transform(contacts_hints_.search_empty(100000).second, [](int64 key) { return UserId(key); }); G()->td_db()->get_binlog_pmc()->set("saved_contact_count", to_string(saved_contact_count_)); G()->td_db()->get_binlog()->force_sync( PromiseCreator::lambda([user_ids = std::move(user_ids)](Result<> result) { if (result.is_ok()) { LOG(INFO) << "Saved contacts to database"; G()->td_db()->get_sqlite_pmc()->set( "user_contacts", log_event_store(user_ids).as_slice().str(), PromiseCreator::lambda([](Result<> result) { if (result.is_ok()) { send_closure(G()->user_manager(), &UserManager::save_next_contacts_sync_date); } })); } }), "save_contacts_to_database"); } void UserManager::on_get_contacts(telegram_api::object_ptr &&new_contacts) { next_contacts_sync_date_ = G()->unix_time() + Random::fast(70000, 100000); CHECK(new_contacts != nullptr); if (new_contacts->get_id() == telegram_api::contacts_contactsNotModified::ID) { if (saved_contact_count_ == -1) { saved_contact_count_ = 0; } on_get_contacts_finished(contacts_hints_.size()); td_->create_handler()->send(); return; } auto contacts = move_tl_object_as(new_contacts); FlatHashSet contact_user_ids; for (auto &user : contacts->users_) { auto user_id = get_user_id(user); if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; continue; } contact_user_ids.insert(user_id); } on_get_users(std::move(contacts->users_), "on_get_contacts"); UserId my_id = get_my_id(); users_.foreach([&](const UserId &user_id, unique_ptr &user) { User *u = user.get(); bool should_be_contact = contact_user_ids.count(user_id) == 1; if (u->is_contact != should_be_contact) { if (u->is_contact) { LOG(INFO) << "Drop contact with " << user_id; if (user_id != my_id) { LOG_CHECK(contacts_hints_.has_key(user_id.get())) << my_id << " " << user_id << " " << to_string(get_user_object(user_id, u)); } on_update_user_is_contact(u, user_id, false, false, false); CHECK(u->is_is_contact_changed); u->cache_version = 0; u->is_repaired = false; update_user(u, user_id); CHECK(!u->is_contact); if (user_id != my_id) { CHECK(!contacts_hints_.has_key(user_id.get())); } } else { LOG(ERROR) << "Receive non-contact " << user_id << " in the list of contacts"; } } }); saved_contact_count_ = contacts->saved_count_; on_get_contacts_finished(std::numeric_limits::max()); } void UserManager::on_get_contacts_failed(Status error) { CHECK(error.is_error()); next_contacts_sync_date_ = G()->unix_time() + Random::fast(5, 10); fail_promises(load_contacts_queries_, std::move(error)); } void UserManager::on_load_contacts_from_database(string value) { if (G()->close_flag()) { return; } if (value.empty()) { reload_contacts(true); return; } vector user_ids; if (log_event_parse(user_ids, value).is_error()) { LOG(ERROR) << "Failed to load contacts from database"; reload_contacts(true); return; } if (log_event_get_version(value) < static_cast(Version::AddUserFlags2)) { next_contacts_sync_date_ = 0; save_next_contacts_sync_date(); reload_contacts(true); } LOG(INFO) << "Successfully loaded " << user_ids.size() << " contacts from database"; load_contact_users_multipromise_.add_promise(PromiseCreator::lambda( [actor_id = actor_id(this), expected_contact_count = user_ids.size()](Result result) { if (result.is_ok()) { send_closure(actor_id, &UserManager::on_get_contacts_finished, expected_contact_count); } else { LOG(INFO) << "Failed to load contact users from database: " << result.error(); send_closure(actor_id, &UserManager::reload_contacts, true); } })); auto lock_promise = load_contact_users_multipromise_.get_promise(); for (auto user_id : user_ids) { get_user(user_id, 3, load_contact_users_multipromise_.get_promise()); } lock_promise.set_value(Unit()); } void UserManager::on_get_contacts_finished(size_t expected_contact_count) { LOG(INFO) << "Finished to get " << contacts_hints_.size() << " contacts out of expected " << expected_contact_count; are_contacts_loaded_ = true; set_promises(load_contacts_queries_); if (expected_contact_count != contacts_hints_.size()) { save_contacts_to_database(); } } void UserManager::on_get_contacts_statuses(vector> &&statuses) { auto my_user_id = get_my_id(); for (auto &status : statuses) { UserId user_id(status->user_id_); if (user_id != my_user_id) { on_update_user_online(user_id, std::move(status->status_)); } } save_next_contacts_sync_date(); } void UserManager::add_contact(Contact contact, bool share_phone_number, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (!are_contacts_loaded_) { load_contacts(PromiseCreator::lambda([actor_id = actor_id(this), contact = std::move(contact), share_phone_number, promise = std::move(promise)](Result &&) mutable { send_closure(actor_id, &UserManager::add_contact, std::move(contact), share_phone_number, std::move(promise)); })); return; } LOG(INFO) << "Add " << contact << " with share_phone_number = " << share_phone_number; auto user_id = contact.get_user_id(); TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); td_->create_handler(std::move(promise)) ->send(user_id, std::move(input_user), contact, share_phone_number); } std::pair, vector> UserManager::import_contacts(const vector &contacts, int64 &random_id, Promise &&promise) { if (!are_contacts_loaded_) { load_contacts(std::move(promise)); return {}; } LOG(INFO) << "Asked to import " << contacts.size() << " contacts with random_id = " << random_id; if (random_id != 0) { // request has already been sent before auto it = imported_contacts_.find(random_id); CHECK(it != imported_contacts_.end()); auto result = std::move(it->second); imported_contacts_.erase(it); promise.set_value(Unit()); return result; } do { random_id = Random::secure_int64(); } while (random_id == 0 || random_id == 1 || imported_contacts_.count(random_id) > 0); imported_contacts_[random_id]; // reserve place for result do_import_contacts(contacts, random_id, std::move(promise)); return {}; } void UserManager::do_import_contacts(vector contacts, int64 random_id, Promise &&promise) { size_t size = contacts.size(); if (size == 0) { on_import_contacts_finished(random_id, {}, {}); return promise.set_value(Unit()); } vector> input_phone_contacts; input_phone_contacts.reserve(size); for (size_t i = 0; i < size; i++) { input_phone_contacts.push_back(contacts[i].get_input_phone_contact(static_cast(i))); } auto task = make_unique(); task->promise_ = std::move(promise); task->input_contacts_ = std::move(contacts); task->imported_user_ids_.resize(size); task->unimported_contact_invites_.resize(size); bool is_added = import_contact_tasks_.emplace(random_id, std::move(task)).second; CHECK(is_added); td_->create_handler()->send(std::move(input_phone_contacts), random_id); } void UserManager::on_imported_contacts( int64 random_id, Result> result) { auto it = import_contact_tasks_.find(random_id); CHECK(it != import_contact_tasks_.end()); CHECK(it->second != nullptr); auto task = it->second.get(); if (result.is_error()) { auto promise = std::move(task->promise_); import_contact_tasks_.erase(it); return promise.set_error(result.move_as_error()); } auto imported_contacts = result.move_as_ok(); on_get_users(std::move(imported_contacts->users_), "on_imported_contacts"); for (auto &imported_contact : imported_contacts->imported_) { int64 client_id = imported_contact->client_id_; if (client_id < 0 || client_id >= static_cast(task->imported_user_ids_.size())) { LOG(ERROR) << "Wrong client_id " << client_id << " returned"; continue; } task->imported_user_ids_[static_cast(client_id)] = UserId(imported_contact->user_id_); } for (auto &popular_contact : imported_contacts->popular_invites_) { int64 client_id = popular_contact->client_id_; if (client_id < 0 || client_id >= static_cast(task->unimported_contact_invites_.size())) { LOG(ERROR) << "Wrong client_id " << client_id << " returned"; continue; } if (popular_contact->importers_ < 0) { LOG(ERROR) << "Wrong number of importers " << popular_contact->importers_ << " returned"; continue; } task->unimported_contact_invites_[static_cast(client_id)] = popular_contact->importers_; } if (!imported_contacts->retry_contacts_.empty()) { auto total_size = static_cast(task->input_contacts_.size()); vector> input_phone_contacts; input_phone_contacts.reserve(imported_contacts->retry_contacts_.size()); for (auto &client_id : imported_contacts->retry_contacts_) { if (client_id < 0 || client_id >= total_size) { LOG(ERROR) << "Wrong client_id " << client_id << " returned"; continue; } auto i = static_cast(client_id); input_phone_contacts.push_back(task->input_contacts_[i].get_input_phone_contact(client_id)); } td_->create_handler()->send(std::move(input_phone_contacts), random_id); return; } auto promise = std::move(task->promise_); on_import_contacts_finished(random_id, std::move(task->imported_user_ids_), std::move(task->unimported_contact_invites_)); import_contact_tasks_.erase(it); promise.set_value(Unit()); } void UserManager::on_import_contacts_finished(int64 random_id, vector imported_contact_user_ids, vector unimported_contact_invites) { LOG(INFO) << "Contacts import with random_id " << random_id << " has finished: " << format::as_array(imported_contact_user_ids); if (random_id == 1) { // import from change_imported_contacts all_imported_contacts_ = std::move(next_all_imported_contacts_); next_all_imported_contacts_.clear(); auto result_size = imported_contacts_unique_id_.size(); auto unique_size = all_imported_contacts_.size(); auto add_size = imported_contacts_pos_.size(); imported_contact_user_ids_.resize(result_size); unimported_contact_invites_.resize(result_size); CHECK(imported_contact_user_ids.size() == add_size); CHECK(unimported_contact_invites.size() == add_size); CHECK(imported_contacts_unique_id_.size() == result_size); std::unordered_map> unique_id_to_unimported_contact_invites; for (size_t i = 0; i < add_size; i++) { auto unique_id = imported_contacts_pos_[i]; get_user_id_object(imported_contact_user_ids[i], "on_import_contacts_finished"); // to ensure updateUser all_imported_contacts_[unique_id].set_user_id(imported_contact_user_ids[i]); unique_id_to_unimported_contact_invites[narrow_cast(unique_id)] = unimported_contact_invites[i]; } if (G()->use_chat_info_database()) { G()->td_db()->get_binlog()->force_sync( PromiseCreator::lambda( [log_event = log_event_store(all_imported_contacts_).as_slice().str()](Result<> result) mutable { if (result.is_ok()) { LOG(INFO) << "Save imported contacts to database"; G()->td_db()->get_sqlite_pmc()->set("user_imported_contacts", std::move(log_event), Auto()); } }), "on_import_contacts_finished"); } for (size_t i = 0; i < result_size; i++) { auto unique_id = imported_contacts_unique_id_[i]; CHECK(unique_id < unique_size); imported_contact_user_ids_[i] = all_imported_contacts_[unique_id].get_user_id(); auto it = unique_id_to_unimported_contact_invites.find(narrow_cast(unique_id)); if (it == unique_id_to_unimported_contact_invites.end()) { unimported_contact_invites_[i] = 0; } else { unimported_contact_invites_[i] = it->second; } } return; } auto it = imported_contacts_.find(random_id); CHECK(it != imported_contacts_.end()); CHECK(it->second.first.empty()); CHECK(it->second.second.empty()); imported_contacts_[random_id] = {std::move(imported_contact_user_ids), std::move(unimported_contact_invites)}; } void UserManager::remove_contacts(const vector &user_ids, Promise &&promise) { LOG(INFO) << "Delete contacts: " << format::as_array(user_ids); if (!are_contacts_loaded_) { load_contacts(std::move(promise)); return; } vector to_delete_user_ids; vector> input_users; for (auto &user_id : user_ids) { const User *u = get_user(user_id); if (u != nullptr && u->is_contact) { auto r_input_user = get_input_user(user_id); if (r_input_user.is_ok()) { to_delete_user_ids.push_back(user_id); input_users.push_back(r_input_user.move_as_ok()); } } } if (input_users.empty()) { return promise.set_value(Unit()); } td_->create_handler(std::move(promise))->send(std::move(input_users)); } void UserManager::remove_contacts_by_phone_number(vector user_phone_numbers, vector user_ids, Promise &&promise) { LOG(INFO) << "Delete contacts by phone number: " << format::as_array(user_phone_numbers); if (!are_contacts_loaded_) { load_contacts(std::move(promise)); return; } td_->create_handler(std::move(promise)) ->send(std::move(user_phone_numbers), std::move(user_ids)); } void UserManager::on_deleted_contacts(const vector &deleted_contact_user_ids) { LOG(INFO) << "Contacts deletion has finished for " << deleted_contact_user_ids; for (auto user_id : deleted_contact_user_ids) { auto u = get_user(user_id); CHECK(u != nullptr); if (!u->is_contact) { continue; } LOG(INFO) << "Drop contact with " << user_id; on_update_user_is_contact(u, user_id, false, false, false); CHECK(u->is_is_contact_changed); u->cache_version = 0; u->is_repaired = false; update_user(u, user_id); CHECK(!u->is_contact); CHECK(!contacts_hints_.has_key(user_id.get())); } } int32 UserManager::get_imported_contact_count(Promise &&promise) { LOG(INFO) << "Get imported contact count"; if (!are_contacts_loaded_ || saved_contact_count_ == -1) { load_contacts(std::move(promise)); return 0; } reload_contacts(false); promise.set_value(Unit()); return saved_contact_count_; } void UserManager::load_imported_contacts(Promise &&promise) { if (td_->auth_manager_->is_bot()) { are_imported_contacts_loaded_ = true; } if (are_imported_contacts_loaded_) { LOG(INFO) << "Imported contacts are already loaded"; promise.set_value(Unit()); return; } load_imported_contacts_queries_.push_back(std::move(promise)); if (load_imported_contacts_queries_.size() == 1u) { if (G()->use_chat_info_database()) { LOG(INFO) << "Load imported contacts from database"; G()->td_db()->get_sqlite_pmc()->get("user_imported_contacts", PromiseCreator::lambda([](string value) { send_closure_later(G()->user_manager(), &UserManager::on_load_imported_contacts_from_database, std::move(value)); })); } else { LOG(INFO) << "Have no previously imported contacts"; send_closure_later(G()->user_manager(), &UserManager::on_load_imported_contacts_from_database, string()); } } else { LOG(INFO) << "Load imported contacts request has already been sent"; } } void UserManager::on_load_imported_contacts_from_database(string value) { if (G()->close_flag()) { return; } CHECK(!are_imported_contacts_loaded_); if (need_clear_imported_contacts_) { need_clear_imported_contacts_ = false; value.clear(); } if (value.empty()) { CHECK(all_imported_contacts_.empty()); } else { if (log_event_parse(all_imported_contacts_, value).is_error()) { LOG(ERROR) << "Failed to load all imported contacts from database"; all_imported_contacts_.clear(); } else { LOG(INFO) << "Successfully loaded " << all_imported_contacts_.size() << " imported contacts from database"; } } load_imported_contact_users_multipromise_.add_promise( PromiseCreator::lambda([actor_id = actor_id(this)](Result result) { if (result.is_ok()) { send_closure_later(actor_id, &UserManager::on_load_imported_contacts_finished); } })); auto lock_promise = load_imported_contact_users_multipromise_.get_promise(); for (const auto &contact : all_imported_contacts_) { auto user_id = contact.get_user_id(); if (user_id.is_valid()) { get_user(user_id, 3, load_imported_contact_users_multipromise_.get_promise()); } } lock_promise.set_value(Unit()); } void UserManager::on_load_imported_contacts_finished() { LOG(INFO) << "Finished to load " << all_imported_contacts_.size() << " imported contacts"; for (const auto &contact : all_imported_contacts_) { get_user_id_object(contact.get_user_id(), "on_load_imported_contacts_finished"); // to ensure updateUser } if (need_clear_imported_contacts_) { need_clear_imported_contacts_ = false; all_imported_contacts_.clear(); } are_imported_contacts_loaded_ = true; set_promises(load_imported_contacts_queries_); } std::pair, vector> UserManager::change_imported_contacts(vector &contacts, int64 &random_id, Promise &&promise) { if (!are_contacts_loaded_) { load_contacts(std::move(promise)); return {}; } if (!are_imported_contacts_loaded_) { load_imported_contacts(std::move(promise)); return {}; } LOG(INFO) << "Asked to change imported contacts to a list of " << contacts.size() << " contacts with random_id = " << random_id; if (random_id != 0) { // request has already been sent before if (need_clear_imported_contacts_) { need_clear_imported_contacts_ = false; all_imported_contacts_.clear(); if (G()->use_chat_info_database()) { G()->td_db()->get_sqlite_pmc()->erase("user_imported_contacts", Auto()); } reload_contacts(true); } CHECK(are_imported_contacts_changing_); are_imported_contacts_changing_ = false; auto unimported_contact_invites = std::move(unimported_contact_invites_); unimported_contact_invites_.clear(); auto imported_contact_user_ids = std::move(imported_contact_user_ids_); imported_contact_user_ids_.clear(); promise.set_value(Unit()); return {std::move(imported_contact_user_ids), std::move(unimported_contact_invites)}; } if (are_imported_contacts_changing_) { promise.set_error(Status::Error(400, "ChangeImportedContacts can be called only once at the same time")); return {}; } vector new_contacts_unique_id(contacts.size()); vector unique_new_contacts; unique_new_contacts.reserve(contacts.size()); std::unordered_map different_new_contacts; std::unordered_set> different_new_phone_numbers; size_t unique_size = 0; for (size_t i = 0; i < contacts.size(); i++) { auto it_success = different_new_contacts.emplace(std::move(contacts[i]), unique_size); new_contacts_unique_id[i] = it_success.first->second; if (it_success.second) { unique_new_contacts.push_back(it_success.first->first); different_new_phone_numbers.insert(unique_new_contacts.back().get_phone_number()); unique_size++; } } vector to_delete; vector to_delete_user_ids; for (auto &old_contact : all_imported_contacts_) { auto user_id = old_contact.get_user_id(); auto it = different_new_contacts.find(old_contact); if (it == different_new_contacts.end()) { auto phone_number = old_contact.get_phone_number(); if (different_new_phone_numbers.count(phone_number) == 0) { to_delete.push_back(std::move(phone_number)); if (user_id.is_valid()) { to_delete_user_ids.push_back(user_id); } } } else { unique_new_contacts[it->second].set_user_id(user_id); different_new_contacts.erase(it); } } std::pair, vector> to_add; for (auto &new_contact : different_new_contacts) { to_add.first.push_back(new_contact.second); to_add.second.push_back(new_contact.first); } if (to_add.first.empty() && to_delete.empty()) { for (size_t i = 0; i < contacts.size(); i++) { auto unique_id = new_contacts_unique_id[i]; contacts[i].set_user_id(unique_new_contacts[unique_id].get_user_id()); } promise.set_value(Unit()); return {transform(contacts, [&](const Contact &contact) { return contact.get_user_id(); }), vector(contacts.size())}; } are_imported_contacts_changing_ = true; random_id = 1; remove_contacts_by_phone_number( std::move(to_delete), std::move(to_delete_user_ids), PromiseCreator::lambda([new_contacts = std::move(unique_new_contacts), new_contacts_unique_id = std::move(new_contacts_unique_id), to_add = std::move(to_add), promise = std::move(promise)](Result<> result) mutable { if (result.is_ok()) { send_closure_later(G()->user_manager(), &UserManager::on_clear_imported_contacts, std::move(new_contacts), std::move(new_contacts_unique_id), std::move(to_add), std::move(promise)); } else { promise.set_error(result.move_as_error()); } })); return {}; } void UserManager::clear_imported_contacts(Promise &&promise) { LOG(INFO) << "Delete imported contacts"; if (saved_contact_count_ == 0) { promise.set_value(Unit()); return; } td_->create_handler(std::move(promise))->send(); } void UserManager::on_clear_imported_contacts(vector &&contacts, vector contacts_unique_id, std::pair, vector> &&to_add, Promise &&promise) { LOG(INFO) << "Add " << to_add.first.size() << " contacts"; next_all_imported_contacts_ = std::move(contacts); imported_contacts_unique_id_ = std::move(contacts_unique_id); imported_contacts_pos_ = std::move(to_add.first); do_import_contacts(std::move(to_add.second), 1, std::move(promise)); } void UserManager::on_update_contacts_reset() { /* UserId my_id = get_my_id(); users_.foreach([&](const UserId &user_id, unique_ptr &user) { User *u = user.get(); if (u->is_contact) { LOG(INFO) << "Drop contact with " << user_id; if (user_id != my_id) { CHECK(contacts_hints_.has_key(user_id.get())); } on_update_user_is_contact(u, user_id, false, false, false); CHECK(u->is_is_contact_changed); u->cache_version = 0; u->is_repaired = false; update_user(u, user_id); CHECK(!u->is_contact); if (user_id != my_id) { CHECK(!contacts_hints_.has_key(user_id.get())); } } }); */ saved_contact_count_ = 0; if (G()->use_chat_info_database()) { G()->td_db()->get_binlog_pmc()->set("saved_contact_count", "0"); G()->td_db()->get_sqlite_pmc()->erase("user_imported_contacts", Auto()); } if (!are_imported_contacts_loaded_) { if (load_imported_contacts_queries_.empty()) { CHECK(all_imported_contacts_.empty()); LOG(INFO) << "Imported contacts were never loaded, just clear them"; } else { LOG(INFO) << "Imported contacts are being loaded, clear them after they will be loaded"; need_clear_imported_contacts_ = true; } } else { if (!are_imported_contacts_changing_) { LOG(INFO) << "Imported contacts were loaded, but aren't changing now, just clear them"; all_imported_contacts_.clear(); } else { LOG(INFO) << "Imported contacts are changing now, clear them after they will be changed"; need_clear_imported_contacts_ = true; } } reload_contacts(true); } void UserManager::update_contacts_hints(const User *u, UserId user_id, bool from_database) { bool is_contact = is_user_contact(u, user_id, false); if (td_->auth_manager_->is_bot()) { LOG_IF(ERROR, is_contact) << "Bot has " << user_id << " in the contacts list"; return; } int64 key = user_id.get(); string old_value = contacts_hints_.key_to_string(key); string new_value = is_contact ? get_user_search_text(u) : string(); if (new_value != old_value) { if (is_contact) { contacts_hints_.add(key, new_value); } else { contacts_hints_.remove(key); } } if (G()->use_chat_info_database()) { // update contacts database if (!are_contacts_loaded_) { if (!from_database && load_contacts_queries_.empty() && is_contact && u->is_is_contact_changed) { search_contacts("", std::numeric_limits::max(), Auto()); } } else { if (old_value.empty() == is_contact) { save_contacts_to_database(); } } } } std::pair> UserManager::search_contacts(const string &query, int32 limit, Promise &&promise) { LOG(INFO) << "Search contacts with query = \"" << query << "\" and limit = " << limit; if (limit < 0) { promise.set_error(Status::Error(400, "Limit must be non-negative")); return {}; } if (!are_contacts_loaded_) { load_contacts(std::move(promise)); return {}; } reload_contacts(false); std::pair> result; if (query.empty()) { result = contacts_hints_.search_empty(limit); } else { result = contacts_hints_.search(query, limit); } vector user_ids; user_ids.reserve(result.second.size()); for (auto key : result.second) { user_ids.emplace_back(key); } promise.set_value(Unit()); return {narrow_cast(result.first), std::move(user_ids)}; } void UserManager::reload_contact_birthdates(bool force) { if (!G()->close_flag() && !td_->auth_manager_->is_bot() && !contact_birthdates_.is_being_synced_ && (contact_birthdates_.next_sync_time_ < Time::now() || force)) { contact_birthdates_.is_being_synced_ = true; td_->create_handler()->send(); } } void UserManager::on_get_contact_birthdates( telegram_api::object_ptr &&birthdays) { CHECK(contact_birthdates_.is_being_synced_); contact_birthdates_.is_being_synced_ = false; if (birthdays == nullptr) { contact_birthdates_.next_sync_time_ = Time::now() + Random::fast(120, 180); return; } contact_birthdates_.next_sync_time_ = Time::now() + Random::fast(86400 / 4, 86400 / 3); on_get_users(std::move(birthdays->users_), "on_get_contact_birthdates"); vector> users; for (auto &contact : birthdays->contacts_) { UserId user_id(contact->contact_id_); if (is_user_contact(user_id)) { Birthdate birthdate(std::move(contact->birthday_)); UserFull *user_full = get_user_full_force(user_id, "on_get_contact_birthdates"); if (user_full != nullptr && user_full->birthdate != birthdate) { user_full->birthdate = birthdate; user_full->is_changed = true; update_user_full(user_full, user_id, "on_get_contact_birthdates"); } if (!birthdate.is_empty()) { users.emplace_back(user_id, birthdate); } } } if (contact_birthdates_.users_ != users) { contact_birthdates_.users_ = std::move(users); send_closure(G()->td(), &Td::send_update, get_update_contact_close_birthdays()); } // there is no need to save them between restarts } vector UserManager::get_close_friends(Promise &&promise) { if (!are_contacts_loaded_) { load_contacts(std::move(promise)); return {}; } reload_contacts(false); auto result = contacts_hints_.search_empty(10000); vector user_ids; for (auto key : result.second) { UserId user_id(key); const User *u = get_user(user_id); if (u != nullptr && u->is_close_friend) { user_ids.push_back(user_id); } } promise.set_value(Unit()); return user_ids; } void UserManager::set_close_friends(vector user_ids, Promise &&promise) { for (auto &user_id : user_ids) { if (!have_user(user_id)) { return promise.set_error(Status::Error(400, "User not found")); } } td_->create_handler(std::move(promise))->send(std::move(user_ids)); } void UserManager::on_set_close_friends(const vector &user_ids, Promise &&promise) { FlatHashSet close_friend_user_ids; for (auto &user_id : user_ids) { CHECK(user_id.is_valid()); close_friend_user_ids.insert(user_id); } users_.foreach([&](const UserId &user_id, unique_ptr &user) { User *u = user.get(); if (u->is_contact && u->is_close_friend != (close_friend_user_ids.count(user_id) > 0)) { on_update_user_is_contact(u, user_id, u->is_contact, u->is_mutual_contact, !u->is_close_friend); update_user(u, user_id); } }); promise.set_value(Unit()); } UserId UserManager::search_user_by_phone_number(string phone_number, Promise &&promise) { clean_phone_number(phone_number); if (phone_number.empty()) { promise.set_error(Status::Error(200, "Phone number is invalid")); return UserId(); } auto it = resolved_phone_numbers_.find(phone_number); if (it != resolved_phone_numbers_.end()) { promise.set_value(Unit()); return it->second; } td_->create_handler(std::move(promise))->send(phone_number); return UserId(); } void UserManager::on_resolved_phone_number(const string &phone_number, UserId user_id) { if (!user_id.is_valid()) { resolved_phone_numbers_.emplace(phone_number, UserId()); // negative cache return; } auto it = resolved_phone_numbers_.find(phone_number); if (it != resolved_phone_numbers_.end()) { if (it->second != user_id) { LOG(WARNING) << "Resolve phone number \"" << phone_number << "\" to " << user_id << ", but have it in " << it->second; it->second = user_id; } return; } auto *u = get_user(user_id); if (u == nullptr) { LOG(ERROR) << "Resolve phone number \"" << phone_number << "\" to unknown " << user_id; } else if (!u->phone_number.empty()) { LOG(ERROR) << "Resolve phone number \"" << phone_number << "\" to " << user_id << " with phone number " << u->phone_number; } else { // the user's phone number can be hidden by privacy settings, despite the user can be found by the phone number } resolved_phone_numbers_[phone_number] = user_id; // always update cached value } const UserManager::UserFull *UserManager::get_user_full(UserId user_id) const { return users_full_.get_pointer(user_id); } UserManager::UserFull *UserManager::get_user_full(UserId user_id) { return users_full_.get_pointer(user_id); } UserManager::UserFull *UserManager::add_user_full(UserId user_id) { CHECK(user_id.is_valid()); auto &user_full_ptr = users_full_[user_id]; if (user_full_ptr == nullptr) { user_full_ptr = make_unique(); user_full_contact_require_premium_.erase(user_id); } return user_full_ptr.get(); } UserManager::UserFull *UserManager::get_user_full_force(UserId user_id, const char *source) { if (!have_user_force(user_id, source)) { return nullptr; } UserFull *user_full = get_user_full(user_id); if (user_full != nullptr) { return user_full; } if (!G()->use_chat_info_database()) { return nullptr; } if (!unavailable_user_fulls_.insert(user_id).second) { return nullptr; } LOG(INFO) << "Trying to load full " << user_id << " from database from " << source; on_load_user_full_from_database(user_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_user_full_database_key(user_id))); return get_user_full(user_id); } void UserManager::load_user_full(UserId user_id, bool force, Promise &&promise, const char *source) { auto u = get_user(user_id); if (u == nullptr) { return promise.set_error(Status::Error(400, "User not found")); } auto user_full = get_user_full_force(user_id, source); if (user_full == nullptr) { TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); return send_get_user_full_query(user_id, std::move(input_user), std::move(promise), source); } if (user_full->is_expired()) { auto input_user = get_input_user_force(user_id); if (td_->auth_manager_->is_bot() && !force) { return send_get_user_full_query(user_id, std::move(input_user), std::move(promise), "load expired user_full"); } send_get_user_full_query(user_id, std::move(input_user), Auto(), "load expired user_full"); } td_->story_manager_->on_view_dialog_active_stories({DialogId(user_id)}); promise.set_value(Unit()); } void UserManager::reload_user_full(UserId user_id, Promise &&promise, const char *source) { TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); send_get_user_full_query(user_id, std::move(input_user), std::move(promise), source); } void UserManager::send_get_user_full_query(UserId user_id, telegram_api::object_ptr &&input_user, Promise &&promise, const char *source) { LOG(INFO) << "Get full " << user_id << " from " << source; if (!user_id.is_valid()) { return promise.set_error(Status::Error(500, "Invalid user_id")); } auto send_query = PromiseCreator::lambda([td = td_, input_user = std::move(input_user)](Result> &&promise) mutable { if (promise.is_ok() && !G()->close_flag()) { td->create_handler(promise.move_as_ok())->send(std::move(input_user)); } }); get_user_full_queries_.add_query(user_id.get(), std::move(send_query), std::move(promise)); } void UserManager::on_get_user_full(telegram_api::object_ptr &&user) { LOG(INFO) << "Receive " << to_string(user); UserId user_id(user->id_); User *u = get_user(user_id); if (u == nullptr) { LOG(ERROR) << "Failed to find " << user_id; return; } apply_pending_user_photo(u, user_id); td_->messages_manager_->on_update_dialog_notify_settings(DialogId(user_id), std::move(user->notify_settings_), "on_get_user_full"); td_->messages_manager_->on_update_dialog_background(DialogId(user_id), std::move(user->wallpaper_)); td_->messages_manager_->on_update_dialog_theme_name(DialogId(user_id), std::move(user->theme_emoticon_)); td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(user_id), MessageId(ServerMessageId(user->pinned_msg_id_))); td_->messages_manager_->on_update_dialog_folder_id(DialogId(user_id), FolderId(user->folder_id_)); td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(user_id), user->has_scheduled_); td_->messages_manager_->on_update_dialog_message_ttl(DialogId(user_id), MessageTtl(user->ttl_period_)); td_->messages_manager_->on_update_dialog_is_blocked(DialogId(user_id), user->blocked_, user->blocked_my_stories_from_); td_->messages_manager_->on_update_dialog_is_translatable(DialogId(user_id), !user->translations_disabled_); send_closure_later(td_->story_manager_actor_, &StoryManager::on_get_dialog_stories, DialogId(user_id), std::move(user->stories_), Promise()); UserFull *user_full = add_user_full(user_id); user_full->expires_at = Time::now() + USER_FULL_EXPIRE_TIME; on_update_user_full_is_blocked(user_full, user_id, user->blocked_, user->blocked_my_stories_from_); on_update_user_full_common_chat_count(user_full, user_id, user->common_chats_count_); on_update_user_full_location(user_full, user_id, DialogLocation(td_, std::move(user->business_location_))); on_update_user_full_work_hours(user_full, user_id, BusinessWorkHours(std::move(user->business_work_hours_))); on_update_user_full_away_message(user_full, user_id, BusinessAwayMessage(std::move(user->business_away_message_))); on_update_user_full_greeting_message(user_full, user_id, BusinessGreetingMessage(std::move(user->business_greeting_message_))); on_update_user_full_intro(user_full, user_id, BusinessIntro(td_, std::move(user->business_intro_))); on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, user->settings_->need_contacts_exception_); on_update_user_full_wallpaper_overridden(user_full, user_id, user->wallpaper_overridden_); bool can_pin_messages = user->can_pin_message_; bool can_be_called = user->phone_calls_available_ && !user->phone_calls_private_; bool supports_video_calls = user->video_calls_available_ && !user->phone_calls_private_; bool has_private_calls = user->phone_calls_private_; bool voice_messages_forbidden = u->is_premium ? user->voice_messages_forbidden_ : false; auto premium_gift_options = get_premium_gift_options(std::move(user->premium_gifts_)); AdministratorRights group_administrator_rights(user->bot_group_admin_rights_, ChannelType::Megagroup); AdministratorRights broadcast_administrator_rights(user->bot_broadcast_admin_rights_, ChannelType::Broadcast); bool has_pinned_stories = user->stories_pinned_available_; auto birthdate = Birthdate(std::move(user->birthday_)); auto personal_channel_id = ChannelId(user->personal_channel_id_); if (user_full->can_be_called != can_be_called || user_full->supports_video_calls != supports_video_calls || user_full->has_private_calls != has_private_calls || user_full->group_administrator_rights != group_administrator_rights || user_full->broadcast_administrator_rights != broadcast_administrator_rights || user_full->premium_gift_options != premium_gift_options || user_full->voice_messages_forbidden != voice_messages_forbidden || user_full->can_pin_messages != can_pin_messages || user_full->has_pinned_stories != has_pinned_stories) { user_full->can_be_called = can_be_called; user_full->supports_video_calls = supports_video_calls; user_full->has_private_calls = has_private_calls; user_full->group_administrator_rights = group_administrator_rights; user_full->broadcast_administrator_rights = broadcast_administrator_rights; user_full->premium_gift_options = std::move(premium_gift_options); user_full->voice_messages_forbidden = voice_messages_forbidden; user_full->can_pin_messages = can_pin_messages; user_full->has_pinned_stories = has_pinned_stories; user_full->is_changed = true; } if (user_full->birthdate != birthdate) { user_full->birthdate = birthdate; user_full->is_changed = true; if (u->is_contact) { reload_contact_birthdates(true); } } if (user_full->private_forward_name != user->private_forward_name_) { if (user_full->private_forward_name.empty() != user->private_forward_name_.empty()) { user_full->is_changed = true; } user_full->private_forward_name = std::move(user->private_forward_name_); user_full->need_save_to_database = true; } if (user_full->read_dates_private != user->read_dates_private_ || user_full->contact_require_premium != user->contact_require_premium_) { user_full->read_dates_private = user->read_dates_private_; user_full->contact_require_premium = user->contact_require_premium_; user_full->need_save_to_database = true; } if (user_full->about != user->about_) { user_full->about = std::move(user->about_); user_full->is_changed = true; td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true); } string description; Photo description_photo; FileId description_animation_file_id; if (user->bot_info_ != nullptr && !td_->auth_manager_->is_bot()) { description = std::move(user->bot_info_->description_); description_photo = get_photo(td_, std::move(user->bot_info_->description_photo_), DialogId(user_id)); auto document = std::move(user->bot_info_->description_document_); if (document != nullptr) { int32 document_id = document->get_id(); if (document_id == telegram_api::document::ID) { auto parsed_document = td_->documents_manager_->on_get_document( move_tl_object_as(document), DialogId(user_id)); if (parsed_document.type == Document::Type::Animation) { description_animation_file_id = parsed_document.file_id; } else { LOG(ERROR) << "Receive non-animation document in bot description"; } } } on_update_user_full_commands(user_full, user_id, std::move(user->bot_info_->commands_)); on_update_user_full_menu_button(user_full, user_id, std::move(user->bot_info_->menu_button_)); } if (user_full->description != description) { user_full->description = std::move(description); user_full->is_changed = true; } if (user_full->description_photo != description_photo || user_full->description_animation_file_id != description_animation_file_id) { user_full->description_photo = std::move(description_photo); user_full->description_animation_file_id = description_animation_file_id; user_full->is_changed = true; } if (personal_channel_id != ChannelId() && !personal_channel_id.is_valid()) { LOG(ERROR) << "Receive personal " << personal_channel_id; personal_channel_id = ChannelId(); } if (user_full->personal_channel_id != personal_channel_id) { user_full->personal_channel_id = personal_channel_id; user_full->is_changed = true; } if (user_full->personal_channel_id != ChannelId()) { auto personal_message_id = MessageId(ServerMessageId(user->personal_channel_message_)); td_->messages_manager_->get_channel_difference_if_needed(DialogId(user_full->personal_channel_id), personal_message_id, "on_get_user_full personal chat"); } auto photo = get_photo(td_, std::move(user->profile_photo_), DialogId(user_id)); auto personal_photo = get_photo(td_, std::move(user->personal_photo_), DialogId(user_id)); auto fallback_photo = get_photo(td_, std::move(user->fallback_photo_), DialogId(user_id)); // do_update_user_photo should be a no-op if server sent consistent data const Photo *photo_ptr = nullptr; bool is_personal = false; if (!personal_photo.is_empty()) { photo_ptr = &personal_photo; is_personal = true; } else if (!photo.is_empty()) { photo_ptr = &photo; } else { photo_ptr = &fallback_photo; } bool is_photo_empty = photo_ptr->is_empty(); do_update_user_photo(u, user_id, as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, *photo_ptr, is_personal), false, "on_get_user_full"); if (photo != user_full->photo) { user_full->photo = std::move(photo); user_full->is_changed = true; } if (personal_photo != user_full->personal_photo) { user_full->personal_photo = std::move(personal_photo); user_full->is_changed = true; } if (fallback_photo != user_full->fallback_photo) { user_full->fallback_photo = std::move(fallback_photo); user_full->is_changed = true; } if (!user_full->photo.is_empty()) { register_user_photo(u, user_id, user_full->photo); } if (user_id == get_my_id() && !user_full->fallback_photo.is_empty()) { register_suggested_profile_photo(user_full->fallback_photo); } if (is_photo_empty) { drop_user_photos(user_id, true, "on_get_user_full"); } // User must be updated before UserFull if (u->is_changed) { LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << user_id; update_user(u, user_id); } user_full->is_update_user_full_sent = true; update_user_full(user_full, user_id, "on_get_user_full"); // update peer settings after UserFull is created and updated to not update twice need_phone_number_privacy_exception td_->messages_manager_->on_get_peer_settings(DialogId(user_id), std::move(user->settings_)); } FileSourceId UserManager::get_user_full_file_source_id(UserId user_id) { if (!user_id.is_valid()) { return FileSourceId(); } auto user_full = get_user_full(user_id); if (user_full != nullptr) { VLOG(file_references) << "Don't need to create file source for full " << user_id; // user full was already added, source ID was registered and shouldn't be needed return user_full->is_update_user_full_sent ? FileSourceId() : user_full->file_source_id; } auto &source_id = user_full_file_source_ids_[user_id]; if (!source_id.is_valid()) { source_id = td_->file_reference_manager_->create_user_full_file_source(user_id); } VLOG(file_references) << "Return " << source_id << " for full " << user_id; return source_id; } void UserManager::save_user_full(const UserFull *user_full, UserId user_id) { if (!G()->use_chat_info_database()) { return; } LOG(INFO) << "Trying to save to database full " << user_id; CHECK(user_full != nullptr); G()->td_db()->get_sqlite_pmc()->set(get_user_full_database_key(user_id), get_user_full_database_value(user_full), Auto()); } string UserManager::get_user_full_database_key(UserId user_id) { return PSTRING() << "usf" << user_id.get(); } string UserManager::get_user_full_database_value(const UserFull *user_full) { return log_event_store(*user_full).as_slice().str(); } void UserManager::on_load_user_full_from_database(UserId user_id, string value) { LOG(INFO) << "Successfully loaded full " << user_id << " of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); // return; if (get_user_full(user_id) != nullptr || value.empty()) { return; } UserFull *user_full = add_user_full(user_id); auto status = log_event_parse(*user_full, value); if (status.is_error()) { // can't happen unless database is broken LOG(ERROR) << "Repair broken full " << user_id << ' ' << format::as_hex_dump<4>(Slice(value)); // just clean all known data about the user and pretend that there was nothing in the database users_full_.erase(user_id); G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); return; } Dependencies dependencies; dependencies.add(user_id); if (user_full->business_info != nullptr) { user_full->business_info->add_dependencies(dependencies); } dependencies.add(user_full->personal_channel_id); if (!dependencies.resolve_force(td_, "on_load_user_full_from_database")) { users_full_.erase(user_id); G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); return; } if (user_full->need_phone_number_privacy_exception && is_user_contact(user_id)) { user_full->need_phone_number_privacy_exception = false; } User *u = get_user(user_id); CHECK(u != nullptr); drop_user_full_photos(user_full, user_id, u->photo.id, "on_load_user_full_from_database"); if (!user_full->photo.is_empty()) { register_user_photo(u, user_id, user_full->photo); } if (user_id == get_my_id() && !user_full->fallback_photo.is_empty()) { register_suggested_profile_photo(user_full->fallback_photo); } td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, false); user_full->is_update_user_full_sent = true; update_user_full(user_full, user_id, "on_load_user_full_from_database", true); if (is_user_deleted(u)) { drop_user_full(user_id); } else if (user_full->expires_at == 0.0) { reload_user_full(user_id, Auto(), "on_load_user_full_from_database"); } } int64 UserManager::get_user_full_profile_photo_id(const UserFull *user_full) { if (!user_full->personal_photo.is_empty()) { return user_full->personal_photo.id.get(); } if (!user_full->photo.is_empty()) { return user_full->photo.id.get(); } return user_full->fallback_photo.id.get(); } void UserManager::drop_user_full_photos(UserFull *user_full, UserId user_id, int64 expected_photo_id, const char *source) { if (user_full == nullptr) { return; } LOG(INFO) << "Expect full photo " << expected_photo_id << " from " << source; for (auto photo_ptr : {&user_full->personal_photo, &user_full->photo, &user_full->fallback_photo}) { if (photo_ptr->is_empty()) { continue; } if (expected_photo_id == 0) { // if profile photo is empty, we must drop the full photo *photo_ptr = Photo(); user_full->is_changed = true; } else if (expected_photo_id != photo_ptr->id.get()) { LOG(INFO) << "Drop full photo " << photo_ptr->id.get(); // if full profile photo is unknown, we must drop the full photo *photo_ptr = Photo(); user_full->is_changed = true; } else { // nothing to drop break; } } if (expected_photo_id != get_user_full_profile_photo_id(user_full)) { user_full->expires_at = 0.0; } if (user_full->is_update_user_full_sent) { update_user_full(user_full, user_id, "drop_user_full_photos"); } } void UserManager::drop_user_photos(UserId user_id, bool is_empty, const char *source) { LOG(INFO) << "Drop user photos to " << (is_empty ? "empty" : "unknown") << " from " << source; auto user_photos = user_photos_.get_pointer(user_id); if (user_photos != nullptr) { int32 new_count = is_empty ? 0 : -1; if (user_photos->count == new_count) { CHECK(user_photos->photos.empty()); CHECK(user_photos->offset == user_photos->count); } else { LOG(INFO) << "Drop photos of " << user_id << " to " << (is_empty ? "empty" : "unknown") << " from " << source; user_photos->photos.clear(); user_photos->count = new_count; user_photos->offset = user_photos->count; } } } void UserManager::drop_user_full(UserId user_id) { auto user_full = get_user_full_force(user_id, "drop_user_full"); drop_user_photos(user_id, false, "drop_user_full"); if (user_full == nullptr) { return; } user_full->expires_at = 0.0; user_full->photo = Photo(); user_full->personal_photo = Photo(); user_full->fallback_photo = Photo(); // user_full->is_blocked = false; // user_full->is_blocked_for_stories = false; user_full->can_be_called = false; user_full->supports_video_calls = false; user_full->has_private_calls = false; user_full->need_phone_number_privacy_exception = false; user_full->wallpaper_overridden = false; user_full->about = string(); user_full->description = string(); user_full->description_photo = Photo(); user_full->description_animation_file_id = FileId(); user_full->menu_button = nullptr; user_full->commands.clear(); user_full->common_chat_count = 0; user_full->personal_channel_id = ChannelId(); user_full->business_info = nullptr; user_full->private_forward_name.clear(); user_full->group_administrator_rights = {}; user_full->broadcast_administrator_rights = {}; user_full->premium_gift_options.clear(); user_full->voice_messages_forbidden = false; user_full->has_pinned_stories = false; user_full->read_dates_private = false; user_full->contact_require_premium = false; user_full->birthdate = {}; user_full->is_changed = true; update_user_full(user_full, user_id, "drop_user_full"); td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true); } bool UserManager::have_secret_chat(SecretChatId secret_chat_id) const { return secret_chats_.count(secret_chat_id) > 0; } const UserManager::SecretChat *UserManager::get_secret_chat(SecretChatId secret_chat_id) const { return secret_chats_.get_pointer(secret_chat_id); } UserManager::SecretChat *UserManager::get_secret_chat(SecretChatId secret_chat_id) { return secret_chats_.get_pointer(secret_chat_id); } UserManager::SecretChat *UserManager::add_secret_chat(SecretChatId secret_chat_id) { CHECK(secret_chat_id.is_valid()); auto &secret_chat_ptr = secret_chats_[secret_chat_id]; if (secret_chat_ptr == nullptr) { secret_chat_ptr = make_unique(); } return secret_chat_ptr.get(); } bool UserManager::have_secret_chat_force(SecretChatId secret_chat_id, const char *source) { return get_secret_chat_force(secret_chat_id, source) != nullptr; } UserManager::SecretChat *UserManager::get_secret_chat_force(SecretChatId secret_chat_id, const char *source) { if (!secret_chat_id.is_valid()) { return nullptr; } SecretChat *c = get_secret_chat(secret_chat_id); if (c != nullptr) { if (!have_user_force(c->user_id, source)) { LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id << " from " << source; } return c; } if (!G()->use_chat_info_database()) { return nullptr; } if (loaded_from_database_secret_chats_.count(secret_chat_id)) { return nullptr; } LOG(INFO) << "Trying to load " << secret_chat_id << " from database from " << source; on_load_secret_chat_from_database( secret_chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_secret_chat_database_key(secret_chat_id)), true); return get_secret_chat(secret_chat_id); } bool UserManager::get_secret_chat(SecretChatId secret_chat_id, bool force, Promise &&promise) { if (!secret_chat_id.is_valid()) { promise.set_error(Status::Error(400, "Invalid secret chat identifier")); return false; } if (!have_secret_chat(secret_chat_id)) { if (!force && G()->use_chat_info_database()) { send_closure_later(actor_id(this), &UserManager::load_secret_chat_from_database, nullptr, secret_chat_id, std::move(promise)); return false; } promise.set_error(Status::Error(400, "Secret chat not found")); return false; } promise.set_value(Unit()); return true; } void UserManager::save_secret_chat(SecretChat *c, SecretChatId secret_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 = SecretChatLogEvent(secret_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::SecretChatInfos, storer); } else { binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::SecretChatInfos, storer); } } save_secret_chat_to_database(c, secret_chat_id); return; } } string UserManager::get_secret_chat_database_key(SecretChatId secret_chat_id) { return PSTRING() << "sc" << secret_chat_id.get(); } string UserManager::get_secret_chat_database_value(const SecretChat *c) { return log_event_store(*c).as_slice().str(); } void UserManager::save_secret_chat_to_database(SecretChat *c, SecretChatId secret_chat_id) { CHECK(c != nullptr); if (c->is_being_saved) { return; } if (loaded_from_database_secret_chats_.count(secret_chat_id)) { save_secret_chat_to_database_impl(c, secret_chat_id, get_secret_chat_database_value(c)); return; } if (load_secret_chat_from_database_queries_.count(secret_chat_id) != 0) { return; } load_secret_chat_from_database_impl(secret_chat_id, Auto()); } void UserManager::save_secret_chat_to_database_impl(SecretChat *c, SecretChatId secret_chat_id, string value) { CHECK(c != nullptr); CHECK(load_secret_chat_from_database_queries_.count(secret_chat_id) == 0); CHECK(!c->is_being_saved); c->is_being_saved = true; c->is_saved = true; LOG(INFO) << "Trying to save to database " << secret_chat_id; G()->td_db()->get_sqlite_pmc()->set(get_secret_chat_database_key(secret_chat_id), std::move(value), PromiseCreator::lambda([secret_chat_id](Result<> result) { send_closure(G()->user_manager(), &UserManager::on_save_secret_chat_to_database, secret_chat_id, result.is_ok()); })); } void UserManager::on_save_secret_chat_to_database(SecretChatId secret_chat_id, bool success) { if (G()->close_flag()) { return; } SecretChat *c = get_secret_chat(secret_chat_id); CHECK(c != nullptr); CHECK(c->is_being_saved); CHECK(load_secret_chat_from_database_queries_.count(secret_chat_id) == 0); c->is_being_saved = false; if (!success) { LOG(ERROR) << "Failed to save " << secret_chat_id << " to database"; c->is_saved = false; } else { LOG(INFO) << "Successfully saved " << secret_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_secret_chat(c, secret_chat_id, c->log_event_id != 0); } } void UserManager::load_secret_chat_from_database(SecretChat *c, SecretChatId secret_chat_id, Promise promise) { if (loaded_from_database_secret_chats_.count(secret_chat_id)) { promise.set_value(Unit()); return; } CHECK(c == nullptr || !c->is_being_saved); load_secret_chat_from_database_impl(secret_chat_id, std::move(promise)); } void UserManager::load_secret_chat_from_database_impl(SecretChatId secret_chat_id, Promise promise) { LOG(INFO) << "Load " << secret_chat_id << " from database"; auto &load_secret_chat_queries = load_secret_chat_from_database_queries_[secret_chat_id]; load_secret_chat_queries.push_back(std::move(promise)); if (load_secret_chat_queries.size() == 1u) { G()->td_db()->get_sqlite_pmc()->get( get_secret_chat_database_key(secret_chat_id), PromiseCreator::lambda([secret_chat_id](string value) { send_closure(G()->user_manager(), &UserManager::on_load_secret_chat_from_database, secret_chat_id, std::move(value), false); })); } } void UserManager::on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value, bool force) { if (G()->close_flag() && !force) { // the secret chat is in Binlog and will be saved after restart return; } CHECK(secret_chat_id.is_valid()); if (!loaded_from_database_secret_chats_.insert(secret_chat_id).second) { return; } auto it = load_secret_chat_from_database_queries_.find(secret_chat_id); vector> promises; if (it != load_secret_chat_from_database_queries_.end()) { promises = std::move(it->second); CHECK(!promises.empty()); load_secret_chat_from_database_queries_.erase(it); } LOG(INFO) << "Successfully loaded " << secret_chat_id << " of size " << value.size() << " from database"; // G()->td_db()->get_sqlite_pmc()->erase(get_secret_chat_database_key(secret_chat_id), Auto()); // return; SecretChat *c = get_secret_chat(secret_chat_id); if (c == nullptr) { if (!value.empty()) { c = add_secret_chat(secret_chat_id); if (log_event_parse(*c, value).is_error()) { LOG(ERROR) << "Failed to load " << secret_chat_id << " from database"; secret_chats_.erase(secret_chat_id); } else { c->is_saved = true; update_secret_chat(c, secret_chat_id, true, true); } } } else { CHECK(!c->is_saved); // secret chat can't be saved before load completes CHECK(!c->is_being_saved); auto new_value = get_secret_chat_database_value(c); if (value != new_value) { save_secret_chat_to_database_impl(c, secret_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; } } // TODO load users asynchronously if (c != nullptr && !have_user_force(c->user_id, "on_load_secret_chat_from_database")) { LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id; } set_promises(promises); } void UserManager::create_new_secret_chat(UserId user_id, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); if (input_user->get_id() != telegram_api::inputUser::ID) { return promise.set_error(Status::Error(400, "Can't create secret chat with the user")); } auto user = static_cast(input_user.get()); send_closure( G()->secret_chats_manager(), &SecretChatsManager::create_chat, UserId(user->user_id_), user->access_hash_, PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result r_secret_chat_id) mutable { if (r_secret_chat_id.is_error()) { return promise.set_error(r_secret_chat_id.move_as_error()); } send_closure(actor_id, &UserManager::on_create_new_secret_chat, r_secret_chat_id.ok(), std::move(promise)); })); } void UserManager::on_create_new_secret_chat(SecretChatId secret_chat_id, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(secret_chat_id.is_valid()); DialogId dialog_id(secret_chat_id); td_->dialog_manager_->force_create_dialog(dialog_id, "on_create_new_secret_chat"); promise.set_value(td_->messages_manager_->get_chat_object(dialog_id, "on_create_new_secret_chat")); } void UserManager::update_user(User *u, UserId user_id, bool from_binlog, bool from_database) { CHECK(u != nullptr); if (u->is_being_updated) { LOG(ERROR) << "Detected recursive update of " << user_id; } u->is_being_updated = true; SCOPE_EXIT { u->is_being_updated = false; }; if (user_id == get_my_id()) { if (td_->option_manager_->get_option_boolean("is_premium") != u->is_premium) { td_->option_manager_->set_option_boolean("is_premium", u->is_premium); send_closure(td_->config_manager_, &ConfigManager::request_config, true); if (!td_->auth_manager_->is_bot()) { td_->reaction_manager_->reload_reaction_list(ReactionListType::Top, "update_user is_premium"); td_->messages_manager_->update_is_translatable(u->is_premium); } } } if (u->is_name_changed || u->is_username_changed || u->is_is_contact_changed) { update_contacts_hints(u, user_id, from_database); u->is_username_changed = false; } if (u->is_is_contact_changed) { td_->messages_manager_->on_dialog_user_is_contact_updated(DialogId(user_id), u->is_contact); send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, DialogId(user_id), "is_contact"); if (is_user_contact(u, user_id, false)) { auto user_full = get_user_full(user_id); if (user_full != nullptr && user_full->need_phone_number_privacy_exception) { on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, false); update_user_full(user_full, user_id, "update_user"); } } u->is_is_contact_changed = false; } if (u->is_is_mutual_contact_changed) { if (!from_database && u->is_update_user_sent) { send_closure_later(td_->story_manager_actor_, &StoryManager::reload_dialog_expiring_stories, DialogId(user_id)); } u->is_is_mutual_contact_changed = false; } if (u->is_is_deleted_changed) { td_->messages_manager_->on_dialog_user_is_deleted_updated(DialogId(user_id), u->is_deleted); if (u->is_deleted) { auto user_full = get_user_full(user_id); // must not load user_full from database before sending updateUser if (user_full != nullptr) { u->is_full_info_changed = false; drop_user_full(user_id); } } u->is_is_deleted_changed = false; } if (u->is_is_premium_changed) { send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, DialogId(user_id), "is_premium"); u->is_is_premium_changed = false; } if (u->is_name_changed) { auto messages_manager = td_->messages_manager_.get(); messages_manager->on_dialog_title_updated(DialogId(user_id)); for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { messages_manager->on_dialog_title_updated(DialogId(secret_chat_id)); }); u->is_name_changed = false; } if (u->is_photo_changed) { auto messages_manager = td_->messages_manager_.get(); messages_manager->on_dialog_photo_updated(DialogId(user_id)); for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { messages_manager->on_dialog_photo_updated(DialogId(secret_chat_id)); }); u->is_photo_changed = false; } if (u->is_accent_color_changed) { auto messages_manager = td_->messages_manager_.get(); messages_manager->on_dialog_accent_colors_updated(DialogId(user_id)); for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { messages_manager->on_dialog_accent_colors_updated(DialogId(secret_chat_id)); }); u->is_accent_color_changed = false; } if (u->is_phone_number_changed) { if (!u->phone_number.empty() && !td_->auth_manager_->is_bot()) { resolved_phone_numbers_[u->phone_number] = user_id; } u->is_phone_number_changed = false; } auto unix_time = G()->unix_time(); if (u->is_status_changed && user_id != get_my_id()) { auto left_time = get_user_was_online(u, user_id, unix_time) - G()->server_time(); if (left_time >= 0 && left_time < 30 * 86400) { left_time += 2.0; // to guarantee expiration LOG(DEBUG) << "Set online timeout for " << user_id << " in " << left_time << " seconds"; user_online_timeout_.set_timeout_in(user_id.get(), left_time); } else { LOG(DEBUG) << "Cancel online timeout for " << user_id; user_online_timeout_.cancel_timeout(user_id.get()); } } if (u->is_stories_hidden_changed) { send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, DialogId(user_id), "stories_hidden"); u->is_stories_hidden_changed = false; } if (!td_->auth_manager_->is_bot()) { if (u->restriction_reasons.empty()) { restricted_user_ids_.erase(user_id); } else { restricted_user_ids_.insert(user_id); } } auto effective_emoji_status = u->emoji_status.get_effective_emoji_status(u->is_premium, unix_time); if (effective_emoji_status != u->last_sent_emoji_status) { if (!u->last_sent_emoji_status.is_empty()) { user_emoji_status_timeout_.cancel_timeout(user_id.get()); } u->last_sent_emoji_status = effective_emoji_status; if (!u->last_sent_emoji_status.is_empty()) { auto until_date = u->last_sent_emoji_status.get_until_date(); auto left_time = until_date - unix_time; if (left_time >= 0 && left_time < 30 * 86400) { user_emoji_status_timeout_.set_timeout_in(user_id.get(), left_time); } } u->is_changed = true; auto messages_manager = td_->messages_manager_.get(); messages_manager->on_dialog_emoji_status_updated(DialogId(user_id)); for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { messages_manager->on_dialog_emoji_status_updated(DialogId(secret_chat_id)); }); u->is_emoji_status_changed = false; } else if (u->is_emoji_status_changed) { LOG(DEBUG) << "Emoji status for " << user_id << " has changed"; u->need_save_to_database = true; u->is_emoji_status_changed = false; } if (u->is_deleted) { td_->inline_queries_manager_->remove_recent_inline_bot(user_id, Promise<>()); } if (from_binlog || from_database) { td_->dialog_manager_->on_dialog_usernames_received(DialogId(user_id), u->usernames, true); } LOG(DEBUG) << "Update " << user_id << ": need_save_to_database = " << u->need_save_to_database << ", is_changed = " << u->is_changed << ", is_status_changed = " << u->is_status_changed << ", from_binlog = " << from_binlog << ", from_database = " << from_database; u->need_save_to_database |= u->is_changed; if (u->need_save_to_database) { if (!from_database) { u->is_saved = false; } u->need_save_to_database = false; } if (u->is_changed) { send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); u->is_changed = false; u->is_status_changed = false; u->is_update_user_sent = true; } if (u->is_status_changed) { if (!from_database) { u->is_status_saved = false; } CHECK(u->is_update_user_sent); send_closure( G()->td(), &Td::send_update, td_api::make_object(user_id.get(), get_user_status_object(user_id, u, unix_time))); u->is_status_changed = false; } if (u->is_online_status_changed) { td_->dialog_participant_manager_->update_user_online_member_count(user_id); u->is_online_status_changed = false; } if (!from_database) { save_user(u, user_id, from_binlog); } if (u->cache_version != User::CACHE_VERSION && !u->is_repaired && have_input_peer_user(u, user_id, AccessRights::Read) && !G()->close_flag()) { u->is_repaired = true; LOG(INFO) << "Repairing cache of " << user_id; reload_user(user_id, Promise(), "update_user"); } if (u->is_full_info_changed) { u->is_full_info_changed = false; auto user_full = get_user_full(user_id); if (user_full != nullptr) { user_full->need_send_update = true; update_user_full(user_full, user_id, "update_user is_full_info_changed"); reload_user_full(user_id, Promise(), "update_user"); } } } void UserManager::update_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog, bool from_database) { CHECK(c != nullptr); if (c->is_being_updated) { LOG(ERROR) << "Detected recursive update of " << secret_chat_id; } c->is_being_updated = true; SCOPE_EXIT { c->is_being_updated = false; }; LOG(DEBUG) << "Update " << secret_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; DialogId dialog_id(secret_chat_id); send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update secret chat", true, true); if (c->is_state_changed) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_secret_chat_state, secret_chat_id, c->state); c->is_state_changed = false; } if (c->is_ttl_changed) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_message_ttl, DialogId(secret_chat_id), MessageTtl(c->ttl)); c->is_ttl_changed = false; } } if (c->is_changed) { send_closure(G()->td(), &Td::send_update, get_update_secret_chat_object(secret_chat_id, c)); c->is_changed = false; } if (!from_database) { save_secret_chat(c, secret_chat_id, from_binlog); } } void UserManager::update_user_full(UserFull *user_full, UserId user_id, const char *source, bool from_database) { CHECK(user_full != nullptr); if (user_full->is_being_updated) { LOG(ERROR) << "Detected recursive update of full " << user_id << " from " << source; } user_full->is_being_updated = true; SCOPE_EXIT { user_full->is_being_updated = false; }; unavailable_user_fulls_.erase(user_id); // don't needed anymore if (user_full->is_common_chat_count_changed) { td_->common_dialog_manager_->drop_common_dialogs_cache(user_id); user_full->is_common_chat_count_changed = false; } if (true) { vector file_ids; if (!user_full->personal_photo.is_empty()) { append(file_ids, photo_get_file_ids(user_full->personal_photo)); } if (!user_full->fallback_photo.is_empty()) { append(file_ids, photo_get_file_ids(user_full->fallback_photo)); } if (!user_full->description_photo.is_empty()) { append(file_ids, photo_get_file_ids(user_full->description_photo)); } if (user_full->description_animation_file_id.is_valid()) { file_ids.push_back(user_full->description_animation_file_id); } if (user_full->business_info != nullptr) { append(file_ids, user_full->business_info->get_file_ids(td_)); } if (user_full->registered_file_ids != file_ids) { auto &file_source_id = user_full->file_source_id; if (!file_source_id.is_valid()) { file_source_id = user_full_file_source_ids_.get(user_id); if (file_source_id.is_valid()) { VLOG(file_references) << "Move " << file_source_id << " inside of " << user_id; user_full_file_source_ids_.erase(user_id); } else { VLOG(file_references) << "Need to create new file source for full " << user_id; file_source_id = td_->file_reference_manager_->create_user_full_file_source(user_id); } } td_->file_manager_->change_files_source(file_source_id, user_full->registered_file_ids, file_ids); user_full->registered_file_ids = std::move(file_ids); } } user_full->need_send_update |= user_full->is_changed; user_full->need_save_to_database |= user_full->is_changed; user_full->is_changed = false; if (user_full->need_send_update || user_full->need_save_to_database) { LOG(INFO) << "Update full " << user_id << " from " << source; } if (user_full->need_send_update) { { auto u = get_user(user_id); CHECK(u == nullptr || u->is_update_user_sent); } if (!user_full->is_update_user_full_sent) { LOG(ERROR) << "Send partial updateUserFullInfo for " << user_id << " from " << source; user_full->is_update_user_full_sent = true; } send_closure(G()->td(), &Td::send_update, td_api::make_object(get_user_id_object(user_id, "updateUserFullInfo"), get_user_full_info_object(user_id, user_full))); user_full->need_send_update = false; if (user_id == get_my_id() && !user_full->birthdate.is_empty() && !td_->auth_manager_->is_bot()) { dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::BirthdaySetup}, Promise()); } } if (user_full->need_save_to_database) { if (!from_database) { save_user_full(user_full, user_id); } user_full->need_save_to_database = false; } } td_api::object_ptr UserManager::get_user_status_object(UserId user_id, const User *u, int32 unix_time) const { if (u->is_bot) { return td_api::make_object(std::numeric_limits::max()); } int32 was_online = get_user_was_online(u, user_id, unix_time); switch (was_online) { case -6: case -3: return td_api::make_object(was_online == -6); case -5: case -2: return td_api::make_object(was_online == -5); case -4: case -1: return td_api::make_object(was_online == -4); case 0: return td_api::make_object(); default: { int32 time = G()->unix_time(); if (was_online > time) { return td_api::make_object(was_online); } else { return td_api::make_object(was_online); } } } } bool UserManager::get_user_has_unread_stories(const User *u) { CHECK(u != nullptr); return u->max_active_story_id.get() > u->max_read_story_id.get(); } td_api::object_ptr UserManager::get_update_user_object(UserId user_id, const User *u) const { if (u == nullptr) { return get_update_unknown_user_object(user_id); } return td_api::make_object(get_user_object(user_id, u)); } td_api::object_ptr UserManager::get_update_unknown_user_object(UserId user_id) const { auto have_access = user_id == get_my_id() || user_messages_.count(user_id) != 0; return td_api::make_object(td_api::make_object( user_id.get(), "", "", nullptr, "", td_api::make_object(), nullptr, td_->theme_manager_->get_accent_color_id_object(AccentColorId(user_id)), 0, -1, 0, nullptr, false, false, false, false, false, false, "", false, false, false, false, false, have_access, td_api::make_object(), "", false)); } int64 UserManager::get_user_id_object(UserId user_id, const char *source) const { if (user_id.is_valid() && get_user(user_id) == nullptr && unknown_users_.count(user_id) == 0) { LOG(ERROR) << "Have no information about " << user_id << " from " << source; unknown_users_.insert(user_id); send_closure(G()->td(), &Td::send_update, get_update_unknown_user_object(user_id)); } return user_id.get(); } td_api::object_ptr UserManager::get_user_object(UserId user_id) const { return get_user_object(user_id, get_user(user_id)); } td_api::object_ptr UserManager::get_user_object(UserId user_id, const User *u) const { if (u == nullptr) { return nullptr; } td_api::object_ptr type; if (u->is_deleted) { type = td_api::make_object(); } else if (u->is_bot) { type = td_api::make_object( u->can_be_edited_bot, u->can_join_groups, u->can_read_all_group_messages, u->is_inline_bot, u->inline_query_placeholder, u->need_location_bot, u->is_business_bot, u->can_be_added_to_attach_menu); } else { type = td_api::make_object(); } auto emoji_status = u->last_sent_emoji_status.get_emoji_status_object(); auto have_access = user_id == get_my_id() || have_input_peer_user(u, user_id, AccessRights::Know); auto accent_color_id = u->accent_color_id.is_valid() ? u->accent_color_id : AccentColorId(user_id); auto restricts_new_chats = u->contact_require_premium && !u->is_mutual_contact; return td_api::make_object( user_id.get(), u->first_name, u->last_name, u->usernames.get_usernames_object(), u->phone_number, get_user_status_object(user_id, u, G()->unix_time()), get_profile_photo_object(td_->file_manager_.get(), u->photo), td_->theme_manager_->get_accent_color_id_object(accent_color_id, AccentColorId(user_id)), u->background_custom_emoji_id.get(), td_->theme_manager_->get_profile_accent_color_id_object(u->profile_accent_color_id), u->profile_background_custom_emoji_id.get(), std::move(emoji_status), u->is_contact, u->is_mutual_contact, u->is_close_friend, u->is_verified, u->is_premium, u->is_support, get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, u->max_active_story_id.is_valid(), get_user_has_unread_stories(u), restricts_new_chats, have_access, std::move(type), u->language_code, u->attach_menu_enabled); } vector UserManager::get_user_ids_object(const vector &user_ids, const char *source) const { return transform(user_ids, [this, source](UserId user_id) { return get_user_id_object(user_id, source); }); } td_api::object_ptr UserManager::get_users_object(int32 total_count, const vector &user_ids) const { if (total_count == -1) { total_count = narrow_cast(user_ids.size()); } return td_api::make_object(total_count, get_user_ids_object(user_ids, "get_users_object")); } td_api::object_ptr UserManager::get_user_full_info_object(UserId user_id) const { return get_user_full_info_object(user_id, get_user_full(user_id)); } td_api::object_ptr UserManager::get_user_full_info_object(UserId user_id, const UserFull *user_full) const { CHECK(user_full != nullptr); td_api::object_ptr bot_info; const User *u = get_user(user_id); bool is_bot = is_user_bot(u); bool is_premium = is_user_premium(u); td_api::object_ptr bio_object; if (is_bot) { auto menu_button = get_bot_menu_button_object(td_, user_full->menu_button.get()); auto commands = transform(user_full->commands, [](const auto &command) { return command.get_bot_command_object(); }); bot_info = td_api::make_object( user_full->about, user_full->description, get_photo_object(td_->file_manager_.get(), user_full->description_photo), td_->animations_manager_->get_animation_object(user_full->description_animation_file_id), std::move(menu_button), std::move(commands), user_full->group_administrator_rights == AdministratorRights() ? nullptr : user_full->group_administrator_rights.get_chat_administrator_rights_object(), user_full->broadcast_administrator_rights == AdministratorRights() ? nullptr : user_full->broadcast_administrator_rights.get_chat_administrator_rights_object(), nullptr, nullptr, nullptr, nullptr); if (u != nullptr && u->can_be_edited_bot && u->usernames.has_editable_username()) { auto bot_username = u->usernames.get_editable_username(); bot_info->edit_commands_link_ = td_api::make_object( "botfather", PSTRING() << bot_username << "-commands", true); bot_info->edit_description_link_ = td_api::make_object( "botfather", PSTRING() << bot_username << "-intro", true); bot_info->edit_description_media_link_ = td_api::make_object( "botfather", PSTRING() << bot_username << "-intropic", true); bot_info->edit_settings_link_ = td_api::make_object("botfather", bot_username, true); } } else { FormattedText bio; bio.text = user_full->about; bio.entities = find_entities(bio.text, true, true); if (!is_premium) { td::remove_if(bio.entities, [&](const MessageEntity &entity) { if (entity.type == MessageEntity::Type::EmailAddress) { return true; } if (entity.type == MessageEntity::Type::Url && !LinkManager::is_internal_link(utf8_utf16_substr(bio.text, entity.offset, entity.length))) { return true; } return false; }); } bio_object = get_formatted_text_object(bio, true, 0); } auto voice_messages_forbidden = is_premium ? user_full->voice_messages_forbidden : false; auto block_list_id = BlockListId(user_full->is_blocked, user_full->is_blocked_for_stories); auto business_info = is_premium && user_full->business_info != nullptr ? user_full->business_info->get_business_info_object(td_) : nullptr; int64 personal_chat_id = 0; if (user_full->personal_channel_id.is_valid()) { DialogId dialog_id(user_full->personal_channel_id); td_->dialog_manager_->force_create_dialog(dialog_id, "get_user_full_info_object", true); personal_chat_id = td_->dialog_manager_->get_chat_id_object(dialog_id, "get_user_full_info_object"); } return td_api::make_object( get_chat_photo_object(td_->file_manager_.get(), user_full->personal_photo), get_chat_photo_object(td_->file_manager_.get(), user_full->photo), get_chat_photo_object(td_->file_manager_.get(), user_full->fallback_photo), block_list_id.get_block_list_object(), user_full->can_be_called, user_full->supports_video_calls, user_full->has_private_calls, !user_full->private_forward_name.empty(), voice_messages_forbidden, user_full->has_pinned_stories, user_full->need_phone_number_privacy_exception, user_full->wallpaper_overridden, std::move(bio_object), user_full->birthdate.get_birthdate_object(), personal_chat_id, get_premium_payment_options_object(user_full->premium_gift_options), user_full->common_chat_count, std::move(business_info), std::move(bot_info)); } td_api::object_ptr UserManager::get_update_contact_close_birthdays() const { return td_api::make_object( transform(contact_birthdates_.users_, [this](const std::pair &user) { return td_api::make_object(get_user_id_object(user.first, "closeBirthdayUser"), user.second.get_birthdate_object()); })); } td_api::object_ptr UserManager::get_secret_chat_state_object(SecretChatState state) { switch (state) { case SecretChatState::Waiting: return td_api::make_object(); case SecretChatState::Active: return td_api::make_object(); case SecretChatState::Closed: case SecretChatState::Unknown: return td_api::make_object(); default: UNREACHABLE(); return nullptr; } } td_api::object_ptr UserManager::get_update_secret_chat_object(SecretChatId secret_chat_id, const SecretChat *secret_chat) { if (secret_chat == nullptr) { return get_update_unknown_secret_chat_object(secret_chat_id); } return td_api::make_object(get_secret_chat_object(secret_chat_id, secret_chat)); } td_api::object_ptr UserManager::get_update_unknown_secret_chat_object( SecretChatId secret_chat_id) { return td_api::make_object(td_api::make_object( secret_chat_id.get(), 0, get_secret_chat_state_object(SecretChatState::Unknown), false, string(), 0)); } int32 UserManager::get_secret_chat_id_object(SecretChatId secret_chat_id, const char *source) const { if (secret_chat_id.is_valid() && get_secret_chat(secret_chat_id) == nullptr && unknown_secret_chats_.count(secret_chat_id) == 0) { LOG(ERROR) << "Have no information about " << secret_chat_id << " from " << source; unknown_secret_chats_.insert(secret_chat_id); send_closure(G()->td(), &Td::send_update, get_update_unknown_secret_chat_object(secret_chat_id)); } return secret_chat_id.get(); } td_api::object_ptr UserManager::get_secret_chat_object(SecretChatId secret_chat_id) { return get_secret_chat_object(secret_chat_id, get_secret_chat(secret_chat_id)); } td_api::object_ptr UserManager::get_secret_chat_object(SecretChatId secret_chat_id, const SecretChat *secret_chat) { if (secret_chat == nullptr) { return nullptr; } get_user_force(secret_chat->user_id, "get_secret_chat_object"); return get_secret_chat_object_const(secret_chat_id, secret_chat); } td_api::object_ptr UserManager::get_secret_chat_object_const(SecretChatId secret_chat_id, const SecretChat *secret_chat) const { return td_api::make_object(secret_chat_id.get(), get_user_id_object(secret_chat->user_id, "secretChat"), get_secret_chat_state_object(secret_chat->state), secret_chat->is_outbound, secret_chat->key_hash, secret_chat->layer); } void UserManager::get_current_state(vector> &updates) const { for (auto user_id : unknown_users_) { if (!have_min_user(user_id)) { updates.push_back(get_update_unknown_user_object(user_id)); } } for (auto secret_chat_id : unknown_secret_chats_) { if (!have_secret_chat(secret_chat_id)) { updates.push_back(get_update_unknown_secret_chat_object(secret_chat_id)); } } users_.foreach([&](const UserId &user_id, const unique_ptr &user) { updates.push_back(get_update_user_object(user_id, user.get())); }); // secret chat objects contain user_id, so they must be sent after users secret_chats_.foreach([&](const SecretChatId &secret_chat_id, const unique_ptr &secret_chat) { updates.push_back( td_api::make_object(get_secret_chat_object_const(secret_chat_id, secret_chat.get()))); }); users_full_.foreach([&](const UserId &user_id, const unique_ptr &user_full) { updates.push_back(td_api::make_object( user_id.get(), get_user_full_info_object(user_id, user_full.get()))); }); if (!contact_birthdates_.users_.empty()) { updates.push_back(get_update_contact_close_birthdays()); } } } // namespace td