/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_values.h" #include "api/api_chat_participants.h" #include "apiwrap.h" #include "info/profile/info_profile_phone_menu.h" #include "info/profile/info_profile_badge.h" #include "core/application.h" #include "core/click_handler_types.h" #include "countries/countries_instance.h" #include "main/main_session.h" #include "ui/wrap/slide_wrap.h" #include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" #include "lang/lang_keys.h" #include "data/notify/data_notify_settings.h" #include "data/data_peer_values.h" #include "data/data_saved_messages.h" #include "data/data_saved_sublist.h" #include "data/data_shared_media.h" #include "data/data_message_reactions.h" #include "data/data_folder.h" #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_forum_topic.h" #include "data/data_session.h" #include "data/data_premium_limits.h" #include "boxes/peers/edit_peer_permissions_box.h" #include "base/unixtime.h" namespace Info { namespace Profile { namespace { using UpdateFlag = Data::PeerUpdate::Flag; auto PlainAboutValue(not_null peer) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::About ) | rpl::map([=] { return peer->about(); }); } auto PlainUsernameValue(not_null peer) { return rpl::merge( peer->session().changes().peerFlagsValue(peer, UpdateFlag::Username), peer->session().changes().peerFlagsValue(peer, UpdateFlag::Usernames) ) | rpl::map([=] { return peer->username(); }); } auto PlainPrimaryUsernameValue(not_null peer) { return UsernamesValue( peer ) | rpl::map([=](std::vector usernames) { if (!usernames.empty()) { return rpl::single(usernames.front().text) | rpl::type_erased(); } else { return PlainUsernameValue(peer) | rpl::type_erased(); } }) | rpl::flatten_latest(); } void StripExternalLinks(TextWithEntities &text) { const auto local = [](const QString &url) { return !UrlRequiresConfirmation(QUrl::fromUserInput(url)); }; const auto notLocal = [&](const EntityInText &entity) { if (entity.type() == EntityType::CustomUrl) { return !local(entity.data()); } else if (entity.type() == EntityType::Url) { return !local(text.text.mid(entity.offset(), entity.length())); } else { return false; } }; text.entities.erase( ranges::remove_if(text.entities, notLocal), text.entities.end()); } } // namespace rpl::producer NameValue(not_null peer) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Name ) | rpl::map([=] { return peer->name(); }); } rpl::producer TitleValue(not_null topic) { return topic->session().changes().topicFlagsValue( topic, Data::TopicUpdate::Flag::Title ) | rpl::map([=] { return topic->title(); }); } rpl::producer IconIdValue(not_null topic) { return topic->session().changes().topicFlagsValue( topic, Data::TopicUpdate::Flag::IconId ) | rpl::map([=] { return topic->iconId(); }); } rpl::producer ColorIdValue(not_null topic) { return topic->session().changes().topicFlagsValue( topic, Data::TopicUpdate::Flag::ColorId ) | rpl::map([=] { return topic->colorId(); }); } rpl::producer PhoneValue(not_null user) { return rpl::merge( Countries::Instance().updated(), user->session().changes().peerFlagsValue( user, UpdateFlag::PhoneNumber) | rpl::to_empty ) | rpl::map([=] { return Ui::FormatPhone(user->phone()); }) | Ui::Text::ToWithEntities(); } rpl::producer PhoneOrHiddenValue(not_null user) { return rpl::combine( PhoneValue(user), PlainUsernameValue(user), PlainAboutValue(user), tr::lng_info_mobile_hidden() ) | rpl::map([user]( const TextWithEntities &phone, const QString &username, const QString &about, const QString &hidden) { if (phone.text.isEmpty() && username.isEmpty() && about.isEmpty()) { return Ui::Text::WithEntities(hidden); } else if (IsCollectiblePhone(user)) { return Ui::Text::Link(phone, u"internal:collectible_phone/"_q + user->phone() + '@' + QString::number(user->id.value)); } else { return phone; } }); } rpl::producer UsernameValue( not_null user, bool primary) { return (primary ? PlainPrimaryUsernameValue(user) : (PlainUsernameValue(user) | rpl::type_erased()) ) | rpl::map([](QString &&username) { return username.isEmpty() ? QString() : ('@' + username); }) | Ui::Text::ToWithEntities(); } QString UsernameUrl(not_null peer, const QString &username) { return peer->isUsernameEditable(username) ? peer->session().createInternalLinkFull(username) : (u"internal:collectible_username/"_q + username + "@" + QString::number(peer->id.value)); } rpl::producer> UsernamesValue( not_null peer) { const auto map = [=](const std::vector &usernames) { return ranges::views::all( usernames ) | ranges::views::transform([&](const QString &u) { return Ui::Text::Link(u, UsernameUrl(peer, u)); }) | ranges::to_vector; }; auto value = rpl::merge( peer->session().changes().peerFlagsValue(peer, UpdateFlag::Username), peer->session().changes().peerFlagsValue(peer, UpdateFlag::Usernames) ); if (const auto user = peer->asUser()) { return std::move(value) | rpl::map([=] { return map(user->usernames()); }); } else if (const auto channel = peer->asChannel()) { return std::move(value) | rpl::map([=] { return map(channel->usernames()); }); } else { return rpl::single(std::vector()); } } TextWithEntities AboutWithEntities( not_null peer, const QString &value) { auto flags = TextParseLinks | TextParseMentions; const auto user = peer->asUser(); const auto isBot = user && user->isBot(); const auto isPremium = user && user->isPremium(); if (!user) { flags |= TextParseHashtags; } else if (isBot) { flags |= TextParseHashtags | TextParseBotCommands; } const auto stripExternal = peer->isChat() || peer->isMegagroup() || (user && !isBot && !isPremium); auto result = TextWithEntities{ value }; TextUtilities::ParseEntities(result, flags); if (stripExternal) { StripExternalLinks(result); } return result; } rpl::producer AboutValue(not_null peer) { return PlainAboutValue( peer ) | rpl::map([peer](const QString &value) { return AboutWithEntities(peer, value); }); } rpl::producer LinkValue(not_null peer, bool primary) { return (primary ? PlainPrimaryUsernameValue(peer) : PlainUsernameValue(peer) | rpl::type_erased() ) | rpl::map([=](QString &&username) { return LinkWithUrl{ .text = (username.isEmpty() ? QString() : peer->session().createInternalLinkFull(username)), .url = (username.isEmpty() ? QString() : UsernameUrl(peer, username)), }; }); } rpl::producer LocationValue( not_null channel) { return channel->session().changes().peerFlagsValue( channel, UpdateFlag::ChannelLocation ) | rpl::map([=] { return channel->getLocation(); }); } rpl::producer NotificationsEnabledValue( not_null thread) { const auto topic = thread->asTopic(); if (!topic) { return NotificationsEnabledValue(thread->peer()); } return rpl::merge( topic->session().changes().topicFlagsValue( topic, Data::TopicUpdate::Flag::Notifications ) | rpl::to_empty, topic->session().changes().peerUpdates( topic->channel(), UpdateFlag::Notifications ) | rpl::to_empty, topic->owner().notifySettings().defaultUpdates(topic->channel()) ) | rpl::map([=] { return !topic->owner().notifySettings().isMuted(topic); }) | rpl::distinct_until_changed(); } rpl::producer NotificationsEnabledValue(not_null peer) { return rpl::merge( peer->session().changes().peerFlagsValue( peer, UpdateFlag::Notifications ) | rpl::to_empty, peer->owner().notifySettings().defaultUpdates(peer) ) | rpl::map([=] { return !peer->owner().notifySettings().isMuted(peer); }) | rpl::distinct_until_changed(); } rpl::producer IsContactValue(not_null user) { return user->session().changes().peerFlagsValue( user, UpdateFlag::IsContact ) | rpl::map([=] { return user->isContact(); }); } [[nodiscard]] rpl::producer InviteToChatButton( not_null user) { if (!user->isBot() || user->isRepliesChat() || user->isSupport()) { return rpl::single(QString()); } using Flag = Data::PeerUpdate::Flag; return user->session().changes().peerFlagsValue( user, Flag::BotCanBeInvited | Flag::Rights ) | rpl::map([=] { const auto info = user->botInfo.get(); return info->cantJoinGroups ? (info->channelAdminRights ? tr::lng_profile_invite_to_channel(tr::now) : QString()) : (info->channelAdminRights ? tr::lng_profile_add_bot_as_admin(tr::now) : tr::lng_profile_invite_to_group(tr::now)); }); } [[nodiscard]] rpl::producer InviteToChatAbout( not_null user) { if (!user->isBot() || user->isRepliesChat() || user->isSupport()) { return rpl::single(QString()); } using Flag = Data::PeerUpdate::Flag; return user->session().changes().peerFlagsValue( user, Flag::BotCanBeInvited | Flag::Rights ) | rpl::map([=] { const auto info = user->botInfo.get(); return (info->cantJoinGroups || !info->groupAdminRights) ? (info->channelAdminRights ? tr::lng_profile_invite_to_channel_about(tr::now) : QString()) : (info->channelAdminRights ? tr::lng_profile_add_bot_as_admin_about(tr::now) : tr::lng_profile_invite_to_group_about(tr::now)); }); } rpl::producer CanShareContactValue(not_null user) { return user->session().changes().peerFlagsValue( user, UpdateFlag::CanShareContact ) | rpl::map([=] { return user->canShareThisContact(); }); } rpl::producer CanAddContactValue(not_null user) { using namespace rpl::mappers; if (user->isBot() || user->isSelf() || user->isInaccessible()) { return rpl::single(false); } return IsContactValue( user ) | rpl::map(!_1); } rpl::producer BirthdayValue(not_null user) { return user->session().changes().peerFlagsValue( user, UpdateFlag::Birthday ) | rpl::map([=] { return user->birthday(); }); } rpl::producer PersonalChannelValue(not_null user) { return user->session().changes().peerFlagsValue( user, UpdateFlag::PersonalChannel ) | rpl::map([=] { const auto channelId = user->personalChannelId(); return channelId ? user->owner().channel(channelId).get() : nullptr; }); } rpl::producer AmInChannelValue(not_null channel) { return channel->session().changes().peerFlagsValue( channel, UpdateFlag::ChannelAmIn ) | rpl::map([=] { return channel->amIn(); }); } rpl::producer MembersCountValue(not_null peer) { if (const auto chat = peer->asChat()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Members ) | rpl::map([=] { return chat->amIn() ? std::max(chat->count, int(chat->participants.size())) : 0; }); } else if (const auto channel = peer->asChannel()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Members ) | rpl::map([=] { return channel->membersCount(); }); } Unexpected("User in MembersCountViewer()."); } rpl::producer PendingRequestsCountValue(not_null peer) { if (const auto chat = peer->asChat()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::PendingRequests ) | rpl::map([=] { return chat->pendingRequestsCount(); }); } else if (const auto channel = peer->asChannel()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::PendingRequests ) | rpl::map([=] { return channel->pendingRequestsCount(); }); } Unexpected("User in MembersCountViewer()."); } rpl::producer AdminsCountValue(not_null peer) { if (const auto chat = peer->asChat()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Admins | UpdateFlag::Rights ) | rpl::map([=] { return chat->participants.empty() ? 0 : int(chat->admins.size() + (chat->creator ? 1 : 0)); }); } else if (const auto channel = peer->asChannel()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Admins | UpdateFlag::Rights ) | rpl::map([=] { return channel->canViewAdmins() ? channel->adminsCount() : 0; }); } Unexpected("User in AdminsCountValue()."); } rpl::producer RestrictionsCountValue(not_null peer) { const auto countOfRestrictions = []( Data::RestrictionsSetOptions options, ChatRestrictions restrictions) { auto count = 0; const auto list = Data::ListOfRestrictions(options); for (const auto &f : list) { if (restrictions & f) count++; } return int(list.size()) - count; }; if (const auto chat = peer->asChat()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Rights ) | rpl::map([=] { return countOfRestrictions({}, chat->defaultRestrictions()); }); } else if (const auto channel = peer->asChannel()) { return rpl::combine( Data::PeerFlagValue(channel, ChannelData::Flag::Forum), channel->session().changes().peerFlagsValue( channel, UpdateFlag::Rights) ) | rpl::map([=] { return countOfRestrictions( { .isForum = channel->isForum() }, channel->defaultRestrictions()); }); } Unexpected("User in RestrictionsCountValue()."); } rpl::producer> MigratedOrMeValue( not_null peer) { if (const auto chat = peer->asChat()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Migration ) | rpl::map([=] { return chat->migrateToOrMe(); }); } else { return rpl::single(peer); } } rpl::producer RestrictedCountValue(not_null channel) { return channel->session().changes().peerFlagsValue( channel, UpdateFlag::BannedUsers | UpdateFlag::Rights ) | rpl::map([=] { return channel->canViewBanned() ? channel->restrictedCount() : 0; }); } rpl::producer KickedCountValue(not_null channel) { return channel->session().changes().peerFlagsValue( channel, UpdateFlag::BannedUsers | UpdateFlag::Rights ) | rpl::map([=] { return channel->canViewBanned() ? channel->kickedCount() : 0; }); } rpl::producer SharedMediaCountValue( not_null peer, MsgId topicRootId, PeerData *migrated, Storage::SharedMediaType type) { auto aroundId = 0; auto limit = 0; auto updated = SharedMediaMergedViewer( &peer->session(), SharedMediaMergedKey( SparseIdsMergedSlice::Key( peer->id, topicRootId, migrated ? migrated->id : 0, aroundId), type), limit, limit ) | rpl::map([](const SparseIdsMergedSlice &slice) { return slice.fullCount(); }) | rpl::filter_optional(); return rpl::single(0) | rpl::then(std::move(updated)); } rpl::producer CommonGroupsCountValue(not_null user) { return user->session().changes().peerFlagsValue( user, UpdateFlag::CommonChats ) | rpl::map([=] { return user->commonChatsCount(); }); } rpl::producer SimilarChannelsCountValue( not_null channel) { const auto participants = &channel->session().api().chatParticipants(); participants->loadSimilarChannels(channel); return rpl::single(channel) | rpl::then( participants->similarLoaded() ) | rpl::filter( rpl::mappers::_1 == channel ) | rpl::map([=] { const auto &similar = participants->similar(channel); return int(similar.list.size()) + similar.more; }); } rpl::producer SavedSublistCountValue( not_null peer) { const auto saved = &peer->owner().savedMessages(); const auto sublist = saved->sublist(peer); if (!sublist->fullCount()) { saved->loadMore(sublist); return rpl::single(0) | rpl::then(sublist->fullCountValue()); } return sublist->fullCountValue(); } rpl::producer CanAddMemberValue(not_null peer) { if (const auto chat = peer->asChat()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Rights ) | rpl::map([=] { return chat->canAddMembers(); }); } else if (const auto channel = peer->asChannel()) { return peer->session().changes().peerFlagsValue( peer, UpdateFlag::Rights ) | rpl::map([=] { return channel->canAddMembers(); }); } return rpl::single(false); } rpl::producer FullReactionsCountValue( not_null session) { const auto reactions = &session->data().reactions(); return rpl::single(rpl::empty) | rpl::then( reactions->defaultUpdates() ) | rpl::map([=] { return int(reactions->list(Data::Reactions::Type::Active).size()); }) | rpl::distinct_until_changed(); } rpl::producer CanViewParticipantsValue( not_null megagroup) { if (megagroup->amCreator()) { return rpl::single(true); } return rpl::combine( megagroup->session().changes().peerFlagsValue( megagroup, UpdateFlag::Rights), megagroup->flagsValue(), [=] { return megagroup->canViewMembers(); } ) | rpl::distinct_until_changed(); } template rpl::producer BadgeValueFromFlags(Peer peer) { return rpl::combine( Data::PeerFlagsValue( peer, Flag::Verified | Flag::Scam | Flag::Fake), Data::PeerPremiumValue(peer) ) | rpl::map([=](base::flags value, bool premium) { return (value & Flag::Scam) ? BadgeType::Scam : (value & Flag::Fake) ? BadgeType::Fake : (value & Flag::Verified) ? BadgeType::Verified : premium ? BadgeType::Premium : BadgeType::None; }); } rpl::producer BadgeValue(not_null peer) { if (const auto user = peer->asUser()) { return BadgeValueFromFlags(user); } else if (const auto channel = peer->asChannel()) { return BadgeValueFromFlags(channel); } return rpl::single(BadgeType::None); } rpl::producer EmojiStatusIdValue(not_null peer) { if (peer->isChat()) { return rpl::single(DocumentId(0)); } return peer->session().changes().peerFlagsValue( peer, Data::PeerUpdate::Flag::EmojiStatus ) | rpl::map([=] { return peer->emojiStatusId(); }); } rpl::producer BirthdayLabelText( rpl::producer birthday) { return std::move(birthday) | rpl::map([](Data::Birthday value) { return rpl::conditional( Data::IsBirthdayTodayValue(value), tr::lng_info_birthday_today_label(), tr::lng_info_birthday_label()); }) | rpl::flatten_latest(); } rpl::producer BirthdayValueText( rpl::producer birthday) { return std::move( birthday ) | rpl::map([](Data::Birthday value) -> rpl::producer { if (!value) { return rpl::single(QString()); } return Data::IsBirthdayTodayValue( value ) | rpl::map([=](bool today) { auto text = Data::BirthdayText(value); if (const auto age = Data::BirthdayAge(value)) { text = (today ? tr::lng_info_birthday_today_years : tr::lng_info_birthday_years)( tr::now, lt_count, age, lt_date, text); } if (today) { text = tr::lng_info_birthday_today( tr::now, lt_emoji, Data::BirthdayCake(), lt_date, text); } return text; }); }) | rpl::flatten_latest(); } } // namespace Profile } // namespace Info