/* 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 "data/data_channel.h" #include "data/data_peer_values.h" #include "data/data_channel_admins.h" #include "data/data_user.h" #include "data/data_chat.h" #include "data/data_session.h" #include "data/data_feed.h" #include "observer_peer.h" #include "auth_session.h" #include "apiwrap.h" namespace { using UpdateFlag = Notify::PeerUpdate::Flag; } // namespace ChatData *MegagroupInfo::getMigrateFromChat() const { return _migratedFrom; } void MegagroupInfo::setMigrateFromChat(ChatData *chat) { _migratedFrom = chat; } ChannelData::ChannelData(not_null owner, PeerId id) : PeerData(owner, id) , inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) { Data::PeerFlagValue( this, MTPDchannel::Flag::f_megagroup ) | rpl::start_with_next([=](bool megagroup) { if (megagroup) { if (!mgInfo) { mgInfo = std::make_unique(); } } else if (mgInfo) { mgInfo = nullptr; } }, _lifetime); Data::PeerFlagsValue( this, MTPDchannel::Flag::f_left | MTPDchannel_ClientFlag::f_forbidden ) | rpl::distinct_until_changed( ) | rpl::start_with_next([=] { if (const auto chat = getMigrateFromChat()) { Notify::peerUpdatedDelayed(chat, UpdateFlag::MigrationChanged); Notify::peerUpdatedDelayed(this, UpdateFlag::MigrationChanged); } }, _lifetime); } void ChannelData::setPhoto(const MTPChatPhoto &photo) { setPhoto(userpicPhotoId(), photo); } void ChannelData::setPhoto(PhotoId photoId, const MTPChatPhoto &photo) { photo.match([&](const MTPDchatPhoto & data) { updateUserpic(photoId, data.vdc_id.v, data.vphoto_small); }, [&](const MTPDchatPhotoEmpty &) { clearUserpic(); }); } void ChannelData::setName(const QString &newName, const QString &newUsername) { updateNameDelayed(newName.isEmpty() ? name : newName, QString(), newUsername); } void ChannelData::setInviteLink(const QString &newInviteLink) { if (newInviteLink != _inviteLink) { _inviteLink = newInviteLink; Notify::peerUpdatedDelayed(this, UpdateFlag::InviteLinkChanged); } } QString ChannelData::inviteLink() const { return _inviteLink; } bool ChannelData::canHaveInviteLink() const { return (adminRights() & AdminRight::f_invite_users) || amCreator(); } void ChannelData::setMembersCount(int newMembersCount) { if (_membersCount != newMembersCount) { if (isMegagroup() && !mgInfo->lastParticipants.empty()) { mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; mgInfo->lastParticipantsCount = membersCount(); } _membersCount = newMembersCount; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::MembersChanged); } } void ChannelData::setAdminsCount(int newAdminsCount) { if (_adminsCount != newAdminsCount) { _adminsCount = newAdminsCount; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::AdminsChanged); } } void ChannelData::setRestrictedCount(int newRestrictedCount) { if (_restrictedCount != newRestrictedCount) { _restrictedCount = newRestrictedCount; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BannedUsersChanged); } } void ChannelData::setKickedCount(int newKickedCount) { if (_kickedCount != newKickedCount) { _kickedCount = newKickedCount; Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BannedUsersChanged); } } MTPChatBannedRights ChannelData::KickedRestrictedRights() { using Flag = MTPDchatBannedRights::Flag; const auto flags = Flag::f_view_messages | Flag::f_send_messages | Flag::f_send_media | Flag::f_embed_links | Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_games | Flag::f_send_inline; return MTP_chatBannedRights( MTP_flags(flags), MTP_int(std::numeric_limits::max())); } void ChannelData::applyEditAdmin( not_null user, const MTPChatAdminRights &oldRights, const MTPChatAdminRights &newRights) { if (mgInfo) { // If rights are empty - still add participant? TODO check if (!base::contains(mgInfo->lastParticipants, user)) { mgInfo->lastParticipants.push_front(user); setMembersCount(membersCount() + 1); if (user->botInfo && !mgInfo->bots.contains(user)) { mgInfo->bots.insert(user); if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) { mgInfo->botStatus = 2; } } } // If rights are empty - still remove restrictions? TODO check if (mgInfo->lastRestricted.contains(user)) { mgInfo->lastRestricted.remove(user); if (restrictedCount() > 0) { setRestrictedCount(restrictedCount() - 1); } } auto userId = peerToUser(user->id); auto it = mgInfo->lastAdmins.find(user); if (newRights.c_chatAdminRights().vflags.v != 0) { auto lastAdmin = MegagroupInfo::Admin { newRights }; lastAdmin.canEdit = true; if (it == mgInfo->lastAdmins.cend()) { mgInfo->lastAdmins.emplace(user, lastAdmin); setAdminsCount(adminsCount() + 1); } else { it->second = lastAdmin; } Data::ChannelAdminChanges(this).feed(userId, true); } else { if (it != mgInfo->lastAdmins.cend()) { mgInfo->lastAdmins.erase(it); if (adminsCount() > 0) { setAdminsCount(adminsCount() - 1); } } Data::ChannelAdminChanges(this).feed(userId, false); } } if (oldRights.c_chatAdminRights().vflags.v && !newRights.c_chatAdminRights().vflags.v) { // We removed an admin. if (adminsCount() > 1) { setAdminsCount(adminsCount() - 1); } if (!isMegagroup() && user->botInfo && membersCount() > 1) { // Removing bot admin removes it from channel. setMembersCount(membersCount() - 1); } } else if (!oldRights.c_chatAdminRights().vflags.v && newRights.c_chatAdminRights().vflags.v) { // We added an admin. setAdminsCount(adminsCount() + 1); updateFullForced(); } Notify::peerUpdatedDelayed( this, Notify::PeerUpdate::Flag::AdminsChanged); } void ChannelData::applyEditBanned(not_null user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights) { auto flags = Notify::PeerUpdate::Flag::BannedUsersChanged | Notify::PeerUpdate::Flag::None; auto isKicked = (newRights.c_chatBannedRights().vflags.v & MTPDchatBannedRights::Flag::f_view_messages); auto isRestricted = !isKicked && (newRights.c_chatBannedRights().vflags.v != 0); if (mgInfo) { // If rights are empty - still remove admin? TODO check if (mgInfo->lastAdmins.contains(user)) { mgInfo->lastAdmins.remove(user); if (adminsCount() > 1) { setAdminsCount(adminsCount() - 1); } else { flags |= Notify::PeerUpdate::Flag::AdminsChanged; } } auto it = mgInfo->lastRestricted.find(user); if (isRestricted) { if (it == mgInfo->lastRestricted.cend()) { mgInfo->lastRestricted.emplace(user, MegagroupInfo::Restricted { newRights }); setRestrictedCount(restrictedCount() + 1); } else { it->second.rights = newRights; } } else { if (it != mgInfo->lastRestricted.cend()) { mgInfo->lastRestricted.erase(it); if (restrictedCount() > 0) { setRestrictedCount(restrictedCount() - 1); } } if (isKicked) { auto i = ranges::find(mgInfo->lastParticipants, user); if (i != mgInfo->lastParticipants.end()) { mgInfo->lastParticipants.erase(i); } if (membersCount() > 1) { setMembersCount(membersCount() - 1); } else { mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; mgInfo->lastParticipantsCount = 0; } setKickedCount(kickedCount() + 1); if (mgInfo->bots.contains(user)) { mgInfo->bots.remove(user); if (mgInfo->bots.empty() && mgInfo->botStatus > 0) { mgInfo->botStatus = -1; } } flags |= Notify::PeerUpdate::Flag::MembersChanged; owner().removeMegagroupParticipant(this, user); } } Data::ChannelAdminChanges(this).feed(peerToUser(user->id), false); } else { if (isKicked) { if (membersCount() > 1) { setMembersCount(membersCount() - 1); flags |= Notify::PeerUpdate::Flag::MembersChanged; } setKickedCount(kickedCount() + 1); } } Notify::peerUpdatedDelayed(this, flags); } bool ChannelData::isGroupAdmin(not_null user) const { if (auto info = mgInfo.get()) { return info->admins.contains(peerToUser(user->id)); } return false; } QString ChannelData::unavailableReason() const { return _unavailableReason; } void ChannelData::setUnavailableReason(const QString &text) { if (_unavailableReason != text) { _unavailableReason = text; Notify::peerUpdatedDelayed( this, Notify::PeerUpdate::Flag::UnavailableReasonChanged); } } void ChannelData::setAvailableMinId(MsgId availableMinId) { if (_availableMinId != availableMinId) { _availableMinId = availableMinId; if (pinnedMessageId() <= _availableMinId) { clearPinnedMessage(); } } } void ChannelData::setFeed(not_null feed) { setFeedPointer(feed); } void ChannelData::clearFeed() { setFeedPointer(nullptr); } void ChannelData::setFeedPointer(Data::Feed *feed) { if (_feed != feed) { const auto was = _feed; _feed = feed; if (was) { was->unregisterOne(this); } if (_feed) { _feed->registerOne(this); } } } bool ChannelData::canBanMembers() const { return amCreator() || (adminRights() & AdminRight::f_ban_users); } bool ChannelData::canEditMessages() const { return amCreator() || (adminRights() & AdminRight::f_edit_messages); } bool ChannelData::canDeleteMessages() const { return amCreator() || (adminRights() & AdminRight::f_delete_messages); } bool ChannelData::anyoneCanAddMembers() const { return !(defaultRestrictions() & Restriction::f_invite_users); } bool ChannelData::hiddenPreHistory() const { return (fullFlags() & MTPDchannelFull::Flag::f_hidden_prehistory); } bool ChannelData::canAddMembers() const { return isMegagroup() ? !amRestricted(ChatRestriction::f_invite_users) : ((adminRights() & AdminRight::f_invite_users) || amCreator()); } bool ChannelData::canSendPolls() const { return canWrite() && !amRestricted(ChatRestriction::f_send_polls); } bool ChannelData::canAddAdmins() const { return amCreator() || (adminRights() & AdminRight::f_add_admins); } bool ChannelData::canPublish() const { return amCreator() || (adminRights() & AdminRight::f_post_messages); } bool ChannelData::canWrite() const { // Duplicated in Data::CanWriteValue(). return amIn() && (canPublish() || (!isBroadcast() && !amRestricted(Restriction::f_send_messages))); } bool ChannelData::canViewMembers() const { return fullFlags() & MTPDchannelFull::Flag::f_can_view_participants; } bool ChannelData::canViewAdmins() const { return (isMegagroup() || hasAdminRights() || amCreator()); } bool ChannelData::canViewBanned() const { return (hasAdminRights() || amCreator()); } bool ChannelData::canEditInformation() const { return isMegagroup() ? !amRestricted(Restriction::f_change_info) : ((adminRights() & AdminRight::f_change_info) || amCreator()); } bool ChannelData::canEditPermissions() const { return isMegagroup() && ((adminRights() & AdminRight::f_ban_users) || amCreator()); } bool ChannelData::canEditSignatures() const { return isChannel() && canEditInformation(); } bool ChannelData::canEditPreHistoryHidden() const { return isMegagroup() && ((adminRights() & AdminRight::f_ban_users) || amCreator()) && (!isPublic() || canEditUsername()); } bool ChannelData::canEditUsername() const { return amCreator() && (fullFlags() & MTPDchannelFull::Flag::f_can_set_username); } bool ChannelData::canEditStickers() const { return (fullFlags() & MTPDchannelFull::Flag::f_can_set_stickers); } bool ChannelData::canDelete() const { constexpr auto kDeleteChannelMembersLimit = 1000; return amCreator() && (membersCount() <= kDeleteChannelMembersLimit); } bool ChannelData::canEditLastAdmin(not_null user) const { // Duplicated in ParticipantsBoxController::canEditAdmin :( if (mgInfo) { auto i = mgInfo->lastAdmins.find(user); if (i != mgInfo->lastAdmins.cend()) { return i->second.canEdit; } return (user != mgInfo->creator); } return false; } bool ChannelData::canEditAdmin(not_null user) const { // Duplicated in ParticipantsBoxController::canEditAdmin :( if (user->isSelf()) { return false; } else if (amCreator()) { return true; } else if (!canEditLastAdmin(user)) { return false; } return adminRights() & AdminRight::f_add_admins; } bool ChannelData::canRestrictUser(not_null user) const { // Duplicated in ParticipantsBoxController::canRestrictUser :( if (user->isSelf()) { return false; } else if (amCreator()) { return true; } else if (!canEditLastAdmin(user)) { return false; } return adminRights() & AdminRight::f_ban_users; } void ChannelData::setAdminRights(const MTPChatAdminRights &rights) { if (rights.c_chatAdminRights().vflags.v == adminRights()) { return; } _adminRights.set(rights.c_chatAdminRights().vflags.v); if (isMegagroup()) { const auto self = session().user(); if (hasAdminRights()) { if (!amCreator()) { auto me = MegagroupInfo::Admin { rights }; me.canEdit = false; mgInfo->lastAdmins.emplace(self, me); } mgInfo->lastRestricted.remove(self); } else { mgInfo->lastAdmins.remove(self); } auto amAdmin = hasAdminRights() || amCreator(); Data::ChannelAdminChanges(this).feed(session().userId(), amAdmin); } Notify::peerUpdatedDelayed(this, UpdateFlag::RightsChanged | UpdateFlag::AdminsChanged | UpdateFlag::BannedUsersChanged); } void ChannelData::setRestrictions(const MTPChatBannedRights &rights) { if (rights.c_chatBannedRights().vflags.v == restrictions() && rights.c_chatBannedRights().vuntil_date.v == _restrictedUntil) { return; } _restrictedUntil = rights.c_chatBannedRights().vuntil_date.v; _restrictions.set(rights.c_chatBannedRights().vflags.v); if (isMegagroup()) { const auto self = session().user(); if (hasRestrictions()) { if (!amCreator()) { auto me = MegagroupInfo::Restricted { rights }; mgInfo->lastRestricted.emplace(self, me); } mgInfo->lastAdmins.remove(self); Data::ChannelAdminChanges(this).feed(session().userId(), false); } else { mgInfo->lastRestricted.remove(self); } } Notify::peerUpdatedDelayed(this, UpdateFlag::RightsChanged | UpdateFlag::AdminsChanged | UpdateFlag::BannedUsersChanged); } void ChannelData::setDefaultRestrictions(const MTPChatBannedRights &rights) { if (rights.c_chatBannedRights().vflags.v == defaultRestrictions()) { return; } _defaultRestrictions.set(rights.c_chatBannedRights().vflags.v); Notify::peerUpdatedDelayed(this, UpdateFlag::RightsChanged); } auto ChannelData::applyUpdateVersion(int version) -> UpdateStatus { if (_version > version) { return UpdateStatus::TooOld; } else if (_version + 1 < version) { session().api().requestPeer(this); return UpdateStatus::Skipped; } setVersion(version); return UpdateStatus::Good; } ChatData *ChannelData::getMigrateFromChat() const { if (const auto info = mgInfo.get()) { return info->getMigrateFromChat(); } return nullptr; } void ChannelData::setMigrateFromChat(ChatData *chat) { Expects(mgInfo != nullptr); const auto info = mgInfo.get(); if (chat != info->getMigrateFromChat()) { info->setMigrateFromChat(chat); if (amIn()) { Notify::peerUpdatedDelayed(this, UpdateFlag::MigrationChanged); } } } namespace Data { void ApplyMigration( not_null chat, not_null channel) { Expects(channel->isMegagroup()); chat->setMigrateToChannel(channel); channel->setMigrateFromChat(chat); } void ApplyChannelUpdate( not_null channel, const MTPDupdateChatDefaultBannedRights &update) { if (channel->applyUpdateVersion(update.vversion.v) != ChannelData::UpdateStatus::Good) { return; } channel->setDefaultRestrictions(update.vdefault_banned_rights); } } // namespace Data