init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
591
Telegram/SourceFiles/data/data_chat.cpp
Normal file
591
Telegram/SourceFiles/data/data_chat.cpp
Normal file
@@ -0,0 +1,591 @@
|
||||
/*
|
||||
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_chat.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_invite_links.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
|
||||
} // namespace
|
||||
|
||||
ChatData::ChatData(not_null<Data::Session*> owner, PeerId id)
|
||||
: PeerData(owner, id) {
|
||||
_flags.changes(
|
||||
) | rpl::on_next([=](const Flags::Change &change) {
|
||||
if (change.diff & Flag::CallNotEmpty) {
|
||||
if (const auto history = this->owner().historyLoaded(this)) {
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void ChatData::setPhoto(const MTPChatPhoto &photo) {
|
||||
photo.match([&](const MTPDchatPhoto &data) {
|
||||
updateUserpic(
|
||||
data.vphoto_id().v,
|
||||
data.vdc_id().v,
|
||||
data.is_has_video());
|
||||
}, [&](const MTPDchatPhotoEmpty &) {
|
||||
clearUserpic();
|
||||
});
|
||||
}
|
||||
|
||||
ChatAdminRightsInfo ChatData::defaultAdminRights(not_null<UserData*> user) {
|
||||
const auto isCreator = (creator == peerToUser(user->id))
|
||||
|| (user->isSelf() && amCreator());
|
||||
using Flag = ChatAdminRight;
|
||||
return ChatAdminRightsInfo(Flag::Other
|
||||
| Flag::ChangeInfo
|
||||
| Flag::DeleteMessages
|
||||
| Flag::BanUsers
|
||||
| Flag::InviteByLinkOrAdd
|
||||
| Flag::PinMessages
|
||||
| Flag::ManageCall
|
||||
| (isCreator ? Flag::AddAdmins : Flag(0)));
|
||||
}
|
||||
|
||||
bool ChatData::allowsForwarding() const {
|
||||
return !(flags() & Flag::NoForwards);
|
||||
}
|
||||
|
||||
bool ChatData::canEditInformation() const {
|
||||
return amIn() && !amRestricted(ChatRestriction::ChangeInfo);
|
||||
}
|
||||
|
||||
bool ChatData::canEditPermissions() const {
|
||||
return amIn()
|
||||
&& (amCreator() || (adminRights() & ChatAdminRight::BanUsers));
|
||||
}
|
||||
|
||||
bool ChatData::canEditUsername() const {
|
||||
return amCreator()
|
||||
&& (flags() & Flag::CanSetUsername);
|
||||
}
|
||||
|
||||
bool ChatData::canEditPreHistoryHidden() const {
|
||||
return amCreator();
|
||||
}
|
||||
|
||||
bool ChatData::canDeleteMessages() const {
|
||||
return amCreator()
|
||||
|| (adminRights() & ChatAdminRight::DeleteMessages);
|
||||
}
|
||||
|
||||
bool ChatData::canAddMembers() const {
|
||||
return amIn() && !amRestricted(ChatRestriction::AddParticipants);
|
||||
}
|
||||
|
||||
bool ChatData::canAddAdmins() const {
|
||||
return amIn() && amCreator();
|
||||
}
|
||||
|
||||
bool ChatData::canBanMembers() const {
|
||||
return amCreator()
|
||||
|| (adminRights() & ChatAdminRight::BanUsers);
|
||||
}
|
||||
|
||||
bool ChatData::anyoneCanAddMembers() const {
|
||||
return !(defaultRestrictions() & ChatRestriction::AddParticipants);
|
||||
}
|
||||
|
||||
void ChatData::setName(const QString &newName) {
|
||||
updateNameDelayed(newName.isEmpty() ? name() : newName, {}, {});
|
||||
}
|
||||
|
||||
void ChatData::applyEditAdmin(not_null<UserData*> user, bool isAdmin) {
|
||||
if (isAdmin) {
|
||||
admins.emplace(user);
|
||||
} else {
|
||||
admins.remove(user);
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::Admins);
|
||||
}
|
||||
|
||||
void ChatData::invalidateParticipants() {
|
||||
participants.clear();
|
||||
admins.clear();
|
||||
setAdminRights(ChatAdminRights());
|
||||
//setDefaultRestrictions(ChatRestrictions());
|
||||
invitedByMe.clear();
|
||||
botStatus = 0;
|
||||
session().changes().peerUpdated(
|
||||
this,
|
||||
UpdateFlag::Members | UpdateFlag::Admins);
|
||||
}
|
||||
|
||||
void ChatData::setFlags(ChatDataFlags which) {
|
||||
const auto wasIn = amIn();
|
||||
_flags.set(which);
|
||||
if (wasIn && !amIn()) {
|
||||
crl::on_main(&session(), [=] {
|
||||
if (!amIn()) {
|
||||
Core::App().closeChatFromWindows(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ChatData::setInviteLink(const QString &newInviteLink) {
|
||||
_inviteLink = newInviteLink;
|
||||
}
|
||||
|
||||
bool ChatData::canHaveInviteLink() const {
|
||||
return amCreator()
|
||||
|| (adminRights() & ChatAdminRight::InviteByLinkOrAdd);
|
||||
}
|
||||
|
||||
void ChatData::setAdminRights(ChatAdminRights rights) {
|
||||
if (rights == adminRights()) {
|
||||
return;
|
||||
}
|
||||
_adminRights.set(rights);
|
||||
if (!canHaveInviteLink()) {
|
||||
setPendingRequestsCount(0, std::vector<UserId>{});
|
||||
}
|
||||
session().changes().peerUpdated(
|
||||
this,
|
||||
UpdateFlag::Rights | UpdateFlag::Admins | UpdateFlag::BannedUsers);
|
||||
}
|
||||
|
||||
void ChatData::setDefaultRestrictions(ChatRestrictions rights) {
|
||||
if (rights == defaultRestrictions()) {
|
||||
return;
|
||||
}
|
||||
_defaultRestrictions.set(rights);
|
||||
session().changes().peerUpdated(this, UpdateFlag::Rights);
|
||||
}
|
||||
|
||||
void ChatData::refreshBotStatus() {
|
||||
if (participants.empty()) {
|
||||
botStatus = 0;
|
||||
} else {
|
||||
const auto bot = ranges::none_of(participants, &UserData::isBot);
|
||||
botStatus = bot ? -1 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
auto ChatData::applyUpdateVersion(int version) -> UpdateStatus {
|
||||
if (_version > version) {
|
||||
return UpdateStatus::TooOld;
|
||||
} else if (_version + 1 < version) {
|
||||
invalidateParticipants();
|
||||
session().api().requestFullPeer(this);
|
||||
return UpdateStatus::Skipped;
|
||||
}
|
||||
setVersion(version);
|
||||
return UpdateStatus::Good;
|
||||
}
|
||||
|
||||
ChannelData *ChatData::getMigrateToChannel() const {
|
||||
return _migratedTo;
|
||||
}
|
||||
|
||||
void ChatData::setMigrateToChannel(ChannelData *channel) {
|
||||
if (_migratedTo != channel) {
|
||||
_migratedTo = channel;
|
||||
if (channel->amIn()) {
|
||||
session().changes().peerUpdated(this, UpdateFlag::Migration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatData::setGroupCall(
|
||||
const MTPInputGroupCall &call,
|
||||
TimeId scheduleDate,
|
||||
bool rtmp) {
|
||||
if (migrateTo()) {
|
||||
return;
|
||||
}
|
||||
call.match([&](const MTPDinputGroupCall &data) {
|
||||
if (_call && _call->id() == data.vid().v) {
|
||||
return;
|
||||
} else if (!_call && !data.vid().v) {
|
||||
return;
|
||||
} else if (!data.vid().v) {
|
||||
clearGroupCall();
|
||||
return;
|
||||
}
|
||||
const auto hasCall = (_call != nullptr);
|
||||
if (hasCall) {
|
||||
owner().unregisterGroupCall(_call.get());
|
||||
}
|
||||
_call = std::make_unique<Data::GroupCall>(
|
||||
this,
|
||||
data.vid().v,
|
||||
data.vaccess_hash().v,
|
||||
scheduleDate,
|
||||
rtmp,
|
||||
Data::GroupCallOrigin::Group);
|
||||
owner().registerGroupCall(_call.get());
|
||||
session().changes().peerUpdated(this, UpdateFlag::GroupCall);
|
||||
addFlags(Flag::CallActive);
|
||||
}, [&](const auto &) {
|
||||
clearGroupCall();
|
||||
});
|
||||
}
|
||||
|
||||
void ChatData::clearGroupCall() {
|
||||
if (!_call) {
|
||||
return;
|
||||
} else if (const auto group = migrateTo(); group && !group->groupCall()) {
|
||||
group->migrateCall(base::take(_call));
|
||||
} else {
|
||||
owner().unregisterGroupCall(_call.get());
|
||||
_call = nullptr;
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::GroupCall);
|
||||
removeFlags(Flag::CallActive | Flag::CallNotEmpty);
|
||||
}
|
||||
|
||||
void ChatData::setGroupCallDefaultJoinAs(PeerId peerId) {
|
||||
_callDefaultJoinAs = peerId;
|
||||
}
|
||||
|
||||
PeerId ChatData::groupCallDefaultJoinAs() const {
|
||||
return _callDefaultJoinAs;
|
||||
}
|
||||
|
||||
void ChatData::setBotCommands(const std::vector<Data::BotCommands> &list) {
|
||||
if (_botCommands.update(list)) {
|
||||
owner().botCommandsChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatData::setPendingRequestsCount(
|
||||
int count,
|
||||
const QVector<MTPlong> &recentRequesters) {
|
||||
setPendingRequestsCount(count, ranges::views::all(
|
||||
recentRequesters
|
||||
) | ranges::views::transform([&](const MTPlong &value) {
|
||||
return UserId(value);
|
||||
}) | ranges::to_vector);
|
||||
}
|
||||
|
||||
void ChatData::setPendingRequestsCount(
|
||||
int count,
|
||||
std::vector<UserId> recentRequesters) {
|
||||
if (_pendingRequestsCount != count
|
||||
|| _recentRequesters != recentRequesters) {
|
||||
_pendingRequestsCount = count;
|
||||
_recentRequesters = std::move(recentRequesters);
|
||||
session().changes().peerUpdated(this, UpdateFlag::PendingRequests);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatData::setAllowedReactions(Data::AllowedReactions value) {
|
||||
if (_allowedReactions != value) {
|
||||
const auto enabled = [](const Data::AllowedReactions &allowed) {
|
||||
return (allowed.type != Data::AllowedReactionsType::Some)
|
||||
|| !allowed.some.empty()
|
||||
|| allowed.paidEnabled;
|
||||
};
|
||||
const auto was = enabled(_allowedReactions);
|
||||
_allowedReactions = std::move(value);
|
||||
const auto now = enabled(_allowedReactions);
|
||||
if (was != now) {
|
||||
owner().reactions().updateAllInHistory(this, now);
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::Reactions);
|
||||
}
|
||||
}
|
||||
|
||||
const Data::AllowedReactions &ChatData::allowedReactions() const {
|
||||
return _allowedReactions;
|
||||
}
|
||||
|
||||
MTPlong ChatData::inputChat() const {
|
||||
return MTP_long(peerToChat(id).bare);
|
||||
}
|
||||
|
||||
namespace Data {
|
||||
|
||||
void ApplyChatUpdate(
|
||||
not_null<ChatData*> chat,
|
||||
const MTPDupdateChatParticipants &update) {
|
||||
ApplyChatUpdate(chat, update.vparticipants());
|
||||
}
|
||||
|
||||
void ApplyChatUpdate(
|
||||
not_null<ChatData*> chat,
|
||||
const MTPDupdateChatParticipantAdd &update) {
|
||||
if (chat->applyUpdateVersion(update.vversion().v)
|
||||
!= ChatData::UpdateStatus::Good) {
|
||||
return;
|
||||
} else if (chat->count < 0) {
|
||||
return;
|
||||
}
|
||||
const auto user = chat->owner().userLoaded(update.vuser_id().v);
|
||||
const auto session = &chat->session();
|
||||
if (!user
|
||||
|| (!chat->participants.empty()
|
||||
&& chat->participants.contains(user))) {
|
||||
chat->invalidateParticipants();
|
||||
++chat->count;
|
||||
return;
|
||||
}
|
||||
if (chat->participants.empty()) {
|
||||
if (chat->count > 0) { // If the count is known.
|
||||
++chat->count;
|
||||
}
|
||||
chat->botStatus = 0;
|
||||
} else {
|
||||
chat->participants.emplace(user);
|
||||
if (UserId(update.vinviter_id()) == session->userId()) {
|
||||
chat->invitedByMe.insert(user);
|
||||
} else {
|
||||
chat->invitedByMe.remove(user);
|
||||
}
|
||||
++chat->count;
|
||||
if (user->isBot()) {
|
||||
chat->botStatus = 2;
|
||||
if (!user->botInfo->inited) {
|
||||
session->api().requestFullPeer(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
session->changes().peerUpdated(chat, UpdateFlag::Members);
|
||||
}
|
||||
|
||||
void ApplyChatUpdate(
|
||||
not_null<ChatData*> chat,
|
||||
const MTPDupdateChatParticipantDelete &update) {
|
||||
if (chat->applyUpdateVersion(update.vversion().v)
|
||||
!= ChatData::UpdateStatus::Good) {
|
||||
return;
|
||||
} else if (chat->count <= 0) {
|
||||
return;
|
||||
}
|
||||
const auto user = chat->owner().userLoaded(update.vuser_id().v);
|
||||
if (!user
|
||||
|| (!chat->participants.empty()
|
||||
&& !chat->participants.contains(user))) {
|
||||
chat->invalidateParticipants();
|
||||
--chat->count;
|
||||
return;
|
||||
}
|
||||
if (chat->participants.empty()) {
|
||||
if (chat->count > 0) {
|
||||
chat->count--;
|
||||
}
|
||||
chat->botStatus = 0;
|
||||
} else {
|
||||
chat->participants.erase(user);
|
||||
chat->count--;
|
||||
chat->invitedByMe.remove(user);
|
||||
chat->admins.remove(user);
|
||||
if (user->isSelf()) {
|
||||
chat->setAdminRights(ChatAdminRights());
|
||||
}
|
||||
if (const auto history = chat->owner().historyLoaded(chat)) {
|
||||
if (history->lastKeyboardFrom == user->id) {
|
||||
history->clearLastKeyboard();
|
||||
}
|
||||
}
|
||||
if (chat->botStatus > 0 && user->isBot()) {
|
||||
chat->refreshBotStatus();
|
||||
}
|
||||
}
|
||||
chat->session().changes().peerUpdated(chat, UpdateFlag::Members);
|
||||
}
|
||||
|
||||
void ApplyChatUpdate(
|
||||
not_null<ChatData*> chat,
|
||||
const MTPDupdateChatParticipantAdmin &update) {
|
||||
if (chat->applyUpdateVersion(update.vversion().v)
|
||||
!= ChatData::UpdateStatus::Good) {
|
||||
return;
|
||||
}
|
||||
const auto session = &chat->session();
|
||||
const auto user = chat->owner().userLoaded(update.vuser_id().v);
|
||||
if (!user) {
|
||||
chat->invalidateParticipants();
|
||||
return;
|
||||
}
|
||||
if (user->isSelf()) {
|
||||
chat->setAdminRights(mtpIsTrue(update.vis_admin())
|
||||
? chat->defaultAdminRights(user).flags
|
||||
: ChatAdminRights());
|
||||
}
|
||||
if (mtpIsTrue(update.vis_admin())) {
|
||||
if (chat->noParticipantInfo()) {
|
||||
session->api().requestFullPeer(chat);
|
||||
} else {
|
||||
chat->admins.emplace(user);
|
||||
}
|
||||
} else {
|
||||
chat->admins.erase(user);
|
||||
}
|
||||
session->changes().peerUpdated(chat, UpdateFlag::Admins);
|
||||
}
|
||||
|
||||
void ApplyChatUpdate(
|
||||
not_null<ChatData*> chat,
|
||||
const MTPDupdateChatDefaultBannedRights &update) {
|
||||
if (chat->applyUpdateVersion(update.vversion().v)
|
||||
!= ChatData::UpdateStatus::Good) {
|
||||
return;
|
||||
}
|
||||
chat->setDefaultRestrictions(ChatRestrictionsInfo(
|
||||
update.vdefault_banned_rights()).flags);
|
||||
}
|
||||
|
||||
void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
|
||||
ApplyChatUpdate(chat, update.vparticipants());
|
||||
|
||||
if (const auto call = update.vcall()) {
|
||||
chat->setGroupCall(*call);
|
||||
} else {
|
||||
chat->clearGroupCall();
|
||||
}
|
||||
if (const auto as = update.vgroupcall_default_join_as()) {
|
||||
chat->setGroupCallDefaultJoinAs(peerFromMTP(*as));
|
||||
} else {
|
||||
chat->setGroupCallDefaultJoinAs(0);
|
||||
}
|
||||
|
||||
chat->setMessagesTTL(update.vttl_period().value_or_empty());
|
||||
if (const auto info = update.vbot_info()) {
|
||||
auto &&commands = ranges::views::all(
|
||||
info->v
|
||||
) | ranges::views::transform(Data::BotCommandsFromTL);
|
||||
chat->setBotCommands(std::move(commands) | ranges::to_vector);
|
||||
} else {
|
||||
chat->setBotCommands({});
|
||||
}
|
||||
using Flag = ChatDataFlag;
|
||||
const auto mask = Flag::CanSetUsername;
|
||||
chat->setFlags((chat->flags() & ~mask)
|
||||
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag()));
|
||||
if (const auto photo = update.vchat_photo()) {
|
||||
chat->setUserpicPhoto(*photo);
|
||||
} else {
|
||||
chat->setUserpicPhoto(MTP_photoEmpty(MTP_long(0)));
|
||||
}
|
||||
if (const auto invite = update.vexported_invite()) {
|
||||
chat->session().api().inviteLinks().setMyPermanent(chat, *invite);
|
||||
} else {
|
||||
chat->session().api().inviteLinks().clearMyPermanent(chat);
|
||||
}
|
||||
if (const auto pinned = update.vpinned_msg_id()) {
|
||||
SetTopPinnedMessageId(chat, pinned->v);
|
||||
}
|
||||
chat->checkFolder(update.vfolder_id().value_or_empty());
|
||||
chat->setThemeToken(qs(update.vtheme_emoticon().value_or_empty()));
|
||||
chat->setTranslationDisabled(update.is_translations_disabled());
|
||||
const auto reactionsLimit = update.vreactions_limit().value_or_empty();
|
||||
if (const auto allowed = update.vavailable_reactions()) {
|
||||
const auto paidEnabled = false;
|
||||
auto parsed = Data::Parse(*allowed, reactionsLimit, paidEnabled);
|
||||
chat->setAllowedReactions(std::move(parsed));
|
||||
} else {
|
||||
chat->setAllowedReactions({ .maxCount = reactionsLimit });
|
||||
}
|
||||
chat->fullUpdated();
|
||||
chat->setAbout(qs(update.vabout()));
|
||||
chat->setPendingRequestsCount(
|
||||
update.vrequests_pending().value_or_empty(),
|
||||
update.vrecent_requesters().value_or_empty());
|
||||
|
||||
chat->owner().notifySettings().apply(chat, update.vnotify_settings());
|
||||
}
|
||||
|
||||
void ApplyChatUpdate(
|
||||
not_null<ChatData*> chat,
|
||||
const MTPChatParticipants &participants) {
|
||||
const auto session = &chat->session();
|
||||
participants.match([&](const MTPDchatParticipantsForbidden &data) {
|
||||
if (const auto self = data.vself_participant()) {
|
||||
// self->
|
||||
}
|
||||
chat->count = -1;
|
||||
chat->invalidateParticipants();
|
||||
}, [&](const MTPDchatParticipants &data) {
|
||||
const auto status = chat->applyUpdateVersion(data.vversion().v);
|
||||
if (status == ChatData::UpdateStatus::TooOld) {
|
||||
return;
|
||||
}
|
||||
// Even if we skipped some updates, we got current participants
|
||||
// and we've requested peer from API to have current rights.
|
||||
chat->setVersion(data.vversion().v);
|
||||
|
||||
const auto &list = data.vparticipants().v;
|
||||
chat->count = list.size();
|
||||
chat->participants.clear();
|
||||
chat->invitedByMe.clear();
|
||||
chat->admins.clear();
|
||||
chat->setAdminRights(ChatAdminRights());
|
||||
const auto selfUserId = session->userId();
|
||||
for (const auto &participant : list) {
|
||||
const auto userId = participant.match([&](const auto &data) {
|
||||
return data.vuser_id().v;
|
||||
});
|
||||
const auto user = chat->owner().userLoaded(userId);
|
||||
if (!user) {
|
||||
chat->invalidateParticipants();
|
||||
break;
|
||||
}
|
||||
|
||||
chat->participants.emplace(user);
|
||||
|
||||
const auto inviterId = participant.match([&](
|
||||
const MTPDchatParticipantCreator &data) {
|
||||
return UserId(0);
|
||||
}, [&](const auto &data) {
|
||||
return UserId(data.vinviter_id());
|
||||
});
|
||||
if (inviterId == selfUserId) {
|
||||
chat->invitedByMe.insert(user);
|
||||
}
|
||||
|
||||
participant.match([&](const MTPDchatParticipantCreator &data) {
|
||||
chat->creator = userId;
|
||||
}, [&](const MTPDchatParticipantAdmin &data) {
|
||||
chat->admins.emplace(user);
|
||||
if (user->isSelf()) {
|
||||
chat->setAdminRights(
|
||||
chat->defaultAdminRights(user).flags);
|
||||
}
|
||||
}, [](const MTPDchatParticipant &) {
|
||||
});
|
||||
}
|
||||
if (chat->participants.empty()) {
|
||||
return;
|
||||
}
|
||||
if (const auto history = chat->owner().historyLoaded(chat)) {
|
||||
if (history->lastKeyboardFrom) {
|
||||
const auto i = ranges::find(
|
||||
chat->participants,
|
||||
history->lastKeyboardFrom,
|
||||
&UserData::id);
|
||||
if (i == end(chat->participants)) {
|
||||
history->clearLastKeyboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
chat->refreshBotStatus();
|
||||
session->changes().peerUpdated(
|
||||
chat,
|
||||
UpdateFlag::Members | UpdateFlag::Admins);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
Reference in New Issue
Block a user