From 897032e0fd3b721a18ea722a98d0e0396e8e4e07 Mon Sep 17 00:00:00 2001 From: levlam Date: Wed, 12 Oct 2022 21:04:18 +0300 Subject: [PATCH] Add support for multiple usernames. --- CMakeLists.txt | 2 + td/generate/scheme/td_api.tl | 29 ++-- td/telegram/ContactsManager.cpp | 172 +++++++++++++----------- td/telegram/ContactsManager.h | 27 ++-- td/telegram/InlineQueriesManager.cpp | 2 +- td/telegram/MessagesManager.cpp | 37 ++--- td/telegram/MessagesManager.h | 5 +- td/telegram/RecentDialogList.cpp | 4 +- td/telegram/SponsoredMessageManager.cpp | 2 +- td/telegram/UpdatesManager.cpp | 4 +- td/telegram/Usernames.cpp | 109 +++++++++++++++ td/telegram/Usernames.h | 110 +++++++++++++++ td/telegram/Venue.h | 1 - td/telegram/WebPageBlock.cpp | 2 +- td/telegram/cli.cpp | 16 +-- test/tdclient.cpp | 3 +- 16 files changed, 391 insertions(+), 134 deletions(-) create mode 100644 td/telegram/Usernames.cpp create mode 100644 td/telegram/Usernames.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d98676c8..943ee751a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -452,6 +452,7 @@ set(TDLIB_SOURCE td/telegram/TopDialogCategory.cpp td/telegram/TopDialogManager.cpp td/telegram/UpdatesManager.cpp + td/telegram/Usernames.cpp td/telegram/Venue.cpp td/telegram/VideoNotesManager.cpp td/telegram/VideosManager.cpp @@ -714,6 +715,7 @@ set(TDLIB_SOURCE td/telegram/UniqueId.h td/telegram/UpdatesManager.h td/telegram/UserId.h + td/telegram/Usernames.h td/telegram/Venue.h td/telegram/Version.h td/telegram/VideoNotesManager.h diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index efd4c04e4..958ef4ada 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -486,11 +486,18 @@ emojiStatus custom_emoji_id:int64 = EmojiStatus; emojiStatuses emoji_statuses:vector = EmojiStatuses; +//@description Describes usernames assigned to a user, a supergroup, or a channel +//@active_usernames List of active usernames; the first one must be shown as the primary username. The order of active usernames can be changed with reorderActiveUsernames or reorderSupergroupActiveUsernames +//@disabled_usernames List of currently disabled usernames; the username can be activated or disabled with toggleUsernameIsActive/toggleSupergroupUsernameIsActive +//@editable_username The active username, which can be changed with setUsername/setSupergroupUsername; if there is at least one active username, then the username can't be empty +usernames active_usernames:vector disabled_usernames:vector editable_username:string = Usernames; + + //@description Represents a user //@id User identifier //@first_name First name of the user //@last_name Last name of the user -//@username Username of the user +//@usernames Usernames of the user; may be null //@phone_number Phone number of the user //@status Current online status of the user //@profile_photo Profile photo of the user; may be null @@ -507,7 +514,7 @@ emojiStatuses emoji_statuses:vector = EmojiStatuses; //@type Type of the user //@language_code IETF language tag of the user's language; only available to bots //@added_to_attachment_menu True, if the user added the current bot to attachment menu; only available to bots -user id:int53 first_name:string last_name:string username:string phone_number:string status:UserStatus profile_photo:profilePhoto emoji_status:emojiStatus is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User; +user id:int53 first_name:string last_name:string usernames:usernames phone_number:string status:UserStatus profile_photo:profilePhoto emoji_status:emojiStatus is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User; //@description Contains information about a bot @@ -716,7 +723,7 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 memb //@description Represents a supergroup or channel with zero or more members (subscribers in the case of channels). From the point of view of the system, a channel is a special kind of a supergroup: only administrators can post and see the list of members, and posts from all administrators use the name and photo of the channel instead of individual names and profile photos. Unlike supergroups, channels can have an unlimited number of subscribers //@id Supergroup or channel identifier -//@username Username of the supergroup or channel; empty for private supergroups or channels +//@usernames Usernames of the supergroup or channel; may be null //@date Point in time (Unix timestamp) when the current user joined, or the point in time when the supergroup or channel was created, in case the user is not a member //@status Status of the current user in the supergroup or channel; custom title will always be empty //@member_count Number of members in the supergroup or channel; 0 if unknown. Currently, it is guaranteed to be known only if the supergroup or channel was received through searchPublicChats, searchChatsNearby, getInactiveSupergroupChats, getSuitableDiscussionChats, getGroupsInCommon, or getUserPrivacySettingRules @@ -732,7 +739,7 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 memb //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this supergroup or channel must be restricted //@is_scam True, if many users reported this supergroup or channel as a scam //@is_fake True, if many users reported this supergroup or channel as a fake account -supergroup id:int53 username:string date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool join_to_send_messages:Bool join_by_request:Bool is_slow_mode_enabled:Bool is_channel:Bool is_broadcast_group:Bool is_verified:Bool restriction_reason:string is_scam:Bool is_fake:Bool = Supergroup; +supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool join_to_send_messages:Bool join_by_request:Bool is_slow_mode_enabled:Bool is_channel:Bool is_broadcast_group:Bool is_verified:Bool restriction_reason:string is_scam:Bool is_fake:Bool = Supergroup; //@description Contains full information about a supergroup or channel //@photo Chat photo; may be null @@ -1149,7 +1156,7 @@ chatsNearby users_nearby:vector supergroups_nearby:vectorcontacts_manager_->on_update_channel_username(channel_id_, std::move(username_)); + // td_->contacts_manager_->on_update_channel_editable_username(channel_id_, std::move(username_)); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_update_channel_username(channel_id_, std::move(username_)); + // td_->contacts_manager_->on_update_channel_editable_username(channel_id_, std::move(username_)); if (!td_->auth_manager_->is_bot()) { promise_.set_value(Unit()); return; @@ -3672,7 +3672,7 @@ template void ContactsManager::User::store(StorerT &storer) const { using td::store; bool has_last_name = !last_name.empty(); - bool has_username = !username.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; @@ -3680,6 +3680,7 @@ void ContactsManager::User::store(StorerT &storer) const { 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(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_received); STORE_FLAG(is_verified); @@ -3690,7 +3691,7 @@ void ContactsManager::User::store(StorerT &storer) const { STORE_FLAG(is_inline_bot); STORE_FLAG(need_location_bot); STORE_FLAG(has_last_name); - STORE_FLAG(has_username); + STORE_FLAG(legacy_has_username); STORE_FLAG(has_photo); // 10 STORE_FLAG(false); // legacy is_restricted STORE_FLAG(has_language_code); @@ -3709,14 +3710,12 @@ void ContactsManager::User::store(StorerT &storer) const { STORE_FLAG(is_premium); // 25 STORE_FLAG(attach_menu_enabled); STORE_FLAG(has_emoji_status); + STORE_FLAG(has_usernames); END_STORE_FLAGS(); store(first_name, storer); if (has_last_name) { store(last_name, storer); } - if (has_username) { - store(username, storer); - } store(phone_number, storer); if (have_access_hash) { store(access_hash, storer); @@ -3743,13 +3742,16 @@ void ContactsManager::User::store(StorerT &storer) const { if (has_emoji_status) { store(emoji_status, storer); } + if (has_usernames) { + store(usernames, storer); + } } template void ContactsManager::User::parse(ParserT &parser) { using td::parse; bool has_last_name; - bool has_username; + bool legacy_has_username; bool has_photo; bool legacy_is_restricted; bool has_language_code; @@ -3758,6 +3760,7 @@ void ContactsManager::User::parse(ParserT &parser) { bool has_is_contact; bool has_restriction_reasons; bool has_emoji_status; + bool has_usernames; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_received); PARSE_FLAG(is_verified); @@ -3768,7 +3771,7 @@ void ContactsManager::User::parse(ParserT &parser) { PARSE_FLAG(is_inline_bot); PARSE_FLAG(need_location_bot); PARSE_FLAG(has_last_name); - PARSE_FLAG(has_username); + PARSE_FLAG(legacy_has_username); PARSE_FLAG(has_photo); PARSE_FLAG(legacy_is_restricted); PARSE_FLAG(has_language_code); @@ -3787,13 +3790,17 @@ void ContactsManager::User::parse(ParserT &parser) { PARSE_FLAG(is_premium); PARSE_FLAG(attach_menu_enabled); PARSE_FLAG(has_emoji_status); + PARSE_FLAG(has_usernames); END_PARSE_FLAGS(); parse(first_name, parser); if (has_last_name) { parse(last_name, parser); } - if (has_username) { + 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)) { @@ -3841,6 +3848,10 @@ void ContactsManager::User::parse(ParserT &parser) { if (has_emoji_status) { parse(emoji_status, parser); } + if (has_usernames) { + CHECK(!legacy_has_username); + parse(usernames, parser); + } if (!check_utf8(first_name)) { LOG(ERROR) << "Have invalid first name \"" << first_name << '"'; @@ -3852,11 +3863,6 @@ void ContactsManager::User::parse(ParserT &parser) { last_name.clear(); cache_version = 0; } - if (!check_utf8(username)) { - LOG(ERROR) << "Have invalid username \"" << username << '"'; - username.clear(); - cache_version = 0; - } clean_phone_number(phone_number); if (first_name.empty() && last_name.empty()) { @@ -4213,13 +4219,14 @@ template void ContactsManager::Channel::store(StorerT &storer) const { using td::store; bool has_photo = photo.small_file_id.is_valid(); - bool has_username = !username.empty(); + bool legacy_has_username = false; bool use_new_rights = true; bool has_participant_count = participant_count != 0; bool have_default_permissions = true; bool has_cache_version = cache_version != 0; bool has_restriction_reasons = !restriction_reasons.empty(); bool legacy_has_active_group_call = false; + bool has_usernames = !usernames.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(false); STORE_FLAG(false); @@ -4231,7 +4238,7 @@ void ContactsManager::Channel::store(StorerT &storer) const { STORE_FLAG(is_megagroup); STORE_FLAG(is_verified); STORE_FLAG(has_photo); - STORE_FLAG(has_username); // 10 + STORE_FLAG(legacy_has_username); // 10 STORE_FLAG(false); STORE_FLAG(use_new_rights); STORE_FLAG(has_participant_count); @@ -4249,6 +4256,7 @@ void ContactsManager::Channel::store(StorerT &storer) const { STORE_FLAG(can_be_deleted); // 25 STORE_FLAG(join_to_send); STORE_FLAG(join_request); + STORE_FLAG(has_usernames); END_STORE_FLAGS(); store(status, storer); @@ -4257,9 +4265,6 @@ void ContactsManager::Channel::store(StorerT &storer) const { if (has_photo) { store(photo, storer); } - if (has_username) { - store(username, storer); - } store(date, storer); if (has_restriction_reasons) { store(restriction_reasons, storer); @@ -4273,13 +4278,16 @@ void ContactsManager::Channel::store(StorerT &storer) const { if (has_cache_version) { store(cache_version, storer); } + if (has_usernames) { + store(usernames, storer); + } } template void ContactsManager::Channel::parse(ParserT &parser) { using td::parse; bool has_photo; - bool has_username; + bool legacy_has_username; bool legacy_is_restricted; bool left; bool kicked; @@ -4293,6 +4301,7 @@ void ContactsManager::Channel::parse(ParserT &parser) { bool has_cache_version; bool has_restriction_reasons; bool legacy_has_active_group_call; + bool has_usernames; BEGIN_PARSE_FLAGS(); PARSE_FLAG(left); PARSE_FLAG(kicked); @@ -4304,7 +4313,7 @@ void ContactsManager::Channel::parse(ParserT &parser) { PARSE_FLAG(is_megagroup); PARSE_FLAG(is_verified); PARSE_FLAG(has_photo); - PARSE_FLAG(has_username); + PARSE_FLAG(legacy_has_username); PARSE_FLAG(legacy_is_restricted); PARSE_FLAG(use_new_rights); PARSE_FLAG(has_participant_count); @@ -4322,6 +4331,7 @@ void ContactsManager::Channel::parse(ParserT &parser) { PARSE_FLAG(can_be_deleted); PARSE_FLAG(join_to_send); PARSE_FLAG(join_request); + PARSE_FLAG(has_usernames); END_PARSE_FLAGS(); if (use_new_rights) { @@ -4344,8 +4354,11 @@ void ContactsManager::Channel::parse(ParserT &parser) { if (has_photo) { parse(photo, parser); } - if (has_username) { + if (legacy_has_username) { + CHECK(!has_usernames); + string username; parse(username, parser); + usernames = Usernames(std::move(username), vector>()); } parse(date, parser); if (legacy_is_restricted) { @@ -4369,17 +4382,16 @@ void ContactsManager::Channel::parse(ParserT &parser) { if (has_cache_version) { parse(cache_version, parser); } + if (has_usernames) { + CHECK(!legacy_has_username); + parse(usernames, parser); + } if (!check_utf8(title)) { LOG(ERROR) << "Have invalid title \"" << title << '"'; title.clear(); cache_version = 0; } - if (!check_utf8(username)) { - LOG(ERROR) << "Have invalid username \"" << username << '"'; - username.clear(); - cache_version = 0; - } if (legacy_has_active_group_call) { cache_version = 0; } @@ -5098,7 +5110,7 @@ string ContactsManager::get_user_search_text(UserId user_id) const { string ContactsManager::get_user_search_text(const User *u) { CHECK(u != nullptr); - return PSTRING() << u->first_name << ' ' << u->last_name << ' ' << u->username; + return PSTRING() << u->first_name << ' ' << u->last_name << ' ' << implode(u->usernames.get_active_usernames()); } string ContactsManager::get_channel_search_text(ChannelId channel_id) const { @@ -5111,7 +5123,7 @@ string ContactsManager::get_channel_search_text(ChannelId channel_id) const { string ContactsManager::get_channel_search_text(const Channel *c) { CHECK(c != nullptr); - return PSTRING() << c->title << ' ' << c->username; + return PSTRING() << c->title << ' ' << implode(c->usernames.get_active_usernames()); } int32 ContactsManager::get_secret_chat_date(SecretChatId secret_chat_id) const { @@ -5130,7 +5142,7 @@ int32 ContactsManager::get_secret_chat_ttl(SecretChatId secret_chat_id) const { return c->ttl; } -string ContactsManager::get_user_username(UserId user_id) const { +string ContactsManager::get_user_first_username(UserId user_id) const { if (!user_id.is_valid()) { return string(); } @@ -5139,15 +5151,15 @@ string ContactsManager::get_user_username(UserId user_id) const { if (u == nullptr) { return string(); } - return u->username; + return u->usernames.get_first_username(); } -string ContactsManager::get_channel_username(ChannelId channel_id) const { +string ContactsManager::get_channel_first_username(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { return string(); } - return c->username; + return c->usernames.get_first_username(); } UserId ContactsManager::get_secret_chat_user_id(SecretChatId secret_chat_id) const { @@ -5332,7 +5344,7 @@ void ContactsManager::check_dialog_username(DialogId dialog_id, const string &us return promise.set_error(Status::Error(400, "Not enough rights to change username")); } - if (username == c->username) { + if (username == c->usernames.get_editable_username()) { return promise.set_value(CheckDialogUsernameResult::Ok); } break; @@ -6699,7 +6711,7 @@ void ContactsManager::set_channel_username(ChannelId channel_id, const string &u return promise.set_error(Status::Error(400, "Username is invalid")); } - if (!username.empty() && c->username.empty()) { + if (!username.empty() && c->usernames.is_empty()) { auto channel_full = get_channel_full(channel_id, false, "set_channel_username"); if (channel_full != nullptr && !channel_full->can_set_username) { return promise.set_error(Status::Error(400, "Can't set supergroup username")); @@ -8208,7 +8220,7 @@ void ContactsManager::finish_get_created_public_dialogs(PublicDialogType type, R void ContactsManager::update_created_public_channels(Channel *c, ChannelId channel_id) { if (created_public_channels_inited_[0]) { bool was_changed = false; - if (c->username.empty() || !c->status.is_creator()) { + if (c->usernames.is_empty() || !c->status.is_creator()) { was_changed = td::remove(created_public_channels_[0], channel_id); } else { if (!td::contains(created_public_channels_[0], channel_id)) { @@ -8809,7 +8821,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, if (is_received || !u->is_received) { on_update_user_name(u, user_id, std::move(user->first_name_), std::move(user->last_name_), - std::move(user->username_)); + Usernames{std::move(user->username_), std::move(user->usernames_)}); } on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(user->emoji_status_))); @@ -8867,7 +8879,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, need_location_bot != u->need_location_bot || can_be_added_to_attach_menu != u->can_be_added_to_attach_menu || attach_menu_enabled != u->attach_menu_enabled) { LOG_IF(ERROR, is_bot != u->is_bot && !is_deleted && !u->is_deleted && u->is_received) - << "User.is_bot has changed for " << user_id << "/" << u->username << " from " << source << " from " + << "User.is_bot has changed for " << user_id << "/" << u->usernames << " from " << source << " from " << u->is_bot << " to " << is_bot; u->is_verified = is_verified; u->is_support = is_support; @@ -9694,8 +9706,8 @@ void ContactsManager::on_load_channel_from_database(ChannelId channel_id, string CHECK(!c->is_being_saved); } - if (temp_c.username != c->username) { - on_channel_username_changed(c, channel_id, temp_c.username, c->username); + if (temp_c.usernames != c->usernames) { + on_channel_usernames_changed(c, channel_id, temp_c.usernames, c->usernames); CHECK(!c->is_being_saved); } } @@ -11660,7 +11672,8 @@ void ContactsManager::on_get_channel_full_failed(ChannelId channel_id) { } } -void ContactsManager::on_update_user_name(UserId user_id, string &&first_name, string &&last_name, string &&username) { +void ContactsManager::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; @@ -11668,7 +11681,7 @@ void ContactsManager::on_update_user_name(UserId user_id, string &&first_name, s User *u = get_user_force(user_id); if (u != nullptr) { - on_update_user_name(u, user_id, std::move(first_name), std::move(last_name), std::move(username)); + on_update_user_name(u, user_id, std::move(first_name), std::move(last_name), std::move(usernames)); update_user(u, user_id); } else { LOG(INFO) << "Ignore update user name about unknown " << user_id; @@ -11676,7 +11689,7 @@ void ContactsManager::on_update_user_name(UserId user_id, string &&first_name, s } void ContactsManager::on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name, - string &&username) { + Usernames &&usernames) { if (first_name.empty() && last_name.empty()) { first_name = u->phone_number; } @@ -11687,11 +11700,11 @@ void ContactsManager::on_update_user_name(User *u, UserId user_id, string &&firs LOG(DEBUG) << "Name has changed for " << user_id; u->is_changed = true; } - td_->messages_manager_->on_dialog_username_updated(DialogId(user_id), u->username, username); - if (u->username != username) { - u->username = std::move(username); + td_->messages_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); + if (u->usernames != usernames) { + u->usernames = std::move(usernames); u->is_username_changed = true; - LOG(DEBUG) << "Username has changed for " << user_id; + LOG(DEBUG) << "Usernames has changed for " << user_id; u->is_changed = true; } } @@ -12595,9 +12608,9 @@ bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &s c->access_hash, c->title, 0); on_chat_update(update, "CHANNEL_PRIVATE"); } else if (!c->status.is_banned()) { - if (!c->username.empty()) { - LOG(INFO) << "Drop username of " << channel_id; - on_update_channel_username(c, channel_id, ""); + if (!c->usernames.is_empty()) { + LOG(INFO) << "Drop usernames of " << channel_id; + on_update_channel_usernames(c, channel_id, Usernames()); } on_update_channel_has_location(c, channel_id, false); @@ -14202,7 +14215,7 @@ void ContactsManager::on_update_channel_noforwards(Channel *c, ChannelId channel } } -void ContactsManager::on_update_channel_username(ChannelId channel_id, string &&username) { +void ContactsManager::on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames) { if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; return; @@ -14210,30 +14223,30 @@ void ContactsManager::on_update_channel_username(ChannelId channel_id, string && Channel *c = get_channel_force(channel_id); if (c != nullptr) { - on_update_channel_username(c, channel_id, std::move(username)); + on_update_channel_usernames(c, channel_id, std::move(usernames)); update_channel(c, channel_id); } else { - LOG(INFO) << "Ignore update channel username about unknown " << channel_id; + LOG(INFO) << "Ignore update channel usernames about unknown " << channel_id; } } -void ContactsManager::on_update_channel_username(Channel *c, ChannelId channel_id, string &&username) { - td_->messages_manager_->on_dialog_username_updated(DialogId(channel_id), c->username, username); - if (c->username != username) { +void ContactsManager::on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames) { + td_->messages_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); + if (c->usernames != usernames) { if (c->is_update_supergroup_sent) { - on_channel_username_changed(c, channel_id, c->username, username); + on_channel_usernames_changed(c, channel_id, c->usernames, usernames); } - c->username = std::move(username); + c->usernames = std::move(usernames); c->is_username_changed = true; c->is_changed = true; } } -void ContactsManager::on_channel_username_changed(const Channel *c, ChannelId channel_id, const string &old_username, - const string &new_username) { +void ContactsManager::on_channel_usernames_changed(const Channel *c, ChannelId channel_id, + const Usernames &old_usernames, const Usernames &new_usernames) { bool have_channel_full = get_channel_full(channel_id) != nullptr; - if (old_username.empty() || new_username.empty()) { + if (old_usernames.is_empty() || new_usernames.is_empty()) { // moving channel from private to public can change availability of chat members invalidate_channel_full(channel_id, !c->is_slow_mode_enabled); } @@ -14609,7 +14622,7 @@ Result ContactsManager::get_bot_data(UserId user_id) c } BotData bot_data; - bot_data.username = u->username; + bot_data.username = u->usernames.get_first_username(); 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; @@ -15206,7 +15219,7 @@ bool ContactsManager::is_channel_public(ChannelId channel_id) const { } bool ContactsManager::is_channel_public(const Channel *c) { - return c != nullptr && (!c->username.empty() || c->has_location); + return c != nullptr && (!c->usernames.is_empty() || c->has_location); } ChannelType ContactsManager::get_channel_type(ChannelId channel_id) const { @@ -16479,7 +16492,8 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char auto old_join_to_send = get_channel_join_to_send(c); auto old_join_request = get_channel_join_request(c); on_update_channel_title(c, channel_id, std::move(channel.title_)); - on_update_channel_username(c, channel_id, std::move(channel.username_)); + on_update_channel_usernames(c, channel_id, + Usernames(std::move(channel.username_), std::move(channel.usernames_))); on_update_channel_photo(c, channel_id, std::move(channel.photo_)); on_update_channel_default_permissions(c, channel_id, RestrictedRights(channel.default_banned_rights_)); on_update_channel_has_location(c, channel_id, channel.has_geo_); @@ -16554,7 +16568,10 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char } on_update_channel_photo(c, channel_id, std::move(channel.photo_)); on_update_channel_status(c, channel_id, std::move(status)); - on_update_channel_username(c, channel_id, std::move(channel.username_)); // uses status, must be called after + on_update_channel_usernames( + c, channel_id, + Usernames(std::move(channel.username_), + std::move(channel.usernames_))); // uses status, must be called after on_update_channel_status on_update_channel_default_permissions(c, channel_id, RestrictedRights(channel.default_banned_rights_)); on_update_channel_has_location(c, channel_id, channel.has_geo_); on_update_channel_noforwards(c, channel_id, channel.noforwards_); @@ -16656,7 +16673,7 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, co } int32 unban_date = (channel.flags_ & CHANNEL_FLAG_HAS_UNBAN_DATE) != 0 ? channel.until_date_ : 0; on_update_channel_status(c, channel_id, DialogParticipantStatus::Banned(unban_date)); - // on_update_channel_username(c, channel_id, ""); // don't know if channel username is empty, so don't update it + // on_update_channel_usernames(c, channel_id, Usernames()); // don't know if channel usernames are empty, so don't update it tl_object_ptr banned_rights; // == nullptr on_update_channel_default_permissions(c, channel_id, RestrictedRights(banned_rights)); // on_update_channel_has_location(c, channel_id, false); @@ -16827,8 +16844,8 @@ td_api::object_ptr ContactsManager::get_user_status_object(U td_api::object_ptr ContactsManager::get_update_unknown_user_object(UserId user_id) { return td_api::make_object(td_api::make_object( - user_id.get(), "", "", "", "", td_api::make_object(), nullptr, nullptr, false, false, - false, false, false, "", false, false, false, td_api::make_object(), "", false)); + user_id.get(), "", "", nullptr, "", td_api::make_object(), nullptr, nullptr, false, + false, false, false, false, "", false, false, false, td_api::make_object(), "", false)); } int64 ContactsManager::get_user_id_object(UserId user_id, const char *source) const { @@ -16861,9 +16878,9 @@ tl_object_ptr ContactsManager::get_user_object(UserId user_id, con auto emoji_status = u->last_sent_emoji_status.is_valid() ? u->emoji_status.get_emoji_status_object() : nullptr; return make_tl_object( - user_id.get(), u->first_name, u->last_name, u->username, u->phone_number, get_user_status_object(user_id, u), - get_profile_photo_object(td_->file_manager_.get(), u->photo), std::move(emoji_status), u->is_contact, - u->is_mutual_contact, u->is_verified, u->is_premium, u->is_support, + user_id.get(), u->first_name, u->last_name, u->usernames.get_usernames_object(), u->phone_number, + get_user_status_object(user_id, u), get_profile_photo_object(td_->file_manager_.get(), u->photo), + std::move(emoji_status), u->is_contact, u->is_mutual_contact, u->is_verified, u->is_premium, u->is_support, get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, u->is_received, std::move(type), u->language_code, u->attach_menu_enabled); } @@ -16991,8 +17008,8 @@ td_api::object_ptr ContactsManager::get_update_unknown auto min_channel = get_min_channel(channel_id); bool is_megagroup = min_channel == nullptr ? false : min_channel->is_megagroup_; return td_api::make_object(td_api::make_object( - channel_id.get(), string(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), 0, false, - false, false, !is_megagroup, false, false, !is_megagroup, false, false, string(), false, false)); + channel_id.get(), nullptr, 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), 0, false, false, + false, !is_megagroup, false, false, !is_megagroup, false, false, string(), false, false)); } int64 ContactsManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const { @@ -17017,9 +17034,10 @@ tl_object_ptr ContactsManager::get_supergroup_object(Channel return nullptr; } return td_api::make_object( - channel_id.get(), c->username, c->date, get_channel_status(c).get_chat_member_status_object(), - c->participant_count, c->has_linked_channel, c->has_location, c->sign_messages, get_channel_join_to_send(c), - get_channel_join_request(c), c->is_slow_mode_enabled, !c->is_megagroup, c->is_gigagroup, c->is_verified, + channel_id.get(), c->usernames.get_usernames_object(), c->date, + get_channel_status(c).get_chat_member_status_object(), c->participant_count, c->has_linked_channel, + c->has_location, c->sign_messages, get_channel_join_to_send(c), get_channel_join_request(c), + c->is_slow_mode_enabled, !c->is_megagroup, c->is_gigagroup, c->is_verified, get_restriction_reason_description(c->restriction_reasons), c->is_scam, c->is_fake); } diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index 885e445fd..f4c588c05 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -39,6 +39,7 @@ #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" +#include "td/telegram/Usernames.h" #include "td/actor/actor.h" #include "td/actor/MultiPromise.h" @@ -131,8 +132,8 @@ class ContactsManager final : public Actor { void for_each_secret_chat_with_user(UserId user_id, const std::function &f); - string get_user_username(UserId user_id) const; - string get_channel_username(ChannelId channel_id) const; + string get_user_first_username(UserId user_id) const; + string get_channel_first_username(ChannelId channel_id) const; int32 get_secret_chat_date(SecretChatId secret_chat_id) const; int32 get_secret_chat_ttl(SecretChatId secret_chat_id) const; @@ -176,7 +177,7 @@ class ContactsManager final : public Actor { void on_update_profile_success(int32 flags, const string &first_name, const string &last_name, const string &about); - void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, string &&username); + void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, Usernames &&usernames); void on_update_user_phone_number(UserId user_id, string &&phone_number); void on_update_user_photo(UserId user_id, tl_object_ptr &&photo_ptr); void on_update_user_emoji_status(UserId user_id, tl_object_ptr &&emoji_status); @@ -199,7 +200,7 @@ class ContactsManager final : public Actor { void on_update_chat_default_permissions(ChatId chat_id, RestrictedRights default_permissions, int32 version); void on_update_chat_pinned_message(ChatId chat_id, MessageId pinned_message_id, int32 version); - void on_update_channel_username(ChannelId channel_id, string &&username); + void on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames); void on_update_channel_description(ChannelId channel_id, string &&description); void on_update_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id); void on_update_channel_linked_channel_id(ChannelId channel_id, ChannelId group_channel_id); @@ -643,7 +644,7 @@ class ContactsManager final : public Actor { struct User { string first_name; string last_name; - string username; + Usernames usernames; string phone_number; int64 access_hash = -1; EmojiStatus emoji_status; @@ -845,7 +846,7 @@ class ContactsManager final : public Actor { int64 access_hash = 0; string title; DialogPhoto photo; - string username; + Usernames usernames; vector restriction_reasons; DialogParticipantStatus status = DialogParticipantStatus::Banned(0); RestrictedRights default_permissions{false, false, false, false, false, false, false, false, false, false, false}; @@ -1078,6 +1079,7 @@ class ContactsManager final : public Actor { static constexpr int32 USER_FLAG_IS_PREMIUM = 1 << 28; static constexpr int32 USER_FLAG_ATTACH_MENU_ENABLED = 1 << 29; static constexpr int32 USER_FLAG_HAS_EMOJI_STATUS = 1 << 30; + static constexpr int32 USER_FLAG_HAS_USERNAMES = 1 << 0; static constexpr int32 USER_FULL_FLAG_IS_BLOCKED = 1 << 0; static constexpr int32 USER_FULL_FLAG_HAS_ABOUT = 1 << 1; @@ -1114,7 +1116,7 @@ class ContactsManager final : public Actor { static constexpr int32 CHANNEL_FLAG_USER_IS_CREATOR = 1 << 0; static constexpr int32 CHANNEL_FLAG_USER_HAS_LEFT = 1 << 2; static constexpr int32 CHANNEL_FLAG_IS_BROADCAST = 1 << 5; - static constexpr int32 CHANNEL_FLAG_IS_PUBLIC = 1 << 6; + static constexpr int32 CHANNEL_FLAG_HAS_USERNAME = 1 << 6; static constexpr int32 CHANNEL_FLAG_IS_VERIFIED = 1 << 7; static constexpr int32 CHANNEL_FLAG_IS_MEGAGROUP = 1 << 8; static constexpr int32 CHANNEL_FLAG_IS_RESTRICTED = 1 << 9; @@ -1137,6 +1139,7 @@ class ContactsManager final : public Actor { static constexpr int32 CHANNEL_FLAG_NOFORWARDS = 1 << 27; static constexpr int32 CHANNEL_FLAG_JOIN_TO_SEND = 1 << 28; static constexpr int32 CHANNEL_FLAG_JOIN_REQUEST = 1 << 29; + static constexpr int32 CHANNEL_FLAG_HAS_USERNAMES = 1 << 0; static constexpr int32 CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 0; static constexpr int32 CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT = 1 << 1; @@ -1262,7 +1265,7 @@ class ContactsManager final : public Actor { void on_set_emoji_status(EmojiStatus emoji_status, Promise &&promise); - void on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name, string &&username); + void on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name, Usernames &&usernames); void on_update_user_phone_number(User *u, UserId user_id, string &&phone_number); void on_update_user_photo(User *u, UserId user_id, tl_object_ptr &&photo, const char *source); @@ -1322,7 +1325,7 @@ class ContactsManager final : public Actor { tl_object_ptr &&chat_photo_ptr); void on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo, bool invalidate_photo_cache); static void on_update_channel_title(Channel *c, ChannelId channel_id, string &&title); - void on_update_channel_username(Channel *c, ChannelId channel_id, string &&username); + void on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames); void on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status); static void on_update_channel_default_permissions(Channel *c, ChannelId channel_id, RestrictedRights default_permissions); @@ -1346,8 +1349,8 @@ class ContactsManager final : public Actor { void on_channel_status_changed(Channel *c, ChannelId channel_id, const DialogParticipantStatus &old_status, const DialogParticipantStatus &new_status); - void on_channel_username_changed(const Channel *c, ChannelId channel_id, const string &old_username, - const string &new_username); + void on_channel_usernames_changed(const Channel *c, ChannelId channel_id, const Usernames &old_usernames, + const Usernames &new_usernames); void remove_linked_channel_id(ChannelId channel_id); ChannelId get_linked_channel_id(ChannelId channel_id) const; @@ -1837,7 +1840,7 @@ class ContactsManager final : public Actor { bool are_contacts_loaded_ = false; int32 next_contacts_sync_date_ = 0; - Hints contacts_hints_; // search contacts by first name, last name and username + Hints contacts_hints_; // search contacts by first name, last name and usernames vector> load_contacts_queries_; MultiPromiseActor load_contact_users_multipromise_{"LoadContactUsersMultiPromiseActor"}; int32 saved_contact_count_ = -1; diff --git a/td/telegram/InlineQueriesManager.cpp b/td/telegram/InlineQueriesManager.cpp index ad8bb37ca..3ba7c3eda 100644 --- a/td/telegram/InlineQueriesManager.cpp +++ b/td/telegram/InlineQueriesManager.cpp @@ -1948,7 +1948,7 @@ void InlineQueriesManager::save_recently_used_bots() { value += ','; value_ids += ','; } - value += td_->contacts_manager_->get_user_username(bot_user_id); + value += td_->contacts_manager_->get_user_first_username(bot_user_id); value_ids += to_string(bot_user_id.get()); } G()->td_db()->get_binlog_pmc()->set("recently_used_inline_bot_usernames", value); diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 71644abe2..25c0203e6 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -19175,7 +19175,7 @@ Result> MessagesManager::get_message_link(FullMessageId CHECK(linked_d != nullptr); CHECK(linked_dialog_id.get_type() == DialogType::Channel); auto *linked_m = get_message_force(linked_d, linked_message_id, "get_public_message_link"); - auto channel_username = td_->contacts_manager_->get_channel_username(linked_dialog_id.get_channel_id()); + auto channel_username = td_->contacts_manager_->get_channel_first_username(linked_dialog_id.get_channel_id()); if (linked_m != nullptr && is_active_message_reply_info(linked_dialog_id, linked_m->reply_info) && linked_message_id.is_server() && have_input_peer(linked_dialog_id, AccessRights::Read) && !channel_username.empty()) { @@ -19192,7 +19192,7 @@ Result> MessagesManager::get_message_link(FullMessageId } } - auto dialog_username = td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id()); + auto dialog_username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()); bool is_public = !dialog_username.empty(); if (m->content->get_type() == MessageContentType::VideoNote && is_broadcast_channel(dialog_id) && is_public) { return std::make_pair( @@ -19236,7 +19236,7 @@ string MessagesManager::get_message_embedding_code(FullMessageId full_message_id return {}; } if (dialog_id.get_type() != DialogType::Channel || - td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id()).empty()) { + td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()).empty()) { promise.set_error(Status::Error( 400, "Message embedding code is available only for messages in public supergroups and channel chats")); return {}; @@ -25914,7 +25914,7 @@ void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dial break; } if (!is_broadcast_channel(message_sender_dialog_id) || - td_->contacts_manager_->get_channel_username(message_sender_dialog_id.get_channel_id()).empty()) { + td_->contacts_manager_->get_channel_first_username(message_sender_dialog_id.get_channel_id()).empty()) { return promise.set_error(Status::Error(400, "Message sender chat must be a public channel")); } break; @@ -26562,8 +26562,8 @@ void MessagesManager::send_secret_message(DialogId dialog_id, const Message *m, make_tl_object( flags, false /*ignored*/, random_id, m->ttl, m->content->get_type() == MessageContentType::Text ? text->text : string(), std::move(media.decrypted_media_), - std::move(entities), td_->contacts_manager_->get_user_username(m->via_bot_user_id), m->reply_to_random_id, - -m->media_album_id), + std::move(entities), td_->contacts_manager_->get_user_first_username(m->via_bot_user_id), + m->reply_to_random_id, -m->media_album_id), std::move(media.input_file_), Promise()); } @@ -33510,25 +33510,30 @@ void MessagesManager::on_get_dialog_query_finished(DialogId dialog_id, Status && } } -void MessagesManager::on_dialog_username_updated(DialogId dialog_id, const string &old_username, - const string &new_username) { +void MessagesManager::on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames, + const Usernames &new_usernames) { CHECK(dialog_id.is_valid()); auto d = get_dialog(dialog_id); if (d != nullptr) { update_dialogs_hints(d); } - if (old_username != new_username) { + if (old_usernames != new_usernames) { message_embedding_codes_[0].erase(dialog_id); message_embedding_codes_[1].erase(dialog_id); } - if (!old_username.empty() && old_username != new_username) { - resolved_usernames_.erase(clean_username(old_username)); - inaccessible_resolved_usernames_.erase(clean_username(old_username)); + if (!old_usernames.is_empty() && old_usernames != new_usernames) { + for (auto &username : old_usernames.get_active_usernames()) { + auto cleaned_username = clean_username(username); + resolved_usernames_.erase(cleaned_username); + inaccessible_resolved_usernames_.erase(cleaned_username); + } } - if (!new_username.empty()) { - auto cleaned_username = clean_username(new_username); - if (!cleaned_username.empty()) { - resolved_usernames_[cleaned_username] = ResolvedUsername{dialog_id, Time::now() + USERNAME_CACHE_EXPIRE_TIME}; + if (!new_usernames.is_empty()) { + for (auto &username : new_usernames.get_active_usernames()) { + auto cleaned_username = clean_username(username); + if (!cleaned_username.empty()) { + resolved_usernames_[cleaned_username] = ResolvedUsername{dialog_id, Time::now() + USERNAME_CACHE_EXPIRE_TIME}; + } } } } diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 93ec24da3..950423941 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -59,6 +59,7 @@ #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" +#include "td/telegram/Usernames.h" #include "td/actor/actor.h" #include "td/actor/MultiPromise.h" @@ -856,7 +857,7 @@ class MessagesManager final : public Actor { void on_dialog_photo_updated(DialogId dialog_id); void on_dialog_title_updated(DialogId dialog_id); - void on_dialog_username_updated(DialogId dialog_id, const string &old_username, const string &new_username); + void on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames, const Usernames &new_usernames); void on_dialog_default_permissions_updated(DialogId dialog_id); void on_dialog_has_protected_content_updated(DialogId dialog_id); @@ -3621,7 +3622,7 @@ class MessagesManager final : public Actor { Timeout reload_dialog_filters_timeout_; - Hints dialogs_hints_; // search dialogs by title and username + Hints dialogs_hints_; // search dialogs by title and usernames FlatHashSet active_live_location_full_message_ids_; bool are_active_live_location_messages_loaded_ = false; diff --git a/td/telegram/RecentDialogList.cpp b/td/telegram/RecentDialogList.cpp index 218335343..56c873cc3 100644 --- a/td/telegram/RecentDialogList.cpp +++ b/td/telegram/RecentDialogList.cpp @@ -52,13 +52,13 @@ void RecentDialogList::save_dialogs() const { switch (dialog_id.get_type()) { case DialogType::User: if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id())) { - username = td_->contacts_manager_->get_user_username(dialog_id.get_user_id()); + username = td_->contacts_manager_->get_user_first_username(dialog_id.get_user_id()); } break; case DialogType::Chat: break; case DialogType::Channel: - username = td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id()); + username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()); break; case DialogType::SecretChat: break; diff --git a/td/telegram/SponsoredMessageManager.cpp b/td/telegram/SponsoredMessageManager.cpp index 64b9ab199..eaf7eb395 100644 --- a/td/telegram/SponsoredMessageManager.cpp +++ b/td/telegram/SponsoredMessageManager.cpp @@ -160,7 +160,7 @@ td_api::object_ptr SponsoredMessageManager::get_sponso if (!td_->contacts_manager_->is_user_bot(user_id)) { break; } - auto bot_username = td_->contacts_manager_->get_user_username(user_id); + auto bot_username = td_->contacts_manager_->get_user_first_username(user_id); if (bot_username.empty()) { break; } diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index f6fba11f9..6e7aacbff 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -56,6 +56,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.hpp" #include "td/telegram/ThemeManager.h" +#include "td/telegram/Usernames.h" #include "td/telegram/VoiceNotesManager.h" #include "td/telegram/WebPagesManager.h" @@ -3181,7 +3182,8 @@ void UpdatesManager::on_update(tl_object_ptr upd void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->contacts_manager_->on_update_user_name(UserId(update->user_id_), std::move(update->first_name_), - std::move(update->last_name_), string()); + std::move(update->last_name_), + Usernames{string(), std::move(update->usernames_)}); promise.set_value(Unit()); } diff --git a/td/telegram/Usernames.cpp b/td/telegram/Usernames.cpp new file mode 100644 index 000000000..344956b2d --- /dev/null +++ b/td/telegram/Usernames.cpp @@ -0,0 +1,109 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// 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/Usernames.h" + +#include "td/telegram/misc.h" +#include "td/telegram/secret_api.h" + +namespace td { + +Usernames::Usernames(string &&first_username, vector> &&usernames) { + if (usernames.empty()) { + if (!first_username.empty()) { + active_usernames_.push_back(std::move(first_username)); + editable_username_pos_ = 0; + } + return; + } + + if (!first_username.empty() && usernames[0]->username_ != first_username) { + LOG(ERROR) << "Receive first username " << first_username << " with " << to_string(usernames); + return; + } + bool was_editable = false; + for (auto &username : usernames) { + if (username->username_.empty()) { + LOG(ERROR) << "Receive empty username in " << to_string(usernames); + return; + } + if (username->editable_) { + if (was_editable) { + LOG(ERROR) << "Receive two editable usernames in " << to_string(usernames); + return; + } + if (!username->active_) { + LOG(ERROR) << "Receive disabled editable usernames in " << to_string(usernames); + return; + } + was_editable = true; + } + } + if (!was_editable) { + LOG(ERROR) << "Receive no editable username in " << to_string(usernames); + return; + } + + for (size_t i = 0; i < usernames.size(); i++) { + if (usernames[i]->active_) { + active_usernames_.push_back(std::move(usernames[i]->username_)); + if (usernames[i]->editable_) { + editable_username_pos_ = narrow_cast(i); + } + } else { + disabled_usernames_.push_back(std::move(usernames[i]->username_)); + } + } + CHECK(editable_username_pos_ != -1); +} + +tl_object_ptr Usernames::get_usernames_object() const { + if (is_empty()) { + return nullptr; + } + return make_tl_object(vector(active_usernames_), vector(disabled_usernames_), + active_usernames_[editable_username_pos_]); +} + +void Usernames::check_utf8_validness() { + for (auto &username : active_usernames_) { + if (!check_utf8(username)) { + LOG(ERROR) << "Have invalid active username \"" << username << '"'; + *this = Usernames(); + return; + } + } + for (auto &username : disabled_usernames_) { + if (!check_utf8(username)) { + LOG(ERROR) << "Have invalid disabled username \"" << username << '"'; + *this = Usernames(); + return; + } + } +} + +bool operator==(const Usernames &lhs, const Usernames &rhs) { + return lhs.active_usernames_ == rhs.active_usernames_ && lhs.disabled_usernames_ == rhs.disabled_usernames_ && + lhs.editable_username_pos_ == rhs.editable_username_pos_; +} + +bool operator!=(const Usernames &lhs, const Usernames &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const Usernames &usernames) { + string_builder << "Usernames["; + if (!usernames.active_usernames_.empty()) { + string_builder << usernames.active_usernames_[usernames.editable_username_pos_] << ' ' + << usernames.active_usernames_; + } + if (!usernames.disabled_usernames_.empty()) { + string_builder << usernames.disabled_usernames_; + } + return string_builder << ']'; +} + +} // namespace td diff --git a/td/telegram/Usernames.h b/td/telegram/Usernames.h new file mode 100644 index 000000000..c36721b5c --- /dev/null +++ b/td/telegram/Usernames.h @@ -0,0 +1,110 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// 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) +// +#pragma once + +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +class Usernames { + vector active_usernames_; + vector disabled_usernames_; + int32 editable_username_pos_ = -1; + + friend bool operator==(const Usernames &lhs, const Usernames &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const Usernames &usernames); + + void check_utf8_validness(); + + public: + Usernames() = default; + + Usernames(string &&first_username, vector> &&usernames); + + td_api::object_ptr get_usernames_object() const; + + bool is_empty() const { + return editable_username_pos_ == -1; + } + + string get_first_username() const { + if (is_empty()) { + return string(); + } + return active_usernames_[0]; + } + + string get_editable_username() const { + if (is_empty()) { + return string(); + } + return active_usernames_[editable_username_pos_]; + } + + const vector &get_active_usernames() const { + return active_usernames_; + } + + template + void store(StorerT &storer) const { + CHECK(!is_empty()) + CHECK(!active_usernames_.empty()) + bool has_many_active_usernames = active_usernames_.size() > 0; + bool has_disabled_usernames = !disabled_usernames_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_many_active_usernames); + STORE_FLAG(has_disabled_usernames); + END_STORE_FLAGS(); + if (has_many_active_usernames) { + td::store(active_usernames_, storer); + td::store(editable_username_pos_, storer); + } else { + td::store(active_usernames_[0], storer); + CHECK(editable_username_pos_ == 0); + } + if (has_disabled_usernames) { + td::store(disabled_usernames_, storer); + } + } + + template + void parse(ParserT &parser) { + using td::parse; + bool has_many_active_usernames; + bool has_disabled_usernames; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_many_active_usernames); + PARSE_FLAG(has_disabled_usernames); + END_PARSE_FLAGS(); + if (has_many_active_usernames) { + td::parse(active_usernames_, parser); + td::parse(editable_username_pos_, parser); + CHECK(static_cast(editable_username_pos_) < active_usernames_.size()); + } else { + active_usernames_.resize(1); + td::parse(active_usernames_[0], parser); + editable_username_pos_ = 0; + } + if (has_disabled_usernames) { + td::parse(disabled_usernames_, parser); + } + check_utf8_validness(); + } +}; + +bool operator==(const Usernames &lhs, const Usernames &rhs); +bool operator!=(const Usernames &lhs, const Usernames &rhs); + +StringBuilder &operator<<(StringBuilder &string_builder, const Usernames &usernames); + +} // namespace td diff --git a/td/telegram/Venue.h b/td/telegram/Venue.h index c42ad410a..c9cf6b793 100644 --- a/td/telegram/Venue.h +++ b/td/telegram/Venue.h @@ -28,7 +28,6 @@ class Venue { string type_; friend bool operator==(const Venue &lhs, const Venue &rhs); - friend bool operator!=(const Venue &lhs, const Venue &rhs); friend StringBuilder &operator<<(StringBuilder &string_builder, const Venue &venue); diff --git a/td/telegram/WebPageBlock.cpp b/td/telegram/WebPageBlock.cpp index f791e4b52..50e0fac88 100644 --- a/td/telegram/WebPageBlock.cpp +++ b/td/telegram/WebPageBlock.cpp @@ -2142,7 +2142,7 @@ unique_ptr get_web_page_block(Td *td, tl_object_ptr(td->contacts_manager_->get_channel_title(channel_id), *td->contacts_manager_->get_channel_dialog_photo(channel_id), - td->contacts_manager_->get_channel_username(channel_id)); + td->contacts_manager_->get_channel_first_username(channel_id)); } else { bool has_access_hash = (channel->flags_ & telegram_api::channel::ACCESS_HASH_MASK) != 0; return td::make_unique( diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index cac285728..51d4d162b 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -250,9 +250,10 @@ class CliClient final : public Actor { User &new_user = *new_user_ptr; new_user.first_name = user.first_name_; new_user.last_name = user.last_name_; - new_user.username = user.username_; - if (!new_user.username.empty()) { - username_to_user_id_[to_lower(new_user.username)] = user.id_; + if (user.usernames_ != nullptr) { + for (auto &username : user.usernames_->active_usernames_) { + username_to_user_id_[to_lower(username)] = user.id_; + } } } @@ -260,9 +261,6 @@ class CliClient final : public Actor { const User *user = users_[user_id].get(); CHECK(user != nullptr); log << user->first_name << " " << user->last_name << " #" << user_id; - if (!user->username.empty()) { - log << " @" << user->username; - } } void update_users(const td_api::users &users) { @@ -278,8 +276,10 @@ class CliClient final : public Actor { FlatHashMap username_to_supergroup_id_; void register_supergroup(const td_api::supergroup &supergroup) { - if (!supergroup.username_.empty()) { - username_to_supergroup_id_[to_lower(supergroup.username_)] = supergroup.id_; + if (supergroup.usernames_ != nullptr) { + for (auto &username : supergroup.usernames_->active_usernames_) { + username_to_supergroup_id_[to_lower(username)] = supergroup.id_; + } } } diff --git a/test/tdclient.cpp b/test/tdclient.cpp index ca95fa4d8..6ced39da3 100644 --- a/test/tdclient.cpp +++ b/test/tdclient.cpp @@ -297,7 +297,8 @@ class SetUsername final : public TestClinetTask { CHECK(res->get_id() == td::td_api::user::ID); auto user = td::move_tl_object_as(res); self_id_ = user->id_; - if (user->username_ != username_) { + auto current_username = user->usernames_ != nullptr ? user->usernames_->editable_username_ : td::string(); + if (current_username != username_) { LOG(INFO) << "SET USERNAME: " << username_; send_query(td::make_tl_object(username_), [this](auto res) { CHECK(res->get_id() == td::td_api::ok::ID);